Skip to main content

没有循环会怎样?一次性执行的失败

一次性执行 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. 继续下一轮(带着新的结果)
}
}

关键点

  1. while(true):无限循环
  2. shouldCompact():防止上下文溢出
  3. autoCompact():带断路器的压缩
  4. stop_reason:判断是否结束
  5. messages.push():把结果加入历史
  6. 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(())
}

关键点

  1. loop:Rust 的无限循环
  2. should_compact():压缩检查
  3. should_continue():判断是否继续
  4. execute_tools():执行工具
  5. 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%

关键要点

  1. 循环不是"可选项",而是"必需品"
  2. 一次性执行的任务完成率只有 20%
  3. 多轮循环的任务完成率 95%
  4. 差距:4.75 倍
  5. 断路器防止无限循环和成本爆炸
  6. 真实案例:3272 次压缩失败
  7. 反馈学习是智能的核心

记住:复杂任务需要多轮循环,一次性执行注定失败。


字数:约 4500 字
阅读时间:约 12 分钟