Commit graph

2421 commits

Author SHA1 Message Date
Mateo Wang
d4f6b4491d
Merge pull request #38656 from aaaaaandrew/litellm_preserve_stream_selected_model
fix(streaming): preserve provider model for cost calculation
2026-08-28 13:00:35 -07:00
yuneng-jiang
4d9025c2bf
Merge pull request #38677 from BerriAI/litellm_/mutmut-test-coverage-gaps-b4dd83
test: close mutation-testing gaps in container, skills and openai-like config factories
2026-08-28 10:58:16 -07:00
Mateo Wang
3a52ae0a3f
Merge pull request #38615 from BerriAI/litellm_mantle_gpt5_cost_tiers
fix(model_prices): add bedrock_mantle gpt-5.5/5.4 272K tiers, align sol with AWS invoice
2026-08-28 10:09:52 -07:00
Yuneng Jiang
0ec619e7d1
test: close mutation-testing gaps in container, skills and openai-like config factories
Mutation testing surfaced three factory functions whose tests ran against them
but asserted nothing that a mutation could break, so every planted bug survived.

- litellm/llms/litellm_proxy/skills/code_execution.py: the OpenAI and Anthropic
  tool schemas were unpinned (the Anthropic one was not reached by any test at
  all) and the handler's default fallbacks were unchecked
- litellm/containers/endpoint_factory.py: the endpoints.json contract, the
  generated sync/async function set and the response-type mapping were unpinned
- litellm/llms/openai_like/dynamic_config.py: the generated Responses API config
  class had no coverage of auth header, URL resolution or the store override

The openai_like tests clear _responses_config_cache around each test. Without
that, the module-level cache hands back a class built before the mutation and
the tests pass against mutated code.

Verified by re-running mutmut per scope:
  llms/litellm_proxy  45.2% -> 62.8%  (70 mutants newly killed)
  containers          36.8% -> 84.3%  (45 mutants newly killed)
  llms/openai_like    55.7% -> 66.9%  (34 mutants newly killed)
2026-08-28 09:28:14 -07:00
tin-berri
ca0b951a43
feat(spend): report prompt caching savings as total and gateway-attributed (#38134)
* feat(spend): report prompt caching savings as total and gateway-attributed

`prompt_caching_savings_spend` credited every cached request, including caching a
client asked for with its own `cache_control` and caching a provider does implicitly,
so the number overstated what the gateway had any hand in.

Gating that column in place would have fixed the overstatement by changing what the
column means, leaving rows written before the change saying "all caching savings" and
rows after saying "gateway-injected only" with nothing to tell them apart, and forcing
a decision about rewriting history. It also breaks the cache-leakage estimate on the
dashboard, whose numerator would be gated while its denominator, the cached token
counts, would not, so the rate it extrapolates from would be quietly diluted.

Report both instead. `prompt_caching_savings_spend` keeps meaning every net dollar
caching saved, which is what a customer means by "what did caching save me", and the
new `gateway_injected_caching_savings_spend` carries the subset litellm caused by
injecting the breakpoints itself. Both are derived from the same marker, so this
changes what is done with it rather than how it is obtained.

The attributed figure is normally the smaller of the two, being a subset of the same
requests, but not always: a request that writes cache it never reads has negative net
savings, and excluding such a request can lift the attributed figure above the total.

Also stops the marker riding into a fallback leg. The fallback rebuild spread the
failed attempt's metadata forward, so a deployment that injected nothing inherited the
marker and was credited anyway, which silently restored the very overstatement this
separates out.

* fix(bedrock): credit gateway caching where the tool cachePoint is placed (#38478)

The savings marker records breakpoints litellm placed, and a tool_config
injection point becomes one only in the converse transform, and only when the
request carries tools. The prompt hook cannot see either condition, so marking
on the point's presence credited request shapes that cached nothing, while
Bedrock tool caching the gateway did cause went uncredited.

Record it at the placement site instead. The marker's reader also resolves its
bucket by value now: litellm_params declares litellm_metadata as None on every
request, so asking the shared name resolver named a bucket that was not there
and the mark was dropped.
2026-08-28 00:19:06 -07:00
tin-berri
39e5b0c2d1
fix(anthropic): drop and self-heal empty thinking blocks on /v1/messages (#38625)
* fix(anthropic): drop and self-heal empty thinking blocks on /v1/messages

* test(anthropic): pin early-signature carry across the blank thinking chunk skip
2026-08-27 21:41:56 -07:00
Andrew Mattie
134a4cd9fd fix(streaming): preserve provider model for cost calculation 2026-08-27 23:26:00 -05:00
Yuneng Jiang
a4049b730c
fix(exceptions): keep a refused connection an APIConnectionError
#38318 taught exception_type to map upstream status codes for providers with
no branch of their own. It reads the status code off the exception, but
_handle_error stamps 500 onto every failure that never carried one, so a
refused connection reached the mapper wearing a status code nothing upstream
had sent, and came back as InternalServerError instead of APIConnectionError.

The two are not interchangeable to a caller: a 5xx says the provider answered
and failed, which the router treats as a reason to cool the deployment down,
while a connection error says the request never landed.

BaseLLMException now records whether its status code was received or
synthesized, _handle_error sets that when it invents the 500, and the status
mapper declines to act on a code litellm made up, so those failures fall
through to the APIConnectionError the branch was always meant to produce.

Genuine upstream 5xx responses are untouched, which the second test pins.
The search transformation assertion #38318 had loosened to InternalServerError
goes back to APIConnectionError for the same reason.
2026-08-27 21:00:24 -07:00
tin-berri
3300fc3a96
fix(moonshot, together_ai): send the reasoning effort Kimi K3 accepts (#38611)
* fix(moonshot, together_ai): send the reasoning effort Kimi K3 accepts

Moonshot documents reasoning_effort as a top-level chat completions field for its reasoning
models, and defaults it to max, but MoonshotChatConfig builds its supported params by
subtracting from the OpenAI base list, which never carried that param. An explicit level
raised UnsupportedParamsError before the request left the proxy, so low and high were
unreachable and every call ran at the provider default

Together accepts low, high and max on Kimi K3. The per-model clamp added for the gpt-oss
family folds max down to high for every model except deepseek-ai/DeepSeek-V4-Pro, so a caller
asking for max silently got roughly half the reasoning budget they paid for

Moonshot now offers reasoning_effort whenever the registry says the model reasons. Together
sends a level the map entry declares unchanged, and keeps its existing table for every level
an entry does not name, so the only value that moves is Kimi K3 at max

* fix(moonshot): unwrap the bridges' effort object to the level string
2026-08-27 20:46:46 -07:00
mateo-berri
837bcba32d fix(model_prices): add bedrock_mantle gpt-5.5/5.4 272K tiers, align sol with AWS invoice
AWS bills a Bedrock GPT-5.5 or GPT-5.4 prompt past 272K tokens under the long-context usage types for the
whole prompt, at 2x input, 2x cache read, and 1.5x output, and the cost map only had the flat rates, so a
300K prompt was logged at half of what the invoice charges. The map's promo rates for gpt-5.6-sol are 20%
under the $5.50 input, $33.00 output, $0.55 cache read, and $6.88 cache write per million the invoice bills.

Adds the *_above_272k_tokens fields to gpt-5.5 and gpt-5.4, moves sol's base and tier rates to the invoiced
ones, replaces the test that pinned the flat behaviour with one that pins the invoiced numbers, and updates
the sol pins in the mantle transformation tests
2026-08-27 19:31:45 -07:00
tin-berri
49e6081978
fix(anthropic): resolve /v1/messages effort tiers through the capability owner (#38492)
* fix(anthropic): resolve /v1/messages effort tiers through the capability owner

The bridge normalizer read three supports_*_reasoning_effort booleans of its own, so it
answered "which levels does this deployment take" independently of the resolver behind
/model_group/info. The two disagreed: a proxy advertising kimi-k3 max forwarded high.

Degrade against resolve_supported_reasoning_efforts instead, with the chains as a declared
table. When no step of a chain is accepted, the fallback is read off that same resolved set
rather than assumed, since an entry naming its levels outright can exclude the tiers the
per-level flags treat as unconditional. none is never chosen as that fallback, being an off
switch rather than a tier, and a deployment accepting no tier at all keeps the floor every
deployment degraded to before.

* test(anthropic): pin the normalized effort at the /v1/messages request boundary

The existing coverage stopped at normalize_reasoning_effort_value, so nothing failed if the
handler dropped or overwrote the normalized tier on its way into completion_kwargs. Drive
_prepare_completion_kwargs instead and assert on the kwargs handed to acompletion, in both the
string and the dict effort shapes, including the provider-prefixed model name the handler is
actually called with.

Against the pre-fix normalizer the fallback case fails, and against the baseline before a map
entry could declare its levels 7 of the 12 fail, so the boundary is pinned rather than restated.
2026-08-27 19:13:13 -07:00
Mateo Wang
10cd9259a3
Merge pull request #38100 from FelipeRodriguesGare/bugfix/tencent-thinking-extra-body
fix(tencent): route thinking through extra_body in chat completions
2026-08-27 18:23:11 -07:00
tin-berri
1ab6fd89d2
fix(anthropic): carry the effort tier only where the target declares reasoning_effort (#38592)
The /v1/messages bridge decided a Claude target could take `reasoning_effort` from
the model name, which says nothing about the params the provider in front of it
accepts. Snowflake serves Claude over the Anthropic dialect and declares `thinking`
alone, so `get_optional_params` raised `UnsupportedParamsError` before the request
reached the wire: every adaptive request carrying an effort tier turned a 200 into
a 400 for all seven of its Claude entries.

The tier is now offered only where the target declares the param, reading the same
`get_supported_openai_params` the sibling `_supports_prompt_cache_key` reads twelve
lines up. A target declaring neither carrier keeps its bare `thinking` block, which
is what this bridge sent before it carried a tier at all.

Without a resolved provider the tier stays behind rather than being offered blind.
Resolving one from the model's prefix instead would run an OAuth device flow for
github_copilot and chatgpt, blocking for minutes, and one of the two callers in that
position is a logging callback. The copilot case is pinned by a test.
2026-08-28 00:41:46 +00:00
tin-berri
44d84360fb
fix(anthropic): carry the adaptive effort tier to every bridged Claude target (#38533)
/v1/messages forwarded `thinking` verbatim for a Claude-family model and then returned,
carrying `output_config.effort` only when the model string started with a Bedrock prefix.
Every other bridged provider got a bare adaptive thinking block, so the caller's effort did
nothing: max and minimal produced byte-identical upstream bodies.

Send those targets the tier as `reasoning_effort`, which is the param they take. Bedrock keeps
taking `output_config`, since the two are not interchangeable there: an application inference
profile ARN resolves to no chat config, so `reasoning_effort` is dropped and the tier vanishes,
and a provider that rebuilds `output_config` from it overwrites a caller-set `thinking.display`
on the way. The tier stays a plain string, the summary already travelling inside the forwarded
`thinking` block. Adaptive with no tier, and budgeted thinking, both stay exactly as they were.
2026-08-27 15:48:42 -07:00
tin-berri
30ff3723b2
feat(model_prices): let a map entry declare its exact reasoning_effort levels (#38481)
Kimi K3 accepts exactly low, high and max, defaults to max, and always thinks.
The map could not say that: medium and high have no supports_*_reasoning_effort
flag because every other reasoning model takes them, so the ten kimi-k3 entries
carried supports_reasoning alone and resolved to unknown. The dashboard then fell
back to a capability-blind level list that deliberately omits max, which is why a
kimi-k3 tier cannot be set to max thinking today.

Add reasoning_effort_levels, an array key in the shape the map already uses for
supported_endpoints and supported_modalities. Where present it is read first and
wins whole; every other entry keeps answering through the per-level flags,
unchanged. It is deliberately a different name from the computed
ModelGroupInfo.supported_reasoning_efforts, which stays derived from a group's
deployments and is never seeded from one deployment's model_info.

The levels are per entry rather than per model, because the deployments differ:
Moonshot, Together, Fireworks and Azure Foundry all forward the level unchanged
and get the model's own low/high/max, while Perplexity documents a six-value
enum it maps down internally and gets that. The /v1/messages degradation chain
consults the same declaration, so the level the map advertises is the level that
path forwards.
2026-08-27 15:38:01 -07:00
Mateo Wang
4ef1c28877
Merge pull request #38431 from BerriAI/litellm_fix_messages_native_tools
fix(anthropic-adapter): pass provider-native and OpenAI-format tools through on /v1/messages
2026-08-27 15:24:07 -07:00
Mateo Wang
649dc23d6a
Merge pull request #38465 from BerriAI/litellm_lit6103_tool_reference_passthrough
fix(anthropic): carry tool_reference tool results through the guardrail translation round trip
2026-08-27 15:23:51 -07:00
Mateo Wang
0c0a1f4eb1
Merge pull request #38457 from BerriAI/litellm_realtime_audio_output_tokens
fix(realtime): bill Gemini Live native-audio output tokens at the audio rate
2026-08-27 15:23:46 -07:00
Mateo Wang
e5b2f5bec2
Merge pull request #38561 from BerriAI/litellm_transcription_srt_vtt_synthesis
fix(transcription): synthesize srt/vtt output for adapters without native subtitle formats
2026-08-27 15:23:38 -07:00
mateo-berri
9e9c7e621f Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_together_reasoning_effort
# Conflicts:
#	litellm/llms/together_ai/chat/transformation.py
#	tests/test_litellm/llms/together_ai/chat/test_together_ai_chat_transformation.py
2026-08-27 14:33:47 -07:00
mateo-berri
c860d511db Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_realtime_audio_output_tokens 2026-08-27 14:25:08 -07:00
Mateo Wang
67c7b97fd2
Merge pull request #38207 from BerriAI/litellm_registry_audit_bedrock_sol_anthropic_1hr
fix(model_prices): rolling registry audit - verified models and rates for Novita, DeepInfra, W&B, Bedrock Sol, Gemini, Fireworks, Azure gpt-5.6, Mistral, Together
2026-08-27 13:42:51 -07:00
mateo-berri
5d8eb049b5 fix(transcription): drop words from srt/vtt replies when synthesis falls back 2026-08-27 13:36:31 -07:00
mateo-berri
f2e64d9818 fix(gemini): ignore non-string response_format when deciding on word timestamps 2026-08-27 13:15:17 -07:00
mateo-berri
c251703e8b fix(realtime): bill trailing audio when a Gemini transcribe Live session closes 2026-08-27 12:43:28 -07:00
mateo-berri
5b80fb0fc0 fix(transcription): synthesize srt/vtt output for adapters without native subtitle formats
Extract the Soniox SRT/VTT cue grouping and rendering into a shared
litellm_core_utils/audio_utils/subtitle_utils module, have Gemini
transcription request word timestamps whenever response_format is srt or
vtt, and let the http handler rewrite the response text into the
synthesized subtitle document (dropping the internally requested words
array) for any provider config that opts in via
supports_subtitle_synthesis
2026-08-27 12:30:13 -07:00
Mateo Wang
493bca667b
Merge pull request #38540 from BerriAI/litellm_gemini_35_transcribe
feat(gemini): day-0 support for gemini-3.5-transcribe and transcribe-live
2026-08-27 12:09:40 -07:00
mateo-berri
462942de65 fix(gemini): bill transcribe-live sessions from streamed audio duration
Gemini Live sends no usageMetadata and no turnComplete for
gemini-3.5-transcribe-live sessions, so realtime spend logged as 0.0.
Attach estimated usage to the input_audio_transcription.completed event
using Google's published billing estimate (25 audio tokens/sec of input,
175 text tokens/min of output) derived from the streamed pcm16 audio
duration, gated to audio_transcription-mode models so conversational
Live models keep billing through usageMetadata. Also capture that usage
in the provider_config backend path so realtime cost calculation sees it.
2026-08-27 11:03:41 -07:00
Mateo Wang
ca9007be39
Merge pull request #38093 from BerriAI/litellm_lit5458_rerank_sigv4_bearer_fix
fix(bedrock): sign rerank requests with the shared header-filtered SigV4 helper (internal copy of #36462)
2026-08-27 10:57:10 -07:00
mateo-berri
e44e2fe242 fix(gemini): keep transcription-only Live turn usage when generationComplete arrives without a delta 2026-08-27 10:35:22 -07:00
mateo-berri
ef4c84dc36 feat(gemini): day-0 support for gemini-3.5-transcribe and transcribe-live
Adds a Gemini audio transcription config that maps /v1/audio/transcriptions
onto the Interactions API (speaker attribution and word timestamps land on
the OpenAI verbose_json shape), registers both models with published pricing,
routes text-only Live sessions to TEXT responseModalities so
gemini-3.5-transcribe-live sessions survive, and makes the token-priced
transcription cost path provider-aware instead of hardcoding OpenAI.
2026-08-27 10:08:23 -07:00
Devin AI
5d26ae0fcd fix(model_prices): absorb Databricks/Z.AI and xAI registry PRs, add Together and Azure deprecation dates
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-27 13:16:12 +00:00
Devin AI
4b3e82b8a3 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_registry_audit_bedrock_sol_anthropic_1hr 2026-08-27 13:03:14 +00:00
Devin AI
63d7920f8b refactor: dedupe server_tool_use web search reads and type fresh test locals
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-27 08:01:52 +00:00
mateo-berri
edde2e50ef fix(openai): drop tool_reference parts from tool messages at the chat boundary
OpenAI's chat completions API rejects tool_reference content parts in
role tool messages, so a mixed text plus reference tool result carried
through the Anthropic adapter turned a previously working request into
a 400 on chat-routed OpenAI and Azure deployments. Strip the reference
parts there, keeping a reference-only result as an empty-text tool
message so the preceding tool_call stays answered, mirroring the
Responses bridge skip.
2026-08-26 23:43:41 -07:00
mateo-berri
e26ea0bc95 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_lit6103_tool_reference_passthrough 2026-08-26 23:07:11 -07:00
mateo-berri
99af9ad9eb fix(anthropic): carry tool_reference tool results through the guardrail translation round trip 2026-08-26 22:35:46 -07:00
mateo-berri
449c091391 fix(realtime): carry audio output tokens into response.done usage so Gemini Live native audio bills at the audio rate 2026-08-26 21:10:12 -07:00
mateo-berri
6d7fafa347 fix(anthropic): close hybrid tool-name allowlist gap and keep native tools through guardrails 2026-08-26 20:37:11 -07:00
mateo-berri
815fa0ff08 fix(anthropic_adapter): carry web search usage into /v1/messages cost breakdown
For non-Anthropic models served over /v1/messages, the outer wrapper recomputes
cost over the adapter-translated Anthropic response dict. That dict dropped every
web search usage signal, so the recompute overwrote the correct cost breakdown
with a token-only one: x-litellm-response-cost-tool-usage read 0.0 and
x-litellm-response-cost-original excluded the search cost, while the total kept it.

The adapter now maps web search request counts (from Usage.server_tool_use or
Gemini's prompt_tokens_details) into usage.server_tool_use.web_search_requests,
matching the Anthropic API shape, and the Gemini web search cost calculator falls
back to server_tool_use when prompt_tokens_details carries no count. The shared
get_web_search_requests helper is now public since five modules consume it.

Resolves LIT-6288
2026-08-26 18:14:10 -07:00
mateo-berri
f7af44a505 fix(anthropic-adapter): pass provider-native and OpenAI-format tools through on /v1/messages 2026-08-26 17:55:15 -07:00
mateo-berri
901bea41e7 Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_gemini_latest_cache_read_rates
# Conflicts:
#	tests/test_litellm/llms/gemini/test_cost_calculator.py
2026-08-26 17:04:59 -07:00
mateo-berri
1e1c231076 fix(model_prices): bill gemini -latest/preview alias cache reads at 10% of input 2026-08-26 16:52:09 -07:00
Mateo Wang
40005cf7f8
Merge pull request #37724 from bisma-nawaz/fix-37647-staging
fix: map Gemini ON_DEMAND_FLEX traffic type to flex service tier
2026-08-26 16:51:56 -07:00
mateo-berri
885d95d71b Merge remote-tracking branch 'origin/litellm_internal_staging' into pr37724
# Conflicts:
#	tests/test_litellm/test_cost_calculator.py
2026-08-26 16:33:28 -07:00
mateo-berri
dca5144dba Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_gemini_maps_grounding_cost
# Conflicts:
#	litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py
2026-08-26 16:27:40 -07:00
mateo-berri
9003b02c3c Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_gemini_web_search_unique_queries_36377
# Conflicts:
#	tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py
2026-08-26 15:39:17 -07:00
Mateo Wang
f7220556e1
Merge pull request #38395 from BerriAI/litellm_gemini_live_voice
fix(gemini-realtime): keep the client's voice on Vertex AI native-audio Live
2026-08-26 15:34:53 -07:00
Hamza Shah
5d4f8b36a6
fix(fireworks_ai): stop using the trace id as the session affinity key (#35754)
get_fireworks_session_id fell back to litellm_trace_id when no session id was
given. That id is generated per request (uuid4 when absent), so x-session-affinity
carried a different value every time and Fireworks prompt caching never hit;
cached_tokens stayed 0 across identical prompts.

The None path the original change described was effectively unreachable because
of it. Drop the fallback so affinity comes only from an id the caller actually
supplied: litellm_session_id, session_id, or metadata.session_id.

Callers who were relying on a trace id for affinity can pass litellm_session_id
instead, which is stable across the requests they want grouped.

Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com>
2026-08-26 18:33:56 -04:00
mateo-berri
241daa4cb7 Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_gemini_maps_grounding_cost
# Conflicts:
#	type-discipline-budget.json
2026-08-26 15:33:28 -07:00