A failing `assert` raises AssertionError, which is an Exception, so an
`except Exception` in the same test catches it, the handler returns, and
pytest records a pass on exactly the regression the assertion guards. TQ009
finds the 29 tests in the suite shaped that way and seeds the ceiling at the
23 left after this branch.
Six of them were in tests/test_litellm/. Narrowing the try to the call and
moving the assertions below it turned two of them red:
- test_sentry_sample_rate asserted SENTRY_API_SAMPLE_RATE was written back
to os.environ, which set_callbacks never does. It now reads the rate off
the initialised client, which is where the value actually lands.
- test_convert_tool_response_with_url_image downloaded from a host that no
longer resolves, and the product swallows a fetch failure, so the skip the
author wrote could never fire and the assertion failure it caught instead
read as a network skip. The download is now served from a stubbed
transport, so the media path runs offline and deterministically.
Also stops two sentry tests leaking a MagicMock into sys.modules, and derives
the budget-coverage test's rule set from the checker instead of a literal
list that goes stale on every new rule.
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>
Gemini API Maps-grounded prompts were billed as web search and Vertex AI Maps-grounded prompts were not billed at all. Classify grounding metadata per candidate into web search vs Maps requests, carry a distinct google_maps_grounding_requests usage counter through non-streaming and streaming paths, and price it via the new google_maps_grounding_cost_per_query cost map key with per-query and per-prompt defaults keyed off web_search_billing_unit. Fixes#35906
Live probes against every gemini_native_audio model on both providers show
setup accepts a valid prebuilt voice and 1007s only unknown voice names, so
the strip predicate rested on a false premise and silently discarded the
client's voice on AI Studio native-audio sessions
Vertex AI Live accepts a speechConfig on setup for native-audio models, so
stripping it silently discarded the voice a client asked for. Confirmed against
a live BidiGenerateContent session on gemini-live-2.5-flash-native-audio and on
gemini-live-2.5-flash-preview-native-audio-09-2025: both return setupComplete
with speechConfig present.
The strip stays in place for Google AI Studio, which was never verified to
accept it, via an overridable predicate matching the existing
_include_function_response_id pattern. The responseModalities TEXT to AUDIO
coercion is unchanged, since Vertex does reject TEXT on these models.
Now that /v1/messages routes provider failures through exception_type, an
Anthropic permission_error fell through the anthropic branch to the generic
APIConnectionError and reached the client as a 500 where the raw exception
used to answer 403. Map 403 to PermissionDeniedError so the status survives
on every route.
The fixture was matched by host alone, so it answered any method and path and
the tests would have stayed green if the request went somewhere else. It now
matches POST on the Foundry route, and asserts the route was called.
Worth pinning on its own: the real path carries a /models prefix, which the
first attempt at this got wrong, so the match now also holds the routing in
place rather than only the retry.
The source drops the rejected field in place, so a payload shared across
tests could in principle be consumed by whichever case ran first. It does
not happen today, because the request is copied before the transform runs,
and the cases pass in reverse and async-first order alike. Building the
payload per call costs nothing and keeps that true if the copy ever goes.