feat(usage): normalize variant cache-token field names across upstreams - #109
Merged
Conversation
The OpenAI-shape usage block convention `prompt_tokens_details.cached_tokens` is dominant but not universal. Several "wild" OpenAI-compatible upstreams report cache hits under different field names — chief among them DeepSeek, which puts `prompt_cache_hit_tokens` + `prompt_cache_miss_tokens` at the usage root (and reports `prompt_tokens` as the sum), and Moonshot / Kimi, Cohere v2 native, and Qwen Singapore legacy region, which all use a flat top-level `cached_tokens` with no wrapper. Cache-write reads a parallel divergence: our Anthropic messages → chat-completions translation pair emits `cache_creation_input_tokens` under the wrapper, while OpenRouter emits `cache_write_tokens` instead when routing to Anthropic / explicit- Gemini / Alibaba. Until now the chat-completions and completions extractors each read only `prompt_tokens_details.cached_tokens` (and chat additionally `cache_creation_input_tokens`), so DeepSeek's cache activity was silently counted as bare input on the largest open-source-friendly cached upstream. `openAICacheTokensFromUsage` in shared/telemetry/usage.ts now resolves both cache-read and cache-write across the variant shapes in a single helper, with explicit precedence (OpenAI canonical → DeepSeek root → flat root; Anthropic-style cache_creation → OpenRouter cache_write). Both per-API extractors call it, replacing their direct field reads; the chat-completions extractor keeps its strict ChatCompletionsUsage parameter type, since the helper accepts `unknown` and narrows internally — the protocol type stays clean of non-standard fields and the variant sniff lives only at the extraction boundary. Fireworks remains an irreducible blind spot: it reports cache stats in HTTP response headers and a kebab-case `perf_metrics` sibling, not in `usage` at all. Provider-specific extraction would be needed; deferred.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The OpenAI-shape
usage.prompt_tokens_details.cached_tokensconvention is dominant but not universal among the "wild" OpenAI-compatible upstreams Floway routes to. Several providers emit cache-hit counts under different field names, and our extractors silently miscounted those rows as zero cache activity (the entire prompt landed on the bareinputdimension).A short cross-provider survey:
usage.prompt_tokens_details.cached_tokens(OpenAI canonical)usage.prompt_cache_hit_tokens+usage.prompt_cache_miss_tokens(root level,prompt_tokensis the sum)usage.cached_tokens(flat, no wrapper)fireworks-cached-prompt-tokens+perf_metrics["cached-prompt-tokens"](outsideusage)A parallel divergence for cache-write under the OpenAI wrapper:
prompt_tokens_details.cache_creation_input_tokensprompt_tokens_details.cache_write_tokensChange
openAICacheTokensFromUsage(usage: unknown): { cacheRead, cacheWrite }helper inshared/telemetry/usage.ts. It sniffs the variants in explicit precedence order:prompt_tokens_details.cached_tokens→prompt_cache_hit_tokens→ flatcached_tokensprompt_tokens_details.cache_creation_input_tokens→prompt_tokens_details.cache_write_tokenstokenUsageFromChatCompletionsUsage,tokenUsageFromCompletionsUsage) now call the helper instead of reading the fields directly. The chat extractor keeps its strictChatCompletionsUsageparameter type — the variant sniff lives at the extraction boundary and the protocol type stays clean of non-standard fields.null prompt_tokens_detailscase (Gemini cache miss), and that a realcached_tokens: 0is honored rather than falling through to the next candidate.Out of scope
perf_metricssibling, completely outside theusageblock. Capturing it needs provider-specific extraction at the transport layer, deferred to a separate change./v1/completionsbug (vllm-project/vllm#44961, fixed by #44383 onmain): pure upstream-side, no gateway code change can recover the omitted field. The Zhipu/GLM fork we observed predates that fix.Test plan
pnpm run typecheck— cleanpnpm run lint— cleanpnpm run test— 3364 / 3364 pass (was 3295 onmain; +16 new test cases acrossshared/telemetry/usage_test.ts,completions/usage_test.ts,chat-completions/usage_test.ts)input_cache_read > 0in the usage row