Commit graph

2708 commits

Author SHA1 Message Date
mateo-berri
0a869b7226 fix(databricks): derive cache rates from published cache DBU
Cache rates were derived as ratios of the dollar input rate (1.25x write,
0.1x read) while input and output derive from the published DBU table
times $0.070. Databricks publishes cache write and cache read DBU per
model, and those are not exact multiples of the input DBU, so the two
rules disagreed by up to 0.1 percent.

Rewrites 43 cache literals across 31 entries to published_cache_DBU x
$0.070. Skips databricks-gemini-2-5-pro and databricks-gemini-2-5-flash,
whose input and output rates predate the current table by a 1.25x
increase; their cache rates stay tied to their own input rate so each
entry remains internally consistent.

Replaces the ratio assertions with a test pinning the absolute published
DBU figures for the five new models, and adds a test pinning the
older-vintage exception. Corrects the metadata note on the five new
entries, which claimed the reference-only *_dbu_cost_per_token fields
drive cost calculation.
2026-08-22 16:42:52 -07:00
mateo-berri
8a1fe281fd fix(responses-adapter): skip null reasoning summary text and keep thinking-only assistant turns 2026-08-22 16:29:35 -07:00
Devin AI
20e92d1e68 fix(anthropic/bedrock): request summarized adaptive thinking for reasoning_effort and use provider thinking token counts
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-22 23:10:42 +00:00
mateo-berri
da3dcb139d fix(databricks): charge the input rate for cache tokens on models with no cache pricing
The shared cost calculator treats a missing cache rate as free, so routing
Databricks through it billed cached tokens at zero on the 14 entries that
publish no cache pricing. On a 10,000 token prompt with 8,000 cache reads
that is $0.0010000 against the correct $0.0050001, a fivefold undercharge.

Those entries now declare cache rates equal to their input rate, which is
what a model with no caching discount should charge, and a test pins every
priced Databricks entry to declaring cache rates so no future entry can
regress into it.

Also repoints the provider-neutral generalization test off an id the new
Opus 5 entry now shadows, adds backup-to-main parity tests for the five new
entries, pins that Databricks Claude is never auto-injected with cache
control despite reporting caching support, and trims the Sonnet 5 pricing
note, which is served on an unauthenticated route.
2026-08-22 15:37:37 -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
mateo-berri
71d6f5f0be Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_24985_thinking_roundtrip 2026-08-22 15:14:16 -07:00
mateo-berri
6c23f5ffba fix(databricks): price Sonnet 5 at standard rates past the introductory window
The introductory DBU rates run through 2026-08-31 and pricing carries no
expiry date, so a static introductory entry would undercharge by a third
from September 1 and let spend outrun enforced budgets. Ship the standard
rates, which match Sonnet 4.5 and 4.6, and keep the introductory numbers
in the entry notes.

Also give the new cost calculator tests full type annotations.
2026-08-22 15:12:02 -07:00
mateo-berri
c55b400f41 fix(databricks): bill cached tokens at cache rates and add missing Claude pricing
Databricks cost calculation multiplied every prompt token by the input rate, so
a cache read cost the same as an uncached token. Route it through
generic_cost_per_token, which already understands cache reads and cache writes,
and add the cache rates the registry was missing.

Adds Claude Opus 4.7, Opus 4.8, Opus 5, Sonnet 5 and Fable 5 on Databricks.
2026-08-22 14:55:30 -07:00
mateo-berri
32bf1aba29 fix(anthropic): stop signing replayed thinking blocks and strip reasoning_content
A reasoning item id is not an Anthropic signature. Passing it off as one got the
block replayed to Anthropic and Bedrock as if it were real, and every backend that
verifies signatures rejected the turn. Thinking blocks now come back unsigned, and
the streaming path no longer emits a signature_delta for them.

Azure AI Foundry, Fireworks, and vLLM reject unknown message fields, so they now
strip reasoning_content alongside thinking_blocks the way Mistral already did.

The thinking-block helpers take ChatCompletionThinkingBlock and
ChatCompletionRedactedThinkingBlock instead of loose mappings.
2026-08-22 14:27:20 -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
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-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
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
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
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
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
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
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
693797420d
test: unwind environment writes in tests/test_litellm with monkeypatch (#37806)
* test: use monkeypatch.setenv for env writes in tests/test_litellm

`os.environ["X"] = v` inside a test leaks the value into every test that runs
after it in the same worker, so ordering decides the result. 262 of those
writes across 40 files now go through pytest's `monkeypatch` fixture, which
restores the previous value at teardown.

The rewrite skips any test that a mock.patch-family decorator wraps, any test
with defaulted positional parameters, any test whose own name is called
directly elsewhere, and rebinds nothing inside nested defs, because in each of
those cases appending a fixture parameter changes what pytest or mock binds.

Ratchets the TQ004 ceiling from 768 to 506.

* fix(test): delete the key through monkeypatch instead of popping it first

Five tests popped a key straight out of `os.environ`, ran, then restored it with
`monkeypatch.setenv`. By the time monkeypatch saw the name it was already gone,
so it recorded "absent" as the value to go back to and deleted the key at
teardown. On a worker that inherited a real `RESEND_API_KEY`, `SENDGRID_API_KEY`,
`UI_PASSWORD`, `LITELLM_SALT_KEY` or `OPENAI_API_KEY`, every test after the first
one ran without it.

`monkeypatch.delenv(..., raising=False)` removes the key and restores whatever
was there, so the try/finally the manual restore needed goes with it.

* chore(test): leave the two cost-calc files to the PR that rewrites them fully

Both files are also in #37815, which converts the module-global writes as well
as the env writes and folds them into one fixture. Two PRs rewriting the same
lines differently is a conflict nobody benefits from resolving, so this one
drops back to staging on those two and keeps the other 39.

TQ004 clears 200 here instead of 275; the rest moves with #37815.
2026-08-21 20:28:37 -07:00
yuneng-jiang
4a008b67ef
test(bedrock): let monkeypatch own bedrock_request_metadata_fields (#37840)
Twenty tests in test_request_metadata.py assigned the global directly and
leaned on an autouse fixture to put it back afterwards. monkeypatch.setattr
does both jobs at the point of use, so each test now says what it sets and the
fixture that existed only to undo them goes away.
2026-08-21 20:16:01 -07:00
yuneng-jiang
f88421bb43
test(llm_http_handler): pin the websocket and callback gates the request path branches on (#37814)
The Responses WebSocket path, the pre-call deployment hook and the
per-frame project quota hook are all selected by small predicates that
nothing asserted directly. Mutating those four decisions left 4 of 6
mutants alive against the mapped test file.

Cover them at the boundary: the rust WebSocket path needs both the
openai provider and the rust flag, a plain CustomLogger must not
advertise a pre-call deployment hook while an overriding or inheriting
one must, and only callbacks that actually expose a callable
enforce_project_io_token_quota_for_frame reach the WebSocket loop.

Kill rate on those four decisions goes 2/6 -> 6/6; the file goes 66 -> 75
passing.
2026-08-21 20:15:42 -07:00
tin-berri
060e40021d
fix(anthropic): resolve the provider exactly once on /v1/messages (#37757)
get_llm_provider ran in the messages handler and again inside completion,
so a provider/vendor/model id lost its vendor segment and reached upstream
bare. Pass the caller's unresolved model down the bridge instead, and move
the responses marker into the canonical provider/responses/model slot.

Reporting stays provider-local on both bridges: message_start names the id
the provider itself knows, through a shared local_model_name helper.

Fixes #37716
2026-08-21 19:47:47 -07:00
ryan-crabbe-berri
b7f8016002 test: gate the test suite on F601, B023, B025 and F632
Four more ruff rules for code the test suite runs but never checks. F601 is the
one that paid: the duplicate key it flagged in a get_form_data fixture was the
mock reproducing the production bug fixed in the previous commit.

B025 removed two unreachable handlers, one of them a pytest.skip shadowed by an
earlier `pass`, so an upstream Vertex flake reported green having asserted
nothing. F632 turned an `is ""` identity check, which passes only on CPython
interning, into the `== ""` it meant. B023 fixed three closures over loop
variables, all latent today but one iteration-order change away from checking the
last case N times.
2026-08-21 18:44:57 -07:00
tin-berri
770bd40f5d
fix(anthropic_messages): gate sampling params on /v1/messages like /chat/completions (#37868)
* fix(anthropic_messages): gate sampling params on /v1/messages like /chat/completions

/v1/messages forwarded temperature/top_p/top_k raw to models that removed
sampling params (supports_sampling_params: false — Claude 4.7+/Fable 5),
producing provider 400s that router fallbacks mask as silent model
downgrades. The chat path already gates these via
AnthropicModelInfo._apply_sampling_param; reuse it in
get_requested_anthropic_messages_optional_param so both endpoints agree:
drop under drop_params, else raise the clean client-side 400.

Fixes #35053

* test(anthropic): drive new sampling-param tests off the kwarg, not the global

The five tests added here set `litellm.drop_params = True` under a manual
try/finally. That trips TQ005 (module-global mutation, 10 new violations
over the ceiling) and it leaks process-wide if the finally is ever
skipped, which is what the save/restore conftest exists to paper over.

`get_requested_anthropic_messages_optional_param` already takes
`drop_params` as a kwarg, and that is the path /v1/messages actually
uses, so pass it directly. `monkeypatch.setattr` pins the global to
False so each test proves the per-request flag alone is sufficient and
cannot pass on a leaked global.

Verified: TQ gate clean, all 10 tests pass, and the 3 that assert the
new gating still fail with the fix in utils.py reverted.

---------

Co-authored-by: Mihidum Hettiyahandi <55163074+mihidumh@users.noreply.github.com>
2026-08-21 18:23:25 -07:00
mateo-berri
81cdf1a821 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_oauth_credential_forwarding 2026-08-21 18:06:42 -07:00
mateo-berri
48aba5f103 fix(proxy): stop forwarding a client Anthropic OAuth token to Bedrock and Vertex
add_provider_specific_headers_to_request tagged the client's Authorization header
with the same provider list as anthropic-beta and anthropic-version, so an
sk-ant-oat subscription token was sent to AWS Bedrock and Google Vertex AI as
well. On Bedrock it replaced the SigV4 signature, or the deployment's own API
key, and AWS answered 403 "Invalid API Key format". On Vertex it went out as a
second Authorization header next to the Google one and Google answered 401
ACCESS_TOKEN_TYPE_UNSUPPORTED.

The credential and those API headers need different scopes, so a request can now
carry more than one ProviderSpecificHeader entry. The API headers keep the
provider list they already had and the credential gets its own entry scoped to
anthropic alone. get_provider_specific_headers takes either a single entry or a
sequence and merges only the entries whose provider list matches, so callers that
pass one entry keep working unchanged.

Bedrock SigV4 signing and the deliberate extra_headers Authorization pass-through
in _sign_request are left alone.
2026-08-21 18:02:40 -07:00
ryan-crabbe-berri
843e90c2f5
Merge pull request #37901 from BerriAI/litellm_ruff_raises_literal_pattern
test: say whether a match= pattern is a regex or a literal (ruff RUF043)
2026-08-21 18:01:37 -07:00
ryan-crabbe-berri
6b088f4bb1 style: wrap the escaped messages under 120 columns 2026-08-21 17:48:57 -07:00
Mateo Wang
6f07119925
Merge pull request #37821 from longwind48/litellm_bedrock_gpt56_runtime_cross_region
feat(bedrock): serve gpt-5.6 cross-region inference profiles on bedrock runtime
2026-08-21 17:30:06 -07:00
Mateo Wang
3029f7eb84
Merge pull request #34752 from SouthernCrossAI/litellm_scx_ai_provider
feat(providers): add SCX.ai as a JSON-configured OpenAI-compatible provider
2026-08-21 17:21:46 -07:00
mateo-berri
729a952322 fix(bedrock): keep rerank on SigV4 when a Bedrock API key is set
Routing rerank through get_request_headers also picked up its
AWS_BEARER_TOKEN_BEDROCK branch. Bedrock API keys are only valid for
Bedrock and Bedrock Runtime actions, not for Agents for Amazon Bedrock
Runtime ones, and rerank is served by bedrock-agent-runtime, so AWS
rejects a bearer-signed rerank call. Opt the rerank handler out of the
bearer path so it keeps signing with SigV4.
2026-08-21 17:17:24 -07:00
ryan-crabbe-berri
91599aef69 test: say whether a match= pattern is a regex or a literal (ruff RUF043) 2026-08-21 16:25:33 -07:00
ryan-crabbe-berri
5ed230701a test: escape the literal match= patterns PT017 minted 2026-08-21 16:22:51 -07:00
ryan-crabbe-berri
6266b3d50a test: keep a real assertion where the tolerance handler lost its last one 2026-08-21 14:08:19 -07:00
ryan-crabbe-berri
243ed4393d test: reject assertions on a caught error inside except (ruff PT017)
A test that asserts on the error inside its own except block passes when the
call stops raising, because nothing runs the handler. That is the exact case
the test exists to catch, so the regression lands green.

Rewrites all 111 such blocks into pytest.raises, which fails when the call
succeeds, and selects PT017 in ruff-tests.toml so no new one lands.
2026-08-21 13:35:08 -07:00
Sai Likhith Kanuparthi
52e181d12d
fix(vertex_ai): convert messages to contents in gemini count_tokens (#36981)
* fix(vertex_ai): convert messages to contents in gemini count_tokens

acount_tokens passed contents=None to the Vertex Gemini countTokens
endpoint when called with messages=, causing a silent zero token count.
The Gemini branch of VertexAITokenCounter.count_tokens never read the
messages parameter, so the request body was {"contents": null}, which
Vertex accepts with HTTP 200 and no totalTokens field.

Convert messages to Gemini contents format using the existing
_gemini_convert_messages_with_history helper when contents is None.
Treat a response without totalTokens as a failure so the caller falls
back to local token counting instead of returning a silent zero.

Fixes #36921

* style: apply ruff format to common_utils.py

Resolves lint CI failure on PR #36981.

Generated with [Devin](https://devin.ai)

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

* chore: suppress LIT002 on messages fallback for Gemini token counter

Adds `# mutable-ok:` suppression on the `messages or []` fallback passed
to `_gemini_convert_messages_with_history`. The [] is a None-fallback;
the helper signature requires list[AllMessageValues], so a tuple would
violate the type contract. Resolves type-discipline-budget CI failure
on PR #36981.

Generated with [Devin](https://devin.ai)

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

* chore: suppress reportPrivateUsage on _gemini_convert_messages_with_history import

Adds `# pyright: ignore[reportPrivateUsage]` on the import of the
shared `_gemini_convert_messages_with_history` helper. The function is
already used by gemini/chat, context_caching, and
vertex_and_google_ai_studio_gemini; reusing it here avoids duplicating
the OpenAI-to-Gemini message conversion. Resolves basedpyright budget
CI failure on PR #36981.

Generated with [Devin](https://devin.ai)

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

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-21 16:33:55 -04:00
ryan-crabbe-berri
ed02a121dd
Merge pull request #37878 from BerriAI/litellm_ruff_no_duplicate_definitions
test: enforce F811 so a duplicate definition cannot silently replace the first
2026-08-21 12:49:43 -07:00
ryan-crabbe-berri
e9d40a8f73 test: enforce F811 so a duplicate definition cannot silently replace the first
A name bound twice keeps only the second binding. In `tests/` that is nearly
always a repeated import, harmless but misleading, and the same rule is what
catches the cases that are not harmless: a local that shadows an import the
module still calls, and a second `def test_x` that quietly replaces the first.

311 of the 344 sites were repeated imports and came out with ruff's own fix.
The remaining 33 needed a decision. Four modules imported a name they never
used because a local definition below already shadowed it. Two comprehensions
bound `call` over `unittest.mock.call`, which those modules import and use.
One test rebound the two module handles its nested reload closure had captured.
One class attribute shadowed an unused `status` import.

The load-test fixtures move to a conftest, which is how pytest is meant to share
them, so the test module no longer imports three fixture names it never calls.
The nine `prisma_client` parameters keep a narrow `noqa`: pytest resolves that
fixture by name before the body runs, so the parameter never shadows anything.
2026-08-21 12:06:19 -07:00
tin-berri
4307b34aca
fix: omit thinking.type=disabled for always-on thinking Claude models (#37510) 2026-08-21 10:27:26 -07:00
Yassin Kortam
7da34e8aed
fix(proxy): make per-model budgets track spend, enforce, and report the same counter (#37736)
Per-model budgets were three separate things pretending to be one. The
enforcement check, the post-call increment and the info endpoints each derived
their own cache key, so a budget could refuse traffic at 429 while /key/info
reported zero usage, and a Bedrock model id never matched a budget keyed on the
bare family name. /user/new echoed a model_max_budget back and stored an empty
dict, and nothing enforced a user-scoped per-model budget at all.

One owner now builds the counter key from the configured budget model, and
enforcement, the increment and the info endpoints all read it. Bedrock ids
resolve through the model-cost map. Auth carries the user's budget onto the
token on every branch that reaches the spend hook, including JWT and
auto-registration. Native passthrough attaches the three budget metadata keys
its StandardLoggingUserAPIKeyMetadata does not carry, so /anthropic/... and
/bedrock/... traffic is counted and capped like /v1/chat/completions.

The dashboard gains the per-model budget editor it never had, on the key create,
key edit and internal-user edit forms. It is read-only without an enterprise
license, matching the write gate the proxy already enforces, and an untouched
budget is left out of an update so an unrelated edit cannot trip that gate.

The editor hydrates from either BudgetConfig spelling, since model_max_budget is
a plain dict that the proxy stores exactly as the client sent it, and it carries
through the fields it does not model. Without both, editing one model would drop
another model row entirely and silently discard its tpm_limit and rpm_limit.

/user/info refreshes its local copy of the user field by field after a save, so
model_max_budget joins that list. Left out, a saved cap read back as the old one
when the form was reopened, and clearing the row to recover would then wipe the
value that had actually persisted.

A zero-dollar cap is the strictest limit expressible, not the absence of one,
so it is enforced rather than skipped on falsiness, spend exactly at the cap is
refused the way every sibling budget check already refuses it, and a counter
that was never written reads as zero spend rather than as unknown. The usage
endpoints read every counter in one batched lookup, so a large model_max_budget
cannot fan out into one concurrent cache call per configured model.

Every auth path honours the same zero-cost skip flag, so none of them can refuse
a free request that another serves. The custom-auth helper gains the flag it
never had, which also changes its pre-existing key and end-user checks.

The compaction summary gate checks the user scope alongside the key and end-user
ones. This file propagates all three budgets into the summary subrequest, so
enforcing only two let compaction increment a counter it could not be refused by.

Custom auth attaches the user's budget to the token unconditionally, since the
post-call spend hook reads it there: gating the attach on the same condition as
enforcement left the counter uncharged whenever the request was not itself
enforceable. An entry that will not validate is skipped rather than raised on,
so one malformed scope cannot abort every other scope's increment or turn a
config typo into a 500.

The edit forms re-seed the budget editor when a different key or user is loaded.
Its rows are seeded once and cannot re-read their own value prop, so without
this a save wrote the previously loaded record's budgets onto the current one.

Only the built-in provider pass-through routes carry the budget metadata.
get_model_from_request deliberately resolves no model for a user-defined
pass-through, since its body is forwarded verbatim and names an upstream model,
so attaching there would charge a counter nothing on that route can refuse.
2026-08-21 09:47:52 -07:00
longwind48
86efa2bcfd feat(bedrock): serve gpt-5.6 cross-region inference profiles on bedrock runtime
GPT-5.6 Sol, Terra and Luna reached the bedrock-runtime data plane on
2026-08-17, separately from the existing bedrock-mantle path. On runtime
they are served only through cross-region inference profiles, so
bedrock/us.openai.gpt-5.6-* had no cost map entry and fell through to the
Invoke route, which rewrites the token cap to max_tokens and is rejected
as unsupported_parameter on both /v1/chat/completions and /v1/responses.

Register the Geo and Global profiles as bedrock_converse so routing
reaches Converse, which AWS documents and serves for these models, and
price each profile from its own published rate table. No bare key: the
control plane reports inferenceTypesSupported INFERENCE_PROFILE with no
on-demand throughput, so a bare id is not invocable.

Declare the published cache-read and cache-write rates. Bedrock rejects
an explicit cachePoint block for these models, so supports_prompt_caching
stays off, but it caches long prefixes implicitly and reports the cache
tokens in usage either way. Without the cost fields a cache-read turn
bills only its uncached tokens: measured against live Bedrock, a
15609-token cached prefix came to $0.000176 instead of $0.00876095.
Clients that resend a long prefix every turn are the worst affected.

Reasoning stays unadvertised. Converse rejects the Anthropic-shaped
thinking block LiteLLM sends for reasoning_effort; the shape these models
accept is additionalModelRequestFields {"reasoning": {"effort": ...}},
which needs a transform change tracked by #34105. Advertising it without
that change is what made the earlier attempt in #37307 fail.
2026-08-21 19:03:04 +08:00
bhuvan2134686
aef09aca18 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_scx_ai_provider
Resolves the three conflicts against the JSON provider registry refactor. The
hardcoded api.scx.ai base-url branch in get_llm_provider_logic.py is dropped in
favour of the generic JSONProviderRegistry.get_by_base_url lookup, which reads
the same base_url and api_key_env from providers.json and additionally honours
an explicitly passed api_key. constants.py and types/utils.py keep both the
cognition and scx-ai entries added on either side.
2026-08-21 17:27:49 +10:00
mateo-berri
64e993773d chore(typing): clear 1.5k basedpyright Any errors across 54 files
Retypes the 54 highest-density reportAny/reportExplicitAny sources with real
types instead of shuffling the ceilings around: typed prisma table Protocols so
the untyped client surface stops at the query, local TypedDicts for JSON and
dict payloads, concrete chunk and logging types on the streaming and callback
surfaces, and 3-argument getattr with a Callable annotation where an SDK object
is genuinely duck-typed

No cast(), no type: ignore, no noqa, no suppression comments, and no new Any
annotations. Whole-tree basedpyright drops 1,941 errors with no rule rising
anywhere, and all three budget files are ratcheted so the cleared headroom
cannot silently grow back

Adds a GDC regression test pinning the named AttributeError that the typed
credential accessor now raises when with_gdch_audience is missing
2026-08-21 05:25:22 +00:00
Mateo Wang
e17988f4fe
Merge pull request #37766 from BerriAI/litellm_sagemaker_chat_inference_component_header
Some checks failed
Publish basedpyright base counts / publish (push) Waiting to run
Code Quality Checks / code-quality (push) Waiting to run
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
LiteLLM Rust / rustfmt, clippy, test (push) Has been cancelled
fix(sagemaker_chat): send the inference component header and honor hf_model_name
2026-08-20 21:01:16 -07:00
Mateo Wang
ecd18c5a82
Merge pull request #37763 from BerriAI/litellm_fix_cognition_swe_1_7_pricing
fix(cognition): price swe-1.7 at the standard tier, add swe-1.7-lightning
2026-08-20 21:01:11 -07:00
Mateo Wang
4b29702418
Merge pull request #37003 from BerriAI/litellm_forward_bedrock_response_headers
fix(bedrock): forward provider response headers on chat completions
2026-08-20 20:35:13 -07:00
ryan-crabbe-berri
b76def0e5d
test: require a match= on broad pytest.raises, and drop duplicate parametrize cases (#37769)
`pytest.raises(Exception)` with no `match=` passes on any error that broad. A
TypeError from a refactor, a botched fixture, an import that moved: all of them
read as the rejection the test claims to police, so the test goes green for the
wrong reason and stays green after the behaviour it guards is gone.

PT011 closes that gap for the 317 sites B017 could not reach, because B017 only
fires on a single-statement body with no `as e` binding. Each pattern here is the
message the code actually raised, recorded by running the sites under a plugin
that logged the concrete type and text per call site, so the assertions describe
observed behaviour rather than a guess. Where a site raises more than one message
across its parametrize cases, the pattern is an alternation of what was seen;
where the exception carries an empty `str()` and puts the text on `.message`, the
site keeps a narrow `noqa` with the reason.

PT014 removes four parametrize cases that were listed twice. The duplicate re-runs
an assertion that already passed, and it usually marks a case someone meant to
vary and forgot to edit.
2026-08-20 20:24:49 -07:00
mateo-berri
2ea633d223 fix(sagemaker_chat): send the inference component header and honor hf_model_name
sagemaker_chat never put X-Amzn-SageMaker-Inference-Component on the request, so any endpoint
backed by inference components answered 400 INFERENCE_COMPONENT_NAME_MISSING and the call never
reached the container. The legacy sagemaker provider has built that header from model_id since
#8889, and this brings the chat provider in line. It goes on in validate_environment, which runs
before the request is SigV4-signed, so the signature covers it

The request body also always named the endpoint rather than the served model, which containers
that validate the body's model answer with a 404. hf_model_name now becomes the body's model
when it is set, and endpoints that do not set it keep sending exactly what they send today
2026-08-20 19:57:46 -07:00
Mateo Wang
354f497faf
Merge pull request #37751 from BerriAI/litellm_fal_gpt_image_2_keyed_pricing
fix(fal_ai): price gpt-image-2 per size and quality from request params
2026-08-20 19:51:26 -07:00