知识模块
🤖 Agent 知识模块
九、工具模块
工具调用概述

工具调用概述

工具调用(Tool Calling)是 Agent 系统的核心能力,使大语言模型能够与外部系统交互、执行实际操作。理解工具调用的发展历程和核心机制,是构建实用 Agent 的第一步。


一、核心原理

1.1 什么是工具调用?

工具调用是指 LLM 在生成文本时,能够识别出需要调用外部工具的场景,并生成结构化的工具调用请求:

┌─────────────────────────────────────────────────────────────┐
│                    工具调用本质                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   用户输入:                                                │
│   "北京现在的天气怎么样?"                                  │
│                                                             │
│   LLM 内部推理:                                            │
│   ┌─────────────────────────────────────────────────────┐  │
│   │ 1. 分析意图:用户需要实时天气信息                    │  │
│   │ 2. 判断能力:我的训练数据没有实时信息                │  │
│   │ 3. 选择工具:有天气查询工具可用                      │  │
│   │ 4. 构建调用:weather_search(city="北京")             │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
│   工具调用输出(结构化):                                  │
│   {                                                         │
│     "tool": "weather_search",                               │
│     "arguments": {                                          │
│       "city": "北京"                                        │
│     }                                                       │
│   }                                                         │
│                                                             │
│   执行结果:                                                │
│   {                                                         │
│     "temperature": "18°C",                                  │
│     "condition": "晴",                                      │
│     "humidity": "45%"                                       │
│   }                                                         │
│                                                             │
│   最终回答:                                                │
│   "北京现在天气晴朗,气温18°C,湿度45%。"                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 工具调用的核心要素

要素说明示例
工具定义描述工具的功能、参数、返回值JSON Schema
意图识别判断是否需要调用工具"搜索" → 需要搜索工具
参数提取从自然语言中提取工具参数"北京" → city 参数
调用执行实际执行工具并获取结果API 调用、代码执行
结果整合将工具结果整合到回答中自然语言描述

1.3 工具调用 vs 普通 LLM 调用

┌─────────────────────────────────────────────────────────────┐
│                    对比:普通调用 vs 工具调用                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   普通 LLM 调用:                                           │
│   ┌─────────┐     ┌─────────┐     ┌─────────┐             │
│   │  输入   │ ──→ │   LLM   │ ──→ │  输出   │             │
│   │ "问题"  │     │  处理   │     │ "回答"  │             │
│   └─────────┘     └─────────┘     └─────────┘             │
│                   单次调用,无外部交互                       │
│                                                             │
│   工具调用:                                                │
│   ┌─────────┐     ┌─────────┐     ┌─────────┐             │
│   │  输入   │ ──→ │   LLM   │ ──→ │工具调用 │             │
│   │ "问题"  │     │  分析   │     │ 请求    │             │
│   └─────────┘     └─────────┘     └────┬────┘             │
│                                        │                    │
│                                        ↓                    │
│   ┌─────────┐     ┌─────────┐     ┌─────────┐             │
│   │  最终   │ ←── │  LLM    │ ←── │ 工具    │             │
│   │  回答   │     │  整合   │     │ 结果    │             │
│   └─────────┘     └─────────┘     └─────────┘             │
│                   多次调用,有外部交互                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、发展历程

2.1 三个阶段演进

┌─────────────────────────────────────────────────────────────┐
│                    工具调用发展历程                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  第一阶段:Prompt Hacking(2022-2023初)                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 特点:通过 Prompt 工程模拟工具调用                    │   │
│  │                                                      │   │
│  │ 方法:                                               │   │
│  │ • 在 Prompt 中描述工具用法                           │   │
│  │ • 让 LLM 输出特定格式(如 [SEARCH:xxx])              │   │
│  │ • 用正则表达式解析输出                               │   │
│  │                                                      │   │
│  │ 问题:                                               │   │
│  │ • 格式不稳定,容易出错                               │   │
│  │ • 无法保证参数正确性                                 │   │
│  │ • 上下文消耗大                                       │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│  第二阶段:Native Function Calling(2023中-2024)           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 特点:LLM 原生支持结构化工具调用                      │   │
│  │                                                      │   │
│  │ 代表:                                               │   │
│  │ • OpenAI Function Calling (2023.06)                  │   │
│  │ • Anthropic Tool Use (2024)                          │   │
│  │ • Google Gemini Function Calling                     │   │
│  │                                                      │   │
│  │ 优势:                                               │   │
│  │ • 结构化输出,格式可靠                               │   │
│  │ • 参数类型验证                                       │   │
│  │ • 多工具选择支持                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│  第三阶段:Protocol Standardization(2024至今)             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 特点:标准化协议实现跨平台工具互联                    │   │
│  │                                                      │   │
│  │ 代表:                                               │   │
│  │ • MCP (Model Context Protocol) - Anthropic           │   │
│  │ • OpenAPI / Swagger 标准                             │   │
│  │ • LangChain Tool Standard                            │   │
│  │                                                      │   │
│  │ 优势:                                               │   │
│  │ • 跨平台兼容                                         │   │
│  │ • 工具生态共享                                       │   │
│  │ • 标准化开发体验                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.2 里程碑事件

时间事件意义
2022.10ReAct 论文发表提出 Thought-Action-Observation 框架
2023.03LangChain 发布标准化 Tool 抽象
2023.06OpenAI Function Calling首个原生工具调用支持
2023.10OpenAI Assistants API内置工具(代码解释器、检索)
2024.04Claude Tool Use多模态工具调用
2024.11MCP 协议发布工具调用标准化

2.3 各厂商实现对比

特性OpenAIAnthropicGoogle开源方案
调用方式function_calltool_usefunction_calling自定义
并行调用✅ 支持✅ 支持✅ 支持需实现
流式输出✅ 支持✅ 支持✅ 支持需实现
多模态✅ Vision✅ 全模态✅ 多模态视实现
工具数量限制128个无明确限制无明确限制无限制

三、工具调用的本质

3.1 从语义空间到执行空间

┌─────────────────────────────────────────────────────────────┐
│                    工具调用的本质映射                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   语义空间(LLM 理解的世界)                                │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                      │  │
│   │   "查询北京天气"  ──→  意图:获取天气信息            │  │
│   │          │                    │                      │  │
│   │          ↓                    ↓                      │  │
│   │   实体识别:北京     参数映射:city="北京"           │  │
│   │                                                      │  │
│   └──────────────────────┬───────────────────────────────┘  │
│                          │                                  │
│                          ↓ 工具调用桥梁                     │
│                                                             │
│   执行空间(真实世界)                                      │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                      │  │
│   │   weather_api.query("北京")  ──→  API 请求          │  │
│   │          │                                           │  │
│   │          ↓                                           │  │
│   │   { temp: 18, condition: "晴" }  ←  API 响应        │  │
│   │                                                      │  │
│   └──────────────────────┬───────────────────────────────┘  │
│                          │                                  │
│                          ↓ 结果映射                         │
│                                                             │
│   回到语义空间                                              │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                      │  │
│   │   "北京现在天气晴朗,气温 18°C"                      │  │
│   │                                                      │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.2 为什么需要结构化输出?

┌─────────────────────────────────────────────────────────────┐
│                    结构化 vs 非结构化输出                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   非结构化输出(Prompt Hacking):                          │
│   ┌─────────────────────────────────────────────────────┐  │
│   │ LLM 输出:                                           │  │
│   │ "我需要搜索一下。[SEARCH: 北京天气]"                  │  │
│   │                                                      │  │
│   │ 问题:                                               │  │
│   │ • 格式不一致:可能是 [SEARCH:xxx] 或 SEARCH(xxx)     │  │
│   │ • 参数解析困难:引号、空格、特殊字符                  │  │
│   │ • 错误恢复:解析失败后怎么办?                        │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
│   结构化输出(Function Calling):                          │
│   ┌─────────────────────────────────────────────────────┐  │
│   │ LLM 输出(JSON):                                   │  │
│   │ {                                                    │  │
│   │   "function": "weather_search",                      │  │
│   │   "arguments": {                                     │  │
│   │     "city": "北京"                                   │  │
│   │   }                                                  │  │
│   │ }                                                    │  │
│   │                                                      │  │
│   │ 优势:                                               │  │
│   │ • 格式统一:标准 JSON 结构                           │  │
│   │ • 类型安全:参数类型由 Schema 约束                   │  │
│   │ • 易于解析:直接 JSON 解析                           │  │
│   │ • 错误处理:Schema 验证提供清晰错误信息              │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

四、代码示例

4.1 Prompt Hacking 方式(早期)

"""
早期工具调用方式:Prompt Hacking
通过提示词工程模拟工具调用
"""
 
def create_tool_prompt(tools: list) -> str:
    """创建工具提示词"""
    tool_descriptions = []
    for tool in tools:
        tool_descriptions.append(f"- {tool['name']}: {tool['description']}")
    
    prompt = f"""
你是一个智能助手,可以使用以下工具:
{chr(10).join(tool_descriptions)}
 
当你需要使用工具时,请按以下格式输出:
[ACTION: 工具名]
[INPUT: 输入参数]
 
示例:
用户:北京天气怎么样?
助手:[ACTION: weather_search]
[INPUT: 北京]
 
现在请回答用户的问题。
"""
    return prompt
 
 
def parse_tool_output(output: str) -> tuple:
    """解析工具调用输出"""
    import re
    
    # 尝试解析 ACTION
    action_match = re.search(r'\[ACTION:\s*(.+?)\]', output)
    input_match = re.search(r'\[INPUT:\s*(.+?)\]', output, re.DOTALL)
    
    if action_match:
        action = action_match.group(1).strip()
        input_val = input_match.group(1).strip() if input_match else ""
        return action, input_val
    
    return None, None
 
 
# 使用示例
tools = [
    {"name": "weather_search", "description": "搜索城市天气"},
    {"name": "calculator", "description": "执行数学计算"}
]
 
prompt = create_tool_prompt(tools)
# 将 prompt 发送给 LLM,然后解析输出

4.2 OpenAI Function Calling 方式

"""
OpenAI Function Calling 示例
使用原生函数调用能力
"""
 
from openai import OpenAI
 
client = OpenAI()
 
# 定义工具(Function Schema)
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如:北京、上海"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "温度单位"
                    }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "执行数学计算",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "数学表达式,如:2+2, 10*5"
                    }
                },
                "required": ["expression"]
            }
        }
    }
]
 
 
def get_weather(city: str, unit: str = "celsius") -> dict:
    """模拟天气 API"""
    # 实际应用中这里会调用真实的天气 API
    weather_data = {
        "北京": {"temp": 18, "condition": "晴"},
        "上海": {"temp": 22, "condition": "多云"}
    }
    return weather_data.get(city, {"temp": 20, "condition": "未知"})
 
 
def calculate(expression: str) -> float:
    """执行数学计算"""
    try:
        return eval(expression)
    except Exception as e:
        return f"计算错误:{str(e)}"
 
 
def run_agent(user_message: str):
    """运行 Agent"""
    messages = [{"role": "user", "content": user_message}]
    
    # 第一次调用:LLM 决定是否调用工具
    response = client.chat.completions.create(
        model="gpt-4",
        messages=messages,
        tools=tools,
        tool_choice="auto"  # 让模型自动决定
    )
    
    message = response.choices[0].message
    
    # 检查是否需要调用工具
    if message.tool_calls:
        # 将助手消息添加到历史
        messages.append(message)
        
        # 执行每个工具调用
        for tool_call in message.tool_calls:
            function_name = tool_call.function.name
            arguments = json.loads(tool_call.function.arguments)
            
            # 执行工具
            if function_name == "get_weather":
                result = get_weather(**arguments)
            elif function_name == "calculate":
                result = calculate(**arguments)
            else:
                result = "未知工具"
            
            # 添加工具结果到消息历史
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": str(result)
            })
        
        # 第二次调用:LLM 根据工具结果生成最终回答
        final_response = client.chat.completions.create(
            model="gpt-4",
            messages=messages
        )
        
        return final_response.choices[0].message.content
    
    return message.content
 
 
# 使用示例
import json
result = run_agent("北京和上海现在的天气怎么样?")
print(result)
# 输出:北京现在天气晴朗,气温18°C;上海多云,气温22°C。

4.3 LangChain 工具调用方式

"""
LangChain 工具调用示例
标准化工具抽象
"""
 
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
 
 
# 使用装饰器定义工具
@tool
def get_weather(city: str) -> str:
    """
    获取指定城市的当前天气
    
    Args:
        city: 城市名称,如:北京、上海
        
    Returns:
        天气信息描述
    """
    weather_data = {
        "北京": "晴天,18°C",
        "上海": "多云,22°C"
    }
    return weather_data.get(city, f"未找到{city}的天气信息")
 
 
@tool
def calculate(expression: str) -> str:
    """
    执行数学计算
    
    Args:
        expression: 数学表达式,如:2+2, 10*5
        
    Returns:
        计算结果
    """
    try:
        result = eval(expression)
        return f"计算结果:{result}"
    except Exception as e:
        return f"计算错误:{str(e)}"
 
 
def create_agent():
    """创建工具调用 Agent"""
    
    # 初始化 LLM
    llm = ChatOpenAI(model="gpt-4", temperature=0)
    
    # 工具列表
    tools = [get_weather, calculate]
    
    # 创建提示词模板
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个智能助手,可以使用工具帮助用户。"),
        ("human", "{input}"),
        ("placeholder", "{agent_scratchpad}"),
    ])
    
    # 创建 Agent
    agent = create_tool_calling_agent(llm, tools, prompt)
    
    # 创建执行器
    agent_executor = AgentExecutor(
        agent=agent,
        tools=tools,
        verbose=True,
        max_iterations=5
    )
    
    return agent_executor
 
 
# 使用示例
if __name__ == "__main__":
    executor = create_agent()
    
    result = executor.invoke({
        "input": "北京天气怎么样?如果气温是18度,换算成华氏度是多少?"
    })
    
    print(result["output"])

五、适用场景

5.1 最佳适用场景

场景具体应用工具需求
实时信息查询天气、股价、新闻搜索工具、API 调用
数据处理计算、分析、转换计算器、代码执行
系统操作文件管理、流程自动化系统工具、CLI
知识检索文档搜索、数据库查询RAG、数据库工具
多模态处理图片分析、音频处理多模态工具

5.2 不适用场景

场景原因替代方案
纯文本对话无需外部工具直接 LLM 调用
确定性流程不需要动态决策硬编码流程
超低延迟需求工具调用增加延迟预计算、缓存

六、面试高频问题

Q1: 工具调用和普通 LLM 调用有什么区别?

答案要点

  1. 交互方式:工具调用涉及多轮交互(LLM → 工具 → LLM),普通调用是单次请求
  2. 输出格式:工具调用需要结构化输出(JSON),普通调用是自由文本
  3. 能力边界:工具调用可突破 LLM 的知识截止和能力限制
  4. 成本和延迟:工具调用通常需要多次 LLM 调用,成本和延迟更高

Q2: 为什么 OpenAI 的 Function Calling 比早期 Prompt Hacking 更可靠?

答案要点

  1. 结构化保证:原生输出 JSON 格式,避免解析错误
  2. 类型安全:通过 JSON Schema 进行参数类型验证
  3. 训练优化:模型专门针对工具调用场景进行了训练
  4. 错误处理:提供清晰的错误信息和重试机制

Q3: 如何设计一个好的工具定义?

答案要点

  1. 清晰的描述:描述工具的功能、适用场景、限制条件
  2. 完整的 Schema:定义所有参数的类型、必填项、枚举值
  3. 合理的粒度:工具功能单一但完整,避免过于复杂或过于简单
  4. 幂等性设计:相同输入产生相同输出,便于缓存和重试

Q4: 工具调用可能遇到哪些问题?如何解决?

答案要点

问题解决方案
幻觉调用提高工具描述清晰度,添加工具使用示例
参数错误Schema 验证 + 自动修复 + 用户确认
执行超时设置超时时间 + 异步执行 + 进度反馈
结果过大结果截断 + 摘要生成 + 分页返回
安全风险沙箱执行 + 权限控制 + 审计日志

Q5: 多工具场景下如何提高工具选择准确率?

答案要点

  1. 语义检索:使用 Tool RAG 根据意图检索相关工具
  2. 分层路由:先分类再选择,减少候选工具数量
  3. 工具描述优化:添加更多使用示例和适用场景描述
  4. Fine-tuning:针对特定工具集进行模型微调

七、小结

概念一句话总结
工具调用本质LLM 从语义空间到执行空间的映射
发展历程Prompt Hacking → Native Function Calling → Protocol Standardization
核心价值突破 LLM 的知识截止和能力限制
关键挑战意图识别、参数提取、执行安全、结果处理

一句话总结:工具调用是 Agent 系统的核心能力,理解其本质和发展历程有助于更好地设计和优化 Agent 应用。


最后更新:2026年3月18日