企业级RAG性能优化与质量治理(2):文档解析与Chunk工程决定知识库上限
文章摘要
很多团队把RAG效果差归因于Embedding模型、向量数据库或大模型,却忽略了最前面的文档解析与Chunk工程。PDF乱码、表格丢失、标题层级消失、旧版本混入和固定长度粗暴切分,会在数据进入向量库前就破坏知识。本文建立一套生产级文档管道:文件识别、结构解析、清洗、质量门禁、结构分块、父子Chunk、Metadata、版本管理、增量索引和自动评测,并给出Spring AI实现框架。
一、企业RAG的效果上限在哪里
完整链路:
原始文件
→ 文档解析
→ 内容清洗
→ 结构恢复
→ Chunk
→ Metadata
→ Embedding
→ 向量库
→ 检索
→ Rerank
→ 大模型回答
如果最前面的解析已经丢失信息:
正确内容没有进入Chunk
后面的Embedding、检索和大模型都无法恢复。
所以企业RAG的第一条原则是:
先保证知识被正确解析和组织,再讨论检索算法。
二、不要把“文件上传成功”等同于“知识入库成功”
一个文件经过多个状态:
UPLOADED
→ TYPE_DETECTED
→ PARSING
→ PARSED
→ QUALITY_CHECKED
→ CHUNKED
→ EMBEDDED
→ INDEXED
→ VERIFIED
任何一步失败,都不应该直接标记为可用。
建议状态:
UPLOADED
PARSING
PARSE_PARTIAL
OCR_REQUIRED
MANUAL_REVIEW
CHUNKING
INDEXING
INDEXED
VERIFIED
FAILED
三、文件类型识别不能只看扩展名
用户上传:
document.pdf
实际可能是:
- 标准文字PDF;
- 扫描PDF;
- 加密PDF;
- 损坏文件;
- 图片改扩展名;
- 内嵌附件PDF;
- 混合文本与扫描页。
需要检测:
MIME
Magic Number
是否加密
页数
文件大小
是否包含文本层
是否包含图片
语言
伪模型:
public record FileInspection(
String detectedType,
long sizeBytes,
int pages,
boolean encrypted,
boolean hasTextLayer,
boolean requiresOcr
) {
}
四、解析器应该按文档类型路由
Markdown → Markdown Reader
HTML → Jsoup Reader
DOCX/PPTX → Tika或专用解析器
普通PDF → PDF版面解析
扫描PDF → OCR+版面分析
复杂表格PDF → Docling等结构解析器
图片 → OCR或视觉模型
统一接口:
public interface EnterpriseDocumentParser {
boolean supports(FileInspection inspection);
ParsedDocument parse(
StoredFile file,
ParseOptions options
);
}
路由器:
@Component
public class ParserRouter {
private final List parsers;
public EnterpriseDocumentParser route(
FileInspection inspection
) {
return parsers.stream()
.filter(parser ->
parser.supports(inspection)
)
.findFirst()
.orElseThrow(() ->
new IllegalArgumentException(
"没有可用解析器"
)
);
}
}
五、ParsedDocument必须是结构化对象
不要只返回:
一大段纯文本
推荐:
public record ParsedElement(
String elementId,
ElementType type,
String text,
int page,
BoundingBox boundingBox,
List sectionPath,
Map metadata
) {
}
元素类型:
TITLE
SECTION_HEADER
PARAGRAPH
LIST
TABLE
CODE
FORMULA
IMAGE_CAPTION
FOOTNOTE
HEADER
FOOTER
结构信息决定后续如何分块。
六、解析阶段需要保留哪些信息
至少保留:
文件名
文档ID
文档版本
页码
章节路径
元素类型
页面坐标
表格ID
图片ID
语言
解析器版本
OCR置信度
这些信息用于:
- 来源引用;
- 页面跳转;
- 结构分块;
- 表格处理;
- 质量检查;
- 问题追溯;
- 重新解析。
七、清洗不是简单去掉空格
需要处理:
页眉页脚
跨页重复,容易污染检索。
页码
保留为Metadata,不应混入正文。
目录
目录和正文高度重复,可能导致错误召回。
水印
机密
仅供内部使用
公司名称
每页重复会影响Embedding。
断行
PDF可能把一句话拆成:
企业级RAG需要
完整的文档治理。
需要根据标点、坐标和段落结构合并。
连字符
英文换行:
retriev-
al
应恢复为:
retrieval
八、清洗必须可追溯
不要覆盖唯一原始结果。
保存:
raw_parse.json
cleaned_parse.json
chunk_result.json
同时记录:
parser_version
cleaner_version
chunk_strategy_version
当检索结果错误时,可以回到每一步定位。
九、质量门禁是生产系统的必要组件
public record DocumentQualityReport(
double emptyPageRatio,
double garbledRatio,
double duplicateLineRatio,
double ocrPageRatio,
int tableCount,
int warningCount,
QualityStatus status
) {
}
质量规则示例:
空白页比例 > 30% → OCR_REQUIRED
乱码率 > 5% → MANUAL_REVIEW
有效文本字符 2.1 住宿标准
正文:……
十一、Chunk领域模型
public record KnowledgeChunk(
String chunkId,
String parentId,
String documentId,
String documentVersion,
String content,
String contentType,
List sectionPath,
int pageStart,
int pageEnd,
Map metadata
) {
}
Chunk ID必须稳定。
可以使用:
document_id
+document_version
+element范围
+chunk_strategy_version
十二、固定长度分块的正确用途
固定Token分块仍然有价值,但应该作为最后一层保护:
结构单元超过Embedding上限
→ TokenTextSplitter再次拆分
而不是用它替代文档结构。
Spring AI示意:
TokenTextSplitter splitter =
TokenTextSplitter.builder()
.withChunkSize(600)
.withMinChunkSizeChars(200)
.withMinChunkLengthToEmbed(20)
.withKeepSeparator(true)
.build();
中文项目还应配置中文标点。
十三、父子Chunk解决精度与完整性的矛盾
子Chunk
- 200—500 Token;
- 主题单一;
- 负责向量召回。
父Chunk
- 完整条款或章节;
- 负责提供回答上下文。
流程:
查询
→ 搜索子Chunk
→ 获取parent_id
→ 加载父Chunk
→ 去重和压缩
→ 交给模型
父子Chunk适合:
- 合同;
- 制度;
- 技术文档;
- 长条款;
- API说明。
十四、表格必须走独立分支
TABLE元素
→ 恢复行列结构
→ 保存Markdown
→ 保存JSON
→ 按整表或行分块
→ 数字计算进入结构化查询
表格Chunk附带:
table_id
title
headers
unit
row_range
page_range
不要让TokenSplitter从表格中间切断。
十五、图片和图表怎么办
图片可能包含:
- 流程图;
- 架构图;
- 产品截图;
- 数据图表;
- 扫描文字。
处理策略:
图片分类
→ OCR或视觉模型描述
→ 保存图片引用
→ 与图注和章节绑定
图表数据如果用于精确问答,应提取结构化数据,不只保存自然语言描述。
十六、Metadata决定企业RAG能否治理
最小字段:
document_id
document_version
chunk_id
parent_id
tenant_id
status
effective_date
expired_at
section_path
page_start
page_end
content_type
parser_version
chunk_strategy_version
用途:
- 多租户隔离;
- 版本过滤;
- 有效期过滤;
- 来源引用;
- 删除;
- 增量更新;
- 检索评测。
十七、文档版本不能靠文件名判断
制度最终版.pdf
制度最终版2.pdf
制度最新正式版.pdf
文件名无法表达真实版本关系。
应该维护:
document_id
version
status
effective_date
expired_at
supersedes_version
发布新版本:
解析新版本
→ 入库新Chunk
→ 验证检索
→ 激活新版本
→ 旧版本标记EXPIRED
不要先删除旧版本。
十八、增量索引如何实现
计算元素或Chunk哈希:
String contentHash = sha256(
normalizedContent
);
对比:
新增Chunk → 写入
修改Chunk → 更新向量
删除Chunk → 删除或失效
未变化Chunk → 跳过
这样可以避免整库重建。
十九、解析器升级也可能需要重建索引
即使原文件不变,以下变化也会改变Chunk:
- 解析器版本;
- OCR模型;
- 表格模型;
- 清洗规则;
- Chunk策略;
- Embedding模型。
因此索引版本应包含:
parser_version
cleaner_version
chunk_version
embedding_version
二十、Spring AI ETL架构
Spring AI ETL核心:
DocumentReader
→ DocumentTransformer
→ DocumentWriter
企业实现:
@Service
public class EnterpriseRagIngestionService {
private final ParserRouter parserRouter;
private final DocumentQualityService qualityService;
private final ChunkingService chunkingService;
private final VectorStore vectorStore;
public void ingest(StoredFile file) {
FileInspection inspection =
inspect(file);
EnterpriseDocumentParser parser =
parserRouter.route(inspection);
ParsedDocument parsed =
parser.parse(
file,
ParseOptions.defaults()
);
qualityService.validate(parsed);
List chunks =
chunkingService.chunk(parsed);
List documents =
chunks.stream()
.map(this::toSpringDocument)
.toList();
vectorStore.add(documents);
}
}
二十一、批量Embedding需要限流和断点
不能一次写入数十万Chunk。
建议:
按Token批次
→ 调用Embedding
→ 写入VectorStore
→ 保存批次状态
→ 失败后重试
状态:
batch_id
start_chunk
end_chunk
status
retry_count
error_code
需要区分:
- 临时限流;
- 永久格式错误;
- 单个Chunk超长;
- 数据库写入失败。
二十二、入库完成后必须做检索验证
每个文档可以生成几个自动验证问题:
标题是什么?
生效日期是什么?
某关键条款是什么?
表格中的某项数据是多少?
如果基础问题无法检索到正确Chunk,文档不应进入正式可用状态。
状态:
INDEXED
→ VERIFYING
→ VERIFIED
二十三、Chunk质量如何评测
完整性
正确答案所需内容是否在同一Chunk或父Chunk中。
纯度
Chunk是否包含多个无关主题。
可检索性
用户问题能否召回它。
可引用性
是否能定位章节和页码。
重复率
多个Chunk是否高度重复。
指标:
chunk_answer_coverage
chunk_topic_purity
duplicate_chunk_ratio
parent_retrieval_success
citation_completeness
二十四、建立文档类型策略库
strategies:
policy:
parser: layout
chunker: clause
parent-child: true
contract:
parser: layout
chunker: clause
preserve-tables: true
manual:
parser: layout
chunker: heading
faq:
parser: structured
chunker: qa-pair
log:
parser: text
chunker: token-window
不要让所有文件使用同一套参数。
二十五、生产级观测指标
parse_success_rate
parse_partial_rate
ocr_required_rate
manual_review_rate
average_parse_time
average_chunks_per_document
duplicate_chunk_ratio
embedding_failure_rate
index_verification_pass_rate
还要关联:
parser_version
chunk_strategy
retrieval_success
answer_quality
才能知道哪个环节真正改善了效果。
二十六、常见失败模式
只保存纯文本
丢失结构和来源。
所有PDF都用同一个Reader
扫描件和复杂表格效果差。
全部固定500 Token
条款与表格被切断。
没有版本状态
旧制度和新制度冲突。
解析成功就直接入库
乱码内容进入生产知识库。
修改分块后不重建测试基线
不知道效果变好还是变差。
二十七、本篇落地清单
□ 文件类型识别
□ 解析器路由
□ 结构化ParsedDocument
□ 页眉页脚清理
□ OCR质量检查
□ 表格独立处理
□ 结构优先分块
□ Token上限保护
□ 父子Chunk
□ 完整Metadata
□ 文档版本管理
□ 增量索引
□ 入库后检索验证
□ 解析和Chunk指标
总结
企业RAG的文档管道应该是:
识别
→ 解析
→ 清洗
→ 质量门禁
→ 结构分块
→ Metadata
→ 版本治理
→ Embedding
→ 索引验证
真正的质量提升往往不来自更大的模型,而来自:
文档没有丢
结构没有乱
表格没有散
版本没有冲突
Chunk保留完整语义
下一篇将继续进入:
企业级RAG混合检索实战:BM25、向量召回、Reranker与动态Top K。
延伸阅读
如果你正在关注企业级 AI 应用、RAG、Agent、MCP 与大模型工程化落地,欢迎访问 智元界:
https://www.zyentor.com/
智元界将持续分享可运行的技术实战、架构设计、问题排查与企业应用案例。