RAG 完整工作原理:从文档加载、切片、Embedding、检索到生成
如果你想知道 RAG 为什么能让大模型回答企业私有知识,核心答案是:先把资料变成可检索的证据,再把检索结果交给模型生成答案。这篇文章适合已经会调用大模型 API、准备构建知识库,或需要在面试中讲清 RAG 链路的后端工程师。
RAG 不是把所有文档塞进 Prompt,也不是向量数据库加一个聊天页面。它是一条包含数据处理、检索、上下文组装和生成约束的系统链路。任何一环的输入输出不清晰,线上回答都可能失真。
RAG 解决的三个问题
大模型本身并不知道你的内部文档,也不会自动知道刚刚更新的业务规则。RAG 主要解决三类问题:
- 知识范围:把模型训练集之外的内部资料作为运行时上下文提供给模型。
- 知识时效:文档更新后重新建立索引,查询时读取当前版本,而不是等待模型重新训练。
- 答案依据:要求模型基于检索到的片段回答,并可以返回来源、文档版本和定位信息。
RAG 能减少知识缺失和部分幻觉,但不能保证答案正确。错误的切片、过期的索引、召回不足或模型忽略上下文,都会让最终答案出错。
从文档到答案的完整数据流
文档源
-> 加载与解析
-> 清洗、切片、保留元数据
-> Embedding
-> 向量库/关键词索引
用户问题
-> 查询改写(可选)
-> 向量召回与关键词召回
-> 过滤、去重、重排
-> 上下文组装
-> LLM 生成带依据的答案
-> 返回答案、来源和观测信息索引阶段通常是异步任务,重点是幂等、可重试和版本管理。查询阶段通常是在线请求,重点是延迟、权限过滤和上下文质量。两阶段不应该因为共用一个“知识库”概念,就混在同一个同步接口里。
索引阶段与查询阶段的输入输出
| 阶段 | 输入 | 关键处理 | 输出 |
|---|---|---|---|
| 加载 | PDF、Markdown、网页、数据库记录 | 解析正文并保留来源 | 文档对象 |
| 切片 | 文档对象 | 按结构和长度拆分 | Chunk + 标题、来源、版本 |
| 向量化 | Chunk 文本 | 调用 Embedding 模型 | 向量和模型版本 |
| 存储 | 向量、Chunk、元数据 | 写入索引并记录版本 | 可检索索引 |
| 召回 | 用户 Query、权限条件 | 相似度或关键词搜索 | 候选 Chunk |
| 重排 | 候选 Chunk | 相关性、时效性、权限过滤 | 上下文集合 |
| 生成 | Query、上下文、回答规则 | 约束模型输出 | 答案和引用 |
切片时不要只保存一段字符串。至少应保留文档 ID、标题路径、来源 URL、更新时间、租户或权限范围,以及切片在原文中的位置。这样才能在回答中给出可追溯来源,也能在权限变化时重新过滤。
一个生产级 RAG 系统的模块边界
一个可维护的实现可以拆成以下模块:
- Loader:负责不同文档源的读取和解析,不负责向量搜索。
- Splitter:根据 Markdown 标题、段落、代码块或 FAQ 问答边界切片,具体参数见 Chunk Size 选择指南。
- Indexer:调用 Embedding 服务并写入向量库,记录模型版本和索引版本。
- Retriever:接收 Query 和过滤条件,返回候选证据;复杂场景可以结合 检索策略。
- Reranker:在候选集上做相关性重排、去重和上下文压缩。
- Generator:只负责 Prompt、模型调用、输出解析和引用格式化。
- Evaluation:用固定问题集评估召回与回答质量,参考 RAG 评估方法。
使用 Go 构建这些模块时,可以让 HTTP 服务负责在线查询,让队列消费者负责索引任务,并使用 context 传递超时和取消信号。这里的优势不是“Go 天生更懂 AI”,而是它适合把 RAG 作为一个具备并发、重试、监控和部署边界的后端服务。
常见失败位置与排查顺序
遇到“回答不准”时,建议按数据流排查,而不是先更换模型:
- 文档是否正确解析:扫描 PDF、表格和代码块是否丢失内容。
- 切片是否保留语义:标题、条件、步骤是否被拆到不同 Chunk。
- 召回是否命中:检查 Top-K、过滤条件和文档版本,确认候选片段真的包含答案。
- 上下文是否被截断:记录最终发送给模型的上下文长度和顺序。
- 模型是否遵守规则:要求无证据时明确说不知道,并返回引用。
- 评估是否可复现:用固定问题集比较改动前后的检索和生成指标。
如果召回结果本身就没有答案,调 Prompt 通常无效;如果召回正确但答案仍然错误,再检查上下文组装、模型参数和输出解析。
