EP04. "Tool Use 基础:让 Claude 会「打电话」求助"
🔒 登录后可标记已读- Claude 只认识训练数据里的东西,问它「现在旧金山天气怎样」它答不出来——Tool Use 就是让 Claude 在需要额外信息时,能「打电话」请你的程序帮忙查,从一个静态知识库变成能接上真实世界数据的助手
- 一次 tool use 是一段完整的对话来回:user 问题 → Claude 的 ToolUse 请求 → 你的程序执行函数、把结果包成 tool_result 送回去 → Claude 给出最终回答,四个环节缺一不可
- Claude 的回应不再只是一段文字,而是「多个 content block」——可能同时有一段文字说明 + 一个 ToolUse block,处理消息历史的方式要跟着改,不能只存文字
- 本课用一个「设提醒」的小项目贯穿:Claude 知道大概日期但没有精确时间,也不会算「一周后的星期四是几号」,更没有内建的提醒功能——这些限制正好用三个自定义工具补上
- 前置知识:EP01 的
messages.create()/多轮对话维护
重点内容
Tool Use 解决什么问题
没有工具的 Claude 碰到「现在实时资讯」类问题(天气、最新事件)只能拒答。Tool Use 的运作是这四步:
- 发起请求:用户提问,连同「可以怎么取得额外数据」的说明一起送给 Claude
- Claude 要求用工具:Claude 判断需要额外信息,说明自己需要什么
- 执行取数据:你的程序去调用对应的 API/数据库,拿到结果
- 给出最终回答:Claude 拿到数据后,整合成完整答案
以查询旧金山天气为例:Claude 判断需要天气数据 → 你的程序调用天气 API → 结果传回 Claude → Claude 结合原始问题给出准确回答。
📌 好处不只是「能查到当下数据」,还包括:能接上外部数据库/服务、回答基于最新信息、需求能用结构化的方式表达出来。
项目场景:教 Claude 设提醒
目标是让用户能用「相对日期」的自然语气设提醒,例如「帮我设个看医生的提醒,是下星期四」,Claude 要能听懂、确认、并真的执行。
Claude 天生做不到这件事,原因有三个:
- 大概知道今天日期,但没有精确到时分秒的时间信息
- 涉及未来日期的时间运算(尤其隔得比较远的日期)容易算错
- 没有内建能真的「设提醒」的功能
📌 就算是「24 小时后提醒我」这种听起来很简单的请求,也需要靠外部工具才能做对。
解法是三个自定义工具,由浅入深依次引入:
- Get Current Date Time:给 Claude 一个精确的「现在几点」基准点
- Add Duration to Date Time:处理日期加减的数学运算,不管多复杂都能算对
- Set a Reminder:真正建立排程通知,补上 Claude 天生缺的执行能力
写 Tool Function:一个普通的 Python 函数
Tool function 就是一个普通的 Python 函数,Claude 判断需要额外信息时会自动被调用。写的时候有三个原则:
- 函数名和参数名要有描述性
- 输入要彻底验证
- 报错信息要有意义——清楚的错误信息能帮 Claude 理解哪里错了、该怎么调整
def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"):
if not date_format:
raise ValueError("date_format cannot be empty")
return datetime.now().strftime(date_format)
用法示例:
get_current_datetime("%H:%M") # "14:30"
get_current_datetime("%B %d, %Y") # "March 15, 2024"
💡 报错信息写得越清楚,Claude 拿到 tool_result 里的错误提示后,越能自己调整参数重试,不用你介入。
写完函数之后,下一步是写一份 JSON schema 告诉 Claude 这个函数怎么用,再把它接进对话系统。
写 Tool Schema:给 Claude 看的说明书
Tool schema 是一份 JSON Schema 文档,描述函数的参数——这份 schema 就是 Claude 用来判断「什么时候该调用这个工具、该怎么传参数」的说明书。
完整的工具定义包含三部分:
| 字段 | 作用 |
|---|---|
name | 函数标识符 |
description | 这个工具做什么、什么时候该用、会返回什么 |
input_schema | JSON Schema 格式描述的参数细节 |
💡 description 建议写 3-4 句话,讲清楚工具的作用、使用场景、返回内容,参数说明也要写详细。
get_current_datetime_schema = {
"name": "get_current_datetime",
"description": "Returns the current date and time formatted according to the specified format",
"input_schema": {
"type": "object",
"properties": {
"date_format": {
"type": "string",
"description": "A string specifying the format of the returned datetime. Uses Python's strftime format codes.",
"default": "%Y-%m-%d %H:%M:%S"
}
},
"required": []
}
}
📌 可以从 Anthropic 的库里 import ToolParam 来包住这份 schema 字典,帮忙做类型检查、少踩类型错误的坑。
请求里带上 tools 参数、处理多 block 回应
在 API 请求里加一个 tools 参数,把可用工具的 JSON schema 列表传进去,Claude 才知道自己有哪些工具能用。
带了 tools 参数之后,Claude 的回应不再只是一段文字,而是可能同时包含:
- 一个文字 block(人类可读的说明)
- 一个 ToolUse block(包含函数名、参数、以及一个用来追踪的 ID)
📌 Claude 不存对话历史,所以消息历史要自己管理——存 assistant 回应时,要存整个 response.content(完整的 block 列表),不能只抽文字出来存,不然 Claude 会丢失自己刚才请求用工具这件事的记录。
完整流程分五步:① 发送带 tools 参数的用户消息 → ② 收到多 block 回应 → ③ 执行对应函数 → ④ 把结果连同完整历史一起送回去 → ⑤ 拿到 Claude 的最终回答。
执行工具、把结果送回去
先从回应里取出 ToolUse block 和它的参数,调用对应函数:
# 取出 tool use block
tool_use_block = response.content[1]
# 拿到参数
input_params = tool_use_block.input
# 用参数调用函数
result = get_current_datetime(**input_params)
💡 **input_params 这个双星号语法,是把字典的键值对拆成关键字参数传进去。
Tool result block 要包含三个字段:
| 字段 | 作用 |
|---|---|
tool_use_id | 对应原始请求里那个 ToolUse block 的 ID |
content | 函数执行结果,转成字符串 |
is_error | 布尔值,标记这次执行是不是失败了 |
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": response.content[1].id,
"content": result,
"is_error": False
}]
})
📌 Claude 可能一次请求好几个工具调用,每个调用都有各自独立的 ID,送结果回去时要对准 ID,就算回应顺序打乱了也能对上号。后续请求要记得继续带上 tools schema,Claude 才能理解对话历史里那些工具引用是什么意思。
完整流程:① 用户提问需要用到工具 → ② Claude 给出 ToolUse block → ③ 你执行对应函数 → ④ 把结果包成 tool_result 发回去 → ⑤ Claude 给出整合了工具结果的最终答案。
多轮工具调用:一个问题要连续用两次工具
有些问题不是叫一次工具就够——比如「103 天后是几号」,得先查「今天是几号」,再拿这个日期去加 103 天,需要连续调用两个工具才能回答。
流程会变成:用户提问 → Claude 请求第一个工具 → 你的程序执行、返回结果 → Claude 发现还需要更多信息、请求第二个工具 → 你的程序再执行一次 → Claude 才给出最终答案。
用循环处理,不写死调用几次:
def run_conversation(messages):
while True:
response = chat(messages)
add_assistant_message(messages, response)
# 判断这次回应是不是在要求用工具,不是的话就跳出循环
if response isn't asking for a tool:
break
tool_result_blocks = run_tools(response)
add_user_message(messages, tool_result_blocks)
return messages
📌 课程页面在这里用伪代码表示「判断是不是在要求用工具」这一步,没有展示 run_tools() 具体怎么实现,只交代了它的角色:把 Claude 请求的所有工具都执行一遍、组装成 tool_result blocks。实务上通常是检查 response.stop_reason 是不是等于 "tool_use" 来做这个判断,这是 Anthropic API 标记「模型这轮要求调用工具」的标准方式。
改造 helper function,接受完整 message 对象而不只是纯文字:
from anthropic.types import Message
def add_user_message(messages, message):
user_message = {
"role": "user",
"content": message.content if isinstance(message, Message) else message
}
messages.append(user_message)
chat() 加上 tools 参数:
def chat(messages, system=None, temperature=1.0, stop_sequences=[], tools=None):
params = {
"model": model,
"max_tokens": 1000,
"messages": messages,
"temperature": temperature,
"stop_sequences": stop_sequences,
}
if tools:
params["tools"] = tools
if system:
params["system"] = system
message = client.messages.create(**params)
return message
只想拿纯文字时,专门写一个抽取函数:
def text_from_message(message):
return "\n".join(
[block.text for block in message.content if block.type == "text"]
)
💡 这些改动的核心是让程序能处理「一条消息里有多个 block」「消息可能是纯文字也可能是完整 Message 对象」这两个新常态,Claude 才能在需要的时候自动串联多个工具调用,把问题彻底答完。
常见错误
- ❌ 存 assistant 回应到 messages 历史时只抽文字存,丢了 ToolUse block——Claude 后续会搞不清楚自己刚才请求过什么工具
- ❌ 送 tool_result 时
tool_use_id对错 ID——Claude 可能一次请求多个工具调用,结果一定要对准各自的 ID - ❌ 后续请求忘了继续带
toolsschema——Claude 会看不懂对话历史里那些工具调用引用的是什么 - ❌ 把多轮工具调用写死成固定次数的调用,而不是用循环判断
stop_reason是否还要求用工具——遇到需要 3 次以上工具调用的问题就会答不完整
Sources
官方文档:
- Claude with Google Cloud's Vertex AI(Claude Academy 课程)— https://academy.claude.com/courses/claude-with-google-cloud-s-vertex-ai