函数 / 工具调用
函数调用(Function Calling / Tool Use)让模型在对话中输出结构化的调用请求,由你的应用执行后再把结果回传。本文解释它是什么、什么时候值得用、请求里 tools 字段的基本结构,以及用聚合 API 时需要注意的兼容性差异。
函数调用到底在做什么
模型本身不会执行代码、不会查询你的数据库,也不能真的调用外部接口。它能做的是:在合适的时机输出一段结构化的调用意图,比如「需要调用 get_weather,参数是 city=上海」。
真正执行的是你的应用。执行完之后,你把结果作为一条新消息塞回对话,模型再基于结果继续生成自然语言回答。
所以函数调用不是一个让模型「变聪明」的开关,而是把模型的语言理解能力接到你已有的确定性代码上的一个接口。
一次典型的往返
- 你在请求里带上可用的工具定义(tools)。
- 用户提问,模型判断需要调用工具,返回一个工具调用请求(含工具名和参数)。
- 你的代码解析参数、执行真实逻辑、拿到返回值。
- 你把返回值作为工具结果消息追加进对话历史,再发一次请求。
- 模型读取结果,生成面向用户的最终回答。
注意第 2 步模型可能一次返回多个调用,也可能什么都不调直接回答——这两者都是正常结果,不是错误。
请求里长什么样
概念上,tools 是一个数组,每个元素描述一个可用函数:名称、用途说明、参数的 JSON Schema。大致形状:
{
"model": "your-model",
"messages": [{"role": "user", "content": "上海现在天气怎么样?"}],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
}
]
}
模型的回复里会出现工具调用结构,而不是(或除了)普通文本。你解析它,执行,然后把结果按该提供方约定的格式回传。
description 和参数说明是给模型看的提示词,不是给人看的注释。写得含糊,模型就容易选错工具或填错参数。
什么时候值得用
适合的场景通常有这些共同点:
- 需要实时或私有数据:天气、库存、订单状态、内部知识库。这些模型训练数据里没有,或者已经过期。
- 需要精确计算或确定性逻辑:金额计算、日期差、正则匹配、单位换算。让代码算比让模型「心算」可靠。
- 需要触发副作用:下单、发邮件、创建工单。这类操作建议加人工确认或权限校验,别让模型直接决定。
- 需要收敛输出格式:把「抽取结构化字段」做成一个工具,比在提示词里反复要求 JSON 更稳定。
不太值得用的情况:纯改写、翻译、总结这类文本任务;或者只有一个固定流程、根本不需要模型判断分支的场合——直接写代码更简单。
用聚合 API 时的几个注意点
一个 key 调多家模型时,函数调用的细节会有差异:
- Schema 深度:有的提供方对嵌套对象、数组、枚举支持更完整,有的对深层嵌套或某些关键字支持有限。先用简单扁平参数跑通,再逐步加复杂度。
- 并行调用:是否支持一次返回多个工具调用、并行执行的语义,各家不同。
- 结果回传格式:工具结果消息的字段名和结构在不同提供方之间不完全一致。切换模型时这部分最容易出问题。
- 强制调用:有的提供方支持指定「必须调用某个工具」或「禁止调用」,有的没有等价选项。
- 流式:流式返回时工具调用的参数是分片到达的,需要自己拼接完整后再解析 JSON,不能边收边解析。
实际做法是:把工具定义和解析逻辑抽成一层薄薄的适配,针对不同模型族做小分支,而不是在业务代码里到处写 if。
常见坑
- 参数是字符串不是对象:有些模型会把 JSON 参数作为字符串返回,需要显式解析,解析失败要有兜底。
- 编造参数:模型可能填一个工具定义里不存在的字段,或者类型不对。校验要在执行前做。
- 死循环:工具返回错误后模型反复重试同一个调用。给循环次数设上限。
- 把工具结果当用户输入:工具返回的内容同样可能包含诱导性文本(比如抓取的网页),不要无脑当指令执行。
- 工具太多:一次塞几十个工具会明显降低选择准确率。按场景分组,或者先做一次粗筛。
小结
函数调用的本质是把模型的判断力和你代码的可靠性拼起来:模型负责「选哪个、填什么」,你负责「真的执行」。设计时先想清楚哪些能力必须由确定性代码完成,再把这些能力包装成描述清晰的工具,剩下的交给模型。