企业级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 是否加密 页数 文件大小 是否包含文本层 是否包含图片 语言

伪模型:

publicrecordFileInspection(StringdetectedType,longsizeBytes,intpages,booleanencrypted,booleanhasTextLayer,booleanrequiresOcr){}

四、解析器应该按文档类型路由

Markdown → Markdown Reader HTML → Jsoup Reader DOCX/PPTX → Tika或专用解析器 普通PDF → PDF版面解析 扫描PDF → OCR+版面分析 复杂表格PDF → Docling等结构解析器 图片 → OCR或视觉模型

统一接口:

publicinterfaceEnterpriseDocumentParser{booleansupports(FileInspectioninspection);ParsedDocumentparse(StoredFilefile,ParseOptionsoptions);}

路由器:

@ComponentpublicclassParserRouter{privatefinalList<EnterpriseDocumentParser>parsers;publicEnterpriseDocumentParserroute(FileInspectioninspection){returnparsers.stream().filter(parser->parser.supports(inspection)).findFirst().orElseThrow(()->newIllegalArgumentException("没有可用解析器"));}}

五、ParsedDocument必须是结构化对象

不要只返回:

一大段纯文本

推荐:

publicrecordParsedElement(StringelementId,ElementTypetype,Stringtext,intpage,BoundingBoxboundingBox,List<String>sectionPath,Map<String,Object>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

当检索结果错误时,可以回到每一步定位。

九、质量门禁是生产系统的必要组件

publicrecordDocumentQualityReport(doubleemptyPageRatio,doublegarbledRatio,doubleduplicateLineRatio,doubleocrPageRatio,inttableCount,intwarningCount,QualityStatusstatus){}

质量规则示例:

空白页比例 > 30% → OCR_REQUIRED 乱码率 > 5% → MANUAL_REVIEW 有效文本字符 < 100 → REJECTED 表格检测到但无表格内容 → MANUAL_REVIEW

阈值应按文档类型配置。

十、先按结构分块,再按Token限制

错误流程:

全文提取 → 每500 Token切分

推荐:

解析章节结构 → 按标题、段落、条款、表格分组 → 超长结构单元再按Token切分

例如:

第二章 费用标准 2.1 住宿标准 2.2 交通标准

Chunk应包含章节路径:

文档:差旅管理制度 章节:第二章 费用标准 > 2.1 住宿标准 正文:……

十一、Chunk领域模型

publicrecordKnowledgeChunk(StringchunkId,StringparentId,StringdocumentId,StringdocumentVersion,Stringcontent,StringcontentType,List<String>sectionPath,intpageStart,intpageEnd,Map<String,Object>metadata){}

Chunk ID必须稳定。

可以使用:

document_id +document_version +element范围 +chunk_strategy_version

十二、固定长度分块的正确用途

固定Token分块仍然有价值,但应该作为最后一层保护:

结构单元超过Embedding上限 → TokenTextSplitter再次拆分

而不是用它替代文档结构。

Spring AI示意:

TokenTextSplittersplitter=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哈希:

StringcontentHash=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

企业实现:

@ServicepublicclassEnterpriseRagIngestionService{privatefinalParserRouterparserRouter;privatefinalDocumentQualityServicequalityService;privatefinalChunkingServicechunkingService;privatefinalVectorStorevectorStore;publicvoidingest(StoredFilefile){FileInspectioninspection=inspect(file);EnterpriseDocumentParserparser=parserRouter.route(inspection);ParsedDocumentparsed=parser.parse(file,ParseOptions.defaults());qualityService.validate(parsed);List<KnowledgeChunk>chunks=chunkingService.chunk(parsed);List<Document>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:layoutchunker:clauseparent-child:truecontract:parser:layoutchunker:clausepreserve-tables:truemanual:parser:layoutchunker:headingfaq:parser:structuredchunker:qa-pairlog:parser:textchunker: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。