Skip to content

chore(ci): promote internal staging to main#31477

Merged
yuneng-berri merged 11 commits into
mainfrom
litellm_internal_staging
Jun 26, 2026
Merged

chore(ci): promote internal staging to main#31477
yuneng-berri merged 11 commits into
mainfrom
litellm_internal_staging

Conversation

@yuneng-berri

Copy link
Copy Markdown
Collaborator

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Type

🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test

Changes

yuneng-berri and others added 11 commits June 25, 2026 23:48
…=true test reaches rpm path

#29256 made auth=true pass-through routes deny-by-default unless the key/team
has allowed_passthrough_routes configured, but this integration test was not
updated. The test key had no allowlist, so the auth=true parametrizations
(rpm_limit=0 -> expect 429, rpm_limit=2 -> expect 207) now hit the 403 gate in
auth before reaching the rpm/forwarding logic they mean to exercise.

Grant the test key allowed_passthrough_routes for /api/public/ingestion so it
clears the gate. Also removes a latent order-dependency: the case only passed
locally when an earlier (auth=false) parametrization registered the route first;
under worker isolation (CI xdist) it failed with 403.
Adds the OCR 3 model (mistral-ocr-2512) released 2025-12-18 to both the
root and bundled backup cost maps at $2 / 1000 pages and $3 / 1000
annotated pages, mirroring the existing Mistral OCR entries. Regresses
the pricing in both maps and verifies completion_cost scales per page.
…ols and errors (adopted from #31286) (#31461)

* fix(guardrails): stop Generic Guardrail API 500 on built-in tools

Requests carrying built-in tools (code_interpreter, file_search, ...) crashed
the Generic Guardrail with a 500. GenericGuardrailAPIRequest.tools validated
each tool against ChatCompletionToolParam, whose base TypedDict requires a
function block, so a tool like {"type": "code_interpreter"} raised a Pydantic
ValidationError before the request was ever sent.

Type the field with a permissive GuardrailToolParam model (type required,
extra=allow) so built-in tools validate and their config is forwarded to the
guardrail intact instead of being stripped.

* feat(guardrails): add complete fail-open (fail_on_error) to Generic Guardrail

The Generic Guardrail already honored unreachable_fallback, which fails open
only on network-unreachable errors. This wires up the existing generic
fail_on_error config (so far implemented only by Model Armor) so that
fail_on_error=false degrades any guardrail error to a critical-log warning and
lets the request proceed as if the guardrail were absent.

Only a valid guardrail response can act: a parsed BLOCKED decision still raises,
while endpoint errors, malformed responses, and internal serialization or
validation errors all fall through when fail_on_error=false. To cover that last
class, the request construction now runs inside the protected block, so the kind
of validation error that previously surfaced as a 500 is caught here too.

Defaults to true (fail closed), matching today's behavior; turning it off is an
explicit availability-over-security choice and is logged at critical level on
every bypass.

* test(guardrails): cover fail_on_error on the response path

The existing fail_on_error tests all drive the request path. Add response-path
(input_type=response) coverage: an endpoint error proceeds unchanged under
fail_on_error=false, and a valid BLOCKED decision still raises. Guards against a
future regression that special-cases input_type in the error handling.

* style(guardrails): black-format the fail-open guard expression

CI runs black (line-length 88) over litellm/; the unreachable_fail_open
assignment exceeded it. Wrap it to satisfy the formatter.

* fix(guardrails): validate tools into GuardrailToolParam at the call site

Changing the request field to List[GuardrailToolParam] left the construction
passing List[ChatCompletionToolParam] (list is invariant), which tripped the
basedpyright reportArgumentType budget gate. Validate each tool explicitly,
which is what Pydantic did implicitly, so the types line up with no Any or
suppression and the serialized payload is unchanged.

* fix(guardrails): make fail-open log message accurate for non-network errors

The fail-open path is now shared by fail_on_error, so it fires for any guardrail
error, not just unreachability. The log said 'unreachable' even for an HTTP 400
or a malformed response; reword to 'error' (the status code and exception are
already logged). Addresses the Greptile review's only finding.

* fix(guardrails): align GenericGuardrailAPIResponse.tools with GuardrailToolParam

Greptile flagged that the request side moved to GuardrailToolParam but the
response side still annotated tools as List[ChatCompletionToolParam], which
mandates a function block and contradicts the new built-in-tools support.
Update the response annotation (and the now-unused import) so the two sides
agree. Runtime is unchanged; from_dict stores the raw dicts and the only
consumer assigns through to GenericGuardrailAPIInputs without inspecting
the elements.

---------

Co-authored-by: Itay Ovadia <[email protected]>
#31267 switched the build backend to maturin to bundle the Rust OCR
bridge into the litellm wheel. On the release runner, maturin builds
with `--compatibility off` and emits a `cp312-cp312-linux_x86_64`
wheel. PyPI only accepts `manylinux*` Linux wheels, so the upload is
rejected with `400 Bad Request - unsupported platform tag
'linux_x86_64'`, which blocked the 1.91.0.dev2 publish.

Revert `[build-system]` to the pure-Python `uv_build` backend and
restore `[tool.uv.build-backend]` so the release once again produces a
universal `litellm-<ver>-py3-none-any.whl` that installs on every
OS / arch / Python.

The Rust bridge stays optional: `litellm/rust_bridge/loader.py` already
falls back gracefully when `_native` is absent (`try/except
ImportError`), so runtime behavior is unchanged - the native module is
simply not bundled. Rust acceleration can ship as a separate
platform-specific package later.
test(pass-through): fix langfuse auth=true test broken by allowed_passthrough_routes gate
…#31443)

/team/member_add could not set budget_duration on an individual member
budget. add_new_member created the budget row with only max_budget and
allowed_models, and TeamMemberAddRequest had no budget_duration field, so
a member added with an explicit per-member budget while the team ran a
recurring member budget got a lifetime cap instead of a recurring
allowance.

Thread budget_duration from TeamMemberAddRequest through
_process_team_members into add_new_member, and pull the member-budget
resolution into a helper that writes budget_duration plus a computed
budget_reset_at. When only a budget_duration is supplied and the team has
a default member budget, the default is cloned and its reset window
overridden so the member keeps the default's max_budget rather than
becoming uncapped; a duration with no team default creates a window-only
budget. Invalid durations are rejected with a 400 before any DB write,
symmetric with /team/member_update.

The available-team self-join bypass only grants the ability to join, so
reject per-member budget and model controls (max_budget_in_team,
budget_duration, allowed_models) for non-admin self-join callers in
_validate_team_member_add_permissions, before any DB write. Otherwise a
self-joining non-admin could set their own cap, reset window, or model
scope past the team default; admins, team admins, and org admins are
unaffected and a clean self-join still inherits the team default budget.

Resolves LIT-4052
…te COUNT(*) (#31423)

The spend-logs UI list endpoint (/spend/logs/ui, /spend/logs/v2) ran a standalone
SELECT COUNT(*) before the page query to compute total_pages. On sharded engines
like YugabyteDB a COUNT(*) is a distributed RPC that contacts every tablet leader
and aggregates partial results regardless of row count, so it hits the distributed
RPC timeout and the logs tab 500s even on a one-minute window with a couple of rows.
The startTime range cannot prune tablets because rows hash to tablets on request_id,
not startTime.

Fold the count into the same scan as the page data with COUNT(*) OVER () and read
total off the returned rows, dropping the helper column before serialisation. One
distributed scan per page load instead of two; the response shape is unchanged. An
empty page carries no count row, in which case the total is zero.

Resolves LIT-4027
…ken (#31412)

When STS rejects a web identity token with InvalidIdentityToken (the
"Incorrect token audience" case), litellm propagated the raw botocore
error, which never names the aud LiteLLM actually sent. Diagnosing an
audience mismatch then required enabling LITELLM_LOG=DEBUG on the prod
instance, which degrades performance.

_auth_with_web_identity_token now catches InvalidIdentityTokenException,
decodes the public aud/iss claims of the resolved JWT without verifying
its signature (no secret is read), and raises an AwsAuthError that
preserves the STS reason and names the token audience and issuer, so the
mismatch is visible from the error alone.

Resolves LIT-4026
…cy (#31414)

litellm_guardrail_latency_seconds was only emitted for pre-call guardrails.
during_call_hook and post_call_success_hook ran guardrails without recording
any latency, so during-call and post-call guardrail time was invisible in the
metric and leaked into litellm_overhead_latency_metric, making the documented
"subtract guardrail latency from overhead" workaround under-report total
guardrail time.

Extract the find-the-PrometheusLogger-and-record step into _emit_guardrail_metrics
and add _run_guardrail_with_metrics, a single wrapper that times a guardrail
coroutine, classifies its outcome (success / intervened / error), enriches any
raised HTTPException, and records the latency under the given hook_type. Route
the pre-call emit, during_call_hook, and post_call_success_hook through it so
every guardrail phase contributes to the metric the same way.

Resolves LIT-3999
…31418)

The auth builder cached the team object under the raw `valid_token.team_id`,
while `get_team_object`, `_cache_team_object`, and `_update_team_cache` all read
and write under `team_id:{id}`. The raw-key write was therefore never served
back, and on a non-team (personal) key, whose team_id is None, the original
unguarded version passed a None key straight to the cache layer; the in-memory
cache tolerates None keys but Redis rejects them with a NoneType key error, so
with `enable_redis_auth_cache: true` the team object never reached the L2 cache
and every request fell back to Postgres.

Write under the canonical `team_id:{id}` key, keeping the existing guard that
skips the write when team_id is None. Add a regression test that drives the real
auth builder for a team-scoped key against an in-memory cache and asserts the
team object is served back under `team_id:{id}` and never under the raw team_id
or a None key.

Resolves LIT-4000
…_wheel

fix(build): restore pure-Python uv_build backend to unblock PyPI publish

@greptile-apps greptile-apps Bot left a comment

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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@CLAassistant

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.
3 out of 4 committers have signed the CLA.

✅ yuneng-berri
✅ yucheng-berri
✅ mateo-berri
❌ yassin-berriai
You have signed the CLA already but the status is still pending? Let us recheck it.

@codspeed-hq

codspeed-hq Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing litellm_internal_staging (5a395ef) with main (bd2a165)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (49d655d) during the generation of this report, so bd2a165 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@mateo-berri mateo-berri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM; thanks!

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.11111% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/llms/bedrock/base_aws_llm.py 84.00% 4 Missing ⚠️
litellm/proxy/utils.py 82.60% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread litellm/proxy/utils.py
_PROXY_SensitiveDataRoutingHandler,
)
from litellm.integrations.custom_logger import CustomLogger
from litellm.integrations.prometheus import PrometheusLogger
@yuneng-berri yuneng-berri enabled auto-merge June 26, 2026 21:20
@yuneng-berri yuneng-berri merged commit a311f11 into main Jun 26, 2026
136 of 139 checks passed
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.

7 participants