Skip to content

Cache cost calculator is chosen by provider but should be chosen by usage shape #7773

Description

@Anuj7411

Problem

CostService selects the cache-aware cost calculator by provider via PROVIDERS_CACHE_COST_CALCULATOR. Most OpenAI-compatible providers (openai, azure, xai, deepseek, fireworks_ai, moonshot) are wired to SpanCostCalculator.textGenerationWithCacheCostOpenAI, which reads cached tokens from exactly one shape:

original_usage.prompt_tokens_details.cached_tokens   // (or the bare-OTel prompt_tokens_details.cached_tokens)

The problem is that the calculator is chosen by provider, but the field the calculator must read is determined by the usage shape the span actually carries, and those two are not always aligned. A provider assigned the OpenAI calculator that emits cache tokens in the Anthropic shape (cache_read_input_tokens / cache_creation_input_tokens) or a bare-OTel cache_read.input_tokens shape will have those tokens invisible to the calculator, so cached input is billed at the full input rate instead of the configured cache-read rate. The mismatch is silent: cost is simply too high, with no error.

This coupling is fragile as provider coverage grows, since a provider's SDK/instrumentation can change the usage shape independently of its canonical name.

Proposal

Route the cache calculator by the usage keys actually present rather than (or in addition to) the provider name: if the span carries prompt_tokens_details.cached_tokens use the OpenAI reader; if it carries cache_read_input_tokens/cache_creation_input_tokens use the Anthropic reader; etc. A small shape-detection helper keyed on which cache fields are populated would make cache pricing correct regardless of which provider emitted the span, and would remove the need to hand-maintain a per-provider calculator map for every new cache-priced provider.

Context

Surfaced while registering additional providers; Baz raised the same cache-shape concern on earlier provider PRs (snowflake / deepinfra). Filing as a standalone robustness improvement rather than folding it into a registration PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions