fix(agent): don't force tool_choice on models that reject it - #1111
Open
denispetre wants to merge 1 commit into
Open
denispetre wants to merge 1 commit into
denispetre wants to merge 1 commit into
Conversation
Claude Opus 5.5 400s on tool_choice "any"/"tool" and its thinking can't be turned off, so every low-code / coded ReAct run fails on its first LLM call and the thinking-off extraction retry can't recover it either. The gateway flags such models with modelDetails.shouldSkipForcedToolChoice; for them the LLM node now sends tool_choice "auto" and re-asks a stalled turn with a trailing "call a tool" user message, thinking kept. A second stall still raises THINKING_LIMIT_EXCEEDED. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Moderate findings remain for legacy-model flag propagation and conversational forced-choice handling; a documentation nit also remains.
Review effort: Lite
Findings: None
What changed in this PR
This PR updates ReAct tool-choice handling for models that reject forced tool calls.
Changes:
- Detects
shouldSkipForcedToolChoice. - Uses
autowith thinking-preserving tool-call nudges. - Adds predicate and ReAct regression tests.
| File | Summary | Final review findings |
|---|---|---|
tests/chat/test_thinking.py |
Tests discovery-flag detection. | None. |
tests/agent/react/test_llm_node.py |
Tests adaptive choice, nudging, and stall limits. | None. |
src/uipath_langchain/chat/thinking.py |
Detects the gateway rejection flag. | Moderate (1 vote): Legacy models discard the discovery flag, so the original forced-choice failure can remain. |
src/uipath_langchain/agent/react/llm_node.py |
Applies adaptive tool choice and retry behavior. | Moderate (1 vote): Legacy-client models may still receive tool_choice="any".Moderate (1 vote): Conversational graphs can bypass the flagged-model override. Nit (1 vote): Update the docstring to distinguish nudge retries from thinking-off extraction. |
src/uipath_langchain/agent/react/forced_extraction.py |
Adds thinking-preserving nudge messages. | None. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Problem: on alpha,
claude-opus-5-5fails every ReAct agent run on its first LLM call (run):llm_nodeforcestool_choice="any"on every non-conversational turn.thinking_rejects_forced_tool_choiceonly fires on an explicit thinking config, and Opus 5.5 thinks by default.claude-opus-5passes the same run (control).Fix: the gateway flags such models with discovery
modelDetails.shouldSkipForcedToolChoice(UiPath/Arima#3103); the LangChain client already exposes that asmodel.model_details. For flagged models,llm_node:tool_choice="auto"instead ofany, including when the graph is configured withanyTHINKING_LIMIT_EXCEEDEDon a second stall, as todayUnflagged models are unaffected. The client-side safety net, which relaxes any forced choice that still reaches the wire, is UiPath/uipath-llm-client-python#139.
Tests:
TestModelRejectsForcedToolChoiceintest_llm_node.py(auto on turn 1 for configured auto/any, nudge keeps thinking and skips extraction, second stall raises) andtests/chat/test_thinking.pyfor the predicate.tests/agent/reactandtests/chatpass.🤖 Generated with Claude Code