chore(release): backport #27319, #27756, #27921, #28395, #29983, #29984, #29986, #30160, #30327 to stable/1.84.x and cut 1.84.8 - #30332
Conversation
* fix anthropic streaming reasoning token usage Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com> * test anthropic streaming reasoning usage end to end Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com> * address anthropic reasoning token text split Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com> * harden anthropic reasoning usage for mocked tokens Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com> --------- Co-authored-by: oss-agent-shin <279349115+oss-agent-shin@users.noreply.github.com> Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com> (cherry picked from commit c15718f)
* fix(proxy): resolve cache handling issues in _lookup_deprecated_key - Updated the in-memory cache for deprecated key lookups to store a 3-tuple (active_token_id, cache_expires_at_ts, revoke_at_ts) instead of a 2-tuple, ensuring proper unpacking and backward compatibility. - Removed duplicate cache reads and added logic to handle legacy cache entries gracefully. - Enhanced unit tests to cover scenarios for cache hits, DB misses, and respect for revoke_at timestamps, ensuring robust handling of the grace-period key-rotation feature. * refactor(proxy): streamline cache handling in _lookup_deprecated_key - Simplified the cache retrieval logic by directly unpacking the 3-tuple cache entries, removing the need for backward compatibility checks for 2-tuple entries. - Updated unit tests to ensure that pre-warmed 3-tuple cache entries are served correctly without unnecessary database lookups. * chore(ci): add new unit test for deprecated key grace period - Included `test_deprecated_key_grace_period.py` in the CI workflow to enhance coverage for deprecated key handling scenarios. * fix(proxy): remove unnecessary check for revoke_at in _lookup_deprecated_key - Eliminated the redundant check for None on revoke_at, streamlining the logic for handling deprecated keys in the cache. This change enhances the efficiency of the key lookup process. * test(proxy): add end-to-end tests for deprecated key lookup behavior - Introduced a new test class `TestDeprecatedKeyLookupDbE2E` to validate the behavior of deprecated key lookups against a real Prisma-backed database. - The test ensures that old key hashes resolve correctly and that repeated lookups utilize the in-memory cache without errors. - Cleaned up the `_lookup_deprecated_key` function by removing an unnecessary check for `revoke_at`, enhancing the efficiency of the key lookup process. (cherry picked from commit 8f25942)
…27921) * fix(router): use forwarded model_id for native Azure container IDs in _init_containers_api_endpoints Azure code-interpreter containers return provider-native IDs (cntr_ + hex) that carry no LiteLLM routing payload, so _decode_container_id returns model_id=None. The router was falling through to call the handler directly, bypassing _ageneric_api_call_with_fallbacks and leaving api_base=None for Azure deployments. Fall back to the model_id forwarded from the proxy ownership check so deployment credentials are always applied. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(azure-containers): strip /openai/responses path from api_base in AzureContainerConfig.get_complete_url When a deployment's api_base is the responses endpoint URL (e.g. .../openai/responses?api-version=...), AzureContainerConfig was appending /openai/containers on top of it, producing the broken path .../openai/responses/openai/containers. Azure returns 404 for that URL while the correct path is .../openai/containers. Strip any /openai/responses suffix from api_base before constructing the containers URL so the resource root is always used as the starting point. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(azure-containers): prefer api-version from api_base URL over deployment's api_version The deployment's api_version (e.g. 2024-08-01-preview) targets the chat/responses API and is too old for the containers API, which requires 2025-04-01-preview. The responses endpoint api_base already carries the correct api-version in its query string. Extract it and use it for the containers URL, overriding the stale deployment-level version. Fixes DELETE and file-upload operations returning 404 due to wrong api-version. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(containers): pass params=None instead of params={} to httpx to preserve api-version httpx erases a URL's query-string when params={} (empty dict) is passed, silently stripping ?api-version=2025-04-01-preview from every container POST/DELETE request. Azure's GET endpoints tolerate a missing api-version; POST (upload) and DELETE are strict, so those returned 404. Fix: use `params or None` in container_handler._async_handle and llm_http_handler.async_container_delete_handler (and all sibling container handlers) so that an empty params dict falls back to None, leaving httpx to preserve the URL's existing query string intact. Adds a regression test that directly documents the httpx behaviour. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(router): remove elif model_id branch from _init_containers_api_endpoints Two reviewer findings addressed: 1. Truncated comment on the model_id fallback line — now complete. 2. Security: the elif branch that fired when container_id was absent allowed any authenticated caller to supply model_id in a POST /v1/containers body and route the request through an arbitrary deployment UUID, bypassing the model-level access checks that only validate `model`. Removed the elif branch; operations without container_id (create, list) route by the caller-supplied `model` field as before. model_id forwarding is kept only inside the container_id block, where the proxy ownership check has already validated the container before forwarding the deployment ID. Adds a regression test pinning the security boundary: no-container-id path calls original_function directly even when model_id is in kwargs. Co-authored-by: Cursor <cursoragent@cursor.com> * test(containers): validate proxy-to-router model_id forwarding for managed IDs Add test_regression_get_container_forwarding_params_sets_model_id_for_managed_id to verify that get_container_forwarding_params (the proxy-side half of the Azure routing fix) correctly extracts and forwards model_id from a LiteLLM-managed encoded container ID. This closes the gap identified by Greptile P1: the previous regression test only injected model_id as a direct kwarg, validating the router in isolation. The new test exercises the actual proxy-to-router data flow through ownership.get_container_forwarding_params, confirming that kwargs["model_id"] is populated before _init_containers_api_endpoints is reached. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(azure-containers): tighten endpoint-path strip to endswith match Use path.endswith() instead of path.find() for _AZURE_ENDPOINT_PATHS so the suffix strip only fires when api_base actually ends with one of the endpoint-specific path suffixes. This is the more precise check greptile flagged on the original find()-based implementation. * Fix sync container handler to preserve URL query string Mirror the async path fix: pass None instead of an empty params dict so httpx does not strip the URL's existing query string (e.g. ?api-version=...), which is required for Azure container routing. Co-authored-by: Yassin Kortam <yassin@berri.ai> * fix(azure-containers): strip trailing slash before endpoint suffix match Co-authored-by: Yassin Kortam <yassin@berri.ai> * fix(containers): recover model_id from stored encoded id for native Azure container IDs get_container_forwarding_params previously only set model_id when the user-supplied container_id was a LiteLLM-managed encoded id. For native upstream IDs (e.g. Azure 'cntr_<hex>') the decode fails and model_id was never forwarded — making the router-side fallback in _init_containers_api_endpoints unreachable in production. Fall back to the stored 'unified_object_id' on the ownership row, which is the encoded form captured at create time when the router selected a specific deployment. Decoding that yields the deployment model_id and restores router-based credential application (api_base, api_key) for retrieve/delete and container-file operations on native IDs. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude <claude@anthropic.com> Co-authored-by: Yassin Kortam <yassin@berri.ai> (cherry picked from commit 7f563b2)
…28395) * fix(proxy): expose Prisma idle/connect timeout + extra DB URL params Operators have reported large numbers of idle Prisma connections that never get closed. The proxy already forwards `connection_limit` and `pool_timeout` to the DATABASE_URL, but had no knob for capping idle or slow connections. Add three new `general_settings` keys that thread through to the DATABASE_URL / DIRECT_URL query string: - `database_connect_timeout` -> Prisma `connect_timeout` - `database_socket_timeout` -> Prisma `socket_timeout` (the main knob for closing idle connections from the LiteLLM side) - `database_extra_connection_params` -> untyped passthrough dict for any other Prisma URL param (`pgbouncer`, `statement_cache_size`, `sslmode`, ...); keys here override LiteLLM defaults. Refactors the duplicated DATABASE_URL/DIRECT_URL param dicts into a single `_build_db_connection_url_params` helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Update litellm/proxy/proxy_cli.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: Yassin Kortam <yassinkortam@g.ucla.edu> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> (cherry picked from commit 2f9ac77)
Three test-only adaptations required by the picks, none touching production code: restore the AsyncMock import that the upstream test_proxy_cli.py carries; drop three patch() entries targeting seed_request_identity, a helper that does not exist on this line; and re-graft the #30160 test block verbatim from the upstream hunk so the @patch decorators are included
The picked #29986 builder tests rely on 'from fastapi import status' and patch seed_request_identity, both present upstream but not on this line; add the import and drop the patch entries (the helper does not exist here so there is nothing to neutralize)
|
|
…combined view (#30327) The grace-period branch assigned the recursive get_data result (a finished LiteLLM_VerificationTokenView) back into the variable that the combined-view dict normalization then subscripts, raising TypeError on every request made with a rotated key inside its grace window; auth surfaced that as a 401. Return the recursive result directly instead. Regression test drives the full get_data flow: old hash misses the view, deprecated table resolves to the active token, and the call must return the view object (cherry picked from commit 5047eaf)
Greptile SummaryNine cherry-picks from staging onto
Confidence Score: 4/5The backport is safe to merge; the most sensitive paths (auth error classification, cached-plan recovery, grace-period key lookup) are all covered by dedicated mock tests and the logic checks out. The set of changes is well-scoped and each fix is independently testable. The only nit is a cosmetic double No files require special attention beyond the minor style note in
|
| Filename | Overview |
|---|---|
| litellm/proxy/utils.py | Fixes the 2-tuple/3-tuple cache bug in _lookup_deprecated_key (revoke_at now stored and checked), removes duplicate cache read, and replaces the unsafe DEALLOCATE-then-retry cached-plan recovery with a clean reconnect-then-retry approach. |
| litellm/proxy/auth/auth_exception_handler.py | Adds a 503 path for infrastructure-level DB failures during auth so valid keys are never rejected as 401 during a DB outage; control flow is correct. |
| litellm/proxy/db/exception_handler.py | Adds is_database_service_unavailable_error and is_prisma_engine_internal_error to classify DB infrastructure failures as 503-worthy rather than 401 auth failures. |
| litellm/proxy/proxy_cli.py | Introduces _build_db_connection_url_params; adds four new DB URL config knobs with backward-compatible fallback. |
| litellm/router.py | Falls back to proxy-forwarded model_id for native upstream container IDs; minor double .strip() call. |
| litellm/proxy/container_endpoints/ownership.py | Adds _CONTAINER_STORED_ID_CACHE and _get_stored_container_id for model_id recovery without extra cold-path DB hits. |
| litellm/llms/anthropic/chat/handler.py | Accumulates streamed thinking blocks so _handle_usage can correctly split reasoning vs text tokens. |
| litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py | Adds model fallback resolution and SSE pipeline hardening ([DONE] sentinels, non-JSON frames). |
| litellm/llms/azure/containers/transformation.py | Adds _normalize_api_base and _extract_api_version helpers for correct containers URL construction. |
| litellm/llms/custom_httpx/container_handler.py | Converts empty query_params dict to None to prevent httpx stripping the existing query string. |
Reviews (1): Last reviewed commit: "test: align adapted auth builder tests w..." | Re-trigger Greptile
| model_id = decoded.get("model_id") or ( | ||
| _forwarded_model_id.strip() | ||
| if isinstance(_forwarded_model_id, str) and _forwarded_model_id.strip() | ||
| else None | ||
| ) |
There was a problem hiding this comment.
.strip() is called twice on _forwarded_model_id — once in the guard condition and again in the value expression. Assigning the stripped value to a local variable eliminates the redundant call.
| model_id = decoded.get("model_id") or ( | |
| _forwarded_model_id.strip() | |
| if isinstance(_forwarded_model_id, str) and _forwarded_model_id.strip() | |
| else None | |
| ) | |
| _stripped_model_id = ( | |
| _forwarded_model_id.strip() | |
| if isinstance(_forwarded_model_id, str) | |
| else "" | |
| ) | |
| model_id = decoded.get("model_id") or (_stripped_model_id or None) |
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 SummaryNine cherry-picks from
Confidence Score: 4/5Safe to merge; all production changes are correct adaptations of staging fixes with comprehensive test coverage and zero new failures on this line's test suite. The auth DB-infra error classification relies on traceback frame inspection to catch a specific prisma-client-py AttributeError, which is a narrow detection mechanism that would silently regress if the upstream library renames its engine subpackage. Additionally, _get_stored_container_id introduces a direct Prisma table query in the container request path rather than going through a helper function, widening the surface of direct-query call sites. Both are quality/maintainability concerns rather than immediate correctness failures; all other changes are straightforward and well-tested. litellm/proxy/db/exception_handler.py (traceback-based prisma engine detection) and litellm/proxy/container_endpoints/ownership.py (direct DB query in request path).
|
| Filename | Overview |
|---|---|
| litellm/proxy/utils.py | Grace-period key rotation: removes duplicate cache read, fixes 2-tuple→3-tuple cache mismatch that caused ValueError on second auth with a rotated key, drops the select clause so revoke_at is populated. |
| litellm/proxy/db/exception_handler.py | Adds is_database_service_unavailable_error and is_prisma_engine_internal_error; the traceback-walk approach in is_prisma_engine_internal_error is brittle to prisma module renames but solves a real edge case with good test coverage. |
| litellm/proxy/auth/auth_exception_handler.py | Adds 503 response for DB-infra failures during auth (instead of 401), correctly placed after the ProxyException re-raise so only raw non-Proxy exceptions reach the new branch. |
| litellm/llms/anthropic/chat/handler.py | Accumulates streaming thinking-block deltas in reasoning_content_chunks so _handle_usage can split reasoning vs text tokens correctly at stream end. |
| litellm/llms/anthropic/chat/transformation.py | Caps estimated_reasoning_tokens at actual completion_tokens with min(), preventing text_tokens from going negative when the token-counter estimate overshoots. |
| litellm/proxy/proxy_cli.py | Refactors DB URL param construction into _build_db_connection_url_params; adds connect_timeout, socket_timeout, pgbouncer, and extra_params knobs; backward compatible (all new fields are opt-in). |
| litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py | Adds _resolve_costing_model to fall back to deployment/model_group when model is 'unknown', adds _extract_model_from_anthropic_chunks to recover model from SSE stream, and hardens the SSE pipeline against [DONE] sentinels and non-JSON frames. |
| litellm/proxy/container_endpoints/ownership.py | Adds _CONTAINER_STORED_ID_CACHE to recover model_id from the encoded ID stored at create time for native upstream container IDs; get_container_forwarding_params becomes async to support the DB lookup; makes a direct Prisma query bypassing helper functions. |
| litellm/router.py | Falls back to the proxy-forwarded model_id when a container_id carries no LiteLLM routing payload (native Azure hex IDs), so deployment credentials are applied correctly. |
| litellm/proxy/_types.py | Adds four new optional ConfigGeneralSettings fields: database_connect_timeout, database_socket_timeout, database_extra_connection_params, database_disable_prepared_statements — all None-defaulted, backward compatible. |
Reviews (2): Last reviewed commit: "test: align adapted auth builder tests w..." | Re-trigger Greptile
| def is_prisma_engine_internal_error(e: Exception) -> bool: | ||
| """True iff ``e`` is a non-``PrismaError`` exception raised from inside | ||
| prisma-client-py's query-engine layer. | ||
|
|
||
| During the instant a DB connection is torn down, the query engine can | ||
| return a malformed error payload (``user_facing_error.meta`` is | ||
| ``null``). prisma-client-py's ``handle_response_errors`` then crashes | ||
| with ``AttributeError: 'NoneType' object has no attribute 'get'`` | ||
| before it can raise the proper P1001 "can't reach database server" | ||
| error. That AttributeError carries no connection keyword, so it can't | ||
| be matched by message; identify it by its ``prisma.engine`` origin | ||
| instead. | ||
|
|
||
| Recognized ``PrismaError`` subclasses are excluded: connectivity ones | ||
| are already classified by type/keyword above, and data-layer ones | ||
| (the DB IS reachable) must stay 401. | ||
| """ | ||
| import prisma | ||
|
|
||
| if isinstance(e, prisma.errors.PrismaError): | ||
| return False | ||
| tb = getattr(e, "__traceback__", None) | ||
| while tb is not None: | ||
| if tb.tb_frame.f_globals.get("__name__", "").startswith("prisma.engine"): | ||
| return True | ||
| tb = tb.tb_next | ||
| return False | ||
|
|
||
| @staticmethod | ||
| def is_database_service_unavailable_error(e: Exception) -> bool: | ||
| """True iff the exception means the database could not answer at the | ||
| infrastructure level (connection refused, socket/interface failure, | ||
| timeout) rather than a genuine auth failure (key not found) or a |
There was a problem hiding this comment.
Traceback inspection is brittle to prisma module renames
is_prisma_engine_internal_error classifies an AttributeError as a DB-infra failure by walking e.__traceback__ and checking whether any frame's __name__ starts with "prisma.engine". This works today, but if prisma-client-py ever renames its engine subpackage (e.g. to prisma._engine, prisma_client.engine, or anything that doesn't share this prefix) the check silently returns False, and the same AttributeError that caused the auth-outage regression would start returning 401 again.
Consider recording a recognizable sentinel on the exception at the raise site inside the prisma shim, or pinning a prisma version range in pyproject.toml alongside a comment that this detection depends on the current naming convention.
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!
| return owner | ||
|
|
||
|
|
||
| async def _get_stored_container_id( | ||
| original_container_id: str, custom_llm_provider: str | ||
| ) -> Optional[str]: | ||
| """Return the ``unified_object_id`` stored at create time, if any. | ||
|
|
||
| Used by :func:`get_container_forwarding_params` to recover the | ||
| deployment ``model_id`` for native upstream container IDs: the stored | ||
| value is the encoded form produced by ``encode_container_id_in_response`` | ||
| when the router selected a specific deployment. | ||
| """ | ||
| model_object_id = _container_model_object_id( | ||
| original_container_id, custom_llm_provider | ||
| ) | ||
|
|
||
| cached = _CONTAINER_STORED_ID_CACHE.get_cache(model_object_id) | ||
| if cached == _NEGATIVE_STORED_ID_SENTINEL: | ||
| return None | ||
| if isinstance(cached, str) and cached: | ||
| return cached | ||
|
|
||
| prisma_client = await _get_prisma_client() | ||
| if prisma_client is None: | ||
| return None | ||
|
|
||
| row = await prisma_client.db.litellm_managedobjecttable.find_first( | ||
| where={ | ||
| "model_object_id": model_object_id, | ||
| "file_purpose": CONTAINER_OBJECT_PURPOSE, | ||
| } | ||
| ) | ||
| stored_id = getattr(row, "unified_object_id", None) if row is not None else None | ||
| _CONTAINER_STORED_ID_CACHE.set_cache( | ||
| model_object_id, | ||
| ( | ||
| stored_id | ||
| if isinstance(stored_id, str) and stored_id | ||
| else _NEGATIVE_STORED_ID_SENTINEL | ||
| ), | ||
| ) | ||
| return stored_id if isinstance(stored_id, str) and stored_id else None | ||
|
|
||
|
|
||
| async def assert_user_can_access_container( |
There was a problem hiding this comment.
Direct Prisma query in request path bypasses helper functions
_get_stored_container_id issues a litellm_managedobjecttable.find_first query directly against prisma_client.db, mirroring the existing pattern in _get_container_owner. The codebase rule requires all DB queries in the request path to go through the get_team/get_user/get_key object helper functions. The container path isn't the auth hot-path, and the call is cache-guarded, so the practical impact is low — but it widens the set of direct-query call sites that need to be updated if the DB access pattern changes (e.g., read-replica splitting, query logging).
Rule Used: What: In critical path of request, there should be... (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!
Relevant issues
Backport of seven staging fixes plus one prerequisite onto
stable/1.84.x, cutting1.84.8. Covers streaming Anthropic reasoning token usage, grace-period key rotation, native Azure container id routing, cached-plan recovery, DB-infra auth error semantics, and Anthropic pass-through cost resolution.Grace-period rotation needed two changes: #27756 fixes the deprecated-key lookup itself, and #30327 (found while verifying this backport, fixed on staging first) completes the end-to-end flow. Both are included, so 1.84.8 carries the complete rotation fix.
What is included
Picks, in staging merge order, each with a
(cherry picked from commit ...)footer:Plus two test-only adaptation commits, the version bump to 1.84.8, and the uv.lock refresh.
Adaptation notes
Every production source file in every pick is patch-id identical to its staging counterpart, with one verified exception class:
litellm/proxy/_experimental/out/...jsbuild artifacttests/test_litellm/proxy/utils/prisma_and_spend/, a directory this line does not have; the PR's six test functions plus the fixture subset they need were grafted verbatim from the staging filesproxy_cli.pyresolved to the staging post-image; the end state of both regions is byte-identical to staging after fix(proxy): expose Prisma idle/connect timeout + extra DB URL params #28395ui/litellm-dashboard/src/lib/http/schema.d.ts, which does not exist on this lineAsyncMock,fastapi.status) and removepatch()entries that targetseed_request_identity, a helper that does not exist on this lineKnown noise on this line
Two pre-existing failures on the
stable/1.84.xtip before any pick, unchanged after the picks:tests/test_litellm/proxy/test_proxy_utils.py::test_get_custom_urlandtests/test_litellm/containers/test_azure_container_transformation.py::TestAzureContainerConfig::test_validate_environment_uses_azure_env_var(reads a real env var over the monkeypatched one in this environment).Screenshots / Proof of Fix
Targeted test delta on this line: baseline 419 passed / 2 failed; after picks 520 passed / 2 failed; zero new failures, both failures are the pre-existing noise above.
Live proxy (this branch, port 4001), streaming Anthropic with
reasoning_effortandinclude_usage:Anthropic pass-through on the generic route still costs correctly after the picks (no regression on the working path):
The unknown-model costing path that produced $0 spend logs needs a router-passthrough deployment shape that does not register on this line's dev setup; behavior there is pinned by the six tests #30160 brings, which pass on this branch.
Grace-period rotation, end-to-end on a live proxy running this branch (key generated, regenerated with
grace_period: "1h", then the OLD key used):An adversarial multi-agent audit of the pick set (diff fidelity vs staging, symbol resolution, pick-test execution, and drifted-caller analysis) returned SURVIVED on all four sub-claims with zero verified refutations, fingerprinted at this branch's HEAD.
Type
🐛 Bug Fix
Changes
Ten cherry-picks onto stable/1.84.x as listed above, two test-only adaptation commits, version bump 1.84.7 -> 1.84.8, uv.lock refresh