什么是“工具调用”(Function Calling)?Agent的手和脚

目录

  • [什么是"工具调用"(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 流程可以抽象为三步:

  1. 定义工具:开发者用 JSON Schema 描述每个工具的功能、参数及类型要求。
  2. 模型决策:模型根据用户输入和工具描述,自主决定是否调用工具、调用哪个工具、传什么参数。
  3. 系统执行与反馈:开发者的程序解析模型输出的调用指令,真正执行对应函数,并将返回值以对话形式回传给模型。

以下是 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失控(对齐问题)》

相关推荐
漓漾li2 小时前
每日面试题(2026-05-15)
架构·go·agent
jiayong232 小时前
Tool Permission 与 Sandbox 的安全流水线:Agent 工具系统的工程边界
java·数据库·安全·agent
小李子呢02112 小时前
大模型是什么?
大模型·agent
无敌昊哥战神2 小时前
大模型(LLM)推理优化技术全景总结
python·算法·大模型
Fleshy数模2 小时前
基于 LangChain 实现 PDF 文档检索:从加载到向量检索全流程
人工智能·数据挖掘·langchain·大模型
阿里云云原生2 小时前
实战揭秘:如何让你的 Agent 无缝接入现有系统?
agent
Li_yizYa3 小时前
【大模型篇】谈谈对于Function Calling、MCP、Skill的理解
ai·大模型
凌奕3 小时前
100 行代码搞懂多 Agent 协同:LangGraph 最小可运行示例(研究员 vs 批评家 + 总结员)
langchain·agent
小李子呢02113 小时前
什么是agent?
agent
带刺的坐椅3 小时前
Spring AI 2.0 GA 倒计时:先别急,来看看 Java AI 框架的另一条路
java·spring·ai·llm·agent·solon