LLM Agent上下文工程实战:从状态管理到成本治理

发布时间:2026/10/5 9:20:28
LLM Agent上下文工程实战:从状态管理到成本治理 做了几年LLM应用我越来越觉得“AI Agent能不能干活”根本不取决于模型多强而是上下文工程做得有多细。很多团队把Agent做成一个能聊天、能调接口的Demo一上生产就开始胡言乱语、工具乱点、上下文爆掉排查了一圈发现不是模型问题是没有人认真管理Agent每一轮看到的“那坨文本”。这篇文章就把上下文工程这件事彻底拆开讲从上下文构成、生命周期管理、压缩策略到并发隔离和成本治理都是我踩过坑之后的实操视角适合正在用LangChain、LangGraph、FastAPI、Django这类框架做Agent开发以及准备把Agent推向生产环境的同学。1. 先说清楚Agent里的“上下文”到底指什么1.1 从单次Prompt到多轮任务上下文发生了什么变化最早我们用大模型上下文就是“你发给模型的那段话”模型基于这段静态文本一次性给出回答不需要记忆也不存在状态。Agent则完全不同它是一个“观察→决策→执行→反思”的循环系统每一轮循环结束都需要把新的信息写回上下文再进入下一轮。这就带来一个本质变化上下文从“一段静态文本”变成了“一份可持续增长、需要持续治理的运行时数据”。我举个例子你让Agent帮忙做一个月度报表。它第一步要读取数据库拿到销售数据第二步根据数据写分析第三步把结果通过邮件发送。这个过程中第一步的工具返回结果如果没被结构化地保留在上下文里第二步的分析就会基于模型的“想象”来做第三步又会把不完整的结论发出去。上下文工程没做好的Agent最典型的表现就是“越跑越傻”对话到第五轮忘了最初目标工具返回结果被忽略最后给你一个看起来合理但完全不能用的东西。所以上下文工程的本质不是提示词工程而是运行时数据管理。它关注的是哪些信息必须出现在模型的视野里、以什么顺序出现、以什么粒度出现、什么时候该丢弃、什么时候该压缩。这个视角一旦建立起来Agent开发就从“调Prompt”升级成了“设计数据流”。1.2 模型看到的上下文和你调试时看到的日志不是一回事做上下文工程的第一课是建立完整的可观测性。你以为Agent看到的是控制台里那几行日志完全不是。模型看到的是你所有提示词模板、工具描述、历史消息、检索回来的记忆片段拼接而成的完整序列顺序、格式、甚至字段名都会直接影响模型的推理质量。我自己调试Agent时第一步永远是“导出完整上下文”而不是看结果。怎么导出LangChain里注册CallbackHandler把每次LLM调用的messages完整打印出来LangGraph里直接dump当前State的快照用FastAPI自建Agent时把每次组装好还没发给模型的payload写入日志库。拿到这些数据之后再去分析Agent在哪个节点开始跑偏是系统提示词没约束住是工具描述写得太模糊还是历史消息冗余太多把关键信息挤到后面了没有这套可观测机制后面谈的所有策略都是盲人摸象。我见过太多团队在“调提示词”上折腾了几天最后导出一看工具调用返回的JSON被截断了模型根本没看到完整数据那当然怎么做都不对。1.3 上下文工程要解决的三类老问题把所有项目的失败案例归拢起来上下文工程解决的其实就是三个问题这也是我判断一个Agent方案成不成熟的标尺。第一是“信息缺失导致的任务跑偏”。模型没有拿到执行上一轮任务所需的关键事实只能靠概率猜。第二是“信息冗余导致的理解稀释”。上下文塞了太多历史闲聊、重复的工具描述、无关的检索片段模型注意力被分散关键指令被淹没。第三是“信息冲突导致的行为混乱”。系统提示词说一件事工具返回的数据暗示另一件事历史消息又是第三种口径模型无所适从输出自然飘忽不定。这三个问题在不同项目里的表现各不相同但底层都指向同一件事上下文不是“越多越好”而是“该有的必须有不该有的一个都不要”。后面讲到的所有技术手段裁剪、摘要、结构化、抽离本质上都是围绕这三个老问题在做缓解。2. 上下文组件拆解系统头、任务态、工具结果与记忆2.1 系统提示词不是开场白是Agent的行为宪法很多人对系统提示词的理解是“给模型打个招呼交代你是谁”这远远不够。在Agent系统里系统提示词是约束模型整段任务行为最高优先级的指令我习惯把它叫“行为宪法”。凡是不能妥协的规则都必须写在这里并且放在上下文序列的最前面保证每一轮循环模型都会重新读到。什么样的内容算“宪法级”第一是身份与目标边界比如“你是一名数据分析助手只处理用户明确授权的数据”第二是工具使用红线比如“调用邮件接口必须先向用户确认收件人否则禁止调用”第三是行为框架比如“每次执行前先输出当前状态再决定下一步禁止跳步执行”第四是输出格式约束比如“所有回复必须包含decision字段value只能是execute或need_clarification”。这些规则一旦变了Agent整个行为风格都会变所以必须放在所有消息之前。实战里我强烈建议把系统提示词做成版本化配置不要硬编码在代码里。我踩过的坑是生产环境Agent突然行为异常定位了半小时发现是同事改了系统提示词没通知大家。现在我把提示词模板放到独立的YAML或数据库表里每次发布重新生成一条版本记录行为一变先查提示词版本排查效率高很多。2.2 任务状态Agent的“工作台账”任务状态是上下文工程里最容易被忽略、又最重要的一部分。它不是模型“说出来的话”而是Agent执行过程中必须持久化的结构化数据当前所处的阶段、已经完成哪些步骤、待确认的信息、中间计算得到的结果。我在LangGraph里通常会定义一个State对象包含current_stage、completed_steps、pending_items、decisions、last_tool_results等字段。这个State每一轮都会被部分注入到上下文里模型看到的不是一整段杂乱的日志而是一份清晰的“工作台账”。这样设计有个极大的好处模型永远知道自己“进行到哪一步了”不会重复执行已经完成的操作也不会跳步。这里要强调一个容易犯的错不要把任务状态和历史聊天记录混在一起。有些同学把所有对话历史直接塞给模型让它自己“理解”目前进行到哪一步这在任务链只有两步时没问题一旦超过五步模型就会忘记前面的决策。正确做法是维护独立的状态对象每次组装上下文时先把状态对象转成文本块放在显眼位置再拼接其他内容。2.3 工具结果与外部记忆最容易爆窗口的部分工具返回结果和检索回来的记忆是上下文体积增长最快的部分。一个搜索工具可能返回5万字的网页内容一个数据库查询可能带出几百行结构化数据如果不做处理直接塞进上下文十几个Token就没了——这里的“Token”不是比喻是真的按千字计费而且模型对这些大量输入的理解效果极差。我的原则是“先提炼、后注入”。工具结果返回后不会直接进入上下文而是先经过一个精简层要么用正则或JSON Path抽出关键字段要么用一次小模型调用做摘要要么把长表格转换成语义摘要加少量关键行。这个过程就像你让实习生去查资料回来汇报时应该给你一份要点清单而不是把整本书摔在你桌子上。外部记忆比如向量数据库召回的内容同理。别把TopK设得太大也别一股脑拼接。很多RAG失效问题根因不是检索质量差而是把太多相似但冗余的片段塞进了上下文模型根本分不清哪个才是真正要用的信息。召回之后先做去重和排序再按语义相关度截断这个步骤值得投入不少精力去优化。3. 上下文管理五板斧裁剪、摘要、结构化、抽离、聚焦3.1 裁剪不是简单砍旧消息而是按需删减最粗暴的上下文管理是“保留最近N轮”这个方案不是不能用但要清楚它的代价。直接丢弃旧消息意味着模型对任务早期目标、关键约束的感知会消失尤其是长周期任务丢到一半它就开始“失忆”。我的做法是“按需保留”每一轮消息进入上下文前先打标签区分“核心约束”“任务进度”“对话过程”“临时输出”四类核心约束永远保留任务进度转存到State中对话过程和临时输出才是优先裁剪的对象。具体到消息列表的操作有两个细节值得注意。第一裁剪单位应该尽量细不要一刀砍掉一整轮用户助手消息可以把一条用户消息里的无关附件描述丢掉保留关键指令第二裁剪后要在上下文中留一条“摘要锚点”比如用一条System消息注明“你与用户之前讨论过A、B、C细节如下…”让模型知道自己缺失了部分历史而不是让对话看起来像凭空跳转。3.2 摘要压缩让模型给模型写“工作纪要”当历史信息确实都要留但窗口不够用的时候摘要压缩是比裁剪更好的选择。思路非常简单当对话长度或上下文Token数超过阈值时触发一次压缩调用用一次LLM调用把历史消息改写成一份结构化的“纪要”纪要里包含已完成事项、当前结论、重要约束、待办事项。这份纪要接下来代替原始历史作为上下文的一部分。这里有个工程细节摘要不是只能做一次它可以分级压缩。第一级是最近几轮交互的细摘要保留较多细节第二级是早期历史的总摘要只留关键决策和结论。LangGraph里我通常把摘要生成做成一个节点Summarization Node通过条件边监听State的当前token数达到阈值就执行压缩。压缩本身也要控制成本摘要Promp不要写得又长又复杂用一次中量级模型就够了少量语义损失可以通过保留“决策记录”来弥补而不是硬保留每一句话。3.3 结构化注入让上下文变成一份可解析的工单模型对冗长自然语言的解析能力远不如对结构化文本。我自己的经验是Agent上下文里凡是涉及任务目标、约束、工具调用历史、状态流转的内容尽量用JSON或YAML格式注入而不是写成大段说明文字。比如给Agent的任务目标我不会写“请帮用户从数据库中找到上个月的销售数据然后生成报告发给指定邮箱”而是组装成这样的结构{ task_id: report_2025, goal: generate_monthly_sales_report, target_receiver: managerexample.com, constraints: [only_use_data_from_q1, must_confirm_receiver], tools_allowed: [query_database, send_email] }实测下来结构化文本能显著减少模型的自由发挥因为字段清晰、边界明确模型更倾向于“照单执行”。同时结构化数据天然便于程序预处理和后处理程序可以从模型输出的JSON决策字段解析出下一步动作而不是从自然语言里碰运气式地抠参数。3.4 状态抽离上下文和运行状态分开存是工程底线这是我做生产级Agent最低的底线上下文是“喂给模型的文本”状态是“驱动程序的数据”两者必须严格分离。上下文可以为了适配不同模型而裁剪、压缩、改写但状态必须保持完整、结构唯一、程序可读它不能因为上下文策略的调整而丢失。实际落地上状态通常存在独立的存储里比如Redis或关系数据库。多轮对话场景中每轮结束后把State序列化存储下一轮请求到达时先从存储恢复State再根据当前输入组装新上下文。这带来的好处是Agent进程即使被重启甚至横向扩容任务也能无缝接续并发用户之间状态天然隔离谁也不会串场。我在FastAPI里做多用户Agent服务时就是用一个Redis Key对应一个会话ID存储完整的State JSON每个请求只恢复自己会话的状态配合异步任务队列轻松扛住了远比单机实例高的并发量。3.5 分段与聚焦检索式注入替代全量堆叠最后一个技巧是关于“到底该把哪些内容送进上下文”的。Agent处理的任务越复杂可能需要参考的知识就越多——产品手册、私有文档、历史案例、用户画像。一股脑全部注入既不经济也不准确正确做法是像RAG那样把知识库拆成块再根据当前任务动态检索出最相关的一部分注入。这个“按需聚焦”的思路贯穿所有的上下文工程不是所有历史都要给模型看不是所有知识都要给出只有当前步骤真正需要的才值得占用上下文空间。比如Agent处于工具调用阶段时用户几轮前的闲聊完全不重要而当Agent要写代码时项目目录结构和编码规范才是有用的上下文。分段聚焦让Agent在有限上下文中保持高响应质量也直接决定你能跑多复杂的多跳任务。4. 从框架到落地LangGraph状态机、并发隔离与中台化4.1 为什么我推荐状态机式架构LangGraph与自研状态流转市面上的Agent框架很多LangChain适合快速原型LangGraph把Agent建模成有状态图执行Java生态里有Spring AI Agent追求极致并发的有人用Rust语言写Agent runtime低代码场景还有扣子Coze这类智能体平台。我个人做生产项目的建议是快速验证用LangChain或扣子真正上业务用LangGraph或者自研状态机。LangGraph的优点在于把“状态”这个概念当成了体系核心每个节点操作的是共享State节点之间有条件边控制流转。这跟上下文工程天然契合你可以在不同节点对State做裁剪、摘要、状态抽离因为State本身是程序可读的不依赖模型输出。对比LangChain早期的Chain式组装LangGraph更像“工作流引擎”而不是简单的“模型调用链”。如果现有技术栈是JavaSpring AI Agent同样提供了清晰的上下文组件切分方式只是生态和社区范例略少一些Rust写Agent则更多是为了极致性能和内存安全适合高并发网关侧场景但开发成本更高非必要不推荐作为全团队首选。4.2 并发场景下的上下文隔离怎么扛住多用户同时跑“AI Agent怎么扛并发”是很多人问我的第一个问题。并发问题有三个层面模型接口的速率限制、服务实例的扩容、以及上下文隔离。前两个好理解第三个才是Agent特有的难点——用户A和用户B的上下文绝不能混在一起否则会出现严重的伦理和业务事故。我的方案是会话上下文以会话ID为维度完全隔离每轮请求都走“恢复State→组装上下文→调用模型→更新State→持久化”的串行闭环。这个闭环里进程内的全局变量绝不能存用户上下文所有状态存Redis或数据库工具调用时把session_id透传到日志链路中方便排障。异步场景还要注意上下文传播如果Agent里发起异步任务容易在Python的async环境里丢失状态因为局部变量并不会自动跨线程传递这里我通常用contextvars来传递会话上下文或者干脆把异步任务改成显式传入session_id作为参数。并发高的情况下上下文组装和模型调用都要做适当的缓存。比如同一系统提示词模板编译后的Token序列往往是固定的可以在缓存里复用高频工具返回的标准化结果也可以按参数取缓存减少Token消耗和接口压力。4.3 中台化与复用上下文模板是公司的资产当Agent从一个项目扩展到十几个场景时上下文工程必须中台化否则每个团队各写各的质量和成本完全失控。我所说的“上下文中台”至少包含三样东西提示词模板中心、上下文策略引擎、以及会话数据服务。模板中心用来统一管理系统提示词和各类任务模板支持版本管理、灰度发布、AB测试。策略引擎用来配置每种任务的上下文生命周期策略什么时候触发摘要、保留多少轮历史、哪些字段必留下。会话数据服务负责把全公司Agent产生的关键会话状态持久化按业务线隔离。这个架构的好处在于业务团队不用关心上下文管理细节只需要在模板中心选择自己的任务模板算法团队则能集中优化通用的上下文策略例如摘要触发阈值、压缩Prompt的措辞复用成本大幅降低。4.4 场景决定上下文设计从常规业务到边缘案例上下文设计没有一套通行方案场景不同内容结构差异非常大。常规业务场景客服、办公助手、数据查询重点在设计清晰的任务态和工具结果提炼保证准确率和可控性。内容创作场景上下文需要大量注入品牌风格、竞品分析、用户画像同时严格保留长文本草稿和修改历史。有些朋友问过Agent能不能接管小红书这类内容平台的自动发布以及能不能做期货交易决策。这类场景其实最能考验上下文工程平台自动发布涉及账号身份、内容规则、频率控制、平台治理约束这些内容必须全部进入系统头的约束区而且执行前要有明确审批流交易决策类场景则把风控约束、实时行情、策略依据、历史持仓全部塞进结构化工单稍有不慎就是真金白银的损失。我的态度很明确技术上Agent都能“做”但如果把你的风控规则、平台合规要求写不进上下文或者模型执行前没有强制人工确认节点那就别上。这属于上下文工程的行为红线跟模型智商无关。5. 常见问题与排查技巧实录5.1 症状Agent越跑越傻如何定位是哪一环丢了如果Agent在对话中表现逐渐变差我建议按下面的顺序排查先导出最近三轮的完整上下文检查系统提示词是否还在最前是否被截断再检查State里的关键字段是否被后续流程覆盖最后检查历史消息里是否被塞入了大量无关内容导致关键指令被淹没。严格来说“越跑越傻”百分之九十九是“开始了上下文污染”而不是模型能力退化。我还在生产实践里发现一个隐蔽问题中间Agent步骤如果调用了流式输出部分框架默认把流出Token直接用于拼接下一轮的历史消息结果模型上一轮的内心Thought和推理过程全部累积到上下文里导致后面的Agent越来越“碎嘴”。解决方式是在历史消息存储时只保留最终输出不保留完整的流式中间过程或者至少对中间过程做摘要再保留。5.2 症状上下文爆窗优先砍掉哪些内容窗口不够时要有一个明确的优先级。我的实践经验是最先砍掉的是“过程性工具输出细节”比如搜索结果的原文、数据库查询的完整行数据这些应该被替换成提炼后的结论其次是“多轮闲聊和低价值对话”这个交给摘要节点处理最后才考虑压缩系统提示词和任务状态。有一个常见的错误是为了省Token把任务状态里的必要字段也删了导致Agent后续步骤失去了依据。我强烈建议把状态字段标记为“不可裁剪区”无论上下文多紧张都不动它。另外压缩时必须保留“关键决策及理由”后面如果用户质疑“为什么这么做”模型还能回答出来否则上下文压缩会把Agent变成一个失忆的、无法解释的工具。5.3 症状工具调用参数错乱上下文顺序是关键Agent调用工具时参数经常传错大部分时候不是模型笨而是工具描述和工具结果在上下文里的顺序或格式有问题。工具描述要写清楚输入参数的类型、必填项、取值范围、约束条件并且举例说明。模型对例子的理解远好于抽象的“字符串类型”描述。我习惯用这样的模板给每个工具写描述tool_name: send_email description: 发送邮件给指定收件人。仅在用户明确确认收件人后调用。 parameters: receiver: type: string required: true description: 收件人邮箱地址 content: type: string required: true description: 邮件正文 example: receiver: managerexample.com content: 请查看月度报表还有一个坑工具的返回结果必须紧跟在该工具调用的消息之后不要让别的步骤插在中间。如果历史消息顺序被打乱模型会误把上一次工具的结果当成这次调用的输入。很多Agent框架内部会自动维护消息顺序但如果你手写组装上下文一定要按“Tool Call→Tool Result”成对出现的规则做校验。5.4 症状并发用户互相串场八成是静态变量惹的祸并发环境下出现上下文串场排查时先检查是不是用了模块级变量存了用户数据。我在FastAPI里就遇到过一个同事把当前用户State放到了模块级变量里暂存结果用户A和用户B轮询时就互相覆盖Agent行为完全错乱。修复方案是全部改从Redis按key恢复确保没有任何跨会话共享的可变状态。另外一个隐蔽问题出现在异步回调上。使用FastAPI的BackgroundTasks或Celery时如果回调事件里引用了外部状态很容易拿到过期或被污染的上下文。我的经验是事件里只传session_id事件处理函数自己从存储重新加载会话状态简洁且安全。总之“上下文永远按会话ID取不随代码流转”这条经验能救你很多次。5.5 成本治理上下文工程也是预算工程最后说下成本。上下文越长每次调用消耗Token越多模型响应速度也越慢这是Agent调用成本的主要组成部分。我一般会给每个Agent任务设定“Token预算”比如一轮长任务总Token不超过12万。达到阈值后强制进入摘要压缩或工具结果精简。这是工程约束不是性能优化预算超标会直接拖垮项目的可用性。预算管理要做好监控每个会话的token开销、每轮平均开销、工具结果消耗占比都需要可视化。我习惯在Agent调用层埋点把每次调用的prompt_token、completion_token、model_name落到监控系统里按天出报表。成本异常波动往往意味着上下文策略出了问题比如检索注入量激增或摘要节点失效提前发现能省下大量不必要的费用。最后再分享一个我自己的习惯每次接手新的Agent项目我都会先强行要求团队回答三个问题模型每一轮需要的核心信息是什么哪些信息可以丢弃、哪些必须保留一旦状态和上下文分裂了程序如何恢复这三个问题想不明白后面用再高级的框架也是白搭。上下文工程从来不是一个“调一下提示词”就能完成的步骤它贯穿系统设计、数据建模、运行期治理和成本控制的全过程与其等Agent上线后抱头排查不如从第一行代码开始就认真对待。你能在上下文上做的功夫决定了Agent是“玩具”还是“生产力工具”。