66
77> 第 1/19 章
88
9+ > * "One loop & Bash is all you need"* — 一个工具 + 一个循环 = 一个 Agent。
10+ >
11+ > ** Harness 层** : 循环 — 模型与真实世界的第一道连接。
12+
913---
1014
11- ## 你想让 AI 帮你干活
15+ ## 问题
1216
13- 大模型很聪明,但它"够不着"真实世界 ——不能读文件、不能跑命令、不能看报错。
17+ 语言模型能推理代码,但碰不到真实世界 ——不能读文件、不能跑命令、不能看报错。
1418
1519你可以给它一个工具(比如 bash),让它第一次调用拿到了结果。但然后呢?
1620
17- ** 你自己把结果复制粘贴回对话框,再让它继续。**
21+ ** 你自己把结果复制粘贴回对话框,再让它继续。** 那你就是那个循环。我们要做的,就是把这个"复制粘贴"自动化。
22+
23+ ---
24+
25+ ## 解决方案
26+
27+ 你问模型:"帮我创建 hello.py"。这个过程只有两轮:
28+
29+ ```
30+ 第 1 轮:
31+ 模型回答: [tool_use: bash "echo 'print(42)' > hello.py"]
32+ ↑ 模型调了工具 → 循环继续
33+ 你帮它执行 bash,把结果 "(no output)" 告诉模型
34+
35+ 第 2 轮:
36+ 模型回答: [text: "已创建 hello.py"]
37+ ↑ 模型没调工具 → 循环结束
38+ ```
39+
40+ 模型在第 1 轮觉得"我需要一个工具来干活",你执行了,把结果喂回去。第 2 轮它觉得"够了",停下来。
1841
19- 那你就是那个循环。我们要做的,就是把这个"复制粘贴"自动化。
42+ ** 整个 Agent 就是一个 while 循环。模型调了工具 → 你执行 → 结果喂回去 → 模型再判断。直到模型说"我不需要工具了",循环退出。 **
2043
21- ## 一个比喻
44+ ---
2245
23- 想象你派助手去图书馆查资料。
46+ ## 工作原理
2447
25- 你问:"Python 怎么读文件?"助手查了一本书,告诉你需要用 ` open() ` 。但他马上意识到光说函数不够,得给你一个例子。于是 ** 他自己回去再查 ** ,找到一段示例代码。查完之后觉得问题彻底回答清楚了,才回来汇报。
48+ 将这个过程翻译成代码。分步来看:
2649
27- 关键在于: ** 助手不是查一次就回来 ** 。他拿到中间结果后,自己判断"还需要继续查吗",需要就继续,不需要才停 。
50+ ** 第 1 步 ** :把用户的问题作为第一条消息 。
2851
29- Agent Loop 就是把这个"自己判断要不要继续"自动化。
52+ ``` python
53+ messages = [{" role" : " user" , " content" : query}]
54+ ```
3055
31- ## 最小的 Agent:一个循环 + 一个工具
56+ ** 第 2 步 ** :将消息和工具定义一起发给 LLM。
3257
33- ![ Agent Loop 流程图] ( images/agent-loop.svg )
58+ ``` python
59+ response = client.messages.create(
60+ model = MODEL , system = SYSTEM , messages = messages,
61+ tools = TOOLS , max_tokens = 8000 ,
62+ )
63+ ```
3464
35- 整个 Agent 就是一个 ` while ` 循环。模型每调用一次工具,循环就转一圈。模型说不调用了,循环就停 。
65+ ** 第 3 步 ** :追加模型回答,检查它是否调了工具。没调 → 结束 。
3666
37- 后面 18 个章节全都在这个循环上叠加新能力——但循环本身,始终不变。
67+ ``` python
68+ messages.append({" role" : " assistant" , " content" : response.content})
69+ if response.stop_reason != " tool_use" :
70+ return
71+ ```
3872
39- ## 代码:不到 30 行
73+ ** 第 4 步** :执行模型要求的工具,收集结果。
74+
75+ ``` python
76+ results = []
77+ for block in response.content:
78+ if block.type == " tool_use" :
79+ output = run_bash(block.input[" command" ])
80+ results.append({
81+ " type" : " tool_result" ,
82+ " tool_use_id" : block.id,
83+ " content" : output,
84+ })
85+ ```
86+
87+ ** 第 5 步** :把工具结果作为新消息追加,回到第 2 步。
88+
89+ ``` python
90+ messages.append({" role" : " user" , " content" : results})
91+ ```
92+
93+ 组装为一个完整函数:
4094
4195``` python
4296def agent_loop (messages ):
4397 while True :
44- # 1. 把消息发给大模型
4598 response = client.messages.create(
4699 model = MODEL , system = SYSTEM , messages = messages,
47100 tools = TOOLS , max_tokens = 8000 ,
48101 )
49-
50- # 2. 记住模型的回答
51102 messages.append({" role" : " assistant" , " content" : response.content})
52103
53- # 3. 模型没调用工具 → 任务完成,退出
54104 if response.stop_reason != " tool_use" :
55105 return
56106
57- # 4. 执行模型要求的所有工具调用
58107 results = []
59108 for block in response.content:
60109 if block.type == " tool_use" :
@@ -64,29 +113,19 @@ def agent_loop(messages):
64113 " tool_use_id" : block.id,
65114 " content" : output,
66115 })
67-
68- # 5. 把工具结果喂回去,循环继续
69116 messages.append({" role" : " user" , " content" : results})
70117```
71118
72- ** 两个关键瞬间** :
73- - ` stop_reason == "tool_use" ` → 模型举手说"我还要用工具",循环继续
74- - ` stop_reason != "tool_use" ` → 模型说"我做完了",循环退出
119+ 不到 30 行,这就是整个 Agent。后面 18 个章节都在这个循环上叠加机制——循环本身始终不变。
75120
76- ## 消息是怎么流动的
121+ ** 循环只有两个信号 ** :
77122
78- ```
79- 用户: "帮我创建一个 hello.py"
80- ↓
81- 助手: [text: "好的,我来创建"] + [tool_use: bash "echo 'print(42)' > hello.py"]
82- ↓ ← stop_reason = "tool_use",循环继续
83- 用户: [tool_result: "(no output)"]
84- ↓
85- 助手: [text: "已创建 hello.py"]
86- ← stop_reason = "end_turn",循环结束
87- ```
123+ | 信号 | 含义 | 循环动作 |
124+ | ------| ------| ---------|
125+ | ` stop_reason == "tool_use" ` | 模型举手说"我要用工具" | 执行 → 结果喂回去 → 继续 |
126+ | ` stop_reason != "tool_use" ` | 模型说"我做完了" | 退出循环 |
88127
89- 每一步都是 ** 追加 ** 到同一个 ` messages ` 列表里。模型看到的是完整对话历史,所以它知道之前做了什么。
128+ ---
90129
91130## 速查
92131
@@ -97,6 +136,8 @@ def agent_loop(messages):
97136| ` messages ` | 累积式消息列表,每一步追加,不删除 |
98137| ` tool_result ` | 工具执行的结果,必须带 ` tool_use_id ` 告诉模型"这是你要的" |
99138
139+ ---
140+
100141## 试一下
101142
102143``` sh
0 commit comments