工具调用
工具调用(function calling)让语言模型通过应用使用外部代码。应用发送会话和工具 schema,模型可以返回结构化工具请求,指定名称与参数。应用执行函数、把结果加入消息,再调用模型取得后续回答。本课使用由客户端执行的工具。
可以把模型看作通过信件协作的顾问。应用寄去问题、所需历史和服务目录,模型回一份答案或工单。收到 get_weather 工单后,仍需应用去执行,再把结果寄回。产生调用描述与执行函数是两步。
工具调用如何工作
模型、应用、工具三方中,应用连接两端:通过 API 联系模型,通过执行器调用函数,例如 def get_weather(city): ...。本例没有模型直接访问本地函数的通道。提供者托管的服务端工具属于另一种安排。
循环如下,代码为教学伪代码:
def ask(question: str) -> str:
messages = [{"role": "user", "content": question}]
while True:
resp = claude.create(messages=messages, tools=TOOL_SCHEMAS,
tool_choice=CHOICE)
messages.append(assistant(resp.content))
if resp.stop_reason != "tool_use":
return text_of(resp)
results = []
for call in tool_uses_of(resp):
try:
results.append(TOOLS[call.name](**call.input))
except Exception as e:
results.append(f"error: {e}")
messages.append(tool_results(results))
TOOL_SCHEMAS 是说明,不是实现。它包含名称、自然语言描述、参数约束。模型结合描述、schema 和其他上下文选择工具,因此描述应说明用途与限制。
本例的 stop_reason: "end_turn" 表示返回答案,"tool_use" 表示该响应包含待执行请求。这个客户端工具流程在此结束模型响应,后续由应用执行。真实实现还需区分 max_tokens 等其他停止原因,不能把所有非 tool_use 都当完整成功。
TOOLS[call.name] 按名称查找实现。新增工具可以扩展 schema 与处理器表,不必给通用循环增加同名分支,但仍需实现、验证和权限规则,不只是写一行就得到完整能力。
工具结果放在 tool_result 块中,经 user-role 消息发送。它是外界提供的输入,不是模型已经执行的证明。下一请求携带用户问题、assistant 的 tool_use 和匹配结果。本课的 Messages API 由客户端管理历史;其他 API 或宿主可以代管会话,不是 HTTP 本身禁止保存状态。
ReAct 循环 在这个机制上组织 Thought → Action → Observation;本课展开其中的工具分发。
工具调用逐步演示
默认问题为 What's the weather in Tokyo right now?,提供 get_weather(city)。天气来自固定表,不是实时天气。
- 准备。
messages有一条天气问题,TOOL_SCHEMAS有 get_weather 定义。 - 请求 1。 组装一条消息和工具定义。
- 往返 1。 录制模型选择用工具获取天气;训练知识不足以提供当前天气。
- 返回工单。 内容摘录为
{"name": "get_weather", "input": {"city": "Tokyo"}},停止原因tool_use。这里只省略了完整块的 type、id 等字段;尚无函数执行。 - 保存请求。 assistant 的 tool_use 加入历史。
- 检查停止原因。
if resp.stop_reason != "tool_use"为假,不提前返回,继续到工具分发。 - 解析。 应用提取 name、input,得到
get_weather(city="Tokyo")。应在执行前验证和授权。 - 执行。 应用进程调用函数,这一步不需要模型继续运行。
- 返回工具结果。 固定表返回
18°C · rain。 - 加入结果。 user-role 的 tool_result 对应请求 ID。历史现在是问题、工单、结果。
- 请求 2。 三条消息加工具定义一起发送。
- 往返 2。 同一端点、同一结构,载荷比此前大。
- 返回答案。 录制回答称东京现在 18°C、有雨,并建议带伞,
stop_reason: "end_turn"。温度来自函数结果,不是模型回忆。 - 保存并结束。 条件为真,执行
return text_of(resp)。 - 计数。 本次一项工具工作需要 2 次模型 API 调用、1 次工具执行。
为什么本例需要两次往返
这个客户端工具协议在 tool_use 响应后把控制权交回应用。应用完成工具后,再提交结果,让模型继续。因此本例有两次模型调用加一次工具耗时,按顺序发生;不是所有 HTTP 协议或托管工具都受同一种流程限制。
每次请求包含所保留的历史与 schema,增加输入量,提示缓存可以降低重复部分的成本。错误恢复还会增加往返:服务只认 Springfield, Missouri,第一次却用 Springfield,本例因而从 2 次模型调用增至 3 次。
工具错误以 is_error: true 返回,也能成为模型的输入。录制错误场景里,错误文本列出合法名称,下一次请求原样采用 Springfield, Missouri。这说明本次恢复如何发生,不保证模型总能修正错误。
边界情况
- 不需要工具。 问
What does HTTP stand for?并附天气工具,auto 的录制响应直接 end_turn:1 次 API、0 次工具。提供工具不等于强制使用。 - 工具抛异常。 应捕获并返回经过处理的 tool_result,保留
is_error: true与对应 ID。避免把敏感堆栈原样暴露,也不要把失败吞掉。本例错误文本帮助模型改用正确键。 - UI 的
tool_choice: "required"。 这是演示的统一标签,Anthropic 原生“必须使用任一工具”写作{"type":"any"},不是这个字符串。强制选择在无需天气的 HTTP 问题上也产生工具请求,演示了一次无用执行。若每轮都强制,每轮都只能再调用工具,模型无法直接回答;本演示第一轮后改回 auto。依赖强制前先确认模型:Claude 最新模型(Opus 5.5、Sonnet 5.5、Fable 5.1)对{"type":"any"}与{"type":"tool"}返回 400 错误,Opus 5、Fable 5 和 OpenAI 的"required"仍会强制。在这些新模型上,用 auto 加调用指令,需要合规输入时加strict: true。本课录制的是 claude-opus-5-5,所以强制轮是模拟的:录制脚本在提示词里告诉模型不允许纯文本回答。这比真实强制弱,三份录制样本中有一份仍直接用文本回答。按业务目的选择强制任意工具或指定工具,而非总是打开。 - 批量工具请求。 东京与旧金山问题的录制响应一次返回两个 tool_use。应用执行两次,再把两个按 id 对应的 tool_result 放进同一条 user 消息,只需一次额外模型往返。批量请求不保证执行器并行;示例 for 循环按顺序调用。
- 不存在的名称或无效参数。 运行时必须验证,拒绝无效调用并报告错误,不应直接执行模型给出的任意内容。
- 无限循环。
while True便于解释,生产应限制轮数、时间和预算,并明确报告未完成。
常见错误
- 认为 API 已执行本地函数。 模型只提出请求,应用还要验证、授权、分发。
- 丢掉请求历史或 ID。 后续结果必须对应 assistant 的 tool_use,顺序与 ID 要满足 API 契约。只发无对应请求的结果会出错。
- 以为上轮由模型自行记住。 本客户端管理的流程应传回所需历史;只提供孤立结果,不提供问题,就缺少任务上下文。
- 结果角色错误。 本课 Anthropic Messages 用 user 消息承载 tool_result。不同 API 用不同结构,不应混用字段。
- 描述没有参数约束。 “获取天气”没有解释城市必须匹配服务列表。写清精确键,如
Springfield, Missouri,或在 schema 使用合法值 enum,可以避免本例模糊输入。
模型的简化
模型选择、参数和最终回答来自仓库记录的 claude-opus-5-5 运行,每个场景选择一份原文记录。required 格用提示词约束模拟强制,因为该模型拒绝强制 tool_choice。播放不调用实时模型。get_weather 从三城市表读取,但确实在本地执行,画面结果包括错误都由函数产生。
代码是伪代码,辅助函数必须保留调用 ID 和错误标记;完整实现还需处理其他 stop_reason、校验、授权和预算。CHOICE 占位写法省略了演示第一轮后 required → auto 的切换。多个工具类型、流式处理和提示缓存未展开。本课展示客户端工具的响应停止、结构化请求、执行、结果回传与历史重发,不能代表所有提供者接口的唯一形式。