资讯动态

AI Agent上下文工程:从ReAct到多步任务落地的核心机制

发布时间:2026/10/26 20:09:07 来源:尧图企业网站定制
1. 为什么上下文工程成了 AI Agent 的分水岭过去大半年我一直在折腾各种 AI Agent 的落地项目从最简单的客服问答机器人到需要多步推理、调用外部工具的复杂任务编排系统踩过的坑可以说能写一本小册子。但真正让我意识到“上下文工程”这件事有多关键的是去年底做的一个数据分析 Agent 项目。当时模型选的是当时能力很强的一款工具链也搭得很完整单步测试全都通过可一旦把任务链路拉长到七八步Agent 就开始“失忆”——前面明明已经查到的数据后面推理时完全当没发生过甚至会把用户最初的需求抛到九霄云外。排查了很久才发现问题根本不在模型能力也不在工具接口而在于我喂给模型的上下文组织方式出了大问题。这就是今天想跟你聊透的话题AI Agent 的上下文工程。简单说它研究的是“在 Agent 每一步思考和行动时到底该把哪些信息塞进模型的上下文窗口里以什么顺序塞塞多少”。这件事听起来像是提示词工程的延伸但实际上它比提示词工程要复杂一个量级。提示词工程更多是单轮对话的静态优化而上下文工程是动态的、多轮的、有状态的它要解决的是“在有限的上下文窗口里如何让 Agent 始终记得该记得的、忘掉该忘掉的、找到该找到的”。这篇文章适合三类人看一是正在搭建 AI Agent 但发现多步任务总是跑偏的开发者二是想理解 ReAct、Plan-and-Execute 这些主流 Agent 架构背后到底怎么管理状态的工程师三是单纯对大语言模型应用感兴趣、想搞清楚“上下文窗口”这个稀缺资源该怎么分配的学习者。我会从设计思路、核心机制、实操落地到问题排查把上下文工程这件事拆开揉碎讲清楚尽量让你看完就能在自己的项目里用上。2. 上下文工程的本质管理一个稀缺且会衰减的资源2.1 上下文窗口不是越大越好而是越“准”越好很多人有个误区觉得现在模型上下文窗口都到 128K 甚至 200K 了那还做什么上下文工程全塞进去不就完了。我一开始也这么想直到实测发现两个问题。第一长上下文存在“中间遗忘”现象模型对上下文开头和结尾的信息召回率明显高于中间部分你把关键信息塞在中间它很可能视而不见。第二上下文越长推理成本和延迟越高而且噪声信息会稀释有效信号导致模型判断力下降。我做过一个对比测试同一个多步推理任务把全部历史对话无脑塞进去准确率反而比精心筛选后的上下文低了将近 20 个百分点。所以上下文工程的核心目标不是“塞满”而是“精准”。你要像一个编辑一样从海量信息里挑出当前这一步真正需要的那几条按合理的顺序排列把无关的、过期的、冗余的信息果断砍掉。这背后其实是一个信息论问题上下文窗口是带宽Agent 的每一步决策是需要传输的信号你要最大化信噪比。2.2 上下文里到底该放什么四类核心信息我在多个项目里总结下来一个 Agent 的上下文通常由四类信息构成每一类都有它的位置和管理策略。第一类是系统指令与角色设定包括 Agent 的身份、能力边界、输出格式要求、安全约束等。这部分通常放在上下文最前面且在整个生命周期内保持稳定不轻易改动。它的作用是给模型定调让后续所有推理都在这个框架内进行。第二类是任务状态与进度也就是 Agent 当前在做什么、已经完成了哪些步骤、下一步计划是什么。这部分是动态变化的是上下文工程里最需要精心维护的部分。我习惯用一个结构化的“任务看板”来表示比如当前目标、已完成子任务列表、待办子任务列表、当前正在执行的子任务。第三类是工具调用结果与外部知识也就是 Agent 通过调用搜索、数据库、API 等拿回来的信息。这部分往往是上下文膨胀的主要来源因为工具返回的内容可能很长但真正有用的可能只有其中一两句。所以这里必须做结果压缩和摘要不能原样塞进去。第四类是历史对话与记忆包括用户的历史输入、Agent 的历史输出、以及从长期记忆中召回的相关片段。这部分需要做相关性过滤和时间衰减越久远、越不相关的信息越应该被压缩或丢弃。2.3 为什么 ReAct 模式对上下文管理要求特别高ReAct 是目前最主流的 Agent 推理模式之一它的核心是“思考-行动-观察”的循环模型先输出一段思考然后决定调用哪个工具工具返回结果后再进入下一轮思考。这个模式天然就是多轮的每一轮都会往上下文里追加新的内容。如果不加控制上下文会随着轮次线性增长很快就会撑爆窗口或者因为噪声太多导致模型迷失。我在用 ReAct 模式搭建一个自动调研 Agent 时最深的一个体会是ReAct 的每一步观察结果都必须经过处理才能进入下一轮上下文。比如工具返回了一篇长文你不能直接把整篇文塞进去而应该让模型先对结果做一次摘要只保留与当前子任务相关的要点。这个“摘要再入上下文”的动作就是上下文工程在 ReAct 里的具体体现。否则你会发现 Agent 跑到第五六轮就开始胡言乱语因为它已经被前面的冗余信息淹没了。3. 上下文工程的核心机制拆解3.1 上下文的分层结构设计我在实际项目里会把上下文分成三个层次来管理这个分层思路借鉴了操作系统的内存管理理念实测下来非常有效。第一层是持久层存放那些在整个任务周期内都不变或很少变的信息比如系统指令、角色设定、全局约束、用户的基本画像。这一层的内容在任务开始时构建一次之后基本不动相当于“常驻内存”。第二层是工作层存放当前任务相关的动态信息包括任务看板、最近几轮的思考与行动记录、当前活跃的工具调用结果。这一层是上下文工程的主战场需要每轮动态更新该加的加该删的删该压缩的压缩。第三层是召回层存放从长期记忆或外部知识库中按需检索出来的信息。这一层不是每轮都有只在需要时触发检索检索结果经过相关性排序和截断后注入。它的作用是给 Agent 提供“它自己不知道但需要知道”的背景知识。这三层的顺序也很讲究。我通常把持久层放最前召回层放中间偏后工作层放最后。为什么工作层放最后因为模型对上下文末尾的信息注意力最强而工作层里的任务看板和当前步骤是 Agent 下一步决策最直接依赖的放在末尾能显著提升决策准确率。这个顺序是我反复调整后确定的你可以根据自己的任务特点微调但“最重要的信息靠近末尾”这个原则值得记住。3.2 上下文压缩摘要、过滤与结构化上下文压缩是上下文工程里最核心的技术手段没有之一。我常用的压缩策略有三种分别应对不同场景。摘要压缩适用于工具返回的长文本。做法是让模型对返回结果做一次针对性摘要只保留与当前子任务目标相关的内容。这里有个技巧摘要的提示词里要明确告诉模型“当前子任务是什么”让它带着目标去摘要而不是泛泛地总结。我试过不带目标的摘要结果模型保留了一堆无关细节压缩效果很差。过滤压缩适用于历史对话和记忆召回。做法是根据相关性分数、时间新鲜度、信息类型等维度给每条信息打分低于阈值的直接丢弃。相关性分数可以用向量相似度算也可以用简单的关键词匹配看你的精度要求。时间新鲜度就是越近的权重越高因为 Agent 任务通常有很强的时序性旧信息往往已经过时。结构化压缩适用于任务状态和进度信息。做法是把自然语言描述的状态转成结构化的键值对或表格比如把“我已经查了 A 公司的营收是 100 亿还没查 B 公司”压缩成{已完成: [A公司营收:100亿], 待完成: [B公司营收]}。结构化之后信息密度大幅提升同样的窗口能装下更多有效状态。3.3 上下文窗口的预算分配策略把上下文窗口当成一个需要做预算的有限资源这个思维转变很重要。我一般会按比例给四类信息分配预算比如持久层占 10%召回层占 20%工作层占 60%留 10% 作为缓冲应对突发长输出。这个比例不是固定的任务越复杂工作层占比应该越高任务越依赖外部知识召回层占比越高。当预算不够时要有明确的降级策略。我的做法是优先压缩召回层因为召回层的信息通常可以通过重新检索获得其次压缩工作层里的历史轮次保留最近三轮更早的做摘要最后才动持久层因为持久层是 Agent 的“人格”和“底线”动了容易出大问题。这个降级顺序在多次压力测试里被证明是稳妥的。4. 实操落地从零搭建一个带上下文管理的 ReAct Agent4.1 整体架构与工具选型这一节我带你走一遍完整的搭建流程。为了让你能直接复现我用 Python 来写模型接口用通用的 OpenAI 兼容格式你可以换成任何支持该格式的模型服务。工具方面我准备两个示例工具一个模拟搜索一个模拟计算器方便演示工具调用结果的上下文处理。整体架构分四个模块上下文管理器负责构建和维护三层上下文推理引擎负责调用模型并解析 ReAct 格式的输出工具执行器负责实际调用工具并返回结果主循环负责串联整个流程控制轮次和终止条件。这个拆分的好处是上下文管理逻辑独立出来方便单独测试和调优不会和推理逻辑耦合在一起。4.2 上下文管理器的实现先看上下文管理器的核心代码。它的职责是维护三个列表并提供构建最终上下文字符串的方法。class ContextManager: def __init__(self, system_prompt, max_tokens8000): self.persistent [{role: system, content: system_prompt}] self.working [] # 工作层任务看板 最近轮次 self.recall [] # 召回层按需检索的知识 self.max_tokens max_tokens self.task_board {goal: , done: [], todo: [], current: } def update_task_board(self, goalNone, doneNone, todoNone, currentNone): if goal is not None: self.task_board[goal] goal if done is not None: self.task_board[done].extend(done) if todo is not None: self.task_board[todo] todo if current is not None: self.task_board[current] current def add_working(self, role, content, compressFalse): if compress: content self._summarize(content) self.working.append({role: role, content: content}) self._trim_working() def _trim_working(self): # 保留最近3轮完整对话更早的做摘要 if len(self.working) 6: old self.working[:-6] summary self._summarize_batch(old) self.working [{role: system, content: f历史摘要{summary}}] self.working[-6:] def build(self): board_str self._format_board() messages self.persistent.copy() messages.append({role: system, content: f当前任务看板\n{board_str}}) messages.extend(self.recall) messages.extend(self.working) return messages这段代码里有几个关键设计点值得说明。第一任务看板是单独维护的每轮都会重新格式化后注入上下文保证 Agent 始终能看到最新状态。第二工作层超过 6 条就触发摘要压缩把更早的对话合并成一条摘要这样上下文长度不会无限增长。第三召回层插在持久层和工作层之间位置固定方便模型区分“背景知识”和“当前操作”。4.3 ReAct 主循环与上下文注入时机主循环的逻辑是构建上下文 - 调用模型 - 解析输出 - 如果是行动则执行工具 - 把观察结果处理后加入工作层 - 进入下一轮。def run_agent(goal, max_steps10): cm ContextManager(SYSTEM_PROMPT) cm.update_task_board(goalgoal, todo[分析需求, 执行任务, 汇总结果], current分析需求) for step in range(max_steps): messages cm.build() response call_llm(messages) thought, action, action_input parse_react(response) cm.add_working(assistant, response) if action finish: return action_input # 执行工具 observation execute_tool(action, action_input) # 关键观察结果先压缩再入上下文 compressed compress_observation(observation, cm.task_board[current]) cm.add_working(user, f观察结果{compressed}) # 更新任务看板 cm.update_task_board(currentf步骤{step1}完成) return 达到最大步数限制这里最关键的一行是compress_observation它决定了工具返回结果以什么形态进入上下文。我的实现是让模型带着当前子任务目标去摘要只保留相关要点并且限制摘要长度不超过 200 字。实测下来这一步能把上下文增长速度降低 60% 以上同时几乎不损失关键信息。4.4 参数选择与实测数据在搭建过程中有几个参数需要你根据实际情况调整。最大轮次我一般设 10 到 15太少任务做不完太多容易陷入死循环。工作层保留轮次设 3 到 5取决于单轮信息量大小。摘要长度上限设 200 到 500 字太短丢信息太长失去压缩意义。召回触发阈值用向量相似度的话设 0.7 左右比较合适低于这个值召回的内容基本不相关。我拿一个“调研三家竞品并对比”的任务做过实测。不做上下文管理时Agent 跑到第 7 轮上下文就超过 8000 token且开始重复调用已经调过的工具最终没能完成任务。加上三层上下文管理和观察结果压缩后同样任务在 9 轮内完成峰值上下文控制在 5000 token 以内工具重复调用率从 40% 降到 8%。这个对比让我确信上下文工程不是锦上添花而是决定 Agent 能不能真正干完活的关键。5. 常见问题与排查技巧实录5.1 Agent 反复调用同一个工具怎么办这是最常见的问题根因通常是 Agent 不记得自己已经调用过了或者调用结果没有正确进入上下文。排查思路分三步先看工作层里有没有保留上一次的调用记录和观察结果如果没有说明压缩策略太激进把关键记录也压掉了再看任务看板有没有更新如果看板里“已完成”列表没加上这一步Agent 就不知道自己做过最后看观察结果是不是被压缩得面目全非导致 Agent 认不出来这是同一个结果。我的解决方法是给每次工具调用生成一个简短的“调用指纹”比如search:竞品A营收把这个指纹记在任务看板的已完成列表里同时在观察结果摘要开头也带上这个指纹。这样 Agent 每轮都能看到“我已经搜过竞品A营收了”自然就不会重复调用。5.2 上下文超长导致模型输出质量骤降有时候任务复杂即使做了压缩上下文还是逼近窗口上限这时候模型输出会明显变差表现为思考变浅、格式错乱、甚至忽略指令。我的应对策略是主动降级一旦检测到上下文 token 数超过预算的 85%就触发强制压缩把召回层清空工作层只保留最近两轮任务看板做极致结构化压缩。这个降级动作虽然会损失一些信息但能保证 Agent 继续运行而不是崩溃。还有一个技巧是分段执行。如果任务实在太大不要指望一个 Agent 一口气做完而是拆成多个子任务每个子任务用独立的上下文子任务之间通过结构化的中间结果传递。这其实就是 Plan-and-Execute 架构的思路用任务分解来规避上下文膨胀。5.3 工具返回结果格式不统一怎么处理不同工具返回的格式千差万别有的是 JSON有的是纯文本有的是 HTML。如果直接塞进上下文模型解析起来很费劲还容易误读。我的做法是在工具执行器里加一层归一化适配器把所有工具返回统一转成“摘要 关键字段”的结构化格式。比如搜索工具返回一堆网页适配器提取出标题、摘要、链接三个字段拼成简洁文本。这样进入上下文的信息格式一致模型处理起来稳定得多。5.4 常见问题速查表问题现象可能原因排查方向解决手段反复调用同一工具调用记录未保留检查工作层和任务看板加调用指纹更新看板输出质量骤降上下文超长统计 token 数主动降级压缩分段执行忽略早期指令中间遗忘检查指令位置关键指令靠近末尾或重复注入工具结果误读格式不统一检查原始返回加归一化适配器任务跑偏目标未持续强调检查看板目标字段每轮注入目标靠近末尾5.5 几个我踩过的坑第一个坑是摘要过度。有次我把观察结果压缩得太狠只留了一句话结果 Agent 后续推理时缺少细节做出了错误判断。后来我定了条规矩摘要必须保留所有数字、专有名词和结论性判断可以砍掉的是修饰性描述和重复表述。第二个坑是召回层污染。有次召回的知识和当前子任务其实不相关但相似度分数刚好过了阈值结果 Agent 被带偏了。后来我加了一道人工规则召回内容必须包含当前子任务的关键词之一否则丢弃。这个规则简单但有效。第三个坑是任务看板更新滞后。有次 Agent 完成了子任务但看板没及时更新导致它下一轮又去做了一遍。后来我把看板更新放在工具执行之后立即进行不等模型输出保证状态实时。上下文工程这件事说到底是在跟模型的注意力机制打交道。你得站在模型的角度想如果我是它这一轮我真正需要看到什么才能做出正确决策。把这个想清楚了很多技巧都是自然推导出来的。我在实际项目里最大的体会是与其花时间调提示词不如先把上下文结构理顺结构对了提示词稍微粗糙一点模型也能跑得不错。后续如果任务复杂度继续上升还可以考虑引入向量数据库做长期记忆、用图结构管理任务依赖这些都是上下文工程的自然延伸。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑