目录
- [什么是"工具调用"(Function Calling)?Agent的手和脚](#什么是“工具调用”(Function Calling)?Agent的手和脚)
-
- 引言:从"纸上谈兵"到"真刀真枪"
- [一、什么是工具调用(Function Calling)?](#一、什么是工具调用(Function Calling)?)
-
- [1.1 先于技术之前,理解"工具"的本质](#1.1 先于技术之前,理解“工具”的本质)
- [1.2 Function Calling:一个标准化的"翻译层"](#1.2 Function Calling:一个标准化的“翻译层”)
- 二、工作原理:从"想"到"做"的完整旅程
-
- [2.1 核心流程:一个永不停止的循环](#2.1 核心流程:一个永不停止的循环)
- [2.2 技术实现:OpenAI Python SDK 示例](#2.2 技术实现:OpenAI Python SDK 示例)
- [2.3 参数解析与格式约束:让模型"说人话"而非"说JSON"](#2.3 参数解析与格式约束:让模型“说人话”而非“说JSON”)
- 三、工具调用的核心挑战:走出"幻想的执行力"
-
- [3.1 工具选择错误(Intent Classification Failure)](#3.1 工具选择错误(Intent Classification Failure))
- [3.2 参数幻觉与编造](#3.2 参数幻觉与编造)
- [3.3 执行幻觉与状态反馈](#3.3 执行幻觉与状态反馈)
- [3.4 流式输出场景下的工具调用](#3.4 流式输出场景下的工具调用)
- [四、主流模型 Function Calling 能力横评](#四、主流模型 Function Calling 能力横评)
- 五、安全边界:别让Agent的手"乱摸"
-
- [5.1 最小权限原则](#5.1 最小权限原则)
- [5.2 高风险操作确认](#5.2 高风险操作确认)
- [5.3 沙箱与速率限制](#5.3 沙箱与速率限制)
- 六、高级实践与未来趋势:MCP协议与工具生态
-
- [6.1 MCP(模型上下文协议):统一工具调用的"USB-C"](#6.1 MCP(模型上下文协议):统一工具调用的“USB-C”)
- [6.2 Agent-to-Agent 工具调用](#6.2 Agent-to-Agent 工具调用)
- [6.3 从文本驱动到多模态工具调用](#6.3 从文本驱动到多模态工具调用)
- 七、总结:给Agent装上可靠而强劲的"四肢"
什么是"工具调用"(Function Calling)?Agent的手和脚
"大模型是大脑,但大脑不会自己动手。Function Calling 就是给这颗大脑接上神经和肌肉的过程------让它不仅能说'我该做什么',还能真正'伸手去做'。没有工具调用的 AI,永远只是纸上谈兵的军师;有了它,AI 才第一次具备了改变现实世界的力量。"
引言:从"纸上谈兵"到"真刀真枪"
想象这样一个场景:你让一个 AI 助手帮你"查一下明天北京的天气,如果下雨就发邮件提醒我带伞"。在 2022 年,这个请求无异于天方夜谭。ChatGPT 会热情地告诉你:"北京明天可能下雨,建议您带伞",但它绝不会真的去查实时天气,更不会碰你的邮箱。它所做的一切,都只是在 "生成文字"------无论多逼真的建议,本质上仍是纸上谈兵。
转折点发生在 2023 年 6 月。OpenAI 在其 API 中引入了一项名为 Function Calling(函数调用) 的能力,允许 GPT-3.5 和 GPT-4 输出结构化的 JSON 对象来描述"我想执行什么操作",而不是生成一段自然语言回复。开发者只需在真实世界中执行这个操作,并把结果告诉模型,模型就能继续推理。这看似微小的技术更新,实则是 AI 从"语言生成器"迈向"行动代理者"的范式革命。
如果把大语言模型(LLM)比作聪明的大脑,那么 Function Calling 就是连接大脑与外部世界的脊髓和四肢 。它让 LLM 第一次获得了"行动力"------不再只是生成文字,而是能真正调用 API、查询数据库、操作文件、发送邮件、控制硬件。用行业流行的话说:工具调用是 Agent 的手和脚,没有它,Agent 就只能"想想",不能"做到"。
本文将深入解析 Function Calling 的技术原理、工程实现、主流模型对比、安全挑战以及未来趋势,带你看懂这双"手"如何让 AI Agent 真正活起来。
一、什么是工具调用(Function Calling)?
1.1 先于技术之前,理解"工具"的本质
在 AI Agent 的语境下,"工具"(Tool) 是指任何能被 Agent 调用以改变外部世界状态或获取新信息的外部程序、API 或函数。它可以很简单------一个获取当前时间的函数,也可以很复杂------调用某个企业 ERP 系统的接口。但所有工具的共性在于:它们为 LLM 提供了超越其训练数据边界的能力。
根据 2023 年一篇影响深远的论文《ToolLLM: Facilitating Large Language Models to Master 16000+ Real-world APIs》,研究人员将工具定义为"带有正式接口规范的外部功能模块",并指出让 LLM 学会在大量 API 中做出精准选择是 Agent 通用化的关键一步。而 Function Calling 正是实现这种"选择与调用"的核心机制。
1.2 Function Calling:一个标准化的"翻译层"
传统软件开发中,一个程序调用另一个函数,靠的是代码层面明确的函数名称和参数传递。但在 LLM 的世界里,"意图"是自然语言,如何将非结构化的用户请求翻译成结构化的函数调用指令?这就是 Function Calling 要做的事。
标准化后的 Function Calling 流程可以抽象为三步:
- 定义工具:开发者用 JSON Schema 描述每个工具的功能、参数及类型要求。
- 模型决策:模型根据用户输入和工具描述,自主决定是否调用工具、调用哪个工具、传什么参数。
- 系统执行与反馈:开发者的程序解析模型输出的调用指令,真正执行对应函数,并将返回值以对话形式回传给模型。
以下是 OpenAI 官方文档中的一个简化版工具定义示例:
json
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市在指定日期的天气预报",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如:北京"
},
"date": {
"type": "string",
"description": "日期,格式YYYY-MM-DD,或者'今天'、'明天'"
}
},
"required": ["city"]
}
}
}
当用户说"查一下明天上海的天气",LLM 并不会直接生成"上海明天天气晴",而是返回一个结构化的 function call 请求 ,如 get_weather(city="上海", date="明天")。真正的天气查询由开发者代码执行,执行结果再反馈给 LLM,LLM 据此生成最终的流畅回答。
这个过程中,LLM 的角色从"信息生产者"转变成了"行动决策者"------它只负责在适当的时机选择正确的工具,而具体执行由外部系统完成。正是这种分离,赋予了 Agent 无比广阔的能力边界。
二、工作原理:从"想"到"做"的完整旅程
2.1 核心流程:一个永不停止的循环
工具调用不是一次性的动作,而是一个嵌入在 Agent 主循环(如 ReAct 模式)中的持续过程。让我们用一张流程图来透视它的完整生命周期:
工具函数 Agent (LLM) 用户 工具函数 Agent (LLM) 用户 "帮我查一下明天北京天气,如果下雨就提醒我" 思考:需要先知道天气,应该调用 get_weather 工具 输出 function_call: get_weather(city="北京", date="明天") 查询天气API,得到结果 "阴转小雨,15-22°C" 返回结果: "明天北京:阴转小雨,15-22°C" 思考:天气信息显示有雨,需要执行提醒操作 输出 function_call: send_email(to="user", subject="带伞提醒", body="明天北京有雨,请带伞") 发送邮件成功 返回结果: "邮件已发送" "已经帮您查了天气,明天北京有小雨,我给您发了提醒邮件,记得带伞哦。"
可以看到,Agent 先后调用了两个不同的工具,而且在调用第二个工具之前,它"理解"了第一个工具返回的结果并做出了逻辑判断。这种推理与行动的交替循环,正是 Agent 和普通 RAG 应用最大的区别。
2.2 技术实现:OpenAI Python SDK 示例
下面我们用 OpenAI 的 Python SDK 来实现上述流程的简化版,让你直观感受代码层面的运作方式:
python
import json
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
# 1. 定义工具列表
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"date": {"type": "string", "description": "日期,如'今天'、'明天'"}
},
"required": ["city", "date"]
}
}
},
{
"type": "function",
"function": {
"name": "send_email",
"description": "发送邮件",
"parameters": {
"type": "object",
"properties": {
"to": {"type": "string", "description": "收件人邮箱"},
"subject": {"type": "string", "description": "邮件主题"},
"body": {"type": "string", "description": "邮件正文"}
},
"required": ["to", "subject", "body"]
}
}
}
]
# 2. 真正的函数实现(模拟)
def get_weather(city: str, date: str) -> str:
# 实际应调用天气API
return f"{city} {date}天气:阴转小雨,15-22°C"
def send_email(to: str, subject: str, body: str) -> str:
# 实际应调用SMTP服务
print(f"邮件已发送:收件人={to}, 主题={subject}, 正文={body}")
return "邮件发送成功"
available_functions = {
"get_weather": get_weather,
"send_email": send_email
}
# 3. 主循环:多轮工具调用直到结束
def run_agent(user_query: str):
messages = [{"role": "user", "content": user_query}]
while True:
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools,
tool_choice="auto" # 让模型自主决定是否调用工具
)
response_message = response.choices[0].message
# 如果模型决定直接回答
if response_message.tool_calls is None:
return response_message.content
# 如果模型要求调用工具
messages.append(response_message) # 将模型的调用请求加入历史
for tool_call in response_message.tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
print(f"🔧 调用工具: {function_name}({function_args})")
# 执行真正的函数
function_result = available_functions[function_name](**function_args)
# 将执行结果作为工具消息加入对话
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": function_result
})
# 测试
result = run_agent("明天北京会下雨吗?如果下雨,提醒一下user@example.com")
print(result)
这段代码展示了 Function Calling 的核心要素:
- 工具描述(Schema) 精确定义了工具的接口契约
tool_choice="auto"赋予模型自主决策权- 多轮循环 支持链式操作(查天气 → 发邮件)
- 结果回传 使模型能根据执行结果继续推理
2.3 参数解析与格式约束:让模型"说人话"而非"说JSON"
一个常见的误解是:Function Calling 只是简单让模型输出 JSON。实际上,OpenAI 的 API 内部经过专门的训练,使模型能理解工具描述并生成符合格式的函数调用输出,而开发者无需进行复杂的提示词工程。
但参数解析仍然充满挑战。比如当用户说"帮我查下沪市最近的温度",模型必须将"沪市"正确映射为 "city: 上海",这涉及命名实体识别和语义理解。为了提高参数填充的准确率,开发者可以利用 strict 模式(部分模型支持)强制要求输出的参数类型必须完全符合 Schema 定义。
python
# 在 Anthropic Claude 中,工具定义支持更严格的类型系统
tool_definition = {
"name": "get_stock_price",
"description": "获取股票价格",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "股票代码,如 AAPL、600519.SH"
}
},
"required": ["ticker"]
}
}
三、工具调用的核心挑战:走出"幻想的执行力"
工具调用并非魔法,它在工程实践中面临着一系列严峻挑战。我们在第 10 篇《AI Agent 的"幻觉"问题》中曾简要提及,在此我们深入展开。
3.1 工具选择错误(Intent Classification Failure)
当 Agent 拥有多个功能相近的工具时,模型可能"选错工具"。例如,系统里同时存在 search_web(网络搜索)和 search_database(内部数据库查询),用户问"我们公司去年的利润是多少",模型却错误地调用了网络搜索,因为它从字面上匹配了"搜索"。
解决思路:
- 增强工具描述 :在描述中明确工具的使用场景和限制。如"仅在用户问题明确提到实时、新闻、最新等词汇时使用"。
- 工具检索(Tool Retrieval) :当工具数量超过 20 个,将所有工具描述全部塞进提示词既费 token 又导致模型注意力分散。一个更好的做法是为工具描述建立向量索引,根据当前用户查询动态检索最相关的 Top-K 个工具,仅将这几个工具的 Schema 注入提示词。
python
# 简化的工具检索示例
from chromadb.utils import embedding_functions
class ToolRetriever:
def __init__(self, embedding_model):
self.embed = embedding_model
self.tool_store = [] # 存储 (tool_definition, vector)
def add_tool(self, tool_def):
text = f"{tool_def['name']}: {tool_def['description']}"
vector = self.embed(text)
self.tool_store.append((tool_def, vector))
def retrieve(self, query: str, top_k=5):
query_vec = self.embed(query)
# 计算余弦相似度并返回 Top-K 工具定义
similarities = [(tool, cosine_sim(query_vec, vec)) for tool, vec in self.tool_store]
top = sorted(similarities, key=lambda x: x[1], reverse=True)[:top_k]
return [t[0] for t in top]
3.2 参数幻觉与编造
我们在第 10 篇中曾详细讨论过参数幻觉:当用户试图关闭某个端口,Agent 明明需要二次确认,却直接回复"已关闭"。另一种常见情况是,必填参数缺失时,模型不是反问用户而是直接"编造"一个值。
根据 2025 年某云厂商内部评测数据,在其模型早期版本的 Function Calling 测试中,对于 required 参数,模型不询问用户直接编造的比例高达 23%,这在高风险操作中是致命的。
解决思路:
- 提示词注入安全守则:在系统提示中明确要求:"如果用户未提供必填参数,必须生成自然语言提问,不得自行推测"。
- 功能开关 :使用
tool_choice参数的一种变体,某些平台允许设置"tool_choice": "required"强制模型必须调用某个工具,同时配合参数级别的错误回调。 - 多轮确认:当模型输出包含缺失参数的调用指令时,系统层拦截,向用户发起澄清对话,而不是直接执行。
3.3 执行幻觉与状态反馈
Agent 可能声称"已发送邮件",但实际上并未调用 send_email 函数,或者函数返回了错误但模型却忽略了错误信息。这是由于自回归模型在"请求-执行-确认"的训练模式中,"确认"语句远多于"执行失败后的修正"语句。
工程级防御:
- 结果验证 :系统在收到工具执行结果后,必须进行格式化和状态码检查,如果发现错误(如返回
500 Internal Server Error),必须明确传递给模型并要求其重试或降级。 - 执行日志与可观测性:每次工具调用都应记录详细日志(输入参数、返回结果、耗时),通过 LangSmith、LangFuse 等可观测性平台监控失败率和异常。
3.4 流式输出场景下的工具调用
在流式(Streaming)模式下,工具调用的解析变得复杂。传统的流式响应是逐 token 返回的,但函数调用需要完整的 JSON 块。OpenAI 2024 年后的实现已经支持在流式传输中包含工具调用:当模型要调用工具时,流中的 delta 会包含 tool_calls 信息,并且可以分块传输参数。开发者需要在客户端拼接完整的 JSON,然后再执行函数。
python
# 处理流式工具调用(简化)
stream = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools,
stream=True
)
tool_call_data = {}
for chunk in stream:
delta = chunk.choices[0].delta
if delta.tool_calls:
for tc in delta.tool_calls:
if tc.id:
tool_call_data[tc.index] = {"id": tc.id, "name": "", "arguments": ""}
if tc.function.name:
tool_call_data[tc.index]["name"] += tc.function.name
if tc.function.arguments:
tool_call_data[tc.index]["arguments"] += tc.function.arguments
# 流结束后,tool_call_data 中就包含了完整的调用信息
四、主流模型 Function Calling 能力横评
2025 年的模型市场,Function Calling 已不再是 OpenAI 的独门秘技。各大模型厂商纷纷推出了自己的函数调用实现,但在稳定性、复杂结构支持、多工具并行、成本等方面存在显著差异。
| 特性 | OpenAI (GPT-4o) | Anthropic (Claude Sonnet 4) | DeepSeek (V3/R1) | 智谱 GLM-4.5 | Google Gemini 2.5 |
|---|---|---|---|---|---|
| 调用方式 | tools 参数 + 原生 finetune |
首个为工具调用设计的模型 | 兼容 OpenAI 格式 | 兼容 OpenAI 格式 | 原生 functionCalling 配置 |
| 并行工具调用 | 支持(一次返回多个) | 支持 | 部分支持(需prompt引导) | 支持 | 支持 |
| 严格模式 | 支持 strict: true |
支持(从提示词层面) | 有限 | 有限 | 有限 |
| 多模态输入+工具调用 | 支持 | 支持 | 不支持 | 支持 | 支持 |
| 成本(每百万输入token) | $2.50 (gpt-4o) | $3.00 (sonnet) | ¥2 (deepseek-chat) | ¥1 (glm-4.5) | $1.25 (gemini 2.5 flash) |
| 工具调用成功率 | 95%+ (简单任务) | 极强,尤其复杂推理 | 约90% (部分边缘案例出问题) | 约92% (持续进步) | 约94% |
数据来源:各厂商官方文档、公开基准测试及社区反馈,2025年Q1。
选型建议:
- 如果追求极致的稳定性和复杂工作流,Claude Sonnet 4 因其对工具使用的原生深度训练而最被推荐。
- 如果对成本敏感且任务主要为国内场景,DeepSeek 和 GLM-4.5 是性价比极高的选择,且对中文参数的理解天然占优。
- 如果需要视觉理解配合工具操作,GPT-4o 和 Gemini 2.5 具有多模态工具调用的优势。
五、安全边界:别让Agent的手"乱摸"
工具调用赋予了 Agent 强大的执行力,但这也意味着一旦失控,损失将比一个单纯的文本错误大得多。安全设计必须内建于工具调用的整个生命周期。
5.1 最小权限原则
每个工具应声明其所需的权限等级,Agent 框架应确保一次会话中 Agent 可用的工具集合是最小化的。例如,一个数据分析 Agent 不应被授予删除生产数据库的权限。
5.2 高风险操作确认
对于支付、删除、群发邮件等不可逆操作,必须在执行前进行人工确认(Human-in-the-loop)。常见的实现模式是:当 Agent 调用高风险工具时,执行器拦截调用,生成一个确认请求发送给用户(如钉钉/飞书消息),待用户回复"确认"后才真正执行。
python
def safe_tool_executor(tool_name, args, risk_level):
if risk_level == "HIGH":
user_approved = ask_user_permission(f"Agent请求执行高风险操作:{tool_name},参数:{args}")
if not user_approved:
return "执行被用户拒绝"
return execute_tool(tool_name, args)
5.3 沙箱与速率限制
工具执行应运行在受限的沙箱环境中(如 Docker 容器),并限制其网络访问、文件系统写入范围和执行超时。此外,对单个 Agent 实例的每分钟工具调用次数进行限制,防止失控循环。
六、高级实践与未来趋势:MCP协议与工具生态
6.1 MCP(模型上下文协议):统一工具调用的"USB-C"
长久以来,不同 AI 模型和不同工具之间的连接都是"点对点"的,开发者需要为每个模型写一次工具集成代码。2024 年底,Anthropic 率先发布了 MCP(Model Context Protocol),旨在为 AI 模型和外部工具/数据源之间建立统一的连接标准。MCP 基于 JSON-RPC 2.0,支持工具发现、调用和资源访问。它就像 USB-C 接口一样,让各种工具可以"即插即用"到任何支持 MCP 的 Agent 上。
MCP模式
任何Agent
MCP Server
工具1 标准接口
工具2 标准接口
传统模式
Agent A
工具1 定制接口
工具2 定制接口
Agent B
到 2025 年,MCP 已被广泛采纳。火山引擎的豆包大模型、阿里百炼、Dify 等平台均支持 MCP 工具接入。MCP 生态的出现,将加速工具市场的形成,未来会有成千上万个预制的 MCP 工具可供开发者直接使用,极大降低了 Agent 开发的门槛。
6.2 Agent-to-Agent 工具调用
工具调用即将从"Agent 调用工具"走向"Agent 调用 Agent"。在一个多智能体系统中,一个智能体可以将另一个智能体视为"工具"------只要那个智能体暴露了符合 Function Calling 规范的接口。这模糊了工具与智能体的边界,形成了一个能力递归的网络。微软的 AutoGen 框架已经展示了这种模式。
6.3 从文本驱动到多模态工具调用
未来的工具调用将不仅限于文本。Agent 可以直接调用图像编辑工具、语音合成工具,甚至通过视觉模型分析屏幕画面后执行图形界面操作(如 Anthropic 的 Computer Use 功能)。这代表着工具调用从"API 级"跃迁到"界面级",Agent 的"手"可以触及任何人类能操作的软件。
七、总结:给Agent装上可靠而强劲的"四肢"
回到开篇的比喻:LLM 是大脑,工具调用就是它的手和脚。一个没有工具调用的 AI 只能坐在椅子上空谈;而有了可靠的工具调用能力,它就能站起来,走出房门,在数字世界乃至物理世界中真正"干活"。
构建生产级 Agent 的工具调用系统,不是简单地把几个 API 扔给模型,而需要精心设计:
- 定义清晰:工具描述、参数类型、约束条件必须准确到没有歧义。
- 动态检索:当工具数量膨胀,需要向量索引来动态发现相关工具。
- 鲁棒执行:必须处理参数缺失、调用失败、结果异常等所有边缘情况。
- 安全护栏:高风险操作要有人工确认,执行环境要隔离。
- 可观测性:每次调用的日志、指标、追踪是调试和优化的基础。
随着 MCP 协议的普及和模型自身工具使用能力的持续增强(例如 DeepSeek R1 的深度推理使其能规划更复杂的工具使用序列),2025 年之后,工具调用将从"技术高手的特技"变为"AI 应用开发的标配"。届时,一个不会调用工具的 AI,就像一个没有双手的人------空有满腹经纶,却难成片瓦之功。
给读者的建议:本文是"Agent 进化论"系列的第十六篇,深度解析了工具调用的原理、实现与挑战。下一篇,我们将转向 Agent 安全领域的核心议题------《Agent的安全边界:如何防止AI失控(对齐问题)》,探讨当Agent变得越来越强时,我们如何确保它始终站在人类这一边。
下一篇预告:《Agent的安全边界:如何防止AI失控(对齐问题)》