AI AgentTechnical Deep Dive

工具调用入门:让模型从回答者变成执行者

发布时间2026/02/07
分类AI Agent
预计阅读10 分钟
作者吴长龙
*

工具调用的价值不在于“模型能调函数”这件事本身,而在于它让系统第一次拥有了与外部世界安全交互的接口。

01.为什么工具调用是 Agent 系统的分水岭

没有工具调用时,模型的能力主要停留在“理解和生成”:

  • 能解释问题
  • 能给建议
  • 能组织文本

但一旦要处理真实业务任务,光会说是不够的。系统往往还需要:

  • 查实时数据
  • 调后端接口
  • 读写文件
  • 执行脚本
  • 把结果回写到外部系统

工具调用的意义就在这里。它把模型从“只能给答案的文本引擎”,变成“能通过接口驱动外部动作的决策器”。

不过要注意,模型本身并不会真的执行函数。它做的是另外一件事:

根据用户意图和工具描述,生成一份结构化的调用建议。

真正执行工具的是你的应用层。

02.Tool Calling 的基本链路

一条完整链路通常有 5 步:

  • 应用把可用工具列表和用户请求一起发给模型
  • 模型判断是否需要工具,以及该调用哪个工具
  • 模型返回工具名和参数
  • 应用执行工具,拿到真实结果
  • 应用把工具结果再交给模型,让它组织最终回答

这一步看起来简单,但每一层都有自己的职责:

  • 模型负责“选择和组织”
  • 应用负责“校验和执行”
  • 工具负责“对外部系统产生可靠副作用”

职责一旦混乱,系统就会变得非常脆弱。

03.先把工具当成 API 契约,而不是模型插件

入门时最容易犯的错误,是把工具写成“给模型看的说明文”。更稳妥的做法是:先把工具当成正式 API 设计,再考虑怎么暴露给模型。

一个靠谱工具至少应该回答清楚:

  • 它叫什么
  • 它做什么
  • 它接收什么参数
  • 参数是否合法
  • 失败时返回什么

例如,一个查询天气的工具可以定义成这样:

python snippetpython
from pydantic import BaseModel, Field


class WeatherArgs(BaseModel):
    city: str = Field(description="城市名称,例如北京、上海")


def get_weather(city: str) -> dict:
    weather_db = {
        "北京": {"condition": "晴", "temperature": "25C"},
        "上海": {"condition": "多云", "temperature": "23C"},
    }
    return weather_db.get(city, {"condition": "未知", "temperature": "未知"})

这个例子很小,但有两个工程好处:

  • 参数边界清楚,便于校验
  • 返回结构稳定,后续能被模型和日志系统同时消费

04.执行器才是工具调用真正的落地点

模型返回工具调用建议后,不应该直接“信任执行”。中间必须有一个执行器层,负责:

  • 校验工具名是否合法
  • 校验参数是否符合 schema
  • 捕获工具异常
  • 记录调用日志
  • 决定是否允许副作用执行

下面是一个简化版执行器:

python snippetpython
import json
from typing import Any


TOOL_REGISTRY = {
    "get_weather": get_weather,
}


def execute_tool_call(tool_name: str, raw_arguments: str) -> dict[str, Any]:
    if tool_name not in TOOL_REGISTRY:
        return {"ok": False, "error": f"unknown tool: {tool_name}"}

    try:
        parsed_args = json.loads(raw_arguments)
    except json.JSONDecodeError:
        return {"ok": False, "error": "invalid json arguments"}

    try:
        validated = WeatherArgs.model_validate(parsed_args)
    except Exception as exc:
        return {"ok": False, "error": f"invalid arguments: {exc}"}

    try:
        result = TOOL_REGISTRY[tool_name](**validated.model_dump())
        return {"ok": True, "result": result}
    except Exception as exc:
        return {"ok": False, "error": f"tool execution failed: {exc}"}

真正上线时,这层通常还会接上权限判断、审计日志、超时控制和重试策略。也就是说,Tool Calling 从来不是“模型直接控制系统”,而是“模型给出结构化意图,系统有边界地执行”。

05.多工具场景下,决定稳定性的往往不是模型,而是工具设计

当工具数量变多时,很多问题并不是模型太弱,而是工具集合本身设计得不适合选择。

比如下面这两组工具:

  • search_docsquery_orderssend_email
  • general_business_tool万能查询统一执行器

显然第一组更容易选对,因为职责边界清楚。第二组虽然看上去灵活,实际上会让模型很难判断什么时候该用哪个接口。

设计多工具时,我更推荐下面几个原则:

  • 一个工具只负责一个明确动作
  • 参数命名贴近业务语义
  • 返回结构尽量稳定
  • 工具描述写清楚适用场景和限制

这样做的收益不只是模型更好用,后续排障、观察和权限治理也更容易。

06.一个完整的最小调用流程

下面这个例子演示了从“请求模型”到“执行工具再回填”的完整闭环:

python snippetpython
from openai import OpenAI

client = OpenAI()

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询城市天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名称"}
                },
                "required": ["city"],
            },
        },
    }
]


messages = [{"role": "user", "content": "明天北京天气怎么样?"}]

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    tools=tools,
)

message = response.choices[0].message

if message.tool_calls:
    tool_call = message.tool_calls[0]
    execution_result = execute_tool_call(
        tool_name=tool_call.function.name,
        raw_arguments=tool_call.function.arguments,
    )

    messages.append(message.model_dump())
    messages.append(
        {
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(execution_result, ensure_ascii=False),
        }
    )

    final_response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
    )
    print(final_response.choices[0].message.content)

这条链路有一个关键认识:工具调用不是单次动作,而是一个小闭环。只有“调用建议、执行结果、最终回答”三步都接住,系统才真正可用。

07.工具调用最常见的四个坑

1. 工具描述写得像广告文案

如果工具描述里充满模糊词,比如“智能”“全面”“万能”,模型反而更难判断。描述应该像接口文档,而不是产品介绍。

2. 参数过多或过于自由

参数越多、自由度越高,模型越容易填错。能通过枚举、默认值或拆工具减少自由度,就尽量减少。

3. 缺少失败路径

很多演示只写成功路径,但真实系统里最重要的恰恰是失败处理:

  • 参数校验失败怎么办
  • 外部 API 超时怎么办
  • 工具无权限怎么办
  • 执行后结果为空怎么办

这些分支如果不设计,线上体验会非常不稳定。

4. 把高风险动作暴露给模型直接执行

像删除数据、转账、发正式通知、改生产配置这类操作,不应该因为“模型会调工具”就直接开放。必须加确认、审批或人工介入。

08.工具调用做对了,会自然过渡到 Agent

很多团队做 Agent 的第一步,其实不是上复杂框架,而是先把工具系统设计好。原因很简单:

  • 没有稳定工具,Agent 无事可做
  • 没有执行边界,Agent 不可控
  • 没有日志和回放,Agent 无法排障

所以从演进路径上看,通常是:

  • 先把单工具调用跑通
  • 再支持多工具选择
  • 再加入状态和多步循环
  • 最后才进入更完整的 Agent 编排

09.总结

工具调用真正改变的,不是模型“会不会回答”,而是系统第一次拥有了“安全做事”的能力。

如果你先把工具看成受约束的业务接口,再让模型参与选择和调度,整个系统会稳很多。反过来,如果一开始就把模型放在执行链路最中央,却没有校验、日志和权限边界,问题几乎一定会在线上暴露出来。