Commit graph

44358 commits

Author SHA1 Message Date
milan
6237c1c679 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_s3v2_encode_object_key
Some checks failed
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
2026-08-23 03:18:53 +00:00
yucheng-berri
d447be15b9
feat(newrelic): per-team New Relic trace routing via team callbacks (#37603)
Some checks failed
UI Unit Tests / ui-unit-tests (push) Waiting to run
Unit Tests: Documentation Validation / documentation (push) Waiting to run
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Waiting to run
Unit Tests: Proxy DB Operations / auth-checks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / budgets (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / custom-logging (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / db-and-spend (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / key-generation (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / logging-misc (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-runtime (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-server-core (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-utils (push) Blocked by required conditions
Unit Tests / core-utils (push) Waiting to run
Unit Tests / enterprise-routing (push) Waiting to run
Unit Tests / integrations (push) Waiting to run
Unit Tests / All Other Providers (push) Waiting to run
Unit Tests / Vertex AI (push) Waiting to run
Unit Tests / misc (push) Waiting to run
Unit Tests / proxy-auth (push) Waiting to run
Unit Tests / proxy-endpoints (push) Waiting to run
Unit Tests / proxy-infra (push) Waiting to run
Unit Tests / proxy-server (push) Waiting to run
Unit Tests / responses-caching-types (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run
CodSpeed Benchmarks / benchmarks (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
2026-08-22 19:13:48 -07:00
Mateo Wang
aae36f4bd4
Merge pull request #37956 from BerriAI/litellm_fix_26167_bridged_session_lookup
fix(responses): keep the conversation when chaining previous_response_id on the bridge
2026-08-22 16:11:08 -07:00
mateo-berri
23e64c8b3d chore(responses): keep the session lookup inside the type-discipline budget 2026-08-22 15:25:59 -07:00
Mateo Wang
75613bf22f
test: add regression coverage for twelve closed issues (#37974)
* test: add regression coverage for twelve closed issues

Adds targeted regression tests for behavior that was fixed but left ungated,
so the fixes cannot silently regress:

- #33772 openai cache_write_tokens cost
- #34309 Responses API cache cost_breakdown
- #35363 /v1/responses batch spend
- #36619 auto-router api_base/api_key leak on a shared model name
- #35359 batch fallbacks within the owning model group
- #36523 passthrough streamed Responses spend log
- #36646 passthrough embeddings spend log
- #37147 non-object metadata on create_batch is a 400
- #35362 unscoped list files reads the managed-file store
- #33221 gpt-5.6 bridges to Responses on function tools alone
- #34487 LLM complexity classifier runs for every caller metadata shape
- #35124 streamed /v1/messages emits success logging on both bridges

Cost assertions read rates from litellm.model_cost rather than hardcoding
dollar amounts, so they do not drift on repricing.

* fix: stop the new regression tests polluting and tripping over shared global state

Two shard failures, both from global state the new tests share with their
neighbours rather than from the behaviour under test.

test_main.py's local_cost_map pinned litellm.model_cost but left the
get_model_info lru_cache warm, so completion_cost billed at whatever prices
were cached earlier in the process while the assertions read the pinned map.
Clear the cache on both sides of the fixture, matching the local_model_cost_map
fixture in tests/test_litellm/conftest.py.

The anthropic messages streaming tests called GLOBAL_LOGGING_WORKER.flush()
on whatever queue happened to be around. A queue left non-empty by an earlier
test is still bound to that test's loop, so join() either hangs or raises
"bound to a different event loop". Rebind to the running loop before the call
and wait for the captured payload instead of a fixed sleep.
2026-08-22 22:24:05 +00:00
yuneng-jiang
ba07340964
chore: update Next.js build artifacts (2026-08-22 22:11 UTC, node v24.19.0) (#37976) 2026-08-22 15:20:50 -07:00
mateo-berri
d6d25ed310 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_26167_bridged_session_lookup 2026-08-22 15:10:26 -07:00
Mateo Wang
11cbe472ac
Merge pull request #36355 from harryzhou2000/fix/responses-bridge-preserve-reasoning-input-items
fix(responses-bridge): preserve reasoning input items and signed thinking blocks
2026-08-22 15:07:02 -07:00
mateo-berri
19a3fe1b66 fix(responses-bridge): fall back to summary text when content carries none
An empty content list, or one holding only opaque blocks, still lets the
provider-bound branch replay the summary text. The inspection path treated
any non-None content as final, so that replayed text stayed invisible to
guardrails and token counting.
2026-08-22 14:51:38 -07:00
yucheng-berri
8a7c873a01
fix(proxy): omit litellm_batch_guardrail when no guardrail acted (#37964)
The field is declared optional on OpenAIFileObject and its own docstring says it
is absent on every upload guardrails did not touch, but the /v1/files routes have
no response_model, so FastAPI falls through to jsonable_encoder with exclude_none
off and serialises the unset default as an explicit null. Every create and
retrieve response on a proxy with no guardrails configured at all picked up a
litellm_batch_guardrail: null it never had before, and so did every row of a file
list, since those rows are the same object.

A wrap serializer drops the key only when nothing set it, so the populated report
still reaches the wire intact, including a record whose guardrail is null. The
managed-files list route spreads a stored file_object blob rather than the model,
so rows persisted before this lands keep their null until it is dropped there too.
2026-08-22 14:51:11 -07:00
yuneng-jiang
7aef79b774
test(e2e): harden the suite against response-cache cross-talk, slow providers and single upstream blips (#37957)
* test(e2e): send no-cache on every cacheable request body, opt in only where a hit is the assertion

The e2e proxy runs with the response cache on, so any test that re-sends an
identical chat, messages, responses, completions, embeddings or rerank body
reads back a redis copy of an earlier call instead of reaching the provider.
Five tests in the last week failed that way. Default cache: {"no-cache": true}
on those request models and pass cache=None only in the two tests whose
assertion is the cache hit itself.

* test(e2e): give image edits and OCR a 180s client timeout

Both routes wait on providers that can legitimately take longer than the
60s transport-wide request timeout (gpt-image edits, Azure Document
Intelligence), and a client-side read timeout there fails a green request.
post/upload now accept a per-call timeout like get already does; only those
two call sites use it.

* test(e2e): rerun once on network errors and upstream 5xx only

Assertion failures still fail on the first attempt; only an outcome whose
error string carries the e2e_http network kind or a 5xx status gets one
more try. Test Engine records every attempt, so the flake rate stays
visible while a single provider blip no longer reds the rc run.

* test(e2e): let the reseed burst survive one upstream failure and print why

The burst is the precondition, not the property: one 5xx among six
concurrent calls still leaves five workers racing the cold counter, which
is what the reseed assertion measures. Two or more failures still abort,
and the failing bodies are now in the message instead of only the status
codes.

* test(e2e): keep polling Jaeger through a transient query failure

poll_traces_for_call already waits up to POLL_TIMEOUT for spans to land,
but a single refused connection to the query API failed the test on the
spot. Jaeger restarted twice during today's gate runs (19:05 and 19:41
UTC, each under a minute) and took ten and three otel tests with it while
the same tests passed on the rc build minutes later. A network failure
now counts as not-yet inside the same deadline; if Jaeger is still
unreachable when the deadline passes the test fails with that error, and
any non-network failure still fails immediately.
2026-08-22 14:47:03 -07:00
Mateo Wang
98dfb789cb
Merge pull request #37946 from BerriAI/litellm_fix_27333_bridge_item_ids
fix(responses): mint Responses API item IDs in the completion bridge
2026-08-22 14:46:54 -07:00
mateo-berri
d2b5034fea test(responses): fold the bridged streaming regressions into the mapped test file 2026-08-22 14:33:23 -07:00
mateo-berri
3d69ec3603 fix(responses-bridge): keep summary-only reasoning text scannable
A reasoning input item that carries only summary text is replayed to the
provider as reasoning_content, so inspection-only callers must see that
text too. They used to fall through to the generic content branch, which
reads content and drops a summary-only item, leaving guardrails and token
counters blind to text the model still receives.
2026-08-22 14:30:52 -07:00
Yassin Kortam
a44bb47563
fix(prometheus): fold auth/pre-call time into litellm_request_total_latency_metric (#37958)
litellm_request_total_latency_metric's start_time is set inside
common_processing_pre_call_logic, which only runs after user_api_key_auth
has already succeeded, so the metric silently excluded authentication and
pre-call setup time despite being documented as total request latency. The
sibling litellm_request_queue_time_seconds metric had the same problem:
its arrival_time was captured after auth too, despite its own comment
claiming to track when the request arrived at the proxy.

request.state.litellm_received_at is now stamped unconditionally at the
very first line of user_api_key_auth (previously only when OTEL was
configured), giving a timestamp that precedes all auth work. Both metrics
now derive from it: queue_time_seconds genuinely spans arrival through the
start of pre-call processing, and the total-latency metric adds that
queue time on top of its existing start/end window so it becomes true
end-to-end latency.

queue_time_seconds ends exactly at start_time rather than a separately
captured timestamp, so its window and the total-latency window share a
boundary instead of overlapping and double-counting a few lines of setup
work on every request.
2026-08-22 14:25:55 -07:00
Yassin Kortam
ba876c98e6
fix(auth): stop the team fallback from widening model access (#37962)
When get_team_object fails, the centralized auth gate rebuilds the team
from the token's own fields. A token whose team row was missing when the
key was read carries team_models=[] and team_blocked=False, and the
model-access check reads an empty model list as every model, so the
rebuilt team grants more than the real team ever did.

get_team_object reported a deleted team and a database that would not
answer as the same 404, so the fallback could not tell a definitive
answer from a degraded read. Raise a TeamNotFoundError subclass, still a
404 with the same detail so every other caller is unaffected, only when
the database answers and the row is absent.

A team that is provably gone now refuses, and no setting overrides that.
Otherwise the grant is merely unknown: a token carrying one may vouch,
since replaying a recorded grant cannot widen it, and a token carrying
none may not. allow_requests_on_db_unavailable still opts back out there,
and is only consulted once the failure is known to be a degraded read.

The Admin UI mints every session key against the UI_TEAM_ID sentinel,
which by design never has a team row, so every UI request hit the new
refusal with no override. Exempt UI_TEAM_ID explicitly so it keeps
reconstructing from the token unconditionally, matching how the MCP
handler and agent_permission_handler already special-case it.

Resolves LIT-5522
2026-08-22 14:25:29 -07:00
Yassin Kortam
15510f0b8b
fix(auth): resolve team object_permission independently in the unresolvable-team fallback (#37960)
* fix(auth): resolve team object_permission independently in the unresolvable-team fallback

When get_team_object fails for a token's team_id, _user_api_key_auth_builder
reconstructs a LiteLLM_TeamTableCachedObj from the token's own cached fields,
carrying team_object_permission_id but leaving object_permission unset. That
silently dropped any vector-store or MCP restriction the team carried,
granting more access than the token's own object_permission_id vouches for.

Resolve the object permission by its id directly via get_object_permission,
independent of the unreadable team row, matching how every other consumer of
a team's object_permission (vector store access checks, MCP tool/server
resolvers) already treats an unresolvable team as "no restriction at this
level" and re-resolves on its own.

* fix(auth): trim ticket references and narrative docstrings per Greptile review

Drop the LIT-5539 ticket id from test names and fixture strings, and shorten
both the new helper's docstring and the regression test docstrings to their
contracts rather than restating the fix's history.
2026-08-22 14:25:11 -07:00
Yassin Kortam
7ed91df836
fix(proxy): make /team/member_delete's four cleanups atomic (#37959)
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.
2026-08-22 14:24:57 -07:00
mateo-berri
9d22acab11 fix(responses): skip the session lookup retry when spend logs are off 2026-08-22 14:22:10 -07:00
mateo-berri
6a55683cd0 refactor: drop the unused response argument from the image item extractor
The image generation item ID no longer comes from the chat completion
response, so the extractor does not need it.
2026-08-22 14:22:08 -07:00
Yassin Kortam
9349b22c64
fix(guardrails): stop PII/PCI masking gaps in SpendLogs, debug logs, and logging_only response (#37965)
The Presidio guardrail masks messages in place inside pre_call_hook, but three
paths independently persisted or emitted the raw pre-guardrail data: the
SpendLogs proxy_server_request body snapshot (taken before the hook runs),
a verbose_proxy_logger.debug dump of the raw request, and logging_only mode's
async_logging_hook, which never masked the model's response before it reached
external logging callbacks.

Resolves LIT-6015
2026-08-22 14:15:55 -07:00
Mateo Wang
0fca861935
Merge pull request #37715 from syahra712/fix/batch-processed-guard
fix: don't retire a completed batch from cost recovery while output_file_id is lagging
2026-08-22 14:07:01 -07:00
mateo-berri
af18f77db6 fix(check_batch_cost): leave a lagging-output completed batch for the next poll cycle 2026-08-22 12:48:39 -07:00
mateo-berri
9cd6240387 Merge remote-tracking branch 'origin/litellm_internal_staging' into HEAD 2026-08-22 12:48:38 -07:00
mateo-berri
f89a3693ba fix(responses): resolve previous_response_id for a just-written turn
The session lookup reads spend logs straight out of the database, so a
follow-up sent seconds after the turn it chains off found nothing while the
row was still queued in the worker that served it, and the conversation was
dropped without an error. Responses calls now ask the spend-log writer to
flush on its next pass instead of waiting out its poll interval, and the
lookup gives a just-finished turn a short second chance.

Replaying a session also accepted `input` only as a string or a single dict,
so the standard list shape dropped every user turn and left the model with
assistant messages alone.
2026-08-22 11:46:24 -07:00
yuneng-jiang
490c9f9f3f
fix(docker): bump wolfi-base digest for busybox 1.38.0-r1 and openssl 3.6.3-r5 (#37950)
The pinned base (built 2026-07-02) ships busybox 1.37.0-r61 and
libcrypto3/libssl3 3.6.3-r3. Grype reports 16 fixable findings against
those revisions, 8 of them High, so the image-scan gate fails once it
gets past the migration step.

The runtime stage's `apk upgrade` cannot clear them. wolfi-base writes an
exact `=version` constraint for every package it ships into
/etc/apk/world, so `apk upgrade` is a no-op even though the fixed
revisions are in the repo. Advancing them means moving the digest.

The new digest carries busybox 1.38.0-r1, libcrypto3/libssl3 3.6.3-r5
and glibc 2.43-r15, which is at or above the fix revision Wolfi's secdb
records for every finding. Verified with cosign against
chainguard-images/images release.yaml, and grype reports no fixable
findings on the rebuilt image.

CVE-2026-14456, CVE-2026-54876, CVE-2026-38752, CVE-2026-38753,
CVE-2026-38754, CVE-2026-38755
2026-08-22 11:45:39 -07:00
yuneng-jiang
deab3676e8
fix(proxy): keep a failed prisma generate from failing the migration entrypoint (#37947)
The standalone migration entrypoint re-runs `prisma generate` after the
migration completes. That refresh writes into the installed prisma package in
site-packages, which an arbitrary non-root uid cannot do, and which no uid can
do under a read-only root filesystem. Both are supported configurations of the
migrations Job: helm/litellm-helm/tests/migrations-job_tests.yaml asserts
runAsNonRoot, runAsUser and readOnlyRootFilesystem all render.

The write has always failed there, but the failure used to be swallowed. Making
migration failures fatal turned it into a hard exit 1, so a Job that applied
every migration correctly now reports Failed and blocks the rollout it was
supposed to gate.

The refresh is redundant in the shipped images: every Dockerfile generates the
client at build time from the same baked schema, copies it into the runtime
stage, and asserts it resolves there. It stays load-bearing only for a source
checkout, where CircleCI runs the entrypoint under `set +e` and ignores the exit
code anyway. So the call stays and only its exit code stops propagating;
migration failures are still fatal.

image-scan never ran on the change that introduced this, because its path filter
did not list the entrypoint it exercises. Add prisma_migration.py and
entrypoint.sh so the non-root offline migration test gates them from now on.
2026-08-22 11:45:19 -07:00
mateo-berri
6d2b7db2fb fix: keep one reasoning item id across a bridged stream
Write the fallback reasoning item id back to the cache so the
reasoning-done path and the completed snapshot cannot drift apart, and
cover the shared delta id and the snapshot alignment with tests.
2026-08-22 11:42:59 -07:00
Mateo Wang
abf99e37d6
Merge pull request #37911 from BerriAI/litellm_fix_agentic_loop_cap_response
fix(websearch_interception): end the turn when the agentic loop hits its ceiling
2026-08-22 11:40:59 -07:00
Mateo Wang
21d30fccf8
Merge pull request #37949 from BerriAI/litellm_fix_pydantic_readonly_warning
fix(types): silence pydantic ReadOnly warning on StandardLoggingRoutingDecision
2026-08-22 11:39:50 -07:00
Mateo Wang
ae25da3d54 fix(responses-bridge): keep reasoning text visible to inspection-only callers
Guardrails, token counting and rate limiting share the input transform with
the provider path, so moving reasoning onto reasoning_content hid it from
them. Provider-bound callers opt in with replay_reasoning.
2026-08-22 11:39:10 -07:00
yuneng-jiang
1c421f3578
fix(ui): keep completion-mode models in the playground chat dropdown (#37954)
PR #36130 added a KNOWN_MODEL_MODES guard to isModelCompatibleWithEndpoint
that hides any model whose mode isn't in the ModelMode enum, to keep
rerank/ocr/batch/etc. models out of chat-style endpoints. mode: completion
(legacy text-completion models) wasn't in that enum, so it got caught by
the same guard and disappeared from every endpoint, including chat, where
it routes fine.

Add ModelMode.COMPLETION and map it to EndpointType.CHAT like the other
chat-compatible modes.
2026-08-22 11:36:24 -07:00
mateo-berri
b103edb588 fix: keep accepting a loop ceiling that spells a whole number
The ceiling used to go through `int(... or 3)`, so anything `int()` accepted
worked. Tightening the new shared validator to `isinstance(int)` turned a
config that boots today into a proxy that refuses to start, because
`max_agentic_loops: os.environ/MAX_AGENTIC_LOOPS` is resolved to a string
before it reaches either check, and a YAML-quoted "5" is a string too.

Accept ints, integral floats, and strings that parse to a whole number. Keep
refusing bools, fractional floats, words, and anything below 1.
2026-08-22 11:24:22 -07:00
mateo-berri
da09e21a23 fix(types): silence pydantic ReadOnly warning on StandardLoggingRoutingDecision 2026-08-22 11:22:25 -07:00
mateo-berri
05ee5756e7 refactor(responses): rebuild the streaming snapshot output instead of mutating items
Align the response.completed item IDs by copying each output item rather than
writing to it in place, and move the regression cases into the existing
completion-response and image-generation test modules.
2026-08-22 11:17:02 -07:00
mateo-berri
70e4273ba1 fix(responses): make previous_response_id resolve on the bridged path
Streaming /v1/responses over the completion bridge minted a fresh resp_{uuid4}
for every response, while spend tracking stored the inner chat completion id as
request_id. The session lookup queries on request_id, so a follow-up sent with
that response id matched no rows and the prior conversation was silently
dropped. The iterator now pulls the first upstream chunk before emitting
response.created, so created, in_progress and completed all carry the same
encoded chat completion id.

Two more ways the same history went missing:

- The session lookup only read spend logs already written to the DB, so a
  follow-up sent inside the batch writer's window found nothing. It now also
  reads the rows still queued in memory.
- Input was only accepted as a string or a single dict, so the list shape the
  Responses API actually sends dropped every user turn from the reconstructed
  history.
2026-08-22 11:12:17 -07:00
devin-ai-integration[bot]
28887f12c5
fix(otel): emit LLM Call spans for speech, image, moderation, ocr and transcription (#37752)
* fix(otel): emit LLM Call spans for speech, image, moderation, ocr and transcription

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

* fix(otel): log the image request before caller headers are merged in

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

* fix(otel): map non-chat routes to standard genai operations

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

* fix(otel): stop caller image headers aliasing the logged request body

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

* fix(otel): keep resolved api_base in async moderation pre_call

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

* fix(otel): log resolved client endpoint for speech pre_call

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

* chore(otel): justify mutable request payloads in speech and image pre_call

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

* fix(otel): keep caller headers out of the logged speech request body

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-22 11:11:21 -07:00
Mateo Wang
076eebb520
Merge pull request #37855 from rimysore/litellm_fix_unscoped_managed_files
fix(files): list and page unscoped managed files locally
2026-08-22 11:08:34 -07:00
mateo-berri
19e077ab51 fix: validate max_agentic_loops wherever it is set
The ceiling was only checked at the feature level, on
litellm_settings.websearch_interception_params. The per-deployment
litellm_params.max_agentic_loops, which wins over it, went straight into
int(kwargs.get("max_agentic_loops", 3) or 3), so a 0 was swallowed by the
falsy fallback and read as the default 3. Asking for the tightest ceiling
handed you the loosest one. A non-integer booted the proxy and then failed
every request to that model with "invalid literal for int() with base 10".

Both settings now share one validator, which names the field it rejected,
and the per-deployment value is checked while the model list is read at
startup so a bad value stops the proxy rather than surfacing per request.
The check sits in load_config rather than on LiteLLM_Params because the
proxy builds its router with ignore_invalid_deployments=True, where a
validation error drops the deployment silently instead of refusing to
start. This is the same placement the complexity_router_config plugin
check already uses.

Chat completions read the same key through a separate path that turned 0
into 1 and true into a ceiling of 1, so it now shares the validator too
and the key means one thing on both surfaces.
2026-08-22 10:53:13 -07:00
mateo-berri
a7afe986e3 fix(responses): replay signed thinking blocks through the completion bridge
encrypted_content on a reasoning input item is written by LiteLLM's own
_encode_thinking_blocks as a JSON array of Anthropic/Bedrock thinking
blocks, so decode it back and replay the signed blocks on the assistant
message instead of dropping them. Providers without a native
ResponsesAPIConfig now keep the verifiable chain-of-thought across turns,
and prior-turn reasoning stops reaching the provider as visible
assistant text.
2026-08-22 10:51:40 -07:00
mateo-berri
005f04edb6 fix(responses): mint Responses API item IDs in the completion bridge
The Chat Completions -> Responses bridge stamped the upstream chatcmpl-*
ID onto message output items, so replaying bridged history into native
OpenAI Responses failed with "Expected an ID that begins with 'msg'".
Image generation calls were minted as chatcmpl-*_img_N instead of ig_*,
and reasoning items used a salted hash() that is not stable across
processes.

Streaming minted msg_* for its incremental events but rebuilt the
response.completed snapshot through the same broken transform, so the
snapshot contradicted the events it had just sent and streaming clients
hit the same 400. The snapshot now reuses the IDs already streamed.

Fixes #27333
2026-08-22 10:39:31 -07:00
mateo-berri
0485b3fcd4 fix: emit content_block_start for every block in the rebuilt stream
A capped turn on a streaming request is rebuilt into SSE by
FakeAnthropicMessagesStreamIterator. It emitted content_block_stop for
every block but content_block_start only for text, thinking,
redacted_thinking and tool_use, so a web search turn's server_tool_use
and web_search_tool_result blocks produced stops with no matching start.

Anthropic's SDK accumulator appends on content_block_start and then
indexes content[event.index] on content_block_delta, so the orphan stops
shifted every later index and client.messages.stream() raised IndexError
on the text block. Unknown block types now pass through with a start of
their own, which keeps position equal to index.

Also corrects two claims that said no current caller reaches the loop
with stream=True. AgenticStreamingIterator does, and it keeps raising,
because its events are already on the wire.
2026-08-22 10:31:56 -07:00
mateo-berri
4f4edea055 test(files): update the managed-files test doubles to the current afile_list
Eleven DummyManagedFiles stubs still declared afile_list(self, purpose,
litellm_parent_otel_span). The real hook grew user_api_key_dict, limit and
after, so the doubles no longer stand in for the interface they replace.
Their tests pass today only because every one of them takes a provider
branch that never reaches the hook, which means a stub going stale is
invisible until some later test does reach it and reads a TypeError as a
behavior change.

Signatures only; no test changes behavior.
2026-08-22 10:27:32 -07:00
mateo-berri
840b0318bf Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_lit6007_pr36355 2026-08-22 10:24:37 -07:00
mateo-berri
e71a48c57e fix(files): accept OpenAI's evals purpose on the files routes
OpenAIFilesPurpose was missing evals, which OpenAI documents. The upload
route validates against that set, so POST /v1/files with purpose=evals was
already being rejected, and the new listing validator extended the same
rejection to GET /v1/files?purpose=evals, turning a purpose OpenAI accepts
into a hard 400. Nothing branches exhaustively on the type, so widening it
changes no routing.

The managed-file listing test fake only understood a created_by filter. The
OR filter a key carrying both a user_id and a team_id produces, the team_id
filter a service-account key produces, and the empty filter a proxy admin
produces all fell through it and returned every row, so the shapes most real
keys send went uncovered. The fake now applies the filter it is handed, and
the listing is tested against all three, including paging an OR filter
across a cursor.

Two docstrings claimed the continuation chunk bounds what a filtered page
costs. It bounds queries per row scanned; the walk is still linear in the
rows the caller owns.
2026-08-22 10:16:45 -07:00
yuneng-jiang
7a1afa1c40
chore(codeowners): add yuneng-berri as owner of the CODEOWNERS file (#37944) 2026-08-22 17:13:56 +00:00
mateo-berri
7d61d9d71e Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_unscoped_managed_files
# Conflicts:
#	tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py
2026-08-22 09:56:50 -07:00
yuneng-jiang
6a0d03914c
test: drop the cwd-relative sys.path.insert calls from the test suite (#37802)
* test: drop the cwd-relative sys.path.insert calls from the test suite

TQ003 stands at 1,077 across 1,058 files, and 1,015 of them are the same shape:
sys.path.insert(0, os.path.abspath("../..")) and its deeper siblings. The
argument resolves against the working directory rather than the file, so from
the repo root, where every job runs pytest, it inserts the directory two levels
above the checkout. It has never pointed at litellm. The package is installed
into the environment anyway, which is what actually makes the import work, and
what the rule's message has said all along.

Removing them leaves 1,634 imports of sys and os with no remaining reference,
and those go too, except where another test module imports the name back out of
the file. The rest of TQ003 is 62 call sites that resolve against __file__ or a
variable, which are a different question and are left alone.

Collection is identical either way: 45,871 tests and the same 51 pre-existing
collection errors before and after, and ruff reports no new undefined name.

* test: drop the duplicate imports the sys.path sweep exposed to F811

* test(pre-call-utils): restore the os import the new bedrock tests need
2026-08-22 09:25:58 -07:00
yuneng-jiang
de1bc29dc7
test: unshadow the module handles the F811 sweep left behind (#37914)
* test: unshadow the module handles the F811 sweep left behind, and pin the two live tests that went red with it

The F811 sweep in #37878 removed the fixture-local `import litellm` from four
conftests, but the bare `import litellm.proxy.proxy_server` a few lines below
still binds `litellm` as a function local, so `importlib.reload(litellm)` runs
before the name is assigned and every test in those directories errors at
setup. The `hasattr` guard on the line above already proves the module is
loaded, so the import only ever bound the name. Drop it, and enable F823 in
ruff-tests.toml, which flags all four sites at the failing line and would have
blocked the sweep

The same sweep renamed the `check_non_streaming_response` parameter but left
one read of `completion`, which now resolves to `litellm.completion`, and
removed an import whose side effect was the only thing making
`litellm.proxy.proxy_server` reachable in the moderation hook test. That test
already takes `monkeypatch`, so patch the router through it and stop leaking
the router into later tests

`test_content_policy_exception_openai` passed vacuously until #37887 turned it
into a real `pytest.raises`, and OpenAI no longer rejects a lyrics prompt with
a content policy error. Inject an AsyncOpenAI client whose transport answers
with OpenAI's own `content_policy_violation` rejection so the mapping to
ContentPolicyViolationError is exercised every run

`test_async_create_batch` hit a 409 cancelling a batch OpenAI had already
marked failed. The cancel step tolerated a completed batch but not a failed
one. Fold both guards into one helper that tolerates a failed batch only when
OpenAI's recorded error is the org's enqueued token limit, and prints the
batch's errors so the reason is in the log either way

* test: close the injected AsyncOpenAI client after the content policy test

* chore(lint): ratchet TQ005 down by the global mutation this branch cleared

* chore(lint): ratchet TQ005 to 2660 on the merged tree

* chore(lint): ratchet TQ005 to 2561 on the merged tree

* chore(lint): ratchet TQ005 to 2548 on the merged tree
2026-08-22 16:16:38 +00:00
yuneng-jiang
fa9fe5a804
bump: litellm-enterprise 0.1.58 -> 0.1.59, litellm-proxy-extras 0.4.88 -> 0.4.89 (#37939) 2026-08-22 09:12:47 -07:00