Skip to content

Function Calling vs Tool Use 实战对比

OpenAI 把它叫 Function Calling,Anthropic 叫 Tool Use,本质都是 让 LLM 决定调用什么工具,再把结果喂回去做下一轮推理。但两家的接口设计差异挺大,迁移时踩坑的人很多。这篇直接把字段、消息流、代码摆出来。

一句话总结

  • OpenAI:tool 是 messages 数组里多一种 role;适合短链调用。
  • Anthropic:tool 是 message content block 的一种类型;适合 Agent 多轮规划。

一、最小例子的对比

需求:让模型根据天气决定回复用户问的问题。一个工具:get_weather(city)

OpenAI

ts
// 1. 第一次调用
const res = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: '北京今天能出门跑步吗?' }],
  tools: [
    {
      type: 'function',
      function: {
        name: 'get_weather',
        description: '查询指定城市的实时天气',
        parameters: {
          type: 'object',
          properties: { city: { type: 'string' } },
          required: ['city'],
        },
      },
    },
  ],
});

const toolCall = res.choices[0].message.tool_calls?.[0];
// toolCall = {
//   id: 'call_abc',
//   type: 'function',
//   function: { name: 'get_weather', arguments: '{"city":"北京"}' }
// }

// 2. 自己跑工具
const args = JSON.parse(toolCall.function.arguments);
const weather = await getWeather(args.city);

// 3. 把结果回喂
const res2 = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [
    { role: 'user', content: '北京今天能出门跑步吗?' },
    res.choices[0].message,
    {
      role: 'tool',
      tool_call_id: toolCall.id,
      content: JSON.stringify(weather),
    },
  ],
  tools,
});

console.log(res2.choices[0].message.content);
// "北京今天 PM2.5 偏高,建议改成室内……"

Anthropic

ts
// 1. 第一次调用
const res = await anthropic.messages.create({
  model: 'claude-sonnet-4-6',
  max_tokens: 1024,
  messages: [{ role: 'user', content: '北京今天能出门跑步吗?' }],
  tools: [
    {
      name: 'get_weather',
      description: '查询指定城市的实时天气',
      input_schema: {
        type: 'object',
        properties: { city: { type: 'string' } },
        required: ['city'],
      },
    },
  ],
});

const toolUse = res.content.find((c) => c.type === 'tool_use');
// toolUse = {
//   type: 'tool_use',
//   id: 'toolu_abc',
//   name: 'get_weather',
//   input: { city: '北京' }   // 已解析为对象
// }

// 2. 自己跑工具
const weather = await getWeather(toolUse.input.city);

// 3. 把结果回喂(注意 role 是 user,不是 tool)
const res2 = await anthropic.messages.create({
  model: 'claude-sonnet-4-6',
  max_tokens: 1024,
  messages: [
    { role: 'user', content: '北京今天能出门跑步吗?' },
    { role: 'assistant', content: res.content },
    {
      role: 'user',
      content: [
        {
          type: 'tool_result',
          tool_use_id: toolUse.id,
          content: JSON.stringify(weather),
        },
      ],
    },
  ],
  tools,
});

二、关键字段对照

概念OpenAIAnthropic
工具声明tools[].function.parameterstools[].input_schema
调用结果(模型→你)message.tool_calls[]content[]tool_use block
工具结果(你→模型)role: "tool" 消息role: "user" + tool_result block
ID 字段tool_call_idtool_use_id
参数解析字符串,要 JSON.parse已经是对象
多工具并行同一 message 多个 tool_calls同一 message 多个 tool_use block
强制调用tool_choice: { type: "function", function: { name } }tool_choice: { type: "tool", name }
强制不调用tool_choice: "none"tool_choice: { type: "none" }
必须调用某个tool_choice: "required"tool_choice: { type: "any" }

三、流式情况下的差异(这是真正的难点)

非流式很简单,流式才是 Agent UI 真正要面对的。两家的流式 Tool Use 信号完全不同。

OpenAI 流式

每个 chunk 的 delta.tool_calls[]累加片段

delta: { tool_calls: [{ index: 0, id: "call_abc", function: { name: "get_weather" } }] }
delta: { tool_calls: [{ index: 0, function: { arguments: '{\"ci' } }] }
delta: { tool_calls: [{ index: 0, function: { arguments: 'ty\":\"' } }] }
delta: { tool_calls: [{ index: 0, function: { arguments: '北京\"}' } }] }

前端要按 index 字段累加 arguments 字符串,结束后再 JSON.parse。

Anthropic 流式

event: content_block_start
data: { content_block: { type: "tool_use", id: "toolu_abc", name: "get_weather", input: {} } }

event: content_block_delta
data: { delta: { type: "input_json_delta", partial_json: "{\"ci" } }

event: content_block_delta
data: { delta: { type: "input_json_delta", partial_json: "ty\":\"北京\"}" } }

event: content_block_stop

partial_json 也是累加字符串。同样在 content_block_stop 后 JSON.parse。

前端 UI 体验差异

  • OpenAI 流式工具调用时,模型不会同时输出文字
  • Anthropic 流式时,模型可以"先想,再决定调工具"——content[] 里会先有 text block,然后才出 tool_use block。所以 Claude 的 Agent 看起来更"会说话"。

四、什么时候用哪家

OpenAI 更适合

  • 短任务、函数固定、不需要 LLM 解释自己在干啥;
  • 已经在用 OpenAI 全家桶(Embedding、Whisper 等);
  • 需要并行调多个工具且不在乎 Agent 风格——OpenAI 在简单 function call 上历史最久,最稳。

Anthropic 更适合

  • 多轮 Agent,需要 LLM 写"我下一步打算 X,因为 Y";
  • 工具有失败/重试场景——Claude 在收到 tool_result 中的错误信息后,自我纠正能力明显更强;
  • 复杂 schema(嵌套、union 类型)——Anthropic 对 input_schema 的遵守度肉眼可见更好。

DeepSeek

DeepSeek 用的是 OpenAI 兼容协议,所以字段跟 OpenAI 完全一样,但工具能力比 GPT-4o 弱一些,简单场景没问题,复杂 Agent 还是用 Claude 或 GPT-4o。

五、迁移:写一层适配

如果要在产品里同时支持两家,建一层抽象:

ts
type Tool = {
  name: string;
  description: string;
  parameters: object;       // JSON Schema
  run: (args: object) => Promise<any>;
};

async function chatWithTools(
  provider: 'openai' | 'anthropic',
  messages: Message[],
  tools: Tool[],
) {
  if (provider === 'openai') {
    return runOpenAI(messages, tools);
  }
  return runAnthropic(messages, tools);
}

runOpenAI / runAnthropic 内部各自管 multi-turn 循环(调工具 → 回喂结果 → 再调,直到模型不再 request tool)。

业务方只跟 chatWithTools 打交道,永远不用碰各家字段差异。

六、踩坑清单

  1. OpenAI 的 arguments 是字符串——一定要 JSON.parse,且要 try/catch(模型偶尔产 invalid JSON,需要重试)。
  2. Anthropic 的 tool_result 必须是数组里的 block,不是字符串——很多人按 OpenAI 习惯写 content: 'xxx' 会被拒。
  3. 多工具并行时,OpenAI 同一轮所有 tool_calls 必须全部回喂结果才能再发下一轮,缺一个就报错。Anthropic 同理。
  4. 错误回喂:工具失败时把错误信息写进 tool_result.content 里,别在客户端兜底——交给 LLM 自己决定要不要重试或换路径。
  5. schema 不要太宽松——additionalProperties: true 会让模型胡乱塞参数,写明 requiredenum
  6. strict mode:OpenAI 2024 后加了 tools[].function.strict: true,开了之后保证输出严格符合 schema。代价是降一点质量,但生产环境强烈推荐。

本站总访问量 --heart 本站访客数 -- 人次