RAG 不是向量搜索:从 Ingestion、Metadata Filter 到 Citation 的工程链路
RAG 不是向量搜索从 Ingestion、Metadata Filter 到 Citation 的工程链路很多人第一次理解 RAG容易把它简化成一句话把文档切片做 embedding放进向量库用户提问时搜出来再让大模型回答。这个描述不能说错但它太容易让人误解。真正落到工程里RAG 不是一个“向量搜索功能”而是一条从数据导入、知识切分、作用域过滤、上下文构建、答案生成、引用追溯到质量评测的完整链路。如果只盯着 embedding 和 vector search很容易忽略更常见、更危险的问题检索到了语义相似但业务无关的内容当前 workspace 里有多个项目系统拿错了项目上下文top-k 返回了很多 chunk但真正应该进入 prompt 的证据并不干净答案引用了真实来源但来源并不支持结论回答错了却不知道错在检索、上下文、生成还是引用。这篇文章想讲清楚一件事RAG 的核心不是“搜到相似内容”而是“在正确范围内找到能支撑回答的证据并让答案可追溯、可评测、可修复”。1. RAG 为什么不能只是“多塞资料”最朴素的想法是既然模型需要上下文那就把相关资料都塞给模型。但这在真实系统里很快会遇到问题。第一context window是有限的。即使模型支持很长上下文也不代表应该把所有材料都放进去。第二上下文越多噪音越多。模型可能被弱相关内容带偏。第三不同文档之间可能存在冲突、版本差异或适用范围差异。第四长上下文会造成注意力稀释。真正关键的证据可能被淹没在大量普通信息里。所以 RAG 的目标不是“把资料尽可能多地塞给模型”而是在有限上下文里放入最相关、最干净、最能支撑回答的证据。这也是为什么 RAG 需要一整条工程链而不仅仅是向量搜索。2. Ingestion 阶段用户提问前已经发生了什么RAG 系统通常不是等用户提问时才开始处理文档。很多工作在用户提问前就已经完成了这一阶段通常叫ingestion或indexing。一个典型 ingestion 流程大致是原始文档Markdown / Skill / README / 代码Parser解析结构Chunking切成知识片段Metadata Extraction提取元数据Embedding生成向量Index存储 content vector metadata这一步的关键是系统不是只保存向量。一个 chunk 至少应该保存三类信息类型例子作用内容chunk 文本给模型作为证据向量embedding用于语义检索元数据project、source_path、heading、chunk_id用于过滤、追溯和评测比如一个 skill 文档里的片段可以被索引成{chunk_id:learning-companion.skill.teaching-protocol.001,source_path:skills/learning-companion/SKILL.md,project:learning-companion-skills,repo_url:https://github.com/huajiexiewenfeng/learning-companion-skills,heading:Teaching Protocol,chunk_type:protocol,content:Use tutor mode when the user asks for teaching...}这里的 metadata 不是附属信息。它决定了后续查询时系统能不能在正确范围内找证据。3. Query 阶段用户提问时真正发生了什么用户真正提问时系统进入 query-time 流程。这时文档通常已经被切好、标好 metadata、生成 embedding并存入索引。查询阶段大致是用户问题Query EmbeddingMetadata Filter限定查询范围Vector / Hybrid SearchTop-k ChunksContext BuilderLLM AnswerCitationEval / Trace这里有一个很容易混淆的点metadata filter 通常不是在 embedding 前做的而是在查询阶段用来限定本次检索范围。metadata 在 ingestion 阶段已经被生成和存储。用户提问时metadata filter 用来告诉系统这次查询应该在哪些项目、哪些文件、哪些类型的 chunk 里找。4. Metadata Filter 不是删除数据而是限制本次查询范围假设一个向量库里同时有多个项目的数据learning-companion-skillsagent-global-contextproject-coding-skillsthinking-skills这些项目都可能出现一些相似词skillcontextreviewmemorydashboardcommittrace如果只靠 embedding系统可能会召回语义上相似、但业务上不相关的内容。比如用户问learning-companion是否支持老师模式正确的检索范围应该类似{project:learning-companion-skills,skill_name:learning-companion,source_type:skill}这并不是把其他项目的向量数据从库里删除。agent-global-context、project-coding-skills的向量还在索引里。只是这一次查询不应该让它们参与召回。所以更准确地说metadata filter 是查询时的作用域约束。它解决的是一个非常重要的问题向量相似不等于业务相关。5. Embedding 解决语义相似Metadata Filter 解决业务范围正确可以把 embedding 和 metadata filter 的分工理解成这样机制解决的问题不能解决的问题Embedding语义相似项目边界、权限边界、业务作用域Metadata Filter查询范围正确语义相关性排序Context Builder组织最终上下文原始索引质量Citation答案可追溯检索本身是否正确embedding 会告诉你哪些 chunk 和这个问题语义接近。metadata filter 会告诉你哪些 chunk 有资格参与这次查询。这两个缺一不可。如果没有 embedding系统只能做关键词或规则匹配召回质量会受限。如果没有 metadata filter系统可能召回语义相似但项目错误的内容。6. 一个真实的 Agent 执行风险拿错项目上下文在普通问答型 RAG 里错召回可能只是导致答案错误。但在 Agent 场景里错召回可能导致更严重的问题操作错文件、提交错仓库、推送到错误 GitHub 项目。想象一个 workspace 里同时存在多个仓库一个是agent-global-context一个是learning-companion-skills还有若干文章、示例、学习记录这里简单介绍一下这两个项目。learning-companion-skills是一个长期学习管理 skill 项目用来把用户的学习计划转换成可追踪的 dashboard 和 map支持每日提醒、轻量老师模式、下课复盘、计划进度和有效掌握度追踪。仓库地址https://github.com/huajiexiewenfeng/learning-companion-skillsagent-global-context是一个面向 AI coding agent 的全局上下文项目用 Markdown 保存用户偏好、工程习惯、环境信息、项目知识和长期记忆让 agent 在跨 session 工作时能有选择地 recall 关键上下文。仓库地址https://github.com/huajiexiewenfeng/agent-global-context这两个项目都和skill、context、review、dashboard、memory等词有关但它们的业务边界完全不同。前者服务长期学习工作流后者服务跨 session 的 agent 全局记忆和上下文。用户说更新 learning-companion 的 README然后推送到 GitHub。如果 agent 只看当前工作目录或者只根据最近上下文判断目标很可能把当前 cwd 里的仓库当成目标仓库。这类错误不是模型“不会写 README”。它更像是scope resolution 失败没有确认目标项目是learning-companion-skillscontext selection 失败错误地把当前目录项目当成主上下文metadata filter 缺失没有用project / repo_url / workspace_path限定执行范围action target 错误在错误仓库里修改、提交和推送。用 RAG/Agent Runtime 的语言说这是project scope 识别失败导致上下文污染并进一步造成错误 action target。所以对 Agent 来说metadata 不只是帮助“回答更准”。它还影响“动作是否落在正确对象上”。7. Context BuilderTop-k 不等于最终上下文即使 metadata filter 做对了也不代表 top-k chunks 可以直接全部塞给模型。top-k只是检索结果。真正进入 prompt 前还需要 Context Builder 做二次处理。Context Builder 至少要考虑哪些 chunk 真正回答当前问题哪些 chunk 只是语义相近但不关键多个 chunk 之间有没有冲突是否超过上下文预算是否保留了 source_path、heading、chunk_id 等引用信息是否需要按任务结构重新组织证据。所以链路应该是Top-k Chunks去掉弱相关内容处理冲突和重复控制上下文长度保留引用元数据组织成可读证据上下文Context Builder 的目标不是“多放材料”而是给模型一个干净、有结构、可追溯的上下文。8. Citation让答案绑定证据当 LLM 生成回答后如果只输出结论系统很难判断这个结论是否来自证据。所以 citation 要把关键判断绑定回具体证据。一个最小 citation 可以包含{source_path:skills/learning-companion/SKILL.md,heading:Teaching Protocol,chunk_id:learning-companion.skill.teaching-protocol.001,support:该 chunk 定义了老师模式的触发词和教学步骤。}其中最重要的是support。因为source_path / heading / chunk_id只能说明证据在哪里。support才说明这段证据为什么能支持答案中的这个判断。如果一个 citation 的来源真实存在但它无法支持结论这不是“没有 citation”而是citation mismatch。这类问题常见于检索到了相关主题但没检索到真正证据chunk 粒度太大具体支撑关系模糊模型先生成结论再硬贴引用上下文污染导致模型错误归因。9. Eval / Trace把错误变成可定位的问题没有 trace 的 RAG很难持续改进。因为回答错了之后你不知道到底是哪一层错了。一个最小 trace 至少应该记录字段作用query用户原始问题query_embedding_id查询向量版本或标识filters本次 metadata filtertop_k_chunks初始召回结果selected_context最终进入 prompt 的上下文answer模型回答citations回答引用的证据failure_type失败分类这样当结果出错时可以定位现象可能问题正确 chunk 没进 top-kretrieval 失败正确 chunk 在 top-k但没进 contextContext Builder 失败正确 evidence 进了 context但答案错generation 失败citation 来源存在但不支持结论citation mismatch回答用了错误项目的文档scope / metadata filter 失败这就是 RAG 从 demo 走向工程系统的关键错误不能只停留在“模型答错了”而要能定位到具体链路环节。10. 总结RAG 不是向量搜索。向量搜索只是链路中的一环。完整的 RAG 工程链路至少包括ingestion文档解析、chunk、metadata、embedding、indexquery-time retrievalquery embedding、metadata filter、vector/hybrid searchcontext builder选择、清洗、组织最终上下文answer generation基于证据生成回答citation把关键结论绑定回证据eval / trace检查质量并定位失败原因。其中metadata filter 是很容易被低估的一层。它不是删除无关向量而是在查询时限定本次召回范围。可以记住这句话embedding 解决语义相似metadata filter 解决业务范围正确citation 解决答案可追溯。如果只做 embedding 和 top-k很容易得到一个“看起来聪明”的 RAG demo。但如果要做能服务真实 Agent 的 Knowledge Runtime就必须把 scope、metadata、context、citation 和 trace 一起设计。因为在 Agent 场景里错召回不只是答案错。它还可能意味着agent 会在错误上下文里执行动作。这才是 RAG 工程化真正需要认真对待的地方。