fix(responses_adapters): map OpenAI Responses cache tokens on /v1/messages - #35138
fix(responses_adapters): map OpenAI Responses cache tokens on /v1/messages#35138devin-ai-integration[bot] wants to merge 13 commits into
Conversation
chore(ci): promote internal staging to main
chore(ci): promote internal staging to main
chore(ci): promote internal staging to main
chore(ci): promote internal staging to main
_await_model_servable used poll_timeout (120s), the spend/log read-back budget. A stuck model reload therefore stalled every suite that creates a deployment for two minutes before failing Give create_model a fixed harness middle ground: model_servable_timeout=40s, polled every 2s, with each /v1/models call capped at 5s and clamped to the remaining deadline so one slow GET cannot overrun the wait. Happy path still returns on the first listing. Not derived from proxy general_settings or env Transport.get accepts an optional per-call timeout for that clamp. Unit tests cover the deadline arithmetic and clamp without a live proxy (cherry picked from commit c082a0e)
create_model returned after the first /v1/models hit that listed the model, so chat could still land on a cold gateway worker (numWorkers>1 / peer pod) and 400 Invalid model name. Require continuous listing for the product default add_deployment interval (30s) after first sight so every worker has synced from the DB; first listing still bounded at 40s (cherry picked from commit 7d1ee2f)
Keep the create_model DB-sync wait in the harness; the pure-function unit file is not needed for this PR (cherry picked from commit 8920465)
When less than one full poll interval remained in the first-listing budget, the pre-sleep check returned NotServable without another /v1/models call. Sleep only min(interval, time left) so a model that becomes listable in the last seconds of the timeout still gets a clamped final poll (cherry picked from commit 8439195)
A poll may start with remaining budget and still return after started+timeout if the transport overruns its clamp. Recheck the first-listing deadline after the response so a late listing does not open the continuous DB-sync phase (cherry picked from commit 7ff2bcb)
…ble_timeout test(e2e): bound the post-/model/new servable wait at 40s
* fix(mcp): resolve call_tool by registry without requiring tool map Multi-worker reloads put MCP servers in the registry from the DB but do not re-run tools/list on every process. Gating call_tool on tool_name_to_mcp_server_name_mapping made cold workers 500 with Tool not found after another worker had already listed the tool. Treat a registry match on server id/name/alias as enough; upstream rejects unknown tools * test(e2e): poll MCP register, tools/list, and tools/call across multi-worker lag Stage multi-worker gateways only load MCP servers and tool maps on the process that handled the request. Poll until the server is listed, the tool appears on tools/list, and tools/call is not a cold-worker 500 so key-access and Datadog MCP e2e stop racing the LB * Revert "fix(mcp): resolve call_tool by registry without requiring tool map" This reverts commit 8b56e51. * test(e2e): tighten MCP multi-worker lag classifier Only retry tools/call on gateway shapes Tool <name> not found and server_not_found, not any 500 that mentions tool/server not found, so upstream failures are not retried until the poll deadline * test(e2e): drop unit file for MCP lag classifier The live await_call_tool polls already cover multi-worker lag; a separate string-match unit module is not worth keeping (cherry picked from commit c274cf3)
test(e2e): poll MCP tools across multi-worker lag (#35047)
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
|
| # then require continuous listing for MODEL_SERVABLE_DB_SYNC_SECONDS (the default | ||
| # reload interval) so every worker has had a chance to sync from the DB. | ||
| MODEL_SERVABLE_TIMEOUT = 40.0 | ||
| MODEL_SERVABLE_DB_SYNC_SECONDS = 30.0 |
There was a problem hiding this comment.
Avoid unconditional stabilization delay
Every create_model call now waits through a fixed 30-second observation window after the model first appears, including single-worker deployments where the first successful listing already establishes readiness. Tests that create several models therefore accumulate minutes of unnecessary setup time and can exhaust CI job budgets; apply this stabilization period only to multi-worker topologies.
Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Greptile SummaryMaps OpenAI Responses cache token details into Anthropic Messages usage
Confidence Score: 4/5The PR appears safe to merge, though the unconditional model stabilization delay should be narrowed to multi-worker E2E deployments Cache-token mapping is consistently applied across streaming and non-streaming adapters with focused regression coverage; the remaining concern is non-blocking E2E runtime inflation from the fixed 30-second wait Files Needing Attention: tests/e2e/proxy_client.py
|
| Filename | Overview |
|---|---|
| litellm/llms/anthropic/experimental_pass_through/responses_adapters/streaming_iterator.py | Adds cache-token extraction and uses it when producing streaming Anthropic usage |
| litellm/llms/anthropic/experimental_pass_through/responses_adapters/transformation.py | Adds cache-read and cache-creation fields to non-streaming Anthropic usage |
| tests/e2e/proxy_client.py | Adds stronger model propagation polling, but its unconditional 30-second stabilization window substantially slows all model creation |
| tests/e2e/mcp/mcp_client.py | Adds targeted retries for recognized multi-worker MCP registry misses |
| tests/e2e/transport.py | Supports per-request GET timeouts used by deadline-aware polling |
Reviews (1): Last reviewed commit: "fix(responses_adapters): map OpenAI Resp..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Superseded by #34957, which shipped the same mapping via shared transforms with native-name precedence. Verified live on staging today, so closing this PR |
TLDR
Problem this solves:
How it solves it:
input_tokens_detailsRelevant issues
Fixes #35127
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Screenshots / Proof of Fix
Live proxy backed by
openai/gpt-4oon/v1/messages, with a ~6000 token stable prefix so OpenAI caches it. A control call to/v1/responsesconfirms OpenAI actually cached the prefix (cached_tokens: 5888); the same warm prefix is then sent through/v1/messagesConfig
Control,
/v1/responses(proves OpenAI cached the prefix)Before the fix, code at
cad32fd9bc(source files reverted on the running proxy)After the fix, code at
df2ec624acType
🐛 Bug Fix
Changes
When
/v1/messagesroutes to an OpenAI (or Azure) model, the request goes through the Responses-API bridge and the result is translated back to Anthropic format. Both the streaming adapter (AnthropicResponsesStreamWrapper._process_event) and the non-streaming adapter (LiteLLMAnthropicToResponsesAPIAdapter.translate_response) read cache counts only from the Anthropic-native usage keyscache_read_input_tokens/cache_creation_input_tokens. An OpenAI Responses usage object never has those names, so cache reads were always reported as 0OpenAI reports the split under
input_tokens_details, ascached_tokensfor reads andcache_write_tokensfor writes.cache_write_tokensis a pydantic extra, so it survivesmodel_dump()but is missed by a fixedgetattrlist. The plain/responsespath already reads these correctly inlitellm/responses/utils.py; this brings the Messages bridge in lineThe extraction now lives in one helper,
_extract_cache_tokens, shared by both adapters. It prefers the Anthropic-native names when present (so Bedrock/Vertex on/v1/messagesis unaffected) and otherwise falls back toinput_tokens_details, handling both a pydantic model and a plain dict.input_tokensis left inclusive of cached tokens, matching what the/responsespath reportsPseudocode:
Final Attestation
Link to Devin session: https://app.devin.ai/sessions/b31da05dd8194af69c5601b6aa979e5f