01 - 智能体循环
⭐ 核心路径 · 第一站
"One loop & Bash is all you need" —— 一个工具 + 一个循环 = 一个智能体。
Harness 层: 循环 —— 模型与真实世界的第一道连接。
学习目标
- 理解 Agent Loop 的模式:消息累积 → LLM 推理 → 工具执行 → 回到推理
- 掌握
stop_reason作为循环退出条件的机制 - 用不到 30 行代码实现一个最小 Agent
- 认识 Harness 工程师的视角:你构建模型栖居的世界,而不是编写智能
核心概念
问题
语言模型能推理代码,但碰不到真实世界 —— 不能读文件、跑测试、看报错。没有循环,每次工具调用都得手动把结果粘回去。你自己就是那个循环。
最小循环
+--------+ +-------+ +---------+
| User | ---> | LLM | ---> | Tool |
| prompt | | | | execute |
+--------+ +---+---+ +----+----+
^ |
| tool_result |
+----------------+
(loop until stop_reason != "tool_use")一个退出条件控制整个流程。循环持续运行,直到模型不再调用工具。
Agent vs. Harness
| 概念 | 含义 |
|---|---|
| Agent | 模型本身 —— 一个通过训练学会推理和行动的神经网络,无法直接被修改 |
| Harness | 模型栖居的世界 —— 工具、知识、上下文管理、权限边界,这些是工程师可以构建和优化的 |
| Agent Loop | 连接 Agent 和 Harness 的核心循环,让模型能看到工具结果并决定下一步 |
三行核心逻辑
整个 Agent Loop 可以浓缩为三句话:
- 发消息给 LLM(携带消息历史和工具定义)
- 检查
stop_reason—— 如果模型没调用工具,结束 - 执行工具调用,把结果追加到消息列表,回到第 1 步
python
while True:
response = client.messages.create(model=MODEL, system=SYSTEM, messages=messages, tools=TOOLS)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return
for tool_call in response.content:
result = execute(tool_call)
messages.append({"role": "user", "content": result})stop_reason 详解
stop_reason 是 API 返回的枚举值,指示模型为什么停止生成:
stop_reason 值 | 含义 | 循环行为 |
|---|---|---|
"tool_use" | 模型要调用工具 | 继续循环 |
"end_turn" | 模型给出最终回答 | 退出循环 |
"max_tokens" | 达到 token 上限 | 退出循环(需额外处理) |
"stop_sequence" | 命中停止序列 | 退出循环 |
安全约束
最小 Harness 只做一件事:阻止危险命令。这不是心智负担,而是 Agent 工程的第一条规则 —— 永远假设模型可能出错。
python
def run_bash(command: str) -> str:
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
return "Error: Dangerous command blocked"
...动手实践:最小 Agent
前置准备
sh
cd learn-claude-code
python agents/s01_agent_loop.py完整代码
python
import os, subprocess
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv(override=True)
if os.getenv("ANTHROPIC_BASE_URL"):
os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]
SYSTEM = f"You are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, don't explain."
TOOLS = [{
"name": "bash",
"description": "Run a shell command.",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"],
},
}]
def run_bash(command: str) -> str:
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
return "Error: Dangerous command blocked"
try:
r = subprocess.run(command, shell=True, cwd=os.getcwd(),
capture_output=True, text=True, timeout=120)
out = (r.stdout + r.stderr).strip()
return out[:50000] if out else "(no output)"
except subprocess.TimeoutExpired:
return "Error: Timeout (120s)"
def agent_loop(messages: list):
while True:
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return
results = []
for block in response.content:
if block.type == "tool_use":
print(f"\033[33m$ {block.input['command']}\033[0m")
output = run_bash(block.input["command"])
print(output[:200])
results.append({"type": "tool_result",
"tool_use_id": block.id, "content": output})
messages.append({"role": "user", "content": results})
if __name__ == "__main__":
history = []
while True:
try:
query = input("\033[36ms01 >> \033[0m")
except (EOFError, KeyboardInterrupt):
break
if query.strip().lower() in ("q", "exit", ""):
break
history.append({"role": "user", "content": query})
agent_loop(history)
response_content = history[-1]["content"]
if isinstance(response_content, list):
for block in response_content:
if hasattr(block, "text"):
print(block.text)
print()尝试这些 Prompt
英文 prompt 对 LLM 效果更好,也可以用中文:
Create a file called hello.py that prints "Hello, World!"List all Python files in this directoryWhat is the current git branch?Create a directory called test_output and write 3 files in it
观察点
- 每次工具调用后,模型都会"看到"结果并决定下一步
- 多步任务(如创建目录再写文件)需要多次循环
- 模型会自动决定何时停止 —— 不需要你判断任务是否完成
关键概念
| 组件 | 作用 | 备注 |
|---|---|---|
| Agent Loop | while True + stop_reason | 循环在模型放弃使用工具时退出 |
| Tools | bash(单一工具) | 最小集合,一个工具足以完成任务 |
| Messages | 累积式消息列表 | 每次迭代追加 assistant + tool_result 消息 |
| Control Flow | stop_reason != "tool_use" | 唯一退出条件 |
| Tool execute | subprocess.run() | 同步执行,结果截断至 50K chars |
| System Prompt | "Act, don't explain." | 引导模型直接行动而非讨论 |
为什么 S01 是 ⭐ 核心路径第一站
后续所有课程都是在此循环上叠加机制:
- [[../02-tool-use/02-工具使用|s02 工具使用]] —— 加一个工具,只加一个 handler
- [[../03-todo-write/03-待办写入|s03 待办写入]] —— 先列步骤再动手
- [[../04-subagent/04-子智能体|s04 子智能体]] —— 大任务拆小,每个小任务干净的上下文
- [[../05-skill-loading/05-技能加载|s05 技能加载]] —— 用到什么知识,临时加载什么知识
- [[../06-context-compact/06-上下文压缩|s06 上下文压缩]] —— 上下文总会满,要有办法腾地方
- [[../07-task-system/07-任务系统|s07 任务系统]] —— 大目标要拆成小任务,排好序,记在磁盘上
- [[../09-agent-teams/09-智能体团队|s09 智能体团队]] —— 任务太大一个人干不完,要能分给队友
循环本身永远不变。理解了这个循环,你就理解了所有 Agent 的本质。
变化一览
| 组件 | 之前 | 之后 |
|---|---|---|
| Agent loop | (不存在) | while True + stop_reason |
| Tools | (不存在) | bash(单一工具) |
| Messages | (不存在) | 累积式消息列表 |
| Control flow | (不存在) | stop_reason != "tool_use" |
| Safety | (不存在) | 危险命令过滤 |
讨论问题
- 如果模型在循环中无限调用工具怎么办?有哪些策略可以防止这种情况?
- 为什么 s01 只暴露
bash一个工具?如果加一个read_file工具会有什么不同? stop_reason的局限性在哪里?考虑max_tokens被截断的场景 —— 模型还没完成工作但被迫停止了。- "Agent 是模型,不是框架" —— 这句话在实践中意味着什么?如果你改不了模型,你能改什么?
- System prompt 说
"Act, don't explain"—— 删除这句话模型的行为会怎样变化?试试看。
延伸阅读
- Learn Claude Code 项目 — s01 练习代码
- Anthropic Messages API 文档 —
stop_reason完整说明 - [[../00-课程概览/综述|← 返回课程综述]] · [[../00-课程概览/教学大纲|📋 查看教学大纲]]