Claude API 的 Function Calling,经常被开发者用来类比 OpenAI Function Calling。但在 Claude 官方文档中,更准确的名称是 Tool Use。
它的核心机制不是让 Claude 直接执行函数,而是让 Claude 根据用户问题返回一个结构化工具调用请求。
真正执行函数、调用数据库、访问外部 API、写入业务系统的,必须是后端代码。
如果把 Claude Tool Use 理解成函数执行器,很容易在工程实现中踩坑。
1. Tool Use 的职责边界
Claude Tool Use 的流程如下:
用户问题
↓
Claude 返回 tool_use
↓
后端执行工具
↓
后端返回 tool_result
↓
Claude 生成最终回答
职责划分如下:
| 模块 | 职责 |
|---|---|
| Claude | 理解意图、选择工具、生成参数 |
| 后端服务 | 校验参数、执行函数、处理异常、返回结果 |
| 业务系统 | 提供真实数据或执行业务动作 |
| Claude | 基于工具结果生成自然语言回答 |
这个边界是实现 Tool Use 的基础。
2. 什么任务适合 Tool Use?
适合工具调用的任务通常有一个共同点:需要外部数据或外部动作。
例如:
- 查询订单状态;
- 查询物流;
- 查询库存;
- 查询天气;
- 查询股票;
- 查询客户信息;
- 创建客服工单;
- 调用 ERP 或 CRM;
- 将自然语言解析成结构化查询条件。
这些任务无法只靠模型自身知识稳定完成。
3. 什么任务不适合 Tool Use?
如果任务是:
- 文章生成;
- 文案改写;
- 摘要总结;
- 翻译润色;
- 普通知识问答;
- 固定 FAQ;
- 邮件草稿;
- 代码解释。
通常不需要工具调用。
直接调用 Claude 更简单,也更省 token。
过度工具化会导致系统复杂度上升,调试成本增加,响应延迟变高。
4. 核心字段
Claude Tool Use 中常见字段包括:
4.1 tools
定义工具列表。
工具描述要清楚说明用途。
4.2 input_schema
定义工具参数结构。
schema 应尽量严格,避免参数不稳定。
4.3 tool_use
Claude 返回的工具调用请求。
它不是执行结果,只是调用意图。
4.4 tool_result
后端执行工具后返回给 Claude 的结果。
4.5 tool_choice
控制 Claude 是否使用工具,以及使用哪个工具。
5. 示例:查询订单状态
用户输入:
查询订单 12345 的物流状态。
Claude 返回:
tool_use:
name: query_order
input:
order_id: "12345"
后端执行:
query_order(order_id="12345")
返回:
订单已发货,当前物流状态为运输中。
后端将结果作为 tool_result 回传 Claude。
Claude 最终回答:
订单 12345 已发货,目前物流状态为运输中。
6. 后端必须做什么?
后端不能直接信任 Claude 生成的参数。
至少需要做:
- 工具白名单校验;
- 参数类型校验;
- 必填字段校验;
- 用户身份校验;
- 权限校验;
- 业务规则校验;
- 超时处理;
- 异常处理;
- 日志脱敏。
尤其是涉及支付、退款、订单修改、删除数据等敏感操作时,必须增加二次确认。
7. 工具设计建议
工具设计不要过细,也不要过粗。
过细会导致工具数量过多,模型选择困难。
过粗会导致工具语义模糊,参数复杂,误调用概率上升。
比较好的工具设计应该满足:
- 名称明确;
- 描述具体;
- 输入参数稳定;
- 输出结构清晰;
- 业务边界单一;
- 错误结果可解释。
8. 常见问题
8.1 Claude 会不会直接调用 API?
不会。Claude 只返回工具调用请求。
8.2 tool_use 能不能直接执行?
不能。后端必须校验后执行。
8.3 工具越多越好吗?
不是。工具越多,模型选择成本越高。
8.4 普通聊天要不要加工具?
不需要。只有依赖外部数据或业务动作时才需要。
9. 生产环境注意事项
上线前建议检查:
- 工具描述是否清晰;
- schema 是否严格;
- 参数是否校验;
- 权限是否完善;
- 错误是否可恢复;
- 日志是否脱敏;
- token 是否统计;
- 调用是否限流;
- 敏感操作是否确认;
- 工具结果是否可追踪。
10. 总结
Claude Function Calling 更准确地说是 Claude Tool Use。
它不是函数执行器,而是工具调用请求生成器。
完整链路是:
用户问题 → Claude 返回 tool_use → 后端执行工具 → 回传 tool_result → Claude 最终回答
Tool Use 的价值在于把自然语言理解能力和后端业务系统连接起来。
真正决定它能否稳定上线的,不只是 Claude 是否会返回 tool_use,而是后端执行链路是否安全、可靠、可观测。
Claude 工具调用的完整流程
第一步:定义工具 tools
定义工具,并不是把真实函数上传给 Claude。更准确地说,你是在给 Claude 一份“工具说明书”。
Claude 会根据工具的 name、description 和 input_schema 来判断:这个工具该不该用、什么时候用、参数应该怎么填。
一个比较典型的工具定义如下:
{
"name": "get_order_status",
"description": "根据订单 ID 查询订单的支付状态、发货状态和预计送达时间。只有当用户明确询问订单状态时才使用。",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号,例如 OD20240601001"
}
},
"required": ["order_id"]
}
}
第二步:发送用户问题和工具列表
第一次请求时,你需要把用户消息和 tools 一起传给 Claude。接下来 Claude 会自己判断:当前问题是否需要调用工具。
第三步:解析 Claude 返回的 tool_use
如果 Claude 认为确实需要使用工具,响应里通常会出现类似这样的结构:
{
"type": "tool_use",
"id": "toolu_01ABC",
"name": "get_order_status",
"input": {
"order_id": "OD20240601001"
}
}
这时候常见的 stop_reason 是 "tool_use"。它的意思是:模型先暂停生成最终回答,等你把工具执行结果传回来。
第四步:在后端执行真实函数
接下来就轮到你的代码登场了。服务端需要根据 name 找到对应函数,然后校验参数,再执行真实业务逻辑。
这里一定要注意:不要无条件信任模型生成的参数。尤其是涉及数据库、支付、文件系统、邮件发送、权限变更这类高风险操作时,后端校验是必须的。
第五步:用 tool_result 把结果发回 Claude
工具执行完以后,需要把结果包装成 tool_result 再回传给 Claude。
其中 tool_use_id 必须和上一轮 Claude 返回的 tool_use.id 对上。这个 ID 不能自己随便生成,否则 Claude 无法知道这个结果对应的是哪一次工具调用。
第六步:获取最终自然语言回答
Claude 收到工具执行结果后,才会基于这些真实数据生成最终回答。也就是说,用户最后看到的那段自然语言回复,是 Claude 结合 tool_result 之后生成的。
核心配置项详解:tools、input_schema、tool_choice 怎么写
tools.name:工具名要短、明确、方便后端分发
工具名建议用英文小写加下划线,例如:
get_order_status
search_products
cancel_order
尽量不要写得太抽象,比如:
get_data
query
do_action
工具名不只是给 Claude 看的,你的后端通常也会根据这个名字做路由分发。如果名字太含糊,很容易造成误调用,后面排查起来也很麻烦。
tools.description:重点写清楚“什么时候用”
很多人写工具描述时,只写这个工具“能做什么”,但其实还不够。对 Claude 来说,更重要的是知道“什么时候该用它”。
不太好的描述:
查询订单。
更推荐这样写:
根据订单 ID 查询订单的支付状态、发货状态和预计送达时间。只有当用户明确询问订单状态、物流进度或订单是否发货时才使用。
Claude 选择工具时,会非常依赖 description。如果你有多个工具,而且职责有一点相近,就更要在描述里把边界写清楚。否则模型选错工具是很常见的事。
input_schema:用 JSON Schema 把参数约束住
input_schema 至少应该包含这些内容:
typepropertiesrequired- 字段级别的
description
如果某个字段只有固定取值,最好直接用 enum 限制住:
{
"type": "object",
"properties": {
"status_type": {
"type": "string",
"enum": ["payment", "shipping", "refund"],
"description": "要查询的状态类型"
}
},
"required": ["status_type"]
}
如果不加 enum,模型可能会生成“物流状态”“发货情况”“delivery”这类不稳定的值。对人来说都差不多,但对后端代码来说,很可能就匹配不上了。
tool_choice:自动、强制和禁用
tool_choice 主要用来控制 Claude 是否调用工具,以及调用哪个工具。常见策略有三种:
- 自动选择:让 Claude 自己判断要不要调用工具;
- 强制指定工具:适合表单抽取、结构化查询、必须走业务系统的场景;
- 禁止工具调用:适合纯文本回答、总结、写作等任务。
比如强制调用某个工具,可以这样写:
{
"tool_choice": {
"type": "tool",
"name": "get_order_status"
}
}
如果只是普通聊天,不希望 Claude 调工具,最简单的方式就是不要传 tools。如果你使用的 SDK 或 API 支持显式禁用,也可以按官方文档里的最新写法来配置。这里建议以 Anthropic 官方文档为准,因为字段形式可能会随版本变化。
temperature 和 max_tokens
工具调用场景里,通常建议把 temperature 设低一点,比如 0 到 0.3。这样模型生成参数时会更稳定,不容易发散。
另外,max_tokens 也别设得太小。Claude 生成 tool_use 本身也要消耗 token。如果限制太紧,就有可能出现响应被截断的情况。
最小可运行示例:用 Claude 调用一个订单查询工具
下面是一个 Python 版本的结构示例,主要是为了展示完整流程。模型名称请以 Anthropic 官方最新模型列表为准。
from anthropic import Anthropic
import json
client = Anthropic(api_key="YOUR_API_KEY")
tools = [
{
"name": "get_order_status",
"description": "根据订单 ID 查询订单支付状态、发货状态和预计送达时间。",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号,例如 OD20240601001"
}
},
"required": ["order_id"]
}
}
]
def get_order_status(order_id: str):
fake_db = {
"OD20240601001": {
"payment_status": "paid",
"shipping_status": "shipped",
"eta": "2026-07-02"
}
}
if order_id not in fake_db:
return {
"ok": False,
"error": "ORDER_NOT_FOUND",
"message": "未找到该订单"
}
return {
"ok": True,
"data": fake_db[order_id]
}
messages = [
{
"role": "user",
"content": "帮我查一下订单 OD20240601001 发货了吗?"
}
]
resp = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
temperature=0,
tools=tools,
messages=messages
)
tool_results = []
for block in resp.content:
if block.type == "tool_use":
if block.name == "get_order_status":
order_id = block.input.get("order_id")
result = get_order_status(order_id)
else:
result = {
"ok": False,
"error": "UNKNOWN_TOOL",
"message": f"未知工具:{block.name}"
}
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result, ensure_ascii=False)
})
if not tool_results:
print("Claude 没有调用工具:", resp.content)
else:
messages.append({
"role": "assistant",
"content": resp.content
})
messages.append({
"role": "user",
"content": tool_results
})
final_resp = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
temperature=0,
tools=tools,
messages=messages
)
print(final_resp.content)
这个例子里有几个地方很关键:
第一,第一次请求只是让 Claude 判断是否需要调用工具,不是直接拿最终答案。
第二,Claude 返回 tool_use 之后,真正的函数执行发生在你的后端。
第三,第二次请求时,必须把上一轮 assistant 消息也放回 messages,也就是那条包含 tool_use 的消息。
第四,tool_result.tool_use_id 必须和 Claude 返回的 tool_use.id 完全匹配。
另外,即使工具执行失败,也建议返回结构化错误,而不是让程序直接崩掉。这样 Claude 还能基于错误信息给用户一个正常解释。
进阶示例:多工具调用与强制工具调用
一个请求里放多个工具会发生什么
假设你现在有两个工具:
get_order_status:查询订单状态;get_shipping_trace:查询物流轨迹。
工具列表可能长这样:
[ { "name": "get_order_status", "description": "查询订单的支付状态、发货状态和预计送达时间。", "input_schema": { "type": "object", "properties": { "order_id": { "type": "string" } }, "required": ["order_id"]
}
},
{
"name": "get_shipping_trace",
"description": "根据物流单号查询包裹运输轨迹。只有用户提供物流单号或明确询问运输轨迹时使用。",
"input_schema": {
"type": "object",
"properties": {
"tracking_no": { "type": "string" }
},
"required": ["tracking_no"]
}
}
]
如果用户说:“查一下订单 OD001 发货了吗”,那更适合调用 get_order_status。
如果用户说:“查一下物流单 SF123 到哪了”,那更适合调用 get_shipping_trace。
多工具场景最容易出问题的地方,就是工具职责写得太重叠。比如两个工具的描述都写成“查询订单相关信息”,Claude 就很可能分不清该用哪个。所以工具职责最好拆清楚,描述也要尽量明确。
如何处理多个 tool_use
Claude 有时会在一次响应里返回多个 tool_use。所以你的代码不要默认“只会有一个工具调用”。
更稳妥的做法是:遍历所有 content block,发现一个 tool_use 就执行一个,并且逐个回传对应的 tool_result。
如何强制 Claude 调用指定工具
强制调用适合这些情况:
- 用户输入必须抽取成固定字段;
- 答案必须经过业务系统查询;
- 你不希望模型凭已有知识或猜测来回答。
示例:
{
"tool_choice": {
"type": "tool",
"name": "get_order_status"
}
}
不过,强制调用也不是完全没有风险。如果用户没有提供必要参数,模型可能会尝试自己猜。所以后端的参数校验依然不能省。
如何限制 Claude 不要调用工具
如果是纯知识问答、写作、总结类任务,一般没有必要传工具列表。最简单的方式就是不传 tools。
如果当前 API 或 SDK 支持显式禁用工具,也可以按最新官方文档配置。总之,不要在没必要的场景里硬加工具调用。
Claude 与 OpenAI Function Calling 有什么不同
不少开发者会把 OpenAI Function Calling 的代码直接搬到 Claude 上,这其实很容易踩坑。两者的理念有相似之处,但消息格式并不一样。
| 维度 | Claude Tool Use | OpenAI Function Calling |
|---|---|---|
| 工具定义字段 | tools | tools |
| 模型返回 | tool_use content block | tool_calls |
| 工具结果回传 | tool_result content block | 通常是 role: tool 消息 |
| ID 绑定 | tool_use_id | tool_call_id |
| 执行位置 | 客户端 / 服务端代码 | 客户端 / 服务端代码 |
| 常见坑 | 消息顺序、content block 格式、tool_use_id | arguments JSON 解析、tool_call_id |
迁移时尤其要注意这些点:
- 不要把 OpenAI 的
role: tool原封不动搬到 Claude; - Claude 的工具结果通常放在 user message 的 content block 里;
- Claude 返回的是结构化 content block,不能只按纯文本读取;
- 上一轮包含
tool_use的 assistant 消息必须放回上下文; tool_use_id如果对不上,Claude 就没法把工具结果和调用请求关联起来。
避坑实录:10 个常见问题与修复方法
1. Claude 没有调用工具
现象:用户明明问了业务问题,但 Claude 直接用文本回答了。 常见原因:工具描述太模糊、用户问题本身不一定需要工具,或者当前是自动选择模式。 处理办法:在 description 里写清楚调用时机。必要时,可以用 tool_choice 强制指定工具。
2. 返回文本而不是 tool_use
现象:程序里找不到 tool_use,后续逻辑直接断了。 常见原因:代码默认每次都会调用工具,但 Claude 并不一定这么做。 处理办法:同时兼容两条路径:一种是 Claude 直接回答,另一种是 Claude 发起工具调用。
3. schema 字段写了,但模型没有填
常见原因:字段没有放进 required,或者字段描述太笼统。 处理办法:把必要字段明确写入 required,同时给每个字段加上清楚的说明和示例。
4. 必填参数缺失
常见原因:用户没有给出足够信息,Claude 无法稳定构造参数。 处理办法:后端要做参数校验。如果缺参数,就让 Claude 追问用户,而不是勉强执行工具。
5. enum 参数乱填
常见原因:状态、类型、渠道这类字段没有用 enum 约束。 处理办法:凡是固定取值的字段,尽量使用枚举。这样后端处理会稳定很多。
6. tool_use_id 不匹配
现象:回传结果后,Claude 没有继续回答,或者报格式相关错误。 常见原因:tool_result.tool_use_id 没有使用原始的 tool_use.id。 处理办法:保存每一个 tool_use.id,回传时原样使用,不要自己生成 ID。
7. 回传 tool_result 后没有最终回答
常见原因:没有把上一轮 assistant 消息加入上下文,或者消息顺序错了。 处理办法:正确顺序应该是:user 原问题 → assistant tool_use → user tool_result。
8. 多工具调用选错工具
常见原因:工具名称太抽象,描述也互相重叠。 处理办法:每个工具只负责一件事,并且在描述里说明适用场景和不适用场景。
9. 工具执行失败后对话中断
常见原因:后端异常直接抛出,没有把错误包装成 Claude 能理解的结果。 处理办法:把失败也包装成结构化响应,比如:
{
"ok": false,
"error": "TIMEOUT",
"message": "订单系统暂时不可用,请稍后重试"
}
同时要注意,不要把数据库连接串、密钥、堆栈信息这类敏感内容暴露给 Claude 或最终用户。
10. 工具调用循环不停止
常见原因:工具返回的信息不够明确,Claude 以为还需要继续查。 处理办法:设置最大工具调用轮数,比如 3 到 5 轮。同时,工具结果里尽量给出明确状态,不要返回模糊信息。
生产环境最佳实践
工具设计:单一职责、明确输入、结构化输出
一个工具最好只做一件事。
查询订单、取消订单、申请退款,应该拆成不同工具,而不是都塞进一个 handle_order 里。工具越“大而全”,Claude 越难判断什么时候该用它,后端也越难做权限和安全控制。
输出方面,推荐使用结构化 JSON:
{
"ok": true,
"data": {
"shipping_status": "shipped",
"eta": "2026-07-02"
}
}
这样 Claude 更容易理解,后端做日志、监控和排错也更方便。
安全边界:不要让模型直接执行危险操作
Claude 生成的参数不能直接信任。涉及写操作时,尤其要加安全保护,比如:
- 用户身份校验;
- 权限校验;
- 参数白名单;
- 二次确认;
- 操作审计日志。
像“取消订单”“发送邮件”“执行 SQL”“删除文件”这类操作,都应该被视为高风险工具。哪怕 Claude 生成的参数看起来很合理,也不能跳过后端校验。
超时、重试与幂等
真实生产环境里,外部 API 失败很正常。比较稳妥的做法是:
- 给每个工具设置超时时间;
- 只读操作可以适度重试;
- 写操作要谨慎重试,并且必须保证幂等;
- 工具失败时返回结构化错误,而不是直接中断对话。
尤其是写操作,比如扣款、退款、取消订单,一定要避免因为重试导致重复执行。
日志与监控
工具调用相关日志非常重要。至少建议记录这些信息:
- 用户输入摘要;
- Claude 选择的工具名;
- 工具参数;
- 执行耗时;
- 返回结果摘要;
- 错误码;
tool_use_id;- 当前调用轮次。
这些日志在排查问题时很有用。比如为什么 Claude 没有调用工具、为什么选错工具、为什么一直循环调用,基本都要靠这些信息定位。
控制 token 成本
不要把外部 API 返回的大段原始 JSON 一股脑塞回 Claude。这样不仅贵,还容易污染上下文。
更好的方式是先在后端做一次筛选或摘要,只把 Claude 生成最终回答真正需要的字段传回去。工具结果越短、越清晰,效果通常越稳定。
ClaudeAPI 接入场景补充
如果你使用的是第三方 Claude API 兼容接入服务,比如 ClaudeAPI,需要注意一点:它并不是 Anthropic 官方服务。
这类平台通常会强调兼容接入、多线路选择、中文支持、企业充值、开票、基础技术协助等能力。但具体支持哪些模型、如何计费、额度怎么算、接口细节是否完全一致、可用性如何,都应该以对应平台官网的最新说明为准。
不过,无论你用的是官方 Anthropic API,还是第三方兼容接入服务,Claude 工具调用的核心逻辑都一样:模型生成 tool_use,你的后端执行工具,然后再用 tool_result 把结果传回去。
FAQ:Claude API Function Calling 常见问题
Claude Function Calling 和 Tool Use 是一回事吗?
基本可以这么理解。Claude 官方更常用的叫法是 Tool Use,而 Function Calling 更多是开发者沿用 OpenAI 体系里的说法。
Claude 会自己调用外部 API 吗?
不会。Claude 只会返回工具调用请求。真正请求外部 API、访问数据库或执行业务逻辑的,是你的服务端代码。
Claude 工具调用需要几个 API 请求?
通常至少两次。第一次拿到 tool_use,第二次回传 tool_result 并获取最终回答。如果任务比较复杂,可能还会有多轮工具调用。
Claude 可以一次调用多个工具吗?
可以。Claude 可能在一次响应中返回多个 tool_use。所以代码应该遍历 content block,而不是只处理第一个。
工具结果应该返回 JSON 还是文本?
生产环境更推荐结构化 JSON。这样 Claude 更容易理解,后端也更方便记录日志和排查问题。
Claude Tool Use 和 MCP 有什么区别?
Tool Use 是 Claude API 里的工具调用机制;MCP 更像是一套连接工具和数据源的协议与生态。简单业务直接用 Tool Use 就够了,如果工具体系很复杂,再考虑 MCP 会更合适。
Claude Tool Use 能替代 RAG 吗?
不能完全替代。RAG 主要解决知识检索和上下文增强的问题;Tool Use 主要解决调用外部函数、业务系统和执行操作的问题。实际项目里,两者经常可以配合使用。
总结:推荐配置与上线检查清单
在把 Claude API 工具调用上线之前,建议认真检查下面这些点:
- 工具名是否清晰、稳定,方便后端分发;
description是否写清楚了调用时机和不适用场景;input_schema是否包含required、字段描述和必要的enum;- 是否正确解析了
tool_usecontent block; - 是否能处理多个
tool_use; - 是否正确回传了
tool_use_id; - 是否把上一轮 assistant 消息加入了上下文;
- 是否处理了 Claude 不调用工具的情况;
- 是否把工具执行失败包装成结构化结果;
- 是否设置了工具超时和重试策略;
- 是否限制了高风险工具的权限;
- 写操作是否增加了用户确认;
- 是否记录了工具调用日志;
- 是否设置了最大工具调用轮数;
- 是否控制了工具返回内容长度;
- 是否测试过不调用工具、参数缺失、多工具冲突和工具失败这些场景。
简单说,Claude API function calling 的难点并不在于把 demo 跑通。真正麻烦的地方,是把工具描述、参数 schema、消息顺序、错误处理和安全边界都设计清楚。只有这些基础工作做扎实,Claude 工具调用才算真正具备生产可用性。