198 lines
6.4 KiB
Markdown
198 lines
6.4 KiB
Markdown
|
|
---
|
|||
|
|
tags: [ai/agent, react-loop, self-reflection, convergence-criteria]
|
|||
|
|
create time: 2026-08-08 18:43
|
|||
|
|
update time: 2026-08-08 18:43
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 循环状态机在Agent中的应用
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
简单的 while loop 只能让 Agent "做一件事直到成功",缺乏对执行过程的结构性理解。基于状态机的循环——特别是 ReAct(Reasoning + Acting)模式——为 Agent 引入了可推理、可观测、可终止的执行框架。这是构建自主智能体的基础架构模式。
|
|||
|
|
|
|||
|
|
## 详解
|
|||
|
|
|
|||
|
|
### ReAct 循环:思考→行动→观察
|
|||
|
|
|
|||
|
|
ReAct 的核心思想是让 LLM 在执行任务时交替进行"推理"和"行动",每步行动后观察结果并据此调整下一步策略。这与人类解决问题的过程高度一致。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
stateDiagram-v2
|
|||
|
|
[*] --> Start
|
|||
|
|
|
|||
|
|
state ReActLoop {
|
|||
|
|
[*] --> Reason["思考\n分析当前局面"]
|
|||
|
|
Reason --> Act["行动\n调用工具或生成答案"]
|
|||
|
|
Act --> Observe["观察\n获取工具返回结果"]
|
|||
|
|
Observe --> Evaluate{"结果是否满足目标?"}
|
|||
|
|
|
|||
|
|
Evaluate -->|"否"| Reason
|
|||
|
|
Evaluate -->|"是"| Done["完成"]
|
|||
|
|
Evaluate -->|"迭代超限"| Timeout["超时/失败"]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Start --> ReActLoop
|
|||
|
|
ReActLoop --> Done
|
|||
|
|
ReActLoop --> Timeout
|
|||
|
|
Done --> [*]
|
|||
|
|
Timeout --> [*]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**三轮 ReAct 的完整展开:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Round 1:
|
|||
|
|
Thought: 用户问「Golang GC 的改进」,我需要先搜索相关信息
|
|||
|
|
Action: GoogleSearch("Go 1.21 GC improvements")
|
|||
|
|
Observation: 返回 3 篇文档摘要...
|
|||
|
|
|
|||
|
|
Round 2:
|
|||
|
|
Thought: 搜索结果包含三色标记法和并发 Mark-Stack 的信息,但缺少具体代码变更
|
|||
|
|
Action: WebFetch("https://go.dev/doc/gc-change")
|
|||
|
|
Observation: 提取到关键改动点列表...
|
|||
|
|
|
|||
|
|
Round 3:
|
|||
|
|
Thought: 综合两轮信息可以形成完整回答
|
|||
|
|
Action: 生成最终答案
|
|||
|
|
Observation: (无,直接交付)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 自反思修正(Self-Reflection)
|
|||
|
|
|
|||
|
|
Self-Reflection 是在每次 Observate 之后额外增加一步自我评估,判断当前进度是否足以完成任务,以及是否需要改变策略。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TB
|
|||
|
|
A["思考"] --> B["行动"]
|
|||
|
|
B --> C["观察"]
|
|||
|
|
C --> D["反思评估\n• 已完成多少?"]
|
|||
|
|
D --> E{"置信度 ≥ 阈值?"}
|
|||
|
|
E -->|"是"| F["生成最终答案"]
|
|||
|
|
E -->|"否"—>|继续下一轮| A
|
|||
|
|
D --> G["诊断问题根源\n• 缺什么信息?"]
|
|||
|
|
G --> H["调整策略\n• 换搜索词/切工具"]
|
|||
|
|
H --> A
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**反思 Prompt 模板:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
请评估当前进展:
|
|||
|
|
1. 我已经获得的信息是什么?
|
|||
|
|
2. 这些信息是否足够回答问题?
|
|||
|
|
3. 如果不够,还缺少什么信息?
|
|||
|
|
4. 下一步应该做什么来补充缺失?
|
|||
|
|
|
|||
|
|
输出格式:
|
|||
|
|
{
|
|||
|
|
"confidence": 0.75,
|
|||
|
|
"sufficient": false,
|
|||
|
|
"missing": ["具体的benchmark数据"],
|
|||
|
|
"next_action": {"tool": "search", "query": "Go benchmark results"}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!NOTE]
|
|||
|
|
> Self-Reflection 不是可有可无的——它让 Agent 从"盲目重试"升级为"有策略地迭代"。没有反思的循环就是蛮力,有反思的循环才是智能。
|
|||
|
|
|
|||
|
|
### 收敛条件设计
|
|||
|
|
|
|||
|
|
无限循环是最危险的 agent bug。必须设置明确的终止条件:
|
|||
|
|
|
|||
|
|
| 条件 | 实现方式 | 典型值 |
|
|||
|
|
|------|---------|--------|
|
|||
|
|
| 最大迭代次数 | 计数器达到上限 | 5-10 次 |
|
|||
|
|
| 连续无改善 | 最近 N 次 iteration 结果无提升 | N = 3 |
|
|||
|
|
| 置信度阈值 | 自评 confidence ≥ 设定阈值 | 0.8 |
|
|||
|
|
| 时间预算 | 总耗时超过 budget | 30s |
|
|||
|
|
| Token 预算 | 累计消耗 tokens 超限 | 根据模型限制 |
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// Go 示例:带收敛条件的 ReAct Loop
|
|||
|
|
type ReActLoop struct {
|
|||
|
|
maxIterations int
|
|||
|
|
noImproveLimit int
|
|||
|
|
confidenceThresh float64
|
|||
|
|
timer *time.Timer
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (r *ReActLoop) Run(ctx context.Context, goal string) (*Result, error) {
|
|||
|
|
var history []Step
|
|||
|
|
noImproveCount := 0
|
|||
|
|
|
|||
|
|
for i := 0; i < r.maxIterations; i++ {
|
|||
|
|
// 检查时间预算
|
|||
|
|
select {
|
|||
|
|
case <-ctx.Done():
|
|||
|
|
return nil, ctx.Err()
|
|||
|
|
default:
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 执行一轮 ReAct
|
|||
|
|
step := r.executeOneRound(ctx, goal, history)
|
|||
|
|
history = append(history, step)
|
|||
|
|
|
|||
|
|
// 收敛判断
|
|||
|
|
if step.Confidence >= r.confidenceThresh {
|
|||
|
|
return step.Result, nil // 满意,提前退出
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if step.SameAsPrevious(history) {
|
|||
|
|
noImproveCount++
|
|||
|
|
} else {
|
|||
|
|
noImproveCount = 0
|
|||
|
|
}
|
|||
|
|
if noImproveCount >= r.noImproveLimit {
|
|||
|
|
break // 反复无效,停止
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 未达最优但已达上限,返回最佳尝试
|
|||
|
|
return bestOf(history), nil
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 与简单 Loop 的区别
|
|||
|
|
|
|||
|
|
| 维度 | 简单 Loop | ReAct 状态机 |
|
|||
|
|
|------|----------|-------------|
|
|||
|
|
| 决策依据 | 固定规则 | LLM 推理 + 工具反馈 |
|
|||
|
|
| 状态感知 | 无 | 携带完整历史上下文 |
|
|||
|
|
| 策略调整 | 不支持 | 支持,每轮可换策略 |
|
|||
|
|
| 终止条件 | 仅一个布尔条件 | 多条件复合判断 |
|
|||
|
|
| 可解释性 | 差 | 好,每步都有 Thought trace |
|
|||
|
|
| 适用场景 | 确定性重复任务 | 探索性、模糊性问题 |
|
|||
|
|
|
|||
|
|
> [!WARNING]
|
|||
|
|
> 不要给 ReAct 过大的最大迭代次数。实验表明,超过 8 轮后错误率开始反弹——因为早期的错误被不断累积进历史,污染了后续推理。8-10 轮是经过验证的黄金区间。
|
|||
|
|
|
|||
|
|
### 防无限循环技巧
|
|||
|
|
|
|||
|
|
除了硬性的 max_iterations,还可以加入更智能的保护:
|
|||
|
|
|
|||
|
|
1. **Token 用量监控**:如果单轮 token 消耗突增 3 倍,说明 LLM 可能陷入冗长循环,强制中断
|
|||
|
|
2. **去重检查**:如果当前 Step 的 Action 和前两轮完全相同且结果也一样,说明卡死,换工具或重试
|
|||
|
|
3. **Entropy 监测**:计算 Thought 文本的 perplexity,长期过高表示 LLM 失去方向感
|
|||
|
|
4. **人工介入网关**:超过一定复杂度自动请求 human-in-the-loop 确认下一步
|
|||
|
|
|
|||
|
|
## 实践场景
|
|||
|
|
|
|||
|
|
**ThumbUP 项目中的迭代优化:**
|
|||
|
|
|
|||
|
|
ThumbUP 的缓存策略选择就是一个典型的 ReAct 决策过程:
|
|||
|
|
1. Thought: 单一缓存层在高 QPS 下有击穿风险
|
|||
|
|
2. Action: 调研业界方案
|
|||
|
|
3. Observation: 二级缓存 + HeavyKeeper 热点探测是主流做法
|
|||
|
|
4. Thought: 需要验证多级缓存的一致性开销
|
|||
|
|
5. Action: 模拟测试
|
|||
|
|
6. Observation: 一致性协议增加了 15ms 延迟
|
|||
|
|
7. Reflection: 权衡后发现利大于弊,采用该方案 → 达成结论
|
|||
|
|
|
|||
|
|
这个过程中,每一步都依赖上一步的观察结果做出调整,而非预先写死的流程分支。
|
|||
|
|
|
|||
|
|
## 扩展阅读
|
|||
|
|
|
|||
|
|
- [[Eino DAG 工作流设计]]
|
|||
|
|
- [[上下文工程与记忆管理]]
|
|||
|
|
- [[沙箱权限治理]]
|