Skip to content

proxy: an unrecognised usage dialect is indistinguishable from no usage, and degraded silently for 4,015 requests #200

Description

@amiddavid

The defect

parseUsage returning ok=false means one of three different things, and nothing distinguishes them:

  1. the provider genuinely reported no usage (an OpenAI response without stream_options, for instance);
  2. the response carried usage in a spelling the parser does not recognise;
  3. usage was present but every tier was zero.

Only the first is benign. The second is a measurement outage, and it degrades silently: a healthy 200, correct savings counters, correct latency, correct everything else — and fresh_input_tokens, cache_read_tokens, cache_write_tokens quietly at 0.

It has already happened, at full scale, for two iterations

Iteration 024 recorded usage_reported=false on 4,015 of 4,015 requests (1,808 arm A, 2,207 arm B, zero exceptions), with all four token fields at 0, every request provider=anthropic route=/v1/messages sse=False buffered=False.

Nothing surfaced it. It was found in a post-mortem two iterations later, while trying to answer a different question — and only because someone went looking for why a cost figure could not be bounded.

The usage was there. LOCA reported Avg Cost: $2.390637 per arm-seed, and litellm computes cost from the response's usage, so the body that flowed gateway → proxy → harness did carry it. The proxy passed it through and parseUsage found nothing in it.

Why a counter, and why this specific split

The parser handles three spellings and silently fails on others. Measured directly over candidate shapes:

Shape parseUsage
Anthropic usage.input_tokens parses
OpenAI usage.prompt_tokens parses
Output-only usage.output_tokens parses
Bedrock Converse camelCase usage.inputTokens finds nothing
usage nested (e.g. response.usage) finds nothing
usage absent correctly nothing
usage present, all tiers zero correctly nothing

So an unrecognised dialect is indistinguishable from a provider that said nothing — which is the same failure the codebase already refuses to accept in two other places:

This is the third instance of that pattern, and the argument is identical.

Suggested fix

  1. A counterusage_unparsed at /stats and cg_usage_unparsed_total at /metrics: a response arrived, it was not empty, and no recognised usage spelling was found in it. Non-zero means the proxy is not accounting tokens for some route or provider, which is currently invisible.
  2. A DEBUG record of the shape on the first such response per process — the top-level keys and the keys under usage, not the body (it carries content). That is the datum that would turn "usage_reported is false" into "the provider is sending camelCase" without needing a body dump at all.
  3. Optionally distinguish case 3 (present but all-zero) in the log line, since it is legitimate on some responses and would otherwise inflate the counter.

Deliberately not part of this: adding the camelCase or nested dialects. That needs a real response body to get the field names right — guessing between cacheWriteInputTokens and cacheCreationInputTokens produces a parser that looks correct and reads zero, which is the failure this issue is about, reproduced in the code meant to fix it. The counter and the shape record are dialect-agnostic and are what make the next gap visible in one run rather than two iterations.

How it was found

Tracing why iteration 024 had no cache-token measurement, with the coref validation session — which also made the point that a counter here would have surfaced this in iteration 022 rather than in iteration 024's post-mortem. Related: #199 (needs these fields live to bound its cost question), #190 (needs them to check whether refusals suppress the recovery).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions