一、RAG文档存储:不只是向量数据库
很多同学以为RAG的存储就是个向量数据库,存向量就行了。这个理解过于简化了。一个生产级RAG系统,存储层需要同时搞定四类数据:
1.1 四层存储架构
① 向量数据存储层
- 存储文档切片经Embedding模型生成的向量(通常256-1536维)
- 核心需求:毫秒级ANN(近似最近邻)搜索
- 主流方案:使用HNSW(分层可导航小世界)图索引,在千万级向量场景下QPS可达2000+,99分位延迟控制在50ms内
② 原始文本存储层
- 存储Markdown/HTML/纯文本等多格式文档内容,单chunk可达10KB以上
- 需考虑:多格式支持、版本控制、冷热分离
- 建议:高频访问文本用分布式文件系统+SSD,低频数据自动降级至对象存储,成本可降低60%
③ 结构化元数据存储层
- 存储文档来源、权限标签、知识分类等30+维度信息
- 使用关系型数据库(如PostgreSQL),支持精确查询和聚合分析
④ 管理信息存储层
- 存储文档版本链、chunk父子关系、解析状态等运维数据
- 需要事务性操作保证数据一致性
1.2 向量数据库选型参考
以Milvus为例,创建RAG知识库Collection的核心代码:
ini
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType
connections.connect(host="localhost", port="19530")
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=65535),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536),
FieldSchema(name="source", dtype=DataType.VARCHAR, max_length=512),
]
schema = CollectionSchema(fields=fields, description="RAG Knowledge Base")
collection = Collection(name="rag_knowledge", schema=schema)
# 创建HNSW索引
index_params = {
"index_type": "HNSW",
"metric_type": "COSINE",
"params": {"M": 16, "efConstruction": 256}
}
collection.create_index(field_name="embedding", index_params=index_params)
二、文档切割策略:RAG效果的核心
分块(Chunking)是RAG实现中最关键的环节之一。如果传整篇文档或过大的块,既昂贵又可能超出模型Token限制;块太小又可能缺少足够上下文。
2.1 固定大小分块(带重叠)
原理:按固定字符数或Token数切分,块与块之间允许重叠。
特点:
- 工程成本:低
- 处理成本:低
- 工具:LangChain递归文本分割器、Hugging Face分块可视化工具
建议:使用BERT Token而非字符数作为计数单位,因为Token基于有意义的语言单元,能保留更多语义信息。
适用场景:非结构化文档,如调查反馈、论坛帖子、邮件、个人笔记等。
2.2 语义分块
原理:使用Embedding将概念相似的内容分组,创建语义连贯的块。
特点:
- 工程成本:高
- 处理成本:高
- 需要开发复杂的自定义逻辑
适用场景:各部分主题有重叠的文档,如金融或医疗领域的专业文档。
2.3 自定义代码分块
原理:使用正则表达式等文本解析技术,根据文档结构模式切分。
特点:
- 工程成本:中等
- 处理成本:低
- 适合结构可推断的半结构化文档
工具:Python(re、BeautifulSoup、lxml、marko)等。
适用场景:专利申请、研究论文、保险单、剧本等。
2.4 文档布局分析
原理:结合OCR与深度学习模型提取文档结构(页眉、页脚、标题、表格等)。
特点:
- 工程成本:中等
- 处理成本:中等
工具:Azure Document Intelligence、LayoutParser等。
适用场景:新闻文章、网页、简历等半结构化文档。
2.5 基于图的分块
原理:使用LLM查找文档中的实体和关系,构建知识图谱。
特点:
- 工程成本:高
- 处理成本:高
工具:Microsoft GraphRAG、Neo4J。
适用场景:体育分析、历史数据等需要跨文档进行复杂查询的领域。
2.6 LLM增强分块
原理:使用LLM生成图像的文字描述或表格摘要作为分块内容。
特点:
- 工程成本:中等
- 处理成本:高
- 对图像和表格等非文本内容特别有用
注意:若分块逻辑将图像描述拆成多个块,每个块都应包含图像URL,确保检索时能访问原始图像。
三、进阶策略:让切割更智能
3.1 Small2Big(小块检索,大块生成)
核心思想:用小块进行高效精准检索,检索到后追溯其所属的大块,用大块内容生成回答。这样既保证了检索精度,又确保LLM有足够的上下文。
3.2 混合自适应分块(HACR)
学术界的混合自适应分块检索算法(Hybrid Adaptive Chunking and Retrieval)是这一方向的前沿探索:
- 先用语义分块做初步划分
- 引入LLM分析识别包含多主题的块,动态切割或聚合
- 融合"细粒度检索-粗粒度生成"协同机制
实验结果表明,该算法在忠实度保持1.0的前提下,答案相关性提升7.6% -4。
3.3 分块对齐优化
为每个块生成示例问题------即这个块最能回答什么样的问题。检索时,系统将用户问题与示例问题比对,大幅提升匹配准确度。
四、分块前的预处理
分块之前,建议先做文档加载和预处理:
- 删除水印、页眉页脚等无关内容
- 将图像引用替换为图像描述(用LLM生成)
- 重新格式化表格以方便处理
- 根据标题、副标题定义文档结构
建议:先合并加载和分块逻辑,当业务需要时再分离。
五、更新策略
如果文档频繁更新,需要考虑增量更新策略:
- 定时批量更新:按日/周自动重新索引
- 增量更新:仅处理变更文档
- 实时流处理:文档变更时实时更新向量数据库
- 版本控制:保存历史版本快照,支持回滚
总结
| 策略 | 工程成本 | 处理成本 | 适用场景 |
|---|---|---|---|
| 固定大小分块 | 低 | 低 | 非结构化文本 |
| 语义分块 | 高 | 高 | 主题交错的文档 |
| 自定义代码 | 中 | 低 | 结构可推断的文档 |
| 布局分析 | 中 | 中 | 新闻、网页、简历 |
| 图分块 | 高 | 高 | 跨文档复杂查询 |
| LLM增强 | 中 | 高 | 含图像、表格的文档 |
选型建议 :从固定大小分块开始,结合Small2Big策略;当数据量增大、场景复杂后,再逐步引入语义分块或多策略组合。记住------分块策略是RAG系统的半永久性设计决策,更换成本较高,上线前务必做充分测试。
面试回答
第一步,存之前先做两件事:
- 加载文档:把 PDF、Word、网页等各种格式的文档读进来。
- 切块(Chunking) :因为大模型的上下文窗口有限,而且不能把整本书都塞进去,所以要把长文档切成一个个小文本块。
第二步,存的不是原文,是向量。
- 每个文本块会被送给一个 Embedding 模型 ,这个模型会把文本转成一串浮点数,也就是向量。语义越相近的文本,它们的向量在空间里就越接近。
- 然后把这个向量 + 原始文本块 一起存到向量数据库里(比如 Milvus、Pinecone、Chroma)。数据库会专门为这些向量建立索引,方便后面做"相似度搜索"。
所以简单说:文档被切成块,每个块变成一个向量,向量和原文一起存进向量库。
常见的几种切割策略(按从简单到复杂说):
- 固定长度切割(最基础)
-
- 比如按 200 个字符或 500 个 token 一刀切,不管句子边界。
- 问题:容易把一句话切成两半,导致语义破裂。一般会加一个 重叠(overlap) ,比如每块 500 字符,重叠 50 字符,保证边界处的信息不丢失。
- 按句子切割
-
- 按句号、感叹号、问号等切,保证每个块是一个或多个完整句子。
- 比固定长度好一点,但句子长短差异大,有的块可能太短没信息,有的太长。
- 按段落切割
-
- Markdown 里的空行、HTML 的段落标签。保留自然段落结构,适合结构清晰的文档。
- 递归字符切割(常见做法,比如 LangChain 的 RecursiveCharacterTextSplitter)
-
- 先按段落切,如果段落还太长,再按句子切,句子还太长就按词或字符切。优先级:段落 > 句子 > 词。这样尽可能保留语义单元。
- 语义切割(更高级)
-
- 不是靠标点或长度,而是看文本的语义拐点。比如把文本用 Embedding 模型向量化,然后找相邻句子的向量相似度,如果突然变低(相似度陡降),就在那里切。这样每个块内部主题更内聚。
- 特定格式的智能切割
-
- 代码:按函数、类来切,或者用语言特定的解析器(比如 Python 按 AST 语法树切)。
- Markdown/HTML:按标题层级切,保证每个块包含一个小节的所有内容。
实际中我们会根据文档类型和业务场景选。比如做客服问答,可能按一问一答切;做长文档摘要,可能切大块一点,比如 1000 token 加重叠。