Agentic AI上下文工程实战:从检索组装到记忆管理的完整架构
如果你最近在搞 Agentic AI 应用大概率遇到过这种场景同一个大模型、同一份 Prompt 模板用户只是换了一种问法智能体就开始胡言乱语甚至把工具调用参数都拼错。我上周排查一个项目花了一整天反复调 Prompt、换温度参数最后发现罪魁祸首根本不是模型理解能力而是上下文工程没做到位。这个标题里藏着的痛点说白了就是一句话智能体能不能真正懂用户看的往往不是模型上限而是你喂给它的上下文长什么样。这篇文章不聊虚的直接把 Agentic AI 上下文工程这件事拆开揉碎。你会看到一套完整的架构思路从检索层、组装层到状态记忆层到底应该怎么设计也会看到一个能直接抄作业的技术栈组合FastAPI 做接口层、LangChain 和 LangGraph 做编排、RAG 做知识注入、pgvector 做向量存储这套方案我实测下来是当前性价比最高的落地组合之一。不管你是刚开始接触智能体开发还是已经在生产环境里被上下文问题折磨过这篇文章都值得看完。1. 智能体听不懂话的根子往往不在模型而在上下文1.1 一个真实的翻车现场换了个检索逻辑整条链路全乱先说个我自己的案例。项目是个企业知识库问答智能体技术栈就是 LangGraph 搭的 Agent知识库走 RAG。最开始一切正常后来我优化检索逻辑把单路向量检索换成了向量 关键词混合检索想着召回更全智能体应该更聪明才对。结果上线没多久就收到反馈用户问上季度的销售数据为什么下滑智能体居然回答了一堆关于销售流程规范的内容完全答非所问。我查遍了日志发现检索层确实召回了销售数据相关的文档问题出在上下文组装环节混合检索把一堆不相关的流程文档也带了进来而且排序在前面。LLM 看到这些噪音注意力被稀释了最终回答了最显眼的那部分内容。这个案例说明一个很扎心的事实检索只是上下文工程的一部分召回之后怎么裁剪、怎么排序、怎么表达同样决定智能体的理解质量。类似的翻车现场做 Agent 的人应该都不陌生。用户问 A智能体答 B多问两轮就开始记忆错乱工具调用参数偶尔正确偶尔离谱。大多数人第一反应是换更大的模型、改 Prompt但实测下来效果很有限。真正的问题往往出在上下文本身该有的信息缺失、不该有的噪音太多、关键信息被淹没。1.2 提示词工程、RAG 与上下文工程三者的边界重新理一遍很多人把这三个概念混在一起用实际上它们解决的问题层次完全不同。提示词工程解决的是怎么问的问题核心是把需求描述清楚让模型知道自己的角色、任务和输出格式。它对单轮对话特别有效但对 Agent 这种多轮、动态的环境光靠提示词远远不够因为聪明的提问盖不住喂进去的垃圾信息。RAG 解决的是喂什么资料的问题核心是把外部知识检索出来拼进 Prompt。但 RAG 有一个隐含假设检索回来的内容都是有用的、组织好的、位置合理的。这个假设在大规模知识库场景下经常不成立检索结果可能冗余、冲突、过时甚至与用户当前意图不匹配。上下文工程解决的是整个上下文如何被构造、组织、维护、更新的问题。它关心的是系统提示词之外的更大图景检索结果怎么去重排序、对话历史怎么取舍压缩、工具调用结果怎么回填、长期记忆怎么沉淀。上下文工程是更高维度的设计它决定了模型最终看到的世界长什么样。1.3 为什么 Agent 比普通 ChatBot 更依赖上下文工程普通 ChatBot 答错了用户重开一个对话就行损失有限。Agentic AI 就不一样了它会自己决定调用什么工具、怎么拆解任务、按什么顺序执行。每一步决策都依赖上下文里的信息而且这些信息是动态累积的用户的历史意图、中间推理结果、工具返回的数据、又被追加进来的知识库文档全都在同一个上下文窗口里竞争注意力。这个场景下任何结构的混乱都会被放大。举个例子工具调用返回了一串 JSON里面有大量无关字段这些字段会占据 token 预算还可能让模型错误地解读重要信息。又比如用户前面提到了华东区后面问他们的渠道占比如果 Agent 的上下文压缩逻辑把华东区这个关键实体丢掉了后面那次工具调用必然跑偏。普通对话模型只需要理解这句话在说什么Agent 却需要理解整个任务脉络走到哪一步了这就是上下文工程成为 Agent 核心瓶颈的根本原因。2. 一条可落地的上下文工程链路从检索召回到上下文组装2.1 组件拆解检索层、组织层、表达层各自管什么我习惯把上下文工程拆成三个层次每层各司其职排查问题的时候也方便定位。检索层负责从知识库、记忆库、工具结果中获取候选信息。常见手段包括向量检索、BM25 关键词检索、混合检索、查询改写等。这一层的核心指标是查全率但不要一味追求查全候选不是越多越好。组织层负责对候选信息做裁剪、排序、去重、压缩决定最终哪些内容能进入模型上下文。这一步是上下文工程里最容易被忽略的部分。多数人做完检索直接把前 N 个结果拼进 Prompt不做任何后处理导致无关内容占用窗口、相关内容被挤出视野。表达层负责决定信息以什么格式呈现。同样的信息用纯文本段落、Markdown 分节、XML 标签包裹还是 JSON 结构化效果差异很大。模型对结构敏感把关键实体、指令、参考资料用明确标记圈出来比让它在长文本里自己找要可靠得多。这三层之间是流水线关系检索层粗糙一点没关系组织层和表达层做得好最终效果依然可控。反过来如果组织层和表达层没有设计检索层再精细也白搭。2.2 上下文窗口预算分配128K 上下文怎么切才够用上下文窗口是 Agent 最稀缺的资源没有预算概念上下文工程就是耍流氓。以 128K 上下文的模型为例我一般这么分配内容模块预算占比说明系统提示词与工具定义10% ~ 15%角色设定、工具 Schema、全局规则检索到的知识库内容20% ~ 30%RAG 检索结果经过重排压缩短期对话历史30% ~ 40%最近几轮完整消息更早的做摘要中间推理与工具调用结果10% ~ 15%当前任务链路上产生的中间数据当前用户问题5% ~ 10%用户的实时输入预留输出空间10% ~ 15%给模型生成答案留出余量你以为的预算分配和模型实际看到的分配可能根本不是一回事。比如工具定义带了大量 description 和示例可能悄悄吃掉 20% 以上的 token再比如检索结果动辄七八千 token直接挤占了对话历史的预算。所以我会在每次组装上下文时统计各模块的 token 消耗动态调整。实际项目里我给检索结果设了一个硬性上限超过 6K token 就触发二次压缩宁可丢一些边缘信息也不能让检索内容反客为主。2.3 结构化表达XML 标签、元数据与顺序的艺术同一段内容给模型看的方式不同理解效果天差地别。我实测下来结构化表达是性价比最高的优化手段之一。把检索到的知识库内容用明确的标签包裹起来效果立竿见影。例如knowledge_source metadata source2024年Q3销售总结.pdf/source page12/page relevance0.91/relevance /metadata content 华东区Q3销售额环比增长12%主要驱动因素是新渠道拓展。 /content /knowledge_source这样做的逻辑是模型在长文本中定位信息的能力远没有我们想象的强标签和元数据相当于给关键信息做了路标。我在项目里加了一层 XML 封装之后引用来源的正确率明显提升幻觉引用也少了很多。再一个是顺序问题。LLM 存在中间丢失现象对上下文开头的信息和结尾的信息记忆更强对中间位置的内容理解最弱。所以我会把当前用户问题放在上下文的末尾紧邻生成位置把最重要的知识内容放在开头或接近末尾的位置牺牲中间位置给次要内容。不要小看这个细节它经常决定智能体会不会答非所问。3. 实战架构FastAPI LangChain LangGraph RAG pgvector 的 Agentic RAG3.1 技术选型为什么是这套组合而不是别的市面上 Agent 框架很多Dify、Coze、AutoGen、CrewAI 各有拥趸。但我做生产级项目更倾向于自组装原因很简单可控性。框架的抽象层越厚上下文被框架黑盒化的程度越高出了问题越难排查。FastAPI 提供异步接口层流式输出和并发处理能力强生态成熟Agent 服务做网关还是做微服务都能撑住。LangChain 的价值在于它积累了丰富的组件抽象文档加载器、text splitter、retriever、嵌入模型接口都能即插即用省去大量重复代码。LangGraph 是 LangChain 团队做的图式 Agent 编排框架它把 Agent 拆成节点和边的状态机每一步上下文变化都能在状态中追踪到这对上下文工程来说至关重要。RAG 解决知识注入问题。pgvector 则是把向量检索放进 PostgreSQL 里业务数据、元数据、向量数据放同一套库省掉组件间的数据同步运维成本低一大截。有人问我为什么不直接用字节的 Coze 或者开源 Dify 的编排能力。我的考虑是这类平台适合快速原型验证但到了生产环境的精调阶段平台封装好的上下文处理逻辑反而成了瓶颈。而 FastAPI 这套组合每一层都是透明的哪里出问题改哪里。3.2 核心工程骨架与数据流下面这个数据流是我一个生产项目里精简出来的核心链路用户请求经 FastAPI 进入 LangGraph 图首节点做查询改写把原始问题重写为更适合检索的表述随后进入检索节点同时执行向量检索和关键词检索用 RRF 融合后取 Top K再进入重排节点用 Cross-Encoder 对候选文档精排只保留与问题最相关的少数文档然后组装上下文把系统提示词、检索文档、对话历史、工具定义按预算拼接Agent 节点判断是否需要调用外部工具如果需要工具则执行调用并把结果回填到状态中后再走一次直到 Agent 生成最终答案。LangGraph 的状态定义是核心我通常这样写from typing import Annotated, TypedDict from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages from langgraph.checkpoint.memory import MemorySaver from langchain_core.messages import BaseMessage class AgentState(TypedDict): messages: Annotated[list, add_messages] query: str rewritten_query: str retrieved_docs: list tool_results: list final_answer: str def retrieve_node(state: AgentState) - dict: docs hybrid_search_with_rerank( querystate[rewritten_query], top_k_retrieve30, top_n_rerank5 ) return {retrieved_docs: docs} def assemble_context(state: AgentState) - dict: system_prompt build_system_prompt() context_parts [] for doc in state[retrieved_docs]: context_parts.append( fknowledge_source\n{wrap_metadata(doc)}\n{wrap_content(doc)}\n/knowledge_source ) user_message build_user_message( querystate[query], context\n.join(context_parts), tool_resultsstate[tool_results] ) return {messages: [user_message]} def agent_node(state: AgentState) - dict: # 这里会做工具调用判断如果需要工具则返回工具节点路由 ...整个过程的状态都显式定义在AgentState里每次节点的输入输出都能在 LangGraph 的检查点checkpoint中看到。排查上下文问题的时候我只需要看某一步状态里的 retrieved_docs 和 messages 就知道问题出在哪这种可观测性是黑盒编排平台给不了的。3.3 检索质量的两个关键旋钮分块策略与混合检索很多 Agent 项目死就死在分块上。文档切成小块检索精度高但缺上下文切成大块上下文完整但噪音多。我在实际项目里测试了几种方案后比较推荐父子分块策略父块用较大的语义单元比如一个章节级别子块切成几百 token 的小段用于检索检索命中子块后把所属的父块内容作为上下文返回。这样既保证检索的精准度又保证上下文足够完整。混合检索是我现在做 RAG 的默认配置。纯向量检索对语义相似的表达效果好但对精确术语、产品型号、人名这类文本不敏感因为嵌入模型对这些词的向量表达往往区分度不够。我用 BM25 关键词检索兜底再通过 RRF 融合两种结果。RRF 公式比加权平均更稳score(d) Σ 1 / (k rank_i(d))通常 k 取 60。它不依赖具体打分尺度只依赖排名避免了不同检索器分数不好归一化的问题。pgvector 建表的时候索引参数设置很关键。我线上用的配置参考如下CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE knowledge_chunks ( id BIGSERIAL PRIMARY KEY, content TEXT NOT NULL, metadata JSONB, embedding vector(1024) ); CREATE INDEX ON knowledge_chunks USING hnsw (embedding vector_cosine_ops) WITH (m 16, ef_construction 64);m16, ef_construction64是我在召回质量和索引构建耗时之间的折中选择。向量维度 1024 对应目前主流的 BGE 和 text-embedding-3 系列模型。HNSW 的 ef_search 在查询时通过hnsw.ef_search设置我调大召回收益时会把 ef_search 临时调高做线上低延迟查询时调回默认。3.4 查询改写与重排序召回之后还要做减法用户提问很少直接适合检索。比如用户问那个新功能什么时候上线的直接拿这句话去做向量检索效果很差因为那个新功能指代不明。此时需要用 LLM 做一次查询改写把模糊表达转成具体的检索语句。我常用的改写策略有两类一类是规则化的意图转换提取关键词和实体再重新组句另一类是 HyDE 思路让模型先基于已有对话历史生成一个假设答案用假设答案去检索再用检索到的真实文档修正。HyDE 在处理模糊问题上效果显著代价是多一次生成调用需要在延迟可接受的前提下用。召回之后的重排序同样重要。初步检索出 Top 30 的候选文档里可能只有三四条真正有用。如果不做精排直接把前五条塞进上下文很可能有几条低相关文本霸占位置。我习惯用 Cross-Encoder 模型做重排它把查询和文档一起编码交互式计算相关性精确度远高于向量检索的双塔相似度。常用的开源模型有 BAAI/bge-reranker-large、bge-reranker-v2-m3配置起来不复杂from langchain_community.cross_encoders import HuggingFaceCrossEncoder reranker HuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-large) pairs [(query, doc.content) for doc in candidate_docs] scores reranker.score(pairs) # 按 scores 排序只保留 top_n 进入上下文重排本质上是把排序权重新交还给一个更精确的模型用多出来的算力换上下文质量。我在实际项目中重排后只保留 Top 3 到 Top 5 的文档上下文干净了模型生成质量反而提升。4. 状态与记忆多轮 Agent 上下文不崩的底层设计4.1 短期状态LangGraph 的消息列表与状态 ReducerAgent 的多轮对话中上下文是流动的。LangGraph 的add_messagesreducer 就是为这种流动设计的每次节点返回新的消息reducer 会把新旧消息合并并按消息 ID 去重而不是简单覆盖。我用它维护 messages 字段效果很稳。但消息列表不能无限增长。对话到第 20 轮的时候完整历史可能已经吃掉几万 token继续全量传给模型既不经济也容易让模型迷失在冗长上下文中。我的做法是引入滑动窗口策略对话超过 8 轮后把最早的消息做一次摘要压缩只保留关键实体、用户意图和已确认的约束条件最近的几轮保持原样。摘要由 LLM 生成存放在单独的 summary 字段里。这样短期状态就变成了早期摘要 近期原始消息的组合结构。4.2 长期记忆向量记忆、实体记忆与摘要记忆怎么搭配跨会话的长期记忆是 Agent 进阶的必经之路。用户上次提到我是华东区的负责人下一次会话这个信息应该被自动带入。我用三种记忆搭配向量记忆负责存经历型内容。每次对话结束后把关键信息用户偏好、项目背景、决策结论写进专门的记忆知识库下次检索时召回相关历史。实体记忆负责存结构化事实适合用 KG 或关系型结构维护用户和项目的关键属性比如所属区域、职位、常用工具。摘要记忆则定期对某个用户的历史对话做全局总结作为系统提示词的一部分注入。这三种记忆的读取优先级是实体记忆 摘要记忆 向量记忆。实体信息确定性强直接拼接进上下文摘要记忆提供背景向量记忆用于回答需要回溯细节的问题。优先级的设计背后逻辑是确定性越强的信息越该被显式注入模糊的信息则放在检索层按需获取避免所有记忆一股脑地挤进上下文反而让模型混淆。4.3 记忆写入的克制原则记忆设计最大的坑不是读不出来而是写太多。曾经有一版记忆模块每次对话结束都把整段对话摘要存入向量库结果用户第二次会话时检索出一堆过期信息智能体的回答反而被过去的自己误导。后来我定了三条写入原则只存对后续交互可能有用的信息对时间敏感的信息附加时间戳检索时按时间衰减排序每类记忆写之前必须经过 LLM 的判断确认价值确认值得写否则丢弃。克制的写入让记忆库保持精简也让读取时的信噪比维持在高位。这里要特别提醒一句记忆的持久化如果不做权限控制和敏感信息过滤会有合规风险。我的项目里写入前会先跑一道脱敏规则把手机号、身份证号这类个人敏感信息摘掉或泛化既符合数据安全要求也避免这些信息被后续检索误用。5. 上下文工程里那些不踩一遍不会懂的坑5.1 上下文越全越好中间丢失现象告诉我们不是上下文工程新手最容易犯的毛病是什么都想给模型看。工具定义写一大段、知识库文档十条全塞进去、对话历史一字不落结果上下文窗口倒是占满了智能体的回答质量却急转直下。我见过一个同事把 20 页产品说明书全塞进上下文让模型根据用户问题找相关内容回答时引用了完全错误的章节。这就是中间丢失现象的典型表现。LLM 对上下文不同位置的敏感度不一致学术界已经验证过在长上下文中模型对开头的信息记忆最好对结尾次之对中间部分记忆最差。上下文越长中间部分的信息越容易被掩盖。所以上下文工程不是在给模型喂得饱饱的而是在给它喂得准准的。删掉无关信息、把关键信息移到显眼位置比增加信息更有效。我在每个节点的组装逻辑里都加了一道裁剪步骤用规则和模型双重判断某种内容是否与当前 step 相关不相关的直接丢弃。5.2 RAG 召回一大堆却变笨top-k、重排与上下文压缩RAG 系统一个常见误区是追求召回多。Top 10 不如 Top 5Top 5 不如 Top 3原因很简单召回的文档越多文档间存在矛盾的概率越大模型要在更多候选里做选择出错率自然上升。我用重排后只保留 Top 3 到 Top 5 的效果最稳定前提是重排模型够准。如果单篇文档很长即使只保留三篇也可能有几千 token此时需要上下文压缩。压缩方式我试过两种抽取式压缩提取文档中最相关的句子组合成摘要优点是保真度高缺点是有时会丢失逻辑关联生成式压缩让 LLM 针对当前查询重写一段精简说明优点是信息密度高缺点是有时引入幻觉内容。实际项目里我采用混合策略对事实性要求高的内容走抽取式对背景铺垫型内容走生成式效果比单一方案好。5.3 工具结果和知识库打架冲突消解策略Agent 调用工具返回的数据和 RAG 检索到的知识库内容有时会互相矛盾。比如知识库说产品支持某个功能工具返回的接口结果却报错说不存在。这种冲突如果同时出现在上下文里模型很可能给出自相矛盾的答案。我在 LangGraph 的组装节点里为上下文数据增加数据来源优先级顺序是实时工具结果 用户明确陈述 知识库文档 模型内部记忆。同时把矛盾的数据用标签区分开例如用data_source typetool和data_source typeknowledge包裹并在系统提示词里明确告知模型当工具结果与知识库内容冲突时以工具结果为准并提示用户可能存在信息不一致。这套规则解决了我项目里大半的智能体精神分裂问题。它背后的逻辑并不复杂实时数据永远比静态文档更接近当前事实关键是你得把这个优先级显式告诉模型而不是指望它自己去判断。6. 把上下文工程从玄学变成科学评估与回归6.1 评估指标拆解检索、生成、Agent 行为三层上下文工程改一版效果是好是坏不能靠感觉必须有量化指标。我从三个层面建立评估体系。检索层看 RecallK 和 MRR。我固定一组标准查询集每个查询标注了准确的相关文档 ID跑完检索引擎后统计召回率用 Recall5 和 Recall10 衡量检索能力是否退化。生成层看答案的忠实度和相关性。忠实度衡量答案是否严格基于给定上下文避免模型编造知识库没有的信息相关性衡量答案是否直接回应了用户问题。我采用 LLM-as-judge 的方式用更强的模型当裁判对生成结果打分用一组固定评测集跑回归。Agent 行为层最贴近业务工具调用成功率、任务完成率、平均轮数、无效工具调用次数。这个层级的指标直接反映上下文工程对 Agent 实际决策的影响也最容易暴露工程缺陷。比如工具调用成功率突然下降往往就是上下文里工具描述占的 token 被压缩过度模型看不懂工具入参了。6.2 搭建评测集与回归测试流程我的做法是维护一个评测即数据的仓库里面包含三类数据数据类型数量来源标准知识问答200 条从线上日志挑选典型问题多轮对话场景50 条覆盖指代消解、意图切换、状态保持工具调用场景80 条覆盖参数生成、多工具协同、工具报错恢复每次代码变更后跑一遍评测 pipeline对比新旧版本在三个层面的指标差异。指标下降的变更直接拉警报不达标不上线。这个仓库我坚持维护了两个月积累到 300 多条高质量用例后上下文工程的迭代速度明显加快因为每个改动都能看到量化的效果反馈。评估是最值得投入的部分它把上下文工程从靠经验调参变成了靠数据迭代。对 Agent 这种系统没有评估体系你永远不知道一次上下文改动是优化还是恶化。最后再分享一条我个人的体会上下文工程做久了你会发现它像一个放大镜放大的是模型的能力边界。上下文组织得好小模型也能迸发出惊艳的效果上下文组织得烂再大的模型也会被噪音拉下水。如果你正在做 Agentic AI 项目建议先把注意力从模型选型上挪开认真梳理一遍自己的上下文链路检索回来的是什么、组装进去的是什么、顺序和标签是否合理、历史记忆是否克制。把这一层做到位你会发现智能体的理解问题解决了大半。