工具调用:Agent的双手

没有工具调用能力的LLM只是一个"会说话的大脑"。真正的Agent需要调用API、执行代码、读写文件——这些都需要工具调用机制。

演进历程

阶段一:Prompt工程时代(2022-2023)

最早的工具调用靠Prompt引导:

你可以使用以下工具:
1. search(query): 搜索网页
2. calculator(expr): 数学计算

如果需要使用工具,请输出以下格式:
<tool>search("天气")</tool>

这种方式的问题:

  • 格式不稳定,模型经常不遵守
  • 参数提取容易出错
  • 无法处理多步工具调用

阶段二:Function Calling时代(2023-2024)

OpenAI在2023年6月推出Function Calling,将工具调用作为原生API能力:

response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "北京天气"}],
    tools=[{
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取城市天气",
            "parameters": {
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"]
            }
        }
    }]
)
# response.choices[0].message.tool_calls

模型输出结构化的工具调用请求,API层解析后执行。

阶段三:标准化时代(2024-2026)

MCP协议将工具调用标准化,实现"一次开发,处处可用"。

Function Calling的技术细节

工作流程

1. 用户消息 + 工具定义  LLM
2. LLM输出  tool_call(name, args)
3. 应用层执行工具
4. 工具结果  LLM
5. LLM基于结果生成回答

关键参数

tools定义:

{
  "type": "function",
  "function": {
    "name": "string",
    "description": "string",
    "parameters": {
      // JSON Schema格式
    }
  }
}

tool_choice控制:

  • auto:模型自主决定是否调用
  • none:禁止调用
  • required:必须调用
  • {"function": {"name": "xxx"}}:指定调用特定工具

并行调用

现代模型支持在单次响应中输出多个tool_call:

用户: "比较北京和上海的房价"

模型输出:
- tool_call: get_price("北京")
- tool_call: get_price("上海")

应用层并行执行两个调用,将结果一起返回

工具调用的挑战

1. 参数准确性

模型生成的参数可能有误:

  • 类型错误(字符串而非数字)
  • 缺失必需参数
  • 参数值不合法

解决策略:

  • 在JSON Schema中添加详细的description和enum约束
  • 应用层做参数验证和类型转换
  • 错误信息返回给模型让其修正

2. 工具选择

当工具集很大时(几十上百个),模型难以选择正确的工具。

解决策略:

  • 工具检索:根据用户查询embedding检索相关工具
  • 分层路由:先分类到工具组,再在组内选择
  • 工具描述优化:description要清晰且独特

3. 多步调用

复杂任务需要多步工具调用,每步依赖前一步的结果。

解决策略:

  • ReAct循环:Thought→Action→Observation→Thought…
  • 计划-执行分离:先规划全部步骤,再逐步执行
  • 动态重规划:执行中发现计划不可行,重新规划

4. 错误处理

工具调用可能失败:网络超时、参数错误、权限不足。

解决策略:

  • 错误信息返回给模型,让模型决定重试还是换方案
  • 重试策略:指数退避+最大重试次数
  • 降级方案:工具不可用时使用备用工具或告知用户

最佳实践

工具定义原则

好的工具定义:

{
  "name": "search_papers",
  "description": "在arXiv上搜索学术论文。当用户需要查找学术研究、论文或技术报告时使用。输入应为英文关键词。",
  "parameters": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "搜索关键词,英文,例如 'transformer attention'"
      },
      "max_results": {
        "type": "integer",
        "description": "返回结果数量,默认5",
        "default": 5
      }
    },
    "required": ["query"]
  }
}

关键要素:

  • description说明何时使用(不只是做什么)
  • 参数描述给出示例值
  • 合理的默认值
  • required字段明确

错误信息设计

好的错误信息:
"API调用失败:城市名'Beiijing'不存在。请检查拼写,支持的城市包括:北京、上海、广州..."

不好的错误信息:
"Error 500"

好的错误信息帮助模型自我修正。模型能从描述性错误信息中推断下一步行动。

工具粒度

  • 粗粒度:一个工具完成复杂操作(如analyze_code(file)
  • 细粒度:拆分为多个工具(read_file + parse_syntax + check_style

粗粒度更可靠(模型调用步骤少),细粒度更灵活(可以组合)。实践中推荐中等粒度——平衡可靠性和灵活性。

MCP带来的改变

MCP协议标准化了工具调用接口后:

  1. 工具开发一次即用:开发一个MCP Server,所有MCP兼容客户端都能用
  2. 动态工具发现:Agent运行时自动发现可用工具
  3. 工具复用:同一个工具服务被多个Agent框架共享
  4. 统一接口:不再需要为每个框架写适配层

总结

工具调用是Agent从"能说"到"能做"的关键桥梁。从Prompt工程到Function Calling再到MCP标准化,每一代演进都在降低开发门槛、提升可靠性。2026年,随着MCP生态成熟,工具开发将成为独立的生态——就像今天的手机App生态一样丰富。