如果要在 Agent 开发里排一个最让人崩溃的现象,我大概率会把"重复漂移"排在第一位。所谓重复漂移,就是 Agent 在长任务执行过程中逐渐偏离原始目标,同时开始反复执行同一个动作——像一台卡了带的老式录音机,明明已经输出了十次一模一样的中间结果,它依然一本正经地继续调用工具、拼接上下文、生成下一轮"看起来合理"的输出。
我这么说的底气,来自上个月一次刻骨铭心的终局交付事故。我们小组负责开发一套面向数据自动化处理的 Agent 系统,原计划在周五完成最终验收,Agent 需要在无人值守的情况下完成数据清洗、指标计算、报告生成和异常复核四个环节。前两周联调测试都很顺利,结果就在交付前夜,Agent 在其中一个关键分支上陷入循环,连续产生了上百次重复的工具调用,直到触发资源上限进程才被强制中断。更头疼的是,模块没有任何自我修复机制。我们连夜排查、反复重启、调整参数,最后还是不得不人工接管,交付整整延期了两天。
事后复盘我们发现,问题并不出在某句提示词或某个工具调用的写法上,而是整个系统里缺少了一个关键底座——认知导航。这个概念当时还在我们心里只剩模糊的影子,等真正把它想清楚并落地之后,我们才意识到它对于应对 Agent 漂移和自愈缺失的价值有多大。
1. 事故现场:一台"复读机"式 Agent 是怎么把交付拖垮的
1.1 从日志里看到的诡异循环
先把事故现场还原一下。当晚我们正在执行一组面向客户日报的批处理任务,流程设计得像一条流水线:读取数据源 -> 清洗字段 -> 计算核心指标 -> 生成报告章节 -> 交叉复核。为了压缩时长,任务被拆成若干并发子任务,每个子任务由一个 Agent 角色独立推进。
问题出现在第 37 步左右。从日志上看,Agent 突然进入了一段"搜索-总结-再搜索-再总结"的循环。我把当时压平后的关键日志抽出来,大概长这样:
step=37 action=web_search query="最新季度营收数据" step=38 action=summarize result="检索到相关页面3个,摘要..." step=39 action=web_search query="最新季度营收数据" step=40 action=summarize result="检索到相关页面3个,摘要内容未发生显著变化..." step=41 action=web_search query="最新季度营收数据" step=42 action=summarize result="同上,未发现新增信息"单独看每一条日志,好像都说得通:它确实在搜索,确实在总结,下一轮也确实带着"更严谨"的意图继续搜索。但把 80 条日志放到一起看,就非常诡异了——搜索关键词几乎一样,总结文本几乎一样,连调用的工具参数都几乎一样。它就钉在同一个节点上,一遍一遍重复同一个动作,完全没有"这件事我已经做完、可以做下一步"的意识。
1.2 事故造成的实际影响
这次事故的代价,远比"延期两天"这四个字要大。
- 交付延期两天,客户验收窗口整体后移,连带后续排期全部被打乱。
- 部分中间数据文件在循环中被反复写入,产生了脏数据,回滚时又牵连了其他干净的分区。
- 人工复核工作量翻了数倍,团队三个成员轮流值守才把报告手工补齐。
- 最隐性的损失是信心:我们对"Agent 能不能在关键时刻稳定跑完"这件事产生了严重怀疑,后续好几周都不敢把复杂的无人值守任务交给它。
1.3 当晚的应急处理
当晚我们做了所有"常规"操作:先重启任务,发现照样卡在同样的位置;再回滚数据到半小时前,重跑一次之后依然循环;最后无奈之下人工接管,写了脚本手动完成剩余章节,才保住了次日早上的一份临时报告。
这一连串操作看起来很朴素,但背后暴露了一个事实:我们对 Agent 执行过程的监控和干预能力极其薄弱。除了"能看到日志"之外,系统对"Agent 是否在真正推进"这件事没有任何判断。那种感觉就像把车开了出去,却没有任何仪表盘——你只知道发动机在响,不知道车到底在不在往前走。
2. 为什么 Agent 会漂移:从生成的本质讲起
2.1 LLM 的每一步都是"局部最优的赌博"
要理解漂移,先得理解 Agent 的行动逻辑。Agent 的每一步动作,本质上都是 LLM 基于当前上下文做的一次概率采样。它不像传统程序那样,有一个确定性的状态转移函数——输入是什么、输出就是什么。它是从概率分布里"挑选"一个最合理的下一步。
这个机制带来一个很微妙的特性:每一步的合理性,都是局部的。前一步如果偏了,后一步就会在偏了的基础上继续推演,误差像滚雪球一样越滚越大。对 Agent 本身来说,它在第 37 步选择"继续搜索"的时候,并不真的知道第 20 步已经完成过同样的搜索,它只知道"当前上下文里,搜索看起来是一个合理动作"。
这也是为什么很多人在初步测试时觉得 Agent"挺聪明",一到长任务、复杂任务就原形毕露——短任务里偏差还没有机会累积,长任务里每步微小偏差叠加起来,足以让 Agent 彻底偏离航道。
2.2 漂移的三条成因链条
多年的项目交付经验让我意识到,Agent 漂移不是单一原因,而是几条因果链共同作用的结果。
其一,目标遗忘链。LLM 的注意力窗口有限,长任务执行到后期,最初的目标描述会被大量的中间指令、工具返回结果、历史输出淹没。模型实际上变成了"只看得到最近几轮对话"的近视眼,它确实想完成任务,但它对"任务到底是什么"的记忆已经模糊了。我们的日志里,Agent 在第 30 步之后,生成的中间结果里已经不再引用初始需求字段,这就是目标被遗忘的典型信号。
其二,上下文污染链。Agent 的每一步输出都会拼接到下一次推理的上下文里。如果前序步骤出现了错误字段、冗余信息或者误导性的中间结论,这些"污染源"会在后续每一轮被反复咀嚼,把决策越带越偏。更麻烦的是,LLM 分不清上下文里哪句是可信的、哪句是错的,它只会一视同仁地当作依据。
其三,循环自洽链。这是最阴险的一条。当 Agent 陷入循环时,它并不是无头苍蝇,而是每一轮都能给自己找到"继续做这件事"的理由——"还没找到需要的信息""结果可能不完整""再搜一次更稳妥"。在这种自洽逻辑下,它单看每一步都正当无比,但整体已经彻底失去推进能力。我们事后分析那 80 条日志,发现其中有 70 条的模型推理字段都在强调"需要进一步确认数据完整性",这就属于典型的循环自洽。
2.3 "单步正常"与"全局失控"的悖论
Agent 工程里最反直觉的地方,就是单个步骤完全正常,整体却在失控。传统程序的行为可预测性来自状态机:执行到哪一行、变量是什么、结果如何,每一步都有确定性的结果。Agent 完全不是这套逻辑,它每一步都是"生成式"的,天然没有全局一致性保障。
这个悖论也是"自愈"问题变得棘手的原因:如果出错的是工具调用参数,重试就行了;但如果出错的是"整个轨迹已经失去靶心",那重试一百次只是在同一个错误的道路上来回碾压。认知导航要解决的,恰恰是这个"全局靶心"问题。
3. "自愈"是个被严重低估的系统工程
3.1 自愈能力模型:并不只是"再试一次"
很多人一提自愈,第一反应是"出错了就重试"。这其实是把复杂问题过度简化了。真正的自愈,至少包含四件事:
- 感知:系统能够感知当前 Agent 状态,知道"我走到哪一步了"。
- 判断:系统能够判断这个状态是否偏离了预期轨道。
- 决策:系统能够决定用哪种方式回到轨道。
- 执行:系统能够生成纠偏指令并注入执行流程,让 Agent 真正回到正轨。
四件事缺一不可,而且每一件事在 Agent 场景下都不容易做到。感知难,因为 Agent 的输出是自然语言,没有一个结构化的"坐标"可以被直接读取;判断难,因为"偏没偏"很多时候是语义层面的,不是数值层面的;决策难,因为纠偏策略本身也是概率性的;执行难,因为它要把外部指令无缝嵌回 Agent 的上下文里,还不能打断整体流程。
3.2 为什么靠提示词调教实现不了自愈
在我接手这个项目之前,团队已经花了不少精力在提示词上做文章。比如在系统提示里写"请务必检查是否偏离目标""如果发现重复,请重新规划"。
这类提示词有没有用?有一点,但远远不够。原因在于 LLM 的自我评估并不可靠,它倾向于对自己生成的输出给出较高置信度。让它"自查"是否偏离目标,它大概率会回答"没有偏离,一切正常"——这不是它有意撒谎,而是它在生成的上下文里,缺少一把客观的尺子。
更深一层的问题是,循环中的每一步在 Agent 眼里都是"有正当理由的",所以用提示词检查"是否重复"时,模型会自动把重复解释为"谨慎"或"深挖",而不是"故障"。这就像让一个迷路的司机自己判断有没有迷路,他多半会说"没有,我只是在找更近的路"。
3.3 从自动驾驶说起:导航是比"更聪明的司机"更靠谱的工程解
那次事故之后,我反复在想一个问题:同样是复杂环境下的自主决策,为什么自动驾驶系统可以在高速上稳定跑几百公里?得到的答案很简单——因为自动驾驶不依赖"司机更聪明",而是依赖一套完整的外部基础设施:地图、GPS、传感器、车道线、实时路况。
这套基础设施里最核心的,是导航系统。导航系统并不负责"替司机踩油门打方向盘",它只做一件事:持续回答三个问题——车现在在哪里?要去哪里?偏航了没有?如果偏航,就重新规划路线。
我后来逐渐意识到,Agent 系统缺的也是这样一套东西。我们太关注"让 Agent 更聪明"了,却忽略了给它配一个中立、客观、独立于生成过程之外的导航层。我们缺的,就是认知导航。
4. 认知导航:Agent 稳定性的关键底座
4.1 把 GPS 的思路搬进 Agent 系统
所谓认知导航,我把它定义为:一组横跨 Agent 执行过程的元层控制机制,目的是不让 Agent 每一步都听指挥,而是保证它始终知道自己在哪里、要去哪里、有没有偏航,并在偏航时能够自动回到正轨。
它和传统控制流的本质区别在于:Agent 的执行仍然是自由的、生成式的,导航层不干预它"怎么做",只负责持续校验"做到哪了、偏没偏"。这就像 GPS 不会帮你踩油门,但它会在你走错路口的时候喊一声:"您已偏航,正在重新规划路线。"
4.2 认知导航的四个核心组件
以我的工程实践来看,一套完整的认知导航体系至少要包含四个部分。
认知地图(Cognitive Map):这是对任务空间的建模。一个任务不是含糊的"做一份报告",而是被拆成若干可验证的里程碑节点,比如"数据已清洗""指标已计算""报告初稿已完成""异常项已复核"。每个里程碑都有可执行的验证函数,能够判断是否真的达成了。认知地图解决的是"要去哪里、怎么算走到"的问题。
状态定位(State Localization):这是从 Agent 每步输出中提取"当前位置"的能力。Agent 每完成一步,导航层需要把这个动作对应的状态更新到地图上,比如"当前停留在'数据已清洗'这个节点"。定位不是简单打标签,而是需要从自然语言输出里抽取语义状态,这一步通常要借助结构化输出协议或者小模型分类器。
偏差检测(Deviation Detection):这是把"当前位置"和"预期位置"进行对比,识别三种典型的异常:重复、停滞、漂移。重复是状态的自我复制;停滞是状态长时间没有推进;漂移是状态偏离了目标相关方向。
纠偏引擎(Correction Engine):这是根据偏差类型和严重程度,生成纠偏指令的模块。它不直接修改 Agent 的内部逻辑,而是以外部指令的形式介入上下文,让 Agent 回到最近一个有效里程碑,或者重新规划子任务路径。
4.3 认知导航 vs 其他方案
有朋友问我:提示词约束、规则引擎、人工审核,是不是也能实现类似效果?我把对比整理了一下,方便大家直观理解:
| 方案 | 核心思路 | 对长任务漂移的防护 | 自动化水平 | 主要局限 |
|---|---|---|---|---|
| 提示词约束 | 在系统提示里反复强调目标 | 弱 | 高 | 无法客观判断"是否偏离",模型自我感觉良好 |
| 规则引擎 | 用 if-else 拦截重复调用 | 中 | 高 | 规则写多了维护成本爆炸,语义层面的漂移拦不住 |
| 人工审核 | 每跑几步就让人看结果 | 中 | 低 | 不可扩展,长任务全程盯人,成本不可接受 |
| 认知导航 | 地图+定位+检测+纠偏 | 强 | 高 | 前期需要建模和协议设计,上手门槛略高 |
我并不是说提示词、规则引擎这些手段没有价值,它们可以作为认知导航的补充,但单独依赖它们,很难在无人值守的长任务里保证稳定。
4.4 认知导航带来的实际影响
把认知导航接入系统之后,影响是立竿见影的。最明显的变化是,我们第一次可以在 Agent 运行的时候说清楚"它现在卡在哪了"——以前只能说"感觉不对",现在系统会直接给出偏差类型和置信度。其次是事故恢复时间大幅缩短,从"人肉排查数小时"降到"系统自动触发纠偏,几十秒内恢复正常"。这两个效果直接决定了我们敢不敢把更复杂的任务交给 Agent 全自动执行。
5. 给 Agent 装上认知导航:从设计到落地
5.1 分层架构设计
实践下来,我推荐把系统分成四层,职责清晰、边界明确:
- 任务解析层:把用户目标解析成认知地图,生成里程碑序列。
- 执行层:Agent 本体,负责实际调用工具、生成内容、推进步骤。
- 导航层:包括定位、检测、纠偏三大模块,独立于执行层运行。
- 监控层:负责指标采集、告警、可视化,面向人类运维者。
关键原则是:导航层不能和执行层共用同一个模型的同一次推理,也就是说它不能在 Agent 的"脑子"里做判断,而要在外部用独立逻辑做校验。只有这样,导航才是客观的。好比你不会让考生自己给自己改卷子。
5.2 认知地图与目标状态建模
认知地图是整个方案的起点。构建步骤如下:
- 把目标任务拆成不超过七个里程碑,每个里程碑对应一个可验证的状态。
- 给每个里程碑写一个验证函数,输入是 Agent 的当前输出,输出是布尔值。
- 定义里程碑之间的依赖关系,构成一张有向图。
数据结构上,我用的是类似这样的定义:
@dataclass class Milestone: id: str description: str dependencies: list[str] validator: Callable[[AgentOutput], bool] @dataclass class CognitiveMap: milestones: dict[str, Milestone] target_state: str max_steps: int def current_position(self, trajectory: list[AgentOutput]) -> str: # 遍历已完成的里程碑,返回最近一个完成的节点 id for m in self.milestones.values(): if m.validator(trajectory[-1]): return m.id return "start"这里有个经验要分享:里程碑的验证函数不要太复杂,越简单越好。能用"输出里是否包含某个关键字段"判断的,就不要强行上语义模型。因为验证函数一旦复杂,本身就会引入误判,让导航系统变成新的故障来源。
5.3 偏差检测的三板斧
偏差检测是认知导航真正的技术核心。根据我的落地经验,做以下三类检测基本覆盖了绝大多数漂移场景。
重复检测(Repetition Detection):计算当前输出与最近 N 步输出的语义相似度。如果连续几轮相似度都超过阈值,就判定为重复。在实现上,我会用文本向量化模型算余弦相似度,比直接比对字符串要鲁棒得多。
def detect_repetition(trajectory: list[AgentOutput], window: int = 5, threshold: float = 0.92) -> bool: """如果最近连续 window 条输出彼此之间的语义相似度都超过阈值,判定为重复漂移""" recent = trajectory[-window:] if len(recent) < window: return False vectors = [embed(text) for text in recent] for i in range(len(vectors) - 1): sim = cosine_similarity(vectors[i], vectors[i + 1]) if sim < threshold: return False return True停滞检测(Stagnation Detection):维护一个"里程碑最后推进时间"的变量,如果当前时间减去最后推进时间超过上限,说明 Agent 一直在原地打转但未必完全重复——比如它可能在换着花样做同一件事。阈值设置没有绝对标准,我们的经验是取"预期单步耗时的 5 到 8 倍"作为停滞判定线。
漂移检测(Drift Detection):计算当前输出与目标状态描述的语义相关性。低于阈值就认为 Agent 正在远离目标。这比重复检测更难调,因为相关性本身是模糊的。我的建议是不要用一个大而全的语义模型,而是分别计算"与当前里程碑的相关性"和"与最终目标的相关性",两条曲线分开监控,哪个低了就报警哪个。
def detect_drift(current_output: str, target_state: str, threshold: float = 0.55) -> bool: relevance = cosine_similarity( embed(current_output), embed(target_state) ) return relevance < threshold5.4 分级纠偏策略
检测到偏差只是第一步,怎么纠才是关键。我把纠偏策略分成四级,每一级干预力度不同。经验是:能用轻度移动解决的,不要直接重来,因为频繁重来会浪费大量算力,还有可能引入新的问题。
一级(轻度偏离):导航层直接在下一轮上下文里注入一条提示,比如"注意:检测到连续多步重复,请基于已有信息直接推进,不要再执行相同动作"。这种提示不改变任务结构,仅仅改变 Agent 的注意力分布,通常能解决简单的循环自洽。
二级(中度偏离):回退到最近一个已验证的里程碑,把中间未经验证的输出全部丢弃,从该里程碑重新执行。这里需要认知地图提供"最近有效节点"的能力,本质上是在局部做一个受限回溯。
三级(严重偏离):放弃当前子任务路径,重新规划从当前状态到目标的路径。注意不是回到起点,而是从"当前位置"重新计算后续路径,这样能减少不必要的重复开销。
四级(危急场景):导航层判定 Agent 已经无法自行恢复,自动冻结任务,标记"需要人工介入",并向监控系统推送完整的偏差报告。千万不要让系统无限循环纠偏,见过太多二次故障,都是在"自动修复"阶段反复横跳造成的。
def dispatch_correction(deviation: Deviation, state: AgentState) -> Correction: if deviation.level == DeviationLevel.MILD: return InjectHint(deviation.reason) if deviation.level == DeviationLevel.MODERATE: return RollbackTo(milestone=state.last_valid_milestone) if deviation.level == DeviationLevel.SEVERE: return Replan(target=state.target_state) return EscalateToHuman()5.5 配套监控指标
认知导航不能只做"事中干预",还需要给运维者提供"事中可观测、事后可复盘"的指标。我最终在监控面板上保留了五个核心指标:
- 重复率:最近 N 步输出中,语义相似度超过阈值的比例。
- 停滞时长:从最后里程碑推进到当前的时间间隔。
- 里程碑达成率:已完成的里程碑数占总数的比例。
- 纠偏触发次数:每小时各类偏差被检测到的次数,按类型区分。
- 自愈成功率:纠偏之后,Agent 在预期步数内恢复推进的比例。
这里的"自愈成功率"是两个行为边界的重要参考。如果成功率高于 0.9,说明纠偏策略有效;如果持续低于 0.7,就要认真考虑是不是认知地图建错了,而不是继续调检测阈值。
6. 常见问题与排查实录
6.1 典型症状与根因对照
和几个同行交流之后,我发现大家遇到的问题高度相似。整理成一张排查表,可以帮你在踩坑时少走弯路:
| 症状 | 可能的根因 | 优先排查方向 |
|---|---|---|
| Agent 反复执行同一动作 | 循环自洽、缺少外部终止条件 | 检查重复检测是否生效,阈值是否过高 |
| 任务跑到后面越来越偏 | 目标遗忘、上下文污染 | 检查里程碑验证函数是否被正确触发 |
| 纠偏后依然原地打转 | 纠偏指令太弱、或者模型未真正理解 | 升级到回退策略,不要只靠提示 |
| 自愈后引发二次错误 | 回退点选择不当、数据状态不一致 | 强化里程碑验证函数,确保回退点可信 |
| 导航层本身误报频繁 | 语义相似度阈值过紧 | 放宽阈值,引入人工标注样本校准 |
6.2 排查步骤
如果正在跑的任务突然表现异常,我建议按这样的顺序排查:
- 先看监控指标,确认是重复、停滞还是漂移。
- 再到日志里定位循环的起始步,通常从首次语义相似度超过阈值那一步开始。
- 检查那一步前后的上下文,判断是工具返回异常,还是目标被遗忘。
- 手动复跑最近一个有效里程碑到异常点之间的路径,确认可复现性。
- 最后决定是调检测阈值,还是升级纠偏等级。
这五步走下来,大部分问题都能定位到具体环节。很多时候,你会发现问题不在 Agent 本身,而在上游——认知地图建得不够细,或者目标任务本身描述得就不清晰。
6.3 几个我后来才懂的小技巧
第一个技巧:给里程碑设计明确的"结束信号"可以大幅减少重复漂移。比如让 Agent 在每个里程碑结束时,必须输出一行包含固定标记的 JSON。导航层看到这个标记,才知道这个节点走完了。这一步能沉淀结构化状态,检测自然就稳定了。
第二个技巧:检测重复不要只看文本相似度,还可以看工具调用参数。文本可能因为改写而变化,但工具参数往往高度结构化。在我们的案例里,重复循环中的搜索参数几乎完全一致,用参数一致性来判断,比语义相似度更早、更准。
第三个技巧:纠偏提示要放在上下文靠前的位置,并且用独立的系统消息注入,而不是拼在对话历史末尾。实测下来,放在末尾的提示容易被最近几轮的长输出淹没,放在开头或作为系统插播消息,效果会明显好很多。
7. 最后说点体会
那次终局交付事故之后,我对 Agent 工程化的理解彻底变了。以前我总觉得,让 Agent 更聪明、理解更复杂的指令,才是解决问题的关键;现在我认为,在长任务里保证稳定推进,比在单轮对话里表现得聪明重要得多。聪明是上限,稳定是底线,底线守不住,上限再高也没有交付价值。
认知导航这个概念,听起来有点像"给 Agent 装 GPS",但它真正解决的,其实是工程上一个非常朴素的问题:我们有了一套独立于 Agent 之外的手段,来回答"它现在到底在干什么、离目标还有多远、要不要帮它拉回来"。有了这套手段,我才敢把真正关键的任务交给 Agent 全自动去跑。
如果让我给正在做类似系统的人一句建议,我会说:不要等事故发生了才想起做导航层。你可以在设计阶段,先定义清楚目标状态和里程碑,哪怕只用最朴素的规则检测,也能在关键时刻救你一命。别让"让 Agent 更聪明"的愿望,盖过了"让 Agent 更可靠"的基本功课。