知识的切分与维护(RAG 核心实践)
在 RAG(检索增强生成)系统中,知识的切分与维护直接决定了系统的检索质量和回答准确性。本章节详细介绍这两个核心环节的最佳实践。
一、知识切分(Chunking)
1.1 什么是知识切分?
将原始文档(PDF、Word、Markdown、HTML、数据库记录等)切割成适合检索和 LLM 处理的文本块(Chunks)。
markdown
原始文档 (10,000 字)
↓ 切分
┌──────┬──────┬──────┬──────┬──────┐
│块1 │块2 │块3 │块4 │块5 │
│500字 │500字 │500字 │500字 │500字 │
└──────┴──────┴──────┴──────┴──────┘
1.2 为什么需要切分?
| 原因 | 说明 |
|---|---|
| 模型上下文限制 | LLM 的上下文窗口有限(如 4K、8K、128K token) |
| 检索精准度 | 小块更容易找到精准相关内容;大块会混入噪声 |
| 成本控制 | 向量化(Embedding)按 token 计费,小块成本可控 |
| 相关性计算 | 小块与问题的语义相似度计算更准确 |
1.3 切分策略详解
策略一:固定大小切分(Fixed-size Chunking)
python
from langchain.text_splitter import CharacterTextSplitter
splitter = CharacterTextSplitter(
chunk_size=500, # 每块 500 字符
chunk_overlap=50, # 重叠 50 字符
separator=" " # 按空格切分
)
chunks = splitter.split_text(document)
| 优点 | 缺点 |
|---|---|
| 实现简单,速度快 | 可能切在句子中间,破坏语义 |
| 块大小可控 | 忽略文档结构 |
策略二:递归切分(Recursive Character Splitting)
最常用的通用策略,按优先级使用分隔符递归切分:
python
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", ",", " ", ""] # 优先级从高到低
)
# 优先按段落切 → 按句子切 → 按逗号切 → 按空格切 → 按字符切
切分过程:
swift
原始文本
↓ 尝试用 "\n\n" 切分
如果块仍大于 500 → 用 "\n" 切分
↓
如果块仍大于 500 → 用 "。" 切分
↓
如果块仍大于 500 → 用 "," 切分
↓
如果块仍大于 500 → 按字符硬切
策略三:语义切分(Semantic Chunking)
使用 NLP 技术识别语义边界。
python
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
splitter = SemanticChunker(
embeddings=OpenAIEmbeddings(),
breakpoint_threshold_type="percentile", # 语义相似度骤降处切分
breakpoint_threshold_amount=95
)
chunks = splitter.split_text(document)
原理:
- 计算相邻句子的语义相似度
- 相似度骤降的地方 → 语义边界 → 在此切分
| 优点 | 缺点 |
|---|---|
| 保留完整语义单元 | 计算开销大 |
| 块大小不均匀 | 需要 Embedding 模型 |
策略四:文档结构切分(Document Structure Chunking)
按 Markdown/HTML 的标题层级切分。
python
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers_to_split_on = [
("#", "Header1"),
("##", "Header2"),
("###", "Header3"),
]
splitter = MarkdownHeaderTextSplitter(headers_to_split_on)
chunks = splitter.split_text(markdown_document)
# 每个块会保留其所属的标题层级信息
# 块.metadata = {"Header1": "引言", "Header2": "背景"}
适用场景: 技术文档、维基百科、结构化手册
策略五:递归摘要切分(Proposition Chunking)
将文本分解为原子命题(每个命题是一个独立的最小信息单元)。
arduino
原文:"OpenAI 于 2015 年由 Sam Altman 和 Elon Musk 等人创立。"
↓ 分解
命题1:"OpenAI 成立于 2015 年。"
命题2:"OpenAI 的联合创始人包括 Sam Altman。"
命题3:"OpenAI 的联合创始人包括 Elon Musk。"
| 优点 | 缺点 |
|---|---|
| 信息粒度最细 | 需要 LLM 辅助,成本高 |
| 跨块重组能力强 | 可能丢失上下文关联 |
1.4 重叠(Overlap)的作用
diff
块1: [==========]
块2: [==========]
块3: [==========]
↑ 重叠区域
为什么需要重叠:
- 避免关键信息被切在边界上导致丢失
- 保持跨块的上下文连贯性(如段落间的过渡句)
经验值:
| 块大小 | 推荐重叠 |
|---|---|
| 128 token | 16-32 token |
| 256 token | 32-64 token |
| 512 token | 64-128 token |
| 1024 token | 128-256 token |
1.5 块大小选择指南
| 场景 | 推荐块大小 | 原因 |
|---|---|---|
| 问答(Q&A) | 200-500 字符 | 答案通常简短 |
| 摘要生成 | 500-1000 字符 | 需要足够上下文 |
| 代码生成 | 500-1000 token | 函数通常在这个长度 |
| 长文档分析 | 1000-2000 字符 | 需要完整段落 |
| 表格数据 | 按行切分 | 保留行完整性 |
经验法则: 不确定时,从 512 token(约 400 中文字符) 开始实验。
二、知识维护(Knowledge Maintenance)
2.1 什么是知识维护?
对已切分和索引的知识库进行更新、删除、版本控制、质量保证等一系列管理活动。
2.2 知识生命周期
markdown
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 采集 │ → │ 清洗 │ → │ 切分 │ → │ 索引 │ → │ 检索 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
↑ ↑ ↑ ↑ ↑
└──────────────┴──────────────┴──────────────┴──────────────┘
持续维护
2.3 核心维护操作
操作一:增量更新
新文档加入时,不重建整个索引。
python
# 伪代码
def add_document(doc):
chunks = split(doc)
embeddings = embed(chunks)
vector_db.insert(embeddings, metadata={
"doc_id": doc.id,
"timestamp": now(),
"version": doc.version
})
挑战: 旧文档删除/更新时,需要同步处理。
操作二:删除与替换
python
# 方案1:逻辑删除(标记为无效)
vector_db.update(doc_id, {"is_deleted": True})
# 检索时过滤 is_deleted=False
# 方案2:物理删除(直接移除)
vector_db.delete(doc_id)
# 方案3:版本控制(保留历史)
vector_db.insert(new_version, metadata={"version": 2, "supersedes": old_id})
操作三:一致性维护
当源文档更新时,确保向量库同步。
| 策略 | 做法 | 适用场景 |
|---|---|---|
| 全量重建 | 每天/每周重新构建整个向量库 | 数据量小,变化频率低 |
| 增量更新 | 只处理变化的文档 | 数据量大,实时性要求高 |
| 双写 | 源文档更新时同步更新向量 | 要求强一致性 |
| 事件驱动 | 监听文档变更事件触发更新 | 复杂系统 |
2.4 质量保证机制
健康检查指标
| 指标 | 定义 | 告警阈值 |
|---|---|---|
| 覆盖度 | 可回答的问题比例 | < 80% |
| 新鲜度 | 知识库中最新文档的时间 | > 30 天无更新 |
| 冗余度 | 相似度过高的文档对比例 | > 20% |
| 孤儿块 | 无法归属到任何文档的块 | > 5% |
| 脏数据率 | 格式错误/乱码的比例 | > 1% |
自动化监控流程
python
# 伪代码:定期健康检查
def health_check():
# 1. 抽样检查:随机选 100 个问题,看检索结果质量
sample_questions = get_sample_questions()
recall_score = evaluate_recall(sample_questions)
# 2. 重复检测:找出相似度 > 0.95 的文档对
duplicates = find_near_duplicates(threshold=0.95)
# 3. 时效性检查:最后更新时间
last_update = get_last_update_time()
# 4. 生成报告
report = {
"recall": recall_score,
"duplicate_count": len(duplicates),
"days_since_update": (now() - last_update).days
}
if report["recall"] < 0.7:
alert("检索召回率过低,需要优化切分策略或 Embedding 模型")
2.5 版本管理与回滚
yaml
# 知识库版本记录
versions:
- version: "v3.2"
timestamp: "2026-05-18"
changes: "新增产品文档 50 篇,更新 API 文档"
recall_score: 0.85
- version: "v3.1"
timestamp: "2026-05-10"
changes: "修复切分边界问题"
recall_score: 0.82
- version: "v3.0"
timestamp: "2026-05-01"
changes: "切换到语义切分策略"
recall_score: 0.79
# 回滚命令
rollback --to v3.1
三、切分与维护的最佳实践
3.1 针对不同文档类型的策略
| 文档类型 | 推荐切分策略 | 维护重点 |
|---|---|---|
| 技术文档 | 按标题层级切分(MarkdownHeaderTextSplitter) | 保持 API 版本同步 |
| 财报/年报 | 固定大小 + 大块(1000-2000 字符) | 定期更新(季度/年度) |
| 对话/聊天记录 | 按会话边界切分 | 隐私清理 |
| 代码库 | 按函数/类切分(Language-specific splitter) | 与 Git 版本同步 |
| 法律合同 | 按条款切分 + 保留章节号 | 追踪修订历史 |
| 论文/学术 | 按章节切分(摘要、引言、结论加强权重) | 引用关系维护 |
3.2 元数据设计
每个块应携带足够元数据,便于过滤和溯源。
json
{
"chunk_id": "doc_123_chunk_5",
"doc_id": "doc_123",
"doc_title": "2025年技术年报",
"doc_type": "annual_report",
"section": "3.2 研发投入",
"page": 23,
"timestamp": "2026-01-15",
"version": 2,
"author": "tech_team",
"tags": ["finance", "R&D", "2025"],
"embedding_model": "text-embedding-3-small"
}
3.3 索引策略
python
# 混合索引:向量索引 + 关键词索引
class HybridIndex:
def __init__(self):
self.vector_index = VectorIndex() # 语义检索
self.keyword_index = InvertedIndex() # 关键词检索(BM25)
def search(self, query):
# 并行检索两种索引
semantic_results = self.vector_index.search(query, top_k=10)
keyword_results = self.keyword_index.search(query, top_k=10)
# 融合排序(RRF:Reciprocal Rank Fusion)
return fusion_rerank(semantic_results, keyword_results)
3.4 维护自动化流程
yaml
# CI/CD Pipeline for Knowledge Base
name: Knowledge Base Maintenance
on:
push:
paths:
- 'docs/**'
- 'knowledge/**'
jobs:
update-knowledge-base:
runs-on: ubuntu-latest
steps:
- name: Checkout documents
uses: actions/checkout@v3
- name: Detect changed documents
run: python scripts/detect_changes.py
- name: Re-chunk changed documents
run: python scripts/chunking.py --files $(cat changed_files.txt)
- name: Update vector database
run: python scripts/update_index.py --mode incremental
- name: Run health checks
run: python scripts/health_check.py
- name: Notify on failure
if: failure()
run: slack notification "知识库更新失败"
四、常见问题与解决方案
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 检索不到相关块 | 块太小,切碎了关键信息 | 增大块大小或增加重叠 |
| 检索结果包含噪声 | 块太大,混入无关内容 | 减小块大小 |
| 答案不连贯 | 相关信息分散在不同块 | 增加重叠或使用多跳检索 |
| 更新后旧答案仍出现 | 旧向量未被删除 | 使用版本标记 + 过滤 |
| 相似文档回答矛盾 | 知识库中存在冲突信息 | 增加时效性权重 + 人工复核 |
| 检索速度慢 | 向量数据库未优化 | 建立索引、使用 ANN 而非 exact search |
五、总结
erlang
┌─────────────────────────────────────────────────────────────────┐
│ 知识切分与维护核心要点 │
├─────────────────────────────────────────────────────────────────┤
│ 切分策略: │
│ • 通用首选:递归切分(RecursiveCharacterTextSplitter) │
│ • 结构化文档:按标题层级切分 │
│ • 追求精度:语义切分 │
│ • 块大小:512 token 起步,按场景调整 │
│ • 重叠:块大小的 10-20% │
├─────────────────────────────────────────────────────────────────┤
│ 维护策略: │
│ • 增量更新而非全量重建 │
│ • 元数据设计要包含版本、时间戳、来源 │
│ • 定期健康检查(召回率、冗余度、新鲜度) │
│ • 版本管理支持回滚 │
│ • 自动化 CI/CD 流程维护 │
└─────────────────────────────────────────────────────────────────┘
一句话总结:
好的切分让检索更精准,好的维护让知识库长治久安。两者共同决定了 RAG 系统的上限。
文档版本:v1.0
最后更新:2026-05-18