未命名文章
RAG 原理
RAG 的核心是先从知识库检索相关内容,再把检索结果作为上下文交给大模型回答,从而减少模型胡编,并让模型能回答私有文档里的问题。
[TOC]
RAG 是什么
RAG,全称是 Retrieval-Augmented Generation。
中文一般翻译为“检索增强生成”。
拆开看:
Retrieval:检索
Augmented:增强
Generation:生成
普通聊天是:
用户问题
-> 大模型
-> 回答
RAG 是:
用户问题
-> 检索知识库
-> 找到相关文档片段
-> 把片段和问题一起给大模型
-> 回答
重点是:
RAG 不是训练模型,而是在模型回答前给它补充上下文。
为什么需要 RAG
大模型有几个天然限制:
不知道你的私有文档
不知道公司内部数据
知识可能过期
容易编造不存在的细节
上下文窗口有限
不能直接翻完整资料库
RAG 的作用是把外部知识接进来。
比如你上传了:
产品手册
合同条款
接口文档
公司制度
课程笔记
项目 README
用户问:
这个系统的上传文件大小限制是多少?
RAG 会先检索相关片段,再让模型基于片段回答。
这样回答就不完全依赖模型记忆。
RAG 和普通 Chat 的区别
普通 Chat:
用户:接口超时是多少?
模型:根据已有知识猜一个答案
RAG:
用户:接口超时是多少?
系统:检索项目文档
文档片段:连接超时 3 秒,读取超时 30 秒
模型:根据片段回答
区别在这里:
普通 Chat:模型直接回答
RAG:模型基于检索上下文回答
所以 RAG 的质量不只取决于模型。
还取决于:
文档质量
解析质量
切片质量
Embedding 质量
检索质量
Prompt 质量
引用来源
RAG 基本流程
完整流程可以分成两条线。
先看整体图:
flowchart TD
A["用户上传文档"] --> B["解析文本"]
B --> C["清洗文本"]
C --> D["文档切片"]
D --> E["生成 chunk Embedding"] E --> F["存入向量数据库"]
G["用户提问"] --> H["问题生成 Embedding"] H --> I["向量检索相关 chunk"] I --> J["拼接上下文"]
J --> K["大模型生成回答"]
K --> L["返回答案和来源"]
F -. "提供检索数据" .-> I
第一条是入库流程:
上传文档
-> 解析文本
-> 清洗文本
-> 文档切片
-> 生成 Embedding -> 存入向量数据库
入库流程图:
flowchart LR
A["上传文档<br/>PDF / DOCX / MD / TXT"] --> B["解析文本<br/>Parser"]
B --> C["清洗文本<br/>Cleaner"]
C --> D["文档切片<br/>Chunker"]
D --> E["生成 Embedding<br/>Embedding Model"] E --> F["写入向量库<br/>Vector Store"]
F --> G["保存 metadata<br/>文件名 / 页码 / chunkIndex"]
第二条是问答流程:
用户提问
-> 问题生成 Embedding -> 向量检索相关 chunk -> 拼接上下文
-> 大模型生成回答
-> 返回答案和来源
问答时序图:
sequenceDiagram
participant U as 用户
participant API as RAG 服务
participant EMB as Embedding 模型
participant VS as 向量数据库
participant LLM as 大模型
U->>API: 提交问题
API->>EMB: 将问题生成 Embedding EMB-->>API: 返回问题向量
API->>VS: 按向量检索 topK chunks VS-->>API: 返回相关 chunks 和来源 metadata API->>API: 拼接上下文和 Prompt API->>LLM: 发送上下文 + 用户问题
LLM-->>API: 返回回答
API-->>U: 返回答案和来源
本节先理解原理。
后面会逐步做数据库、上传、解析、清洗、切片和 Embedding。
核心组件
一个 RAG 系统至少有这些组件:
Document:原始文档
Parser:文档解析器
Cleaner:文本清洗器
Chunker:切片器
Embedding Model:向量模型
Vector Store:向量库
Retriever:检索器
Prompt:问答提示词
LLM:生成模型
Source:来源信息
不要把 RAG 只理解成“向量数据库”。
向量数据库只是其中一部分。
如果文档解析和切片做得差,向量库再强也救不了。
最小组件关系图:
flowchart TD
DOC["Document<br/>原始文档"] --> PARSER["Parser<br/>文档解析器"]
PARSER --> CLEANER["Cleaner<br/>文本清洗器"]
CLEANER --> CHUNKER["Chunker<br/>切片器"]
CHUNKER --> EMB1["Embedding Model<br/>生成 chunk 向量"]
EMB1 --> STORE["Vector Store<br/>保存 chunk + metadata + embedding"]
QUESTION["用户问题"] --> EMB2["Embedding Model<br/>生成问题向量"]
EMB2 --> RETRIEVER["Retriever<br/>检索 topK chunks"] STORE --> RETRIEVER RETRIEVER --> PROMPT["Prompt<br/>上下文 + 问题 + 规则"]
PROMPT --> LLM["LLM<br/>生成回答"]
LLM --> SOURCE["Answer + Source<br/>答案和来源"]
Document 和 Chunk
Document 是原始文档。
比如:
合同.pdf
项目说明.docx
README.md
接口文档.txt
Chunk 是文档切出来的小片段。
比如一份 20 页 PDF,可能切成:
chunk 1:标题和第一段
chunk 2:第二段到第三段
chunk 3:接口说明
...
为什么要切片?
因为:
模型上下文有限
向量检索需要较小片段
整篇文档太大不方便匹配
用户问题通常只对应部分内容
Chunk 太大:
检索不精准
上下文浪费
模型容易抓不到重点
Chunk 太小:
上下文断裂
答案缺少前后关系
检索结果碎片化
所以后面会重点讲切片。
Embedding 是什么
Embedding,中文一般翻译为“向量表示”。
它会把文本变成一串数字。
比如:
"什么是 RAG?"
-> [0.12, -0.03, 0.88, ...]
语义相近的文本,向量距离通常更近。
例如:
"文件上传限制是多少?"
"最大文件大小是多少?"
这两个句子字面不完全一样,但语义相近。
Embedding 可以帮助系统找出相关文档片段。
Vector Store 是什么
Vector Store,中文可以理解为“向量存储”或“向量数据库”。
它负责存:
chunk 文本
chunk metadata
chunk embedding
然后根据问题向量做相似度搜索。
常见选择:
pgvector
Milvus
Qdrant
Weaviate
Chroma
Elasticsearch dense_vector
本路线先以 PostgreSQL + pgvector 为主。
原因是:
容易本地启动
和业务数据库接近
SQL 好理解
后续能加 metadata 过滤
适合学习阶段
Retriever 是什么
Retriever,中文可以理解为“检索器”。
它负责把用户问题变成检索结果。
最小逻辑:
用户问题
-> 生成问题向量
-> 向量库相似度搜索
-> 返回 topK chunks
topK 表示返回最相关的前 K 条。
比如:
topK = 4
表示拿 4 个相关片段给模型。
检索器后面还可以加:
metadata 过滤
关键词检索
混合检索
重排序
权限过滤
本节先理解最小检索。
RAG Prompt
RAG 的 Prompt 通常包含:
角色说明
回答规则
检索上下文
用户问题
无法回答时的兜底
引用来源要求
示例:
你是一个知识库问答助手。
请只根据给定的上下文回答问题。
如果上下文中没有答案,请回答“根据已上传资料无法确定”。
不要编造上下文之外的信息。
上下文:
{context}
用户问题:
{question}
这条规则很重要:
如果上下文中没有答案,不要编造
否则 RAG 也会变成普通 Chat。
来源信息
RAG 项目最好返回来源。
比如:
{
"answer": "上传文件大小限制是 20MB。",
"sources": [ { "documentName": "接口文档.md",
"chunkIndex": 3, "page": 2 } ]}
来源的作用:
用户能核对
开发能排查
减少胡编风险
方便后续做引用跳转
如果只返回答案,不返回来源,很难判断答案是否可信。
最小 RAG 伪代码
先看整体伪代码:
question = "文件上传大小限制是多少?"
query_vector = embedding_model.embed_query(question)
chunks = vector_store.similarity_search(
query_vector, top_k=4,)
context = "\n\n".join(chunk.text for chunk in chunks)
prompt = f"""
请只根据上下文回答问题。
上下文:
{context}
问题:
{question}
"""
answer = llm.invoke(prompt)
真实项目会多很多细节。
但主线就是这几个步骤。
项目边界
本节要先确定项目边界。
建议第一个 RAG Demo 只做:
上传 PDF / DOCX / MD / TXT解析成纯文本
切成 chunk生成 embedding存 pgvector根据问题检索 topK模型基于上下文回答
返回来源
先不做:
复杂权限
多租户
图片 OCR表格结构化抽取
多模态检索
知识图谱
自动评测平台
大规模分布式索引
边界越清楚,第一版越容易跑通。
RAG 失败的常见原因
常见问题:
文档解析丢内容
页眉页脚干扰太多
chunk 切得太碎
chunk 切得太大
embedding 模型不适合中文
topK 太少
检索结果没有来源
Prompt 没要求基于上下文回答
上下文太长导致重点被稀释
没有处理“找不到答案”
排查顺序:
先看原文是否解析正确
再看 chunk 是否合理
再看检索结果是否相关
最后看模型回答是否遵守上下文
不要一出问题就换模型。
RAG 的很多问题在模型之前就已经发生了。
排查流程图:
flowchart TD
A["RAG 回答效果差"] --> B{"原文解析正确吗?"}
B -- "否" --> B1["修复 Parser<br/>检查 PDF / DOCX / Markdown 解析结果"]
B -- "是" --> C{"chunk 合理吗?"}
C -- "否" --> C1["调整切片策略<br/>chunk size / overlap / 按标题切分"]
C -- "是" --> D{"检索结果相关吗?"}
D -- "否" --> D1["优化检索<br/>embedding 模型 / topK / metadata 过滤 / 混合检索"]
D -- "是" --> E{"Prompt 约束清楚吗?"}
E -- "否" --> E1["强化 Prompt<br/>要求基于上下文回答并返回来源"]
E -- "是" --> F{"来源足够支撑答案吗?"}
F -- "否" --> F1["补充资料或扩大检索范围"]
F -- "是" --> G["再检查模型输出<br/>是否需要更强模型或人工校验"]
常见问题
RAG 是不是微调
不是。
微调会改变模型参数。
RAG 不改变模型参数,只是在回答时提供外部上下文。
RAG 能保证答案一定正确吗
不能。
RAG 只能提高可追溯性和事实相关性。
仍然需要:
好的文档
好的切片
好的检索
好的提示词
必要的人工校验
文档要不要全部塞给模型
不要。
应该先检索,再只放相关片段。
否则上下文太长、成本高、噪声大。
为什么要返回来源
因为 RAG 的可信度来自“答案能回到资料”。
没有来源,用户只能相信模型。
有来源,用户可以核对。
练习清单
完成几件事情:
能解释 RAG 是什么
能画出入库流程
能画出问答流程
能解释 Document 和 Chunk能解释 Embedding能解释 Vector Store能解释 Retriever能写一个 RAG Prompt能说明 RAG 和普通 Chat 的区别
能列出第一版项目边界
建议目录:
ai-agent-study
├── docs
│ └── rag-principle.md
├── java
│ └── rag-api
└── python
└── rag-service
小结
本节的结论:
RAG 的本质是“先检索,再生成”,不是让模型凭空记住你的资料。
入库链路:
文档
-> 解析
-> 清洗
-> 切片
-> Embedding -> 向量库
问答链路:
问题
-> 检索 chunk -> 拼上下文
-> 模型回答
-> 返回来源
后面会按这条链路逐步实现。