Claude Function Calling 工程实践:别把 Tool Use 当函数执行器

0 阅读21分钟

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 是否统计;
  • 调用是否限流;
  • 敏感操作是否确认;
  • 工具结果是否可追踪。

cover-2026-04-13T02-52-21.png

10. 总结

Claude Function Calling 更准确地说是 Claude Tool Use。

它不是函数执行器,而是工具调用请求生成器。

完整链路是:

用户问题 → Claude 返回 tool_use → 后端执行工具 → 回传 tool_result → Claude 最终回答

Tool Use 的价值在于把自然语言理解能力和后端业务系统连接起来。

真正决定它能否稳定上线的,不只是 Claude 是否会返回 tool_use,而是后端执行链路是否安全、可靠、可观测。

Claude 工具调用的完整流程

第一步:定义工具 tools

定义工具,并不是把真实函数上传给 Claude。更准确地说,你是在给 Claude 一份“工具说明书”。

Claude 会根据工具的 namedescriptioninput_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 至少应该包含这些内容:

  • type
  • properties
  • required
  • 字段级别的 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 官方文档为准,因为字段形式可能会随版本变化。

temperaturemax_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 UseOpenAI Function Calling
工具定义字段toolstools
模型返回tool_use content blocktool_calls
工具结果回传tool_result content block通常是 role: tool 消息
ID 绑定tool_use_idtool_call_id
执行位置客户端 / 服务端代码客户端 / 服务端代码
常见坑消息顺序、content block 格式、tool_use_idarguments 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_use content block;
  • 是否能处理多个 tool_use
  • 是否正确回传了 tool_use_id
  • 是否把上一轮 assistant 消息加入了上下文;
  • 是否处理了 Claude 不调用工具的情况;
  • 是否把工具执行失败包装成结构化结果;
  • 是否设置了工具超时和重试策略;
  • 是否限制了高风险工具的权限;
  • 写操作是否增加了用户确认;
  • 是否记录了工具调用日志;
  • 是否设置了最大工具调用轮数;
  • 是否控制了工具返回内容长度;
  • 是否测试过不调用工具、参数缺失、多工具冲突和工具失败这些场景。

简单说,Claude API function calling 的难点并不在于把 demo 跑通。真正麻烦的地方,是把工具描述、参数 schema、消息顺序、错误处理和安全边界都设计清楚。只有这些基础工作做扎实,Claude 工具调用才算真正具备生产可用性。