Claude Tool Use 怎么用?从零到生产的完整教程(2026)

14 阅读1分钟

上周接了个需求,做一个能查天气、查数据库、还能发邮件的 AI 助手。一开始想着用 LangChain 套一层,后来发现 Claude 原生的 Tool Use(也叫 Function Calling)已经很成熟了,根本不需要额外框架。但官方文档写得有点绕,我踩了不少坑才把整条链路跑通。把摸索出来的东西全写下来,让你少走弯路。

Claude Tool Use 是 Anthropic 提供的原生函数调用能力,允许你在对话中定义工具(函数),Claude 会自动判断何时调用哪个工具、提取参数,你执行函数后把结果返回给它,它再生成最终回答。目前 Claude Sonnet 4.6 和 Opus 4.6 都支持,Sonnet 性价比最高,Opus 复杂推理更强。

先说结论

维度说明
支持模型Claude Opus 4.6、Sonnet 4.6、Haiku 4.6
协议格式Anthropic 原生格式 / OpenAI 兼容格式均可
核心流程定义工具 → 发送请求 → Claude 返回工具调用 → 你执行函数 → 结果回传 → Claude 生成回答
并行调用支持,一次可调用多个工具
嵌套调用支持,工具结果可触发新一轮工具调用
踩坑重灾区tool_result 的 content 必须是字符串、工具描述写得烂会导致调用率暴跌

核心流程一图看懂

sequenceDiagram
 participant U as 你的代码
 participant A as Claude API
 participant T as 本地工具函数

 U->>A: 发送消息 + 工具定义
 A->>U: 返回 tool_use(要调用哪个工具、参数是什么)
 U->>T: 执行本地函数
 T->>U: 返回执行结果
 U->>A: 把 tool_result 回传
 A->>U: Claude 生成最终回答

这个流程是整个 Tool Use 的骨架,后面的代码全是围绕这个转的。

环境准备

pip install openai httpx

这里用的是 OpenAI SDK。因为很多聚合平台都兼容 OpenAI 协议,Claude 的 Tool Use 在 OpenAI 兼容模式下也能正常工作,切换模型零成本。

方案一:基础 Tool Use(单工具调用)

先来最简单的场景——让 Claude 调用一个查天气的函数。

import json
from openai import OpenAI

client = OpenAI(
 api_key="your-api-key",
 base_url="https://api.ofox.ai/v1" # 聚合接口,一个 Key 调 Claude/GPT/Gemini
)

# 1. 定义工具
tools = [
 {
 "type": "function",
 "function": {
 "name": "get_weather",
 "description": "获取指定城市的当前天气信息,包括温度、湿度、天气状况",
 "parameters": {
 "type": "object",
 "properties": {
 "city": {
 "type": "string",
 "description": "城市名称,如:北京、上海、深圳"
 },
 "unit": {
 "type": "string",
 "enum": ["celsius", "fahrenheit"],
 "description": "温度单位,默认摄氏度"
 }
 },
 "required": ["city"]
 }
 }
 }
]

# 2. 你的本地函数(实际项目中这里调真实 API)
def get_weather(city: str, unit: str = "celsius") -> dict:
 # 模拟数据,实际替换成真实天气 API
 fake_data = {
 "北京": {"temp": 22, "humidity": 45, "condition": "晴"},
 "上海": {"temp": 26, "humidity": 72, "condition": "多云"},
 "深圳": {"temp": 31, "humidity": 80, "condition": "雷阵雨"},
 }
 data = fake_data.get(city, {"temp": 20, "humidity": 50, "condition": "未知"})
 if unit == "fahrenheit":
 data["temp"] = data["temp"] * 9 / 5 + 32
 data["city"] = city
 data["unit"] = unit
 return data

# 3. 发送请求
messages = [
 {"role": "user", "content": "深圳今天天气怎么样?适合跑步吗?"}
]

response = client.chat.completions.create(
 model="claude-sonnet-4.6",
 messages=messages,
 tools=tools,
 tool_choice="auto" # 让 Claude 自己决定要不要调工具
)

# 4. 处理工具调用
assistant_message = response.choices[0].message

if assistant_message.tool_calls:
 # Claude 决定调用工具
 messages.append(assistant_message) # 先把 assistant 消息加进去
 
 for tool_call in assistant_message.tool_calls:
 func_name = tool_call.function.name
 func_args = json.loads(tool_call.function.arguments)
 
 print(f"Claude 要调用: {func_name}({func_args})")
 
 # 执行本地函数
 if func_name == "get_weather":
 result = get_weather(**func_args)
 
 # 把结果回传(注意:content 必须是字符串!)
 messages.append({
 "role": "tool",
 "tool_call_id": tool_call.id,
 "content": json.dumps(result, ensure_ascii=False)
 })
 
 # 5. 让 Claude 根据工具结果生成最终回答
 final_response = client.chat.completions.create(
 model="claude-sonnet-4.6",
 messages=messages,
 tools=tools
 )
 
 print(final_response.choices[0].message.content)
else:
 # Claude 觉得不需要调工具,直接回答了
 print(assistant_message.content)

实测输出:

Claude 要调用: get_weather({'city': '深圳'})
深圳今天31°C,湿度80%,有雷阵雨。不太建议户外跑步哦,
湿度太高容易中暑,雷阵雨也不安全。建议等晚上凉快点再去,
或者去室内跑步机上练练。

Claude 不是机械地把数据复述一遍,它会结合天气数据给出建议。这就是 Tool Use 比硬编码模板强的地方。

方案二:多工具 + 自动循环调用

真实项目很少只有一个工具。下面这个例子定义了三个工具,并且实现了自动循环——Claude 可能调完一个工具还想调另一个,代码自动处理。

import json
from openai import OpenAI

client = OpenAI(
 api_key="your-api-key",
 base_url="https://api.ofox.ai/v1"
)

# 定义多个工具
tools = [
 {
 "type": "function",
 "function": {
 "name": "search_products",
 "description": "根据关键词搜索商品,返回商品列表(名称、价格、库存)",
 "parameters": {
 "type": "object",
 "properties": {
 "keyword": {"type": "string", "description": "搜索关键词"},
 "max_results": {"type": "integer", "description": "最多返回几条,默认5"}
 },
 "required": ["keyword"]
 }
 }
 },
 {
 "type": "function",
 "function": {
 "name": "get_product_reviews",
 "description": "获取指定商品ID的用户评价摘要",
 "parameters": {
 "type": "object",
 "properties": {
 "product_id": {"type": "string", "description": "商品ID"}
 },
 "required": ["product_id"]
 }
 }
 },
 {
 "type": "function",
 "function": {
 "name": "create_order",
 "description": "创建订单。注意:仅在用户明确表示要购买时才调用",
 "parameters": {
 "type": "object",
 "properties": {
 "product_id": {"type": "string"},
 "quantity": {"type": "integer", "description": "购买数量,默认1"}
 },
 "required": ["product_id"]
 }
 }
 }
]

# 模拟业务函数
def search_products(keyword, max_results=5):
 return [
 {"id": "P001", "name": f"机械键盘-{keyword}款", "price": 359, "stock": 42},
 {"id": "P002", "name": f"薄膜键盘-{keyword}入门", "price": 89, "stock": 156},
 ]

def get_product_reviews(product_id):
 reviews = {
 "P001": {"score": 4.7, "count": 2341, "summary": "手感好,Cherry轴,就是有点吵"},
 "P002": {"score": 4.1, "count": 892, "summary": "便宜够用,适合办公"},
 }
 return reviews.get(product_id, {"score": 0, "summary": "暂无评价"})

def create_order(product_id, quantity=1):
 return {"order_id": "ORD20260329001", "status": "created", "product_id": product_id, "quantity": quantity}

# 函数路由
FUNC_MAP = {
 "search_products": search_products,
 "get_product_reviews": get_product_reviews,
 "create_order": create_order,
}

def chat_with_tools(user_input: str):
 messages = [
 {"role": "system", "content": "你是一个电商购物助手,帮用户搜索商品、查看评价、下单购买。"},
 {"role": "user", "content": user_input}
 ]
 
 max_rounds = 5 # 防止死循环
 
 for round_num in range(max_rounds):
 response = client.chat.completions.create(
 model="claude-sonnet-4.6",
 messages=messages,
 tools=tools,
 tool_choice="auto"
 )
 
 msg = response.choices[0].message
 
 # 没有工具调用,说明 Claude 准备好回答了
 if not msg.tool_calls:
 return msg.content
 
 # 有工具调用,执行并回传
 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" [Round {round_num + 1}] 调用 {func_name}({func_args})")
 
 result = FUNC_MAP[func_name](**func_args)
 
 messages.append({
 "role": "tool",
 "tool_call_id": tool_call.id,
 "content": json.dumps(result, ensure_ascii=False)
 })
 
 return "工具调用轮次超限,请简化问题"

# 测试
answer = chat_with_tools("我想买个键盘打代码用,帮我看看有啥推荐的,评价好的那种")
print(answer)

跑起来后,Claude 会先调 search_products,看到结果后自动调 get_product_reviews 查评价,再综合两次结果给出推荐。两轮工具调用,全自动。

踩坑记录

这部分是我花时间最多的地方。

坑 1:tool_result 的 content 不是字符串直接报错

content 字段必须是字符串,不能直接传 dict。

# ❌ 错误写法
messages.append({
 "role": "tool",
 "tool_call_id": tool_call.id,
 "content": {"temp": 22} # 这会报 422 错误
})

# ✅ 正确写法
messages.append({
 "role": "tool",
 "tool_call_id": tool_call.id,
 "content": json.dumps({"temp": 22}, ensure_ascii=False)
})

坑 2:工具描述写太简单,Claude 不调或者乱调

这个坑比较隐蔽。我一开始给 get_weather 的 description 写的是 "获取天气",三个字。结果 Claude 经常不调这个工具,直接瞎编一个天气回答。

description 要写清楚三件事:这个函数干什么、输入什么、返回什么。参数的 description 也一样,别偷懒。

# ❌ 太简单
"description": "获取天气"

# ✅ 写清楚
"description": "获取指定城市的当前天气信息,返回温度(数字)、湿度(百分比)、天气状况(文字描述)"

坑 3:忘记把 assistant message 加回 messages

这个 bug 特别坑,报错信息还不明显。Claude 返回工具调用后,必须先把那条 assistant 消息原封不动加回 messages,再加 tool result,顺序不能乱。

# ❌ 漏掉了 assistant message
messages.append({
 "role": "tool",
 "tool_call_id": tool_call.id,
 "content": result_str
})

# ✅ 先加 assistant,再加 tool result
messages.append(assistant_message) # 这一步不能少!
messages.append({
 "role": "tool",
 "tool_call_id": tool_call.id,
 "content": result_str
})

坑 4:并行工具调用时 tool_call_id 必须一一对应

Claude 有时候一次返回多个 tool_calls(比如同时查两个城市的天气)。每个 tool_result 的 tool_call_id 必须和对应的 tool_call 匹配,搞混了就 400 错误。用上面 for tool_call in msg.tool_calls 的写法就不会出问题。

tool_choice 参数详解

这个参数控制 Claude 调不调工具、怎么调:

行为适用场景
"auto"Claude 自己决定(推荐)大多数场景
"none"禁止调工具纯聊天轮次
"required"强制必须调工具你确定这轮一定要调工具
{"type": "function", "function": {"name": "xxx"}}强制调指定工具流程编排、测试

90% 的时候用 "auto" 就够了。只有在做严格的多步骤流程编排时才需要强制指定。

小结

Claude Tool Use 的核心就是那个请求-调用-回传的循环,搞懂这个剩下的都是细节。重点记四条:description 要写详细、content 必须是字符串、assistant message 不能漏、循环要设上限防死循环

我现在用的方案是通过 ofox.ai 的聚合接口调 Claude,它是一个 AI 模型聚合平台,一个 API Key 可以调 Claude Opus 4.6、GPT-5、Gemini 3 等 50+ 模型,低延迟直连,支持支付宝付款。换模型不用改代码,Claude 的 Tool Use 和 GPT-5 的 Function Calling 用同一套代码就能跑,方便对比效果。

代码我放 Gist 上了,直接 copy 就能跑。有问题评论区见。