你有没有遇到过这种情况,让 Claude 算个大数乘法,它信心满满地给你一个答案,但你拿计算器一验,差了十万八千里?

别怪 AI 笨。Claude 本质上是个语言模型,它的强项是理解和生成文字,不是算数。就像你公司里那个文案写得贼溜的同事,让他做财务报表,那不是为难人吗?

但聪明人有办法。不会算?找计算器。不会查数据?找数据库。这就是今天要聊的 Tool Use(工具使用),也叫 Function Calling(函数调用)。在提示词工程里,这是让 AI 编程能力真正上一个台阶的技巧,说白了就是教 AI “打电话”,自己搞不定的事,打个电话给专业工具,拿到结果接着干。


Tool Use 到底是什么?一个点外卖的故事

想象你坐在办公室,肚子饿了,想吃黄焖鸡米饭。你自己不会做饭(就像 Claude 不会真正执行代码),但你会点外卖啊:

  1. 你告诉外卖平台:我要黄焖鸡米饭,微辣,一份米饭(告诉工具名字和参数)
  2. 平台下单,商家开始做(工具执行)
  3. 外卖送到,你开吃(拿到结果,继续干活)

Tool Use 就是这个流程。Claude 不会真的去调用计算器或数据库,但它会说:“我需要调用 calculator 这个工具,参数是 1984135 乘以 9343116”。然后你的代码去执行计算,把结果喂回给 Claude,Claude 再用人话把结果告诉用户。

整个过程拆开看:

  1. Claude 说出它想调用什么工具、传什么参数(下单)
  2. 你的代码去执行工具,Claude 暂停等着(等外卖)
  3. 把结果还给 Claude,它接着回答用户(外卖到了,开吃)

其实就是之前学过的"替换"和"链式调用"的组合技。之前我们往提示词里塞文本,现在塞的是工具执行结果,套路一样,换了个马甲。


实战:给 Claude 配一个计算器

光说不练假把式,直接上代码。要做的事很简单,给 Claude 一个计算器工具,让它遇到数学题自己去算。

第一步:告诉 Claude 有哪些工具可用

这就像新入职的时候,领导给你介绍公司通讯录:“采购找老张,财务找老李,技术找老王。“你得先知道有谁能帮你,才知道什么事找谁。

系统提示词分两部分。第一部分是"通用说明”,告诉 Claude 工具调用的格式和规矩:

system_prompt_tools_general_explanation = """你可以使用一组函数来回答用户的问题。

你可以通过以下格式调用函数:
<function_calls>
<invoke name="$FUNCTION_NAME">
<parameter name="$PARAMETER_NAME">$PARAMETER_VALUE</parameter>
</invoke>
</function_calls>

调用结果会出现在 <function_results> 块中。
拿到结果后,你可以继续回答用户的问题。"""

第二部分是"具体工具清单”,就是那份通讯录:

system_prompt_tools_specific_tools = """可用的工具:
<tools>
<tool_description>
<tool_name>calculator</tool_name>
<description>
基础算术计算器,支持加减乘除
</description>
<parameters>
<parameter>
<name>first_operand</name>
<type>int</type>
<description>第一个操作数</description>
</parameter>
<parameter>
<name>second_operand</name>
<type>int</type>
<description>第二个操作数</description>
</parameter>
<parameter>
<name>operator</name>
<type>str</type>
<description>运算符,必须是 +, -, *, / 之一</description>
</parameter>
</parameters>
</tool_description>
</tools>
"""

# 拼起来就是完整的系统提示词
system_prompt = system_prompt_tools_general_explanation + system_prompt_tools_specific_tools

注意看工具描述的结构:名字、功能说明、参数列表(每个参数有名字、类型、描述)。写得越清楚,Claude 调用得越准。就像你给外卖备注写"微辣、不要香菜、多放蒜",总比只写"正常做"靠谱。

第二步:让 Claude 遇到数学题自己调用

multiplication_message = {
    "role": "user",
    "content": "算一下 1,984,135 乘以 9,343,116"
}

stop_sequences = ["</function_calls>"]

# Claude 的回复
function_calling_response = get_completion(
    [multiplication_message],
    system_prompt=system_prompt,
    stop_sequences=stop_sequences
)
print(function_calling_response)

关键细节:stop_sequences=["</function_calls>"]。意思是当 Claude 输出了 </function_calls> 这个标签时,立刻暂停。为什么要暂停?因为 Claude 已经"下了单",在等你去执行工具。你不暂停它,它就自己编一个结果出来,那就白搭了。

Claude 会回复类似这样的内容:

<function_calls>
<invoke name="calculator">
<parameter name="first_operand">1984135</parameter>
<parameter name="second_operand">9343116</parameter>
<parameter name="operator">*</parameter>
</invoke>

看,Claude 自己判断该用 calculator 工具,参数也填对了。它不是在算数,它是在"下单"。

第三步:执行工具,拿到结果

现在该你的代码出马了。定义一个真正的计算函数:

def do_pairwise_arithmetic(num1, num2, operation):
    if operation == '+':
        return num1 + num2
    elif operation == "-":
        return num1 - num2
    elif operation == "*":
        return num1 * num2
    elif operation == "/":
        return num1 / num2
    else:
        return "不支持的运算"

然后从 Claude 的回复里提取参数,调用函数:

def find_parameter(message, parameter_name):
    """从 Claude 的回复中提取指定参数的值"""
    parameter_start_string = f'name="{parameter_name}">'
    start = message.index(parameter_start_string)
    start = start + len(parameter_start_string)
    end = start
    while message[end] != "<":
        end += 1
    return message[start:end]

# 提取三个参数
first_operand = find_parameter(function_calling_response, "first_operand")
second_operand = find_parameter(function_calling_response, "second_operand")
operator = find_parameter(function_calling_response, "operator")

# 真正执行计算
result = do_pairwise_arithmetic(int(first_operand), int(second_operand), operator)
print(f"计算结果: {result:,}")
# 输出: 18,535,584,938,460

这个数字是精确的,不是 Claude 编的。因为计算是 Python 干的,不是语言模型猜的。

第四步:把结果喂回给 Claude

结果有了,但还得按约定的格式包装好让 Claude 能认出来。就像快递得有单号,结果也得有"标签":

def construct_function_results(invoke_results):
    """把工具结果包装成 Claude 能识别的格式"""
    constructed_prompt = (
        "<function_results>\n"
        + '\n'.join(
            f"<result>\n<tool_name>{res['tool_name']}</tool_name>\n"
            f"<stdout>\n{res['tool_result']}\n</stdout>\n</result>"
            for res in invoke_results
        ) + "\n</function_results>"
    )
    return constructed_prompt

# 包装结果
formatted_results = [{
    'tool_name': 'calculator',
    'tool_result': result
}]
function_results = construct_function_results(formatted_results)

最后,把完整的对话(用户问题 + Claude 的工具调用 + 工具结果)拼起来,让 Claude 继续回答:

# 拼接完整的 Claude 第一次回复(加上被截断的结束标签)
full_first_response = function_calling_response + "</function_calls>"

# 构建完整对话链
messages = [
    multiplication_message,                              # 用户提问
    {"role": "assistant", "content": full_first_response}, # Claude 调用工具
    {"role": "user", "content": function_results}          # 工具返回结果
]

# Claude 的最终回答
final_response = get_completion(messages, system_prompt=system_prompt)
print(final_response)

Claude 会回复类似:

1,984,135 乘以 9,343,116 的结果是 18,535,584,938,460

这次答案是精确的,因为计算是计算器做的,Claude 只管理解问题和组织语言。


Claude 聪明在哪?不该用工具的时候不用

你可能会想:给了 Claude 一个计算器,它是不是什么问题都想拿计算器算一下?

不是。来看:

non_multiplication_message = {
    "role": "user",
    "content": "法国的首都是什么?"
}

function_calling_response = get_completion(
    [non_multiplication_message],
    system_prompt=system_prompt,
    stop_sequences=stop_sequences
)
print(function_calling_response)

Claude 直接回答:“法国的首都是巴黎。“根本没调用计算器。

这就对了。你告诉同事"有问题可以找我”,不代表他什么事都来烦你。Claude 也一样,不需要工具就不调用,该用的时候才用。


进阶练习:给 Claude 配一个迷你数据库

光有计算器太单调了。来个更实际的场景,给 Claude 一个数据库,让它能查用户、查产品、添加记录。

先准备数据库(其实就是个 Python 字典,世界上最小的"数据库”):

db = {
    "users": [
        {"id": 1, "name": "Alice", "email": "alice@example.com"},
        {"id": 2, "name": "Bob", "email": "bob@example.com"},
        {"id": 3, "name": "Charlie", "email": "charlie@example.com"}
    ],
    "products": [
        {"id": 1, "name": "Widget", "price": 9.99},
        {"id": 2, "name": "Gadget", "price": 14.99},
        {"id": 3, "name": "Doohickey", "price": 19.99}
    ]
}

再写四个操作函数:

def get_user(user_id):
    """根据 ID 查用户"""
    for user in db["users"]:
        if user["id"] == user_id:
            return user
    return None

def get_product(product_id):
    """根据 ID 查产品"""
    for product in db["products"]:
        if product["id"] == product_id:
            return product
    return None

def add_user(name, email):
    """添加新用户"""
    user_id = len(db["users"]) + 1
    user = {"id": user_id, "name": name, "email": email}
    db["users"].append(user)
    return user

def add_product(name, price):
    """添加新产品"""
    product_id = len(db["products"]) + 1
    product = {"id": product_id, "name": name, "price": price}
    db["products"].append(product)
    return product

现在关键来了,你得写一个系统提示词,把这四个工具的"使用说明书"告诉 Claude。每个工具写清楚名字、用途、参数和类型。

system_prompt_tools_specific_tools_sql = """可用的工具:
<tools>
<tool_description>
<tool_name>get_user</tool_name>
<description>根据用户 ID 查询用户信息</description>
<parameters>
<parameter>
<name>user_id</name>
<type>int</type>
<description>要查询的用户 ID</description>
</parameter>
</parameters>
</tool_description>

<tool_description>
<tool_name>get_product</tool_name>
<description>根据产品 ID 查询产品信息</description>
<parameters>
<parameter>
<name>product_id</name>
<type>int</type>
<description>要查询的产品 ID</description>
</parameter>
</parameters>
</tool_description>

<tool_description>
<tool_name>add_user</tool_name>
<description>向数据库添加新用户</description>
<parameters>
<parameter>
<name>name</name>
<type>str</type>
<description>用户的名字</description>
</parameter>
<parameter>
<name>email</name>
<type>str</type>
<description>用户的邮箱地址</description>
</parameter>
</parameters>
</tool_description>

<tool_description>
<tool_name>add_product</tool_name>
<description>向数据库添加新产品</description>
<parameters>
<parameter>
<name>name</name>
<type>str</type>
<description>产品名称</description>
</parameter>
<parameter>
<name>price</name>
<type>float</type>
<description>产品价格</description>
</parameter>
</parameters>
</tool_description>
</tools>
"""

拿几个例子测试:

examples = [
    "往数据库里加一个叫 Deborah 的用户。",
    "添加一个叫 Thingo 的产品。",
    "告诉我 2 号用户的名字。",
    "告诉我 3 号产品的名字。"
]

Claude 会分别调用 add_useradd_productget_userget_product,参数填得明明白白。你只需要把参数解析出来,调用对应的 Python 函数,再把结果喂回去就行。


常见坑和对策

工具描述写得太随意

Claude 选工具、填参数,全靠你写的工具描述。描述模糊,Claude 就容易选错或传错。

工具描述得当产品说明书写。名字直观,功能精确,参数说明带上类型和取值范围。别偷懒,这是整个 Tool Use 链条里最吃功夫的地方。

忘记设 stop_sequences

不设 stop_sequences,Claude 输出完工具调用格式后会接着往下编,自己造一个假结果出来。那搞 Tool Use 还有啥意义。

stop_sequences=["</function_calls>"],每次都加上。这是让 Claude 知道"到这就该停了,等真正的结果"。

结果格式不对

Claude 认 <function_results> 这个标签,你不按格式返回,Claude 就读不懂,要么报错要么乱说。

严格按模板包装结果,工具名字和执行结果一个不能少。用前面写好的 construct_function_results 函数就行,别自己造轮子。

参数解析太脆弱

示例里用字符串查找提取参数,能跑但不够结实。Claude 回复格式稍有变化(多个空格、换行位置不同),就可能解析炸了。

生产环境建议用 XML 解析库来提取参数,或者直接用 Anthropic 官方的 Tool Use API,它返回结构化的 JSON,根本不用你手动解析。


小抄:Tool Use 核心流程

记住这张流程图:

用户提问
    |
    v
Claude 分析:需要工具吗?
    |           |
  不需要      需要
    |           |
  直接回答    输出工具调用(名字 + 参数)
                |
                v
            你的代码执行工具
                |
                v
            把结果按格式返回给 Claude
                |
                v
            Claude 用结果回答用户

一句话版本:Claude 下单,你的代码跑腿,结果送回来 Claude 接着说。


总结

回顾一下:Claude 不会真的执行工具,它只会说"我想调用 xxx,参数是 yyy",真正干活的是你的代码。工具描述写得清楚 Claude 就用得准,写得模糊它就瞎猜。整个流程就是 Claude 下单、代码跑腿、结果回传,本质上是替换和链式调用的组合。

下一步可以做什么

  • 试试给 Claude 配一个天气查询工具,让它能回答"今天北京天气怎么样"
  • 把多个工具组合起来,让 Claude 在一次对话里调用不同工具完成复杂任务
  • 了解 Anthropic 官方的 Tool Use API,比手工解析 XML 方便太多,直接返回结构化 JSON,参数提取和错误处理都帮你做了

你在用 AI 的过程中,最想给它配什么工具?计算器、数据库、还是让它能发邮件?评论区说说。


觉得有用的话,点个赞或转发给正在学 AI 开发的朋友。关注梦兽编程,后续还有搜索与检索增强的内容。有问题随时评论区聊。