Skip to content

fix: Search Agent doesn't reply with results, must be prompted again (#7084)#7119

Closed
InfoSage05 wants to merge 7 commits into
unslothai:mainfrom
InfoSage05:fix/7084-search-agent-no-response
Closed

fix: Search Agent doesn't reply with results, must be prompted again (#7084)#7119
InfoSage05 wants to merge 7 commits into
unslothai:mainfrom
InfoSage05:fix/7084-search-agent-no-response

Conversation

@InfoSage05

Copy link
Copy Markdown
Contributor

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:

  1. No-op break discards 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), the break exits the entire loop. Tool calls k+1 through N are silently discarded, creating orphaned tool_calls in the assistant message with no matching tool results. The chat template produces malformed token sequences on subsequent iterations, leading to empty/garbled output.

  2. No budget nudge when force_final_answer triggers — When the duplicate no-op limit is hit, force_final_answer is 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.

  3. 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.

  4. No conversation validation before final pass — If orphaned tool_calls exist 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:

  • Added _repair_conversation_state() — scans for orphaned tool_calls in assistant messages and inserts placeholder role: "tool" results so the chat template never sees unmatched references (safety net)
  • Added max_consecutive_errors parameter (default: 3) to ToolLoopController — after N consecutive tool errors, forces final answer to prevent retry spirals
  • Consecutive error counter reset on success, incremented on error; forces final answer when threshold reached

llama_cpp.py (GGUF path):

  • Two-phase tool call processing — Phase 1 classifies ALL tool calls and builds the complete assistant message before appending anything. Phase 2 executes or no-ops each decision with continue instead of break. This eliminates orphaned tool_calls and ensures no parallel calls are lost.
  • Removed _append_budget_exhausted_nudge = False override — budget nudge is always appended when the tool loop ends, instructing the model to produce a text answer
  • Added _repair_conversation_state(conversation) before the final streaming pass

safetensors_agentic.py:

  • Same two-phase restructuring as GGUF path
  • Added budget nudge + conversation repair in the force_final_answer path (was previously missing)
  • Added _repair_conversation_state(conversation) after the budget exhausted nudge

Testing

  • Parallel tool call with duplicate: mock 3 tool calls where the 2nd is a duplicate — verify all 3 get proper results and conversation is well-formed
  • All tool calls fail: mock web_search to always fail — verify loop breaks after 3 consecutive errors and produces a final text answer
  • force_final_answer path: trigger via duplicate_noop_limit — verify budget nudge is appended and final pass produces text
  • Conversation repair: construct conversation with orphaned tool_calls — verify repair inserts missing placeholder results
  • Integration: run full search flow and verify user always receives a text answer after tool calls complete

InfoSage05 and others added 4 commits June 27, 2026 00:47
…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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread studio/backend/core/inference/llama_cpp.py Outdated
Comment thread studio/backend/core/inference/safetensors_agentic.py Outdated
Comment thread examples/paddleocr_vl_finetuning.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

completion = tool_controller.record_noop(decision)
conversation.append(completion.model_message())

P1 Badge Defer no-op nudges until tool results are appended

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".

Comment thread examples/paddleocr_vl_finetuning.py Outdated
Comment thread unsloth/registry/_paddleocr.py Outdated
Comment thread studio/backend/core/inference/llama_cpp.py
Comment thread examples/paddleocr_vl_finetuning.py Outdated
Comment thread studio/backend/core/inference/tool_loop_controller.py Outdated
… 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)
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@oobabooga

Copy link
Copy Markdown
Member

Thanks for digging into #7084, @InfoSage05.

I reproduced #7084 on the reported model. The corruption comes from web_search decoding a binary response (a PDF) into U+FFFD replacement characters, not from the tool loop. It is fixed in #7130. I also checked the "orphaned tool_calls" diagnosis by driving the loop directly. A no-op is never added to the assistant message, so no orphan is produced. _repair_conversation_state guards a state the loop doesn't create. The branch also fails 9 of the existing tool-loop tests and bundles the unrelated PaddleOCR example, so it can't go in as is.

It did surface one real bug, though. The no-op break drops the parallel tool calls after it. I've landed just that piece on its own in #7157, with tests. Closing this in favor of those two. Thanks again for the report.

@oobabooga oobabooga closed this Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Search Agent doesn't reply with results, must be prompted again

2 participants