LLM 工具调用:让模型会调 API 与函数
工具调用(function calling / tool use)让模型以结构化方式请求执行一个函数,由外部代码运行并把结果返回,模型再据此继续。它把模型从”只会说”变成”能动手”——但动手的永远是你的代码,模型只负责”决定调哪个、传什么参”。
是什么
你给模型一组工具描述(名字、用途、参数 schema)。模型在合适时输出调用请求,而不是自然语言;你的代码执行后,把结果作为一条新消息回灌给模型,模型据此生成最终回答。整个过程里,模型不直接触达外部系统,真正的副作用由你控制——这一点对安全至关重要。
需要区分两种形态:一种是”函数调用”专用通道(模型显式返回结构化调用对象,如 OpenAI 的 tool_calls);另一种是让模型在文本里输出特定格式的调用(如 JSON),由你解析。前者更可靠,因为模型被训练成按 schema 输出,解析失败率低。
{
"name": "get_weather",
"arguments": { "city": "深圳" }
}
为什么有用
没有工具调用时,模型只能”建议”你运行某段代码,落地要靠人。有了它,模型能串联检索、计算、数据库、第三方 API,构成可执行的智能体。其思想可追溯到 ReAct(Reason + Act)范式:模型交替进行”推理”与”行动”,从环境反馈中修正下一步。典型应用包括:联网问答(调搜索 API)、数据分析(调 Python 执行器)、工单处理(调 CRM 接口)。当任务需要”实时或私有数据”时,工具调用几乎是唯一路径,因为模型权重本身不含这些动态信息。
怎么用
以 OpenAI 风格接口为例,工具描述是一组 JSON Schema。模型返回 tool_calls,你执行函数后,用 role: "tool" 的消息把结果回灌:
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名,如 深圳"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}]
messages = [
{"role": "user", "content": "深圳今天多少度?"},
{"role": "assistant", "content": None,
"tool_calls": [{"id": "call_1", "type": "function",
"function": {"name": "get_weather",
"arguments": '{"city": "深圳"}'}}]},
{"role": "tool", "tool_call_id": "call_1",
"content": '{"temp_c": 28, "cond": "晴"}'}, # 外部执行结果
]
# 再把 messages 发回模型,得到最终自然语言回答
一个健壮的调用循环需要”分发器”:根据 tool_calls 里的名字查表执行对应函数,把结果收集回消息列表,再请求模型,直到模型不再要求调用工具。当模型一次返回多个 tool_calls 且无依赖时,可以并行执行再一起回灌,降低延迟。Anthropic 的 Claude 用 tool_use / tool_result 块表达同样语义;LangChain 把工具抽象为统一 Tool 接口,DSPy 用 dspy.Tool 包装可调用对象。无论哪套,骨架都是”描述→请求→执行→回灌”。
# 分发器骨架(示意)
def dispatch(tool_calls, registry):
results = []
for call in tool_calls:
fn = registry[call.function.name]
args = json.loads(call.function.arguments)
try:
out = fn(**args)
results.append({"role": "tool", "tool_call_id": call.id, "content": str(out)})
except Exception as e:
results.append({"role": "tool", "tool_call_id": call.id, "content": f"ERROR: {e}"})
return results
注意点
设计可靠工具是第一要务:工具描述要清晰、参数用严格 schema 并显式标注必填项,避免歧义;每个工具只做一件事、名字自解释;执行侧做参数校验与超时控制,失败时返回结构化错误而非抛异常中断链路。用 Pydantic 之类做 schema 校验能挡掉大部分畸形输入,例如把 unit 限定为枚举,模型就无法编出 “kelvin”。
常见陷阱有三类:模型可能编造不存在的参数或取值(尤其 enum 约束缺失时);可能重复调用同一工具陷入循环;也可能忽略返回结果自顾自编答案。应对手段包括:对关键操作加确认或权限闸门、限制单轮调用次数、把工具结果明确标注为”观察(observation)“以提示模型据此作答。
工程清单:
- 参数 schema 显式、可校验,enum 防越界
- 工具只做一件事,名字自解释
- 执行失败返回结构化错误,而非抛异常
- 对写操作加审批/幂等保护
- 限制单轮调用次数,防循环
- 工具结果做观测与日志,便于排查
安全与可观测:工具调用依赖模型”理解何时该调”,对罕见或长尾工具,模型可能漏调;多工具并行时的状态一致性需你自己维护;不要假设模型一定按 schema 出参——执行前务必校验。每次工具调用都带来额外延迟与成本,应在”该调才调”与”能缓存就缓存”之间权衡。生产环境建议加调用追踪(如 LangSmith 这类 tracing 工具),记录每一步的请求、参数与返回,便于定位”模型为什么调错工具”。
小结
工具调用把模型从”只会说”变成”能动手”。关键是把工具接口设计得像 API 一样严谨,并在执行侧做好校验、超时、权限与观测。模型决定”调不调、调哪个、传什么”,而副作用与安全性始终握在开发者手里——这条边界划清楚,工具调用才是资产而非隐患。
参考与延伸阅读
- OpenAI 官方工具调用(Tool use)文档。已核验(https://platform.openai.com/docs/guides/tool-calls)。
- Anthropic 官方 Tool use 文档。已核验(https://docs.anthropic.com/en/docs/build-with-claude/tool-use)。
- LangChain 官方 Tools 文档。已核验(https://python.langchain.com/docs/concepts/tools/)。
- ReAct 论文(Yao et al., 2022)。已核验(https://arxiv.org/abs/2210.03629)。