什么是 RAG?
RAG = Retrieval-Augmented Generation
中文:检索增强生成
一句话理解:
先从资料中"查答案",再让大模型"根据资料作答"。
为什么需要 RAG?
企业内部的资料,大模型不一定有,所以会出现乱说
还有不可能把几百页的说明书给大模型的,这样开销很大
大模型有两个问题:
- 会胡编(幻觉 hallucination)
- 不知道私有数据
RAG 解决方式:
1 2 3 4 5 6 7
| 问题 ↓ 去知识库查资料 ↓ 把资料交给大模型 ↓ 大模型根据资料回答
|
RAG 整体流程
离线阶段: 文档解析 → Chunk 切分 → Embedding 向量化 → 向量入库
在线阶段: 用户问题 Embedding → 语义缓存匹配 → Milvus 向量检索 + BM25 混合召回 → Reranker 精排 → Prompt 拼接 → 大模型生成答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| ┌──────────────────────────────────────────────────────────────────────┐ │ 离 线 阶 段 │ │ │ │ PDF 文档解析 │ │ ↓ │ │ 文本分片(Chunking) 按段落/章节字数等,LLM有上下文长度限制 │ │ ↓ │ │ Embedding 将文字翻译为高维度向量,语义相近则空间距离就近 │ │ ↓ │ │ 向量数据库(Milvus) 存储 向量 + 原始文本的对照表 │ └──────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────┐ │ 在 线 阶 段 │ │ │ │ 用户提问 │ │ ↓ │ │ Embedding 向量化 │ │ ↓ │ │ 语义缓存匹配 ──命中──→ 直接返回缓存答案 │ │ ↓ 未命中 │ │ 双路检索召回 │ │ ┌──────────────────────────┐ ┌──────────────────────────┐ │ │ │ BM25 稀疏检索 │ │ BGE 稠密检索 │ │ │ │ │ │ │ │ │ │ 用户查询 │ │ 用户查询 │ │ │ │ ↓ │ │ ↓ │ │ │ │ 分词 → 词频统计(TF) │ │ BGE 模型 Embedding │ │ │ │ ↓ │ │ ↓ │ │ │ │ 逆文档频率(IDF)计算 │ │ 生成查询向量 Q │ │ │ │ ↓ │ │ ↓ │ │ │ │ TF × IDF 得分 │ │ ANN 索引检索(HNSW/IVF) │ │ │ │ ↓ │ │ 余弦相似度计算 cos(Q,D) │ │ │ │ 按关键词匹配度排序 │ │ ↓ │ │ │ │ ↓ │ │ 按语义相似度排序 │ │ │ │ 返回 Top-K 候选文档 │ │ 返回 Top-K 候选文档 │ │ │ └──────────┬───────────────┘ └──────────┬───────────────┘ │ │ └──────────┬──────────────────┘ │ │ ↓ │ │ RRF 融合算法(Reciprocal Rank Fusion) │ │ ↓ │ │ Reranker 精排(bge-reranker-base) │ │ ↓ │ │ 构造 Prompt(角色设定 + 规则 + 检索资料 + 问题) │ │ ↓ │ │ 调用大模型(GLM) │ │ ↓ │ │ 生成答案 │ └──────────────────────────────────────────────────────────────────────┘
|
常见问题与解决方案
- 文档解析出现乱码,表格行错位 → OCR + 人工复核
- 颗粒度问题 → 切太大噪音太多,太小语义丢失
- 检索准确率 → 加入混合检索 + rerank
- 用户提问模糊性 → 比如问"货没了去补",需要查询重写(Query Rewrite)后再去搜索
大模型选型和评估标准:
- 推理能力
- 指令遵循能力
- 防幻觉能力
进阶方案:GraphRAG 和 Agentic RAG
分块策略
chunk_size:256 / 512 / 1024(token 或字节)
chunk_overlap:10%~20% 相邻块重叠部分,防止语意断裂
策略:
基础分块: 固定长度、递归字符(首选,按优先级分隔符递归切分 \n\n → \n → " ",能保留句子完整性)、按句切分
结构感知: HTML、Markdown、对话格式
语义/主题: 向量相似度
高级策略: 小-大分块、代理分块(让模型切分,成本很高)
复杂文档最佳实践: 采用混合分块,可以先按结构粗切,再语义/递归细切,做命中率测试
建议:
| 场景 |
推荐方式 |
| 通用文本 |
递归字符分块(RecursiveCharacterTextSplitter) |
| Markdown 文档 |
MarkdownHeaderTextSplitter |
| HTML 页面 |
HTMLHeaderTextSplitter |
| 追求最高精度 |
SemanticChunker(成本高) |
| 代码文件 |
按函数/类切分,保留完整结构 |
LangChain 分块伪代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| from langchain.text_splitter import ( RecursiveCharacterTextSplitter, CharacterTextSplitter, MarkdownHeaderTextSplitter, HTMLHeaderTextSplitter, )
splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", " "], length_function=len, ) chunks = splitter.split_text(document)
splitter = CharacterTextSplitter( chunk_size=256, chunk_overlap=20, separator="\n", ) chunks = splitter.split_text(document)
headers_to_split = [ ("#", "h1"), ("##", "h2"), ("###", "h3"), ] splitter = MarkdownHeaderTextSplitter(headers_to_split=headers_to_split)
docs = splitter.split_text(markdown_text)
headers_to_split = [("h1", "Header 1"), ("h2", "Header 2")] splitter = HTMLHeaderTextSplitter(headers_to_split=headers_to_split) docs = splitter.split_text(html_text)
from langchain_experimental.text_splitter import SemanticChunker from langchain_openai import OpenAIEmbeddings
splitter = SemanticChunker( OpenAIEmbeddings(), breakpoint_threshold_type="percentile", ) docs = splitter.split_text(document)
|
检索策略
- 假想性文档嵌入: 让 LLM 生成理想答案,再去向量化检索
- 混合搜索: 先通过向量相似度找到相近的,再 BM25 稀疏检索锁定匹配结构,加权融合结果合并,向量 0.6,关键词 0.4
- 多子问题检索: 让大语言模型生成多个语义等价的子问题,再去查,然后用 RAG-Fusion 的 RRF 算法对多个查询结果进行打分重排,强调共识文档——就是多个评委都打分了就是它了
- 抽象+具体双路: 用户问的过具体,可以让 LLM 抽象问题,将 2 个都拿去检索,既抓细节,又抓背景