news 2026/4/22 14:28:40

Claude Tool Use 完全教程:从零实现 Function Calling,附完整代码(2026)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Claude Tool Use 完全教程:从零实现 Function Calling,附完整代码(2026)

上周接了个私活,甲方要做一个能查天气、查航班、还能下单的智能客服。一开始寻思用 Claude 纯文本对接就行,结果发现 LLM 不能直接调外部 API——它只会"说话",不会"动手"。折腾了两天 Tool Use(也就是 Anthropic 版的 Function Calling),总算把整条链路跑通了,坑踩了不少,写篇完整教程分享一下。

Claude Tool Use 是 Anthropic 提供的函数调用能力,允许你在对话中定义工具(函数),模型判断何时调用、传什么参数,你的代码执行完工具后把结果喂回去,模型再生成最终回答。目前 Claude Opus 4.6 和 Sonnet 4.6 都支持,Sonnet 4.6 性价比最高,我个人项目基本都用它。

先说结论

维度说明
核心原理你定义工具 schema → 模型返回tool_use决策 → 你执行函数 → 结果喂回模型
支持模型Claude Opus 4.6、Sonnet 4.6、Haiku 4.6
协议格式Anthropic 原生 Messages API(也兼容 OpenAI 格式的 Function Calling)
难度比 OpenAI 的 function calling 稍复杂,但更灵活
适用场景智能客服、数据查询、自动化工作流、Agent 编排

Tool Use 的工作流程

先看一张图,理解整个调用链路:

外部工具/APIClaude API你的代码用户外部工具/APIClaude API你的代码用户"北京明天天气怎么样?"发送消息 + 工具定义返回 tool_use(调用 get_weather)执行 get_weather("北京")返回天气数据发送 tool_result"北京明天晴,最高32℃..."展示最终回答

关键点:模型本身不执行任何函数,它只是告诉你"我觉得现在该调这个工具,参数是这些"。真正执行的是你的代码。这个设计很安全,但也意味着你得自己写执行逻辑和循环。

环境准备

# 安装 Anthropic 官方 SDKpipinstallanthropic>=0.39.0# 或者你用 OpenAI 兼容格式也行(后面会讲)pipinstallopenai>=1.50.0

API Key 的话,可以去 Anthropic 官方申请,也可以用聚合平台的 Key。我现在用的是 ofox.ai,一个 API Key 能调 Claude、GPT-5、Gemini 3 等 50+ 模型,不用每家单独注册,改个 base_url 就行,省事。

方案一:Anthropic 原生 SDK 实现 Tool Use

这是最标准的写法,直接用 Anthropic 的 Messages API。

第一步:定义工具

importanthropicimportjson# 定义工具列表(JSON Schema 格式)tools=[{"name":"get_weather","description":"获取指定城市的天气信息,包括温度、湿度、天气状况","input_schema":{"type":"object","properties":{"city":{"type":"string","description":"城市名称,如:北京、上海、深圳"},"date":{"type":"string","description":"日期,格式 YYYY-MM-DD,不传则默认今天"}},"required":["city"]}},{"name":"search_flights","description":"查询两个城市之间的航班信息","input_schema":{"type":"object","properties":{"departure":{"type":"string","description":"出发城市"},"arrival":{"type":"string","description":"到达城市"},"date":{"type":"string","description":"出发日期,格式 YYYY-MM-DD"}},"required":["departure","arrival","date"]}}]

input_schema就是标准的 JSON Schema,模型会根据description判断什么时候该调哪个工具。description 写得好不好直接影响调用准确率,这是我踩的第一个坑——一开始写得太简略,模型经常选错工具。

第二步:模拟工具执行函数

defexecute_tool(tool_name:str,tool_input:dict)->str:"""模拟执行工具,实际项目中这里对接真实 API"""iftool_name=="get_weather":# 实际项目中调用天气 APIcity=tool_input.get("city","未知")returnjson.dumps({"city":city,"temperature":"28℃","humidity":"65%","condition":"多云转晴","wind":"东南风3级"},ensure_ascii=False)eliftool_name=="search_flights":departure=tool_input.get("departure")arrival=tool_input.get("arrival")date=tool_input.get("date")returnjson.dumps({"flights":[{"flight_no":"CA1234","time":"08:30-11:00","price":980},{"flight_no":"MU5678","time":"14:15-16:45","price":1120},],"date":date,"route":f"{departure}{arrival}"},ensure_ascii=False)returnjson.dumps({"error":f"未知工具:{tool_name}"})

第三步:完整的 Tool Use 循环

这是核心代码,处理模型可能多轮调用工具的情况:

defchat_with_tools(user_message:str):client=anthropic.Anthropic(api_key="your-api-key")messages=[{"role":"user","content":user_message}]# 循环处理,因为模型可能连续调用多个工具whileTrue:response=client.messages.create(model="claude-sonnet-4-20250514",max_tokens=4096,tools=tools,messages=messages)print(f"[Stop Reason]:{response.stop_reason}")# 如果模型认为不需要调工具,直接返回文本ifresponse.stop_reason=="end_turn":# 提取文本内容forblockinresponse.content:ifhasattr(block,"text"):print(f"[最终回答]:{block.text}")return# 如果模型要调工具ifresponse.stop_reason=="tool_use":# 把 assistant 的响应加到消息列表messages.append({"role":"assistant","content":response.content})# 收集所有 tool_resulttool_results=[]forblockinresponse.content:ifblock.type=="tool_use":print(f"[调用工具]:{block.name}, 参数:{block.input}")# 执行工具result=execute_tool(block.name,block.input)tool_results.append({"type":"tool_result","tool_use_id":block.id,# 必须对应!"content":result})# 把工具结果喂回去messages.append({"role":"user","content":tool_results})# 测试chat_with_tools("帮我查一下北京明天的天气,顺便看看北京到上海后天的航班")

运行结果大概长这样:

[Stop Reason]: tool_use [调用工具]: get_weather, 参数: {'city': '北京', 'date': '2026-07-16'} [调用工具]: search_flights, 参数: {'departure': '北京', 'arrival': '上海', 'date': '2026-07-17'} [Stop Reason]: end_turn [最终回答]: 帮你查到了: 1. 北京明天天气:多云转晴,气温28℃,湿度65%,东南风3级,适合出行。 2. 北京到上海后天的航班: - CA1234,08:30-11:00,980元 - MU5678,14:15-16:45,1120元

注意模型一次返回了两个tool_useblock,说明它能并行判断需要调用多个工具。

方案二:用 OpenAI 兼容格式调用

如果你的项目已经在用 OpenAI SDK,不想换,也能调 Claude 的 Tool Use。很多聚合平台都做了协议转换,比如 ofox.ai 就兼容 OpenAI 的 Function Calling 格式来调 Claude。

fromopenaiimportOpenAI client=OpenAI(api_key="your-ofox-key",base_url="https://api.ofox.ai/v1"# 聚合接口,一个 Key 调所有模型)# OpenAI 格式的 tools 定义openai_tools=[{"type":"function","function":{"name":"get_weather","description":"获取指定城市的天气信息","parameters":{"type":"object","properties":{"city":{"type":"string","description":"城市名称"},"date":{"type":"string","description":"日期 YYYY-MM-DD"}},"required":["city"]}}}]defchat_openai_format(user_message:str):messages=[{"role":"user","content":user_message}]whileTrue:response=client.chat.completions.create(model="claude-sonnet-4-20250514",# 通过聚合接口调 Claudemessages=messages,tools=openai_tools,tool_choice="auto")msg=response.choices[0].message# 没有工具调用,直接输出ifnotmsg.tool_calls:print(f"[最终回答]:{msg.content}")return# 处理工具调用messages.append(msg)# 先把 assistant 消息加上fortool_callinmsg.tool_calls:func_name=tool_call.function.name func_args=json.loads(tool_call.function.arguments)print(f"[调用工具]:{func_name}, 参数:{func_args}")result=execute_tool(func_name,func_args)messages.append({"role":"tool","tool_call_id":tool_call.id,"content":result})chat_openai_format("深圳今天天气怎么样?")

两种方案的核心差异:

对比项Anthropic 原生 SDKOpenAI 兼容格式
SDKanthropicopenai
工具定义字段input_schemaparameters(包在function里)
停止原因stop_reason == "tool_use"msg.tool_calls是否为空
结果回传角色role: "user"+type: "tool_result"role: "tool"
切换模型只能用 Claude改 model 参数就能换 GPT-5/Gemini 3

我个人更推荐方案二,代码通用性更好。万一哪天要换模型,改一行model=就完事。

踩坑记录

坑 1:tool_use_id 没对上

最常见的报错。模型返回的每个tool_useblock 都有唯一的id,回传tool_resulttool_use_id必须一一对应。漏了或者对错了直接 400。

# ❌ 错误:硬编码 id{"type":"tool_result","tool_use_id":"some-random-id","content":"..."}# ✅ 正确:从 block.id 拿{"type":"tool_result","tool_use_id":block.id,"content":result}

坑 2:工具 description 写得太烂

一开始把get_weather的 description 写成"查天气",两个字。结果模型经常在不该查天气的时候也调这个工具。后来改成详细描述——“获取指定城市的实时天气信息,包括温度、湿度、天气状况、风力等级”,准确率直接上来了。

description 要写清楚这个工具能干什么、返回什么、什么时候该用。把它当成给模型看的文档来写。

坑 3:没处理多轮工具调用

有些复杂场景,模型会先调工具 A 拿到结果,再根据结果调工具 B。如果代码只处理一轮就退出了,就会漏掉后续调用。一定要用while True循环,直到stop_reason == "end_turn"才退出。

坑 4:工具返回的内容太长

有一次把整个数据库查询结果(几百条)直接扔给模型当 tool_result,直接超 token 了。解决方案是在execute_tool里做好数据裁剪,只返回模型需要的关键字段。

坑 5:Streaming 模式下解析 tool_use

stream=True的话,tool_use 的内容会分成多个 chunk 到达,需要自己拼接 JSON。这块比较烦,建议先用非 streaming 模式把逻辑跑通,再改 streaming。

进阶:强制调用指定工具

有时候你希望模型必须调某个工具,不要自作主张直接回答。用tool_choice参数:

# 强制调用指定工具response=client.messages.create(model="claude-sonnet-4-20250514",max_tokens=4096,tools=tools,tool_choice={"type":"tool","name":"get_weather"},# 强制调 get_weathermessages=messages)# 或者让模型自己决定(默认行为)tool_choice={"type":"auto"}# 或者禁止调用任何工具tool_choice={"type":"none"}

做确定性流程的时候很有用,比如用户明确说了"查天气",就不需要让模型再判断一次。

小结

Claude Tool Use 的核心就三步:定义工具 → 处理 tool_use 响应 → 回传 tool_result,循环往复直到模型给出最终回答。给 LLM 装上了"手",让它能操作外部世界。

几个建议:

  1. description 认真写,这是影响准确率的第一因素
  2. while循环处理多轮工具调用,别只做一轮
  3. tool_result 做好数据裁剪,别把原始大数据甩给模型
  4. 要频繁切换 Claude/GPT-5/Gemini 3 对比效果的话,用 OpenAI 兼容格式更方便,改 model 参数就行

代码都是实际项目里跑过的,复制过去改改 API Key 就能用。有问题评论区聊。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 14:28:03

自动驾驶轨迹优化实战:如何用iLQR算法解决避障与车道保持问题

自动驾驶轨迹优化实战:iLQR算法在避障与车道保持中的应用 当一辆自动驾驶汽车在城市道路上穿行时,它需要实时处理复杂的道路环境——突然出现的行人、变道的车辆、弯曲的车道线。传统控制方法往往难以应对这种高维非线性优化问题,而迭代线性二…

作者头像 李华
网站建设 2026/4/22 14:24:17

别再用Excel画波特图了!手把手教你用LTspice快速搞定电路频响分析

别再用Excel画波特图了!手把手教你用LTspice快速搞定电路频响分析 在电子工程领域,频响分析就像电路设计师的"听诊器"——它能准确揭示系统对不同频率信号的响应特性。传统方法中,不少工程师习惯用Excel手动输入公式计算并绘制波特…

作者头像 李华
网站建设 2026/4/22 14:23:15

vDisk课表联动功能技术文档说明

vDisk课表联动功能技术文档说明vDisk课表联动依托IDV本地镜像能力,可按学校排课自动切换教学环境,降低机房人工运维工作量,弱网环境也能稳定运行。本文为澄成vDisk IDV云桌面课表联动功能的配置、适配与异常排查技术说明,帮助学校…

作者头像 李华