Commit graph

44697 commits

Author SHA1 Message Date
yuneng-jiang
72b8b47ee8
bump: litellm-enterprise 0.1.59 -> 0.1.60, litellm 1.99.0 -> 1.100.0 (#38243) 2026-08-25 19:44:45 -07:00
Deepanshu Lulla
d16c4dad4b
feat(logging): add async_post_call_failure_deployment_hook (#36657)
* feat(logging): add async_post_call_failure_deployment_hook

CustomLogger already has async_pre_call_deployment_hook and
async_post_call_success_deployment_hook, both firing once per real
deployment attempt from wrapper_async since the router re-enters that
wrapper fresh on every retry and fallback step. There was no failure-side
counterpart; the only failure signal, async_log_failure_event, fires once
per logical client request behind a dedup gate, so fallback chain attempts
2+ were invisible to callbacks needing per-deployment-attempt granularity.

Adds async_post_call_failure_deployment_hook(request_data, exception,
call_type) to CustomLogger and a matching dispatcher in utils.py, called
from wrapper_async's except block. It needs no dedup coordination since
each real attempt naturally re-enters the wrapper once. Unlike its two
siblings, the dispatcher wraps each callback call in its own try/except
since it runs on the wrapper's own exception path and a broken callback
must never mask the exception about to be re-raised to the caller.

* feat(logging): pass fallback_depth through to async_post_call_failure_deployment_hook

Router already tracks fallback_depth internally on each fallback hop
(litellm/router_utils/fallback_event_handlers.py), incrementing it once per
target tried, but nothing surfaced it to CustomLogger callbacks. Reads it
off request_data in the dispatcher and passes it through as a best-effort
int | None keyword: None on the first, pre-fallback attempt or a bare SDK
call with no router, 1 on the first fallback hop, 2 on the second, and so
on. Verified live against a real multi-hop Router fallback chain before
adding the regression tests.

* fix(logging): fire async_post_call_failure_deployment_hook on internal calls too

The failure hook was gated behind the same not _is_litellm_internal_call
check as the request-level dedup-gated failure logging, so a failed
internal sub-call (e.g. an emulated file-search step) never reached it,
even though its async_pre_call_deployment_hook and
async_post_call_success_deployment_hook siblings already fire
unconditionally for such calls.

* chore: retrigger CI (lint job hit a transient GitHub Actions infra outage on the prior push)

* chore: retrigger CI (lint job hit the same GitHub Actions infra outage again)

* fix(logging): scope async_post_call_failure_deployment_hook to the actual model call

The hook was dispatched from the wrapper's broad outer except, which also
catches BudgetExceededError (raised before any deployment attempt),
errors from async_pre_call_deployment_hook, and errors raised after a
successful model call (post_call_processing, async_post_call_success_deployment_hook,
caching). None of those are a deployment attempt failing, so the hook
misreported them as one.

Scoped the hook to a try/except around the model call itself, so it only
fires when that specific call raises, matching its own documented contract.

* test: assert the callback actually ran in the failure-hook error-isolation test

An upstream test-quality gate (TQ001) flagged this test for asserting
nothing, so it could only fail by raising. Track whether the exploding
callback actually ran and assert on it, so the test would catch a
dispatcher that silently skipped every callback instead of isolating a
raising one.

* fix(logging): harden async_post_call_failure_deployment_hook against 5 maintainer-verified issues

A maintainer's live-proxy A/B review against base found five real
problems with the failure hook, all reproduced and fixed:

- The dispatcher called overrides with fallback_depth as a required
  keyword, so an override matching this PR's own earlier 3-arg
  proof-of-fix example raised TypeError, swallowed at debug level, on
  every call. Now checks the override's signature once per class and
  omits the keyword when unsupported.
- A callback mutating the exception it receives (e.g. status_code)
  changed what the real caller got back, since it was the same live
  object about to be re-raised. Callbacks now receive a same-class
  snapshot instead.
- request_data exposed attempted_targets, the router's own live
  fallback-walk bookkeeping shared by reference across every hop, so a
  callback calling .record() on it could make the router skip a
  deployment it never actually tried. Now excluded from what the hook
  receives.
- The hook's own await sat directly in the model-call except block, so
  a caller-side cancellation landing mid-await (e.g. asyncio.wait_for)
  replaced the real deployment exception with CancelledError/
  TimeoutError. Now isolated so hook dispatch can never mask the real
  failure.
- The timestamp used for the reported failure duration was captured
  after the hook ran, so a slow callback inflated
  async_log_failure_event's duration. Now captured before the hook
  dispatches.

* fix(logging): preserve traceback/cause/context on the failure-hook exception snapshot

Bugbot found a real gap in the previous round's exception-mutation fix:
_snapshot_exception_for_hook only copied __dict__ and args, so a
callback formatting or inspecting the failure chain saw an empty
traceback and lost chained-exception context, even though the live
exception still has them. __traceback__/__cause__/__context__ aren't
stored in __dict__, so they need copying explicitly.

* fix(logging): preserve __suppress_context__ on the failure-hook exception snapshot

Setting __cause__ has a documented CPython side effect of implicitly
forcing __suppress_context__ to True. Since the previous round's
traceback fix set __cause__ before __suppress_context__, a normal
implicit-chaining exception (no `raise ... from`, __suppress_context__
naturally False) got its context wrongly suppressed on the snapshot.
Now __suppress_context__ is set explicitly, after __cause__, so it
always reflects the real exception.

* fix(logging): use MappingProxyType for the failure-hook's sanitized request_data

A LIT002 budget check (surfaced by rebasing onto a moved base) flagged
the dict comprehension building safe_request_data as mutable
construction. MappingProxyType is also a strictly better fit here: a
genuinely read-only view, not just an immutable-looking dict, matching
the intent that callbacks should never be able to mutate what they're
handed.

---------

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-08-25 19:01:03 -07:00
Mateo Wang
ea6ac3dd99
Merge pull request #38283 from BerriAI/litellm_together_regression_tests
test(together_ai): regression suite across chat, responses, and messages surfaces
2026-08-25 17:58:14 -07:00
Mateo Wang
d0c527f3bb
Merge pull request #36245 from BerriAI/litellm_fix_headroom_stream_leak
fix(anthropic): buffer streamed responses carrying server-fulfilled tools so retrieval tool calls never reach the client
2026-08-25 17:48:31 -07:00
mateo-berri
602bf1c0ac test(together_ai): close the injected async client after the streaming test 2026-08-25 17:45:54 -07:00
mateo-berri
7c3da429c7 test(together_ai): regression suite across chat, responses, and messages surfaces
Adds streaming, async, /v1/responses, and /v1/messages coverage for the
Together AI overhaul (#38233, #38248, #38230, #38265, #38275), plus the
legacy api.together.xyz host and TOGETHER_AI_API_BASE through
litellm.completion. Each new test fails under a one-line mutation of the
merged code.
2026-08-25 17:40:41 -07:00
mateo-berri
8d877980e0 fix(caching): carry has_buffered_provider_output through the anthropic stream cache writer 2026-08-25 17:31:46 -07:00
Mateo Wang
99e1eaaca0
Merge pull request #38205 from BerriAI/litellm_decrease_anys_opus5_round2
refactor(repositories): type prisma table access with one generic protocol
2026-08-25 17:19:57 -07:00
mateo-berri
64d8b24c10 fix(router): deliver the hold-back retrieval error to the client instead of falling back 2026-08-25 17:00:00 -07:00
ryan-crabbe-berri
434add755a
Merge pull request #38274 from BerriAI/litellm_test_lint_os_environ
test: gate the test tree on B003 so a test cannot swap os.environ for a plain dict
2026-08-25 16:51:45 -07:00
mateo-berri
7965cfdd2b fix(proxy): keep listing a keyless user's own models when their user row is missing
Keys minted by /key/generate get no LiteLLM_UserTable row, so /v2/model/info?user_models_only=true for such a user hit the new None guard and returned 400 where the merge base returned the user's own models. Skip the team-model merge for a missing row instead of raising, since a user with no row belongs to no team
2026-08-25 16:51:03 -07:00
mateo-berri
98b04de455 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_decrease_anys_opus5_round2
# Conflicts:
#	litellm/responses/litellm_completion_transformation/session_handler.py
2026-08-25 16:48:41 -07:00
ryan-crabbe-berri
50a42ba38e
Merge pull request #38273 from BerriAI/litellm_fix_flow_builder_guardrail_dropdown_zindex
fix(ui): stack policy flow builder below the popup layer so guardrail options render
2026-08-25 16:43:53 -07:00
mateo-berri
1aca5a93d4 Merge remote-tracking branch 'origin/litellm_fix_headroom_stream_leak' into litellm_fix_headroom_stream_leak
# Conflicts:
#	litellm/proxy/common_request_processing.py
#	tests/test_litellm/proxy/test_budget_reservation.py
2026-08-25 16:40:05 -07:00
mateo-berri
1b37397633 fix(router): forward the hold-back keepalive ping live and carry the withheld-output flag through the stream wrappers
The router's pre-content ping filter dropped AgenticAnthropicStreamingIterator's
hold-back keepalive, so a held-back turn sent the client nothing until the buffer
settled. A ping that no lifecycle frame precedes is now forwarded live, since a
fallback's message_start can still follow it without overlapping lifecycles

The proxy's cancel-refund guard checked isinstance against the iterator, but the
proxy only ever sees it behind FallbackAwareAnthropicMessagesStream and
AnthropicMessagesStreamingResponse, so a disconnect during hold-back refunded the
budget reservation anyway. Both wrappers now forward a duck-typed
has_buffered_provider_output flag, and the router wrapper follows a fallback
source so the flag tracks the stream actually being consumed
2026-08-25 16:39:14 -07:00
yucheng-berri
ba8d8b6e14
fix(logging): redact tool call arguments to valid JSON and preserve null content (#38182)
* fix(logging): redact tool call arguments to valid JSON and preserve null content

Resolves LIT-6102

* refactor(logging): centralize redacted tool-call arguments constant and satisfy test-quality gate

* fix(responses): drop Final annotations on loop-assigned locals flagged by basedpyright

* fix(responses): skip custom tool calls in redacted-arguments normalizer

* fix(logging): keep the redaction sentinel in stored tool-call arguments and preserve null output text
2026-08-25 16:38:18 -07:00
mateo
1bc441ca2a fix(proxy): detect held-back provider output through the anthropic streaming response wrapper
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-25 23:33:47 +00:00
yucheng-berri
75bf9f9452
fix(router): persist attempted_fallbacks and original_model_group into spend logs metadata (#38107) 2026-08-25 16:22:01 -07:00
mateo-berri
d16afd2027 fix(memory): return 404 when the memory row vanishes before delete
prisma-client-py delete() returns None when the row is already gone, so the
handler reported deleted: true for a row this caller never removed. Surface
the same 404 the read path uses instead
2026-08-25 16:20:17 -07:00
mateo-berri
4582496c8a Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_decrease_anys_opus5_round2
# Conflicts:
#	basedpyright-code-budget.json
#	litellm/proxy/auth/user_api_key_auth.py
#	litellm/proxy/management_endpoints/team_endpoints.py
#	litellm/proxy/management_helpers/utils.py
#	ruff-strict-budget.json
#	tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py
#	type-discipline-budget.json
2026-08-25 16:17:29 -07:00
Mateo Wang
1ff615c335
Merge pull request #38275 from BerriAI/litellm_together_chat_template_kwargs
fix(together_ai): strip internal thinking fields from outbound messages, keep reasoning_content
2026-08-25 16:15:50 -07:00
Deepanshu Lulla
6684bb343b
perf(streaming): add shared JSONFragmentAccumulator for Vertex and Anthropic (#36610)
* perf(streaming): add shared JSONFragmentAccumulator for Vertex and Anthropic

Vertex's handle_accumulated_json_chunk and Anthropic's
_handle_accumulated_json_chunk each independently accumulated SSE fragments
into a JSON envelope with self.accumulated_json += fragment. Because the
attribute holds a live reference, CPython copies the whole prior buffer on
every fragment, making buffer assembly O(n^2) in total payload size.
Anthropic additionally had no completeness heuristic at all and called
json.loads on the whole buffer after every fragment, and could wedge forever
on two concatenated envelopes.

Add JSONFragmentAccumulator in litellm_core_utils/: fragments append to a
list in O(1), a could_close_json heuristic lets callers skip the join+parse
entirely until a value could plausibly be complete, and pop_next_value peels
one JSON value off the front of the buffer at a time using
json.JSONDecoder().raw_decode, keeping any unconsumed remainder instead of
failing on concatenated values. Migrate both providers onto it; Anthropic's
__next__/__anext__ end-of-stream handlers now delegate to
_handle_accumulated_json_chunk(is_final=True) instead of duplicating the
parse-and-reset logic inline.

Fixes #31861.

* test(streaming): close diff-coverage gaps in JSONFragmentAccumulator migration

Codecov flagged 11 uncovered lines in the migration: the accumulated_json
setter, __next__/__anext__'s end-of-stream drain branches in both providers,
and the pop_next_value "not found" path when a buffer's newest fragment ends
in "}" but is genuinely incomplete (an inner object closed, the outer one
didn't). Add targeted tests for each.

* fix(streaming): make JSONFragmentAccumulator's completeness heuristic O(1)

could_close_json rescanned every preceding blank fragment on each call, so a
hostile upstream that sends malformed JSON (never closing) followed by many
blank keepalive fragments could drive that scan, and the join+parse it
gates, to O(n^2) total. Track the last non-blank fragment's trailing byte
incrementally in append/pop_next_value/set instead of rescanning the buffer.

Reported by automated review on PR #36610.

* fix(streaming): make JSONFragmentAccumulator.pop_next_value O(1) per call

pop_next_value previously rebuilt the full remaining string and sliced a
new remainder on every call, so draining N concatenated JSON values
already sitting in one buffer cost O(n^2) total. Replace the rebuild-and-
slice with a materialize-once cursor: pending fragments are joined into
the buffer only when new ones have arrived since the last pop, and
consumed values are dropped by advancing an offset instead of copying
the remaining string.

* test(streaming): make JSONFragmentAccumulator drain regression test CI-stable

The 80k-value drain test used an absolute ms budget that flaked on a
busier CI runner (233.5ms vs a 150ms budget calibrated on a quiet
machine). Replace it with a doubling-ratio check: draining twice as many
concatenated values should take roughly 2x as long for O(n), not
~4x for O(n^2), and that ratio holds regardless of machine speed.

* test(streaming): suppress TQ002 on the append-laziness spy test

A test-quality gate (TQ002: don't assert only that a mock was called)
landed upstream since this branch's last rebase and now flags
test_append_never_calls_raw_decode. The test verifies append() defers
all decoding to pop_next_value, which has no caller-observable proxy
other than spying on the stdlib call it must avoid making.

* test(vertex): spy on raw_decode instead of json.loads in accumulator regression tests

Post-migration to the shared JSONFragmentAccumulator, Vertex's decode
path goes through json.JSONDecoder.raw_decode, not json.loads. The two
O(n^2)/partial-fragment regression tests still patched json.loads, which
that path never calls, so both passed unconditionally regardless of
whether the underlying implementation regressed. Verified by simulating
an eager-reparse regression: both tests now fail against it and pass
against the correct implementation.

* fix(streaming): widen JSONFragmentAccumulator's whitespace skip to match str.strip()

pop_next_value's whitespace skip only matched json.decoder.WHITESPACE's
ASCII set, narrower than str.strip() (Unicode-aware) which the O(1)-cursor
rewrite replaced. A non-ASCII separator like U+00A0 between two
concatenated JSON values on one SSE line made raw_decode fail on it, and
the buffer never advanced past that byte again, permanently stranding
everything after it for the rest of the stream. Use str.isspace() to
match str.strip()'s tolerance.

---------

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-08-25 16:15:47 -07:00
mateo-berri
4ab654cbc5 merge: litellm_internal_staging into litellm_fix_headroom_stream_leak 2026-08-25 16:04:33 -07:00
mateo-berri
8a61b286d1 test(together_ai): annotate preserved-thinking test helper and capture list 2026-08-25 16:03:43 -07:00
devin-ai-integration[bot]
bb22742025
fix(rerank): emit latency and cost headers on /rerank (#35419)
* fix(rerank): emit latency and cost headers on /rerank

Thread the logging object into the rerank httpx calls and pass hidden_params through to get_custom_headers, so x-litellm-overhead-duration-ms, x-litellm-response-duration-ms, x-litellm-response-cost, x-litellm-call-id and the LITELLM_DETAILED_TIMING x-litellm-timing-* headers show up on rerank like they do on chat completions

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(rerank): keep zero response cost in the /rerank cost header

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* ci: assign the new rerank endpoint tests to the proxy-endpoints shard

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test: suppress TQ008 on the rerank header tests with reasons

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: milan <milan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yassin <yassin@berri.ai>
2026-08-25 15:54:25 -07:00
mateo-berri
1ba66b1555 fix(together_ai): strip internal thinking fields from outbound messages, pin chat_template_kwargs passthrough 2026-08-25 15:48:58 -07:00
ryan-crabbe-berri
8298cbe1f7 test: gate the test tree on B003 so a test cannot swap os.environ for a plain dict 2026-08-25 15:43:26 -07:00
Devin AI
3ebd18b057 fix(ui): stack policy flow builder below the popup layer so guardrail options render
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-25 22:37:02 +00:00
Mateo Wang
04818a3554
Merge pull request #38267 from BerriAI/litellm_fix_responses_user_document_drop
fix(anthropic): carry user-content document blocks through the /v1/messages responses bridge
2026-08-25 15:20:30 -07:00
Mateo Wang
e4ff44f623
Merge pull request #38265 from BerriAI/litellm_together_tools_passthrough
fix(together_ai): pass tools through for models missing from the registry
2026-08-25 15:12:08 -07:00
Mateo Wang
dc40377959
Merge pull request #38261 from BerriAI/litellm_fix_responses_tool_result_document_drop
fix(anthropic): carry tool_result document blocks through the /v1/messages responses bridge
2026-08-25 15:09:23 -07:00
Mateo Wang
296ebc1103
Merge pull request #38252 from BerriAI/litellm_pr_template_caveat_severity
docs(pr-template): split Caveats bullets into severity tiers and call for plain engineering language
2026-08-25 15:04:37 -07:00
mateo-berri
61ee2f5ecb fix(anthropic): carry user-content document blocks through the /v1/messages responses bridge 2026-08-25 15:00:57 -07:00
Yassin Kortam
6c0c91c5ad
fix(team): serialize member_add, member_delete, and delete under the team's advisory lock (#37969)
* fix(proxy): make /team/member_delete's four cleanups atomic

The team roster update, the user.teams update, the team membership
delete, and the team-scoped verification token delete ran as four
sequential writes with no transaction around them, so a failure
between any two left the removal half applied. Thread a single
prisma transaction through all four writes, following the same
tx.<table> pattern /team/member_add and /team/member_update already
use, so either all four land or none do.

* fix(team): serialize member_add, member_delete, and delete under the team's advisory lock

/team/member_add validated a team exists and then wrote the user's teams array and
a membership row without holding anything across that gap, so a /team/delete could
commit its reference sweeps in between and leave a member pointing at a team id that
no longer exists. The write path already re-read members_with_roles under a row lock
before this change, but SELECT ... FOR UPDATE can deadlock with the access-group
endpoints, which lock an access group and then a team.

member_add now takes pg_advisory_xact_lock(hashtext(team_id)) before re-reading the
team and only writes if it is still there, so a delete that already committed is
visible before any write happens. delete_team takes the same lock around its own
row delete and reference sweep, so the two requests can never interleave: whichever
acquires the lock first runs to completion before the other's read can proceed.

Dropping the row lock from member_add's read also dropped the incidental protection
it gave against a concurrent member_delete, which still wrote from the snapshot it
validated against, unlocked, and could silently overwrite whatever member_add had
just committed. member_delete now takes the same advisory lock and re-reads the
roster under it before computing its own write, so it can never resurrect a member
by overwriting from stale data.

Resolves LIT-5544

* fix(team): run member writes on the advisory lock's transaction

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(team): keep member writes on the lock holder's connection after merge

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(team): keep the transactional member create an upsert on user_id

The transaction path was creating the email-identified user row outright, where the
regular client path upserts on user_id. Share one upsert helper between both member
paths so the create stays idempotent on the lock holder's connection.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(team): read member_delete's user and key rows on the lock-holding transaction

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-25 21:55:11 +00:00
Mateo Wang
9962f4fba8
Merge pull request #38251 from BerriAI/litellm_fix_tool_result_document_drop
fix(anthropic): translate tool_result document blocks in the /v1/messages bridge
2026-08-25 14:54:01 -07:00
Mateo Wang
a40157c55d
chore: make it more concise 2026-08-25 14:53:24 -07:00
Mateo Wang
d61ccd0f58
chore: make it more clear 2026-08-25 14:51:53 -07:00
Mateo Wang
99c1b33fd3
chore: humanize the CLAUDE.md 2026-08-25 14:50:14 -07:00
mateo-berri
ec365a3c12 fix(together_ai): pass tools through for models missing from the registry
Tools passed for Together models with no supports_function_calling entry in
model_prices_and_context_window.json were rejected with UnsupportedParamsError
by default and silently dropped under drop_params, which made models emit tool
calls as plain text. Fail open instead: pass tool params through with a warning
and let Together validate. Models the registry explicitly marks as not
supporting function calling keep the loud contract: raise by default, drop with
a warning under drop_params.
2026-08-25 14:38:34 -07:00
Deepanshu Lulla
75c4565dde
fix(cerebras): add max_retries and extra_headers to get_supported_openai_params (#36601)
Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-08-25 14:10:56 -07:00
Mateo Wang
d8d384eada
Merge pull request #38230 from BerriAI/litellm_together_models_backfill
feat(models): add missing Together AI serverless models to the cost map
2026-08-25 14:02:58 -07:00
mateo-berri
c3c9e3a919 test(anthropic): pin dropped file-id and empty-url document sources to string output 2026-08-25 13:58:37 -07:00
mateo-berri
e1a0920445 refactor(anthropic): bind tool_result file parts once instead of seeding an empty tuple 2026-08-25 13:54:34 -07:00
ryan-crabbe-berri
02cba40bb6
Merge pull request #37756 from BerriAI/litellm_jwt_team_allowed_routes_wildcard
fix(auth): support wildcard prefixes in jwt team_allowed_routes
2026-08-25 13:49:31 -07:00
Yassin Kortam
c70b911122
fix(router): support mid-stream fallback for anthropic_messages route type (#38153)
anthropic_messages goes through _ageneric_api_call_with_fallbacks rather
than _acompletion, so its returned streaming iterator was never wrapped
by the chat-completions fallback handler. A retriable SSE event: error
frame (overloaded_error, internal_server_error) from a native
Anthropic/Bedrock passthrough passed through to the client unchanged,
and a MidStreamFallbackError raised by the completion-bridge path's
CustomStreamWrapper propagated unhandled.

Add _aanthropic_messages_streaming_iterator, mirroring
_acompletion_streaming_iterator: it detects a retriable SSE error event
via the new parse_anthropic_error_event helper, raises
MidStreamFallbackError once real generated content (a content_block_delta
frame) has not yet reached the caller, and re-enters the Router's
fallback chain. A MidStreamFallbackError raised directly by the source
iterator (the completion-bridge path) is gated the same way via its own
is_pre_first_chunk flag. The raised MidStreamFallbackError carries a
status-coded original_exception built from the parsed error type, so
status_code/cooldown logic sees the real 429/500/503/etc. instead of a
hardcoded 503.

Lifecycle/bookkeeping frames (message_start, content_block_start, ping,
...) never disqualify a fallback attempt by themselves, since Anthropic
routinely sends message_start before an overload error - but they are
buffered rather than forwarded immediately, since forwarding one and
then appending a fallback attempt's own message_start would produce two
overlapping message lifecycles on one SSE stream. Buffered frames flush,
in order, once real content arrives or the stream ends without error.
Once real content has streamed, or the error is a non-retriable 4xx, the
chunk (or exception) is forwarded as-is rather than starting a second
lifecycle. Content and error coalesced into a single physical read are
handled the same way: once the client has genuinely received the content
(bundled in that same forwarded chunk), no fallback is attempted. A
`ping` keepalive is dropped outright before any real content arrives
(it recurs indefinitely on a slow-starting connection and carries
nothing worth buffering), and the pre-content lifecycle buffer is capped
at MAX_BUFFERED_PRE_CONTENT_ANTHROPIC_CHUNKS, forcing an early commit to
the primary stream so a hostile or pathological upstream can't grow it
without bound. is_anthropic_ping_chunk only matches a chunk whose every
event: line is event: ping, so a ping coalesced with real content or a
retriable error into one physical transport chunk is never dropped.

The fallback request kwargs also deep-copy nested litellm_metadata/metadata
(matching the Responses API path) so the primary attempt's
deployment-specific fields never leak into the fallback request, and the
fallback deployment's own provider headers are merged onto the wrapper's
_hidden_params so they still reach the client/logging pipeline. A
fallback that resolves to a non-streaming response (e.g. an agentic
tool-use interception loop) is synthesized into a real Anthropic SSE
event sequence via the new anthropic_messages_response_as_sse_events
helper, instead of yielding a raw dict into the byte stream - including
a trailing signature_delta for a thinking block, and a message_start
whose stop_reason/stop_sequence/output_tokens stay null/zero the way a
real stream's does instead of leaking the completed response's final
state.

Resolves #24004
2026-08-25 13:40:46 -07:00
devin-ai-integration[bot]
c96245d7bf
fix(scim): preserve existing team memberships when POST /Users adoption carries no groups (#38166)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-25 13:39:34 -07:00
devin-ai-integration[bot]
6d0cc1423e
fix(scim): return user_id as Group members[].value on transformed group responses (#38161)
* fix(scim): return user_id as Group members[].value on transformed group responses

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* style: format scim transformation tests per ruff

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test: drop redundant assertion comment

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore: retrigger ci

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-25 13:37:45 -07:00
ryan-crabbe-berri
b1f903743c Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_jwt_team_allowed_routes_wildcard 2026-08-25 13:33:29 -07:00
mateo-berri
5998c0d1d2 fix(anthropic): carry tool_result document blocks through the /v1/messages responses bridge 2026-08-25 13:32:26 -07:00
ryan-crabbe-berri
e5adf7d926
Merge pull request #37493 from BerriAI/devin_ai_lit5815_guardrail_tag_mode_ui
fix(ui): render tag-based guardrail mode instead of crashing the guardrails page
2026-08-25 13:24:19 -07:00