litellm/tests/unit/llms/gemini
Mateo Wang b51b9b7d43
fix(anthropic)!: answer 400 for a malformed tool_result text on /v1/messages and count_tokens (#47347)
* fix(anthropic): answer 400 for a malformed tool_result text on the Responses bridge and count_tokens

A tool_result text block whose text is null, a number, or missing made
/v1/messages and /v1/messages/count_tokens answer 500 on OpenAI and Azure
models. Both routes now answer 400 naming the block, and drop the block
when drop_params is on.

* fix(anthropic): keep a missing tool_result text as empty text, bound the error, honor body drop_params on count_tokens

* fix(anthropic): apply the tool_result text check on the chat/completions bridge and every count route

The chat/completions bridge used for Azure, Gemini and other providers now answers the same 400 for a
null or numeric tool_result text and drops the block under drop_params, so a model group that mixes
OpenAI with another provider answers the same way on every deployment. Gemini and Vertex count_tokens
apply the same check, count_tokens turns on drop_params for Claude Code and Codex the way /v1/messages
does, and a tool_result without a tool_use_id is checked too

* fix(proxy): answer 400 for a malformed tool_result text on count_tokens models without a count API

Providers with no token counting API fall back to the local tokenizer, which counted a null or numeric tool_result text as fine while /v1/messages on the same model answers 400. The fallback now runs the same check, unless drop_params applies to the deployment or the request

* fix(proxy): honor request drop_params on the local count fallback when no deployment resolves

* test(integration): cover non-string tool_result text on both /v1/messages bridges and count routes

* fix(anthropic): keep a dropped tool_result on Mistral and reject it before a guardrail rewrite

Under drop_params a tool_result whose only text block was null or a number became an
empty tool message, and Mistral's empty-message filter removed it, so Mistral answered
400 for a missing tool reply. The filter now only removes empty assistant messages,
which is what Mistral rejects.

A guardrail that rewrites structured_messages wrote the dropped text back as "", so a
null or numeric text was silently blanked even without drop_params. The rewrite path now
answers the same 400 as the bridges unless the request or global drop_params is set.

* test(integration): cover the Mistral empty tool reply and the guardrail rewrite tool_result text check

* fix(anthropic): keep a malformed tool_result text through a guardrail rewrite and keep Mistral's null-message drop

A guardrail that rewrites structured_messages now writes the caller's null or numeric
tool_result text back in place, so the route's bridge and the deployment's drop_params
decide after routing, the same as with no guardrail. The rewrite path no longer answers
its own 400 before routing.

Mistral drops a null-content system or user message again, as main does, and only a tool
reply is exempt from that filter so a dropped tool_result keeps its tool call.

* test(mistral): expect empty system and user turns dropped and the empty tool reply kept

* perf(anthropic): restore kept tool_result text parts in one linear pass

* test(integration): cover the tool_result text restore across tool_results, the chat bridge, and a batch upload behind a rewriting guardrail

* fix(anthropic): let guardrails scan malformed tool_result text and honor drop_params on native Anthropic

A rewriting guardrail now sees a non-string tool_result text as its JSON text and the write-back
restores the caller's value by the sanitized tool_use_id when the guardrail left it unchanged.
Native Anthropic drops the bad block under drop_params on /v1/messages and count_tokens, and
Mistral keeps an empty tool reply only when it answers a tool call, sent as empty text.

* fix(anthropic): compare a kept tool_result text to its scanned form once and align the wire tests with drop_params

* test(integration): cover native Anthropic drop_params resolution, guardrail scanning of numeric tool_result text, and the Mistral orphan tool message

* fix(count_tokens): honor the router's default drop_params when counting a malformed tool_result text

* test(count_tokens): send the tool_result count tests through a real request body instead of a mocked parser

* test(count_tokens): send the native Claude count test through a real request body

* fix(anthropic): keep each non-string tool_result text when tool_use_ids sanitize to the same id

* fix(anthropic): keep non-ASCII characters in the guardrail's view of a non-string tool_result text

* test(gemini): mock the count_tokens upstream with respx instead of patching the client factory

* fix(mistral): keep an empty tool reply only for an open call, and forward non-object message items on the native drop

An empty tool reply re-answering a call the conversation already moved past made Mistral reject the request, and a tool call id that is not a string crashed the filter. Native Anthropic under drop_params assumed every message item was an object.

* fix(mistral): find answering tool replies in one pass instead of copying the open call set per reply

* fix(mistral): ignore dropped empty placeholders when tracking open tool calls

* fix(mistral): drop an empty tool reply when another reply to the same call carries the result

* refactor(anthropic): share tool_result text helpers to shrink the diff

* fix(anthropic): answer 400 for a non-string tool_result text on the OpenAI Responses bridge
2026-10-19 21:13:46 +00:00
..
audio_transcription test(unit): make every tests/unit directory a package so pytest collection is unique 2026-09-20 11:50:59 +00:00
files test(unit): make every tests/unit directory a package so pytest collection is unique 2026-09-20 11:50:59 +00:00
google_genai test: migrate SDK and proxy-booting legacy CircleCI tests to their tiers (#45758) 2026-10-11 00:17:21 -07:00
image_edit test(unit): make every tests/unit directory a package so pytest collection is unique 2026-09-20 11:50:59 +00:00
image_generation test: move whole-unit legacy test files into tests/unit and delete dead skips (#44807) 2026-10-07 05:02:28 +00:00
realtime fix(realtime)!: keep turn_detection.type when a transcript guardrail disables auto-response (#44939) 2026-10-16 15:24:40 +00:00
videos test(unit): make every tests/unit directory a package so pytest collection is unique 2026-09-20 11:50:59 +00:00
__init__.py test(unit): make every tests/unit directory a package so pytest collection is unique 2026-09-20 11:50:59 +00:00
test_cost_calculator.py test: move tests/test_litellm/llms into tests/unit/llms (#43191) 2026-09-25 12:43:23 -07:00
test_gemini_client_setup.py test: move tests/test_litellm/llms into tests/unit/llms (#43191) 2026-09-25 12:43:23 -07:00
test_gemini_common_utils.py fix(anthropic)!: answer 400 for a malformed tool_result text on /v1/messages and count_tokens (#47347) 2026-10-19 21:13:46 +00:00
test_gemini_image_generation_transformation.py chore(typing): clear 670 basedpyright Any errors by validating provider payloads at the boundary (#45388) 2026-10-13 15:35:23 -07:00
test_gemini_streaming.py test: migrate local_testing SDK and provider tests out of build_and_test (#45760) 2026-10-10 23:14:25 +00:00
test_gemini_tts.py refactor(llms): expose public names for private provider helpers (#45037) 2026-10-07 08:49:25 -07:00
test_image_usage_transformation.py refactor(types): replace Any with proven types in 157 files (#44798) 2026-10-06 15:31:54 +00:00