Skip to content

feat(arize): emit prompt-caching tokens as OpenInference span attributes - #26568

Closed
restato wants to merge 5 commits into
BerriAI:litellm_internal_stagingfrom
restato:feat/otel-anthropic-cache-tokens
Closed

feat(arize): emit prompt-caching tokens as OpenInference span attributes#26568
restato wants to merge 5 commits into
BerriAI:litellm_internal_stagingfrom
restato:feat/otel-anthropic-cache-tokens

Conversation

@restato

@restato restato commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Restores cached_tokens emission that PR #24112 added but PR #26506 inadvertently removed during a refactor for raw OpenAI Pydantic usage support.

Adds cache_creation_tokens emission, which has never been wired up despite the OpenInference LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE constant being defined.

Pre-Submission checklist

  • I have added testing in the tests/test_litellm/ directory — 4 new tests in tests/test_litellm/integrations/arize/test_arize_utils.py
  • My PR passes all unit tests for the touched module (tests/test_litellm/integrations/arize/test_arize_utils.py → 8/8 passing)
  • My PR's scope is as isolated as possible — single change to _set_usage_outputs to emit two additional OpenInference attributes
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Type

🐛 Bug Fix

Changes

What

_set_usage_outputs now emits prompt-cache breakdown as OpenInference span attributes:

LiteLLM normalized field OpenInference attribute
usage.prompt_tokens_details.cached_tokens llm.token_count.prompt_details.cache_read
usage.prompt_tokens_details.cache_creation_tokens llm.token_count.prompt_details.cache_write

Why

LiteLLM normalizes provider-specific cache fields onto prompt_tokens_details in Usage.__init__:

  • Anthropic cache_read_input_tokensprompt_tokens_details.cached_tokens
  • DeepSeek prompt_cache_hit_tokensprompt_tokens_details.cached_tokens
  • OpenAI native prompt_tokens_details.cached_tokens → already there
  • Anthropic cache_creation_input_tokensprompt_tokens_details.cache_creation_tokens

Reading from prompt_tokens_details keeps the OTEL emitter provider-agnostic.

Without these attributes, observability backends (Langfuse, Arize Phoenix) cannot display the cache breakdown nor apply correct cost calculation — Anthropic prices cache reads at 0.1× and cache writes at 1.25× standard input.

History of regression

  1. PR fix(otel): use completion_tokens_details for Chat Completions API rea… #24112 — added cached_tokens emission ✅
  2. PR fix(arize): _set_usage_outputs handles raw OpenAI Pydantic CompletionUsage #26506 — refactored _set_usage_outputs to handle raw OpenAI CompletionUsage Pydantic objects, but the new function body dropped the cached_tokens block. Cache reads stopped reaching observability backends. ❌
  3. This PR — restores cached_tokens and adds cache_creation_tokens. ✅

Tests

tests/test_litellm/integrations/arize/test_arize_utils.py:

  • test_arize_set_attributes_anthropic_cache_tokens — both cache_read and cache_write set
  • test_arize_set_attributes_openai_cached_tokens — only cache_read set (no cache_write concept on OpenAI)
  • test_arize_set_attributes_deepseek_cache_hit_tokensprompt_cache_hit_tokens normalized → cache_read set
  • test_arize_set_attributes_no_cache_tokens_omits_attributes — neither attribute set when usage lacks cache fields

Lint

  • black --check passes on changed files
  • ruff check passes on _utils.py; pre-existing T201 print warning in TestArizeLogger (line 469) is unrelated to this PR
    ~

yuneng-berri and others added 5 commits April 23, 2026 17:55
* feat(openai): day-0 support for GPT-5.5 and GPT-5.5 Pro

Add pricing + capability entries for the new GPT-5.5 family launched by
OpenAI on 2026-04-24:

- gpt-5.5 / gpt-5.5-2026-04-23 (chat): $5/$30/$0.50 per 1M
  input/output/cached input
- gpt-5.5-pro / gpt-5.5-pro-2026-04-23 (responses-only): $60/$360/$6
  per 1M input/output/cached input

Other fees (long-context >272k, flex, batches, priority, cache
discounts) follow the same ratios as GPT-5.4, with context window
retained at 1.05M input / 128K output.

No transformation / classifier code changes are required:
OpenAIGPT5Config.is_model_gpt_5_4_plus_model() already matches 5.5+ via
numeric version parsing, and model registration is driven from the
JSON. The existing responses-API bridge for tools + reasoning_effort
(litellm/main.py:970) already covers gpt-5.5-pro.

Tests:
- GPT5_MODELS regression list now covers gpt-5.5-pro and dated variants
- New test_generic_cost_per_token_gpt55_pro cost-calc test
- Updated test_generic_cost_per_token_gpt55 for long-context fields

* fix(openai): mirror reasoning_effort flags onto gpt-5.5 dated variants

gpt-5.5-2026-04-23 and gpt-5.5-pro-2026-04-23 were missing the
supports_none_reasoning_effort, supports_xhigh_reasoning_effort, and
supports_minimal_reasoning_effort flags that their non-dated
counterparts define. Reasoning-effort routing in OpenAIGPT5Config is
fully capability-driven from these JSON flags — since an absent flag
is treated as False for opt-in levels (xhigh), users pinning to a
dated snapshot would silently lose xhigh support and diverge from the
base alias on logprobs + flexible temperature handling.

Copy the flags onto both dated variants so every dated snapshot
inherits the base model's reasoning-effort capability profile.

Adds a parametrized regression test that asserts
supports_{none,minimal,xhigh}_reasoning_effort parity between each
dated variant and its non-dated counterpart, preventing future drift
when new snapshots are added.
…s) (BerriAI#26361)

* feat(azure): add azure/gpt-5.5 + azure/gpt-5.5-pro entries (+ dated variants)

Azure variants of OpenAI's GPT-5.5 family. Microsoft has not yet
shipped GPT-5.5 on Azure OpenAI (latest GA on the Foundry models page
is GPT-5.4 as of 2026-04-24), but adding the entries day-0 mirrors the
established precedent for azure/gpt-5.4* (which were in the cost map
before the Azure rollout) so cost tracking and capability flags work
the moment customers deploy.

Schema follows the existing azure/gpt-5.4* shape:
- Same base/long-context pricing as openai/gpt-5.5*: $5/$30 chat,
  $60/$360 pro per 1M, with priority tier 2x base
- Azure variants drop the flex/batches keys (Azure has no flex tier)
  but keep priority pricing, matching gpt-5.4* precedent
- mode=chat for the thinking model, mode=responses for pro

reasoning_effort capability flags mirror the OpenAI variants exactly
since Azure proxies the same API contract: minimal rejection on both
chat and pro, low/none rejection on pro. Once BerriAI#26456 (which sets
supports_low_reasoning_effort + minimal=false on openai/gpt-5.5*)
lands, OpenAI and Azure flag profiles align.

Tests pin entry presence + pricing for all four Azure variants and
verify the live-API-derived reasoning_effort flags.

* test: register supports_low_reasoning_effort in cost-map JSON schema

azure/gpt-5.5-pro and azure/gpt-5.5-pro-2026-04-23 added in this branch
carry supports_low_reasoning_effort=false. The strict
'additionalProperties: false' schema in
test_aaamodel_prices_and_context_window_json_is_valid rejected the new
key. Register it alongside the other supports_*_reasoning_effort
entries.

Note: the runtime side of this flag (code that reads it) lands in
BerriAI#26456. Until that PR merges the flag is inert for both Azure and
OpenAI pro entries, but having the schema accept it lets cost-map
tests pass on either merge order.
`_set_usage_outputs` emits only total / completion / prompt / reasoning
tokens, leaving `LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ` and
`LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE` defined-but-unused. As a
result, observability backends (Langfuse, Arize Phoenix) cannot display
the prompt-cache breakdown nor apply correct cost calculation for
Anthropic / Bedrock prompt caching (cache reads at 0.1x, cache writes
at 1.25x of standard input).

Read from `usage.prompt_tokens_details` so the change is provider-agnostic.
LiteLLM's `Usage.__init__` already normalizes provider-specific cache
fields onto this object:

- Anthropic `cache_read_input_tokens` -> prompt_tokens_details.cached_tokens
- DeepSeek `prompt_cache_hit_tokens`  -> prompt_tokens_details.cached_tokens
- OpenAI native cached_tokens -> prompt_tokens_details.cached_tokens
- Anthropic `cache_creation_input_tokens` -> prompt_tokens_details.cache_creation_tokens

Mapping:
- prompt_tokens_details.cached_tokens
    -> llm.token_count.prompt_details.cache_read
- prompt_tokens_details.cache_creation_tokens
    -> llm.token_count.prompt_details.cache_write

Note: PR BerriAI#24112 introduced a similar `cached_tokens` block in
litellm_oss_staging_03_21_2026 but has not yet landed on main; PR BerriAI#26506
on litellm-oss-staging-04-25-2026 refactored the function without
including it. This change brings the emission directly to main with
extended cache_creation coverage.

Tests:
- Anthropic: both cache_read and cache_write emitted
- OpenAI: only cache_read emitted (no cache_write concept)
- DeepSeek: prompt_cache_hit_tokens normalized -> cache_read emitted
- No-cache: neither attribute emitted
@greptile-apps

greptile-apps Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR restores the cached_tokens OpenInference span attribute that was accidentally dropped in PR #26506 and adds the previously-unwired cache_creation_tokens attribute, both emitted from _set_usage_outputs in the Arize integration. The fix reads from the provider-agnostic prompt_tokens_details field that LiteLLM already normalizes across Anthropic, DeepSeek, and OpenAI, and is covered by four new mock-only tests.

Confidence Score: 4/5

Safe to merge; the change is a small, isolated additive fix with good test coverage and no breaking changes.

Only a single P2 style finding (truthy guard vs. is not None for zero token counts). No logic bugs, no security concerns, no backwards-incompatible changes. Score capped at 4 due to the P2 finding per scoring guidelines.

No files require special attention.

Important Files Changed

Filename Overview
litellm/integrations/arize/_utils.py Adds prompt-cache token emission (cache_read / cache_write) to _set_usage_outputs; dual dict/object access pattern is correct; only nit is truthy-check silencing explicit zeros.
tests/test_litellm/integrations/arize/test_arize_utils.py Adds 4 well-scoped mock-only tests covering Anthropic, OpenAI, DeepSeek, and no-cache paths; no real network calls; assertions are precise and correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[_set_usage_outputs] --> B{usage present?}
    B -- No --> Z[return]
    B -- Yes --> C[emit total / completion / prompt tokens]
    C --> D[emit reasoning_tokens if present]
    D --> E[get prompt_tokens_details]
    E --> F{dict-like? hasattr get}
    F -- Yes --> G[dict .get cached_tokens / cache_creation_tokens]
    F -- No --> H[getattr cached_tokens / cache_creation_tokens]
    G --> I{cached_tokens truthy?}
    H --> I
    I -- Yes --> J[set LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ]
    I -- No --> K{cache_creation_tokens truthy?}
    J --> K
    K -- Yes --> L[set LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE]
    K -- No --> M[done]
    L --> M
Loading

Reviews (1): Last reviewed commit: "feat(arize): emit prompt-caching tokens ..." | Re-trigger Greptile

Comment thread litellm/integrations/arize/_utils.py
@codecov

codecov Bot commented Apr 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@restato

restato commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

@ryan-crabbe-berri Please review.

@oss-pr-review-agent-shin

Copy link
Copy Markdown
Contributor

🤖 litellm-agent: This PR was marked BLOCKED 7 days ago with no subsequent activity. Closing automatically.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants