Skip to content

[Infra] Promote Internal Staging to main - #27559

Merged
ryan-crabbe-berri merged 32 commits into
mainfrom
litellm_internal_staging
May 9, 2026
Merged

[Infra] Promote Internal Staging to main#27559
ryan-crabbe-berri merged 32 commits into
mainfrom
litellm_internal_staging

Conversation

@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor

Promote litellm_internal_stagingmain to cut a nightly release.

Type

🚄 Infrastructure

Michael Riad Zaky and others added 30 commits May 6, 2026 13:13
…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>
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>
@CLAassistant

CLAassistant commented May 9, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
6 out of 11 committers have signed the CLA.

✅ milan-berri
✅ shivamrawat1
✅ SHARP155
✅ ryan-crabbe-berri
✅ yuneng-berri
✅ Michael-RZ-Berri
❌ Michael Riad Zaky
❌ cursoragent
❌ ishaan-berri
❌ oss-agent-shin
❌ yassin-berriai


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.

@codspeed-hq

codspeed-hq Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing litellm_internal_staging (b888177) with main (fa81017)

Open in CodSpeed

@codecov

codecov Bot commented May 9, 2026

Copy link
Copy Markdown

@greptile-apps

greptile-apps Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This is a nightly staging-to-main promotion covering several independent features: read-replica routing for Prisma via DATABASE_URL_READ_REPLICA, a structured OTel capture-mode enum replacing the message_logging boolean, skip_tool_message_in_guardrail support mirroring the existing system-message flag, budget-reservation rework with image-gen cost estimation and a fixed output-token fallback, HTTP status-code correctness fixes across auth, and MCP extra-header forwarding for OpenAPI tool upstreams.

  • Read-replica routing (routing_prisma_wrapper.py, utils.py, prisma_client.py): new RoutingPrismaWrapper routes reads to a reader endpoint and gracefully falls back to the writer on reader failure; IAM token refresh is wired for both endpoints independently.
  • Budget reservation overhaul (budget_reservation.py): removes _get_smallest_remaining_budget in favor of DEFAULT_MAX_OUTPUT_TOKENS_FALLBACK (16 384 tokens) when the caller omits max_tokens, adds image-generation cost estimation, and clamps adversarial max_tokens values at the model ceiling.
  • Auth status codes (auth_checks.py, user_api_key_auth.py, auth_exception_handler.py): model-access denials changed from 401 \u2192 403, expired-key from 400 \u2192 401, budget-exceeded from 400 \u2192 429.

Confidence Score: 3/5

The 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.

Important Files Changed

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)

  1. tests/test_litellm/proxy/test_budget_reservation.py, line 609-660 (link)

    P1 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 (adversarial max_tokens clamping).
    • 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_budget eliminated the budget pre-check that happened when no max_tokens was specified. Now, uncapped requests use DEFAULT_MAX_OUTPUT_TOKENS_FALLBACK × cost_per_token as 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

Comment on lines 2849 to 2853
object_type=object_type
),
param="model",
code=status.HTTP_401_UNAUTHORIZED,
code=status.HTTP_403_FORBIDDEN,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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)

Comment thread litellm/proxy/utils.py
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
Comment thread litellm/proxy/utils.py
from litellm.proxy.db.prisma_client import PrismaWrapper
from litellm.proxy.db.prisma_client import (
PrismaWrapper,
parse_iam_endpoint_from_url,
Comment thread litellm/proxy/utils.py
PrismaWrapper,
parse_iam_endpoint_from_url,
)
from litellm.proxy.db.routing_prisma_wrapper import RoutingPrismaWrapper
Comment thread litellm/proxy/utils.py
Comment on lines +2621 to +2623
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
Comment thread litellm/__init__.py
)
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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@veria-ai

veria-ai Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Medium: Team admins can test unauthorized model credentials

This PR adds /health/test_connection to the non-proxy-admin self-managed route set. The endpoint loads configured model credentials before checking authorization, then authorizes team admins based on caller-supplied model_info.team_id, so a team admin can run a health call against a proxy-configured model they do not otherwise have access to.


Status: 1 new · 3 open
Risk: 6/10

…ted (#27488)

Co-authored-by: Michael Riad Zaky <michaelr@Mac.localdomain>
Comment thread litellm/proxy/_types.py
"/project/list",
"/project/info",
# Endpoint enforces proxy-admin vs team-admin model access itself.
"/health/test_connection",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushing back - team admins are vetted and trusted people within the organization

@ryan-crabbe-berri
ryan-crabbe-berri merged commit e182a5e into main May 9, 2026
124 of 125 checks passed
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
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.