摘要

Claude Code 这类 coding agent 对 tool use 的依赖很深:读文件、搜索、执行命令、补丁编辑、浏览器验证都要通过模型发起工具调用,再由宿主执行工具并把结果回放给模型。只要工具协议有一个环节不匹配,用户看到的就不是“模型略差一点”,而是工具不执行、参数错位、API 400、并行调用丢失、reasoning 状态断裂,甚至 agent 在错误历史里反复自我修复。

因此,Claude Code 适配 DeepSeek、Qwen 等非 Anthropic 模型,或继续扩展到其他模型后端时,不能静态假设它们都支持和 Anthropic Claude 相同的 function calling。更稳妥的做法是把模型工具能力当成运行时事实,而不是配置表常识:启动时、模型切换时、chat template 变化时、供应商升级后,都要做 capability probing,探测 schema 遵循、单工具调用、并行工具调用、tool_result 回放、thinking 状态、XML/JSON 格式、错误修复能力,再根据结果选择适配路径。

本文讨论一个面向 Claude Code 兼容层的能力探测与纠偏设计。重点不是证明某个模型“支持”或“不支持”工具调用,而是建立一套可复测、可灰度、可回滚的协议层,让不同模型在真实 agent 工作流里暴露差异,并被系统性吸收。

为什么静态兼容表不够

Anthropic 的 tool use 协议有清晰的状态机:应用在请求中声明工具,模型返回 tool_use,宿主执行工具,然后把匹配 tool_use.id 的 tool_result 放回下一条 user message。官方文档还强调 tool_result 必须紧跟前一轮 tool_use,并且在 user message content 数组前部出现。并行工具调用也有独立语义,必要时可以通过 disable_parallel_tool_use=true 关闭。

这套协议对 Claude 是原生路径,但跨模型时会遇到三类断点。

第一类是消息结构不同。OpenAI-compatible API 通常使用 tools、tool_calls、tool_call_id、role=tool,Anthropic 使用 content block 里的 tool_use 和 tool_result。Qwen3-Coder 社区案例中还出现了 XML 风格的 <tool_call>、<function=...> 格式,需要专门 parser 才能从文本里解析工具调用。

第二类是隐藏状态不同。DeepSeek thinking mode 的官方文档要求,在 tool call 后继续对话时回传 prior assistant turns 的 reasoning_content。多个社区 issue 报告了类似现象:agent 或 SDK 没有透传 reasoning 内容,导致 API 返回 400。这说明兼容层不能只转换可见的 tool_calls,还必须维护模型特定的 reasoning 状态。

第三类是“看似支持”不等于“可用于 agent”。一个模型可能能在简单示例中生成函数名和 JSON 参数,但在流式输出、嵌套 schema、并行调用、工具错误修复、工具结果回放、多轮上下文裁剪后失败。社区中关于 Qwen3-Coder 的案例就集中在这类问题上:模型生成了看似正确的 tool call,但框架没有执行;或者输出格式不是 OpenAI-compatible,需要适配 XML tool call。

所以,模型 registry 里写一句 supportsToolCall: true 只能作为提示,不能作为执行依据。agent 需要知道的是更细的能力向量。

能力维度

一个可执行的 capability profile 至少应覆盖以下维度。

维度 需要探测的问题 典型纠偏
工具定义 schema 是否遵循 name、description、input_schema 或 OpenAI parameters;是否尊重 required、enum、嵌套对象、数组 schema 降级、拆分复杂工具、增加参数校验和重试
单工具调用 是否能在明确任务中发出一个可解析工具调用 启用工具提示模板、切换 parser、退回纯文本命令确认
并行工具调用 是否会一次发出多个工具调用;多个调用的 id 和参数是否完整 支持则并行执行;不支持则串行化;必要时设置 disable_parallel_tool_use
工具结果回放 是否能消费上一轮工具结果并继续完成任务;id 是否正确配对 保持 tool_use/tool_result 紧邻关系;修复 role 和 content block
thinking / reasoning 状态 是否要求回传 reasoning_content、thinking block 或其他隐藏推理状态 保存真实 reasoning 内容并按模型协议重注入;禁止用空字符串伪造
JSON 格式 JSON 参数是否严格合法;是否会混入解释文本、尾逗号、注释、Markdown fence 容错解析、局部修复、失败后要求模型重新发工具调用
XML 格式 是否使用 <tool_call>、<function=...> 等标签,而不是结构化 API 字段 启用 XML parser;把 XML 解析结果映射为统一 ToolCall IR
流式增量 streaming 下工具调用字段是否分片稳定;是否丢参数或结束标记 累积 delta 后再解析;无法稳定时关闭 streaming tool call
错误修复 工具返回 is_error 或 schema 校验失败后,模型是否能修正参数并重试 自动生成结构化错误反馈;限制重试次数;失败降级到用户确认
上下文裁剪 历史清理后是否仍满足协议要求 裁剪前做闭合检查;保留模型要求的 reasoning 和最近工具链

这里的重点是“能力不是布尔值”。例如 Qwen 路径可能不是“不支持工具调用”,而是“不稳定支持 OpenAI-compatible 结构化 tool call,但在某些 chat template 下更适合 XML parser”。DeepSeek thinking mode 也不是“不能 tool call”,而是“tool call 后必须把 reasoning 状态作为协议状态保留下来”。这类能力必须被 profile 表达出来。

统一中间表示

兼容层最好不要让业务逻辑直接依赖某个供应商的原始消息格式,而是引入统一 ToolCall IR。

type ToolCallIR = {
  id: string;
  name: string;
  input: unknown;
  format: "anthropic" | "openai" | "xml" | "json-text";
  raw: unknown;
};

type ToolResultIR = {
  toolCallId: string;
  content: unknown;
  isError: boolean;
};

type CapabilityProfile = {
  model: string;
  provider: string;
  endpoint: string;
  chatTemplateHash?: string;
  schemaLevel: "strict-json-schema" | "simple-object" | "string-only";
  toolCallFormat: "anthropic" | "openai" | "xml" | "json-text" | "none";
  supportsParallelToolUse: boolean;
  requiresReasoningReplay: boolean;
  supportsStreamingToolUse: boolean;
  errorRepair: "reliable" | "limited" | "none";
  verifiedAt: string;
  probeVersion: string;
};

业务层只处理 IR:模型生成的 Anthropic tool_use、OpenAI tool_calls、Qwen XML 文本、JSON 文本都先解析成 ToolCallIR。执行工具后,再根据目标模型 profile 把 ToolResultIR 编码回对应协议。这样,探测和纠偏可以集中在 adapter 层,不会污染 agent 的工具执行逻辑。

探测协议

探测协议应当使用无副作用工具,避免在用户项目里执行真实命令。推荐准备三个固定探针工具:

[
  {
    "name": "probe_echo",
    "description": "Return the input payload unchanged.",
    "input_schema": {
      "type": "object",
      "properties": {
        "message": { "type": "string" },
        "tag": { "type": "string", "enum": ["alpha", "beta"] }
      },
      "required": ["message", "tag"]
    }
  },
  {
    "name": "probe_add",
    "description": "Add two integers.",
    "input_schema": {
      "type": "object",
      "properties": {
        "a": { "type": "integer" },
        "b": { "type": "integer" }
      },
      "required": ["a", "b"]
    }
  },
  {
    "name": "probe_fail_once",
    "description": "Always returns a structured error for repair probing.",
    "input_schema": {
      "type": "object",
      "properties": {
        "path": { "type": "string" }
      },
      "required": ["path"]
    }
  }
]

完整 probing 可以分七步。

1、基础格式探测:要求模型调用 probe_echo,输入固定字符串和 enum 值。观察它输出 Anthropic block、OpenAI tool_calls、XML tag、JSON 文本,还是自然语言说明。
2、schema 遵循探测:使用 enum、required、整数类型、嵌套对象分别测试。兼容层不只看是否调用工具,还要对参数做本地 JSON Schema 校验。
3、单工具闭环探测:执行 probe_add,把结果回放给模型,要求它基于工具结果回答。验证模型是否消费了 tool_result,而不是凭空计算或忽略工具结果。
4、并行调用探测:要求同时调用两个独立工具,例如一次 echo、一次 add。若模型只发一个调用或把两个调用混在一个参数里,标记为不支持并行,后续将计划拆成串行。
5、错误修复探测:让 probe_fail_once 返回结构化错误,例如 {"error":"path must be absolute"},观察模型是否能修正参数并重新调用,还是陷入解释文本。
6、thinking 状态探测:对 DeepSeek thinking mode 等模型,在 tool call 后继续请求,验证是否必须回传 reasoning_content。如果缺失会 400,profile 必须记录 requiresReasoningReplay=true。
7、streaming 探测:在流式模式下重复基础格式和单工具闭环,确认 delta 能否稳定合并成完整工具调用。若不稳定,只关闭 streaming tool call,不必关闭普通 streaming。

探测结果不应该只给 pass/fail,而要记录证据。

{
  "model": "qwen3-coder-local",
  "probeVersion": "tool-probe-v1",
  "results": {
    "singleTool": {
      "status": "pass",
      "format": "xml",
      "parser": "qwen-tool-call-xml"
    },
    "parallelTool": {
      "status": "fail",
      "reason": "model emitted one combined textual block"
    },
    "schema": {
      "status": "partial",
      "failures": ["enum not respected under nested object"]
    }
  }
}

这份证据既用于运行时选择 adapter,也用于后续回归。

自动纠偏策略

能力探测之后,兼容层应当自动选择最小必要纠偏,而不是强行把所有模型塞进同一个协议。

协议编码纠偏:如果模型 profile 是 Anthropic 原生路径,就保留 content block、tool_use.id、tool_result.tool_use_id 和 is_error。如果是 OpenAI-compatible 路径,就转换为 tool_calls 和 role=tool。如果是 Qwen XML 路径,就启用 XML parser,把 <tool_call> 内的函数名和参数映射到 IR,再把工具结果用该模型更容易消费的文本或模板回放。

schema 降级纠偏:当探测发现模型不能稳定遵循复杂 JSON Schema 时,不要把完整 Claude Code 工具 schema 原样暴露。可以把复杂工具拆成多个简单工具,减少 oneOf、anyOf、深层嵌套和宽泛 union;对高风险参数做本地校验;失败后返回明确错误,让模型重发调用。

并行纠偏:当 profile 标记不支持并行工具调用时,planner 层应把可并行步骤串行化。对 Anthropic 路径可以使用 disable_parallel_tool_use=true;对其他模型则在系统提示和执行器层共同约束“每轮最多一个工具调用”。这会牺牲速度,但能避免两个工具调用被拼接成不可解析文本。

reasoning 回放纠偏:DeepSeek thinking mode 的关键是保存真实 reasoning_content,并在后续 tool call 回合完整回传。社区中 Vercel AI、OpenCode、Hermes Agent、OpenClaw、Cursor 相关讨论都指向同一类问题:兼容层丢失 prior assistant turns 的 reasoning 内容后,API 会拒绝请求。这里不能用空 reasoning、摘要 reasoning 或重新生成 reasoning 代替,必须把供应商返回的原始字段作为协议状态保存。

工具结果位置纠偏:Anthropic 文档要求 tool_result 紧跟 tool_use,并位于下一条 user message content 数组前部。兼容层做历史清理、缓存切分、消息合并时,要先检查工具链是否闭合,不能把普通文本插到未闭合工具结果之前,也不能只保留 tool_use 而删除对应 tool_result。

解析纠偏:JSON 文本路径和 XML 路径都要有容错 parser,但容错不能无限放宽。推荐顺序是:严格解析;可恢复语法修复;本地 schema 校验;若仍失败,把机器可读错误作为 tool error 或 assistant feedback 返回,让模型重新发工具调用。不要把解析失败的半截参数拿去执行真实工具。

错误修复纠偏:工具执行失败时,错误反馈要结构化,而不是只返回大段堆栈。示例:

{
  "is_error": true,
  "error_code": "SCHEMA_VALIDATION_FAILED",
  "message": "input.path must be an absolute path",
  "retryable": true
}

这样可以探测和利用模型的自修复能力。若某模型多次无法修复,就把该能力降级为 limited 或 none,改为用户确认或宿主侧补全。

灰度与回归机制

模型能力会漂移。供应商升级模型、切换 endpoint、修改 chat template、本地推理框架更新 parser,都会改变工具调用表现。因此 capability probing 不能只在第一次接入时跑一次。

推荐把 profile 绑定到四个维度:provider、model、endpoint、chatTemplateHash。任一维度变化,都重新探测。对于云端模型,还应设置 TTL,例如 24 小时或一周自动刷新一次轻量探测。

灰度发布可以分三层。

1、离线回归:在 CI 中对已知模型 adapter 跑固定 probe transcript,验证 Anthropic、DeepSeek、Qwen XML、OpenAI-compatible 四类编码路径没有破坏。
2、影子探测:用户切换模型时,先在后台运行无副作用 probes。探测失败不影响现有会话,但不允许把该模型标记为可执行工具 agent。
3、小流量启用:新 profile 先只允许低风险工具,例如只读搜索、只读文件读取;通过真实任务遥测后再开放写文件、执行命令、浏览器自动化等高风险工具。

回归指标应覆盖协议正确性和用户体验两类。

指标 说明
tool call parse success rate 模型输出能否被解析成 ToolCallIR
schema validation failure rate 参数是否通过本地 schema 校验
tool result closure error 是否出现未闭合或错配 tool_use/tool_result
reasoning replay error 是否出现 DeepSeek thinking mode 这类状态回放失败
parallel downgrade rate 有多少任务因不支持并行而被串行化
repair success rate 工具错误后模型是否能自动修正
unsafe execution blocked parser 或 schema 阻止了多少次不可靠执行

当某个 profile 的关键指标退化时,系统应自动回退到更保守模式:关闭并行、关闭 streaming tool call、启用严格 schema、本地强校验、只读工具白名单,必要时把模型标记为 toolCallFormat=none,让它只做规划和解释,不直接驱动工具。

一个兼容层状态机

可以把运行时流程压缩成一个状态机。

Model selected
  -> Load cached capability profile
  -> If missing/stale: run probes
  -> Choose adapter and parser
  -> Encode tools for target model
  -> Receive model output
  -> Parse into ToolCallIR
  -> Validate schema and safety policy
  -> Execute tool or return structured error
  -> Encode ToolResultIR back to model protocol
  -> Preserve required reasoning/tool state
  -> Record telemetry for regression

这个状态机的好处是边界清楚。模型只负责生成意图;parser 负责把不同格式归一化;validator 决定是否允许执行;executor 只处理可信 IR;encoder 再按 profile 回放结果。任何一步失败,都能降级,而不是把错误扩散到整个 agent。

结论

Claude Code 跨模型适配的难点,不是把一个字段名从 tool_use 改成 tool_calls,而是把工具调用当成协议状态机来维护。Anthropic 的强约束、DeepSeek thinking mode 的 reasoning 回放、Qwen3-Coder 的 XML tool call 社区案例,都说明同一个“function calling”标签下面有不同的消息结构、隐藏状态、解析格式和错误恢复能力。

可靠的兼容层应当运行时探测,而不是静态假设;应当记录能力证据,而不是只写布尔开关;应当自动纠偏和灰度回归,而不是等用户任务失败后再人工定位。只有这样,DeepSeek、Qwen 等已有证据覆盖的模型,以及未来需要单独建立 profile 的其他后端,才能在 Claude Code 这类高工具密度 agent 中被稳健接入,而不是停留在 demo 级 function calling。

参考链接

• Anthropic Tool use overview
• Anthropic Define tools
• Anthropic Handle tool calls
• Anthropic Parallel tool use
• Anthropic Tool Runner SDK
• DeepSeek Thinking Mode
• DeepSeek Tool Calls
• DeepSeek Reasoning Model
• Vercel AI issue: DeepSeek thinking mode tool calling
• OpenCode issue: DeepSeek V4 thinking mode tool call
• NousResearch Hermes Agent issue: DeepSeek V4 thinking mode tool call
• OpenClaw issue: reasoning_content replay
• DeepSeek Cursor Proxy
• Cursor Forum discussion: DeepSeek reasoning_content passthrough
• Qwen Function Calling
• Alibaba Cloud Model Studio: Qwen Function calling
• Qwen3-Coder issue: XML tool call format
• Qwen Code issue: generated tool call but tool not executed
• llama.cpp issue: Qwen3-Coder XML tool call parser
• Unsloth Qwen3-Coder GGUF discussion
• Qwen HF discussion: tool calling chat template
• LM Studio issue: Qwen3-Coder streaming tool return
• Roo Code issue: Qwen3-Coder tool call failure
• Anthropic Engineering: Advanced tool use
• Tool Calling is Linearly Readable and Steerable in Language Models
• From Text to Voice: A Reproducible and Verifiable Framework for Evaluating Tool Calling LLM Agents