结构节点提取的四阶段流水线
上一篇讲到 TikaDocumentParserService.parse() 完成了文本提取和清洗。按照 parse() 的执行顺序,接下来就是 structureNodeExtractor.extract()——它负责从清洗后的纯文本中提取出文档的结构节点(标题、列表、步骤等)。这个方法内部是一条相当复杂的四阶段流水线,我们这篇单独拆开来讲。
四阶段流水线总览
先看一张总览图:
总入口:DocumentStructureNodeExtractor
/**
* 文档结构节点提取器。
* <p>
* 这是整条结构解析流水线的总入口,负责把“文档标题 + 解析后的纯文本”
* 逐步转换成可落库的结构节点候选列表。
* </p>
* <p>
* 内部固定采用四阶段流水线:
* 1. 信号提取:逐行识别标题、列表、步骤、正文、噪声等结构信号;
* 2. 歧义消解:对低置信度候选行做二次判定;
* 3. 层级构建:把扁平信号组装成带父子关系的 draft 树;
* 4. 树校验:修复路径、层级和兄弟关系,生成最终节点。
* </p>
*/
@AllArgsConstructor
@Component
public class DocumentStructureNodeExtractor {
/** 第一阶段:信号提取器,负责将文本逐行扫描并识别出标题、列表、正文等结构信号 */
private final DocumentStructureSignalExtractor signalExtractor;
/** 第二阶段:歧义消解器,对低置信度的信号借助 LLM 进行二次判定 */
private final DocumentStructureAmbiguityResolver ambiguityResolver;
/** 第三阶段:层级解析器,将扁平的信号列表组装成带有父子关系的草稿树 */
private final DocumentStructureHierarchyResolver hierarchyResolver;
/** 第四阶段:树校验器,修复无效父节点、重算深度、重建路径,输出最终候选节点 */
private final DocumentStructureTreeValidator treeValidator;
/**
* 文档结构提取的总入口方法。
* <p>
* 这个方法本身不承载复杂规则判断,它更像一个“编排器”:
* 负责把标题和正文送入四阶段流水线,并保证每一阶段的输出都成为下一阶段的输入。
* </p>
* <p>
* 其中有两个特别重要的边界语义:
* 1. 如果正文为空,不进入任何复杂规则,直接返回一个只有 DOCUMENT 根节点的结果;
* 2. 如果正文非空,哪怕后续没有抽出任何显式标题,也会在树构建阶段保留根节点,
* 从而保证下游始终面对的是一棵合法结构树,而不是空列表。
* </p>
*
* @param documentTitle 文档标题,可为空;为空时统一兜底为“文档”
* @param parsedText 解析后的纯文本正文
* @return 结构化节点候选列表,供后续结构节点落库、导航索引和图谱投影复用
*/
public List<DocumentStructureNodeCandidate> extract(String documentTitle, String parsedText) {
// 对标题和文本做空值保护和去除首尾空白
String normalizedTitle = StrUtil.blankToDefault(documentTitle, "文档").trim();
String normalizedText = StrUtil.blankToDefault(parsedText, "").trim();
// 如果正文为空,说明当前文档没有任何可供解析的结构内容;
// 此时直接返回一个根节点,既能保持数据结构稳定,也能让下游明确知道“文档存在但无结构”。
if (normalizedText.isBlank()) {
return List.of(new DocumentStructureNodeCandidate(
1,
DocumentStructureNodeTypeEnum.DOCUMENT.getCode(),
null,
0,
0,
0,
"",
normalizedTitle,
normalizedTitle,
"/document",
"",
"",
null
));
}
// 第一阶段:信号提取 —— 逐行扫描文本,识别标题、列表、噪声等结构信号
DocumentStructureSignalBatch signalBatch = signalExtractor.extract(normalizedTitle, normalizedText);
// 从信号批次中取出原始信号列表(防御性空值处理)
List<DocumentStructureSignal> rawSignals = signalBatch == null ? List.of() : signalBatch.signals();
// 从信号批次中取出所有行的规范化文本,供歧义消解时作为上下文窗口使用
List<String> allLines = signalBatch == null ? List.of() : signalBatch.contextLines();
// 第二阶段:歧义消解 —— 对“像标题又像列表”的候选行做二次判定,
// 目的是减少误把列表当标题、或误把标题当正文的情况。
List<DocumentStructureSignal> resolvedSignals = ambiguityResolver.resolve(normalizedTitle, allLines, rawSignals);
// 第三阶段:层级构建 —— 将扁平信号列表组装为带有父子关系的草稿节点树。
List<DocumentStructureNodeDraft> drafts = hierarchyResolver.resolve(normalizedTitle, resolvedSignals);
// 第四阶段:树校验与构建 —— 修复层级异常、重算深度、重建路径,输出最终候选节点。
return treeValidator.validateAndBuild(normalizedTitle, drafts);
}
}
这个类本身不承载复杂逻辑,更像一个"编排器"——把四个阶段串起来,每一阶段的输出作为下一阶段的输入。
第一阶段:信号提取(SignalExtractor)
这是整条流水线中代码量最大的一个阶段。DocumentStructureSignalExtractor 的职责是:逐行扫描文档纯文本,通过正则模式匹配和启发式规则,把每一行分类为标题、列表项、步骤、表格行、引用、正文、噪声等结构信号。
付费内容提示
该文档的全部内容仅对「码力全开」项目实战&技术讲解 知识星球用户开放
加入星球,一次获得完整项目资料、全栈技术知识库和长期答疑服务。
100万+字全栈技术知识库深入讲解技术核心、数据库、中间件和分布式等内容
8套热门的实战项目持续更新的企业级项目覆盖高并发、微服务、数据中台 和 AI Agent 等方向
AI 技术知识大模型面试详解覆盖 AI 模型原理、Agent、RAG、MCP、Skills、Harness 等核心知识点
文档 + 视频两种讲解形式既能系统阅读,也能跟随视频理解核心业务
完整项目实战资料
每套项目均包含从 0 到 1 讲解文档核心业务讲解视频从基础项目到复杂业务场景,项目资料会持续更新。
- 01Nexus Agent AI 智能体
- 02Nexus Agent Pro 完全版
- 03黑马点评Plus
- 04大麦
- 05大麦Pro
- 06大麦AI
- 07流量切换
- 08数据中台
加入后还能获得
进入星球后,即可享受上述所有服务,保证不会再有其他隐藏费用。从学习、面试到项目启动,都可以继续获得支持。
- 1 对 1 解答项目和技术问题都可以提问
- 针对性补充没有讲清楚的内容会继续补充
- 面试与简历指导梳理回答技巧和项目亮点
- 中间件云环境项目依赖可以直接接入使用
- 面试后复盘被问住的问题可以继续交流
- 远程问题解决项目启动问题可协助排查
