fix: persist tool call results when the LLM request fails mid-turn - #10293
Linyesantan wants to merge 1 commit into
Conversation
When the LLM request fails after tools have already run in the current turn, the whole turn was dropped from the conversation history. The user saw several tool calls, but the model reported only what it did in the previous turn and could not continue from where it stopped. `_save_to_history` already persisted a turn whose LLM response was missing altogether (`llm_response is None`), but a response that came back with role="err" fell through: the condition required `llm_response is None`, so the method returned without writing anything. Include the error case in that condition, and append a short assistant turn when the history would otherwise end on a tool result, because an unpaired tool call is rejected by the next request. Co-Authored-By: opencode <noreply@opencode.ai>
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py" line_range="543-547" />
<code_context>
+ ):
+ # A tool result must be followed by an assistant turn, otherwise
+ # the next request is rejected as an unpaired tool call.
+ if (
+ llm_failed
+ and message_to_save
+ and message_to_save[-1].get("role") == "tool"
+ ):
+ message_to_save.append(
+ Message(
+ role="assistant",
</code_context>
<issue_to_address>
**issue (broader_impact):** When the runner finishes with `llm_response is None` after executing tools, `_save_to_history` persists the assistant tool call and tool result but does not append an assistant message, leaving the saved history ending with a `tool` message. The next LLM request receives an unpaired tool call and is rejected by providers that require tool results to be followed by an assistant turn.
**Triggers:** When the runner has executed at least one tool but produces no final LLM response.
**Suggested fix:** Apply the fallback-assistant append logic to the `llm_response is None` path as well as the `llm_failed` path.
```suggestion
if (
(llm_response is None or llm_failed)
and message_to_save
and message_to_save[-1].get("role") == "tool"
):
```
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and if the new persistence or synthetic assistant message is wrong, the conversation history can retain a malformed or misleading turn after the code is reverted. The affected history is bounded and can be cleared or repaired, but reverting alone does not undo the stored record.
Blocking findings: astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py:547
| if ( | ||
| llm_failed | ||
| and message_to_save | ||
| and message_to_save[-1].get("role") == "tool" | ||
| ): |
There was a problem hiding this comment.
issue (broader_impact): When the runner finishes with llm_response is None after executing tools, _save_to_history persists the assistant tool call and tool result but does not append an assistant message, leaving the saved history ending with a tool message. The next LLM request receives an unpaired tool call and is rejected by providers that require tool results to be followed by an assistant turn.
Triggers: When the runner has executed at least one tool but produces no final LLM response.
Suggested fix: Apply the fallback-assistant append logic to the llm_response is None path as well as the llm_failed path.
| if ( | |
| llm_failed | |
| and message_to_save | |
| and message_to_save[-1].get("role") == "tool" | |
| ): | |
| if ( | |
| (llm_response is None or llm_failed) | |
| and message_to_save | |
| and message_to_save[-1].get("role") == "tool" | |
| ): |
fix: persist tool call results when the LLM request fails mid-turn
Problem
When the LLM request fails after tools have already run in the current turn, the
whole turn is dropped from the conversation history. The user watches the bot run
several tool calls, then the request fails, and on the next message the bot reports
only the tool calls from the previous turn and cannot continue from where it
stopped.
Root cause
InternalAgentSubStage._save_to_historyalready persists a turn whose LLMresponse was missing altogether (
llm_response is None), but a response thatcame back with
role="err"fell through the condition:llm_responseis notNonein the error case, so neither branch matched and themethod returned without writing anything, discarding the user message, every tool
call, and every tool result of the turn.
Fix
Include the error case in that condition. When the history would otherwise end on
a tool result, append a short assistant turn so the next request does not carry an
unpaired tool call.
The turn is only persisted when
req.tool_calls_resultis non-empty, so a turnthat failed before running any tool behaves exactly as before.
Test
Two cases added to
tests/test_conversation_checkpoint.py:Full suite: 3613 passed. The two failures in
tests/test_fastapi_v1_dashboard.pyare pre-existing and reproduce without this change.
Summary by Sourcery
Persist completed tool interactions when an LLM request fails mid-turn so the agent can continue from the correct conversation state.
Bug Fixes:
Tests: