Google's shim mislabels streamed tool calls finish_reason: "stop"
medium · inference · probed live 2026-07-26 · affects Google (OpenAI-compat)
finish_reason states why generation stopped; agent loops branch on it —
"tool_calls" means execute tools and continue, "stop" means the turn
is done. It must not depend on transport.
What was sent
A deterministic tool prompt; the streaming leg sends the identical body plus
"stream": true
(download):
{
"model": "gemma-4-31b-it",
"messages": [
{
"role": "user",
"content": "Call `get_weather` for Paris. Do not answer directly."
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": [
"city"
]
}
}
}
],
"reasoning_effort": "minimal",
"temperature": 0,
"max_tokens": 200
}
What came back
Non-streaming — the correct label:
{
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"tool_calls": [
{ "id": "t6afm4c1", "type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" } }
]
}
}
Streaming — identical tool call, wrong label:
{
"finish_reason": "stop",
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{ "id": "xo93xcxj", "type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" } }
]
}
}
Every other OpenAI-dialect provider probed returns "tool_calls" on both
transports. The streamed leg also drops the thought_signature the non-streamed
leg returns — the token Google's own dialect expects replayed on the next turn.
Why it matters
A harness that dispatches on finish_reason behaves differently depending on transport: streamed, it is told the turn finished cleanly and terminates with an unexecuted tool call — HTTP 200, well-formed body, silent.
Run it yourself
curl "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions" \
-H "Authorization: Bearer $GEMINI_API_KEY" -H "Content-Type: application/json" \
-d @stream-finish.google-openai.json
POST the body twice, with and without "stream": true; compare the final
finish_reason.