Skip to content

代理循环深入解析

代理循环是 go-code 执行引擎的核心。但将其描述为"循环"掩盖了它的真实本质:它是一个状态机,每次迭代代表定义良好的状态之间的转换,由模型通过 stop_reason 机制控制。

将代理循环理解为状态机,而不是简单的迭代,揭示了某些设计决策的原因以及它们如何有助于可靠性。

状态机视角

在执行的任何时刻,代理都处于定义好的状态之一。这些状态之间的转换由模型的响应决定:

┌─────────────────────────────────────────────────────────────────────────────┐
│                        代理状态机                                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│         ┌──────────────────────────────────────────────────────────────┐   │
│         │                                                              │   │
│         │    ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌───────┐ │   │
│         │    │ 请求    │───▶│ 等待    │───▶│ 响应    │───▶│ 决策  │ │   │
│         │    │ 状态    │    │ 状态    │    │ 状态    │    │ 状态  │ │   │
│         │    └─────────┘    └─────────┘    └─────────┘    └────┬────┘ │   │
│         │         ▲              │              │               │      │   │
│         │         │              │              │               │      │   │
│         │         └──────────────┴──────────────┴───────────────┘      │   │
│         │                              │                                  │   │
│         │                              │                                  │   │
│         │              ┌───────────────┴───────────────┐                 │   │
│         │              │                               │                 │   │
│         │              ▼                               ▼                 │   │
│         │    ┌─────────────────┐           ┌─────────────────────────┐     │   │
│         │    │  end_turn      │           │  tool_use               │     │   │
│         │    │  (终止状态)    │           │  (继续状态)             │     │   │
│         │    │                 │           │                         │     │   │
│         │    │ 返回结果给用户  │           │  1. 权限检查            │     │   │
│         │    └─────────────────┘           │  2. 工具执行            │     │   │
│         │                                    │  3. 添加结果到历史     │     │   │
│         │                                    │  4. 转换回请求状态     │     │   │
│         │                                    └─────────────────────────┘     │   │
│         │                                                              │      │   │
│         └──────────────────────────────────────────────────────────────┘      │   │
│                                                                              │
│    错误转换:                                                                │
│    ┌──────────────────────────────────────────────────────────────────┐     │
│    │  max_tokens ──▶ 返回带警告的截断响应                             │     │
│    │  max_turns  ──▶ 返回带循环终止警告                              │     │
│    │  error      ──▶ 返回错误给用户                                  │     │
│    └──────────────────────────────────────────────────────────────────┘     │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

stop_reason 作为状态转换

API 响应中的 stop_reason 字段不仅仅是状态代码 — 它是决定代理下一步行动的状态转换信号。每个可能的值代表不同的状态转换:

end_turn:终止状态

stop_reasonend_turn(或 stop_sequence)时,模型已完成其任务。响应包含给用户的最终消息,代理循环终止。这是一个终止状态 — 不会发生进一步转换。

这个状态代表模型对用户请求已完成的置信度。Harness 不会质疑这个决定 — 它信任模型的评估并返回结果。

tool_use:继续状态

stop_reasontool_use 时,模型已请求执行一个或多个工具来收集额外信息或执行操作。代理转换到工具执行:

  1. 权限门:每个请求的工具都会经过权限系统。如果任何工具被拒绝,其结果为错误,但循环继续。

  2. 并行执行:多个工具请求并行执行。模型可以同时请求几个操作,Harness 协调它们的执行。

  3. 结果捕获:工具结果(或错误)被捕获并添加到对话历史。

  4. 状态转换:工具执行后,代理转换回请求状态,在那里使用更新的历史构建下一个 API 请求。

max_tokens:截断状态

stop_reasonmax_tokens 时,响应因达到最大 token 限制而被截断。这不是失败 — 它是一个边界条件。Harness 返回部分响应并附带警告,允许用户决定是否继续。

max_turns:循环限制状态

当代理达到最大迭代次数(MAX_TURNS = 50)仍未完成时,它会终止并发出警告。这是一个安全边界 — 见下一节了解为什么存在这个。

为什么存在 MAX_TURNS

模型没有对时间、资源约束或迭代次数的内在理解。如果让它无限期运行,代理可能:

  • 重复调用同一工具进入无限循环
  • 在无效策略之间振荡
  • 消耗无限制的计算资源

MAX_TURNS 提供了一个硬性安全边界。在 50 次迭代后,即使行为不当的代理也会终止。这个数字是根据经验观察选择的:复杂任务很少需要超过 50 次工具调用,而简单任务在此限制之前就能完成。

该值可配置,允许需要更多迭代的用户增加限制,同时仍保持对执行时间的某些边界。

MAX_TURNS 时会发生什么

达到限制时,代理:

  1. 停止发出新的 API 请求
  2. 将累积的响应返回给用户
  3. 包含指示循环已终止的警告消息

用户然后可以决定是否手动继续任务或调整 MAX_TURNS 设置。

长对话的历史管理

对话历史不仅仅是日志 — 它是允许模型理解正在进行的任务的上下文。管理这个历史是 Harness 最关键的职责之一。

Token 预算问题

历史中的每条消息都消耗模型上下文窗口的 token。具有 50 轮对话、每轮 6 次工具调用可能轻松超过 100,000 token — 超过大多数模型的支持。

没有管理,代理将要么:

  • 在达到 token 限制时失败
  • 截断任务所需的关键上下文

三级压缩

go-code 实施了三级历史管理方法:

第一级:完整保留 — 对话早期消息完整保留。这些建立了任务上下文和初始推理。

第二级:选择性压缩 — 对话中间的消息被压缩成保留关键信息(工具调用、其结果、重要决策)同时删除冗长细节的摘要。

第三级:修剪 — 如果对话超过一定长度,旧消息被完全修剪,只保留最近的上下文。

这种分层方法确保模型始终可以访问:

  • 原始任务描述
  • 最近的工具执行及其结果
  • 当前工作状态

同时丢弃与当前任务不再相关的信息。

为什么压缩很重要

没有压缩,系统将限制在约 8-10 次工具调用,然后达到 token 限制。通过压缩,对话可以跨越 50+ 次迭代,同时保持足够的上下文让模型完成复杂任务。

压缩是有损的 — 某些信息不可避免地被丢弃。但这必要的权衡。替代方案(无压缩、限制对话长度)限制性更强。

权限门作为安全检查点

每个工具执行在执行前都经过权限系统。这不是可选的 — 它是保护危险操作的强制检查点

为什么是门而不是过滤器

过滤器允许危险操作通过,依靠模型避免它们。门主动拦截并阻止不符合配置策略的操作。

这个区别至关重要,因为模型以用户的完整权限运行。没有门,行为不当的模型可能:

  • 删除用户目录中的所有文件
  • 执行恶意 shell 命令
  • 窃取敏感数据

门架构

权限门在工具调用级别操作:

  1. 级别检查:根据用户权限级别(只读、工作区写入、危险完全访问)评估请求的工具。

  2. 路径匹配:对于文件操作,glob 模式确定是否允许特定路径。

  3. 操作评估:某些操作(如删除)被视为固有危险,可能需要额外批准。

  4. 用户提示:如果启用人工在环模式且操作需要批准,在执行继续之前提示用户。

  5. 决策执行:门返回批准或拒绝。如果被拒绝,工具不执行,并向模型返回错误结果。

模型看到什么

模型看不到权限门 — 它只看到工具结果。如果工具被拒绝,模型会收到指示操作被阻止的错误结果。然后模型必须决定如何继续:尝试不同方法、请求许可,或向用户报告问题。

流式传输和实时反馈

代理循环支持流式输出,允许用户在生成响应时看到模型的响应。这不仅仅是用户体验功能 — 它提供早期进度指示

为什么流式传输重要

没有流式传输,用户将默默等待完整响应到达 — 对于长输出可能需要许多秒。通过流式传输,用户可以:

  • 看到请求已被接收并正在处理
  • 观察长响应的部分进度
  • 如果看起来不对,可以中断操作

流式传输如何工作

API 客户端增量接收 token,并为每个 token 增量调用回调。Harness 使用此回调来:

  • 将文本输出流式传输到终端
  • 在工具执行期间显示进度指示器
  • 为用户提供实时反馈

循环本身继续处理完整响应 — 流式传输纯粹是显示优化。

状态机术语中的执行流程

1. 初始化
   - 创建新会话历史
   - 将用户消息添加到历史
   - 转换到: 请求

2. 请求
   - 如需要应用压缩
   - 构建 API 请求(消息 + 工具)
   - 发送请求到 API
   - 转换到: 等待

3. 等待
   - 接收流式响应
   - 缓冲完整响应
   - 转换到: 响应

4. 响应
   - 解析 stop_reason
   - 提取内容块
   - 转换到: 决策

5. 决策(状态分支)
   - end_turn    → 终止
   - tool_use    → 工具执行
   - max_tokens  → 终止(带警告)
   - max_turns   → 终止(带警告)
   - error       → 终止(带错误)

6. 工具执行
   - 对于每个 tool_use 块:
     a. 权限门检查
     b. 执行工具(带超时)
     c. 捕获结果(或错误)
   - 将所有结果添加到历史
   - 转换到: 请求

7. 终止
   - 格式化最终响应
   - 返回给用户
   - 执行结束

相关文档

基于 MIT 许可证发布