我最早用 ChatGPT 的 function calling 时,总觉得这事有点玄。你给模型丢几个 JSON 格式的工具描述,它就能自己判断用户意图,然后选出对的工具、填上参数。这让我好奇了很久——模型到底是怎么“认出”该用哪个工具的?难道它内部有个搜索引擎,拿用户输入和工具描述算相似度?

后来我翻了很多论文和官方文档,又反复调试过几次 prompt,才慢慢想通:模型不是在做显式的语义检索,而是在利用它预训练阶段学到的语言理解能力,把工具描述当成“语境”去匹配用户意图。 这个过程,和少样本学习(few-shot learning)的机制几乎一模一样。
这个认知帮我解决了很多工具调用失败的问题。今天我就把这段思考过程摊开,说说工具描述的语义匹配到底是怎么工作的,以及为什么少样本示例能救命。
模型不是“找”工具,而是“读”描述
很多人(包括最初的我)会下意识地把工具调用想象成两步:第一步,模型根据用户输入去候选工具里搜索最相关的一个;第二步,生成调用参数。但如果你真的去看底层实现,会发现根本没有“搜索”这一步。
以 OpenAI 的 function calling 为例,你把工具描述放在 system prompt 或 user prompt 里,跟对话历史一起送入模型。模型在生成每个 token 时,注意力机制会同时考虑所有上下文——包括工具描述、用户话、系统指令。当它“决定”要生成一个函数调用时,并不是先检索到一个工具,而是直接根据上下文生成出函数名和参数。这个生成过程,受它从训练数据里学到的“函数调用应该长这样”的模式影响,也受当前 prompt 里工具描述的字面意思影响。
换句话说,模型是在做文本生成,不是在做信息检索。它“知道”该调用哪个工具,是因为它已经把工具描述读进脑子里,理解了每个工具是干啥的,然后结合用户的话,生成最匹配的调用。
那工具描述凭什么能引导模型选对工具?靠的就是语义匹配。比如一个工具的描述是“获取当前天气,需要城市名”,另一个是“查询股票价格,需要股票代码”。当用户说“北京今天多少度”时,模型在预训练中积累了无数关于“天气”、“温度”的语义关联,它能“理解”这句话跟天气工具更相关,哪怕这两个工具描述从未同时出现在训练数据里。这种跨越语言面的泛化能力,就是大模型能做工具调用的基础。
但这里有一个坑:语义匹配不是精确的。如果两个工具描述写得太像,或者有歧义,模型就容易选错。比如你把一个工具描述写成“获取指定城市的天气”,另一个写成“获取指定城市的天气预警”,当用户说“告诉我北京天气怎么样”时,模型可能犹豫——它不知道该返回完整天气还是预警。这就是为什么工具描述要写得清晰、有区分度,不是随便写两句就行。
少样本示例:给模型打样
理解了语义匹配的底层逻辑,你就明白为什么少样本示例(few-shot examples)在工具调用里这么重要了。它不是“锦上添花”,而是直接塑造模型对工具调用的预期。
模型在生成函数调用时,会参考 prompt 里已有的示例。如果你在请求里附上几个例子:
// 示例 1
用户:北京今天天气
助手:get_weather(city="北京")
// 示例 2
用户:苹果股价
助手:get_stock_price(symbol="AAPL")
// 真实请求
用户:上海明天会下雨吗?
模型看到这些例子后,会做两件事:第一,它学会了你期望的函数调用格式(比如用中文还是英文函数名,参数怎么传);第二,它从例子中提取了“用户意图 → 工具选择”的映射规律。这种规律不是显式记忆,而是模型通过注意力机制在例子中捕捉到的共现模式——就像你教小孩认识动物,给他看几张猫的图片,他就能认出没见过的新猫。
我做过一个实验:在一个需要调用三个不同 API 的系统里,不加任何示例,工具调用准确率大概 70%;加了 2 个示例后,直接跳到 92%。最让我惊讶的是,示例里并不需要覆盖所有工具,只要每个工具给一个例子,甚至只给部分工具的例子,模型就能举一反三。这说明少样本学习在工具调用中触发了模型的上下文内泛化——它不只是照搬例子,而是学会了“根据用户意图匹配工具”这个抽象任务。
但少样本也有陷阱:示例太多会挤占上下文窗口,而且如果示例写得不一致(比如同一个工具在不同例子中用了不同名称),模型反而会困惑。所以常见的做法是:为每个工具准备 1-3 个典型示例,覆盖最常见的使用场景,并且保持格式统一。
语义匹配 vs 少样本示例:谁更重要?
这个问题我以前纠结过很久。后来发现,它们不是竞争关系,而是互相补充的两个维度。用一个表格说清楚:
| 维度 | 语义匹配(工具描述) | 少样本示例 |
|---|---|---|
| 作用机制 | 通过描述文本的语义相似度,让模型在生成时“联想到”对应工具 | 通过示例模板,直接告诉模型“在这种情境下,应该这样调用” |
| 适用场景 | 工具数量多、描述清晰、用户意图明确 | 工具格式复杂、需要特定输出规范、或模型容易选错时 |
| 优势 | 不占上下文窗口,可无限扩展工具数量 | 显著提升准确率,尤其对弱模型或易混淆场景 |
| 局限 | 描述模糊或重叠时易出错 | 消耗 token,示例过多导致上下文过长,且可能过拟合到示例写法 |
| 实际建议 | 每个工具描述要包含:功能、输入参数、返回结果,并避免与其他工具描述用词雷同 | 每个工具至少给 1 个示例,并确保示例间格式一致;复杂工具可以给 2-3 个不同场景 |
一个更底层的认知是:少样本示例本质上是在帮模型做“语义对齐”。单纯靠工具描述,模型需要自己从描述文字中推断出“哪种用户意图应该选我”,这中间有 gap。而示例直接填补了这个 gap,它用具体的对话-调用对,把工具描述和用户意图之间的映射关系实例化。所以,即使你的工具描述写得很好,加上示例依然能大幅提升稳定性。
工具描述到底该怎么写?
既然语义匹配是核心,那工具描述就不是随便写写。我踩过的坑告诉我,好描述和坏描述的差距,能直接从 60% 的准确率拉到 90%。
几个关键原则:
- 功能描述要具体,不要抽象。“获取信息”是坏描述,“获取指定城市的实时天气,包括温度、湿度、风速”是好描述。越具体,语义向量的方向越清晰。
- 参数描述要带例子。比如“city: 城市名称,例如‘北京’、‘上海’”。模型不仅会理解参数类型,还会模仿例子中的格式,减少参数提取错误。
- 避免功能重叠。如果你有两个工具都跟“天气”相关,一定要在描述里把区别写清楚,比如“获取天气”和“获取天气预警”,后者要强调“当用户询问极端天气或预警信息时使用”。
- 用自然语言,不要用代码风格。模型理解自然语言远好于理解 JSON Schema 里的 description 字段。虽然 function calling 接收 JSON Schema,但里面 description 字段的内容才是模型真正“读”的,所以把它当成给人类看的说明来写。
Anthropic 在 Claude 的工具使用文档里也强调:工具描述应该清晰、简洁,并包含功能边界(比如“这个工具只返回当前天气,不返回预报”)。这种做法能有效减少模型误选。
一个容易忽视的细节:工具名称也是语义匹配的一部分
很多人把注意力全放在 description 字段上,却忘了工具名称(name)本身也是文本。模型在读工具描述时,工具名称和描述是连在一起编码的。比如一个工具叫“get_weather”,另一个叫“fetch_weather”,它们虽然语义很近,但和用户话里的“查天气”匹配时,差异可能不大。但如果你把工具名起成“query_weather_service”和“retrieve_weather_data”,那区别就更小了。所以,工具名称最好也能直观反映功能,并且和描述用词一致。
我遇到过因为工具名称太像而导致的串线问题:一个叫“get_weather”,一个叫“get_weather_forecast”,当用户说“明天天气怎么样”时,模型有时会选第一个,有时选第二个,随机性很大。后来我把第二个名改成“get_forecast”,描述里明确“返回未来7天天气预报”,问题就解决了。这个细节说明,语义匹配是全局的,名称和描述一起参与模型决策。
模型真的“理解”工具吗?
到这里,你可能已经明白:模型并不真正理解工具能做什么,它只是在下游任务中表现出色。它通过预训练获得了语言相关性,然后在 prompt 里用工具描述和示例把这种相关性引导到正确的工具上。这有点像用一根激光笔,在黑夜中给一只猫指路——猫并不理解光斑的意义,但它会顺着光斑跑。
这个认知也解释了为什么工具调用会有“幻觉”:当模型觉得某个工具描述跟用户输入最相关,但实际上那个工具根本不具备用户需要的功能时,它依然会强行生成调用,然后再用下一个工具或回复来“圆谎”。所以,在工具调用链路里,永远不要完全信任模型,必须要做输入校验和结果兜底。
和微调比,少样本示例的优势在哪?
有人可能会想,既然少样本示例能提升准确率,那直接微调一个专门做工具调用的模型不更好?这要分场景。
微调确实能从根本上改变模型的行为,让它更擅长工具调用。但微调的成本高,而且工具集一变就得重新训练。少样本示例的优势在于即插即用——你新增一个工具,只需在 prompt 里加一段描述和几个示例,模型立刻就能用,不需要重新训练。这个特性让少样本示例成为 LLM 做工具调用时的标配。
不过,当工具数量非常多(比如上百个),少样本示例的 token 消耗会急剧上升,而且所有工具描述都塞进 prompt 也可能超出上下文长度。这时就需要结合其他技术,比如先把工具描述做向量化存到向量数据库,检索时只把最相关的几个工具描述放进 prompt。但这就是另一个话题了。
收尾:工具调用的能力边界
我想用一个判断来结尾:现在的工具调用,本质上是语言模型在 prompt 工程下的“条件反射”,它很强大,但远没有到“推理”工具选择的地步。这意味着,如果你把工具描述写得清晰、示例给得恰当,它就能表现得像模像样;可一旦遇到模糊指令、或者两个工具描述过于相似,它就会露怯。
所以,不要指望模型天生就能自己选对工具。把工具描述当成产品说明书来写,把少样本示例当成用户手册里的 FAQ 来设计,然后反复测试边界 case——这才是让工具调用稳定的正路。
我自己的经验是:花 80% 的时间在打磨工具描述和示例上,只有 20% 的时间在调整代码。因为模型的能力就在这里,你怎么告诉它,它才会怎么用。
原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/102.html