ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent上下文工程实战:从状态管理到污染排查

Agent上下文工程实战:从状态管理到污染排查 1. 为什么说上下文是 Agent 的“第二大脑”做 AI Agent 开发这大半年我踩过最深的坑不在模型选型不在推理框架而在一个看起来特别“软”的环节——上下文工程。你给 GPT-4o、Claude 这类模型直接丢一个问题它回答得往往不错可一旦把它包装成 Agent让它自己规划步骤、调用工具、读取文档、处理中间结果问题就全冒出来了答非所问、重复调用同一个工具、把上一次对话的结论当成当前问题的答案、甚至完全忘记用户最开始要的是什么。这些问题根子上都指向同一个原因上下文没管好。所谓上下文工程简单说就是管理“进入大模型窗口的所有内容”的一整套方法——系统提示词、用户输入、工具返回结果、历史对话、检索到的外部知识、中间推理过程全都算。它直接决定了模型在一个具体任务里能“看到”什么进而决定了它能不能做出正确的决策。模型本身不会记忆你给它看什么它就只能基于什么来思考你给错了、给多了、给乱了它就会在错误的信息上自信地胡说八道。传统RAG检索增强生成解决的是“如何把知识库内容塞进上下文”但面向 Agent上下文工程的范畴要大得多。Agent 会多轮调用工具每轮工具返回都可能携带大量噪音Agent 要自主决策就必须把“目标、约束、进度、已知条件、待办事项”这些状态信息完整地呈现给模型Agent 还要具备记忆短期记忆管当前任务长期记忆管跨会话的经验。每一层处理不当都会直接反映在最终效果上。一句话概括我的体会模型能力决定 Agents 的上限上下文工程决定 Agents 的下限。你把上下文管好了一个普通的模型也能稳定干活管不好再强的模型也会变成一个“看起来聪明、用起来崩溃”的玩具。这篇文章我不会堆概念而是从实战角度拆开讲Agent 的上下文到底由哪些部分组成、每一部分该怎么设计、主流框架LangGraph、Spring AI Agent 这类是怎么管理的以及我最常遇到的项目级上下文污染问题和对应的排查手段。内容偏工程向适合已经跑过简单 Agent Demo、正在做真实项目的开发者参考。2. 我踩过的第一个坑Agent 忘了“用户最开始要什么”先讲一段真实经历。刚上手 LangGraph 做 Agent 那会儿我搭了一个所谓的“数据分析助手”用户输入问题Agent 自己决定要不要调用 Python 代码解释器、要不要查数据库、要不要搜索文档最后汇总结果。第一次测的时候还算顺利但第二轮我就发现了一个诡异的现象——用户先问“帮我统计上季度各区域销售额”Agent 正确调用了 SQL 工具拿到了数据接着用户追问“东北区哪个月降幅最大”Agent 居然又跑了一遍 SQL 查了全量数据然后开始回答“上季度各区域的销售额分布是……”直接把第一个问题的结论重复了一遍。我调试了很久最后打开 LangGraph 的 Trace 日志才看明白问题在第二轮对话里Agent 的上下文只有“当前用户消息 最近几轮消息摘要”而摘要把第一轮的关键约束——“统计对象是各区域销售额重点是区域间对比”——给压缩掉了。模型看到的只是零散的“用户要统计”“工具返回了表格”它判断不出自己当前究竟处在任务的哪个阶段、已经完成了什么、下一步该做什么。于是它选择“重新做一遍”而不是“基于已有结论继续回答”。这个案例特别典型。它说明了一个被很多人忽略的事实Agent 的上下文不仅是“信息容器”更是“状态载体”。模型每次推理都需要回答三个问题我现在在哪一步、我手里有什么、我接下来要干什么。这三个问题如果答案不清晰任何复杂任务都会翻车。传统单轮 Prompt 不需要考虑这一点因为问题、背景、约束都在同一条用户消息里但 Agent 是跨多轮、跨工具的信息会被一步步拆分、传递、加工你必须在每一轮都把“任务状态”完整、准确地交付给模型。后来我总结了一套自己的上下文状态管理规则核心就三条每一轮消息都必须带上完整的任务目标描述哪怕会重复也不能省。不要指望模型从历史对话里“领悟”目标。工具调用结果必须附带结构化摘要尤其是查询类工具返回原始表格前先算好“这个结果说明了什么”把这句结论给模型。上下文中的每条消息都明确标注角色——哪些是用户的要求哪些是系统的约束哪些是工具返回的事实。角色混在一起时模型很容易把工具输出误当成自己的判断产生幻觉。这三条听起来简单但真正做到位需要你对 Agent 的每一条消息流都有清晰的掌控。接下来的章节我会逐一展开细说。3. Agent上下文的四大组成不止是“提示词”那么回事很多人一提上下文就以为“只要把 Prompt 写长一点、把资料贴进去”就行。真做 Agent 就会发现这是一种极其危险的简化。以一个典型的 LangGraph Agent 为例我每一轮向模型发送的上下文实际上由四类内容叠在一起每一类都有独立的工程策略。3.1 系统提示词Agent的“岗位说明书”和“操作手册”这是上下文里最稳定、也最常被轻视的部分。系统提示词不只是一个“角色设定”它更是一个完整的“岗位说明书 操作手册”。我现在的推荐写法是分区块组织每个区块解决一类问题角色与目标用一两句话说清楚这个 Agent 是干什么的、服务谁、追求什么效果。能力边界明确它能调用哪些工具、不能做什么。这个相当重要——不给模型划边界它就会自己“发明”工具调用或者在没有依据的情况下强行回答。工作流程如果有标准流程先分析、后规划、再执行、最后复盘写清楚。工具使用规则每个工具什么时候用、怎么填参数、返回结果后要做什么。这里可以引用工具函数命名让模型把工具名和它的具体动作绑定起来。输出格式规范要求模型最终回答时采用什么结构方便下游解析。禁忌清单明确哪些事绝对不能做比如不编造数据、不越权操作、不重复调用同一工具。写系统提示词有一条心得不要让它过长。模型对上下文的注意力不是均匀分配的越靠后的内容权重往往越高系统提示词如果写了几千字的“规章制度”真正关键的约束反而会被淹没。我现在给自己的上限是 1500 字左右确保每条指令都是“必要指令”而不是“可能有用”的废话。实测下来精简过的系统提示词指令遵循率明显高于长篇大论版本。3.2 用户消息层把“目标”和“约束”结构化很多 Agent 项目犯的错误是直接把用户的一句话丢进模型就期望它能干复杂活儿。实际上用户表达往往模糊、缺省、口语化模型要理解“帮我看看最近的异常订单分析下原因”这句话背后到底藏着多少隐含需求是很吃上下文的。我的做法是在 Agent 入口做一个“目标解析 约束补全”的前置环节把用户的原始消息加工成结构化的任务描述再放进上下文。加工之后的消息大概是这个形态用户核心目标分析最近7天异常订单退款率5%的主要原因 任务背景异常订单集中在华东区涉及3个商品线 约束条件 1. 只基于数据库实表数据不猜测 2. 结论需附数据支撑 3. 若数据不足明确告知用户不要编造 待办事项 - [ ] 查询异常订单明细 - [ ] 分析原因分布 - [ ] 给出改进建议这段内容放在用户消息层的最前面模型一眼就能看到“目标约束待办”后面无论发生多少轮工具调用它都不容易跑偏。这个做法本质上是把“用户意图”显式化为“任务状态”是上下文工程里性价比最高的一个动作。3.3 工具定义与返回结果Agent的“双手”和“眼睛”工具调用是 Agent 最核心的能力也是上下文最容易“灌水”的地方。每调用一次工具函数定义、参数、返回结果都会进入上下文。如果工具返回一大段 JSON 日志、一大堆无关字段模型既浪费 token又容易从噪音里提取错误信息。我对工具层做了两个关键改造一是工具描述必须精简且包含“什么时候用”。同样一个查询订单的函数写“查询订单信息”和写“当用户询问订单详情/物流/金额时使用入参order_id必填”模型调用准确率完全两个级别。工具描述本质上是给模型看的“路由说明”越是清晰的路由说明模型的工具选择越准确。二是工具返回结果必须做“后处理压缩”。我习惯在工具返回之后加一个轻量级的加工层把原始 JSON 转写为“结构化摘要 关键字段”。比如查询订单返回了一个包含 40 个字段的订单对象我会让加工层只提取模型真正需要用到的字段订单号、金额、状态、用户备注并增加一行“数据分析结论该订单金额异常偏高可能是拆单未遂或误操作”。模型拿到这行结论后续推理的正确率会高很多。记住一个原则让 Agent 的“眼睛”看结论而不是让它自己去原始数据里大海捞针。3.4 历史对话与记忆短期、长期、工作记忆三层分离Agent 的记忆不是“聊天记录的堆积”而是分层的。我目前在生产环境用的是三层结构短期记忆当前任务最近几轮的完整消息。保留完整原始消息不压缩用于保证局部上下文精确。工作记忆当前任务的关键状态例如“已调用的工具列表”“已获取到的结论”“待验证的假设”。每次工具调用之后我会在工作记忆里追加一条结构化记录。长期记忆跨会话的、值得沉淀的经验例如“用户偏好用表格看数据”“上次排查发现数据源A有时间延迟问题优先用数据源B”。长期记忆通过向量检索只取最相关的 3-5 条注入上下文。把历史对话不加区分地全量塞进上下文的做法我劝你趁早放弃。它最直接的两个恶果token 成本暴涨模型注意力被旧内容稀释导致对近期的关键信息“视而不见”。记忆分层之后信息才能各归其位。下面用一张表汇总这四类组成的职责和关键策略方便对照自查上下文组成部分核心职责推荐策略常见问题系统提示词角色、能力边界、工作流程、输出规范分区结构化控制在1500字内过长导致关键约束被稀释用户消息层目标、背景、约束、待办入口解析并结构化原始消息模糊模型自行发挥工具定义与返回工具选择、结果理解精简描述结果摘要化原始JSON灌入token暴涨且噪音大历史对话与记忆状态延续、经验复用三层分离短期/工作/长期全量堆叠注意力被稀释4. 主流 Agent 框架里的上下文管理方式LangGraph 与 Spring AI Agent 的对照市面上 Agent 框架很多不同框架对上下文的处理思路也完全不同。我实际用得最多的是LangGraph也调研过Spring AI AgentJava 生态还简单看过 Rust 生态的做法。这一节主要说说 LangGraph 和 Spring AI Agent 的差异以及它们各自逼你养成的上下文管理习惯。4.1 LangGraph状态图思维下的上下文流LangGraph 的核心抽象是状态图。你把 Agent 的推理过程定义为一个个节点node节点之间通过状态state传递信息。这意味着上下文不再是“一条消息长河”而是一个可编程、可迁移的状态对象。我在 LangGraph 里最常用的一种做法是自定义 state 结构把上下文的四类组成显式建模出来class AgentState(TypedDict): messages: Annotated[list, operator.add] task: dict # 结构化任务目标 memory: dict # 工作记忆 tool_results: dict # 工具结果缓存每轮节点执行时我可以精确控制要往模型消息里塞什么、不塞什么。比如在“规划”节点我只把 task、memory 和最近几条 messages 拼给模型在“执行工具”节点我把 tool_results 的最新结果注入。这种精细控制带来的好处很明显——上下文的最小化模型每次看到的永远只是“完成这一步推理所必需的内容”不必背负全局历史。LangGraph 的高阶玩法是把“路由逻辑”也写进上下文。比如根据工具结果判断“是否需要追问用户”“是否需要切换工具”这些决策尽量不给模型自由发挥而是用明确的规则写进下游节点的 prompt。这样既省 token也减少模型“自作主张”带来的不确定性。4.2 Spring AI AgentJava 生态的结构化讲究Spring AI Agent 我接触得不算深但它的设计思路和 LangGraph 有显著区别。它更强调声明式配置和与 Spring Boot 生态的整合其上下文管理非常适合企业级、规范化场景。在 Spring AI Agent 里提示词模板、工具描述、历史消息都以配置类和注解的形式管理天然要求开发者对上下文“显式声明”。例如用Tool注解定义工具时工具的功能描述是强制项这就是在逼你把“模型路由信息”写清楚。它的消息结构SystemMessage、UserMessage、ToolMessage也很规范强制你在代码层面就区分上下文的角色避免了 LangGraph 里常见的“消息角色混乱”问题。如果你所在团队以 Java 为主整体工程规范要求高Spring AI Agent 会是一个更稳的选择。它牺牲了一些灵活性但换来了清晰的结构和更低的出错率。4.3 框架无关的一条原则谁负责管理上下文谁就拥有 Agent 的质量不管是 LangGraph 的状态对象、Spring AI 的声明式配置还是基于 Rust 的高性能实现背后其实是一个共同的理念上下文管理必须显式化、工程化而不能依赖模型的“临场发挥”。模型偶尔能靠推理能力“救场”但一个可靠的 Agent 架构必须把上下文作为一个一等公民来设计——有结构、有规范、有流转逻辑。我在换框架时对这一点体会特别深。最初用纯函数调用的方式写 Agent上下文全凭手拼字符串一遇到多轮任务就崩后来迁移到 LangGraph虽然上手成本高了一些但所有上下文流转都有了明确的“状态依托”Bug 率肉眼可见地下降了一截。框架之于上下文工程就像轨道之于火车轨道本身不产生动力但没有轨道火车跑不了长途。5. Token 预算上下文工程的底层物理约束聊上下文就不能不谈 token。模型窗口是有限的上下文工程的一切策略最终都要落到“在这有限的窗口里放什么、放多少、怎么放”这个终极问题上。很多人开发 Agent 到中期会发现“唉我的上下文为什么塞不下了”这其实就是没做好 token 预算管理。我在项目里建立了一套 token 预算分配表每次构建上下文之前先按预算“计划分配”再动手拼装。一份典型的长任务预算大致如下以 128k 上下文窗口为例上下文区块预算占比预算量估算说明系统提示词1%约1-2k保持精简必要时拆成“常驻按需加载”两段用户任务描述1%约1-2k结构化目标约束待办工具定义5%约5-8k工具多了建议分组建装不是全量加载工作记忆与任务状态4%约4-6k必须精简为结论型记录工具返回结果摘要15%约15-20k原始数据绝不直接进入模型检索到的参考资料20%约20-30k按相关性排序只取 Top-N历史消息20%约20-30k短中长记忆分层裁剪预留余量34%约40k应对模型思维链输出和突发信息这张表的核心思想是长期对话中必须给“未来”留出余量。很多出问题的 Agent 都是把上下文塞到 95% 满才开始回复模型写到一半窗口就爆了或者被迫遗忘一部分早期关键约束——这就是典型的“预算管理失败”。Token 预算还涉及一个底层原理注意力机制是资源池。大模型的注意力不是无限的相关内容越多每条内容分到的注意力权重就越低越往后、越长的上下文遗忘和忽略现象就越严重。这意味着“塞得越满”往往效果越差反而“精准而克制”的上下文能获得更好的模型响应质量。上下文工程本质上是一门管理稀缺注意力的艺术。关于 token 计算我再补充一个实操细节不同模型的 token 成本不一样中文场景尤其要注意。Claude、GPT 这类模型对中文的 token 切分差异很大同样 1000 个汉字在不同模型里 token 数可能从 800 到 2000 不等。所以做 token 预算时不要用“字数”估要用“token 计数器”实测你的内容类型然后校准预算。6. 项目级上下文污染的四种常见形态与排查方法前面讲的都是构建侧的优化实际开发中上下文“被污染”才是导致 Agent 行为异常的头号原因。我把自己这一年在真实项目里遇到的上下文污染形态整理了一下分享四种最常见的每一种都附了排查思路和修复方案。6.1 故障形态一工具结果“篡改”了用户目标这是最危险的一种污染。情景还原用户说“帮我推荐一款适合送给爸爸的500元以内的手表”Agent 第一次调用商品搜索工具时返回结果里混入了大量“1000元以上智能手表”因为工具端按“综合排序”返回了混合数据。模型看到丰富的数据居然开始围绕“1000元智能手表”做推荐完全无视了“500元以内”这个约束。根因工具返回的现实数据“喧宾夺主”压过了用户目标在上下文中的权重。模型天然容易被具体的、近期的、细节丰富的信息吸引而抽象的、放在前文的约束目标反而权重偏低。排查方法看 Trace 日志里的最终一轮模型输入确认工具返回结果和用户消息在上下文中的相对位置。如果工具结果出现在最后、且篇幅很长基本可以判断是它抢占了模型注意力。修复方案一是把用户任务约束在每次工具调用之后“重新强化”比如在工具返回后的下一轮 prompt 开头重申“用户目标500元以内手表偏差是违反需求的”二是对工具返回结果做硬过滤在工具层直接把不符合约束的数据滤掉而不是留给模型判断。记住能靠程序解决的问题不要丢给模型。6.2 故障形态二长会话中早期关键信息被“挤出”窗口情景还原用户在会话一开始设置了偏好“所有回复用表格形式数字保留一位小数”Agent 前几轮一直遵守得很好但聊到第 10 轮的时候模型开始输出大段文字数字也不管小数位了。根因滑动窗口式的历史消息管理把最早的用户偏好信息当成“过期消息”截掉了。上下文保留的是“最近 N 条消息”而不是“最重要的消息”。排查方法看历史消息保留策略的源码或配置。很多框架默认保留最近 N 轮不会区分消息的重要性。修复方案在做历史消息裁剪前先识别出哪些消息是“持久化约束”类信息用户偏好、任务边界、数据口径把它们单独抽出放进系统提示词层或工作记忆层再对剩余历史消息做窗口裁剪。这是我在项目里改过之后效果非常明显的一处优化——裁剪前先做“重要消息提取”再把提取结果注入到一个固定位置即便窗口不断滚动关键约束也始终在场。6.3 故障形态三检索知识喧宾夺主压制模型自身推理情景还原Agent 带 RAG 功能用户问“明天上海会不会下雨”RAG 检索到一堆关于“上海历史气候”的文档模型开始长篇大论介绍上海亚热带季风气候特征就是不直接回答明天天气。根因检索结果在上下文里占据大篇幅模型把它当成“主导信息”而把用户的直接问题当成“次要信息”处理。尤其是在系统提示词没说明“检索资料仅作参考”的情况下模型很容易跟着资料走。排查方法检查系统提示词里有没有“知识库信息优先级”声明。我建议明确写一句“知识库信息仅供参考请优先基于用户问题直接回答知识库内容与用户问题冲突时以用户问题和已验证数据为准。”修复方案给检索模块加一个“相关性判定 摘要压缩”的中间层。检索到资料后不要直接把原始段落丢进上下文而是先让一个轻量级模型或规则判断资料与用户问题的相关度只把“高分相关”的部分压缩成要点句再注入。这一步对完整性、准确性和 token 成本都是巨大优化。6.4 故障形态四多 Agent 协作时的“上下文串味”情景还原我做一个“客服主管 Agent 技术专员 Agent”的协作系统用户先咨询了退款政策又追问“为什么我的 App 一直闪退”结果技术专员 Agent 的回复里居然引用了退款政策的条款说“根据退款政策闪退问题可以申请退款处理”完全答非所问。根因多 Agent 共享了一个全局消息列表技术专员 Agent 构建上下文时把客服 Agent 的历史回复也拿了进来导致信息串味。排查方法检查消息的路由与隔离机制。如果所有 Agent 都从同一个“全局会话”取消息就极容易出现这种互相污染。修复方案给多 Agent 系统设计“按 Agent 隔离的消息视图”。每个 Agent 只能看到自己的计划、思考、工具结果和用户需求涉及跨 Agent 传递的信息必须以“显式交接”的方式写入对方的工作记忆而不是直接让对方读全局聊天记录。我用 LangGraph 时会为每个子 Agent 单独维护一个 state主 Agent 需要传递信息时通过一个明确的“交接消息”来完成。这张表汇总了四种污染形态的“症状—根因—解法”污染形态核心症状根因推荐解法工具结果压过用户目标模型遗忘用户约束工具数据量大且靠近末尾工具层硬过滤每轮重申目标早期关键信息被挤出中途开始违反偏好滑动窗口不分主次持久化约束提取到固定层RAG资料压过推理长篇跑题不回答问题检索内容权重失衡相关度判定摘要压缩多Agent互相串味答非所问引用他人结论共享全局消息列表消息视图隔离显式交接7. 三个让上下文质量明显提升的工程技巧这一节分享三个我实测过、立竿见影的优化技巧。它们不属于某个框架的标配能力但都能显著改善 Agent 的行为质量。7.1 技巧一结论先行逐层展开模型对上下文里的信息处理不是线性的、平等的。我观察到把“结论/要点”放在“详细数据”之前模型的正确率显著更高。这和人类的阅读习惯一样先知道结论再看数据大脑更容易建立结构。所以在构建工具返回结果摘要、RAG 检索片段时我都会要求处理层输出“先一句结论再列出支撑数据”。例如查询订单异常情况优先给模型的是“异常订单集中在华东区主要原因是配送超时占比62%。” 然后才是具体的订单列表。模型拿到结论后再看数据它就会围绕结论做判断而不是在数据里漫无目的地寻找模式。7.2 技巧二给每个上下文区块打标签显式分隔当上下文包含多类信息时系统约束、用户目标、工具结果、检索知识、任务状态我会在提示词里用显式的分隔标签把它们区隔开例如system_role你是一个数据分析助手专注于订单分析。/system_role user_task用户核心目标分析最近7天异常订单原因约束仅基于事实数据。/user_task tool_result工具返回异常订单原因分布为配送超时62%、商品质量21%、其他17%。/tool_result knowledge_ref知识库华东区域配送网络近期压单严重平均延迟2.1天。/knowledge_ref task_state已完成查询异常分布待办给出改进建议。/task_state分隔标签的真正价值不是“格式化好看”而是给模型一个信息结构导航。模型在自我注意力计算时能够更清楚地识别“哪一段对应哪种用途”减少信息在语义空间里的混淆。实测下来打了标签之后Agent 工具路由准确率有接近 5 个百分点的提升对复杂任务尤其明显。7.3 技巧三上下文监控与日志回溯这是我最想强调、也最容易被忽视的技巧上下文必须可观测。我在所有 Agent 项目里都加入了一个中间件把每一轮发给模型的完整上下文 JSON 打到日志里并计算每轮 token 消耗和注意力分布。真正遇到诡异行为时这个日志就是唯一的、最有效的排查入口。具体做法很简单写一个 logging 拦截器在模型调用前后把 messages 数组、模型输出、token 统计全部序列化存储。出现问题时你先看“模型当时到底看到了什么”再追溯“这些内容从哪来、为什么进来”。很多网络上的调试教程会直接告诉你“加这句话进提示词”但如果你不会看上下文日志下次遇到新问题你依然会抓瞎。先学会看上下文再谈调提示词。8. 我的上下文工程设计落地清单文章的最后我把整个上下文工程的落地路径浓缩成一份可直接对照的自查清单。这份清单来自我的项目实践你拿去对着你的 Agent 代码逐条检查通常能一次性发现两到三个潜在问题。设计阶段上下文四类组成是否有明确的模块边界系统提示词、用户任务、工具结果、历史记忆系统提示词是否控制在 1500 字以内且分区块组织用户输入是否经过“目标解析 约束补全”的结构化前置处理工具描述是否写清楚了“什么时候用、怎么用、返回后做什么”工具返回结果是否有“摘要化 结论化”的后处理层运行阶段历史消息是否按短期/工作/长期三层管理而不是全量堆叠多轮对话中持久化约束用户偏好、任务边界是否存在于固定位置而不是随窗口滚动每次工具调用后是否重新强化了用户目标检索知识入库前是否有相关度判定和摘要压缩多 Agent 协作时每个 Agent 的消息视图是否隔离是否预留了 30% 以上的 token 余量避免上下文爆满监控阶段是否记录了每一轮完整上下文日志是否监控了 token 消耗和关键约束在上下文中的存活情况是否有基于日志回溯的异常排查 SOP这套清单看起来琐碎但每一条背后都是一个真实的线上事故。把上下文工程当成 Agent 架构的一等公民来对待前期多花一点时间设计和埋点后期就能少熬好几个晚上的 Debug。上下文工程没有银弹有的只是一件一件做对的小事。
返回列表