摘要

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.idtool_result 放回下一条 user message。官方文档还强调 tool_result 必须紧跟前一轮 tool_use,并且在 user message content 数组前部出现。并行工具调用也有独立语义,必要时可以通过 disable_parallel_tool_use=true 关闭。

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

第一类是消息结构不同。OpenAI-compatible API 通常使用 toolstool_callstool_call_idrole=tool,Anthropic 使用 content block 里的 tool_usetool_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 是否遵循 namedescriptioninput_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.idtool_result.tool_use_idis_error。如果是 OpenAI-compatible 路径,就转换为 tool_callsrole=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
}

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

灰度与回归机制

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

推荐把 profile 绑定到四个维度:providermodelendpointchatTemplateHash。任一维度变化,都重新探测。对于云端模型,还应设置 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