Function Calling
Function Calling(工具调用)让模型根据工具定义输出结构化的调用请求,由程序执行后把结果交回模型。模型从不直接执行函数。本文讲调用链路、消息结构、工具契约设计、可靠性保障,以及工具数量很多时如何让模型选对工具。
1. 面试核心
先记住这几条,再读正文- 链路是:程序发送工具定义 → 模型返回工具名和参数 → 程序校验并执行 → 结果以工具消息按调用 ID 写回 → 模型继续生成。卡 · 调用链路
- 模型根据工具的名称、描述和参数 Schema 选择工具,所以工具描述就是提示词的一部分,需要写清用途、适用条件和参数含义。
- 工具契约包括参数 Schema、返回结构、错误码和副作用说明。错误信息要让模型能据此修正,而不是只返回
500。卡 · 工具契约设计 - 可靠性靠程序保证:Schema 校验、业务校验、权限检查、超时重试、幂等和重复调用检测。卡 · 可靠性保障
- 工具很多时先检索或分组,再把少量候选工具的完整定义放入上下文。卡 · 大量工具的选择
2. 调用链路
tools = [{
"name": "get_order",
"description": "按订单号查询订单状态和物流信息。用户询问订单进度、物流时使用。",
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string", "description": "以 SO 开头的订单号"}},
"required": ["order_id"],
},
}]
resp = client.messages.create(model=MODEL, max_tokens=1024, tools=tools, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
results = [
{"type": "tool_result", "tool_use_id": block.id,
"content": run_tool(block.name, block.input)} # 程序执行模型请求的调用
for block in resp.content if block.type == "tool_use"
]
if results:
messages.append({"role": "user", "content": results})
不同提供方的字段名不同(有的叫 tool_calls 和 role: "tool"),结构一致:
- 请求中带上工具列表。
- 模型决定调用时,返回一个或多个调用块,每个有唯一 ID、工具名和 JSON 参数。
- 程序执行,生成结果。
- 结果以独立的工具结果消息返回,并引用对应的调用 ID。
- 模型读取结果,继续调用工具或给出最终回答。
2.1 为什么要单独的工具结果消息
- 对应关系:一次回复可能包含多个并行调用,ID 把每个结果和请求一一对应。
- 来源区分:工具结果不是用户说的话,单独的类型让模型知道这是执行结果,也便于程序把它标为不可信内容。
- 协议约束:多数 API 要求每个调用都有对应结果,缺失会报错;这也迫使程序处理每一个调用,包括失败的调用。
3. 工具契约设计
| 部分 | 要求 |
|---|---|
| 名称 | 动词 + 对象,含义唯一,如 search_orders、cancel_order |
| 描述 | 做什么、什么时候用、什么时候不用、与相似工具的区别 |
| 参数 Schema | 类型、枚举、格式、必填项;参数描述写清单位和取值来源 |
| 返回结构 | 只返回模型需要的字段,大结果分页或返回引用 |
| 错误 | 错误码 + 可读原因 + 修正建议,如 INVALID_ORDER_ID: 订单号应以 SO 开头 |
| 副作用 | 是否修改外部状态、是否幂等、是否需要确认 |
设计原则:
- 按任务而非按接口封装。一个
schedule_meeting工具内部调用日历查询和创建,比暴露三个底层接口让模型自己组合更可靠。 - 减少相似工具。描述难以区分的工具会被混用,合并或明确各自边界。
- 参数让模型容易填对。用枚举代替自由文本,用业务 ID 代替数据库自增 ID,避免要求模型做单位换算。
- 返回让模型容易读懂。返回语义字段(名称、状态)而不是内部编码;截断时明确告诉模型“已截断,共 3200 行,可用 page 参数读取”。
4. 可靠性保障
- Schema 校验:参数类型、必填、枚举不符时不执行,返回校验错误让模型修正。支持严格模式的提供方可以在生成时约束参数符合 Schema。
- 业务校验:ID 是否存在、金额是否超限、时间是否在允许范围内。
- 权限检查:按当前用户和工具风险等级判断是否允许,写操作需要确认,见工具授权与执行沙箱。
- 执行控制:超时、有限次数的退避重试、熔断;写操作通过幂等键防止重复执行。
- 结果处理:异常转换为结构化错误返回给模型;大结果截断或落盘。
- 循环控制:同一工具相同参数重复调用、连续失败达到阈值时停止并转入兜底。
减少“工具幻觉”(调用不存在的工具、编造参数值)的办法:只提供当前任务需要的工具;参数要求来自上文的 ID 时,在描述中写明“必须来自 search_orders 的结果”;程序校验 ID 是否在本次会话中出现过。
5. 大量工具的选择
工具定义占用上下文,并且工具越多,模型越容易选错。几十个以上时可以:
- 分层路由:先由意图分类或路由模型确定领域,再只加载该领域的工具。
- 工具检索:把工具描述向量化或建立关键词索引,按当前请求检索 Top-K 个工具,把完整定义放入上下文。也可以提供一个“搜索工具”的工具,让模型按需检索。
- 组合为高层工具或 Skill:把常用的多步调用封装成一个工具,减少候选数量。
- 评测命中率:建立“请求 → 正确工具”的评测集,统计 Top-1 命中率和检索召回率,工具描述修改后回归。
6 · 面试题卡片
6.1 Function Calling 的完整调用链路是什么?为什么工具结果需要单独的 Tool Message?
出现公司:虾皮、TCS、快手、阿里淘天、字节跳动
考察重点
- 模型提出调用、程序执行的分工。
- 调用 ID、并行调用和工具结果消息的作用。
相关内容:调用链路、为什么要单独的工具结果消息。
参考回答
程序在请求中附带工具列表,包括名称、描述和参数 JSON Schema。模型依据描述和对话判断是否需要工具,需要时返回调用块:调用 ID、工具名和参数。程序校验参数和权限后执行,把结果作为工具结果消息追加,引用对应的调用 ID,再次请求模型;模型据此继续调用或给出回答。
单独的 Tool Message 有三个作用:一次回复可能有多个并行调用,ID 保证结果与请求对应;把执行结果和用户输入区分开,便于模型理解来源,也便于程序把它当作不可信内容处理;API 要求每个调用都有结果,迫使程序处理包括失败在内的所有调用。
6.2 如何保障工具调用的可靠性?模型传入错误或危险参数怎么办?
出现公司:字节跳动、凌脉、TCS、快手、去哪儿旅行
考察重点
- Schema 校验、业务校验和权限检查的分层。
- 错误回传、重试、幂等与重复调用检测。
参考回答
分层处理。生成阶段用严格模式或清晰的 Schema、枚举减少错误;执行前用 Schema 校验类型和必填项,再做业务校验(ID 是否存在、金额范围)和权限检查,危险操作需要用户确认。校验失败不执行,返回带原因和修正建议的结构化错误,让模型修正后重试。
执行阶段设置超时、有限次数的退避重试和熔断,写操作用幂等键防止重复。记录每次调用的工具名和参数,相同调用重复或连续失败达到阈值时停止并进入兜底,例如转人工或返回部分结果。为减少编造参数,要求 ID 来自前序工具结果,并由程序核对。
6.3 Tool 的设计原则是什么?如何设计参数 Schema 和错误码?
出现公司:字节跳动、安软、百度
考察重点
- 按任务封装、名称与描述、参数和返回设计。
- 让模型能据此修正的错误信息与副作用声明。
相关内容:工具契约设计。
参考回答
适合做成 Tool 的是需要外部数据或确定性执行的能力,例如查询、计算、写入。按用户任务封装,而不是一比一暴露底层接口,减少模型需要组合的步骤;相似工具合并或在描述中写清区别。
名称用动词加对象;描述写用途、何时使用和何时不用。参数 Schema 标注类型、枚举、格式和必填项,参数描述写清来源和单位,尽量使用枚举和业务 ID。返回只给模型需要的语义字段,大结果分页。错误码配可读原因和修正建议,区分可重试(超时、限流)和需要改变决策(参数错误、权限不足)的错误。同时声明副作用、幂等性和是否需要确认,供程序做权限控制。
6.4 工具扩展到几百上千个时,如何保证模型选中正确的工具?
出现公司:百度、深信服、字节跳动、元戎启行
考察重点
- 工具定义的上下文成本与选择错误率。
- 分层路由、工具检索和命中率评测。
相关内容:大量工具的选择。
参考回答
十个左右的工具可以全部放入上下文,依靠清晰的描述区分。工具数量增多后,全部放入既占用大量上下文,又会提高选错率。
做法是两阶段:先用意图分类或路由确定领域,或者按请求对工具描述做向量与关键词混合检索,只把 Top-K 个候选的完整定义放入上下文;也可以给模型一个“搜索工具”的工具按需加载。常用的多步组合封装成高层工具或 Skill,减少候选数量。最后建立“请求—正确工具”的评测集,统计检索召回率和最终选择准确率,工具新增或描述修改后回归。