Function Calling
🎯 学习目标
- 说清楚 Function Calling 与「模型直接联网」的本质区别
- 能编写符合 JSON Schema 的工具定义并在 API 中注册
- 掌握 tool_calls 消息结构与 tool result 回传格式
- 设计可维护的工具描述,减少参数幻觉与误调用
引言
Function Calling(也称 Tool Use)是让 LLM 决定要不要调用外部函数、生成结构化参数,但真正执行发生在你的后端。这一节我们从零搭一条「查天气」链路,理解为什么「模型不直接执行代码」反而是更安全、更可控的设计。
章节正文
第 1 步:建立心智模型:谁决策、谁执行
想象用户问:「北京明天需要带伞吗?」模型本身没有实时天气数据。Function Calling 的流程是:
- 你在请求里注册
get_weather工具及其参数 Schema - 模型阅读用户问题,输出
tool_calls(工具名 + JSON 参数) - 你的服务器调用真实天气 API
- 你把执行结果作为
tool角色消息塞回对话 - 模型基于 Observation 生成自然语言回答
关键点:LLM 只负责选工具和填参数,不负责执行。 这样你可以做权限校验、限流、审计,避免模型「假装查过数据库」。
第 2 步:编写工具定义(JSON Schema)
工具定义的质量直接决定调用成功率。描述要回答三个问题:干什么、何时用、参数含义是什么。
const tools = [
{
type: 'function',
function: {
name: 'get_weather',
description: '查询指定城市未来 24 小时天气预报。用户问天气、穿衣、是否带伞时使用。',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '城市名,如 Beijing' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'], description: '温度单位' },
},
required: ['city'],
},
},
},
]实践建议:description 写「触发条件」而不只是功能名;枚举值用 enum 约束;必填字段写进 required,减少模型编造可选参数。
第 3 步:发起带工具的 Chat 请求
以 OpenAI 兼容 API 为例,把 tools 和 tool_choice 一并传入:
const response = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: '北京明天要带伞吗?' }],
tools,
tool_choice: 'auto', // auto | required | none | { type:'function', function:{ name:'...' } }
})
const message = response.choices[0].message
if (message.tool_calls?.length) {
for (const call of message.tool_calls) {
const args = JSON.parse(call.function.arguments)
console.log(call.function.name, args) // get_weather { city: 'Beijing', ... }
}
}当 finish_reason === 'tool_calls' 时,不要直接把 assistant 消息展示给用户——先执行工具,再发起第二轮请求。
第 4 步:执行工具并回传 tool result
执行完成后,消息历史应包含:user → assistant(含 tool_calls)→ tool(每个 call 一条)。
async function runGetWeather({ city, unit = 'celsius' }) {
// 调用真实 API 或 mock
return { city, forecast: '小雨', temp: 18, unit }
}
const toolResults = []
for (const call of message.tool_calls) {
const args = JSON.parse(call.function.arguments)
const result = await runGetWeather(args)
toolResults.push({
role: 'tool',
tool_call_id: call.id,
content: JSON.stringify(result),
})
}
const final = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'user', content: '北京明天要带伞吗?' },
message,
...toolResults,
],
})
console.log(final.choices[0].message.content)tool_call_id 必须与 assistant 里的 call 一一对应,否则部分 API 会报错。
第 5 步:并行调用、错误处理与 tool_choice 策略
一次 assistant 消息可包含多个 tool_calls,后端应 Promise.all 并行执行(注意下游 API 限流)。
工具失败时,不要把异常吞掉——把结构化错误回传给模型,让它向用户解释或换策略:
content: JSON.stringify({ error: true, message: '城市未找到', code: 'CITY_NOT_FOUND' })tool_choice 策略:
- auto:模型自行决定(默认)
- required:强制走工具(实时数据场景)
- none:禁用工具(纯对话或安全模式)
- 指定函数名:固定走某一工具(审批流中的固定步骤)
动手练习
- 为「查公司通讯录」设计一个
search_employee工具 Schema,要求支持按姓名或工号查询,并写清何时不应调用该工具。 - 实现上述天气示例的完整两轮对话,用 mock 数据代替真实 API。
- 故意传入非法
city参数,观察模型在收到 error tool result 后的回复是否更诚实。 - 对比 tool_choice 为 auto 与 required 在同一问题上的行为差异,记录各 3 个测试用例。
- 为工具执行添加 5 秒超时,超时后回传 timeout 错误,检查最终用户可见文案是否友好。
常见问题
Q:Function Calling 和 MCP 是什么关系?
Function Calling 是模型 API 层的协议:模型如何表达「我要调某个函数」。MCP 是工具服务层的开放标准:如何把文件、数据库、API 封装成统一可发现的 Tools/Resources。常见做法是 MCP Server 暴露能力,客户端转成模型所需的 tools 数组。
Q:模型返回的参数 JSON 解析失败怎么办?
先做 JSON.parse 容错;失败时将原始 arguments 与 schema 校验错误一并作为 tool result 或 system 提示回传,让模型重试一次。生产环境建议对关键参数做服务端校验,不要盲信模型输出。
Q:一个对话可以连续调用多轮工具吗?
可以。每轮执行完 tool result 后再次请求模型,直到 finish_reason 为 stop 且没有新的 tool_calls。多轮调用就是 Agent Loop 的基础,下一节 ReAct 会系统讲解循环与终止条件。
本节小结
Function Calling 的核心是「模型决策 + 后端执行 + 结果回传」。把 Schema 写清楚、执行放在受信环境、错误结构化回传,是从 Demo 走向可用工具链的第一步。