ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

AI Agent开发核心:上下文工程实战与LangGraph落地要点

AI Agent开发核心:上下文工程实战与LangGraph落地要点 聊到 AI Agent十有八九的人第一反应是框架选型LangChain 还是 LangGraph或者模型 API 怎么调、工具函数怎么写。但真正让我在一次又一次项目返工里栽跟头的从来不是这些而是上下文工程。你可能也有过这种体验单轮对话测试的时候 Agent 聪明得不行一旦进入真实的多轮任务它就开始失忆、开始答非所问甚至把上一个任务的中间结果当成当前任务的输入整个流程直接跑偏。这篇文章想聊的就是 Agent 开发里最容易被低估、又最决定项目生死的一层上下文工程。我会把它拆开揉碎讲清楚上下文窗口、状态管理、记忆分层、检索注入、压缩裁剪这些核心环节到底在解决什么问题并给出一套我在 LangGraph 里实际落地过的方案。适合正在做 Agent 应用、想从 demo 走向生产环境的人参考也适合那些被Agent 聊着聊着就傻了折磨过的同学拿来当排查手册。1. 上下文工程为什么是 Agent 开发的隐形地基1.1 Agent 与传统对话机器人最本质的差别传统对话机器人本质上是一个无状态的问答系统用户问一句模型答一句答完就完。它的上下文每次都是全新的一张白纸顶多靠显式传入的历史消息拼凑一点表面记忆。但 Agent 不是这样它要在一个任务里规划多步动作调用外部工具读取返回结果再根据结果决定下一步行动。这一步的输出是下一步的输入一个循环套一个循环整个过程中模型必须持续记得自己从哪里来、要到哪里去。我见过不少人第一次用 Agent 框架时的困惑明明工具函数写得没问题模型也够聪明但任务一复杂就散架。原因几乎都在上下文上——每一步该携带哪些信息、哪些信息已经过期、哪些中间结果优先级更高这些如果没有人去显式管理模型只能靠猜。而大模型的注意力机制是有限的给它一大堆无关紧要的历史它就会在真正的关键信息上犯迷糊。这就是上下文工程存在的理由不是让模型读得多而是让它读得对。1.2 上下文工程到底在工程什么这里我给出一个比较完整的定义上下文工程是围绕大语言模型应用特别是 Agent 系统对最终进入模型的所有信息——系统提示词、用户输入、历史对话、检索结果、工具执行反馈——进行有目的的设计、组织、更新和裁剪的实践集合。它不是一个单一功能而是多个层面的组合。我自己在做项目时习惯把上下文工程拆成五个维度来审视上下文窗口模型单次能接受的最大 Token 数这是硬边界所有设计都要在预算内完成。状态管理Agent 在多步执行中当前处于哪个阶段、已经收集了哪些关键信息需要有结构化的载体。记忆分层哪些信息是本次任务要用的短期记忆哪些是跨会话需要保留的长期记忆要分开存储。知识注入外部数据库、文档、API 返回的结构化数据如何按需检索并拼接到上下文中。压缩裁剪当上下文接近窗口上限时是丢弃、摘要、还是结构化提取要有明确策略。这五个维度不是独立的它们互相制约。比如你要塞进去的知识太多就得压缩历史对话压缩做得太狠又会影响 Agent 对任务上下文的理解。上下文工程的本质就是在这些约束之间找到一个对当前任务来说最优的平衡点。2. 拆解上下文工程的四个核心环节2.1 系统提示词Agent 的人设与行为边界系统提示词是所有上下文的地基。它不参与具体任务的推理却决定了 Agent 用什么样的身份、以什么样的规则去处理上面的一切信息。很多人对系统提示词的理解停留在写个角色扮演但放在 Agent 场景里它真正要承载的是三件事行为约束、决策偏好、输出格式契约。以我自己维护的一个数据分析 Agent 为例系统提示词里会有这么几条硬规则只有确认数据口径之后才能执行查询宁可多问一次也不要猜。工具调用失败时必须如实说明失败原因禁止编造工具返回值。最终回答必须包含数据来源、计算方式和置信度缺失任何一个都视为不合格回答。这些规则看起来简单但它们实质上是把上下文的范围预先划定好了。模型在每一轮决策时都会参照这些规则从而知道哪些信息值得放进推理链路、哪些信息可以直接忽略。我建议每个 Agent 项目在起步阶段就把系统提示词当成一等公民来对待不要用一句你是一个有用的助手糊弄过去。2.2 会话记忆短期与长期的分层设计记忆是上下文工程里最容易踩坑的地方。早期我做 Agent 时图省事把每一轮对话全部拼进 Prompt看起来记忆满满实际上一百轮之后前面的内容早就把注意力窗口挤爆了模型根本分不清哪些是当前任务的有效状态。后来我把记忆拆成了两层来管理。短期记忆是当前会话内、当前任务执行过程中的关键信息通常以结构化的状态对象存在而不是把原始聊天记录一股脑塞进去。长期记忆则跨会话保存用户的偏好、历史结论、领域实体信息存放在向量数据库或普通数据库中需要时再检索回来。这里有一个关键认知模型需要的不是所有历史而是所有历史对当前决策有用的部分。与其追求一字不差的完整记忆不如做好信息提炼。我通常会在每轮任务结束之后让 Agent 生成一条结构化的经验摘要存进长期记忆库。下次用户再来重新认识一遍的场景就会越来越少。2.3 动态检索让外部知识按需进入上下文Agent 光靠模型内部的参数知识是远远不够的它必须能够访问外部数据这就离不开检索环节。但检索不是简单地把文档向量化、召回 Top-K 塞进 Prompt 就完事了你要考虑召回内容与当前任务的相关度排序、去重、以及检索结果在上下文中的呈现位置。在实践里用户的原始问题往往信息不足。比如用户问上个月华东区的销售情况怎么样如果你直接拿这句话去向量检索召回的结果可能五花八门。更好的做法是让 Agent 先把用户问题改写成一个适合检索的查询语句提取出上个月华东区销售情况这些关键限定条件再去做检索。这一步润色通常能让召回的准确率提升好几个档次。同时检索结果回填到上下文时要有明显的结构标记比如用参考资料区块包裹并标注来源编号。这样模型在回答时能明确区分这是基于外部资料的信息和这是它自己的常识推断避免两者混淆生成幻觉内容。2.4 上下文压缩Token 预算的取舍艺术不管你用多大窗口的模型Token 永远是稀缺资源。128K 的窗口看起来很大但塞进一套完整的历史对话、几份检索文档、还有 Agent 的中间推理过程之后空间很快就没了。压缩的本质是用尽可能少的 Token 保留尽可能多的决策价值。我的压缩策略分成三档。第一档是裁剪把超过保留条数限制的早期消息直接丢弃。第二档是摘要保留关键事件和结论用一段话概括中间过程。第三档是结构化提取把对话中的重要实体、数值、状态变更抽成 JSON 或表格只保留纯结构。每种策略的取舍都不一样裁剪最简单粗暴但可能丢失关键信息摘要信息密度高但有失真风险结构化提取对下游任务接口友好但需要额外消耗一次模型调用。实际项目中我会对任务类型做区分。如果当前 Agent 只是一个问答型助手裁剪加摘要就够了。如果 Agent 正在做多步骤的数据处理或代码生成结构化提取几乎必不可少因为它能保证下游的工具调用拿到的是干净的参数而不是一段需要二次解析的散文。3. 实操在 LangGraph 里搭建可落地的上下文管理方案3.1 用 State 显式管理多轮对话中的上下文LangGraph 和 LangChain 早期那种线性 Chain 最大的区别就是它把 State 作为 Agent 执行过程中的中央数据总线。我在搭建上下文管理方案时第一件事就是把整个任务过程中要流转的字段全部定义清楚。我一般会定义一个这样的状态结构当前任务的目标描述、原始输入、中间结论列表、已调用工具清单、当前执行步骤编号、消息历史数组、以及一个专门存放引用来源的字典。每个节点要从状态里读哪些字段、写哪些字段都在图定义里显式声明。比如意图识别节点只读原始输入、写入当前任务目标工具调用节点读当前任务目标和中间结论写已调用工具清单。这套方式的优势在于上下文不再是一成不变的大字符串而是一个可以被精确读写的结构化对象。调试的时候你能清楚地看到每一步状态的变化。之前用纯 Prompt 拼接方案时光是排查哪一步把错误的历史带进了下一步就能耗掉半天切到 State 之后这个问题基本绝迹。3.2 会话历史的存取策略与消息裁剪模型 API 需要的是一个消息数组所以状态里的消息历史最终还是要转成标准格式传给模型。这里我踩过一个典型的坑把状态里的结构化字段全部转成消息结果每一个字段都被模型当成用户指令重新解读了一遍造成了大量无效推理。正确的做法是给历史消息划分角色和用途。系统提示词只出现一次用户的原始输入按时间顺序排列Agent 的中间推理过程如果对后续决策没有价值就只保留结论性的内容。我在 LangGraph 里写了一个专门的历史管理节点它的职责是从状态里读取本轮需要的结构化字段结合短期记忆库中的摘要拼装出一个紧凑的消息列表再交给后续的模型调用节点。裁剪阈值我一般设成最长保留最近 20 轮对话超过的部分走摘要逻辑。这个数字不是拍脑袋而是跟模型窗口和任务复杂度挂钩。如果是简单的问答20 轮绰绰有余如果是代码生成类的长任务我可能只保留最近 5 轮把更大的 Token 预算留给工具返回的代码差异信息。3.3 接入向量检索与长期记忆的完整流程长期记忆部分我的方案是用一个独立的向量库来存历史经验摘要而不是把原始对话全部灌进去。每次会话结束会有一个专门的节点把本次任务的结论、关键数据、用户偏好整理成一条摘要记录连同可检索的元数据一起写入向量库。当新会话开始时Agent 先根据用户的初始输入做一次向量检索把最相关的几条历史经验拉回来放进系统提示词或上下文的最前面。这个设计的好处是Agent 可以在对话一开始就获得这个人上次聊到一半的倾向这个客户最在意什么之类的背景信息而不是像以前那样每次都要从零开始试探。具体实现上我在 LangGraph 里增加了一个 memory_lookup 节点用会话 ID 和用户输入拼接出检索向量召回 Top-3 摘要然后把它渲染成历史背景区块。我自己实测加上这层长期记忆之后用户在第二次、第三次使用时明显感觉到 Agent 更懂人了不再重复询问已经回答过的问题。3.4 并发场景下的上下文隔离如何扛住多路会话AI Agent 怎么扛并发是很多人从 demo 走向线上时绕不开的问题。Agent 本身是无状态的但它的上下文必须是有状态隔离的。如果两个用户的会话跑在同一个上下文体里你的 Agent 就会出现串话、误用他人数据这类灾难级问题。我的做法是给每一次会话分配一个全局唯一的 session_id所有状态、内存对象、向量检索的过滤条件都绑定这个 ID。在 LangGraph 里每个会话实例拥有独立的 State 对象后端只需要保证 session_id 到 State 的映射一致就能做到逻辑隔离。真正考验并发的其实是存储层如果多个 worker 同时读写同一个会话的状态需要引入版本号或锁机制否则会出现覆盖写。从部署角度我建议把会话状态放在 Redis 或关系型数据库里而不是放在进程内存中。进程内存虽然快但一旦服务重启或多实例部署状态就全丢了。用 Redis 配合 session_id 做存储每次节点执行前加载、执行后保存配合 TTL 自动清理过期会话是目前比较成熟且稳妥的方案。4. 常见问题与排查技巧实录4.1 上下文污染Agent 行为跑偏的排查思路上下文污染是 Agent 开发里最隐蔽的问题。表面上看 Agent 还能答但答得越来越离谱尤其当某些脏数据混进上下文后它会一本正经地基于错误前提做推理。我遇到过最典型的一次是某个工具调用返回了一个空数组被 Agent 解读成了用户没有订单然后它顺着这个结论给出了整整三屏的错误分析。排查上下文污染我的第一个动作是开完整日志。每一步发给模型的消息数组全部落盘查看是否有异常字段混入。第二个动作是检查工具返回值的解析逻辑是不是所有返回都被完整拼进了上下文有没有写过滤规则。通常我会要求所有工具返回都带上状态码 状态说明 业务数据三件套模型只被允许在处理业务数据时推理状态码为异常时直接走异常处理分支不允许自行脑补原因。4.2 Token 超限的四种应对手段Token 超限是早晚会遇到的。与其等它爆了再去救火不如在系统设计之初就留好后手。我常用的有四种手段按优先级排列第一是裁剪历史消息这个最便宜但只对历史冗余多的场景有效第二是摘要历史适合对话轮次多但信息密度高的场景第三是对工具返回结果做字段级别筛选只保留关键字段这个收益很大因为工具返回往往是最占 Token 的大头第四是主动替用户拆解长任务把一个大任务拆成多个子任务分批处理避免单轮上下文爆炸。这四种手段未必互斥。我在生产环境里通常是组合用的历史消息走摘要工具结果走字段筛选如果还是接近阈值就触发任务拆分提示告诉用户这个问题信息量比较大我建议分三步处理第一步先确认 XX 数据。4.3 聊久了记忆错乱摘要与遗忘机制的平衡摘要做多了模型会开始混淆真实记忆和摘要记忆。比如摘要里写着用户上次提到喜欢简约风格但真实对话里用户后来反悔说现在想换轻奢风如果摘要更新不及时Agent 就会拿着旧偏好做新决策。这个问题的根源在于摘要只做了增量累积没有做冲突覆盖。我的处理方式是在摘要结构里加入时间戳和置信度字段。每次新的用户偏好出现时如果与旧摘要冲突就以新信息为准同时把旧摘要标记为已废弃。此外我还设了一个遗忘机制超过一定时间没有被检索命中的长期记忆会被定期降权或删除。遗忘不是缺点反而是保持 Agent 长期健康的关键就像人脑一样总记住所有的细节最终只会让判断变得迟钝。4.4 LLM 调用链变长之后上下文性能与成本优化Agent 任务越复杂LLM 调用次数就越多上下文越长成本和延迟就越高。我优化性能时的切入点有两个。第一个是减少无效标记能不传的字段不传能不带的对话记录不带能压缩的推理过程一定压缩。第二个是引入摘要缓存如果同一个用户多次发起类似任务我可以复用之前任务的经验摘要Skip 掉重复的知识检索和上下文拼装。成本侧的优化我会区分深思型任务和快速响应型任务。需要多步推理的场景上下文完整性优先。而像把用户查询改写为检索语句这种子任务根本不需要携带全部上下文给它一个缩略版就足够。把这些子任务独立出来用小窗口、低温度去跑整体成本能降下来不少。5. 最后分享一个我用命换来的经验做了这么多项目我最大的体会是上下文工程不是一次性的架构设计而是一个持续迭代的过程。你可能一开始把系统提示词、记忆、检索都安排得明明白白但上线之后用户真实的使用方式会给你上一课——他们提出来的问题大概率不在你的预设路径里新的上下文模式会不断冒出来。所以我建议所有做 Agent 的朋友从第一天起就把上下文的日志和监控当成基础设施来做。每一步进入模型的 Prompt 是什么、用了多少 Token、模型最终基于哪几条信息得出了结论这些都要能回溯。不然等到线上出了问题面对一堆黑盒调用你根本无从下手。再补充一个小技巧像RAG 召回质量下降历史消息裁剪误伤关键信息这类问题很多都藏在上下文拼装的细节里。你可以在开发环境准备一组固定任务的回归测试集每次改动上下文策略之后就全量跑一遍确保优化了一处、没有砸掉三处。这比任何理论都实用。
返回列表