回想 Day 1 的对话机器人 —— 你问它"现在几点?",它只能瞎猜(因为 LLM 训练数据里没有当下时间)。
Tool Use 让 LLM 不再只是"文字生成器",而是能:
Day 1 你用了 3 种 role:system、user、assistant。Day 2 引入第 4 种:tool。
| Role | 谁在说话 | 内容 |
|---|---|---|
system | 你(开发者) | 身份设定、行为规范 |
user | 用户 | 提问 |
assistant | LLM | 文本回答 或 tool_calls(调用工具的请求) |
tool | 你的代码 | 工具执行结果(回传给 LLM) |
轮次 1:
[system] 你是助手,可以用 get_current_time 工具查时间。
[user] 现在几点?
[assistant] tool_calls: [{id: "call_1", name: "get_current_time", args: {}}]
↑ 注意这里没有 content!LLM 说"我要用工具"
你的代码执行 get_current_time() → 返回 "2026-07-12 12:30:00"
轮次 2(同一次对话):
[tool] call_1 的结果:2026-07-12 12:30:00
[assistant] 现在是 2026 年 7 月 12 日中午 12 点半。
↑ 现在有 content 了,是最终回答
while 循环,直到 LLM 不再要求调用工具为止。
cd ~/Documents/practice/agents
# 建 day2 目录(复用 day1 的 .env 是常见做法,但我们各建各的更清晰)
mkdir -p day2
cd day2
# 复用之前的虚拟环境?也可以每天一个
python3 -m venv venv
source venv/bin/activate
# 依赖跟 Day 1 一样
pip install openai python-dotenv
把 Day 1 的 .env 拷过来(同一个 API Key 就行):
cp ../day1/.env .env
要让 LLM 知道"你可以用哪些工具",得用一种它认识的格式描述。这个格式叫 tools schema,本质是 JSON Schema。
tools = [
{
"type": "function",
"function": {
"name": "get_current_time", # 工具名 → LLM 会用这个名字调用
"description": "获取当前的日期和时间,返回 ISO 格式字符串。",
# ↑ 这个描述极其重要!LLM 靠它判断"什么时候该用这个工具"
"parameters": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "时区,例如 'Asia/Shanghai'。默认为本地时区。"
}
},
"required": [] # 哪些参数必填
}
}
}
]
description ≈ 方法的 Javadoc / Go doc commentparameters ≈ 函数签名先写一个不执行的 demo —— 只观察 LLM 返回的 tool_calls 长什么样。这一步很关键,让你亲眼看到"LLM 是怎么表达'我想调用工具'的"。
见 day2/demo1_tool_basics.py(我已经建好,直接跑即可)。
=== 问题 1:现在几点? ===
LLM 返回的 assistant 消息:
content: None ← 注意:没有文字回答!
tool_calls: [
{
id: "call_abc123",
type: "function",
function: {
name: "get_current_time",
arguments: '{"timezone": "Asia/Shanghai"}'
↑ 注意:arguments 是字符串(JSON string),不是 dict!
}
}
]
=== 问题 2:你好,介绍下自己 ===
LLM 返回的 assistant 消息:
content: "你好!我是..." ← 这次有文字回答
tool_calls: None ← 没调工具(因为不需要)
content = None 而 tool_calls 有值arguments 是 JSON 字符串,不是 dict,用之前要 json.loads()现在写一个真正的 Agent:不但会调用工具,还能把结果回传给 LLM,让 LLM 继续对话。
while True:
# 1. 调 LLM
response = llm(messages, tools=tools)
msg = response.choices[0].message
# 2. 把 assistant 消息加进历史(无论有没有 tool_calls 都要加!)
messages.append(msg)
# 3. 如果 LLM 没要求调工具,任务完成
if not msg.tool_calls:
print(msg.content)
break
# 4. 有 tool_calls:逐个执行,把结果作为 role="tool" 消息加进历史
for tool_call in msg.tool_calls:
result = execute_tool(tool_call.function.name, tool_call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id, # 必须!用来匹配是哪次调用的结果
"content": str(result),
})
# 5. 回到循环开头,让 LLM 拿着 tool 结果继续
assistant 消息(含 tool_calls)加进 messages —— 下轮 LLM 会说"我不知道你在说啥"tool_call_id —— LLM 不知道你回的是哪次调用arguments 忘了 json.loads() —— 类型错误| 工具 | 作用 | 参数 |
|---|---|---|
get_current_time | 返回当前时间 | 无 |
read_file | 读取本地文件 | path: 文件路径 |
# 问题 1:需要调用一个工具
你: 现在几点?
# 问题 2:需要调用另一个工具
你: 帮我看看 sample.txt 里写了什么?
# 问题 3:需要连续调用两个工具(真正的 Agent 感觉!)
你: 帮我读一下 sample.txt,然后告诉我现在几点。
# 问题 4:不需要工具
你: 你好,介绍下自己
给 Agent 加一个新工具:
list_files(directory):列出某个目录下的所有文件
然后问 Agent:"当前目录下有哪些文件?帮我读一下最小的那个。"
观察 Agent 会不会:
list_files 拿到文件列表| 坑 | 症状 | 解决 |
|---|---|---|
| tool_calls 没加到 messages | 下轮 LLM 说"我没有调过工具" | 确保每次 messages.append(msg),包括有 tool_calls 的 |
| arguments 直接当 dict 用 | TypeError 或参数错乱 | json.loads(tool_call.function.arguments) |
| tool_call_id 缺失 | API 400 报错 | role=tool 的消息必须带 tool_call_id |
| 无限循环 | LLM 反复要求调同一个工具 | 加最大轮次限制(比如 10 轮) |
| 工具报错让 Agent 崩溃 | 抛出 Python 异常 | 工具内部 try/except,把错误消息作为结果返回给 LLM,让它自己判断 |
| LLM 幻觉调用不存在的工具 | tool_call 里的 name 你没定义 | execute_tool 里判断,返回错误消息给 LLM |
收工前,你应该能:
while 循环而不是一次调用cd ~/Documents/practice/agents
git status # 确认 .env 不在里面
git add day2/
git commit -m "Day 2: Tool Use 工具调用(观察 + 完整 Agent Loop)"
git push
Day 3 会引入 RAG(检索增强生成) —— 让 Agent 能"读"你的私人笔记。
你会学到: