芥末
发布于 2026-06-16 / 3 阅读
0
0

从 Prompt 到 Agent Loop:让 AI Agent 稳定工作的循环设计方法

很多人使用 AI Agent 时,第一反应仍然是“这次 Prompt 怎么写”。这种思路适合一次性问答,却很难支撑复杂任务:代码要改、测试要跑、错误要分析、文档要更新、结果还要验收。只靠人工一轮轮补充指令,本质上还是人在手动推动流程。

Agent Loop 要解决的是另一类问题:让 AI Agent 在明确目标、工具权限和护栏约束下,自己完成“观察状态 → 制定动作 → 调用工具 → 接收反馈 → 修正下一步”的循环。

它的关键不在于把 Prompt 写得更长,而在于把工作设计成一个可运行、可验证、可收敛的系统。

flowchart TD
    A[目标 Objective] --> B[组装上下文 Context]
    B --> C[模型推理 Reasoning]
    C --> D[选择动作 Action]
    D --> E[调用工具 Tools]
    E --> F[观察结果 Observation]
    F --> G{是否满足停止条件}
    G -- 否 --> B
    G -- 是 --> H[输出结果 Result]
    G -- 超预算/无进展 --> I[安全停止 Guardrail Stop]

这套循环把 AI 从“回答问题的聊天窗口”推进到“能执行任务的自动化单元”。不过,循环一旦跑起来,也会带来新的工程问题:什么时候该停,失败信息怎么复用,多个 Agent 如何分工,记忆给多大会失焦,怎样防止一本正经地编造事实,成本如何控制。

这些问题都不能只靠“写好 Prompt”解决。

Agent Loop 解决的是什么问题

一次性 Prompt 的典型流程是:

sequenceDiagram
    participant 人
    participant AI

    人->>AI: 说明任务
    AI-->>人: 给出结果
    人->>AI: 补充错误、日志、要求
    AI-->>人: 修改结果
    人->>AI: 再补充约束
    AI-->>人: 再修改

这种模式的问题很明显:上下文靠人复制,错误靠人转述,任务是否完成靠人判断。只要任务链条稍长,协作成本就会迅速上升。

Agent Loop 把这些人工动作工程化:

环节 一次性 Prompt Agent Loop
目标表达 人写一段自然语言 目标 + 可验证完成条件
上下文 人手动粘贴 根据当前状态动态组装
工具调用 AI 建议,人执行 Agent 按权限调用工具
失败处理 人重新描述错误 错误日志进入下一轮输入
验收方式 人主观判断 测试、规则、检查器共同判断
停止方式 人觉得差不多 满足条件、超预算或无进展停止

所以,Agent Loop 的工程核心可以概括成一句话:

不要只设计“AI 应该说什么”,要设计“AI 如何持续获得状态、采取动作、利用反馈,并在正确的时间停下来”。

Agent Loop 的基本组成

一个可落地的 Agent Loop 通常包含七类组件。

组件 作用 典型实现
Automations 自动触发循环 定时任务、Webhook、CI 事件、队列消息
Worktrees 隔离执行环境 Git worktree、临时目录、容器、沙箱
Skills 可复用操作手册 Markdown 规程、命令模板、代码生成规范
Plugins / Connectors 外部工具连接 GitHub、Slack、MCP(模型上下文协议)、数据库、浏览器
Sub-agents 分工或验收 生成器、审查器、事实检查器、测试修复器
Memory 外挂记忆 Markdown、向量库、关系数据库、事件日志
Guardrails 资源与行为护栏 最大轮数、预算上限、权限控制、无进展检测

这些组件不是越多越好。一个小型代码修复 Agent,可能只需要目标、文件读写工具、测试命令、最大轮数和停止条件;一个长期运行的研发助手,才需要完整的记忆、技能沉淀、多 Agent 验收和成本控制。

停止条件:先定义“什么叫完成”

Agent Loop 最容易失控的地方,是目标说得太软。

“把代码改好”不是一个好的停止条件,因为模型可能把“看起来合理”误认为“已经完成”。更稳的写法是:

目标:
修复用户登录接口在空密码输入时返回 500 的问题。

停止条件:
1. 新增一个能复现该问题的测试;
2. 修复后该测试通过;
3. 原有登录相关测试全部通过;
4. 修改范围不包含无关模块。

停止条件要尽量具备三个特征:

特征 说明 示例
可观察 Agent 能拿到结果 测试输出、Lint 结果、HTTP 状态码
可判断 不依赖模糊感受 exit code 为 0、断言通过
可收敛 不会无限追求更好 最多修改 N 个文件、最多运行 M 轮

代码、数据处理、构建发布这类任务,通常都能写出硬停止条件。麻烦的是软目标,例如“写得更清楚”“方案更稳妥”“周报让管理者满意”。这种目标很难完全自动验收,可以用“分级完成”降低误判风险。

目标类型 停止条件写法 适合做法
硬目标 测试、命令、断言通过 自动停止
半软目标 检查清单全部满足 自动初筛 + 人工确认
软目标 明确不可自动验证的部分 输出置信等级和待确认项

例如,让 Agent 做方案评审时,不要让它简单说“方案可行”,而是要求它输出:

结论等级:
- 可验证事实:哪些信息来自代码、日志、数据或文档;
- 推理判断:哪些结论是基于工程经验推断;
- 待确认问题:哪些点需要人或外部系统确认。

这样不能把软目标变成硬目标,但能防止 Agent 假装自己已经完成了无法验证的判断。

Context 不是写出来的,而是组装出来的

传统 Prompt 是一段固定文字。Agent Loop 中的 Context(上下文)更像一个动态构建结果:每一轮都根据当前状态重新生成。

一个代码修复循环的上下文可能包含:

- 用户目标
- 当前任务状态
- 最近一次失败日志
- 本轮允许调用的工具
- 相关文件摘要
- 已尝试过的方案
- 停止条件
- 安全约束

动态上下文的价值在于,Agent 不需要靠“记住所有对话”工作,而是每一轮都拿到当前最相关的信息。

flowchart LR
    A[任务目标] --> F[Context Builder]
    B[文件树摘要] --> F
    C[失败日志] --> F
    D[历史尝试] --> F
    E[相关记忆] --> F
    F --> G[下一轮 Prompt]
    G --> H[Agent 动作]
    H --> I[新状态]
    I --> F

Context Builder(上下文构造器)是 Agent Loop 的核心部件之一。它决定了模型看到什么、不看到什么,以及以什么顺序看到。

一个常见原则是:稳定规则放前面,动态状态放中间,最新错误放后面。因为最新错误通常最影响下一步动作,而稳定规则负责约束行为边界。

[角色与边界]
你是代码修复 Agent,只能修改与当前 bug 相关的文件。

[任务目标]
修复登录接口空密码输入返回 500 的问题。

[停止条件]
新增复现测试;修复后所有登录测试通过。

[当前状态]
已新增测试 test_empty_password_returns_400,目前失败。

[最近失败日志]
AssertionError: expected 400, got 500

[允许动作]
读取文件、修改文件、运行指定测试。

Context 不是越大越好。塞入太多历史记录会让模型失焦,甚至被无关信息带偏。工程上更可靠的做法,是按相关性、时间和层级筛选。

失败是下一轮输入,不是流程终点

在普通程序里,错误通常意味着流程停止。在 Agent Loop 里,错误更像新的观测数据。

比如测试失败后,不应该只告诉 Agent“继续修”,而应该把结构化失败信息放回下一轮:

{
  "last_action": "run_tests",
  "command": "pytest tests/test_login.py",
  "exit_code": 1,
  "error_summary": "空密码输入时接口返回 500,而不是 400",
  "stack_trace": "...",
  "changed_files": [
    "app/auth.py",
    "tests/test_login.py"
  ]
}

这样做有两个好处:

  1. Agent 能基于真实反馈修正,而不是凭想象继续改。
  2. 循环系统可以判断是否“有进展”,避免反复尝试同一种错误方案。

无进展检测可以非常简单。例如连续三轮测试失败原因相同、修改文件相同、错误栈相同,就触发停止,把问题交给人处理。

flowchart TD
    A[执行动作] --> B[得到反馈]
    B --> C{反馈是否变化}
    C -- 有变化 --> D[继续循环]
    C -- 无变化 --> E[记录重复失败]
    E --> F{达到重复阈值}
    F -- 否 --> D
    F -- 是 --> G[停止并请求人工介入]

Agent Loop 不应该追求永远自动解决问题。稳定的系统要能识别“继续烧 token 也没意义”的时刻。

多 Agent 编排的六种拓扑

多 Agent 不是简单地多开几个模型实例。真正重要的是拓扑,也就是它们如何分工、通信和合并结果。

拓扑 工作方式 适合场景 主要风险
顺序流水线 A 的输出交给 B,B 交给 C 文档生成、数据清洗、发布流程 前一步错误会传染到后面
协调者-工作者 一个协调者拆任务,多个工作者执行 大任务拆解、批量修改 协调者判断错会整体跑偏
扇出合并 多个 Agent 并行处理,再合并 方案征集、代码审查、候选生成 合并器可能偏向表达更自信的一方
生成-验证 一个生成,一个检查 代码生成、事实核查、格式审查 同模型盲区会重合
共享状态 多个 Agent 读写同一任务状态 长期项目、复杂协作 状态污染、覆盖、冲突
辩论对抗 正反双方互相挑战 风险分析、架构取舍 可能一起走向错误共识

用图表示,常见的“生成-验证”拓扑是这样:

flowchart LR
    A[任务目标] --> B[Maker 生成方案]
    B --> C[Checker 检查方案]
    C --> D{是否通过}
    D -- 通过 --> E[输出]
    D -- 不通过 --> F[反馈问题]
    F --> B

Maker-Checker(生成者-检查者)模式听起来很稳,但有一个隐蔽问题:如果两个 Agent 使用同一个底层模型,它们可能共享同样的盲区。生成者看不出的 off-by-one 错误,检查者也可能看不出来。

更稳的验收方式应该尽量引入异质信号:

检查对象 更可靠的 Checker
代码正确性 单元测试、属性测试、静态分析
链接真实性 HTTP 请求、搜索接口、白名单源
数据结论 SQL 查询、日志、指标平台
安全边界 规则引擎、权限系统、人工审批
文案质量 多 Agent 评审 + 人工终审

LLM 适合做语义审查、完整性检查和风险枚举,但不应该独自承担所有验收职责。只要能用程序验证,就优先交给程序。

记忆:分层,而不是全塞进去

Memory(记忆)用于突破上下文窗口限制,让 Agent 能利用长期信息。问题是,记忆太小没用,记忆太大会让 Agent 被历史噪声干扰。

一种实用设计是三层记忆:

层级 内容 用途
L0 一句话摘要 快速判断是否相关
L1 几条关键结论 放入常规上下文
L2 完整记录 只有高相关时展开

例如一次事故复盘可以这样存:

# memory/login-empty-password-500.md

## L0
登录接口遇到空密码曾因未校验输入触发 500,正确行为是返回 400。

## L1
- 问题出在 auth service 调用 hash 前没有检查空字符串。
- 测试用例应覆盖空密码、缺失密码、正常密码三类输入。
- 修复时不要改动 token 生成逻辑。

## L2
完整日志、提交 diff、测试输出、复盘记录……

检索时先看 L0,相关再展开 L1,只有需要证据时才读取 L2。

flowchart TD
    A[当前任务] --> B[检索 L0 摘要]
    B --> C{是否相关}
    C -- 否 --> D[忽略]
    C -- 是 --> E[读取 L1 结论]
    E --> F{是否需要证据}
    F -- 否 --> G[放入上下文]
    F -- 是 --> H[展开 L2 全文]
    H --> G

记忆还需要治理,否则会变成污染源。错误结论、过时方案、一次性临时信息如果长期保留,会让 Agent 在未来任务中反复踩坑。

记忆系统至少要记录四类元数据:

元数据 作用
来源 这条记忆来自日志、代码、人工输入还是模型推断
时间 判断是否可能过期
置信等级 区分事实、推断和待确认信息
适用范围 防止跨项目、跨版本误用

Guardrails:资源护栏和认知护栏要分开

Guardrails(护栏)不只是不让 Agent 做危险操作,也包括防止它无限循环、烧光预算、污染记忆、编造事实。

可以把护栏分成两类:

类型 解决的问题 适合放在哪里
资源类护栏 成本、轮数、权限、超时 Loop 框架底层
认知类护栏 胡说、失忆、错误记忆、虚假引用 可插拔治理层

资源类护栏应该尽量写死,因为它们是系统安全边界。例如最大循环次数、最大 token 预算、禁止访问的目录、禁止执行的命令,都不应该依赖 Agent 自觉遵守。

认知类护栏变化更快,适合做成可迭代的规则层。例如发布前检查清单、事实核查策略、记忆写入审批、失败转技能规则,都可能随着业务变化调整。

一个最小可用的护栏配置可以长这样:

loop:
  max_iterations: 8
  max_tokens: 80000
  timeout_minutes: 20

permissions:
  allow:
    - read_files
    - edit_files
    - run_tests
  deny:
    - delete_repository
    - access_secrets
    - deploy_production

review:
  require_tests: true
  require_source_check_for_external_claims: true
  require_human_approval_for_memory_write: true

这里的重点不是 YAML 格式,而是把“Agent 可以做什么、不能做什么、什么时候必须停”变成系统配置,而不是藏在一段自然语言里。

治理层:让 Loop 跑得稳一点

Agent Loop 解决的是“让 Agent 自己转起来”。但循环跑起来之后,还需要一层治理机制处理三个问题:

  1. 不胡说:外部事实、链接、数据结论必须可核查。
  2. 不失忆:关键经验能沉淀下来,并在需要时被检索。
  3. 不重复踩坑:失败要转成可复用规则,而不是只存在一次对话里。

可以把这层称为轻量治理层。它不替代 Agent 框架,也不替代工具系统,只是在循环外面包一层白盒约束。

flowchart TD
    A[Agent Loop] --> B[执行任务]
    B --> C[生成结果]
    C --> D[治理层检查]
    D --> E{是否通过}
    E -- 是 --> F[发布/提交]
    E -- 否 --> G[返回问题清单]
    G --> A

    D --> H[事实核查]
    D --> I[记忆检索]
    D --> J[失败转技能]

轻量治理层可以包含三个模块。

模块 作用 典型规则
白盒记忆治理 管理长期经验 记忆分层、来源标注、写入审批
发布前自检 防止错误输出 链接检查、数据出处、未验证声明标记
失败转技能 减少重复错误 每次失败沉淀一条可复用规则

例如,发布前自检可以要求 Agent 在输出前生成一份检查结果:

## 发布前检查

- 外部链接:已逐个访问验证 / 不涉及
- 数字和指标:已标注来源 / 不涉及
- 事实性结论:有证据支持 / 已标为推断
- 未验证内容:已列入待确认项
- 记忆写入:需要人工确认 / 不写入

这类检查不能保证完全正确,但能显著降低“模型把推断包装成事实”的概率。

软目标的处理:不要把评分器当真理

很多团队会尝试让另一个大语言模型充当 Judge(评判器),给结果打分,超过阈值就停止。这个方法能用,但不能过度信任。

LLM Judge 的问题在于评分可能漂移。同一份输出,在不同时间、不同采样参数、不同上下文下,分数可能变化很大。它更适合做排序、挑刺和辅助判断,不适合作为唯一停止条件。

更稳的做法是把软目标拆成检查清单:

任务:改写一份技术方案,让评审更容易理解。

可检查条件:
1. 是否说明背景问题;
2. 是否列出核心方案;
3. 是否给出架构图;
4. 是否说明风险和取舍;
5. 是否标出待确认事项;
6. 是否避免无来源的性能承诺。

然后让 Judge 只判断这些具体项,而不是判断“好不好”。

不稳定写法 更稳写法
给这份方案打 0 到 1 分,超过 0.8 就通过 检查是否包含背景、方案、风险、证据、待确认项
判断这段文字是否吸引人 判断标题是否具体、段落是否有信息增量、是否存在空话
判断方案是否值得做 列出收益证据、成本假设、风险、不可验证部分

软目标不能完全自动化时,就诚实地保留人工确认点。自动化系统最危险的状态,不是“不知道”,而是“不知道自己不知道”。

防止 AI 一本正经地胡说

Agent Loop 自动化程度越高,事实错误的危害越大。一次聊天中的幻觉可能只是一句错话;一个自动循环中的幻觉可能进入文档、代码注释、周报、配置甚至记忆库。

事实核查要尽量外部化,不要只让模型自查。

flowchart LR
    A[Agent 生成结论] --> B{是否包含外部事实}
    B -- 否 --> C[进入普通审查]
    B -- 是 --> D[检索或访问来源]
    D --> E{来源是否存在}
    E -- 否 --> F[标记为不可用]
    E -- 是 --> G{来源是否支持结论}
    G -- 是 --> H[保留并附来源]
    G -- 否 --> I[降级为推断或删除]

对外部事实可以使用三种策略:

策略 做法 适合场景
强制来源 每个事实性结论必须有来源 技术文档、研究报告、对外材料
白名单来源 只允许引用可信域名或内部系统 合规要求高的场景
结构化输出 把事实、推断、建议分字段输出 方案评审、事故分析、周报

例如输出可以强制分成:

{
  "verified_facts": [
    {
      "claim": "登录接口在空密码输入时返回 500",
      "source": "tests/test_login.py::test_empty_password"
    }
  ],
  "inferences": [
    "问题可能来自输入校验缺失"
  ],
  "unknowns": [
    "生产环境是否存在相同错误还需要查看日志"
  ]
}

这样即使模型仍然可能出错,错误也更容易被发现和拦截。

成本控制:多 Agent 和长循环必须精打细算

Agent Loop 的成本主要来自三件事:循环轮数、上下文长度、模型数量。多 Agent 并行后,成本会按倍数上升;如果每个 Agent 都带着长上下文反复推理,token 消耗会非常快。

成本优化可以从四层入手。

层级 优化方式 示例
模型分级 简单任务用便宜模型,关键判断用强模型 路由、摘要、格式检查用小模型
上下文压缩 只传必要信息 传错误摘要而不是完整日志
中间结果缓存 不把所有中间产物塞回 Prompt 文件、变量、数据库存储中间结果
循环限额 控制最大轮数和无进展重试 连续三轮无变化即停止

一个常见模型分级结构如下:

flowchart TD
    A[任务进入] --> B[便宜模型路由]
    B --> C{任务类型}
    C -- 简单格式处理 --> D[小模型执行]
    C -- 代码修改 --> E[强模型执行]
    C -- 风险判断 --> F[强模型 + 规则检查]
    D --> G[轻量检查]
    E --> H[测试验证]
    F --> I[人工确认]

不要把所有东西都放进 Prompt。中间产物如果能用文件、数据库、变量保存,就不要每一轮都重复发送。Prompt 里只放当前决策需要的信息。

例如:

不要传:
- 完整构建日志 20000 行
- 所有历史 diff
- 全部项目文件树

改成传:
- 失败命令
- 失败测试名
- 最相关的 30 行错误栈
- 最近一次 diff 摘要
- 可按需读取的文件路径

成本控制不是最后才做的优化项,而是 Loop 设计的一部分。没有预算边界的 Agent Loop,很容易从“自动化助手”变成“自动烧钱脚本”。

一个最小可用的 Agent Loop 模板

把前面的设计合起来,一个最小可用的代码修复 Loop 可以这样组织:

objective: 修复登录接口空密码返回 500 的问题

stopping_conditions:
  - 新增失败测试复现该问题
  - 修复后该测试通过
  - 登录相关测试全部通过
  - 未修改无关模块

context_builder:
  include:
    - task_objective
    - stopping_conditions
    - current_diff_summary
    - latest_test_failure
    - relevant_memory_l1
  exclude:
    - unrelated_history
    - full_logs_by_default

tools:
  - read_file
  - edit_file
  - run_tests

guardrails:
  max_iterations: 8
  max_tokens: 80000
  stop_if_same_failure_repeats: 3

review:
  checker:
    - unit_tests
    - static_rules
    - llm_review
  require_human_approval:
    - memory_write
    - production_deploy

对应的执行流程:

flowchart TD
    A[接收任务] --> B[生成复现测试]
    B --> C[运行测试确认失败]
    C --> D{是否复现}
    D -- 否 --> E[重新分析问题]
    D -- 是 --> F[修改实现]
    F --> G[运行相关测试]
    G --> H{测试是否通过}
    H -- 否 --> I[把失败日志写入下一轮上下文]
    I --> F
    H -- 是 --> J[运行审查清单]
    J --> K{是否通过}
    K -- 否 --> L[返回问题清单继续修正]
    L --> F
    K -- 是 --> M[输出结果]

这个模板不复杂,但已经覆盖了 Agent Loop 最关键的工程点:目标明确、上下文动态、失败反馈、工具执行、测试验收、护栏停止。

实际落地时最容易踩的坑

表现 处理方式
停止条件模糊 Agent 自称完成,但结果不可验证 把目标改成测试、命令、清单或人工确认点
Checker 同源盲区 两个 Agent 一起误判 引入测试、规则、外部检索或不同模型
记忆过载 Agent 被无关历史带偏 L0/L1/L2 分层检索,按相关性展开
护栏只写在 Prompt Agent 忘记遵守或绕开 资源护栏下沉到框架配置
事实未核查 生成不存在的链接、数据、引用 外部检索、白名单来源、结构化事实字段
循环太长 成本上升但结果无进展 最大轮数、重复失败检测、人工介入
拓扑过度复杂 多 Agent 比单 Agent 更混乱 从单 Loop 开始,只在瓶颈明确时拆分
中间结果全进上下文 token 快速膨胀 文件化、缓存化,只传摘要和索引

什么时候不适合用 Agent Loop

Agent Loop 不是所有任务的默认答案。以下场景直接用一次性 Prompt 或普通脚本更合适:

场景 更合适的方式
一次性小问题 直接问答
规则完全确定 普通脚本或工作流引擎
风险极高且难以回滚 人工执行,AI 只做辅助分析
缺少可观察反馈 先补日志、测试、指标
无法设置权限边界 不要让 Agent 自动执行

Agent Loop 适合的是“目标明确、过程可反馈、结果可验证、失败可修正”的任务。它不是让 AI 获得无限自由,而是把自由限制在一个可控循环里。

总结

Agent Loop 的核心能力,不是让 Prompt 更聪明,而是让 AI Agent 在工程约束下持续行动。一个稳定的 Loop 至少要回答六个问题:

  1. 目标是什么,什么叫完成;
  2. 每一轮上下文如何组装;
  3. Agent 可以调用哪些工具;
  4. 失败信息如何进入下一轮;
  5. 谁来验收结果;
  6. 超预算、无进展或高风险时如何停止。

只要这些问题没有设计清楚,多 Agent、长期记忆、自动化工具接得越多,系统越容易失控。反过来,如果停止条件、上下文、反馈、护栏和治理层都设计得足够清楚,Agent Loop 就能从“会聊天的模型”变成一个能稳定处理任务的工程系统。


评论