← 文章 / AI技术
n8n 6小时前 · 2026-09-02 15:33:10 · 5 阅读

超越 Prompt 工程:构建长时间运行的 Agent

你们太依赖 prompt 来推进 agent 设计了。无论把它叫作上下文工程,还是见鬼的循环工程,本质上都是在要求 LLM 确保只生成事实信息,并自行检查这些信息。

越是让 LLM 自我评估或自行分析,就越容易引入幻觉和偏移。

用文本生成器自动处理那些你不想亲自做的工作时,仍然要把它当作普通软件来对待,并设计好它的运行逻辑。

在讨论长时间运行的 agent 之前,先记住以下几点:

  • 短时间运行的 agent 完全没问题。解决问题时,优先使用最简单的工具。
  • 要区分模型和 agent。模型接收文本输入并生成文本输出,除此之外什么也不做;agent 才是负责执行的部分。agent 依赖模型。例如,模型在生成 JSON 对象的过程中触及 token 上限,这是模型层面的事件(输出被截断),但会在 agent 层面造成后果(工具调用格式错误、状态写入失败、账本条目损坏)。

这一系列文章讨论的是 harness。LLM 调用可以被用作工具(比如把 40 个工具调用结果总结成 200 字),但它应该属于 harness 决定执行的确定性函数调用:由 harness 按自己的调度执行,并自行验证输出,而不是 agent 在任务进行到一半时突然想起来:“天啊,我现在真该压缩一下上下文了。”

第 1 部分:上下文与记忆

每次向 LLM 发送 prompt 时,都会重新发送完整的对话历史。类似聊天的体验只是 UI,是一种用户幻觉™。这意味着,随着对话变长,你可能会耗尽上下文窗口,导致模型开始截断内容;同时也可能出现上下文腐化和偏移,让模型逐渐偏离主题。

由于你始终发送完整的对话,上下文管理反而更容易了。你可以在窗口范围内整理上下文,解开藏在深处的死结,并尽可能提高信息密度。即使你获得了价值一百万美元的——抱歉,是一百万 token 的上下文窗口,仍然无法避免上下文腐化和偏移。

这也意味着,你可以接手一个 agent 的上下文,把它交给另一个 agent,然后继续会话。这样一来,就能丢弃思考 token 和工具调用的额外开销,只保留语义上真正相关的内容。

上下文也有自己的生命周期。它会从系统提示词、工具定义、用户提示词开始,逐步加入对话内容、推理过程、工具调用等信息。因此,在思考如何管理这些长时间运行的会话时,也应该从整个生命周期的角度出发。

创建并理解上下文

使用 LLM 的过程中,上下文会自然构建起来。真正需要有意识管理的是:要清楚哪些内容进入了上下文窗口。Gumloop 提供了一个很实用的 Context Usage Meter,可以实时显示一次对话占用了模型上下文窗口的多少空间,并按以下类别拆分 token:

  • 系统
  • AI 指令
  • 能力
  • 工具
  • 技能
  • 子代理
  • 对话

对于长时间运行的 agent,你会发现对话部分不断增加,而系统提示词和工具定义基本保持不变。

压缩上下文

随着上下文不断累积,可以删去语义上无关的 token,也可以把较大的内容压缩成语义等价但更短的摘要。

Google 的 ADK Context Compaction 会总结 agent 工作流事件历史中较早的部分,从而缩小上下文规模。它采用滑动窗口,在一次会话中收集并总结 agent 工作流事件数据。当当前会话中的工作流事件或调用次数达到特定阈值后,系统就会对较早的事件进行摘要。

但不能一直靠总结旧上下文来解决问题。到了某个阶段,运行框架应该执行一次完整的上下文重置:销毁当前会话,并根据持久化产物重新构建下一次请求。这些产物将在下一节介绍。

存储上下文

LLM 是无状态的,因此如果上下文只是临时存在,整个 agent 会话都会丢失。不过,你可以把上下文写入持久化存储。更进一步的做法是将其设计成不可变账本:agent 可以写入和读取,但不能修改或删除。

这些存储服务需要由开发者自行配置,并以确定性的方式定义权限。对 LLM 说“请永远不要更新账本”,不叫软件工程。

具体存储哪些内容由你决定:可以保存整个上下文窗口,也可以保存摘要,或者只记录 H1 标题等。

在 Google 的 EAP 中,memory generation 将其中几项决策整合到了一起。

  • 提取只会从源数据中筛选出最有意义的信息,并将其持久化为记忆,而不是把所有内容一股脑存进去。
  • 整合会把新提取的信息与已有内容合并,让记忆随着新信息的输入不断演进。
  • 生成在后台异步执行,Agent 无需等待其完成。
  • 事件摄取会持续接收并管理对话事件,并根据你配置的批处理规则自动触发生成。
  • 提取过程还支持自定义。你可以通过指定主题和提供少量示例,告诉 Memory Bank 哪些内容才算有意义。

召回上下文

有了持久化的上下文,你可以把账本用作重建机制。开启新会话时,Agent 可以读取持久化计划、进度记录,以及按追加方式保存的历史事件,在不重放完整对话历史的情况下,还原“我现在进行到哪一步了”。

Cloudflare 的说法是,你可以裁掉 20% 的员工,用 AI 取而代之,并把 Agent 的计划作为上下文。如果 Agent 拥有结构化计划,那么计划本身就能提供足够的上下文:“我现在处于 7 个步骤中的第 3 步,这一步是‘等待制裁检查结果’,而结果刚刚到达。”

Google 的 EAP 同样提供了另一组由托管存储和检索带来的功能。

  • 整合和检索按身份隔离,因此一个实体的记忆不会泄漏到另一个实体。
  • 存储具有持久性,可从多个环境访问,包括 Agent Runtime、本地环境或其他部署环境。检索可以限定在特定身份内进行相似度搜索,只提取相关内容,而不是全部内容。
  • 可以设置生存时间(TTL),让过时信息自动过期;TTL 适用于插入或生成的记忆。
  • 修订版本会自动保存,你可以查看一条记忆如何随着新信息的输入而变化。
  • IAM 条件可以限制哪些主体能够读取或写入指定作用域中的记忆。

将身份视为上下文管理的一部分

你可以检索属于特定用户身份的全部记忆。记忆的作用域会在生成或创建时确定,并且不可修改。

这样一来,按身份划分的记忆就能在同一用户或实体的不同任务和会话之间持久保存。在 Google 的 Agent Memory Bank 中,系统会通过 LLM 从会话事件中提取并整合记忆,将其关联到用户或 Agent 身份,再通过相似度搜索进行检索,同时利用基于 TTL 的过期机制和修订历史进行管理。

在 Cloudflare 的实现中,Identity 是 Agent 持久且可寻址的属性。Durable Object 的身份会在休眠或重启后继续存在,无需重新建立;Agent 发生崩溃后,正是通过这一锚点重新找到任务状态和身份记忆。

要实现这种持久性,还需要额外的机制来确保实例之间以及故障点前后的连续性。下面将对此展开讨论。

第二部分:持久化执行

在完成上下文管理后,你现在已经有了一些持久化存储。很好——会话丢失时,可以用它把上下文重新加载回 Agent。

“嘿,Claude,帮我把这个取回来,然后从刚才——”

不行!

我们要讨论的是:发生故障时,如何以确定性的方式恢复 Agent。

长时间运行的 Agent,并不是一直持续执行。它只在需要时运行,同时记录任务、上下文以及此前的上下文。大多数时候,Agent 都是在等待。但正如第一部分所述,LLM 每次收到 Prompt 时都会获得完整的对话请求,因此,所谓持久性,归根结底就是:如何有效组织这些数据,让 LLM 在不同任务之间保持一致的行为。

哪些内容需要持久化,哪些不需要

回到 Cloudflare 的文章,其中介绍的长时间运行 Agent 模式会确保以下内容在多次调用之间持久存在:

  • Agent 状态,即 Agent 继续会话所需的持久化数据
  • 创建的所有 SQLite 表,包括基于 SQLite 构建的抽象层
  • 计划任务:它们存储在 SQLite 中,并通过触发 alarm 唤醒 Agent
  • 每个 WebSocket 客户端的连接状态

以下内容则无需持久化:

  • 内存中的变量
  • 正在运行的计时器
  • 尚未结束的 HTTP 调用
  • 回调和 Promise 链

Agent 需要具备身份和持久化状态,但不需要一个始终运行的计算实例。你可以将其配置为在事件发生时唤醒,并在任务完成后再次休眠。唤醒源构成了 Agent 与外界交互的完整接口;无需通过始终运行的循环,让 Agent 连续数周保持“运行”状态。

唤醒 agent 的一种方式是使用 webhook 回调:agent 启动外部任务,注册自己的回调 URL,然后进入休眠,直到收到回调后再被唤醒。对于不支持回调的服务,也可以配置带退避机制的轮询:agent 发起一次轮询,随后以逐渐增加的间隔重新安排下一次轮询,并为间隔设置上限。

此外,还可以定义覆盖范围更广的自动化工作流,将包含多个步骤且各步骤可独立重试的流水线交给专门的工作流引擎处理,而不是在 agent 内部管理步骤顺序。

Sub-agent 也能以各自独立的方式获得同样的持久化能力。每个子 agent 都拥有自己的状态、调度任务、持久化 fiber 和生命周期,并将自身数据与父 agent 的数据存放在同一层级结构下。

持久化的关键在于:父 agent 无需在子 agent 工作期间持续运行。它可以启动任务后进入休眠,等子 agent 的调度任务或恢复检查触发时再被唤醒。即使发生崩溃,也不会导致整个 agent 家族同时中断;每个身份都可以独立恢复。

将 Token 和速率限制监控作为预测信号

通过监控每个会话和租户的 Token 消耗情况,可以针对触发速率限制或错误时的情形定义续作和重试策略。如果累计用量趋势显示,在下一个检查点之前可能触发速率限制,运行框架就可以提前创建检查点、降低请求速率,或切换模型/提供商。

恢复机制

最粗粒度的恢复方式,是让会话在启动时读取账本。即使没有更细致的策略,一个简单的“我进行到哪一步了”检查也能让任务继续执行。随后,可以在一项工作开始时持久化一条记录,在预先定义的节点保存中间状态,并在重启后从最近一次保存的状态恢复。

幂等性在这里非常重要,尤其是由 webhook 驱动的 agent:调用方可能会重试投递,因此必须避免重复产生副作用。支撑这一整套机制的通用模式,是事件溯源或基于日志的恢复:

前文介绍过一种不可变的任务账本,其中记录了 agent 的计划:应该发生什么、按什么顺序发生,等等。此外,还需要一份独立的只追加执行日志,记录实际发生的一切,包括每次工具调用、每次模型响应以及每次状态转换。无论由哪个进程或容器接手任务,都可以通过确定性重放这份日志来重建状态。

这正是 Restate 的 journal 功能以及 DBOS 的 workflow/step 注解所采用的底层思路。

Restate 会在执行下一步前持久化当前步骤,并通过确定性重放重建崩溃前的状态;DBOS 则将检查点写入 Postgres。

这些工具还为 agent 提供补偿和回滚机制,以便在发生故障时撤销部分操作。Agent 往往需要执行多个动作,一旦中途出错,就必须系统性地回退已产生的变更,才能保持一致性。

持久化执行服务商概览

可以通过考察 DBOS、Restate、Inngest 等服务商,了解不同的实现思路。常见能力包括:

  • 基于外部事件暂停与恢复——提供一流的工作流暂停原语,让流程等待 webhook、审批或人工决策等信号到达后再继续执行;即使期间重新部署,也能在之后恢复,而且等待时无需持续占用计算资源。
  • 流程控制与并发限制——提供持久化队列,并支持按租户或工作流设置并发上限和速率限制,避免重试与扇出操作压垮下游系统。
  • 将持久化能力融入应用代码,还是交给独立的编排层:有些方案通过语言库或注解,让普通控制流自动具备持久化能力;另一些则将其作为托管平台原语,并采用独立的执行模型。

你也可以在 n8n 中自行设计持久化执行逻辑:利用其基于确定性工作流的功能定义重试机制,将数据写入持久化存储,并使用 schedule、webhook 等确定性触发器。

身份也是持久化能力的一部分

在 Cloudflare 的模型中,agent 名称就是路由键;无论休眠、重启还是重新部署,这一身份都会持续存在。凭证按 agent 或 sub-agent 身份进行隔离。持久化带来的另一个好处是可追溯性:由于身份本身具备持久性,执行日志中的每条记录都能关联到一个持久化的 agent 身份。

在平台层面,Google 的 Agent Identity and Registry 就是这一理念的产品化实现,用来追踪由哪个身份、以哪个版本执行了哪项任务。

第 3 部分:任务推进与评估

你会发现,agent 会逐渐出现错误和幻觉。要判断它是否仍在完成原定任务,而不是直接问它,大多数 AI 工程师想到的“完美”方案是:再问另一个 LLM。

呃……

让 LLM 充当评判者,是最容易实现、也最不可靠的解决方案。因为这只是让同类模型以同样的方式犯错,只不过多绕了一层。本文关注的是:尽量减少进度验证对任何 LLM 判断的依赖;而在确实需要 LLM 的地方,则把它限制在范围狭窄、结果可检查的角色中。

以 checklist 作为进度单位

在任务台账或 checklist 中,每一项都必须在 agent 执行前定义好完成标准。提前写清楚什么才算完成,是最有效的做法,因为这样可以防止 agent 在执行过程中自行重新定义“完成”。

配套原则是一次只处理一项。每轮只完成一项,可以避免 agent 试图同时处理所有事情,最后每项都只做到一半;同时也让验证变得可控,因为每轮只包含一个需要检查的状态变化。

确定性的验证门

与其“问模型是否完成”,不如直接检查执行日志或系统当前状态,并返回一个独立于模型调用的布尔值。

下面按成本和可靠性从低到高,列出几类验证门:

  • 状态码/响应码:API 调用是否返回 200,而不是 4xx/5xx。
  • Schema 验证:响应能否解析为有效的 JSON/XML,并符合预期结构(必需字段齐全、类型正确)。
  • 跨字段一致性检查:响应中的用户名是否与请求用户的身份一致;返回的 ID 是否与请求中的 ID 匹配。
  • 状态差异检查:步骤声称创建、更新或删除的对象,是否确实在目标系统中出现、发生变化或消失(执行操作后重新查询)。
  • 测试执行:针对代码变更运行单元测试或集成测试。

其他用于验证操作的机制还包括:

用状态机确保流程合规推进——在语义解析中,有限状态机(FSM)会将工具调用或步骤类型定义为状态,将允许的状态顺序定义为转换;运行时,再通过 FSM 解析实际的操作序列。机器之外的状态属于违规,意外的状态转换则属于异常。由于合法状态集合规模较小且固定,每次转换在写入执行账本前,都可以对照执行日志进行验证。

沙箱与行为基线——也可以先让 agent 在受控环境(即沙箱)中运行,观察它的实际行为:调用了哪些工具、传输了多少数据、访问了哪些目标、执行了哪些系统调用。然后可以:

  • 对于已确认安全的具体操作,再逐步放行到生产环境;最好将其固化为确定性的允许列表条目,而不是再次依赖 agent 自行判断;
  • 将这份行为画像作为最小权限配置和偏差告警的参考基线。

针对 agent 行为的非生成式检查——有些检查完全不需要生成文本。仅编码器分类器(例如 DeBERTa、RoBERTa 和 ModernBERT 等 BERT 系列模型)可以基于标注的对齐/偏离或良性/恶意样本进行微调,并输出一个与阈值比较的标量结果,从而在不引入生成式模型的情况下直接给出判定。

检测工具调用模式中的异常——监控递归循环(反复调用同一工具,只对参数做细微变化)、token 数量突然激增以及乱序执行,并在这些问题不断累积前终止运行。

合理使用 LLM-as-judge

只有在执行之前明确定义了意图,才能对意图进行评估。

“明确”包括:列举清楚的工具允许列表;任务必须经过的步骤或状态序列;指定的数据源;基于规则定义的子 agent 生成机制;以及明确的 API 端点和方法。

这些定义完成后,评估就可以归结为一组针对执行日志的“是/否”问题:这一步是否执行、字段是否正确填写、调用是否返回预期代码、检索到的数据是否通过 schema 校验,等等。

如果确实要用 LLM 做评估,就应将任务限定为范围狭窄、结果可核验的判断。例如:“这条执行轨迹是否符合分类体系中的 X 类?”之所以可以接受,是因为此时模型扮演的是一种模糊编译器:将观察到的行为映射到确定的类别,而不是临时臆造什么叫“好”。

实现

本文讨论的内容会涉及多个产品,也需要持续的工程投入。不要把它当作构建可靠长期运行智能体的分步指南或通用框架;更准确地说,这是一份对确定性组件的探索,而这些组件往往被那些自称循环工程专家的人忽略。

我会继续探索这些主题,以及它们在 n8n 和更广泛市场中的应用。如果你有改进建议、反馈或发现错误,欢迎在 LinkedIn 上联系我。

分享给我们

n8n 用户来自不同背景,拥有不同的经验和兴趣。我们一直希望通过博客文章介绍各类用户及其项目。如果你正在使用 n8n,并希望为社区带来启发,欢迎联系我们 💌

分享
原始来源: n8n

评论 (0)