上周接了个需求,做一个能查天气、查数据库、还能发邮件的 AI 助手。一开始想着用 LangChain 套一层,后来发现 Claude 原生的 Tool Use(也叫 Function Calling)已经很成熟了,根本不需要额外框架。但官方文档写得有点绕,我踩了不少坑才把整条链路跑通。把摸索出来的东西全写下来,让你少走弯路。
Claude Tool Use 是 Anthropic 提供的原生函数调用能力,允许你在对话中定义工具(函数),Claude 会自动判断何时调用哪个工具、提取参数,你执行函数后把结果返回给它,它再生成最终回答。目前 Claude Sonnet 4.6 和 Opus 4.6 都支持,Sonnet 性价比最高,Opus 复杂推理更强。
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 兼容模式下也能正常工作,切换模型零成本。
先来最简单的场景——让 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 = [ , "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"] } } }, }, "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": "ORD", "status": "created", "product_id": product_id, "quantity": quantity} # 函数路由 FUNC_MAP = 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 查评价,再综合两次结果给出推荐。两轮工具调用,全自动。
这部分是我花时间最多的地方。
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) })
这个坑比较隐蔽。我一开始给 get_weather 的 description 写的是 "获取天气",三个字。结果 Claude 经常不调这个工具,直接瞎编一个天气回答。
description 要写清楚三件事:这个函数干什么、输入什么、返回什么。参数的 description 也一样,别偷懒。
# ❌ 太简单 "description": "获取天气" # ✅ 写清楚 "description": "获取指定城市的当前天气信息,返回温度(数字)、湿度(百分比)、天气状况(文字描述)"
这个 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 })
Claude 有时候一次返回多个 tool_calls(比如同时查两个城市的天气)。每个 tool_result 的 tool_call_id 必须和对应的 tool_call 匹配,搞混了就 400 错误。用上面 for tool_call in msg.tool_calls 的写法就不会出问题。
这个参数控制 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 就能跑。有问题评论区见。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/277106.html