Tool Use:让 AI 学会打电话,不会的事找人帮忙

你有没有遇到过这种情况,让 Claude 算个大数乘法,它信心满满地给你一个答案,但你拿计算器一验,差了十万八千里?
别怪 AI 笨。Claude 本质上是个语言模型,它的强项是理解和生成文字,不是算数。就像你公司里那个文案写得贼溜的同事,让他做财务报表,那不是为难人吗?
但聪明人有办法。不会算?找计算器。不会查数据?找数据库。这就是今天要聊的 Tool Use(工具使用),也叫 Function Calling(函数调用)。在提示词工程里,这是让 AI 编程能力真正上一个台阶的技巧,说白了就是教 AI “打电话”,自己搞不定的事,打个电话给专业工具,拿到结果接着干。
Tool Use 到底是什么?一个点外卖的故事
想象你坐在办公室,肚子饿了,想吃黄焖鸡米饭。你自己不会做饭(就像 Claude 不会真正执行代码),但你会点外卖啊:
- 你告诉外卖平台:我要黄焖鸡米饭,微辣,一份米饭(告诉工具名字和参数)
- 平台下单,商家开始做(工具执行)
- 外卖送到,你开吃(拿到结果,继续干活)
Tool Use 就是这个流程。Claude 不会真的去调用计算器或数据库,但它会说:“我需要调用 calculator 这个工具,参数是 1984135 乘以 9343116”。然后你的代码去执行计算,把结果喂回给 Claude,Claude 再用人话把结果告诉用户。
整个过程拆开看:
- Claude 说出它想调用什么工具、传什么参数(下单)
- 你的代码去执行工具,Claude 暂停等着(等外卖)
- 把结果还给 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_user、add_product、get_user、get_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 开发的朋友。关注梦兽编程,后续还有搜索与检索增强的内容。有问题随时评论区聊。