实战 SOP
实战 SOP

AI agent 工具调用实战 SOP:让大模型学会调工具

function calling / tool use 是 AI agent 的核心能力。本 SOP 给开发者一个可复制的工具调用实现模板:定义工具 schema -> 模型决策调哪个工具 -> 应用执行 -> 回填结果进下一轮。对照 OpenAI(function calling / Responses API vs Chat Completions、strict 模式)与 Anthropic tool use 当前官方 API,含五条踩坑(schema 不严谨/参数幻觉/不处理调用失败/无超时/安全沙箱)与五个 FAQ。API 以官方为准。

发布于 2026年8月9日12 分钟阅读
<!-- ai-agent-tool-calling-sop | sop | AI agent 工具调用实战 SOP:让大模型学会调工具 -->

大模型能写诗、能推理、能翻译,但你问它「SKU-8821 现在还有多少库存」,它只能编一个数字。训练数据里没有你的实时业务数据,模型的「知识」停留在训练截止那天。要让 agent 真正干活——查数据库、调 API、读文件、下订单——靠的不是更大的上下文窗口,而是一套让模型「会调工具」的机制:function calling(OpenAI 叫法)或 tool use(Anthropic 叫法)。

这篇 SOP 给你一个可复制的实现模板:定义工具 schema → 模型决策 → 执行 → 回填结果,四步循环。代码基于官方文档核实,截至 2026-08-09,API 以官方为准。与同站的 MCP Server 开发实战 SOP(搭标准化工具层)和 MCP 客户端横评(选哪个客户端)互补:那两篇解决「工具怎么被标准化暴露」,这篇解决「模型侧怎么决策调用」。


一、工具调用是什么:四步循环

工具调用的本质,是让大模型学会「我不懂,但我找懂的人来办」。整套机制拆成四步:

  1. 定义工具 schema:你告诉模型有哪些工具可用、每个工具吃什么参数(用 JSON Schema 描述)。
  2. 模型决策:用户发指令,模型结合指令和工具列表,决定是否调用工具、调哪个、传什么参数。注意模型只生成「调用意图」,不真正执行。
  3. 执行:你的代码拿到模型返回的调用意图,真正去查数据库、调 API、读文件,拿到真实结果。
  4. 回填结果:把执行结果塞回对话,模型基于结果生成最终回答(或决定再调一次工具)。

关键认知:模型不执行代码,它只产出结构化的「调用意图」。真正执行的是你的应用代码。这个分工是工具调用安全的基石——模型不会偷偷 rm 你的数据库,除非你写了这样的工具还告诉它可以用。


二、定义工具 schema:JSON Schema 是通用语言

不管 OpenAI 还是 Anthropic,工具定义的内核都是 JSON Schema。以一个「查库存」工具为例:

json
{
  "name": "check_inventory",
  "description": "查询某 SKU 的当前库存数量与所在仓库。当用户询问商品库存、是否缺货、发货地时调用。",
  "parameters": {
    "type": "object",
    "properties": {
      "sku": { "type": "string", "description": "商品 SKU 编码,如 SKU-8821" },
      "warehouse": {
        "type": "string",
        "enum": ["beijing", "shanghai", "guangzhou"],
        "description": "仓库代号,未指定时查全部仓库合计"
      }
    },
    "required": ["sku"]
  }
}

三条写 schema 的纪律:

  • description 写清「什么时候调」:模型靠 description 决策,不是靠 name。写「查询库存」太弱,写「当用户询问商品库存、是否缺货、发货地时调用」模型才知道触发时机。description 还要写清「不调」的边界——比如「不要用此工具查历史库存,只查当前」——防止模型在不该调时硬调。
  • required 明确:哪些参数必填、哪些可选,不要全标 required 也不要全不标。可选参数在 description 里说明缺省行为(如「未指定 warehouse 时查全部仓库合计」)。
  • enum 限定枚举值:仓库代号用 enum 限定,比让模型自由填字符串安全得多——模型可能编出 wuhan 而你的系统没这个仓。同理,布尔型参数用 enum ["true","false"] 比裸 string 稳。

schema 写得越严谨,模型参数幻觉越少。第六节会展开。两个进阶技巧:一是加 additionalProperties: false 锁死参数集合,防止模型塞入你没定义的额外字段;二是开 strict: true(OpenAI)强制模型严格按 schema 出参,结构化输出模式下模型连多余字段都塞不进来,这是压住参数幻觉最有效的一招。第六节会展开。


三、OpenAI function calling 实现

OpenAI 当前有两条 API 路径:Chat Completions(经典,仍在用)和 Responses(新一代,带会话状态)。两者工具定义结构略有不同,但循环逻辑一致。下面以 Chat Completions 为主示例——它最通用、教程最多。

注意:早期 2023 年的 functions / function_call(单数)参数已废弃,当前统一用 tools / tool_choice(复数)。别再抄老教程。

typescript
import OpenAI from "openai";
const openai = new OpenAI();

// 1. 定义工具 schema(Chat Completions 格式:外层包一层 function)
const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = [
  {
    type: "function",
    function: {
      name: "check_inventory",
      description: "查询某 SKU 的当前库存数量与所在仓库。",
      parameters: {
        type: "object",
        properties: {
          sku: { type: "string", description: "商品 SKU 编码" },
          warehouse: { type: "string", enum: ["beijing", "shanghai", "guangzhou"] }
        },
        required: ["sku"],
        additionalProperties: false
      }
    }
  }
];

async function checkInventory(sku: string, warehouse?: string) {
  // 你的真实查询逻辑:查数据库 / 调 ERP API
  return { sku, qty: 142, warehouse: warehouse ?? "beijing" };
}

// 2. 模型决策:带上 tools 发请求
const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
  { role: "system", content: "你是库存助手,缺信息时调用工具查真实库存,绝不编造数字。" },
  { role: "user", content: "SKU-8821 在北京仓还有多少货?" }
];

const resp = await openai.chat.completions.create({
  model: "gpt-5.6",
  messages,
  tools,
  tool_choice: "auto" // auto | none | required | {type:"function",function:{name:"check_inventory"}}
});

// 3. 执行:模型决定调用 check_inventory
const msg = resp.choices[0].message;
if (msg.tool_calls && msg.tool_calls.length > 0) {
  messages.push(msg); // 回填 assistant 的工具调用意图
  for (const call of msg.tool_calls) {
    const args = JSON.parse(call.function.arguments);
    const result = await checkInventory(args.sku, args.warehouse);
    // 4. 回填结果,用 tool_call_id 关联调用与结果
    messages.push({
      role: "tool",
      tool_call_id: call.id,
      content: JSON.stringify(result)
    });
  }
  // 5. 模型拿到结果,生成最终自然语言回答
  const final = await openai.chat.completions.create({ model: "gpt-5.6", messages, tools });
  console.log(final.choices[0].message.content);
  // => "SKU-8821 在北京仓还有 142 件。"
}

几个关键字段:tool_choice 控制模型行为——"auto"(默认,模型自己判断要不要调)、"none"(禁止调)、"required"(必须调至少一个)、或指定 {type:"function",function:{name:"..."}} 强制调某函数。call.id / tool_call_id 是调用与结果的关联键,千万别丢。加 strict: true 可强制模型严格按 schema 出参(结构化输出),能大幅压住参数幻觉。

Responses API 的等价流程:用 openai.responses.create(),响应 output 里是 type:"function_call" 项(含 name/arguments/call_id),回填用 {type:"function_call_output", call_id, output}。它带 previous_response_id 会话状态,多轮不用自己攒 messages。


四、Anthropic tool use 实现

Anthropic 的 tool use 与 OpenAI 思路一致,但 API 形态有四处差异值得对照:

python
import anthropic, json
client = anthropic.Anthropic()

# 1. 定义工具:注意是 input_schema 不是 parameters,外层不包 function
tools = [{
    "name": "check_inventory",
    "description": "查询某 SKU 的当前库存数量与所在仓库。",
    "input_schema": {
        "type": "object",
        "properties": {
            "sku": {"type": "string", "description": "商品 SKU 编码"},
            "warehouse": {"type": "string", "enum": ["beijing", "shanghai", "guangzhou"]}
        },
        "required": ["sku"]
    }
}]

def check_inventory(sku, warehouse=None):
    # 你的真实查询逻辑
    return {"sku": sku, "qty": 142, "warehouse": warehouse or "beijing"}

messages = [{"role": "user", "content": "SKU-8821 在北京仓还有多少货?"}]

# 2. 模型决策
response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto"},  # auto | any | tool | 加 disable_parallel_tool_use
    messages=messages
)

# 3. 执行:response.content 里找 tool_use block
if response.stop_reason == "tool_use":
    messages.append({"role": "assistant", "content": response.content})
    tool_use = next(b for b in response.content if b.type == "tool_use")
    result = check_inventory(**tool_use.input)  # input 已是 dict,无需 JSON.parse
    # 4. 回填:role 用 "user",content 是 tool_result block
    messages.append({
        "role": "user",
        "content": [{"type": "tool_result", "tool_use_id": tool_use.id, "content": json.dumps(result)}]
    })
    # 5. 模型拿到结果生成最终回答
    final = client.messages.create(
        model="claude-opus-5", max_tokens=1024, tools=tools, messages=messages
    )
    print(final.content[0].text)

四处差异:① schema 字段叫 input_schema(OpenAI 叫 parameters);② 工具直接平铺,不外包 function;③ 结果回填的 role 是 "user"、content 是 tool_result block(OpenAI 用 role:"tool" + tool_call_id);④ tool_use.input 已是解析好的 dict,不用 JSON.parsetool_choice{"type":"any"} 等价于 OpenAI 的 "required"{"type":"tool","name":"..."} 等价于强制指定函数。选型择哪家?如果你的 agent 主要调你自己写的工具(查你的库、调你的 API),两家都行,调用循环一致,差异只在字段命名和结果回填形态。如果你依赖服务器侧工具(如 Anthropic 的 web_search / code_execution 服务器工具),就得按对应平台的 API 走。另一个选型考量:并行工具调用,Anthropic 原生支持一轮返回多个 tool_use block、用 disable_parallel_tool_use 控制,OpenAI Chat Completions 也支持返回多个 tool_calls,你的执行循环得用 for 遍历而不是只取第一个。


五、执行与回填循环:带上错误处理

真实环境里工具会失败——数据库超时、API 403、SKU 不存在。关键原则:别把异常吞掉,也别让 agent 卡死,把错误结构化回填给模型让它自己决定下一步

python
def run_tool(tool_use):
    try:
        result = check_inventory(**tool_use.input)
        return {"content": json.dumps(result), "is_error": False}
    except InventoryNotFound:
        # 业务错误:SKU 不存在,回填让模型询问用户
        return {"content": f"SKU {tool_use.input['sku']} 不存在,请确认编码", "is_error": True}
    except Exception as e:
        # 系统错误:超时 / 403,回填让模型重试或换路径
        return {"content": f"工具执行失败:{type(e).__name__},可重试一次", "is_error": True}

# 回填时标记 is_error,Claude 会据此调整策略
messages.append({
    "role": "user",
    "content": [{"type": "tool_result",
                 "tool_use_id": tool_use.id, **run_tool(tool_use)}]
})

加三道护栏:最大循环次数(防止模型无限重试调用失败的工具,5-10 次封顶);超时(每个工具调用设 10-30s 超时,别让一次慢查询拖垮整个 agent);错误降级(连续失败 N 次后让模型改用「抱歉,库存查询暂时不可用」的兜底话术,而不是继续撞墙)。这三道护栏背后的逻辑是「模型不可靠」:模型会反复调一个失败的工具,会在参数上微调重试,会忽略之前的错误信息。你必须在应用层为它划边界,否则一个慢查询就能燒掉十几万 token 和数十分钟时间。实践中建议把「工具调用历史 + 错误次数」记录在会话状态里,达到上限就强制截断,给用户一个明确的「当前无法完成」而不是让它永远打转。


六、五个踩坑

坑 1:schema 写得太松,参数幻觉 description 模糊、参数全 string、没 enum,模型就会编参数。表现为模型给你一个结构合法但语义错误的调用——比如 warehouse: "wuhan"(你没这仓)。修法:每个参数写清 description,枚举值用 enum,必填项明确标 required,能加 strict: true 就加。

坑 2:模型编造参数值 模型把用户随口说的「那个商品」填成 sku: "that-product"。模型擅长结构化,不擅长猜真实 ID。修法:在 system prompt 里写「缺 SKU 时先问用户,不要猜测」;或加一个 search_product 模糊搜索工具,让模型先搜后查,而不是直接编 SKU。

坑 3:不处理 tool 调用失败,agent 卡死 模型调了工具,你的代码抛异常没回填结果,下一轮请求缺了 tool_result,API 直接报错。这是最常见的线上事故。修法:所有工具执行包 try/except,失败也回填一个 is_error: true 的结果,让模型自己决定重试还是换方案。

坑 4:没有超时和循环上限 模型反复调用一个慢工具,或陷入「调用失败-重试-失败」死循环,token 和时间双双爆表。修法:每个工具加超时,整个 agent 循环加最大轮次(建议 5-10 轮),超限就强制返回兜底回答。

坑 5:工具能力太宽,安全失控 给模型一个 execute_sql(query) 工具,它可能拼出 DROP TABLE。工具粒度越细越安全——用 check_inventory(sku, warehouse) 而非 run_any_sql(sql)。涉及写操作(下单、退款、删除),务必在工具层加权限校验和二次确认,别让模型一句话就把钱退了。


七、常见问题

Q1:function calling 和 tool use 是一回事吗? 是。OpenAI 早期叫 function calling,后改名 tool calling / tools;Anthropic 一直叫 tool use。两者底层逻辑一样:模型产出结构化调用意图,应用执行后回填结果。API 字段名不同,循环相同。

Q2:模型会自己写代码执行吗? 不会。模型只产出「调哪个工具、传什么参数」的意图(一段 JSON)。真正执行的是你的应用代码。模型拿不到你的数据库密码,也跑不了你机器上的 shell——除非你显式提供了这样的工具。

Q3:OpenAI Responses API 和 Chat Completions 该用哪个? Chat Completions 更通用、教程多、跨框架兼容好,适合大多数场景。Responses API 带会话状态(previous_response_id)、原生支持多轮和后台模式,适合复杂 agent 编排。新项目可优先 Responses,老项目继续用 Chat Completions 完全没问题。实际判断依据:你需不需要服务端会话状态(Responses 原生支持,Chat Completions 得自己收集 messages);你的框架(LangChain / LlamaIndex 等)是否已适配 Responses;团队成员对哪套更熟。别为了「新」而迁,Chat Completions 不是待废弃 API,它仍是全行业工具调用的主力路径。

Q4:怎么让模型调得更准、更少幻觉? 三招:① schema 严谨——description 写清触发时机,参数加 enum 和 description,开 strict: true;② system prompt 约束——「缺信息先问用户,不要猜测参数」「只调必要工具」;③ 少即是多——工具列表别堆几十个,模型选择压力越大越容易选错,用按需加载(OpenAI 的 tool_search)分批暴露。

Q5:和 MCP 是什么关系? 互补。MCP 是「标准化工具层」——定义工具怎么被发现、被传输,让一个工具能被所有客户端调用(见 MCP Server 开发 SOP)。function calling / tool use 是「模型侧决策机制」——定义模型怎么决定调哪个工具。一个 agent 通常两者都用:MCP 把工具标准化暴露出来,模型的 function calling 机制决定何时调、调哪个。模型侧决策 + 标准化工具层,拼起来才是完整 agent。


可复用 system prompt 模板

text
你是 {角色},可以调用以下工具完成任务:{工具列表}

调用纪律:
1. 缺少必填参数时,先向用户询问,绝不猜测或编造参数值
2. 只调用完成任务所需的最少工具,不冗余调用
3. 工具返回错误(is_error)时,向用户说明问题并建议下一步,不要反复重试同一调用
4. 工具返回的数据是唯一事实来源,不要用训练知识覆盖工具结果
5. 涉及写操作(下单/退款/删除)时,先向用户确认再调用

可用工具:
- check_inventory(sku, warehouse?): 查库存
- search_product(keyword): 模糊搜商品
- create_order(sku, qty, address): 下单(需用户确认)

参考来源

本文由 AI 辅助生成,经人工审核编辑。最后更新:2026-08-09

常见问题

function calling 和 tool use 是一回事吗?
是。OpenAI 早期叫 function calling,后改名 tool calling / tools;Anthropic 一直叫 tool use。两者底层逻辑一样:模型产出结构化调用意图,应用执行后回填结果。API 字段名不同,循环相同。
模型会自己写代码执行吗?
不会。模型只产出「调哪个工具、传什么参数」的意图(一段 JSON)。真正执行的是你的应用代码。模型拿不到你的数据库密码,也跑不了你机器上的 shell--除非你显式提供了这样的工具。
OpenAI Responses API 和 Chat Completions 该用哪个?
Chat Completions 更通用、教程多、跨框架兼容好,适合大多数场景。Responses API 带会话状态(previous_response_id)、原生支持多轮和后台模式,适合复杂 agent 编排。新项目可优先 Responses,老项目继续用 Chat Completions 完全没问题。别为了「新」而迁,Chat Completions 不是待废弃 API,它仍是全行业工具调用的主力路径。
怎么让模型调得更准、更少幻觉?
三招:① schema 严谨--description 写清触发时机,参数加 enum 和 description,开 strict: true;② system prompt 约束--「缺信息先问用户,不要猜测参数」「只调必要工具」;③ 少即是多--工具列表别堆几十个,模型选择压力越大越容易选错,用按需加载(OpenAI 的 tool_search)分批暴露。
和 MCP 是什么关系?
互补。MCP 是「标准化工具层」--定义工具怎么被发现、被传输,让一个工具能被所有客户端调用。function calling / tool use 是「模型侧决策机制」--定义模型怎么决定调哪个工具。一个 agent 通常两者都用:MCP 把工具标准化暴露出来,模型的 function calling 机制决定何时调、调哪个。模型侧决策 + 标准化工具层,拼起来才是完整 agent。

相关文章