第 63 章:循环的引擎

卷五协议验证日期:2026-05-17,基于 Agentic Loop 模式和本书卷一/卷四设计分析
这是卷五的基石章——把所有协议组装成一个能自主工作的 Agent 引擎。
路线图
1 | graph LR |
法则七:Agent 就是循环加工具
Agent 的形式化定义:1
Agent = LLM + Tools + Loop + ContextManager
循环是引擎——它把单次 API 调用变成持续的自主行动。
Agent Loop 的状态机
1 | stateDiagram-v2 |
核心实现
1 | // → src/my-agent/agent-loop.ts |
使用 Agent Loop
1 | // → src/my-agent/usage-example.ts |
上下文管理的三种策略
| 策略 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 压缩 | 用模型摘要历史 | 保留语义 | 摘要可能丢失细节 |
| 截断 | 丢弃最早的消息 | 简单 | 可能丢失关键上下文 |
| 分层 | 近期完整 + 远期摘要 | 平衡 | 实现复杂 |
我们的实现用分层策略:保留最后 3 条,其余压缩为摘要。
试试看
任务 1:让 Agent 执行一个需要多轮工具调用的任务(如”在项目中搜索所有 TODO 注释并汇总”)。观察完整循环。
任务 2:触发 max_iterations 限制(设为 2),观察 Agent 如何处理被截断的任务。
任务 3:实现”人类确认”——在每次工具调用前暂停,等待用户输入 y/n。
常见错误
| 现象 | 原因 | 解法 |
|---|---|---|
| 无限循环 | max_iterations 太大 | 设置合理上限(默认 25) |
| 上下文溢出 | 未触发 compaction | 降低 maxContextTokens 阈值 |
| 工具结果丢失 | messages 中漏了 tool_result | 确保推入消息历史 |
| 重复调用同一工具 | 返回值不清晰 | 在 tool_result 中加更多信息 |
实现循环检测:终结那个贯穿全书的 Bug
还记得卷零 ch02 的那个 Agent 死循环吗?在卷一中我们理解了循环退出条件,卷二中追踪了工具执行引擎的局限,卷三中我们调试定位了 root cause,卷四中讨论了为什么这是一个架构权衡。现在,我们亲手解决它。
设计:工具调用历史追踪
1 | // → src/my-agent/loop-detector.ts |
集成到 AgentLoop
1 | // → 在 AgentLoop 中集成循环检测 |
测试循环检测
1 | // → test/loop-detector.test.ts |
那个 Bug 的终结
用这个 LoopDetector 重新跑卷零的那个场景:1
2
3
4
5
6
7第 1 轮:Read("src/auth.ts") → LoopDetector: 第 1 次
第 2 轮:Read("src/auth.ts") → LoopDetector: 第 2 次
第 3 轮:Read("src/auth.ts") → LoopDetector: 第 3 次
→ ⚠️ 检测到可能的循环
→ 注入警告:"Read 已被调用 3 次。请解释下一步意图,或尝试不同的方法。"
→ Agent 收到警告,重新思考,决定 Edit("src/auth.ts:42")
→ 任务继续,正常完成
这个 bug 从卷零跟你到卷五,经历了”认识 → 分析 → 调试 → 反思 → 解决”的完整旅程。这就是为什么这本书叫《跟着消息走》——同一条消息,在不同层次上展现出完全不同的意义。
检测 Agent 的幻觉 — 当模型”确认”了不存在的事
循环检测解决了 Agent “反复做同一件事”的问题。但 Agent 还有另一个更隐蔽的失败模式:Hallucination(幻觉)——模型说它改了代码,但其实没改;说测试全部通过,但测试根本没跑。
幻觉的三种形态
| 形态 | 表现 | 例子 |
|---|---|---|
| 事实幻觉 | 断言代码中存在不存在的函数/变量 | “第 42 行有个 handleLogin 函数”——其实没有 |
| 行动幻觉 | 声称执行了操作但实际没执行 | “我已经修复了 bug”——但 src/auth.ts 没变化 |
| 状态幻觉 | 声称验证了结果但实际没验证 | “测试全部通过”——但 npm test 根本没跑 |
循环检测捕获的是”重复调用相同工具”——一个可量化的模式。幻觉检测更难,因为它捕获的是”模型声称 vs 实际情况”的不一致——需要外部验证。
策略一:要求引用,然后验证
最有效的幻觉预防是要求模型引用源码行号,然后用 Read 工具验证:1
2
3
4
5
6// → system prompt 中的幻觉预防指令
const HALLUCINATION_PREVENTION = `
When you make claims about code, cite specific file paths and line numbers.
If you claim to have fixed a bug, run the tests to verify.
If you are unsure whether a claim is correct, say "I'm not sure" rather than guessing.
`
验证中间件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// → 在 AgentLoop 中验证模型的代码引用
function verifyCodeClaims(response: string, workspacePath: string): ClaimVerification {
// 提取所有 "文件名:行号" 模式的引用
const references = response.match(/(\S+\.\w{1,4}):(\d+)/g) ?? []
const verified: string[] = []
const unverified: string[] = []
for (const ref of references) {
const [file, line] = ref.split(":")
const fullPath = path.join(workspacePath, file)
if (fs.existsSync(fullPath)) {
const content = fs.readFileSync(fullPath, "utf-8")
const actualLines = content.split("\n")
if (parseInt(line) <= actualLines.length) {
verified.push(ref)
continue
}
}
unverified.push(ref)
}
return { verified, unverified, hallucinationRisk: unverified.length / references.length }
}
如果 hallucinationRisk > 0.5(超过一半引用无法验证),注入警告:1
2
3
4
5
6if (verification.hallucinationRisk > 0.5) {
this.messages.push({
role: "user",
content: `以下代码引用无法验证:${verification.unverified.join(", ")}。请重新检查这些位置。`
})
}
策略二:行动验证
Agent 声称”已修改 src/auth.ts“。在下一轮循环中验证:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// → 在工具执行后添加验证钩子
class ActionVerifier {
private expectedModifications = new Map<string, string>() // file → expected content hash
// Agent 声称修改了文件 → 记录预期
expectModification(filePath: string, expectedHash: string): void {
this.expectedModifications.set(filePath, expectedHash)
}
// 下一轮验证
verify(): VerificationResult {
const results: string[] = []
for (const [file, expectedHash] of this.expectedModifications) {
if (!fs.existsSync(file)) {
results.push(`✗ ${file} 不存在——Agent 可能声称修改了一个不存在的文件`)
continue
}
const actualHash = crypto.createHash("sha256")
.update(fs.readFileSync(file))
.digest("hex")
if (actualHash !== expectedHash) {
results.push(`✗ ${file} 内容与 Agent 声称的不符`)
}
}
this.expectedModifications.clear()
return { verified: results.length === 0, details: results }
}
}
策略三:自检提示
1 | // → 在每次工具结果返回后追加自检提示 |
三种策略的防御矩阵
| 策略 | 防止什么 | 代价 | 实施难度 |
|---|---|---|---|
| 引用验证 | 事实幻觉 | 每轮多一个 Read 调用 | 中 |
| 行动验证 | 行动幻觉 | 记录和比较 hash | 低 |
| 自检提示 | 状态幻觉 | 增加 ~50 tokens/turn | 极低 |
建议从自检提示开始(零成本),然后加行动验证,最后加引用验证。
检查点
- 理解了 Agent Loop 的五状态机模型
- 实现了完整的 AgentLoop(含上下文管理)
- 能处理四种 stop_reason 的不同分支
- 实现了上下文压缩(compaction)策略
- 能追踪 token 消耗和循环次数
- 实现了 LoopDetector(连续相同工具+相同参数检测)
- 能将循环警告注入上下文,给模型改正机会
- 写了循环检测的单元测试(相同工具、不同工具、不同参数)
- 理解幻觉的三种形态(事实/行动/状态)和三种防御策略
- 实现了引用验证中间件和行动验证器