know-engine

父子分段的切分和检索逻辑

传统固定长度切分会把一个完整的语义单元(如一段说明、一个步骤)切成两半,而我们为了解决这个问题,采用了标题分段+父子分段的方式。 先按 Markdown 标题层级切分,保证每个分段边界在标题处。超长分段保留完整父分段,用于展示时替换…

TL;DR

传统固定长度切分会把一个完整的语义单元(如一段说明、一个步骤)切成两半,而我们为了解决这个问题,采用了标题分段+父子分段的方式。 先按 Markdown 标题层级切分,保证每个分段边界在标题处。超长分段保留完整父分段,用于展示时替换…

传统固定长度切分会把一个完整的语义单元(如一段说明、一个步骤)切成两半,而我们为了解决这个问题,采用了标题分段+父子分段的方式。 先按 Markdown 标题层级切分,保证每个分段边界在标题处。超长分段保留完整父分段,用于展示时替换,检索时用小分片定位,返回时用父分段展示完整内容。

传统方式:         本设计:
├─ 分段1(前500字)   ├─ 分段1-完整内容(父分段)
├─ 分段2(后500字)   │   ├─ 分段1a(子分片,用于检索)
                   │   └─ 分段1b(子分片,用于检索)
                   └─ 分段2-完整内容

检索阶段:小分片做向量匹配,精度高,能定位到具体知识点 展示阶段:用父分段替换子分片,内容完整,用户看到完整上下文

切分逻辑

在MarkdownHeaderParentTextSplitter中,我们实现基于markdown标题层级进行文档分段,并建立负载关系 Step1 , 标题检测与元数据维护 - 使用 headerStack 维护当前标题层级 - 遇到新标题时,将之前累积的内容保存为一个分段 - 每个分段生成唯一的 chunkId Step2,聚合相同元数据的行 - 相同元数据(相同标题路径)的行合并为一个分块 - 标题与其下第一段内容合并 Step3,chunkSize 二次切割 - 超出 chunkSize 的分段 - 保留完整分片,标记 skipEmbedding=1(不用于向量检索) - 生成多个子分片,每个子分片携带 parentChunkId

// splitByChunkSize  方法, 超出 chunkSize 的分片处理
if
    result
}


    fullMetadata
    fullMetadata
    result


        subMetadata
        subMetadata
        result

}

需要用到的metadata的定义:

检索逻辑

在检索相关代码(KnowEngineElasticsearchHnswContentRetriever\KnowEngineElasticsearchKnnContentRetriever)中,针对父子分段做了特殊处理。 在检索到相应的chunk之后,做父子分段的处理: - 检查检索结果是否携带 parentChunkId - 若有,读取父分段的完整文本 - 用父分段内容替换子分段,获得更完整的语义

// 父分段替换
String parentChunkId = content.textSegment().metadata().getString(PARENT_CHUNK_ID);
if (parentChunkId != null) {
    // 读取 parentChunk 的完整文本
    String segmentText = knowledgeSegmentService.getTextByChunkId(parentChunkId);
    if (segmentText != null) {
        TextSegment parentSegment = TextSegment.from(segmentText, content.textSegment().metadata());
        finalContents.remove(content);      // 移除子分段
        finalContents.addAll(parentDocs);   // 加入父分段
    }
}
版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。