Skip to content

fix(api): support Claude Code Anthropic tool flows with Qwen chat templates#5051

Open
ace-xc wants to merge 1 commit into
xorbitsai:mainfrom
ace-xc:fix/anthropic-qwen-tool-template
Open

fix(api): support Claude Code Anthropic tool flows with Qwen chat templates#5051
ace-xc wants to merge 1 commit into
xorbitsai:mainfrom
ace-xc:fix/anthropic-qwen-tool-template

Conversation

@ace-xc

@ace-xc ace-xc commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #5037.

This PR improves Anthropic-compatible message handling for Claude Code style requests and fixes Qwen/Qwen3 tool-call prompt rendering failures.

Claude Code sends Anthropic Messages API payloads with content blocks such as system, tool_use, and tool_result. These need to be converted before reaching Xinference model backends and chat templates. Without normalization, Qwen/Qwen3 templates can receive OpenAI-style tool call arguments as JSON strings and fail while rendering Jinja templates with:

TypeError: Can only get item pairs from a mapping.

Changes

  • Convert Anthropic-style messages into OpenAI-style internal messages:
    • fold top-level and in-array system content into a leading system message
    • flatten text content blocks
    • convert tool_use blocks into assistant tool_calls
    • convert tool_result blocks into tool role messages
    • convert Anthropic tools / tool_choice into OpenAI-compatible shapes
  • Normalize tool-call arguments before chat template rendering:
    • detect templates that iterate over tool_call.arguments with |items / .items()
    • parse JSON-string tool arguments into mappings only for those templates
    • keep protocol-facing OpenAI responses unchanged, where function.arguments remains a JSON string
  • Harden tool schema handling for templates that iterate over tool.parameters.properties.
  • Make Anthropic response conversion accept both string and dict function.arguments.
  • Add compatibility fallback for BuildGradioEmbeddingInterfaceRequest to avoid startup failures from mixed/stale installs.

@XprobeBot XprobeBot added the bug Something isn't working label Jun 18, 2026
@XprobeBot XprobeBot added this to the v2.x milestone Jun 18, 2026

@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 introduces compatibility and conversion layers to support Anthropic-style Messages API requests (such as those from Claude Code) by mapping them into OpenAI-style messages and tools expected by Xinference backends. It also adds normalization helpers to coerce tool arguments and parameters into dictionaries when required by chat templates. The reviewer feedback suggests loosening the string-matching logic in _chat_template_needs_mapping_tool_arguments and _chat_template_needs_mapping_tool_parameters to ensure compatibility with templates using different variable names.

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 on lines +264 to +276
def _chat_template_needs_mapping_tool_arguments(chat_template: str) -> bool:
compact_template = "".join(chat_template.split())
return (
"tool_call.arguments|items" in compact_template
or "tool_call.arguments.items()" in compact_template
or (
(
"_args=tool_call.arguments" in compact_template
or "_args=tc.arguments" in compact_template
)
and "_args.items()" in compact_template
)
)

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.

high

这里的匹配逻辑过于严格。如果 chat template 中直接使用 tool_call.function.arguments|itemscall.arguments|items(而不是 tool_call.arguments|items),当前的检测逻辑将会漏掉,导致模板渲染失败。\n\n由于 _normalize_messages_for_chat_template 只会针对消息中的 tool_calls 进行参数规范化,因此将匹配范围放宽到 "arguments|items""arguments.items()" 是完全安全且更具鲁棒性的。

    @staticmethod\n    def _chat_template_needs_mapping_tool_arguments(chat_template: str) -> bool:\n        compact_template = "".join(chat_template.split())\n        return (\n            "arguments|items" in compact_template\n            or "arguments.items()" in compact_template\n            or (\n                (\n                    "_args=tool_call.arguments" in compact_template\n                    or "_args=tc.arguments" in compact_template\n                )\n                and "_args.items()" in compact_template\n            )\n        )

Comment on lines +279 to +284
def _chat_template_needs_mapping_tool_parameters(chat_template: str) -> bool:
compact_template = "".join(chat_template.split())
return (
"tool.parameters.properties|items" in compact_template
or "tool.parameters.properties.items()" in compact_template
)

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.

high

与工具参数的匹配类似,如果模板中使用的是 tool.function.parameters.properties|items 或者是其他变量名(例如 parameter.properties|items),当前的硬编码检测也会失效。\n\n建议将匹配条件放宽为 "parameters.properties|items""parameters.properties.items()",以提高对不同模型模板的兼容性。

    @staticmethod\n    def _chat_template_needs_mapping_tool_parameters(chat_template: str) -> bool:\n        compact_template = "".join(chat_template.split())\n        return (\n            "parameters.properties|items" in compact_template\n            or "parameters.properties.items()" in compact_template\n        )

@qinxuye

qinxuye commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Thanks, could you use English for PR title and content?

@ace-xc ace-xc changed the title fix(api): 支持 Claude Code Anthropic 工具调用与 Qwen 模板渲染 fix(api): support Claude Code Anthropic tool flows with Qwen chat templates Jun 18, 2026
@ace-xc

ace-xc commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, could you use English for PR title and content?

ok, I have edited it.

@qinxuye

qinxuye commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Lint(pre-commit) failed, could you fix it?

@ace-xc

ace-xc commented Jun 18, 2026 via email

Copy link
Copy Markdown
Contributor Author

@qinxuye

qinxuye commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Also, gemini gives some comments, could you please resolve them?

@qinxuye

qinxuye commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

#5049 seemed have solved this issue, can you confirm that?

ace-xc added a commit to ace-xc/inference that referenced this pull request Jun 18, 2026
Address review feedback on xorbitsai#5051 and make the lint CI pass.

- Broaden chat-template detection to `arguments|items` and
  `parameters.properties|items` so aliased variable names
  (e.g. tool_call.function.arguments) are also normalized
- Add tests for tool_call / tool parameter template aliases
- Drop the duplicate BuildGradioEmbeddingInterfaceRequest import
  fallback that triggered mypy no-redef
- Annotate template_for_normalization as Optional[str] (mypy)
- Apply black formatting

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ace-xc

ace-xc commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

#5049 seemed have solved this issue, can you confirm that?

ok, I'll continue checking.

ace-xc added a commit to ace-xc/inference that referenced this pull request Jun 18, 2026
Address review feedback on xorbitsai#5051 and make the lint CI pass.

- Broaden chat-template detection to `arguments|items` and
  `parameters.properties|items` so aliased variable names
  (e.g. tool_call.function.arguments) are also normalized
- Add tests for tool_call / tool parameter template aliases
- Drop the duplicate BuildGradioEmbeddingInterfaceRequest import
  fallback that triggered mypy no-redef
- Annotate template_for_normalization as Optional[str] (mypy)
- Apply black formatting
@ace-xc
ace-xc force-pushed the fix/anthropic-qwen-tool-template branch 3 times, most recently from c2e5110 to 5a4694e Compare June 18, 2026 13:57
@qinxuye

qinxuye commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

#5049 seemed have solved this issue, can you confirm that?

ok, I'll continue checking.

Have you had any conclusion?

…bitsai#5037)

Builds on xorbitsai#5049, which folds the Anthropic system prompt. Claude Code
>= 2.1.154 also sends tool history as Anthropic content blocks and tool
definitions carrying `input_schema`. These reach the Qwen/Qwen3 chat
template unconverted and crash it with
`TypeError: Can only get item pairs from a mapping` when a tool call's
`arguments` is a JSON string but the template iterates
`tool_call.arguments | items`.

- Convert Anthropic content blocks into OpenAI-style messages: tool_use
  -> assistant tool_calls, tool_result -> tool role, text/image flattened
  (system folding stays in _normalize_anthropic_messages from xorbitsai#5049)
- Convert Anthropic tools / tool_choice into OpenAI-compatible shapes
- Normalize JSON-string tool-call arguments and tool parameters into
  mappings only for templates that iterate them with |items / .items(),
  leaving protocol-facing OpenAI responses unchanged
- Accept "tool" as a valid final message role
- Add tests for the conversion helpers and the template normalization
@ace-xc
ace-xc force-pushed the fix/anthropic-qwen-tool-template branch from 5a4694e to 464e872 Compare June 25, 2026 04:30
@ace-xc

ace-xc commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Builds on xorbitsai#5049, which folds the Anthropic system prompt. Claude Code

= 2.1.154 also sends tool history as Anthropic content blocks and tool
definitions carrying input_schema. These reach the Qwen/Qwen3 chat
template unconverted and crash it with
TypeError: Can only get item pairs from a mapping when a tool call's
arguments is a JSON string but the template iterates
tool_call.arguments | items.

  • Convert Anthropic content blocks into OpenAI-style messages: tool_use
    -> assistant tool_calls, tool_result -> tool role, text/image flattened
    (system folding stays in _normalize_anthropic_messages from xorbitsai#5049)
  • Convert Anthropic tools / tool_choice into OpenAI-compatible shapes
  • Normalize JSON-string tool-call arguments and tool parameters into
    mappings only for templates that iterate them with |items / .items(),
    leaving protocol-facing OpenAI responses unchanged
  • Accept "tool" as a valid final message role
  • Add tests for the conversion helpers and the template normalization

Yes. I rechecked this against v2.11.0 and the latest main.
My conclusion is that v2.11.0 already includes the Anthropic system prompt handling fix, but it does not cover Claude Code's tool_use/tool_result flow, Anthropic input_schema/tool_choice conversion, or the Qwen chat-template mapping issue. I have rebased this PR onto the latest main and made it compatible with the newer tool-call argument normalization changes from #5060.
The PR has been updated. I also added/kept tests for the Claude Code Anthropic tool-flow conversion and Qwen chat-template normalization.

@qinxuye

qinxuye commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Builds on xorbitsai#5049, which folds the Anthropic system prompt. Claude Code

= 2.1.154 also sends tool history as Anthropic content blocks and tool
definitions carrying input_schema. These reach the Qwen/Qwen3 chat
template unconverted and crash it with
TypeError: Can only get item pairs from a mapping when a tool call's
arguments is a JSON string but the template iterates
tool_call.arguments | items.

  • Convert Anthropic content blocks into OpenAI-style messages: tool_use
    -> assistant tool_calls, tool_result -> tool role, text/image flattened
    (system folding stays in _normalize_anthropic_messages from xorbitsai#5049)
  • Convert Anthropic tools / tool_choice into OpenAI-compatible shapes
  • Normalize JSON-string tool-call arguments and tool parameters into
    mappings only for templates that iterate them with |items / .items(),
    leaving protocol-facing OpenAI responses unchanged
  • Accept "tool" as a valid final message role
  • Add tests for the conversion helpers and the template normalization

Yes. I rechecked this against v2.11.0 and the latest main. My conclusion is that v2.11.0 already includes the Anthropic system prompt handling fix, but it does not cover Claude Code's tool_use/tool_result flow, Anthropic input_schema/tool_choice conversion, or the Qwen chat-template mapping issue. I have rebased this PR onto the latest main and made it compatible with the newer tool-call argument normalization changes from #5060. The PR has been updated. I also added/kept tests for the Claude Code Anthropic tool-flow conversion and Qwen chat-template normalization.

okay, I will review this PR asap.

@qinxuye qinxuye 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.

I think one response-conversion edge case is still missing. In xinference/api/restful_api.py, _convert_openai_to_anthropic still does json.loads(arguments) unconditionally and only catches JSONDecodeError. If a backend/tool parser returns function.arguments as a dict, this raises TypeError and the Anthropic /v1/messages non-streaming response becomes 500. I reproduced it locally with arguments={"city":"Beijing"}.

Please handle mapping values directly before calling json.loads, and catch TypeError for non-string invalid values, e.g. keep dicts as input_data = arguments, otherwise parse strings and fall back to {} on invalid input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Claude Code >= v2.1.154版本支持

3 participants