| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
A function call and its result are two separate session events. When a turn ends between them (a restart, an OOM kill, a disconnect, a cancellation), the session keeps a function_call that no function_response answers. That history is replayed on every later turn, and a provider that requires strict pairing rejects the whole conversation. Anthropic answers "tool_use ids were found without tool_result blocks immediately after", and the session stays unusable until it is deleted. Give every unanswered call a placeholder result in the immediately following content, while the request contents are assembled. The stored events are left untouched, so recorded history stays intact and a session that is already broken heals on its next turn without a migration. It sits above the session service, so it covers every store and every provider. A call the framework is holding open (a long-running tool, an approval, a request for user input) is described as awaiting a response, not as having returned nothing: told a tool returned nothing, the model reissues the call or proceeds without it; told the call is still awaiting a response, it can wait. Those ids are read from long_running_tool_ids, which lives on the event and does not survive the conversion to contents.
Anthropic requires every tool result to precede any other block in the message that carries it. Appending the placeholder to the end of a partially answered turn put it after a trailing text part, which is rejected for the same reason the missing result was. Insert the placeholder into the leading run of responses instead, and warn only for a call with no recorded response, so a call that is merely awaiting an answer stays quiet across the turns it spans.
| Back | FazBrowse Home | New Git URL |
Link to Issue or Description of Change
Problem
A function call and its result are two separate session events. When a turn ends between them (a restart, an OOM kill, a client disconnect, a cancellation), the session keeps a function_call that no function_response answers.
That history is replayed on every later turn, so the session does not recover on its own. A provider that requires strict pairing rejects the whole conversation: Anthropic answers tool_use ids were found without tool_result blocks immediately after, and the session stays unusable until it is deleted.
_drop_orphaned_function_responses already covers the mirror case, a response with no call. The unanswered call has no owner.
Change
Pair every function call with a result while the request contents are assembled, in _get_contents. A call the immediately following content does not answer gets a placeholder result there.
A call the framework holds open (a long-running tool, an approval, a request for user input) is described as awaiting a response, not as having returned nothing. The difference changes what the model does next: told a tool returned nothing, it reissues the call or proceeds without it; told the call is still awaiting a response, it can wait. Those ids come from long_running_tool_ids, which lives on the event and does not survive the conversion to contents. Only a call with no recorded response is logged, so a call that is merely awaiting an answer stays quiet across the turns it spans.
A conversation whose calls are all answered is returned unchanged.
The mirror change for adk-go is google/adk-go#1405.
Testing plan
uv run pytest tests/unittests — 13067 passed, 2 pre-existing failures in test_import_loading.py that are present on main as well.
Seven new cases in tests/unittests/flows/llm_flows/test_contents_function.py: the interrupted turn at the end of the history and in the middle, the partially answered parallel turn, the same turn with a trailing text part, the call held open in both directions, and calls without an id. Two more convert the repaired contents through anthropic_llm.content_to_message_param and assert both halves of the provider invariant, that every tool_use block is answered by the next message and that the results precede any other block in it; a guard test asserts the same check rejects unrepaired input.
Two assertions in tests/unittests/apps/test_compaction.py are updated, because a pending call now reaches the model with a placeholder.