[Infra] Promote Internal Staging to main - #27559
Conversation
…logging. _PROXY_VirtualKeyModelMaxBudgetLimiter subclasses RouterBudgetLimiting but does not run its __init__, so the periodic Redis sync task never starts and spend stayed in memory. Push the increment pipeline when Redis is configured so multi-worker enforcement and cache keys stay consistent. Co-authored-by: Cursor <cursoragent@cursor.com>
Assert _push_in_memory_increments_to_redis runs after async_log_success_event when dual_cache.redis_cache is set, and is skipped when Redis is not configured. Co-authored-by: Cursor <cursoragent@cursor.com>
Mirrors the system-message skip in PR #25481 for tool-role messages. Adds a global litellm.skip_tool_message_in_guardrail flag and a per-guardrail litellm_params.skip_tool_message_in_guardrail override, applied in the OpenAI and Anthropic chat translation handlers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a tri-state control (inherit / yes / no) when creating or editing guardrails so admins can set litellm_params.skip_tool_message_in_guardrail without YAML, mirroring the existing skip_system_message control. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.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>
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>
* feat(sso): show full IdP claims in /sso/debug/callback The debug callback only displayed the proxy-parsed OpenID summary, so customers couldn't verify what custom claims (team_id, team_alias, roles, etc.) the IdP was actually returning. Render two new sections — Raw Claims (userinfo) and Access Token Claims (decoded JWT) — alongside the existing parsed view. Strip bearer tokens defense-in-depth in case a non-conforming IdP places them in its userinfo response. Resolves LIT-2838 * Update litellm/proxy/management_endpoints/ui_sso.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix(sso): hoist json.dumps out of f-string for py3.10 ruff --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Operators upgrading past 35bbca6 (which made /metrics auth default-on) see "Malformed API Key passed in. Ensure Key has 'Bearer ' prefix." with no hint that litellm_settings.require_auth_for_metrics_endpoint: false restores the previous unauthenticated behavior. Append that discovery hint to the existing 401 body so a Prometheus scraper that breaks after upgrade has a clear migration path. No behavior change.
…eContent [Feat] Honor OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT
fix(proxy): point /metrics 401 at the opt-out flag
…o remaining headroom reserve_budget_for_request fell back to reserving the entire remaining team/key/user headroom whenever a request omitted max_tokens, which pinned the spend counter at max_budget for the duration of the in-flight request and false-positive-blocked every concurrent or back-to-back request until the success callback reconciled. Surfaced as an integration-test team being budget-blocked at its $2000 cap while DB spend was $0.144. Switch the missing-max_tokens path to a fixed default of 16384 output tokens (mirrors parallel_request_limiter_v3's DEFAULT_MAX_TOKENS_ESTIMATE precedent), and clamp explicit max_tokens at the model's max_output_tokens for reservation accounting only. The outbound request body is unchanged, so providers see whatever the caller actually sent; only the local integer used to compute reservation cost is bounded. This also prevents a hostile max_tokens=999999999 from inflating one request's reservation up to the entire team headroom. For Opus 4.7 (output $25/M, max_output 128K) on a $2000 budget the worst-case per-request reservation drops from "everything left" to $3.20, raising admittable concurrency from 1 to ~625.
…PLICA (#27493) - Introduce RoutingPrismaWrapper that transparently routes read operations (find_*, count, group_by, query_raw, query_first) to a reader endpoint while writes remain on the writer, enabling Aurora-style reader/writer endpoint splits - Add IAMEndpoint dataclass and parse_iam_endpoint_from_url() to capture static connection fields from a reader URL so only the IAM token needs to rotate, avoiding the need for separate DATABASE_HOST_READ_REPLICA/etc. env vars - Enhance PrismaWrapper with per-instance knobs (db_url_env_var, iam_endpoint, recreate_uses_datasource, log_prefix) so writer and reader wrappers are independent: the reader writes its fresh URL to DATABASE_URL_READ_REPLICA and passes datasource override to Prisma since Prisma only auto-reads DATABASE_URL - Fix deadlock in PrismaWrapper.__getattr__: when called from inside a running event loop, schedule the token refresh as a background task instead of blocking with run_coroutine_threadsafe + future.result(), which would deadlock the loop thread waiting for a coroutine that needs the loop to run - Fix botocore crash when DATABASE_PORT is unset by defaulting to "5432" in both proxy_cli.py and PrismaWrapper.get_rds_iam_token(); passing None caused botocore to embed the literal string "None" in the presigned URL - Implement graceful reader degradation: reader connect/recreate failures are non-fatal; wrapper sets _reader_unavailable=True and silently routes reads to the writer to keep the proxy serving traffic during transient reader outages - Add PrismaClient.writer_db property so the reconnect smoke-test always validates the writer engine specifically; query_raw on the routing wrapper would route to the reader and not verify the newly-recreated writer - Expose DATABASE_URL_READ_REPLICA in Helm chart (values.yaml + deployment.yaml) via both plain value and secret key reference, and document the field in docker-compose.yml - Add 887-line test suite covering routing logic, IAM token refresh paths, reader degradation scenarios, datasource override behavior, and the deadlock regression Co-authored-by: Yassin Kortam <yassinkortam@g.ucla.edu>
…itellm_/elegant-franklin-038d44
Image-generation routes (dall-e-3, flux, etc.) have no per-token output cost so they fell through to the no-reservation read-time-only path. Concurrent image requests against a depleted budget could all pass common_checks (counter exactly at max_budget passes the strict-`>` gate) and reach the provider before reconciliation caught up. Add per-image reservation in _estimate_request_max_cost_for_model: when the model has a per-image cost field, reserve `n × cost_per_image` upfront. The atomic counter increment serializes concurrent admissions, so the second request sees the post-first-reservation counter and raises BudgetExceededError instead of silently leaking through. Both `output_cost_per_image` and `input_cost_per_image` are honored — naming is inconsistent across providers (OpenAI dall-e-3 uses input_cost_per_image, aiml/dall-e-3 uses output_cost_per_image for the same per-generated-image price). Per-pixel pricing (DALL-E 2 size variants) and TTS/STT routes still fall through to read-time enforcement; those are follow-ups.
The previous detection treated any model with input_cost_per_image
or output_cost_per_image as image generation. Several chat and
embedding models carry those fields to price multimodal vision input,
not generated images:
- gemini-3.1-pro-preview (mode=chat) has output_cost_per_image=0.00012
alongside input/output token pricing.
- azure/gpt-realtime-* (mode=chat) has input_cost_per_image=5e-6.
- amazon.titan-embed-image-v1 (mode=embedding) has
input_cost_per_image=6e-5.
For these models the image-gen branch fired first and reserved a
fraction of a cent per request, short-circuiting the token-priced
path entirely. Long Gemini chats reserved 1 × $0.00012 instead of
the true token cost.
Gate strictly on mode in {"image_generation", "image_edit"}. All 197
real image_generation entries and all 31 image_edit entries
(Flux Kontext, Stability inpaint/outpaint, etc.) carry the right mode,
so the field-presence fallback was unnecessary.
Adds regression tests for the chat-model-with-image-cost-field case
and for image_edit reservation.
…27218) The Create New Key dialog had a "Default" key-type option that was misleading: it was not the actual default selection (AI APIs is), and it grants access to all routes — broader than the description implied. Rename the option to "Full Access" with an accurate description, and reorder the dropdown to AI APIs → Management → Full Access. Switch the inner labels to antd Typography for consistency with the rest of the UI. UI-only change; the underlying enum value ("default") is unchanged so the API contract is preserved.
…edis_flush fix(proxy): flush virtual-key model_max budget spend to Redis after success logging
fix(realtime): add /openai/v1/realtime to routes for logging
The antd mock omits Typography, which caused all 15 tests in create_key_button.test.tsx to fail with "No 'Typography' export is defined on the 'antd' mock" after #27218 switched the key-type dropdown labels to Typography.Text / Typography.Paragraph. Add Typography (with .Text, .Paragraph, .Title subcomponents) to the mock so the dropdown renders in the test environment.
fix(proxy): bound budget reservation per request instead of pinning to headroom
…abae fix(ui-tests): add Typography to antd mock in create_key_button test
* fix(mcp): forward extra_headers for OpenAPI MCP tools OpenAPI-generated tools only applied static closure headers and BYOK Authorization via ContextVar. Copy MCPServer.extra_headers from the incoming MCP request into _request_extra_headers (set in server.py before local tool dispatch), merge in openapi_to_mcp_generator via a small helper. OAuth2 M2M: do not forward caller Authorization from raw_headers (same rule as _prepare_mcp_server_headers for managed MCP). Adds TestRequestExtraHeaders and clarifies mcp_server_manager registration comment. Fixes #26794 Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(mcp): access has_client_credentials on MCPServer directly Greptile: getattr default was redundant; property exists on MCPServer and mcp_server is non-None inside the extra_headers forwarding block. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
…guardrails feat(guardrails): optional skip tool message in unified guardrail inputs
Our `uv.lock` already resolves jinja2 to 3.1.6, so Docker / CI installs get that version. The `pyproject.toml` floor was lagging at 3.1.0, which means downstream consumers using `--resolution=lowest-direct` or older constraint files can land on 3.1.0-3.1.5 instead of the version we actually test against. Aligns the declared floor with the resolved version so external installers see the same baseline our test matrix exercises. `uv lock` diff is metadata-only (no resolved-version drift).
…7541) - Remove litellm-js/proxy and litellm-js/spend-logs TypeScript packages that provided Cloudflare Worker proxy and Node.js spend logging services, as these are no longer maintained - Remove deprecated Docker variants (Dockerfile.alpine, Dockerfile.dev, Dockerfile.custom_ui, Dockerfile.health_check, Dockerfile.ghcr_base) that have been superseded by the primary Dockerfile - Remove legacy Kubernetes manifests (kub.yaml, service.yaml) from deploy/kubernetes in favor of the Helm chart - Remove stale index.yaml Helm chart index pinned to an old version (v1.43.18) - Remove dev_config.yaml development configuration file that contained hardcoded credentials and example endpoints - Clean up ~3,500 lines of unused code and configuration to reduce repository maintenance burden Co-authored-by: Yassin Kortam <yassinkortam@g.ucla.edu>
build(packaging): raise jinja2 floor to 3.1.6
* Fix proxy auth status code tests Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com> * Update user model access status expectation 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>
|
Michael Riad Zaky seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis is a nightly staging-to-main promotion covering several independent features: read-replica routing for Prisma via
Confidence Score: 3/5The read-replica routing and OTel changes look solid, but the budget-reservation rework removes the concurrent-admission guard for uncapped requests and replaces the tests that covered that race path, leaving the behaviour unverified in CI. The removal of _get_smallest_remaining_budget eliminates the live-counter budget pre-check for requests that omit max_tokens. The two tests that validated that concurrent-admission path were renamed and replaced with tests for entirely different scenarios, so the race-condition guard is now untested. The HTTP status-code changes are semantically correct but will silently change behaviour for any operator client that branches on the numeric code. litellm/proxy/spend_tracking/budget_reservation.py and its test file warrant the closest look. The auth status-code changes span auth_checks.py, user_api_key_auth.py, and auth_exception_handler.py.
|
| Filename | Overview |
|---|---|
| litellm/proxy/spend_tracking/budget_reservation.py | Removed _get_smallest_remaining_budget and replaced uncapped-output estimation with DEFAULT_MAX_OUTPUT_TOKENS_FALLBACK (16384). Image-generation cost estimation added. Race-condition guard for uncapped requests is no longer verified by tests. |
| tests/test_litellm/proxy/test_budget_reservation.py | Several race-condition tests renamed and replaced with tests for different scenarios (adversarial max_tokens, image-gen budget). The removed tests verified concurrent-admission guard behavior that is now untested. |
| litellm/proxy/db/routing_prisma_wrapper.py | New file implementing read-replica routing for Prisma. Writer-first fallback on reader failure is well handled; _RoutedActions lazily consults _should_use_reader() so mid-call reader degradation is picked up correctly. |
| litellm/proxy/db/prisma_client.py | Added IAMEndpoint dataclass and reader-wrapper support. DATABASE_PORT now defaults to '5432' fixing a botocore presigned-URL bug. Writer/reader PrismaWrapper construction refactored cleanly. |
| litellm/proxy/utils.py | PrismaClient now wires DATABASE_URL_READ_REPLICA into a RoutingPrismaWrapper; smoke-test after reconnect targets writer_db directly so the SELECT 1 validates the writer engine specifically. |
| litellm/proxy/auth/auth_checks.py | HTTP 401→403 for model-access denials; semantically correct but potentially breaking for existing clients. |
| litellm/proxy/auth/user_api_key_auth.py | Expired-key error code changed from 400 to HTTP 401; correct semantics, but backwards-incompatible for clients checking the numeric code. |
| litellm/proxy/auth/auth_exception_handler.py | BudgetExceededError now returns 429 (or the exception's own status_code) instead of hardcoded 400; semantically correct. |
| litellm/integrations/opentelemetry.py | Adds structured capture-mode enum (NO_CONTENT/SPAN_ONLY/EVENT_ONLY/SPAN_AND_EVENT) sampled at init; resolves before every request. Backwards-compatible with legacy message_logging bool. |
| litellm/proxy/_experimental/mcp_server/openapi_to_mcp_generator.py | Adds _request_extra_headers ContextVar and _merge_openapi_tool_request_headers() to forward allowlisted caller headers to OpenAPI tool upstreams. Precedence rules prevent callers from overriding static operator headers. |
| litellm/proxy/_experimental/mcp_server/server.py | Populates _request_extra_headers ContextVar from MCPServer.extra_headers allowlist; OAuth2 M2M path correctly skips forwarding caller's Authorization header. |
| litellm/proxy/management_endpoints/ui_sso.py | Debug SSO callback now surfaces raw_claims and access_token_claims alongside parsed result; token fields are scrubbed from both before rendering to prevent bearer-token leakage into HTML. |
| litellm/proxy/hooks/model_max_budget_limiter.py | After recording spend, flushes in-memory increments to Redis when a Redis cache is configured, preventing stale in-memory counters from lagging behind other replicas. |
| ui/litellm-dashboard/src/components/networking.tsx | teamInfoCall now URL-encodes the teamID parameter, preventing injection via special characters in team IDs. |
| litellm/proxy/_types.py | Adds /openai/v1/realtime routes to LiteLLMRoutes and /health/test_connection to non-admin routes. |
Comments Outside Diff (1)
-
tests/test_litellm/proxy/test_budget_reservation.py, line 609-660 (link)Race-condition tests replaced without equivalent coverage
Two tests that validated race-condition safety were removed and not re-added:
test_should_shrink_uncapped_reservation_when_counter_advances— verified that when a concurrent reservation advances the counter while a second reservation is in flight, the second reservation correctly shrinks to the remaining budget. The replacement (test_should_clamp_reservation_to_model_ceiling_when_caller_overrequests) tests a different scenario (adversarialmax_tokensclamping).test_should_shrink_uncapped_reservation_multiple_times— verified that multiple budget counters share the smallest remaining budget. This is gone entirely.
Removing
_get_smallest_remaining_budgeteliminated the budget pre-check that happened when nomax_tokenswas specified. Now, uncapped requests useDEFAULT_MAX_OUTPUT_TOKENS_FALLBACK × cost_per_tokenas the reservation estimate, which may exceed or fall below the actual remaining budget, leaving the race-condition guard unverified.Rule Used: What: Flag any modifications to existing tests and... (source)
Reviews (1): Last reviewed commit: "Fix proxy auth status code tests (#27555..." | Re-trigger Greptile
| object_type=object_type | ||
| ), | ||
| param="model", | ||
| code=status.HTTP_401_UNAUTHORIZED, | ||
| code=status.HTTP_403_FORBIDDEN, | ||
| ) |
There was a problem hiding this comment.
HTTP status code changes are backwards-incompatible without a flag
Three status codes changed in this PR: model access denied 401 → 403, expired-key errors 400 → 401, and budget-exceeded errors 400 → 429. Each change is semantically correct per RFC 9110, but any client that parses the numeric code to branch its retry or error-display logic will silently change behavior. Per the team's rule on backwards-incompatible changes, these should either be gated behind a user-controllable flag or the changelog should call them out prominently so operators know to update their error-handling code.
Rule Used: What: avoid backwards-incompatible changes without... (source)
| from litellm.proxy.db.log_db_metrics import log_db_metrics | ||
| from litellm.proxy.db.prisma_client import PrismaWrapper | ||
| from litellm.proxy.db.prisma_client import ( | ||
| PrismaWrapper, |
| from typing import Any, Callable, Optional | ||
|
|
||
| from litellm._logging import verbose_proxy_logger | ||
| from litellm.proxy.db.prisma_client import PrismaWrapper |
| from litellm.proxy.db.prisma_client import PrismaWrapper | ||
| from litellm.proxy.db.prisma_client import ( | ||
| PrismaWrapper, | ||
| parse_iam_endpoint_from_url, |
| PrismaWrapper, | ||
| parse_iam_endpoint_from_url, | ||
| ) | ||
| from litellm.proxy.db.routing_prisma_wrapper import RoutingPrismaWrapper |
| from litellm.proxy.auth.rds_iam_token import ( | ||
| generate_iam_auth_token, | ||
| ) |
| if not self.iam_token_db_auth: | ||
| return None | ||
|
|
||
| from litellm.proxy.auth.rds_iam_token import generate_iam_auth_token |
| ) | ||
| store_audit_logs = False # Enterprise feature, allow users to see audit logs | ||
| skip_system_message_in_guardrail: bool = False | ||
| skip_tool_message_in_guardrail: bool = False |
| # estimate_request_max_cost still returns None when the model is unknown | ||
| # to the cost map (no token-priced cost fields, e.g. image/audio routes). | ||
| # In that case we fall back to read-time enforcement only. | ||
| if reservation_cost is None or reservation_cost <= 0: |
There was a problem hiding this comment.
High: Budget reservation bypass for unpriced requests
Returning without a reservation for models/routes that cannot be priced lets a budgeted user start many concurrent image/audio/unknown-model requests: each request sees the same pre-call spend, skips the in-flight counter, and reaches the provider before post-call spend is recorded. Keep a conservative reservation for None estimates, or block budgeted requests whose maximum cost cannot be estimated.
There was a problem hiding this comment.
Pushing back - the fail close method is too restrictive for our use case.
| # the read replica when one is configured. | ||
| _MODEL_READ_METHODS = frozenset( | ||
| { | ||
| "find_first", |
There was a problem hiding this comment.
High: Stale auth and authorization reads on read replicas
Routing every find_* call to the replica includes reads of litellm_verificationtoken, users, teams, and model permissions used during request authentication. With replica lag, a user whose key or team/model access was just revoked can continue authenticating after cache invalidation because the auth path rehydrates from the stale replica; keep security-sensitive auth, authorization, and budget tables on the writer and only route non-admission reads to the replica.
There was a problem hiding this comment.
Pushing back - we do not support multi db setups, this is for AWS RDS where there are 2 endpoints with different IAM roles. There will be no replication lag introduced by us
Medium: Team admins can test unauthorized model credentialsThis PR adds Status: 1 new · 3 open |
…ted (#27488) Co-authored-by: Michael Riad Zaky <michaelr@Mac.localdomain>
| "/project/list", | ||
| "/project/info", | ||
| # Endpoint enforces proxy-admin vs team-admin model access itself. | ||
| "/health/test_connection", |
There was a problem hiding this comment.
Medium: Unauthorized model health checks
Allowing non-proxy-admin callers through to /health/test_connection lets a team admin set litellm_params.model to any configured deployment and set model_info.team_id to a team they administer. The endpoint then loads that deployment's configured credentials before can_user_make_model_call() runs, so the caller can trigger a provider health request and receive routing metadata for models outside their team; gate this route to proxy admins, or verify that the requested configured model actually belongs to the caller's team before merging and using its credentials.
There was a problem hiding this comment.
Pushing back - team admins are vetted and trusted people within the organization
[Infra] Promote Internal Staging to main
Promote
litellm_internal_staging→mainto cut a nightly release.Type
🚄 Infrastructure