为什么 AI 需要工具调用能力

大语言模型虽然强大,但存在天然的局限性:它们无法获取实时信息、不能执行代码、无法操作外部系统。Function Calling(函数调用)正是为解决这些问题而生——它让 AI 能够像人类使用工具一样,自主选择并调用外部 API 和函数,从而突破模型的边界。

想象一个场景:用户问「今天北京天气怎么样?」。纯文本模型无法回答,因为它的训练数据截止于某个日期。但具备了 Function Calling 能力的 AI 会这样做:识别出需要调用天气 API → 构造正确的 API 请求 → 解析返回的 JSON 数据 → 用自然语言回复用户。这就是工具调用循环的魅力。

Function Calling 的工作原理

Function Calling 不是 AI 真的去调用函数,而是一个精心设计的协议:

  1. 定义工具:开发者向模型描述可用的函数(名称、参数、用途)
  2. 模型决策:AI 分析用户输入,决定是否需要调用工具、调用哪个工具、传什么参数
  3. 开发者执行:开发者代码执行实际的函数调用
  4. 结果反馈:将执行结果返回给模型,模型据此生成最终回复

这个协议的精妙之处在于:AI 负责「决定做什么」,但实际执行权始终在开发者手中,保证了安全性和可控性。

技能链拆解:工具编排链

我们以「工具编排链」技能链为例,拆解每个节点在工具调用流程中的作用:

节点 1:需求理解(sp-188)——AI 首先需要准确理解用户的意图。用户说「帮我查一下最近的航班」,AI 需要识别出这是一个需要调用外部工具的任务,而非简单的文本问答。

节点 2:工具选择(sp-179)——从可用工具集中选择最合适的工具。如果用户要查航班,应该调用航班查询 API 而非天气 API。这个节点需要 AI 具备「工具匹配」的能力。

节点 3:工具调用(sp-181)——构造正确的参数并执行调用。AI 需要将用户模糊的需求(如「最近的航班」)转化为精确的 API 参数(如 date=2026-07-13, from=北京, to=上海)。

节点 4:结果解析(sp-187)——将 API 返回的结构化数据转化为用户友好的自然语言回复。一个航班查询可能返回几十条 JSON 数据,但用户只需要最相关的 2-3 个选项。

实战:构建一个多工具 Agent

以下代码展示了一个具备多个工具的 Agent Loop 实现:

import json
from openai import OpenAI
import requests

client = OpenAI(
    api_key="your-deepseek-api-key",
    base_url="https://api.deepseek.com"
)

# 定义可用工具
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的实时天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名称,如北京"}
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "search_web",
            "description": "在互联网上搜索最新信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "搜索关键词"},
                    "num_results": {"type": "integer", "description": "返回结果数量"}
                },
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "执行数学计算",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {"type": "string", "description": "数学表达式"}
                },
                "required": ["expression"]
            }
        }
    }
]

# 工具实现函数
def execute_tool(tool_name, arguments):
    if tool_name == "get_weather":
        city = arguments.get("city")
        # 实际项目中调用天气 API
        return f"{city}今天晴,气温 25°C,湿度 60%"
    elif tool_name == "search_web":
        query = arguments.get("query")
        return f"关于'{query}'的搜索结果:找到 3 条相关信息..."
    elif tool_name == "calculate":
        expression = arguments.get("expression")
        try:
            result = eval(expression)
            return f"计算结果:{expression} = {result}"
        except:
            return "计算表达式无效"
    return "未知工具"

class ToolCallingAgent:
    def __init__(self, max_iterations=5):
        self.max_iterations = max_iterations
        self.messages = []

    def run(self, user_input):
        self.messages = [{
            "role": "system",
            "content": "你是一个智能助手,可以使用工具来完成任务。请一步步思考并合理使用工具。"
        }, {
            "role": "user",
            "content": user_input
        }]

        for i in range(self.max_iterations):
            print(f"=== 第 {i+1} 轮 ===")

            response = client.chat.completions.create(
                model="deepseek-chat",
                messages=self.messages,
                tools=tools
            )

            msg = response.choices[0].message

            # 如果模型决定直接回复
            if msg.content and not msg.tool_calls:
                return msg.content

            # 如果模型决定调用工具
            if msg.tool_calls:
                self.messages.append(msg)

                for tool_call in msg.tool_calls:
                    func_name = tool_call.function.name
                    func_args = json.loads(tool_call.function.arguments)
                    print(f"调用工具:{func_name}({func_args})")

                    result = execute_tool(func_name, func_args)

                    self.messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "content": result
                    })

        return "达到最大循环次数"

# 使用示例
agent = ToolCallingAgent(max_iterations=5)
result = agent.run("北京今天天气怎么样?另外帮我算一下 156 * 23 + 89")
print(f"最终回复:{result}")

工具调用的最佳实践

工具描述要精准:模型的工具选择完全依赖于你的工具描述。描述越清晰,选择越准确。建议每个工具描述包含:功能说明、适用场景、参数详解、返回值格式。

参数校验不可少:模型可能生成不合理的参数。在 execute_tool 中始终进行参数校验,防止因参数错误导致工具调用失败。

错误要反馈给模型:当工具调用失败时,不要简单返回错误信息,而是将结构化的错误描述返回给模型,让它有机会调整策略。例如:「调用天气 API 失败:城市名'北惊'不存在,建议使用'北京'」。

控制工具数量:可用工具过多会增加模型的选择难度。建议将工具数量控制在 10 个以内,或将工具分组,根据上下文动态提供。

工具调用与 Agent Loop 的深度结合

工具调用不是一次性操作,而是融入 Agent Loop 的持续过程。一轮工具调用的结果可能触发下一轮的工具调用——比如先搜索「AI 领域最新论文」,然后根据搜索结果中的论文标题,再调用 PDF 解析工具获取论文详情。这种链式工具调用正是 Agent Loop 强大能力的体现。

想亲手编排这个技能链?

在技能链中打开 →