chore(release): backport #30787, #30788, #31035, #31122, #31133 to stable/1.84.x and cut 1.84.10 - #31169
Conversation
…treams (#30788) A streaming request that breaks mid-flight, for example on a mid-stream read timeout, still bills the provider for the chunks already delivered, yet the proxy recorded that interrupted request as a zero-spend failure. An earlier revision logged the recovered partial usage through the success path, which mislabeled a failed request as a success and produced a misleading spend row This recovers the partial usage where the failure is actually logged. The streaming handler assembles the usage from the chunks seen so far and stashes it, with its cost, on the logging object before firing the failure handlers. The proxy failure hook lifts that usage and cost onto request_data before the non-serialisable logging object is popped, and the spend-log writer records the real partial spend on the failure row instead of a hardcoded zero; get_logging_payload honors the recovered usage for the token columns and _failure_handler_helper_fn preserves the recovered cost so the non-DB failure loggers stay consistent A request that recovers via a successful fallback is unaffected: the failure hook only fires when the whole request fails, so the fallback's combined-usage success row stays the single source of truth and there is no double counting Resolves LIT-3825 Co-authored-by: veria-ai[bot] <224490171+veria-ai[bot]@users.noreply.github.com> (cherry picked from commit 4847fa5)
…nthropic streams (#31035) Streaming and pass-through requests could be logged with $0 cost or dropped from SpendLogs entirely while the upstream provider still billed every token. This closes the leak paths not already covered by #30160, #30787 and #30788. - Catch a stream_chunk_builder raise in the core CustomStreamWrapper (sync and async). Large agentic tool-use / thinking streams can make assembly re-raise as APIError from inside the except-StopIteration handler, where the sibling except does not catch it, so it escaped __next__/__anext__ and dropped the request; recover best-effort usage from the raw chunks instead - Add a usage-only fallback for Anthropic streaming pass-through: when stream_chunk_builder returns None or raises, rebuild usage from the message_start / message_delta SSE events via AnthropicConfig.calculate_usage so cache, web-search and geo tokens are priced instead of left at $0 - Decode buffered pass-through bytes with errors="replace" so a stream cut mid-multibyte-sequence still logs the usage events already received - Record response_cost into model_call_details on the pass-through success path (it is read from there, not from kwargs), matching the gemini/cohere/openai handlers - Name the key (alias + masked key) in the virtual-key BudgetExceededError so operators don't have to reverse-map spend back to a key (cherry picked from commit b24b964)
Bumps the 12 packages osv-scanner flags on litellm_internal_staging, taking the scan from 24 known vulnerabilities to zero. vcrpy goes to 8.2.1 first so aiohttp can move to 3.14.1 (vcrpy <= 8.1.1 cannot import aiohttp 3.14), then the two aiohttp ignore entries are dropped from osv-scanner.toml. The langchain stack moves together since langchain 1.3.9 requires langgraph 1.2.x. Runtime deps cryptography (48.0.1), starlette (1.3.1), python-multipart (0.0.32), pydantic-settings (2.14.2) and pypdf (6.13.3) are bumped via relock, and the dashboard's js-yaml, ws and form-data overrides are bumped too. Also removes the paths filter on the OSV workflow so it runs on every PR rather than only when a lockfile changes, which is why it never showed up on recent code-only PRs (cherry picked from commit a8a1472)
…31133) Re-pins LITELLM_BUILD_IMAGE and LITELLM_RUNTIME_IMAGE across all 6 Dockerfiles from the prior digests (openssl 3.6.2-r3) to the current chainguard wolfi-base digest c61ac691 (openssl 3.6.3-r2, >= the fixed 3.6.3-r0). The runtime stage is the shipped image, so the runtime digest is what actually resolves the customer-facing CVE; the build image is bumped too for hygiene. Two Dockerfiles tracked a second equally-stale digest; both are unified onto the patched one. (cherry picked from commit fda08dd)
|
|
Greptile SummaryThis backport cherry-picks five already-merged staging changes onto
Confidence Score: 4/5Safe to merge; all changes are targeted bug fixes with no observable regressions in the test suite and no modifications to auth or permission logic other than an informational improvement to the budget error message. The cost-recovery chain threads through six files and coordinates state via The largest single file change is
|
| Filename | Overview |
|---|---|
| litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py | Adds three cooperative features: a tail-scan interrupted-stream detector (_stream_was_interrupted), a retokenizer that corrects the undercounted output_tokens placeholder on interrupted Anthropic streams, and a usage-only fallback (_build_usage_only_response_from_chunks) that recovers cost from raw SSE events when stream_chunk_builder fails on large agentic/tool-use streams. Also sets model_call_details["response_cost"] from the success path so the pass-through spend is recorded correctly. |
| litellm/litellm_core_utils/streaming_handler.py | Wraps the end-of-stream stream_chunk_builder call in a try/except in both sync and async iterators, recovering best-effort usage from raw chunks when assembly fails. Adds _record_partial_usage_for_failure called from the timeout/exception handlers to stash partial usage and cost on the logging object before the failure handler fires. |
| litellm/litellm_core_utils/litellm_logging.py | Failure-path response_cost initialization now skips zeroing when combined_usage_object is already set, preserving the recovered partial spend from an interrupted stream. |
| litellm/proxy/utils.py | In post_call_failure_hook, lifts combined_usage_object and response_cost from the logging object onto request_data before popping litellm_logging_obj, so downstream failure-path spend callbacks can record the real partial spend. |
| litellm/proxy/hooks/proxy_track_cost_callback.py | Failure hook now checks for combined_usage_object on request_data and passes through the recovered response_cost (floored at 0) to update_database instead of the hardcoded zero. |
| litellm/proxy/auth/auth_checks.py | Budget-exceeded error now names the key (key_alias + masked key_name) so operators can identify the exhausted key without reverse-mapping spend to a key. |
| litellm/proxy/spend_tracking/spend_tracking_utils.py | When normal usage extraction yields nothing (failed stream), falls back to combined_usage_object if it is a litellm.Usage instance so the failure row records the recovered partial token counts. |
| litellm/types/utils.py | Widens server_tool_use parameter in Usage.__init__ to accept dict and coerces it to ServerToolUse, enabling the usage-only fallback to pass a dict directly from the raw SSE event. |
| litellm/proxy/pass_through_endpoints/streaming_handler.py | Decodes accumulated bytes with errors="replace" so a stream cut mid-multibyte-sequence still produces decodable lines (including already-received usage events) rather than raising UnicodeDecodeError. |
| litellm/proxy/pass_through_endpoints/llm_provider_handlers/base_passthrough_logging_handler.py | Mirrors the model_call_details["response_cost"] assignment added to the Anthropic handler so the base pass-through success path also records spend in the correct dict key. |
| Dockerfile | Updates wolfi-base image digest to patch the OpenSSL CVE-2026-34182 in both build and runtime stages. |
| pyproject.toml | Bumps security-flagged runtime dependencies (cryptography 48.0.1, aiohttp 3.14.1, vcrpy 8.2.1, langchain 1.3.9) and loosens two pins (mlflow >=3.11.1, semantic-router >=0.1.15) to allow the lock to resolve; JS deps also bumped (js-yaml 4.2.0, ws 8.21.0). |
Reviews (1): Last reviewed commit: "chore: refresh uv.lock for 1.84.10" | Re-trigger Greptile
Relevant issues
Backports five already-merged
litellm_internal_stagingchanges ontostable/1.84.xand cuts1.84.10. Three are the interrupted/agentic Anthropic stream cost-recovery family (#30787, #30788, #31035): a stream that breaks mid-flight, or an agentic tool-use stream thatstream_chunk_buildercannot reassemble, was logged with undercounted or zero spend even though the provider billed every token; these recover realistic usage on the failure and pass-through paths and name the key in the virtual-key budget error. The other two are maintenance: a dependency relock that moves the runtime-facing packages off versions flagged by the upstream scanner (#31122) and a wolfi-base image digest refresh (#31133).1.84.9already shipped as a container image (ghcr.io/berriai/litellm:v1.84.9, built from thestable/1.84.xtipb860b2f64ff), so this PR bumps to1.84.10rather than re-cutting1.84.9.What is included
In staging merge order:
Every functional commit is a
git cherry-pick -xof its staging squash; the bump and the lock refresh are the only generated commits.Adaptation notes
stable/1.84.xpredates several intermediate staging changes, so three picks were adapted on the line; each adaptation keeps the pick's claimed behavior and was content-verified against staging.first_api_call_start_timelift came from an unrelated OTEL PR (fix(otel): cap metric attribute cardinality with include/exclude lists #30257) that is not on this line. Only fix(proxy): record partial spend on the failure row for interrupted streams #30788's own recovered-usage lift (combined_usage_object+response_cost) was taken; the OTEL lift was left out.server_tool_usedict and relies onUsage.__init__coercing it to aServerToolUse. That coercion is not on this line (its staging origin is a 7000-file rebase squash, not cleanly pickable), so the three-line coercion was folded into the pick, byte-identical to staging'sUsage.__init__. Without it two of fix(passthrough,streaming): recover cost on interrupted and agentic Anthropic streams #31035's own tests fail. The budget-error change was applied on the line's existingif spend >= valid_token.max_budget:guard; the staging context also carried a separate NaN-defense from a prior PR which was left out as out of scope.osv-scanner.tomlor OSV workflow, so those parts are no-ops here and were dropped. The dependency bumps were applied to the line's pyproject (cryptography 48.0.1, aiohttp 3.14.1, vcrpy 8.2.1, langchain 1.3.9, langgraph 1.2.x, js-yaml 4.2.0, ws 8.21.0) and both lockfiles were regenerated on the line. Two looser pins were needed for the relock to resolve and match staging:mlflowto>=3.11.1,<4.0(the exact==3.11.1pin capped cryptography below 47) andsemantic-routerto>=0.1.15,<1.0(the pinned 0.1.12 is a yanked release). Reaching staging's starlette / python-multipart / pydantic-settings / pypdf would require a fastapi major bump and was left out for scope; nothing regressed.Dockerfile,docker/Dockerfile.databaseanddocker/Dockerfile.non_rootexist on this line, so the digest was bumped in those; the line's older base digest and staging's both resolved to the same patched target. Thebackend/gateway/migrationsDockerfiles are not on this line.Known noise on this line
stable/1.84.xcarries five pre-existing targeted-test failures unrelated to these picks. They fail on the line before any backport and are not regressions:test_streaming_handler.py::test_gemini_legacy_vertex_stop_finish_reason_normalisedtest_streaming_handler.py::test_gemini_legacy_vertex_tool_calls_finish_reason_with_stop_enumtest_proxy_utils.py::test_get_custom_urltest_auth_checks.py::test_get_key_object_should_raise_if_reconnect_fails_on_db_connection_errortest_auth_checks.py::test_get_key_object_should_reconnect_once_on_db_connection_errorTargeted suite before the picks: 431 passed, 5 failed. After all picks and the relock: 463 passed, 5 failed. The added 32 passing tests are the picks' own regression tests; the failing set is identical to the baseline, so the picks add zero new failures.
Screenshots / Proof of Fix
Live proxy on
stable/1.84.xwith the picks applied, against real provider APIs.Budget error now names the key (#31035), same key exhausted before and after:
Interrupted Anthropic pass-through stream reproduced (the #30787 path): a
max_tokens: 4000stream cut by client disconnect at ~2.5s carries only themessage_startoutput_tokensplaceholder (3) with no terminalmessage_delta; the proxy returns 200 and the silent re-tokenization recovery is covered byTestInterruptedStreamOutputTokenRecovery.Sanity (unchanged from baseline):
/health/livelinessalive, a realgpt-4ocompletion succeeds, and a normal Anthropic pass-through stream logs its terminalmessage_deltausage.Relocked environment imports cleanly:
cryptography 48.0.1,aiohttp 3.14.1,litellm+ proxy import OK.Type
🐛 Bug Fix
Changes
Cost-recovery on interrupted and agentic Anthropic streams (#30787, #30788, #31035), a runtime dependency relock and a base-image digest refresh (#31122, #31133), and the
1.84.10version bump with regenerated lockfiles