工具调用入门:让模型从回答者变成执行者
工具调用的价值不在于“模型能调函数”这件事本身,而在于它让系统第一次拥有了与外部世界安全交互的接口。
01.为什么工具调用是 Agent 系统的分水岭
没有工具调用时,模型的能力主要停留在“理解和生成”:
- •能解释问题
- •能给建议
- •能组织文本
但一旦要处理真实业务任务,光会说是不够的。系统往往还需要:
- •查实时数据
- •调后端接口
- •读写文件
- •执行脚本
- •把结果回写到外部系统
工具调用的意义就在这里。它把模型从“只能给答案的文本引擎”,变成“能通过接口驱动外部动作的决策器”。
不过要注意,模型本身并不会真的执行函数。它做的是另外一件事:
根据用户意图和工具描述,生成一份结构化的调用建议。
真正执行工具的是你的应用层。
02.Tool Calling 的基本链路
一条完整链路通常有 5 步:
- •应用把可用工具列表和用户请求一起发给模型
- •模型判断是否需要工具,以及该调用哪个工具
- •模型返回工具名和参数
- •应用执行工具,拿到真实结果
- •应用把工具结果再交给模型,让它组织最终回答
这一步看起来简单,但每一层都有自己的职责:
- •模型负责“选择和组织”
- •应用负责“校验和执行”
- •工具负责“对外部系统产生可靠副作用”
职责一旦混乱,系统就会变得非常脆弱。
03.先把工具当成 API 契约,而不是模型插件
入门时最容易犯的错误,是把工具写成“给模型看的说明文”。更稳妥的做法是:先把工具当成正式 API 设计,再考虑怎么暴露给模型。
一个靠谱工具至少应该回答清楚:
- •它叫什么
- •它做什么
- •它接收什么参数
- •参数是否合法
- •失败时返回什么
例如,一个查询天气的工具可以定义成这样:
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
- •捕获工具异常
- •记录调用日志
- •决定是否允许副作用执行
下面是一个简化版执行器:
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_docs、query_orders、send_email - •
general_business_tool、万能查询、统一执行器
显然第一组更容易选对,因为职责边界清楚。第二组虽然看上去灵活,实际上会让模型很难判断什么时候该用哪个接口。
设计多工具时,我更推荐下面几个原则:
- •一个工具只负责一个明确动作
- •参数命名贴近业务语义
- •返回结构尽量稳定
- •工具描述写清楚适用场景和限制
这样做的收益不只是模型更好用,后续排障、观察和权限治理也更容易。
06.一个完整的最小调用流程
下面这个例子演示了从“请求模型”到“执行工具再回填”的完整闭环:
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.总结
工具调用真正改变的,不是模型“会不会回答”,而是系统第一次拥有了“安全做事”的能力。
如果你先把工具看成受约束的业务接口,再让模型参与选择和调度,整个系统会稳很多。反过来,如果一开始就把模型放在执行链路最中央,却没有校验、日志和权限边界,问题几乎一定会在线上暴露出来。