全部笔记All notes

大模型 Agent 与工具调用

阅读 7m 24s7m 24s read

Agent 与工具调用

Function Calling 机制、Agent 架构和实现模式。


什么是工具调用?

工具调用(Function Calling / Tool Use)让大模型能够”使用工具”。模型本身只能生成文本,但通过工具调用,它可以:

  • 查询实时数据(天气、股价、新闻)
  • 执行计算和代码
  • 操作外部系统(发邮件、创建任务)
  • 检索知识库

这是构建 AI Agent 的核心能力。

工具调用的本质:

大模型本身是”无状态”的文本生成器,它不能访问互联网、不能执行代码、不能操作数据库。工具调用是一种”桥接”机制,让模型可以通过你的代码与外部世界交互。

工具调用 vs 传统 API:

方面传统 API工具调用
调用决策程序员硬编码模型自主决定
参数提取手动解析模型从自然语言提取
灵活性固定流程动态适应
适用场景结构化输入自然语言输入

工具调用流程

工具调用是一个多轮交互过程:

  1. 用户提问 + 告诉模型有哪些工具可用
  2. 模型决策:判断是否需要调用工具,生成调用参数
  3. 执行工具:你的代码执行实际操作
  4. 返回结果:将工具执行结果告诉模型
  5. 生成回答:模型基于工具结果生成最终回答

模型不会真正执行工具,它只是”决定”调用什么工具、传什么参数。实际执行由你的代码完成。

sequenceDiagram
    participant User
    participant LLM
    participant Tool
    
    User->>LLM: 问题 + 工具定义
    LLM->>LLM: 决定调用工具
    LLM-->>User: tool_call
    User->>Tool: 执行工具
    Tool-->>User: 结果
    User->>LLM: 工具结果
    LLM-->>User: 最终回答

关键理解:

  • 模型只是”建议”调用工具,不会真正执行
  • 你可以选择执行或拒绝工具调用
  • 工具结果需要再次发送给模型
  • 模型可能需要多次工具调用才能完成任务

工具定义

工具定义告诉模型有哪些工具可用、每个工具的功能和参数。好的工具定义是工具调用成功的关键。

工具定义对比

字段OpenAIClaudeGemini
容器tools[].functiontools[]tools[].functionDeclarations[]
名称namenamename
描述descriptiondescriptiondescription
参数parametersinput_schemaparameters

统一结构

所有厂商都使用 JSON Schema 定义参数:

{
  "name": "get_weather",
  "description": "获取天气",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {"type": "string", "description": "城市名"}
    },
    "required": ["city"]
  }
}

工具定义最佳实践:

要素建议
name简洁明了,使用 snake_case
description详细描述功能和使用场景
parameters每个参数都要有 description
required明确标注必需参数
enum对于有限选项,使用 enum 约束

好的 description 示例:

{
  "name": "search_products",
  "description": "在商品数据库中搜索产品。当用户询问商品信息、价格、库存时使用此工具。支持按名称、类别、价格范围搜索。",
  "parameters": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "搜索关键词,如产品名称或描述"
      },
      "category": {
        "type": "string",
        "enum": ["electronics", "clothing", "food"],
        "description": "产品类别,可选"
      },
      "max_price": {
        "type": "number",
        "description": "最高价格限制,单位:元"
      }
    },
    "required": ["query"]
  }
}

工具调用响应

flowchart LR
    A[LLM 响应] --> B{包含工具调用?}
    B -->|是| C[提取 tool_call]
    B -->|否| D[直接返回文本]
    
    C --> E[执行工具]
    E --> F[返回结果给 LLM]
    F --> A

响应格式对比

厂商工具调用字段结束原因参数格式
OpenAItool_callstool_callsJSON 字符串
Claudecontent[].tool_usetool_use对象
Geminiparts[].functionCallSTOP对象

注意: OpenAI 的 arguments 是 JSON 字符串,需要 json.loads() 解析;Claude 和 Gemini 的参数已经是对象。


Agent 架构

Agent 是能够自主决策、多步执行的 AI 系统。它不只是”问一答一”,而是能够规划任务、调用工具、根据结果调整策略。

Agent vs 简单对话:

方面简单对话Agent
交互模式一问一答多步骤执行
决策能力无自主决策
工具使用可选核心能力
状态管理无维护执行状态
错误处理简单可重试、可调整

ReAct 模式

ReAct(Reasoning + Acting)是最经典的 Agent 模式。它交替进行”思考”和”行动”:

  • Thought:分析当前状态,决定下一步
  • Action:执行具体操作(调用工具)
  • Observation:观察执行结果
  • 循环直到任务完成
flowchart TD
    A[输入] --> B[思考 Thought]
    B --> C[行动 Action]
    C --> D[观察 Observation]
    D --> E{完成?}
    E -->|否| B
    E -->|是| F[输出]

ReAct 的优势:

  1. 可解释性:每一步都有明确的思考过程
  2. 可调试:可以看到 Agent 的决策逻辑
  3. 可控制:可以在任何步骤介入

实现框架

def agent_loop(query, tools, max_steps=10):
    messages = [{"role": "user", "content": query}]
    
    for step in range(max_steps):
        resp = llm.chat(messages, tools=tools)
        
        if not resp.tool_calls:
            return resp.content  # 完成
        
        # 记录助手消息
        messages.append({"role": "assistant", "tool_calls": resp.tool_calls})
        
        # 执行工具
        for call in resp.tool_calls:
            result = execute_tool(call.name, call.args)
            messages.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": json.dumps(result)
            })
    
    return "达到最大步数"

Agent 循环的关键点:

  1. 设置最大步数,防止无限循环
  2. 正确维护消息历史
  3. 处理工具执行错误
  4. 判断任务是否完成

常见 Agent 模式

根据复杂度和场景,Agent 有不同的架构模式。

1. 单工具 Agent

最简单的形式,模型只能调用一个工具。适合单一功能场景,如天气查询、翻译等。

flowchart LR
    A[用户] --> B[LLM]
    B --> C[工具]
    C --> B
    B --> A

2. 多工具 Agent

模型可以从多个工具中选择,根据任务需要调用不同工具。这是最常见的 Agent 形式。

flowchart TD
    A[用户] --> B[LLM]
    B --> C{选择工具}
    C --> D[搜索]
    C --> E[计算]
    C --> F[代码执行]
    D & E & F --> B
    B --> A

3. 多 Agent 协作

复杂任务可以拆分给多个专门的 Agent,由一个协调者统筹。例如:研究员负责收集信息,写作者负责生成内容,审核者负责检查质量。

flowchart LR
    A[协调者] --> B[研究员]
    A --> C[写作者]
    A --> D[审核者]
    B --> A
    C --> A
    D --> A

tool_choice 控制

tool_choice 参数控制模型如何选择工具。不同场景需要不同的策略。

值说明场景
autoLLM 自动决定默认,让模型判断是否需要工具
none禁止调用工具纯对话,不需要工具
required必须调用工具强制执行某个操作
{name: "xxx"}指定工具明确知道要用哪个工具

并行工具调用

现代模型支持一次返回多个工具调用,可以并行执行以提升效率。

例如用户问”北京和上海的天气”,模型可以同时返回两个 get_weather 调用,你可以并行执行它们。

flowchart TD
    A[LLM] --> B[tool_call 1]
    A --> C[tool_call 2]
    A --> D[tool_call 3]
    
    B & C & D --> E[并行执行]
    E --> F[合并结果]
    F --> G[返回 LLM]
# 并行执行
import asyncio

async def execute_parallel(tool_calls):
    tasks = [execute_tool(c.name, c.args) for c in tool_calls]
    return await asyncio.gather(*tasks)

内置工具

厂商工具说明
OpenAIweb_search网页搜索
OpenAIcode_interpreter代码执行
OpenAIfile_search文件检索
GeminicodeExecution代码执行
GeminigoogleSearchGoogle 搜索

Model Context Protocol (MCP)

除了各厂商私有的 Function Calling 格式,Anthropic 推出了开放标准 MCP。

  • 标准化:统一了连接本地资源(数据库、文件、Git)的方式
  • 互操作:写一次 Server,任何支持 MCP 的 Client(如 Claude Desktop, Cursor)都能用
  • 详解:请参考 27-大模型-MCP协议详解

最佳实践

实践说明
工具描述清晰LLM 依赖描述决策
参数验证执行前校验参数
错误处理工具失败要告知 LLM
限制步数防止无限循环
日志记录便于调试追踪

工具设计原则

好的工具设计是 Agent 成功的关键。

命名规范

动词_名词 格式,清晰表达功能

✅ get_weather      获取天气
✅ search_documents 搜索文档
✅ send_email       发送邮件

❌ weather          不清楚是获取还是设置
❌ do_stuff         太模糊

描述要点

工具描述是 LLM 决策的依据,需要包含:

  1. 功能说明:这个工具做什么
  2. 使用场景:什么时候应该用
  3. 限制条件:什么情况不能用
{
  "name": "search_web",
  "description": "搜索互联网获取实时信息。当用户询问最新新闻、实时数据、或你不确定的事实时使用。不要用于已知的常识性问题。"
}

参数设计

{
  "parameters": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "搜索关键词,应该简洁明确,2-5 个词最佳"
      },
      "limit": {
        "type": "integer",
        "description": "返回结果数量,默认 5",
        "default": 5
      }
    },
    "required": ["query"]
  }
}

记忆管理

长对话中,Agent 需要管理记忆以保持上下文连贯。

记忆类型

flowchart LR
    A[记忆] --> B[短期记忆]
    A --> C[长期记忆]
    A --> D[工作记忆]
    
    B --> B1[当前对话]
    C --> C1[向量数据库]
    D --> D1[当前任务状态]
类型存储用途
短期记忆消息列表当前对话上下文
长期记忆向量数据库历史知识检索
工作记忆变量当前任务中间状态

上下文压缩

当对话过长时,需要压缩历史消息:

def compress_history(messages, max_tokens=4000):
    """压缩对话历史"""
    # 保留系统消息
    system = [m for m in messages if m["role"] == "system"]
    
    # 保留最近 N 轮
    recent = messages[-10:]
    
    # 中间部分生成摘要
    middle = messages[len(system):-10]
    if middle:
        summary = summarize(middle)
        return system + [{"role": "system", "content": f"历史摘要:{summary}"}] + recent
    
    return system + recent

调试技巧

可视化执行过程

def agent_loop_debug(query, tools):
    print(f"🎯 任务: {query}\n")
    
    for step in range(10):
        print(f"--- 步骤 {step + 1} ---")
        resp = llm.chat(messages, tools=tools)
        
        if resp.tool_calls:
            for call in resp.tool_calls:
                print(f"🔧 调用工具: {call.name}")
                print(f"   参数: {call.args}")
                result = execute_tool(call.name, call.args)
                print(f"   结果: {result[:100]}...")
        else:
            print(f"💬 回答: {resp.content}")
            return resp.content

常见问题排查

问题可能原因解决方案
不调用工具描述不清晰优化工具描述
调用错误工具工具太相似区分工具职责
参数错误参数描述不明添加示例和约束
无限循环缺少终止条件限制最大步数

相关文档