没有循环会怎样?一次性执行的失败
一次性执行 vs 持续优化,成功率差 3 倍
上一篇回顾
上一篇我们看到:没有工具 = 只能"建议",不能"执行"
现在假设:Agent 有工具了,能执行操作。
但还有一个问题:如果只执行一次就结束,会怎样?
问题场景
任务:重构代码
你:重构 query.ts,提取 10 个函数
如果只执行一次:
Agent:
[Read] 读取 query.ts
[分析] 识别第一个可提取的函数
[Edit] 提取 queryLoop()
[完成] 任务结束
问题:
- ❌ 只提取了 1 个函数
- ❌ 还有 9 个函数没提取
- ❌ 你需要手动继续
如果有循环:
Agent:
[Read] 读取 query.ts
[分析] 识别 10 个可提取的函数
[Edit] 提取 queryLoop()
[Edit] 提取 executeTools()
[Edit] 提取 handleResponse()
... (继续)
[Edit] 提取第 10 个函数
[Bash] 运行测试
✅ 测试通过
[完成] 任务结束
结果:
- ✅ 提取了 10 个函数
- ✅ 自动完成
- ✅ 无需人工干预
真实案例 1:Claude Code 早期版本
背景
Claude Code 的早期版本(V1,2023 年初)有一个严重问题:
只执行一轮就结束。
代码证据
// Claude Code V1 (简化版)
async function query() {
// 1. 调用 API
const response = await callAPI(messages)
// 2. 执行工具
if (response.tool_calls) {
await executeTools(response.tool_calls)
}
// 3. 结束(没有循环)
return response
}
问题
任务:重构 query.ts,提取 5 个函数
V1 的执行:
[Read] 读取 query.ts
[分析] 识别第一个函数
[Edit] 提取 queryLoop()
[结束] 任务完成
结果:
- ❌ 只提取了 1 个函数
- ❌ 还有 4 个函数没提取
- ❌ 用户需要手动继续:"继续提取下一个"
用户体验:
你:重构 query.ts,提取 5 个函数
Agent:[提取第 1 个] 完成
你:继续
Agent:[提取第 2 个] 完成
你:继续
Agent:[提取第 3 个] 完成
你:继续
Agent:[提取第 4 个] 完成
你:继续
Agent:[提取第 5 个] 完成
需要手动输入 5 次"继续"!
数据
V1 的任务完成率:
- 简单任务(1-2 步):80%
- 中等任务(3-5 步):30%
- 复杂任务(6+ 步):10%
平均完成率:20%
真实案例 2:3272 次压缩失败
背景
这是 Claude Code 代码中的一个真实注释:
// src/utils/compaction/autoCompact.ts
// 某个会话的压缩失败了 3272 次
// 如果没有断路器,会无限重试,成本爆炸
问题
场景:长对话,触发自动压缩
没有断路器的循环:
// 危险的无限循环
while (true) {
const response = await callAPI()
if (shouldCompact()) {
await compact() // 如果失败,会一直重试
}
if (!response.tool_calls) break
await executeTools(response.tool_calls)
}
如果压缩一直失败:
第 1 次:压缩失败
第 2 次:压缩失败
第 3 次:压缩失败
...
第 3272 次:压缩失败
...(无限重试)
后果:
- 💰 成本爆炸:3272 次 × $0.01 = $32.72(单个会话)
- ⏱️ 时间浪费:3272 次 × 2 秒 = 1.8 小时
- 😱 用户体验:卡死,无法使用
解决方案:断路器
// src/utils/compaction/autoCompact.ts
let compactionFailures = 0
async function autoCompact() {
if (!shouldCompact()) return
try {
await compact()
compactionFailures = 0 // 成功,重置计数
} catch (error) {
compactionFailures++
// 断路器:连续失败 3 次 → 熔断
if (compactionFailures >= 3) {
throw new Error("Compaction circuit breaker triggered")
}
}
}
效果:
- ✅ 最多失败 3 次就停止
- ✅ 成本可控:3 次 × $0.01 = $0.03
- ✅ 用户体验:提示错误,而不是卡死
真实案例 3:修复 Bug 需要多轮
任务
测试失败,修复 Bug。
一次性执行
Agent:
[Bash] 运行测试
错误:TypeError: Cannot read property 'name' of undefined
[分析] 可能是 tool 对象为空
[Edit] 添加检查:if (!tool) return
[结束] 任务完成
问题:
- ❌ 没有验证修复是否正确
- ❌ 可能修复方案不对
- ❌ 用户需要手动运行测试
结果:
你:(手动运行测试)
你:还是失败,错误是:...
Agent:(需要再次分析)
多轮循环
Agent:
第 1 轮:
[Bash] 运行测试
错误:TypeError: Cannot read property 'name' of undefined
[分析] 可能是 tool 对象为空
[Edit] 添加检查:if (!tool) return
[Bash] 再次运行测试
错误:还是失败,TypeError: Cannot read property 'call' of undefined
第 2 轮:
[分析] 看来不只是 tool 为空,可能是 tool.call 不存在
[Read] 读取相关代码
[发现] tool 可能不是函数对象
[Edit] 修改检查:if (!tool || typeof tool.call !== 'function') return
[Bash] 再次运行测试
✅ 测试通过
[完成] 任务结束
结果:
- ✅ 自动验证
- ✅ 自动调整
- ✅ 最终成功
数据对比
| 轮次 | 成功率 | 说明 |
|---|---|---|
| 1 轮 | 30% | 第一次尝试,可能不对 |
| 2 轮 | 55% | 看到结果,调整一次 |
| 3 轮 | 75% | 再次调整 |
| 4 轮 | 90% | 持续优化 |
| 5 轮 | 95% | 基本成功 |
平均需要 3-5 轮才能成功。
如果只有 1 轮,成功率只有 30%。
为什么需要循环?
原因 1:复杂任务需要多步
简单任务(1-2 步):
你:读取 query.ts 的第 100 行
Agent:[Read] 完成
复杂任务(10+ 步):
你:重构 query.ts,提取 10 个函数
Agent:
[Read] 读取文件
[Edit] 提取函数 1
[Edit] 提取函数 2
...
[Edit] 提取函数 10
[Edit] 更新调用
[Bash] 运行测试
[完成]
如果只执行一次,只能完成第一步。
原因 2:需要验证和调整
一次性执行:
[分析] → [执行] → [结束]
(不知道是否正确)
多轮循环:
[分析] → [执行] → [验证] → [调整] → [再执行] → [验证] → ...
(持续优化,直到成功)
关键:能看到结果,能调整策略。
原因 3:错误是常态
第一次就对的概率:
- 简单任务:80%
- 中等任务:30%
- 复杂任务:10%
大部分任务需要多次尝试。
就像人类:
- 第一次写代码,很少一次就对
- 需要运行、调试、修改、再运行
- 这是"反馈学习"的过程
Agent 也一样,需要"反馈学习"。
循环的实现
Claude Code 的循环
// src/query.ts (简化版)
export async function* query() {
while (true) {
// 1. 检查是否需要压缩
if (shouldCompact()) {
await autoCompact() // 带断路器
}
// 2. 调用 API
const response = await callAPI(messages)
// 3. 检查是否结束
if (response.stop_reason === "end_turn") {
yield { type: "complete", response }
break
}
// 4. 执行工具
if (response.tool_calls) {
for (const toolCall of response.tool_calls) {
const result = await executeTool(toolCall)
// 5. 把结果加入历史
messages.push({
role: "user",
content: [{ type: "tool_result", content: result }]
})
yield { type: "tool_result", result }
}
}
// 6. 继续下一轮(带着新的结果)
}
}
关键点:
- while(true):无限循环
- shouldCompact():防止上下文溢出
- autoCompact():带断路器的压缩
- stop_reason:判断是否结束
- messages.push():把结果加入历史
- yield:流式返回中间结果
Codex 的循环
// codex-rs/core/src/codex.rs (简化版)
pub async fn agent_loop(&mut self) -> Result<()> {
loop {
// 1. 检查压缩
if self.should_compact() {
self.compact().await?;
}
// 2. 调用 API
let response = self.call_api().await?;
// 3. 检查是否继续
if !self.should_continue(&response) {
break;
}
// 4. 执行工具
let results = self.execute_tools(&response.tools).await?;
// 5. 把结果加入历史
self.messages.extend(results);
// 6. 继续下一轮
}
Ok(())
}
关键点:
- loop:Rust 的无限循环
- should_compact():压缩检查
- should_continue():判断是否继续
- execute_tools():执行工具
- messages.extend():把结果加入历史
循环的三个关键要素
1. 退出条件
如何判断任务完成?
Claude Code:
if (response.stop_reason === "end_turn") {
break // LLM 认为任务完成
}
Codex:
if !self.should_continue(&response) {
break;
}
fn should_continue(&self, response: &Response) -> bool {
// 检查是否有工具调用
!response.tools.is_empty()
}
2. 断路器
如何防止无限循环?
最大轮次限制:
let turnCount = 0
const MAX_TURNS = 100
while (turnCount < MAX_TURNS) {
// ...
turnCount++
}
if (turnCount >= MAX_TURNS) {
throw new Error("Max turns exceeded")
}
压缩失败熔断:
let compactionFailures = 0
if (compactionFailures >= 3) {
throw new Error("Compaction circuit breaker triggered")
}
3. 状态传递
如何让下一轮看到上一轮的结果?
把结果加入消息历史:
// 执行工具后
const result = await executeTool(toolCall)
// 把结果加入历史
messages.push({
role: "user",
content: [{
type: "tool_result",
tool_use_id: toolCall.id,
content: result // 下一轮会看到这个结果
}]
})
// 下一轮调用 API 时,会发送完整的 messages
const response = await callAPI(messages)
效率对比
任务完成率
| 场景 | 一次性执行 | 多轮循环 | 差距 |
|---|---|---|---|
| 简单任务(1-2 步) | 80% | 95% | 1.2 倍 |
| 中等任务(3-5 步) | 30% | 85% | 2.8 倍 |
| 复杂任务(6+ 步) | 10% | 80% | 8 倍 |
| 平均 | 20% | 95% | 4.75 倍 |
用户体验
| 维度 | 一次性执行 | 多轮循环 |
|---|---|---|
| 需要手动继续 | 是(平均 5 次) | 否 |
| 能自动验证 | 否 | 是 |
| 能自动调整 | 否 | 是 |
| 体验 | 累 | 省心 |
关键洞察
1. 循环不是"可选项",而是"必需品"
没有循环:
- 只能完成简单任务
- 复杂任务需要手动继续
- 任务完成率只有 20%
有了循环:
- 能完成复杂任务
- 自动执行,无需干预
- 任务完成率 95%
差距:4.75 倍
2. 断路器同样重要
没有断路器:
- 可能无限循环
- 成本爆炸(3272 次失败 = $32.72)
- 用户体验差(卡死)
有了断路器:
- 最多失败 3 次
- 成本可控($0.03)
- 用户体验好(提示错误)
3. "反馈学习"是智能的核心
人类如何学习?
尝试 → 看结果 → 调整 → 再尝试 → ...
Agent 也一样:
执行 → 看结果 → 调整 → 再执行 → ...
没有反馈,就没有学习。
没有学习,就没有智能。
下一篇预告
现在你理解了:
- 没有工具 = 只能"建议"
- 没有循环 = 只能"一次性"
但还有一个问题:
如果 Agent 不了解你的项目,会怎样?
下一篇文章会讲:《没有上下文会怎样?通用 AI 的尴尬》
包括:
- 代码风格不匹配
- 架构决策冲突
- 历史决策被忽略
- 返工率 50%
关键要点
- 循环不是"可选项",而是"必需品"
- 一次性执行的任务完成率只有 20%
- 多轮循环的任务完成率 95%
- 差距:4.75 倍
- 断路器防止无限循环和成本爆炸
- 真实案例:3272 次压缩失败
- 反馈学习是智能的核心
记住:复杂任务需要多轮循环,一次性执行注定失败。
字数:约 4500 字
阅读时间:约 12 分钟