fix: Search Agent doesn't reply with results, must be prompted again (#7084)#7119
fix: Search Agent doesn't reply with results, must be prompted again (#7084)#7119InfoSage05 wants to merge 7 commits into
Conversation
…etuning example with WER/CER metrics Adds explicit model routing for PaddlePaddle/PaddleOCR-VL-1.6 in the unsloth registry (org=PaddlePaddle, base_name=PaddleOCR, multimodal), disables compile optimization for this trust_remote_code model in loader.py, and provides a comprehensive finetuning example with WER/CER OCR benchmark evaluation via jiwer.
Add PaddleOCR-VL-1.6 registry entry, loader routing, and finetuning example with WER/CER evaluation
…orphaned tool calls and conversation corruption - Two-phase tool call processing: classify all calls and build assistant message first, then execute/no-op each one. Replaces break with continue so parallel tool calls are never silently discarded (Fix 1). - Add _repair_conversation_state safety net that inserts placeholder tool results for orphaned tool_call IDs before the final answer pass (Fix 2). - Add max_consecutive_errors guard to ToolLoopController (default 3) to break out of failed-tool retry spirals (Fix 3). - Always append budget-exhausted nudge when force_final_answer triggers in both GGUF and safetensors paths (Fix 4). - Remove _append_budget_exhausted_nudge=False override in GGUF path so the model is always instructed to produce a text answer.
There was a problem hiding this comment.
Code Review
This pull request adds support and a fine-tuning example for PaddleOCR-VL-1.6 using Unsloth, alongside refactoring the tool loop execution in the backend inference engines (llama_cpp.py and safetensors_agentic.py) to classify tool calls in two phases and repair orphaned tool call states. The review feedback highlights that checking or tool_calls when appending assistant messages can result in empty assistant messages if all calls are no-ops, which may cause errors in strict chat templates. Additionally, it is recommended to use greedy decoding (do_sample=False) during OCR evaluation to ensure deterministic and reliable WER/CER metrics.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
💡 Codex Review
unsloth/studio/backend/core/inference/llama_cpp.py
Lines 8690 to 8691 in 99de804
When a parallel response contains any no-op decision before all executable calls have been handled, Phase 1 has already appended an assistant message containing the executable tool_calls, but this line inserts the no-op user nudge before later executable calls append their role: "tool" results. That produces a sequence like assistant(tool_calls=[...]) -> user -> tool, which is still malformed for chat templates that expect tool results to immediately follow the assistant tool call turn; the mirrored safetensors loop has the same ordering at safetensors_agentic.py:560. Queue the no-op nudges until after all pending tool results, or avoid adding a user message while there are unresolved executable tool calls.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… no-op nudges, fix empty assistant - Add extra_duplicate_keys parameter to prepare_call so identical tool calls in the same batch are classified as duplicates before the assistant message is built (P1 same-turn duplicate suppression) - Split Phase 2 into 2a (execute) and 2b (no-op nudges) so tool results always appear before user-role nudges, maintaining valid conversation structure (P1 deferred no-op nudges) - Drop 'or tool_calls' from assistant message guard and only append when there is content text or executable tool_calls; skip no-op nudge messages when no assistant message was appended (medium)
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
for more information, see https://pre-commit.ci
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
Thanks for digging into #7084, @InfoSage05. I reproduced #7084 on the reported model. The corruption comes from It did surface one real bug, though. The no-op |
Description
Fixes #7084 — Search Agent makes tool calls but doesn't provide any answer, and the chat becomes "corrupted" where no subsequent responses can be produced.
Root Cause
After thorough code review of the agentic tool loop (both GGUF and safetensors paths), multiple issues were identified that cause the described behavior:
No-op
breakdiscards valid parallel tool calls — When the model emits N parallel tool calls in one turn and the k-th call is a no-op (duplicate/disabled/render_html_repeat), thebreakexits the entire loop. Tool calls k+1 through N are silently discarded, creating orphanedtool_callsin the assistant message with no matching tool results. The chat template produces malformed token sequences on subsequent iterations, leading to empty/garbled output.No budget nudge when
force_final_answertriggers — When the duplicate no-op limit is hit,force_final_answeris set and the GGUF path sets_append_budget_exhausted_nudge = False. The model enters the final streaming pass without being told to stop calling tools and produce a text answer, resulting in empty or tool-XML-only output.No consecutive-error guard — Failed tools (e.g., DuckDuckGo rate limiting) cause the model to retry repeatedly, consuming all 25 iterations with error messages instead of useful content. The conversation becomes dominated by noise, and the final answer pass has no useful context.
No conversation validation before final pass — If orphaned
tool_callsexist in the conversation when the final pass runs, the chat template produces malformed input and the model generates nothing useful.Changes
tool_loop_controller.py:_repair_conversation_state()— scans for orphanedtool_callsin assistant messages and inserts placeholderrole: "tool"results so the chat template never sees unmatched references (safety net)max_consecutive_errorsparameter (default: 3) toToolLoopController— after N consecutive tool errors, forces final answer to prevent retry spiralsllama_cpp.py(GGUF path):continueinstead ofbreak. This eliminates orphanedtool_callsand ensures no parallel calls are lost._append_budget_exhausted_nudge = Falseoverride — budget nudge is always appended when the tool loop ends, instructing the model to produce a text answer_repair_conversation_state(conversation)before the final streaming passsafetensors_agentic.py:force_final_answerpath (was previously missing)_repair_conversation_state(conversation)after the budget exhausted nudgeTesting
force_final_answerpath: trigger via duplicate_noop_limit — verify budget nudge is appended and final pass produces texttool_calls— verify repair inserts missing placeholder results