fix: make tool-use nudge conditional on detected tool intent#1
Conversation
The agentic loop previously nudged the LLM to use tools on every text
response, making casual conversation impossible ("hi, how are you"
would trigger up to 3 retry iterations with "please use tools" injected).
- Add `llm_mentions_tool_intent()` heuristic that checks whether the
LLM's text response mentions an intent phrase ("I'll use", "let me
call", etc.) near a known tool name within an 80-char window
- Only nudge when the heuristic fires, letting plain conversational
responses pass through immediately
- Soften the nudge prompt to give the LLM an exit if no tool was needed
- Save the pre-nudge response and fall back to it if the post-nudge
response is also text (false-positive recovery)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 WalkthroughThe agent loop now detects textual intent to use tools and performs a single nudge when the LLM mentions tool usage but didn’t call any tools. It collects tool names before adding tool definitions to the LLM context, and saves the original LLM text in Sequence DiagramsequenceDiagram
participant User as User
participant LLM as LLM
participant Agent as Agent Loop
participant Detector as Tool Intent Detector
participant Tools as Tool Executor
User->>LLM: Sends prompt
LLM-->>Agent: Returns response text
Agent->>Agent: Check pre_nudge_response
alt pre_nudge_response exists
Agent-->>User: Return saved original response
else no pre_nudge_response
Agent->>Detector: Analyze response for intent (llm_mentions_tool_intent)
Detector-->>Agent: Intent detected? (bool)
Agent->>Agent: Check if any tools executed
alt No tools executed & intent detected
Agent->>Agent: Save response as pre_nudge_response
Agent->>LLM: Send nudged prompt (one-time)
LLM-->>Agent: Return post-nudge response
Agent->>Tools: Execute tools if called
Tools-->>Agent: Return tool results
Agent->>Agent: Discard pre_nudge_response if tools ran
Agent-->>User: Return final response (with tool results)
else Tools executed or no intent
Agent-->>User: Return current response
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/agent/agent_loop.rs`:
- Around line 1115-1126: The current RespondResult::Text branch unconditionally
returns pre_nudge_response when present, which discards cases where the nudge
led to actual tool execution; change the logic inside the RespondResult::Text
match arm to only fall back to pre_nudge_response.take() when no tools were
executed (e.g., check a field like output.tool_calls.is_empty() or
output.tools_used == 0 or call a method like output.was_tool_called()),
otherwise let the tool-augmented result continue processing; update the
condition that wraps pre_nudge_response.take() to include this "no tools
executed" check while keeping the debug message and returning
AgenticLoopResult::Response(original) only in that case.
In `@src/util.rs`:
- Around line 71-76: The slice for the intent-matching window can panic on UTF-8
boundaries because window_end is computed as a byte index; update the
calculation to use floor_char_boundary to ensure window_end is a valid char
boundary before slicing. Locate the loop over INTENT_PHRASES where
lower.find(phrase) is used and modify the computation of window_end (and any use
of window_start/window_end for slicing) to call
lower.floor_char_boundary((window_start + 80).min(lower.len())) so the resulting
&lower[window_start..window_end] slice is safe.
…UTF-8 slicing When the tool-use nudge led to actual tool calls, pre_nudge_response was not cleared, causing the final tool-augmented text to be discarded in favor of the stale original. Clear the stash in the ToolCalls branch. Also use floor_char_boundary in llm_mentions_tool_intent to prevent a panic when the 80-byte search window lands mid-character in multi-byte UTF-8 text. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/util.rs`:
- Around line 71-76: The code incorrectly calls floor_char_boundary as a method
on lower; change the call to the standalone function by invoking
floor_char_boundary(&lower, (window_start + 80).min(lower.len())) (or whatever
the helper signature requires) instead of lower.floor_char_boundary(...), so in
the loop with INTENT_PHRASES use window_end = floor_char_boundary(&lower,
(window_start + 80).min(lower.len())); to get a valid char boundary.
…consistency Replace std method call `lower.floor_char_boundary(...)` with the standalone `floor_char_boundary()` polyfill defined in the same module, maintaining backward compatibility and consistency across the codebase. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The agentic loop previously nudged the LLM to use tools on every text response, making casual conversation impossible ("hi, how are you" would trigger up to 3 retry iterations with "please use tools" injected).
llm_mentions_tool_intent()heuristic that checks whether the LLM's text response mentions an intent phrase ("I'll use", "let me call", etc.) near a known tool name within an 80-char windowSummary by CodeRabbit
Bug Fixes
Tests