6.1 函数调用 Function Calling:从 Prompt 约定到原生结构化调用


6.1 函数调用 Function Calling:从 Prompt 约定到原生结构化调用

让 LLM 调用工具,听起来简单——告诉它「这是工具,你用吧」——但落地有一大堆细节:怎么描述工具、LLM 怎么表达调用意图、参数怎么填、调用结果怎么回传。这些问题经历了从「Prompt 约定」到「原生结构化」再到「协议标准化」的三段演进。

6.1.1 函数调用的核心问题

让 LLM 调用一个工具,本质上要回答四个问题:

问题 含义
有哪些工具 工具清单怎么告诉 LLM
什么时候调 LLM 怎么判断「现在该调工具了」
怎么调 工具名 + 参数怎么表达
结果怎么用 工具返回怎么回传给 LLM

早期 ReAct 用「Prompt 约定」回答这四个问题;现代 LLM 用「原生结构化调用」;最新的 MCP 协议试图「标准化」。下面分别看。

6.1.2 第一阶段:Prompt 约定(ReAct 式)

早期 LLM 没有原生工具调用能力,靠 Prompt 约定——告诉 LLM 「按 Action: tool[args] 格式输出」。

Prompt 约定的工具描述

你可以使用以下工具: 1. search(query): 在网上搜索查询词,返回结果摘要 2. calculator(expression): 计算数学表达式 3. get_stock(ticker): 查询股票实时价格 当需要使用工具时,按以下格式输出: Thought: <你的推理> Action: <工具名>[<参数>] 示例: Thought: 我需要查苹果股价。 Action: get_stock[AAPL]

Prompt 约定的问题

问题 表现
格式不稳 LLM 偶尔不按格式输出
解析脆弱 正则解析易崩
参数难表达 复杂参数(嵌套对象、数组)很难用文本
工具数量受限 工具多了 Prompt 太长
校验靠运气 LLM 可能调不存在的工具或给错参数

⚠️ Prompt 约定的最大风险是「调了不存在的工具」。LLM 可能脑补一个听起来合理的工具名(如 send_email_via_gmail),而实际注册表里没有。没有兜底校验的 Prompt 约定,迟早出生产事故

6.1.3 第二阶段:原生结构化调用(Function Calling)

2023 年 6 月,OpenAI 率先在 API 层引入原生 Function Calling——LLM 直接输出结构化的工具调用 JSON,而不是文本。Anthropic、Google、阿里等很快跟进。

原生调用的三要素

要素一:工具描述(JSON Schema)

用 JSON Schema 描述工具,比自然语言严谨得多:

{ "name": "get_stock_price", "description": "查询股票实时价格", "parameters": { "type": "object", "properties": { "ticker": { "type": "string", "description": "股票代码,如 AAPL、GOOGL" }, "exchange": { "type": "string", "enum": ["NASDAQ", "NYSE", "HKEX"], "description": "交易所,可选" } }, "required": ["ticker"] } }

要素二:调用决策(结构化输出)

LLM 决定调工具时,输出结构化对象(而非文本):

{ "tool": "get_stock_price", "parameters": { "ticker": "AAPL", "exchange": "NASDAQ" } }

这个输出可被严格解析、校验,告别正则脆弱性。

要素三:结果回传

工具执行后,结果以「tool message」形式回传:

[tool result] { "price": 187.5, "currency": "USD", "timestamp": "2026-07-20T09:30Z" }

原生调用 vs Prompt 约定

维度 Prompt 约定 原生调用
输出格式 自由文本 结构化 JSON
解析 正则(脆) JSON 解析(稳)
参数表达 简单值尚可 复杂嵌套无压力
校验 强(Schema 约束)
工具数量 中(仍受 Prompt 上限)
模型支持 任何 LLM 需模型支持

💡 原生调用是工程上的巨大进步:它把「让 LLM 调工具」从「靠 Prompt 巧妙引导」变成「API 原生支持」,稳定性数量级提升。今天所有主流 LLM 都支持 Function Calling,生产 Agent 几乎都用原生调用而非 Prompt 约定

6.1.4 多工具调用与并行调用

原生调用支持两个高级能力:多工具调用并行调用

多工具调用

一次让 LLM 输出多个工具调用,按顺序执行:

[ { "tool": "get_stock_price", "parameters": { "ticker": "AAPL" } }, { "tool": "get_stock_price", "parameters": { "ticker": "MSFT" } }, { "tool": "get_stock_price", "parameters": { "ticker": "GOOGL" } } ]

并行调用

OpenAI 等模型支持并行工具调用——一次输出多个无依赖的工具调用,框架并行执行,显著降低延迟。这正是 3.4 节 ReWOO 思想的官方实现。

6.1.5 工具描述的最佳实践

JSON Schema 描述工具看似简单,写得好坏差很多。几条经验:

实践一:description 要充分

❌ "description": "查股票" ✅ "description": "查询指定股票代码的实时价格,支持美股(AAPL)、港股(0700.HK)、A股(600519.SH)。返回最新成交价、涨跌幅、成交量。"

LLM 选工具主要靠 description 匹配。描述越具体,选错率越低。

实践二:参数要带约束

❌ "ticker": { "type": "string" } ✅ "ticker": { "type": "string", "pattern": "^[A-Z]{1,5}(\\.[A-Z]{2,4})?$", "description": "..." }

带 enum、pattern、范围约束,能在 LLM 输出阶段就拦截大量错误。

实践三:description 含「何时用」

"description": "查询股票实时价格。 仅当用户明确询问股价时使用; 历史股价请用 get_historical_stock; 财报数据请用 get_financial_report。"

明确「何时用、何时不用」,能有效减少误调用。

⚠️ 工具描述是 Prompt Engineering 的延伸:很多人以为原生调用就不用写 Prompt 了,其实 JSON Schema 里的 description 字段就是 Prompt 的一部分。描述写得差,再好的模型也会调错工具

6.1.6 第三阶段:MCP 协议(标准化尝试)

每个 LLM 厂商都有一套 Function Calling 格式,工具开发者要为每家适配一遍。2024 年 Anthropic 提出 MCP(Model Context Protocol,模型上下文协议),试图标准化。

MCP 解决的问题

问题 MCP 的解法
工具格式各厂不同 统一协议描述工具
工具与模型耦合 工具与模型解耦,一次开发多模型可用
工具散落各处 统一注册与发现
工具状态难管 协议内建资源、提示、工具三类原语

MCP 的三大原语

原语 含义 类比
Tools 可调用的函数 Function Calling
Resources 可读取的数据源 文件、数据库
Prompts 可复用的提示模板 Prompt 库

MCP 的架构

MCP 的核心思想:把「工具」做成独立的 MCP Server,Host(如 Claude Desktop、IDE、Agent 框架)通过统一协议连接任意 Server。写一次工具,所有支持 MCP 的客户端都能用

💡 MCP 的意义:它在做工具调用领域的「USB 接口」——一个标准化的连接协议,让工具生态不再被单一模型厂商锁定。MCP 是否能成为事实标准仍在演化中,但它代表了工具调用标准化的方向。第 8 章会进一步讨论 MCP 的工程影响。

6.1.7 函数调用的失败模式与兜底

即便原生调用,也有失败模式:

失败 表现 兜底
调不存在的工具 LLM 脑补工具名 注册表校验,拒绝并提示
参数类型错 给 string 传 number Schema 校验
参数缺 漏 required Schema 校验 + 回喂 LLM
参数值错 ticker 拼错 工具内校验 + 错误回喂
调用时机错 不该调时调 description 写清「何时用」
死循环调用 反复调同一工具 重复检测

错误回喂:失败的关键兜底

工具调用失败时,不能吞掉错误,要把错误信息格式化后回喂 LLM,让它自主决定下一步(第 2.4 节原则):

[Thought] 我要查苹果股价。 [Action] get_stock_price(ticker="APPL") ← 拼错 [Observation] Error: ticker "APPL" not found. Did you mean "AAPL"? [Thought] 我拼错了,应该是 AAPL。 [Action] get_stock_price(ticker="AAPL") ← 改对 [Observation] { price: 187.5, ... }

⚠️ 错误回喂是 Function Calling 工程化的命脉。一个把错误吞掉的 Agent,会在错误方向上狂奔;一个把错误回喂的 Agent,能像人类一样「错了就改」。这是 ReAct 范式(第 4 章)的核心循环在 Action 层的具体体现。

6.1.8 函数调用 vs 代码解释器

本章还会讲「代码解释器」(6.3 节)。先做个区分:

维度 函数调用 代码解释器
调用什么 预定义的函数 LLM 自己写的代码
灵活性 低(受工具集限制) 高(图灵完备)
安全性 高(函数可控) 低(代码能干任何事)
适用 已知能力(查询、操作) 未知能力(数据分析、计算)

函数调用是「让 LLM 用我们准备好的工具」;代码解释器是「让 LLM 自己造工具」。两者互补——已知能力用函数调用,未知能力用代码解释器。

6.1.9 函数调用的反模式

反模式 表现 后果 正确做法
工具描述太简 description 一句话 选错工具 详细描述+何时用
参数无约束 没有 enum/pattern 参数错乱 Schema 严格约束
吞错误 失败返回空 Agent 困惑 错误回喂
工具过多全暴露 上百个全塞 Prompt 注意力稀释 工具检索(6.2 节)
不做注册表校验 LLM 调啥就执行啥 调不存在工具 强制校验
不限制并行 无依赖检测就并行 数据不一致 依赖分析(6.2 节)

本节小结

  • 函数调用要回答四个问题:有哪些工具、何时调、怎么调、结果怎么用。经历了三段演进:Prompt 约定 → 原生结构化 → MCP 协议标准化。
  • Prompt 约定(ReAct 式) 靠文本格式(Action: tool[args]),简单但脆弱——格式不稳、解析易崩、参数难表达、易调不存在的工具。
  • 原生结构化(Function Calling) 用 JSON Schema 描述工具,LLM 输出结构化调用对象。支持多工具调用与并行调用,稳定性数量级提升。今天所有主流 LLM 都支持,是生产标配。
  • 工具描述的最佳实践:description 要充分(含何时用)、参数要带约束(enum/pattern)、明确「何时用何时不用」。JSON Schema 的 description 就是 Prompt 的一部分
  • MCP 协议试图做工具调用的「USB 接口」——一次开发,所有支持 MCP 的客户端可用,避免厂商锁定。
  • 失败模式:调不存在工具、参数错、时机错、死循环。错误回喂是兜底命脉——失败时格式化错误回喂 LLM,让它自主纠错。
  • 函数调用(用准备好的工具)与代码解释器(让 LLM 造工具)互补,6.3 节展开后者。

下一节《6.2 工具集成与 API 编排》将讨论多工具管理——工具注册表、工具检索、并行/串行编排。


作者与出处
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 会发光的石头的小龙虾 转发
评论区 (0)
U