fix: treat hallucinated tool calls as plain response when tools are removed - #9913
fix: treat hallucinated tool calls as plain response when tools are removed#9913FionaFaust wants to merge 2 commits into
Conversation
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/agent/runners/tool_loop_agent_runner.py" line_range="1014-1020" />
<code_context>
+ # Protocol safety net: every tool_call_id must have a matching tool
+ # result, otherwise the context would contain a dangling
+ # assistant(tool_calls) message that providers reject.
+ if len(tool_call_result_blocks) < len(llm_resp.tools_call_ids):
+ for tool_call_id in llm_resp.tools_call_ids:
+ if not any(
+ block.tool_call_id == tool_call_id
+ for block in tool_call_result_blocks
+ ):
+ tool_call_result_blocks.append(
+ ToolCallMessageSegment(
+ role="tool",
</code_context>
<issue_to_address>
**issue (bug_risk):** The placeholder guard only runs when the number of result blocks is smaller than the number of tool-call IDs, so equal-length results with duplicate IDs still leave another call unmatched. A tool execution that emits multiple result blocks for one call while a later call produces none creates a protocol-invalid assistant/tool message pair without triggering the guard.
**Triggers:** When one tool call yields multiple result blocks and another tool call yields no result block.
**Suggested fix:** Check each `tool_call_id` independently regardless of list lengths, and append a placeholder for every ID absent from the result blocks.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and if the fallback misclassifies a legitimate tool call or inserts an incorrect placeholder result, the current run's conversation context can contain a wrong tool outcome and produce a bad final answer. Reverting prevents future occurrences, but already affected runs may need to be retried or have their context cleared.
Blocking findings: astrbot/core/agent/runners/tool_loop_agent_runner.py:1020
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Address Sourcery review: the guard must check each tool_call_id independently, since equal block/id counts with duplicate ids can still leave another call unmatched.
|
Thanks for the review - good catch. I've updated the fix to check each
Validation: |
Summary
When an agent run reaches the
max_steplimit,run_agentremoves all tools (req.func_tool = None) and appends a user message asking the model to answer directly. If the model still returns tool calls (hallucination), the run used to:RUNNINGand the step loop exits without a response), andassistant(tool_calls)message (with no matchingtoolresult) to the context, which then gets persisted into the conversation history and re-sent to the provider on every later turn.Closes #9912
Root cause
_handle_function_toolsreturns immediately whenreq.func_toolis falsy (if not req.func_tool: return), appending notoolresult.step()then unconditionally extends the context withassistant(tool_calls)+ (empty) results, creating a protocol-invalid dangling message, and never transitions the state away fromRUNNING.Changes
step()hallucination guard: before finalizing/entering the tool-call branch, if the model returned tool calls while no tools are available, strip thetools_call_*fields and finalize via the existing_complete_with_assistant_response. The run reachesDONE, the user receives a reply, and no dangling tool-call message is appended. (This also coversskills_likemode, where the previous "remove tools" step was ineffective because tools were still resolved from the raw tool set.)ToolCallsResult, if anytool_call_idhas no matchingtoolresult block, append a placeholdertoolmessage so the context always stays protocol-valid even if a future path yields partial results.Tests
test_hallucinated_tool_call_when_tools_removed_finalizes_with_plain_response: tools removed + model hallucinates a tool call -> run completes,llm_resultis produced, no dangling tool-call message in the context.test_tool_calls_without_results_get_placeholder_tool_blocks: tool call produces no result -> placeholdertoolblock is appended and paired correctly.tests/test_tool_loop_agent_runner.py: 42 passed.ruff check/ruff format --checkon the touched files: clean.Summary by Sourcery
Prevent hallucinated or incomplete tool calls from leaving agent runs unfinished or conversation history protocol-invalid.
Bug Fixes:
Tests: