Skip to content

fix(langgraph): fall back A2UI tool_choice for reasoning/thinking-mode models - #2234

Open
contextablemark wants to merge 1 commit into
mainfrom
fix/langgraph-a2ui-tool-choice-thinking-mode
Open

fix(langgraph): fall back A2UI tool_choice for reasoning/thinking-mode models#2234
contextablemark wants to merge 1 commit into
mainfrom
fix/langgraph-a2ui-tool-choice-thinking-mode

Conversation

@contextablemark

Copy link
Copy Markdown
Contributor

Problem

The A2UI render subagent binds the inner render_a2ui tool with a forced named tool_choice so the subagent is guaranteed to emit a structured surface:

model.bind_tools([RENDER_A2UI_TOOL_DEF], tool_choice="render_a2ui")

Reasoning / "thinking-mode" models on some OpenAI-compatible providers reject a forced/object tool_choice and 400 the request. Observed on Alibaba DashScope qwen* with thinking enabled:

openai.BadRequestError: 400 - InternalError.Algo.InvalidParameter:
The tool_choice parameter does not support being set to required or object in thinking mode

That aborts the render mid-stream: the surface never paints and the client surfaces a terminated run error. Real OpenAI (and any provider that honors a named tool_choice) is unaffected — only reasoning-mode providers that forbid forced choices hit this.

Fix

Two changes, both fully backward compatible:

1. Runtime fallback (integrations/langgraph/python/ag_ui_langgraph/a2ui_tool.py)
If binding the forced choice fails with that specific 400, downgrade to tool_choice="auto" and retry — once, and for the rest of the run so subsequent recovery attempts don't re-trip it.

  • Providers that honor a named tool_choice hit the success path on the first try and never enter the fallback → their behavior is byte-for-byte unchanged. The new path can only turn a hard crash into a success.
  • The error match is deliberately narrow (both tool_choice and thinking mode must appear in the message), matched on the error text rather than the openai SDK's exception classes, so unrelated 400s propagate untouched.
  • The existing validate→retry loop still gates the result, so a model that skips the tool under auto degrades gracefully (re-prompt, then structured hard-fail envelope) rather than crashing the stream.

2. tool_choice knob (shared A2UIToolParams in sdks/python/a2ui_toolkit)
Lets a caller pin the choice up front (e.g. "auto" for a known reasoning model). Defaults to the forced render tool, so existing callers are unaffected, and every framework adapter gets the knob for free.

Tests

integrations/langgraph/python/tests/test_a2ui_tool.py (12 pass):

  • default forces render_a2ui; explicit tool_choice override reaches bind_tools
  • thinking-mode 400 → falls back to auto and still emits a valid operations envelope (asserts ["render_a2ui", "auto"] bind sequence)
  • an unrelated 400 is not swallowed (propagates; no fallback)
  • predicate unit tests for the error matcher

Toolkit suite (100 pass) confirms the A2UIToolParams/resolver change is non-breaking.

🤖 Generated with Claude Code

…e models

The A2UI render subagent binds the inner `render_a2ui` tool with a forced
named `tool_choice` so the subagent is guaranteed to emit a structured
surface. Reasoning / "thinking-mode" models on some OpenAI-compatible
providers reject a forced/object `tool_choice` and 400 the request
(observed on Alibaba DashScope qwen* with thinking enabled):

    The tool_choice parameter does not support being set to required or
    object in thinking mode

That aborts the render mid-stream — the surface never paints and the
client surfaces a `terminated` run error.

Two changes, both fully backward compatible:

1. Runtime fallback (langgraph adapter): if binding the forced choice
   fails the request with that specific 400, downgrade to
   `tool_choice="auto"` and retry — once, and for the rest of the run so
   subsequent recovery attempts don't re-trip it. Providers that honor a
   named `tool_choice` (OpenAI, etc.) hit the success path on the first
   try and never enter the fallback, so their behavior is unchanged. The
   error match is narrow (both "tool_choice" and "thinking mode" must
   appear) so unrelated 400s propagate untouched, and the existing
   validate/retry loop still gates the result — a model that skips the
   tool under "auto" degrades gracefully instead of crashing.

2. `tool_choice` knob (shared toolkit `A2UIToolParams`): lets a caller
   pin the choice up front (e.g. "auto" for a known reasoning model).
   Defaults to the forced render tool, so existing callers are unaffected;
   every framework adapter gets the knob for free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@contextablemark
contextablemark requested a review from a team as a code owner July 23, 2026 02:46
@github-actions

Copy link
Copy Markdown
Contributor

Python Preview Packages

Version 0.0.0.dev1784774805 published to TestPyPI.

Warning: These packages are built from contributor code that may not yet have been vetted for correctness or security. Install at your own risk and do not use in production.

Install with uv

Add the TestPyPI index to your pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = true

Then install the packages you need:

# Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1784774805' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1784774805' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1784774805' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1784774805' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1784774805' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1784774805' --index testpypi

Install with pip

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  ag-ui-protocol==0.0.0.dev1784774805

Use --extra-index-url https://pypi.org/simple/ so pip can resolve
transitive dependencies (pydantic, fastapi, etc.) from real PyPI.


Commit: a30fbe9

@pkg-pr-new

pkg-pr-new Bot commented Jul 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@2234

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@2234

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@2234

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@2234

@ag-ui/mcp-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-middleware@2234

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@2234

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@2234

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@2234

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@2234

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@2234

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@2234

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@2234

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@2234

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@2234

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@2234

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@2234

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@2234

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@2234

@ag-ui/watsonx

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/watsonx@2234

@ag-ui/a2ui-toolkit

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-toolkit@2234

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@2234

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@2234

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@2234

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@2234

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@2234

commit: 13f7d9d

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.

1 participant