Commit graph

45043 commits

Author SHA1 Message Date
mateo-berri
da09e21a23 fix(types): silence pydantic ReadOnly warning on StandardLoggingRoutingDecision 2026-08-22 11:22:25 -07:00
mateo-berri
766f72f1d7 fix(anthropic): round-trip thinking blocks to OpenAI backends
The experimental /v1/messages adapters lost prior-turn reasoning three
different ways once the request left for an OpenAI-shaped backend.

On the Responses path, thinking blocks were flattened into output_text
inside the assistant message, so the model read its own private reasoning
back as visible prose and no reasoning item was ever sent. They now become
Responses reasoning input items, grouped by signature so summary parts that
arrived as one item go back as one item. The response direction stops
hardcoding signature=None and carries the reasoning item id, which is what
lets the next turn regroup them; the streaming wrapper emits the matching
signature_delta.

On the chat completions path the adapter attached thinking_blocks but never
set reasoning_content, so Moonshot and DeepSeek substituted a single-space
placeholder and other providers sent nothing. It is now derived from the
thinking blocks.

With use_chat_completions_url_for_anthropic_messages and a model that itself
bridges to /v1/responses, the assistant message was dropped whole: reasoning,
text, and all. That branch now emits the reasoning items and the message
content alongside the tool calls.

Fixes #24985
2026-08-22 11:19:42 -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
dee93e2d48 fix: keep a marker on its own line bound to the statement directly below it 2026-08-22 11:12:26 -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
6d6a2fcfb8 fix: match a marker to the statement it is written against, not to its line 2026-08-22 10:57:00 -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
9906770e41 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_google_interactions_cost
# Conflicts:
#	litellm/constants.py
#	litellm/interactions/main.py
#	litellm/litellm_core_utils/litellm_logging.py
#	litellm/litellm_core_utils/llm_cost_calc/usage_object_transformation.py
#	litellm/proxy/hooks/proxy_track_cost_callback.py
#	litellm/proxy/management_endpoints/credential_migration.py
#	tests/test_litellm/litellm_core_utils/test_litellm_logging.py
#	tests/test_litellm/test_cost_calculator.py
#	ui/litellm-dashboard/src/lib/http/schema.d.ts
2026-08-22 10:39:45 -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
fdcf867dbf fix: read the one assignment a statement holds, and the loop that walks a query
Reading every operator let a comparison beside an assignment look like one.
`ok := n = 1 AND stmt = '<dml>'` registered `stmt` as written, which collided
with the `EXECUTE stmt` further down and flagged a block that rewrites nothing.
A statement holds one assignment at most, so the search now stops at the first
operator that reads as one: everything after it is the expression being
assigned, where an `=` only ever compares. Nine shapes were flagged this way,
a cast, a `coalesce`, a `format`, a named-argument arrow and the rest, and all
of them are valid PL/pgSQL that leaves the table untouched.

`INTO` and `USING` no longer count as names an `EXECUTE` runs. Masking blanks a
literal in place, so `EXECUTE '<sql>' INTO n` left `INTO` looking like the name
being run, and an ordinary query reaching the same word collided with it. The
docstring claiming that collision was impossible was wrong, and both words are
now dropped instead.

A loop is a fourth way a literal reaches a variable. `FOR stmt IN SELECT
'<dml>' LOOP EXECUTE stmt` empties the table and the gate passed it, so the
target of a `FOR` or a `FOREACH` is read as assigned too.

Reading each statement once rather than once per operator also drops the cost
of a statement with thousands of them from seconds to milliseconds.
2026-08-22 10:38:55 -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
mateo-berri
cc5ff14d47 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_ban_data_migrations 2026-08-22 09:55:24 -07:00
mateo-berri
813d3a991f fix: read every assignment operator, not only the statement's first
A bare = was found with partition, so a comparison earlier on the line took
the one slot and the assignment after it went unread. INTO targets and the
name an EXECUTE runs are also allowed to sit on the next line now.
2026-08-22 09:55:18 -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
Mateo Wang
d6f5ea2b37
Merge pull request #37905 from BerriAI/litellm_fix_oauth_credential_forwarding
fix(proxy): stop forwarding a client Anthropic OAuth token to Bedrock and Vertex
2026-08-22 09:03:24 -07:00
yuneng-jiang
d369c9583e
test(router): let monkeypatch own expose_router_debug_in_errors (#37848)
Thirteen tests flipped the flag directly, and an autouse fixture reset it to
True around each of them by hand. monkeypatch.setattr does both jobs, so the
fixture keeps only the part that says what the default is, and each test states
its own override at the point it needs one.
2026-08-22 09:02:40 -07:00
mateo-berri
174c28a79e Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_agentic_loop_cap_response 2026-08-22 08:53:36 -07:00
yuneng-jiang
89187cd030
test(anthropic): let monkeypatch own litellm.callbacks in the cache control tests (#37847)
Fifteen tests assigned litellm.callbacks directly and left the conftest global
snapshot to clean up after them. monkeypatch.setattr restores it as part of the
test, so the file no longer depends on that safety net to stay isolated.
2026-08-22 08:52:02 -07:00
mateo-berri
206e3b8560 docs: say the loop ceiling covers non-streaming /v1/messages 2026-08-22 08:51:30 -07:00
mateo-berri
1f57e7ea19 fix: stop reading an INSERT target table as an assignment target
Scanning a statement's own literals for SQL only makes sense when the name
before INTO is a variable the body later executes. INSERT INTO names a table
there, so an insert into a table sharing a variable's name was flagged for
whatever its column values happened to spell. An INSERT that really does
assign reaches INTO through RETURNING, which the preceding word separates.

Also names the scope boundary in the module docstring: the ban is on
row-rewriting DML, not on everything whose cost scales with table size.
2026-08-22 08:49:02 -07:00
mateo-berri
2adccb3b83 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_unscoped_managed_files 2026-08-22 08:45:56 -07:00
mateo-berri
092449b32f Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_fix_oauth_credential_forwarding 2026-08-22 08:45:05 -07:00
mateo-berri
349e7e6990 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_oauth_credential_forwarding
# Conflicts:
#	tests/test_litellm/test_main.py
2026-08-22 08:44:58 -07:00
yucheng-berri
5285ae86d5
fix(ptu): warn when config.yaml declares PTU while attribution is off (#37898) 2026-08-22 08:25:16 -07:00
yuneng-jiang
7dff9953cb
test: drop the leftover set_verbose from eleven test files (#37845)
Twenty-three tests across eleven files opened with litellm.set_verbose = True
and never put it back, so the flag stayed on for everything that ran after them
in the same process. None of those files read the output it produces: no
caplog, no capsys, no assertion on a log line, so the flag was left over from
debugging. Deleting it beats restoring it, since restoring keeps the noise.

Ten of the eleven stop leaving the flag on. test_volcengine_embedding.py still
ends with it set, from something it exercises rather than from the test itself,
which is worth its own look.
2026-08-22 08:23:27 -07:00
Devin AI
4f5e290f60 refactor(proxy): type the per-model budget plumbing added yesterday
Drops a pyright suppression, getattr string access, and bare dict annotations from the model_max_budget code, and trims a comment referencing its own PR.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-22 08:08:42 +00:00
yuneng-jiang
b9bff0998c
test(bedrock): drop the leftover set_verbose from the embedding tests (#37844)
Fifteen tests opened with litellm.set_verbose = True and never put it back, so
the flag stayed on for everything that ran after them in the same process.
Nothing in the file reads the output it produces: there is no caplog, no capsys
and no assertion on a log line, so the flag was left over from debugging.
Deleting it beats restoring it, since restoring keeps the noise.
2026-08-21 22:54:07 -07:00
yuneng-jiang
322293ad95
test(interactions): drop the save/restore scaffolding around the legacy flag (#37841)
Seven tests captured litellm.use_legacy_interactions_schema, wrapped their body
in a try, and put it back in a finally. monkeypatch.setattr does that, so the
capture, the try and the finally go and the bodies lose an indentation level.

The remaining hand-rolled restores stay. They hold the flag only across the
iterator's constructor and put it back before the test iterates, so handing
them to monkeypatch would widen that window to the whole test and change what
the streaming assertions run against.
2026-08-21 22:43:03 -07:00
yuneng-jiang
ce1321466b
test(http-handler): drop the save/restore scaffolding around litellm globals (#37839)
Nine tests in test_http_handler.py captured litellm.disable_aiohttp_transport,
force_ipv4, ssl_ecdh_curve or the request_timeout pair, wrapped their whole body
in a try, and put the value back in a finally. monkeypatch.setattr does all of
that, so the captures, the try and the finally go away and the bodies lose a
level of indentation. The class-scoped restore_request_timeout fixture existed
only for that same bookkeeping and goes with them.

litellm.in_memory_llm_clients_cache is left alone on purpose: the eviction tests
assert a handler is garbage collected, and monkeypatch holds the replaced value
alive until teardown, which keeps the weakref they check from clearing.
2026-08-21 22:32:24 -07:00
yuneng-jiang
092d97708d
test(s3): stop the logger tests leaking s3_callback_params on failure (#37831)
Ten tests set litellm.s3_callback_params by hand. Four of them reset it to None
on the last line of the test body, which only runs when the test passes; the
other six wrap the body in try/finally to put the old value back. Raising inside
test_s3_verify_false_handling on the current file leaves the whole callback
config, bucket, endpoint and keys, set in the process for whatever runs next.

monkeypatch.setattr covers both shapes and restores on failure, so the 28 TQ005
violations and the try/finally scaffolding come out together.

51 tests pass, and the wider tests/test_litellm/integrations tree is unchanged.
The five TQ002 mock-echo tests in this file are left alone; those need a
judgement about what S3 logging should assert, not a mechanical sweep.
2026-08-21 22:21:29 -07:00
yuneng-jiang
3ac339cfbb
test: stop the zai tests from leaking env and litellm globals into the session (#37834)
test_zai_provider.py set LITELLM_LOCAL_MODEL_COST_MAP and litellm.model_cost
directly and never put them back, so every test that ran after it in the same
process saw a local cost map instead of the real one. The two respx tests did
the same to litellm.disable_aiohttp_transport with no restore at all.

Both now go through monkeypatch, which restores on teardown including when the
test fails. The cost-map setup moves into a fixture requested by exactly the
five tests that read the cost map.
2026-08-21 22:10:34 -07:00
yuneng-jiang
d74fc77eb1
docs(terraform/provider): the provider now ships at the LiteLLM version (#37912)
The provider is published in lockstep with LiteLLM: every dev, rc and stable
release mirrors terraform/provider/ from the release commit and tags it with
the LiteLLM version, alongside the aws/google modules. The 0.x line ends at
0.4.0, and the CHANGELOG headings no longer drive a release.

RELEASING.md describes the new flow and how to recover a version whose
goreleaser run failed; README gains a Versioning section with the re-pin
note for anyone on `~> 0.4`; CHANGELOG records the change under Unreleased.

goreleaser gets `prerelease: auto` so a v1.99.0-dev.1 / -rc.1 tag in the
mirror is marked as a pre-release instead of becoming the repo's latest
release. The registry ingests it either way.
2026-08-21 22:09:38 -07:00
yuneng-jiang
add2d23df2
test(e2e): bypass the proxy response cache in the mid-conversation system and fallback tests (#37915)
The mid-conversation system tests prime the prompt cache by re-sending an
identical /v1/messages body until its usage shows the full prefix read back
three times in a row. The e2e stack runs with the litellm response cache on,
so every resend after the first is served from redis with the first call's
usage and the streak can never form; the three unflagged-model tests have
failed on every litellm-e2e build since the consecutive-read check landed.
Send cache: {"no-cache": true} on RichMessagesRequest, as test_cache_control
already does, so each resend reaches the provider.

The two fallback tests sent the same "say hi" / max_tokens=16 body to the
gpt-5.5 fallback, so one empty (finish_reason=length) completion served the
second test from the response cache and failed both. Give each test a unique
prompt and leave gpt-5.5 enough tokens to emit text.
2026-08-21 22:07:16 -07:00
yuneng-jiang
39a580aa91
test(guardrails): stop five guardrail test files leaking env vars on failure (#37828)
Onyx, prompt security, hiddenlayer, repelloai and deepkeep all write straight to
os.environ and unset again at the bottom of each test. None of the five has a
try/finally, so the moment a test fails it returns to the runner with the keys
still set and whatever runs next in that worker inherits them.

Raising inside test_onyx_guard_with_custom_timeout_from_kwargs on the current
files leaves ONYX_API_BASE and ONYX_API_KEY behind; doing the same in
test_hiddenlayer_config_saas leaves HIDDENLAYER_API_BASE. Both come back clean
after this.

89 raw writes and the hand-rolled deletes become monkeypatch calls. The
class-level setup_method and teardown_method pair in the onyx file, sweeping the
same three keys twice, becomes one autouse fixture. The sys.path.insert lines
and their now-unused imports go too, and litellm.set_verbose = True, which only
turned global debug logging on for whatever ran next, is dropped rather than
restored.

test_onyx_guard_config and test_prompt_security_guard_config asserted nothing at
all, so they could only fail by raising. Each now pins what init_guardrails_v2
produces: exactly one guardrail of the right class on litellm.callbacks,
carrying the configured name, default_on and hook. The zero-assert tests in the
other three are left alone; those are a judgement about each guardrail rather
than a mechanical sweep.

tests/test_litellm/proxy/guardrails passes at 2873.
2026-08-21 21:29:31 -07:00
yuneng-jiang
6bce3dce0d
test(callbacks): unwind the callbacks global the policy engine and realtime tests scaffold around (#37826)
Some checks are pending
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
Code Quality Checks / code-quality (push) Waiting to run
UI Unit Tests / ui-unit-tests (push) Waiting to run
Terraform Provider / gofmt, vet, build, test (push) Waiting to run
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (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
* test(policy-engine): unwind the callback global the pipeline tests scaffold around

Every one of the 16 tests in this file set litellm.callbacks by hand, each
wrapping its body in a try/finally to put the old value back, and each capturing
that old value with a .copy() first. That is 32 TQ005 violations and about 70
lines of scaffolding to say what monkeypatch.setattr says in one.

The write also sat outside the try, so the block that restores it did not cover
the statement that changed it.

16 tests pass either way, and litellm.callbacks reads restored on both sides,
because the conftest snapshot already lists it. The point is that these tests
stop depending on that snapshot to clean up after them.

* test(realtime): unwind the same callback global in the realtime streaming tests

Same global, same shape as the previous commit. 25 writes to litellm.callbacks,
2 of them wrapped in a try/finally that resets to [] rather than to the old
value, and 12 tests that write it with no protection at all.

monkeypatch.setattr replaces all of them, and the sys.path.insert with its
now-unused os and sys imports goes too.

Both sides read restored here as well, for the same reason as the previous
commit: litellm.callbacks is in the conftest snapshot. What changes is that
these tests no longer lean on it.

101 tests pass in this file, 16 in the policy engine one.

* style(realtime): wrap the one signature the monkeypatch param pushed past 120
2026-08-21 21:19:35 -07:00
milan
329202004e test(a2a): stub card resolver in create_a2a_client unit tests
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-22 04:17:16 +00:00
yuneng-jiang
73307070c2
test(key-management): unwind the global writes the key tests scaffold around (#37822)
Seventeen tests in this file save a litellm module global, open a try, write
it, and restore it in a finally. Four more sit behind autouse fixtures that
reset the flag to a hard-coded False rather than to whatever it was.

monkeypatch.setattr does all of that, so the capture, the try and the finally
come out and the test body loses a level of indentation. The alias-format
fixtures stop guessing the value they are restoring to.

Also drops the sys.path.insert, whose argument resolves four levels above the
repo, so it was never what made the imports work.

TQ003 1077 -> 1076 and TQ005 2836 -> 2796, and the budget ceilings come down
with them. 443 tests pass either way; the conftest snapshot was already
catching these globals, so this is about not needing it.
2026-08-21 21:09:27 -07:00
milan
d6f9bce4bb fix(a2a): normalize agent card protocolBinding casing before transport match
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-22 04:05:02 +00:00
yuneng-jiang
0c97eea660
test(cost-calc): stop 182 global writes leaking out of the cost-calc suites (#37815)
* test(cost-calc): stop 182 global writes leaking out of the cost-calc suites

Across test_cost_calculator.py and llm_cost_calc/test_llm_cost_calc_utils.py,
58 tests opened by setting LITELLM_LOCAL_MODEL_COST_MAP in os.environ and
replacing litellm.model_cost, and none of them put the env var back. The
second file already had a _local_model_cost_map fixture doing it by hand with
a try/finally, so both idioms sat in the same file.

Keep that fixture, give it monkeypatch, and have every one of those tests ask
for it. The margin and discount tests drop their hand-rolled
copy-then-restore in favour of monkeypatch.setattr, which also puts the
global back when an assertion fails part way through.

Both files also drop a sys.path.insert whose argument resolves outside the
repo, so it was never what made the imports work.

TQ003 1077 -> 1075, TQ004 768 -> 693, TQ005 2836 -> 2731, and the budget
ceilings come down with them.

* fix(test): make the streamed-cost tests load the map they assert against

The local_cost_map fixture set LITELLM_LOCAL_MODEL_COST_MAP but never reloaded
litellm.model_cost, and reading the variable is not what loads the map. So the
three streaming-cost tests billed against whatever map the process happened to
be holding, and their hardcoded prices only held when something else had
already swapped in the checked-in one. This branch stops the cost-calc tests
leaking that map, which left test_main billing at the ambient prices instead.

The fixture now loads the map it names, so the prices these tests assert hold
on their own.
2026-08-21 21:00:04 -07:00
yuneng-jiang
7481649830
test(datadog): restore an empty DD_API_KEY instead of unsetting it (#37832)
Both datadog test files hand-roll what monkeypatch.setenv already does: read the
old value, write the test value, put the old one back on the way out. The cost
management fixture checks the old value for truthiness rather than for None, so
an operator running the suite with DD_API_KEY set to the empty string gets it
deleted rather than restored. Starting from DD_API_KEY="" and running test_init
leaves it None on the current file, and "" after this.

13 raw os.environ writes become monkeypatch.setenv, the two fixtures stop being
yield fixtures because there is nothing left to do on the way out, and the now
unused os import goes with them.

27 tests pass across the two files, 88 across tests/test_litellm/integrations/datadog.
2026-08-21 20:39:07 -07:00