
前一阵子帮一个做内部运维工具的团队做技术复盘他们原本那套 RAG 问答系统上线大半年文档检索体验一直挺稳。直到业务方提了一串新需求——“帮我看下这个工单为什么卡了一个小时没流转”“能不能直接列出这个变更影响了哪几条链路”——团队发现光靠检索文档已经顶不住了。这不是模型变笨了而是我们把大模型用得太窄了。传统 RAG 把所有问题都当成“在文档里找答案”可真正的 Agent 场景需要的是把文档知识、业务状态、工具能力、历史轨迹放在一起动态拼出适合当前任务的一份上下文。这个拼装过程现在有个更准确的名字——上下文工程。这篇文章我想聊聊从 RAG 升级到 Agent 的过程中为什么上下文工程会成为核心瓶颈以及当数据规模变大、关联变多的时候为什么普通向量数据库不够用要把向量索引放到数据湖的体系里去。文章会给出一个可以直接参考的落地管线以及我在实际推进里踩过的几个坑。适合正在做 RAG 增强、或者正准备把问答系统往 Agent 方向升级的团队。1. 从拼装上下文到构建上下文RAG 为什么会被 Agent 逼到墙角1.1 传统 RAG 的三个步骤以及它最容易被忽略的天花板标准 RAG 的流程可以压缩成三步离线把文档切块、向量化、写入索引在线用 query 去索引里召回 TopK把命中的文本块拼进 Prompt交给模型生成。这个流程在“知识型问答”场景下确实非常能打。比如内部制度、产品文档、FAQ问题基本是“某个流程是什么”“某个字段怎么填”答案就藏在某个段落里召回准确率稍微差一点模型也能靠理解能力兜底。但一旦问题从“是什么”变成“为什么我的订单卡住了”“这个任务现在卡在哪一步”RAG 就开始露怯。我见过最多的情况是系统成功检索到了退款政策文档却没有去查订单状态检索到了任务操作手册却没有看任务实际流转日志。因为传统 RAG 的索引里存的是文档切片检索条件只有一个 query它根本没有能力把结构化的业务状态拉进来。更深一层的问题在于分块。很多团队为了让 embedding 效果稳定喜欢固定 512 个 token 切块。但一个完整业务流程经常被切成两截前半截在讲条件后半截在讲例外模型只捞到后半截就很容易答错。这是切块粒度与语义完整性之间的经典矛盾也是传统 RAG 第一个要被 Agent 打破的围墙。1.2 Agent 真正缺的不是“知识”而是“状态、行动、上下文”Agent 和 RAG 最大的区别在于目标不同。RAG 的目标是“回答一个问题”Agent 的目标是“完成一个任务”。完成任务意味着需要拆解步骤、调用工具、读取状态、维护中间变量、在失败时重试。这些能力对应到模型输入上就变成对上下文的四种新要求状态上下文当前任务进行到哪一步哪些变量已经从接口拿回来了哪些还在等工具上下文哪个工具可用入参出参的 schema 是什么校验规则是什么记忆上下文这个任务里已经执行过哪些动作出现过的关键信息是什么知识上下文从文档和资料中检索到的证据片段。传统 RAG 只具备第四类而且连第四类都做得比较浅。Agent 要求系统一次性把四类上下文按合理的优先级组装起来还不能超出模型的窗口限制。这就是“上下文工程”真正要解决的问题。换个生活化的类比如果把 Agent 比作一个新员工知识库只是书架上下文工程则是带教导师在开会前提前整理的“背景材料包 待办清单 已有进度 可用工具”。新员工厉害不假但你只给他一堆书他连公司当前的项目状态都不知道再强的推理能力也发挥不出来。上下文工程就是负责把这份材料包准备得恰到好处的那个角色。2. 向量数据湖普通向量数据库承接不住 Agent 的上下文2.1 向量数据库解决了“快”但没有解决“齐”和“活”很多人问我Agent 上下文一多直接用向量数据库不行吗数据量小、业务单一的场景下完全可行。可一旦业务复杂起来向量数据库的三个短板就会显露出来。第一是“不齐”。向量数据库擅长存文本块却不太擅长表达“实体 - 关系 - 状态”这种结构。比如一个工单它的状态在业务表里备注在另一个日志流里同类故障的知识又散在文档库里Agent 要的是三者的关联结果而向量数据库只能给你其中一类。你当然可以硬把所有东西都拼成一个文本再向量化但只要状态一变整个文本就要重新嵌入代价很高。第二是“不活”。向量索引的更新通常很重文档改了或者状态变了往往要重建索引。业务状态是分钟级变化的比如“工单当前等待审批”和“工单已经审批通过”只差一个字段但语义完全不同。全量重建根本跟不上这种频率结果就是 Agent 拿到了一个已经过期的“状态快照”。第三是“不透明”。向量数据库几乎没有治理概念数据从哪来、血缘是什么、过期没有、谁改的都不好追溯。Agent 场景一旦出问题你连“模型当时到底看到了什么”都说不清。这个成本在调试阶段会被无限放大。2.2 向量数据湖怎么和上下文工程结合向量数据湖不是让你抛弃向量数据库而是一种更底层的数据组织方式。所有数据先进湖湖里存两条路一条是结构化字段一条是向量字段。二者共用同一套表结构、同一套权限、同一套更新机制。打个比方向量数据库像一个小书柜所有文档按相似度排列找书很快但书柜不知道这本书是谁买的、借给谁了、库房里还剩几本。向量数据湖更像一个有库存系统的仓库进货、入库、质检、上下架是标准流程书架只是出货前的临时摆放点。你关心的是整条供应链而不是最后那一下的摆放顺序。技术形态上可以用数据湖表格式存储加向量列实现再配一个异步向量索引构建任务。新数据到达湖里后由更新任务计算 embedding写进向量索引业务状态字段变更时即使 embedding 不变也能用同样的实体 ID 把最新状态带进上下文装配阶段。这个设计的关键在于向量只是入口真正的上下文来源是结构化状态和实体关系。2.3 上下文服务的四层结构存储、索引、装配、投递把上下文工程当成一个系统来看我习惯拆成四层存储层统一承接文档、业务实体、日志、工具结果保留历史版本。这一层解决“有没有”的问题索引层同时提供向量索引、全文索引和 Metadata 过滤按业务域分 partition。这一层解决“找得到”的问题装配层根据 Agent 当前的任务和实体 ID决定检索什么、拼什么、拼多少。这一层解决“拼得好”的问题投递层把装配结果按 Token 预算裁剪、排序、格式化成模型友好的结构。这一层解决“喂得下”的问题。这四层里面最容易被忽略的是装配层。很多 RAG 项目直接跳到“检索 拼接”跳过了“规划”。正确的做法是先判断任务属于哪一类——查事实、做操作、做对比、跟进度——再决定要引用哪几类来源最后才是执行检索和拼装。装配层没有想清楚下面的存储和索引建得再好出来的上下文也是一锅乱炖。3. 上下文工程落地一条可复用的完整实现管线3.1 数据接入先解决实体统一再谈向量化我用一个非常常见的运维工单场景来走完整条管线。假设 Agent 要做三件事解释工单是什么问题、判断影响范围、给出处理建议。这三件事分别需要工单描述和备注文本、资产关系数据结构化、历史相似工单向量检索。第一步不是建索引而是定实体。所有数据到湖里时都统一打上 entity_id比如工单号再带上 event_time、source_type。工单、日志、资产、知识库文档全部靠这个公共 ID 关联。这一步做不好后面所有多源上下文都是空中楼阁因为检索出来两块数据你根本不知道它们是不是在说同一件事。实际做法是先画一遍“任务需要哪些数据”再为每一份数据确定它挂在哪个实体下。比如“判断影响范围”需要资产表和变更记录那就让变更记录也带上资产 ID“给出处理建议”需要知识库和相似工单那就让历史工单也带上故障类型标签。实体统一之后向量化才有意义。3.2 向量化和索引策略分块、模型、索引参数怎么配分块建议按语义边界而不是固定长度。我的常用做法是先按标题和段落切超过阈值再二次切。块长设在 256 到 512 token 之间overlap 设 32 到 64。这里面的取舍很直接块越大单块信息越完整但召回精度越差也越占 Token 预算块越小精度上去了但语义可能被切断模型要靠前后块才能理解完整逻辑。Embedding 模型选型上中文场景优先选中文效果好的通用模型维度在 768 到 1024 左右比较均衡。不要一上来就选超大模型向量维度和检索耗时、存储成本直接挂钩。如果你同时要存文本向量和配图向量建议分开建索引不要混在一个向量列里否则相似度计算会被没意义的维度干扰。索引参数方面HNSW 这种图索引可以设 M16、efConstruction200。召回阶段用 es 相对高的配置保证召回率拿到 TopK 结果后再做一次重排序从 20 条里筛出 5 条真正有用的。索引按业务域分 partition比如工单域、知识域、资产域分开冷热数据也分开历史工单可以降到低频存储避免拖慢在线检索。3.3 装配器核心逻辑路由、检索、拼装、预算控制装配器是整个上下文工程的中枢。核心流程可以用一段伪代码来描述def assemble_context(task, entity_id, state, history): plan route(task) # 判断任务类型查证型/操作型/跟进型 ctx {} # 1. 拉取结构化状态永远先于文本 ctx[state] fetch_state(entity_id) # 2. 根据状态决定检索 query而不是用用户原文 queries plan.build_queries(task, state) hits vector_search(queries, top_k20) hits rerank(hits, task, top_k5) # 3. 调用工具并入参出参校验 ctx[tool_schema] fetch_tool_schema(plan.tools) ctx[tool_result] call_tool(plan.tools, entity_id) # 4. 压缩历史轨迹控制长度 ctx[memory] summarize_history(history, max_tokens500) return budget_control(ctx, max_tokens2000)这个流程里有两个关键技巧。一是 query 改写用户问“工单为什么卡着”装配器解析出“statuspending 且等待时长超过 N 小时”用这个结构化条件去过滤而不是拿原话去全文检索。二是 budget_control先分配硬预算比如系统指令 500 token、状态 200、工具 schema 300、证据 800、记忆 200超出后从低优先级砍起优先保状态和证据。预算控制的优先级顺序很重要。我的经验是结构化状态永远排在第一位因为它是最新的事实其次是工具返回结果再次是记忆摘要检索证据排最后。原因是检索证据的错误容忍度最高模型能对证据做一定程度的纠错但状态和工具结果是硬事实错了就是错了。3.4 反馈闭环让上下文越用越准最后一步是把 Agent 的执行结果回收进湖里。用户采纳了处理建议就把这条完整上下文打上“effective”标签作为未来分块和检索的正样本用户改了答案就把改写后的结果作为负样本。这些样本攒多了就可以做一次分块或者检索策略的回测。比如发现某类问题经常因为“缺少上一步的审批状态”而失败就可以在装配器里为这类任务强制插入状态字段而不是去改 Prompt。这个机制比调 Prompt 有效得多因为它是改在数据链路层面而不是指望模型自己理解。每两周建议做一次批量回放收集这段时间的生产失败案例用当前的装配器重跑一遍看失败率有没有下降。如果没有就去检查是不是某些分块一直没更新或者检索策略已经跟不上业务变化。把反馈闭环跑起来上下文工程才算真正“活了”。4. 常见问题与排查实录四个高频坑的定位与解法4.1 召回结果看着相关模型就是不采纳这类问题的现象非常有迷惑性用相似度排序时前几条结果肉眼看着“沾边”但放在一起之后逻辑断裂模型总回一句“根据现有信息无法确认”。我排查过好几个案子最后基本都落在同一个根因上分块边界切断了因果链条或者关键词语义和业务语义没有对应上。我的处理方法是开离线批测把所有失败案例的上下文快照拉出来逐个看“模型当时读到的到底是哪几块”。解决手段通常有三个调分块策略改按标题和段落语义切加规则过滤把与任务实体无关的块直接丢掉引入重排序模型在 TopK 基础上按任务相关性重新排序。重排序能提升准确性但会引入额外延迟高频场景要评估清楚再上。4.2 上下文太长Token 预算很快就爆炸这个坑几乎每个人都会踩。原因很简单装配器觉得“多给点总比少给好”于是把能搜到的证据全塞进去。结果模型窗口很快就满了还出现“长篇大论把关键状态淹没”的现象。建议先建一个预算模型。比如总窗口是 8k模型输出至少留 1.5k那么上下文可用的就是 6.5k。系统指令固定 1k工具 schema 和状态各 0.5k剩下的 4.5k 才轮得到证据和记忆。证据进入之前先过一道评估如果任务需要对比多个工单证据可以放多点如果只是查单一状态证据上限控制在 1.5k 就够。设置完预算之后用影子模式跑两周对比是否出现“上下文缩短但效果反而变好”的情况这种情况我遇到不止一次。4.3 数据已经变了Agent 还在用旧上下文曾经有个场景用户已经在界面上看到“工单状态已更新”但 Agent 检索出来的还是变更前生成的文本快照。原因是文本快照在工单状态变化时没有同步触发重建而检索索引里存的是旧版本。排查时先看数据管线业务库到湖、湖到向量索引、索引再到装配器每一段都有缓存和延迟。我的经验是给每一条上下文记录加上 version 和 timestamp装配时拿着实体 ID 去校验最新状态如果状态变了但文本快照还没更新宁愿先用结构化状态字段也不要拿旧文本凑数。等异步更新任务把新快照构建好之后再切换回文本。4.4 上下文看不到摸不着复盘全靠猜这是我在所有 RAG 和 Agent 项目里最推荐优先做的事情全链路 Trace 加上下文快照。每次 Agent 执行时把路由结果、检索 query、命中块 ID、最终上下文全文、模型输出一起存下来固定成一个统一的记录结构。出问题之后拿 Trace 做回放很快就能定位是检索、装配还是模型理解出了问题。下面这张速查表是我自己整理的遇到类似问题可以直接对着排查。表象深层原因排查路径解决方案召回结果看似相关但模型不采纳分块割裂语义、证据与任务实体脱钩查看上下文快照定位命中的块按语义边界切分、加规则过滤、引入重排序上下文过长Token 预算爆炸没有预算控制和优先级排序统计各类上下文的占比预算模型加优先级裁剪旧数据反复出现链路有缓存且没有版本校验检查各段延迟和缓存策略上下文带 version 和 timestamp状态变更优先问题无法复现改 Prompt 全靠猜没有保存上下文快照查看 Trace 日志回放执行过程全链路 Trace 加快照存档5. 衡量上下文工程做得好不好从离线指标到线上指标5.1 离线指标先跑通召回率、MRR 和上下文利用率上下文工程还不能只看最终效果因为模型能力会掩盖很多问题。我习惯拆成两个层次评估。第一层是检索质量。按业务域构建评估集每个任务标注“应该召回哪些实体、哪些文档段”然后计算召回率和 MRR。这个指标主要反映分块和向量化策略是否合理。第二层是上下文利用率。统计最终上下文里有多少 Token 真正被模型引用。上下文利用率等于被引用的证据块数除以总证据块数。如果检索回来 5 块模型只引用了 1 块大概率是召回策略太宽或者证据与任务脱钩。这个指标比对话流畅度客观得多因为它完全建立在可统计的事实上。5.2 线上指标紧盯端到端任务成功率更贴近业务的指标是端到端任务成功率。给每个任务定义一个可验证的完成条件比如“工单状态被修改且变更记录写入成功”算一次成功“给出了建议但用户没采纳且继续追问”算失败。这个指标的好处是绕过“对话是否流畅”这种主观评价直接说明上下文工程有没有帮 Agent 把事办成。如果端到端成功率上不去先不要急着调 Prompt优先去检查上下文快照里有没有关键状态缺失。我遇到过不少案例表面上像是模型回答不对实际是装配器压根没把“当前审批人”放进上下文模型只能靠猜。5.3 不要只看单点指标要用一把尺子做基线不同团队、不同场景离线指标往往没有可比性。我的做法是固定一个任务集合和一个成功率计算口径把它作为团队内部的“基线尺子”。任何新方案先拿旧方案跑出基线然后新方案必须百在成功率和延迟上同时不劣于基线才允许灰度。这套评估体系看着简单但非常有必要。上下文工程一旦做起来随之而来的就是大量的参数调整和方案对比。没有一把固定的尺子你根本没法定性判断某个改动到底是变好了还是变差了。把离线指标和线上指标配合着看才能把每一次调整的收益量化出来。结合我自己的体会上下文工程做得好不好最终要看你面对失败案例时是胸有成竹地知道它坏在哪一环还是满头雾水地反复调 Prompt。向量数据湖给的是地基装配器给的是大脑而真正让这套系统持续变好的是每一份被记录下来的上下文快照以及每次失败案例带来的策略修正。再往后的扩展方向大概率就是把这些快照变成自动化的评估集让装配器具备自我进化的能力但那是另一个更长的故事了。