从章节分割到多粒度 RAG:小说检索增强完整实战(附代码示例)

Git 仓库: langchain4j-spring-agent
相关文章:RAG 增强与向量基础篇:继续搭建“模型 + 向量 + 会话 + 工具”协同底座
相关文章:零成本打造本地多引擎大模型与向量服务:Xinference 全栈部署 + 性能调优实战
适用场景:中文长篇小说 / 文学语料的智能问答检索增强
技术栈:Spring、向量检索(VectorStore)、Hybrid 检索、分块策略、Rerank、Graph(预留)


一、写作背景

在构建面向中文长篇小说(如《红楼梦》《斗破苍穹》)的知识问答系统时,最初很多人会选择“按章节分割 + 向量检索”的简单方案。然而实践中很快遇到两个核心痛点:

  1. 章节粒度太粗:一个章节可能数千字甚至上万字,Embedding 稀释严重,相关问句只对应其中极小片段。
  2. 召回质量参差不齐:经常命中一堆不相关背景,或者就是找不到关键信息,导致回答漂移、幻觉增多。

本文将结合一个实际可运行的测试用例(NovelSplitPrintDemoTest),以及系统化的《RAG 增强检索方案》,从“章节分割 → 多粒度索引 → Hybrid + Rerank → 上下文组装”完整给出演进思路与落地细节,帮助你一步步升级小说类 RAG 系统的效果与稳定性。


二、基础章节分割示例:NovelSplitPrintDemoTest

最初我们实现了一个基于章节规则(支持“第X章”“第X回”“第X卷”等)拆分小说文本的分割器 DoupoNovelSplitter,并提供了一个直接可运行的 JUnit / main 演示类:

文件位置:langchain4j-spring-ai-chat-v2/src/test/java/com/soft/nda/chat/rag/splitter/novel/NovelSplitPrintDemoTest.java

2.1 核心演示代码(节选)

@Test
void printSplitResult() throws Exception {
    String fileProp = "F:/workspace/.../红楼梦.txt"; // 你自己的小说文本路径
    boolean chapterOnly = true; // 是否仅按章节,不做长章节二次窗口拆分
    Path p = Path.of(fileProp);
    byte[] bytes = Files.readAllBytes(p);
    MultipartFile file = new MockMultipartFile("file", p.getFileName().toString(), "text/plain", bytes);

    // 1. 自动编码识别读取
    String text = PlainTextFileUtil.readPlainTxt(file);
    System.out.println("[信息] 原始文本长度=" + text.length());

    // 2. 小说标题检测(文件名 / 《书名》 / 头部行)
    String novelTitle = NovelTitleDetector.detect(p.getFileName().toString(), text);
    System.out.println("[信息] 小说标题=" + novelTitle + " chapterOnly=" + chapterOnly);

    // 3. 章节拆分
    DoupoNovelSplitter splitter = new DoupoNovelSplitter(chapterOnly);
    List<DoupoNovelSplitter.DoupoChunk> chunks = splitter.split(text);
    System.out.println("[信息] 分块数量=" + chunks.size());

    // 4. 输出详情
    for (int i = 0; i < chunks.size(); i++) {
        DoupoNovelSplitter.DoupoChunk c = chunks.get(i);
        System.out.println("章节标题=" + c.chapterTitle + " 序号=" + c.chapterIndex + " 部分=" + c.part + "/" + c.partTotal);
        System.out.println(c.text);
    }
}

2.2 单纯章节分割的局限

局限具体表现后果
粒度太大向量表示被平均化检索结果相关度低
问句语义细粒度问 “宝玉和黛玉第一次对话内容是什么?” 却命中整章节需要大量无关内容清洗
无标签 / 实体缺乏关键词过滤维度难做混合检索与 Rerank
缺少上下文桥接章节边界处语义割裂回答不连贯

为了解决这些问题,我们需要进入多粒度与增强策略阶段。


三、RAG 增强总体思路概览

一个可持续迭代的小说 RAG 系统应包含:

  1. 多粒度索引:章节摘要 + 窗口块(语义/滚动窗口)
  2. Hybrid 检索:Dense 向量 + BM25 关键词合并打分
  3. Rerank 阶段:跨编码器或轻量排序增强 TopK 相关度
  4. 上下文组装:命中窗口补章节摘要 + 相邻桥接句 → Layered Context
  5. 结果引用与幻觉检测:保证可追溯与事实一致性

下面展开关键策略与实施细节。


四、分块与索引增强策略(关键)

4.1 多粒度分层

  • 章节级:保留章节标题 + 摘要(summary)向量,用于补充宏观语境。
  • 窗口级:对正文做 400~600 字滑动窗口,overlap 50~80 字,提升定位精度。

4.2 自适应与语义边界切分

  • 段落 >600 字拆分;<150 且连续合并提升稳定。
  • 可选:句向量余弦差值 >0.25 处作为语义断点(减少语义跨越)。

4.3 关键词与实体抽取

  • 轻量实现:正则 + 停用词过滤 + TopN 词频。
  • 实体:人名 / 地名 / 门派 / 特殊物品,可维护一个词典或自动统计高频专有名词。

4.4 桥接与再聚合

  • 每块外层增加 bridge_left / bridge_right 句子(不参与向量计算),回答时提高上下文连贯。
  • 检索命中相邻块(同章节且序号连续)→ 自动合并展示。

4.5 冗余过滤

  • 块与章节摘要余弦 >0.95 → 标记冗余,索引时可降权或跳过。
  • SimHash 去重:近似重复文本不重复建向量。

五、Hybrid 检索与多样性控制

5.1 Dense + Lexical

  • Dense:窗口块 embedding。
  • Lexical:BM25 / ES term。可对实体词加权。
  • 合并打分:score = w_dense * s_dense + w_lex * s_lex

5.2 多样性(Diversity)

  • 若命中块之间余弦相似度 >0.92 → 对后者乘以 1 - penalty(例如 penalty=0.15)。

六、Rerank 与伪相关反馈 (PRF)

6.1 Rerank

  • 初检 topK(如 40)→ RerankTopN(如 16)→ 输出最终 topK_answer(如 8)。
  • 模型不可用时:构造轻量得分 = 语义余弦 + 关键词重合率 + 实体匹配率。

6.2 PRF

  • 取前 3 块抽取新关键词 / 实体 → 追加扩展查询 → 合并结果提升召回覆盖率。

七、Query 预处理与扩展

步骤示例策略输出
标准化繁简转换、去广告符号规范问句
实体识别正则捕获人名“宝玉”“黛玉”实体列表
意图分类问“关系”还是“事件细节”意图标签
扩展实体 + 同义词 + 近义 paraphrase多查询集合

八、上下文组装与回答可信度

8.1 Layered Context

  • Primary:命中窗口正文。
  • Supplement:对应章节摘要 + 邻接桥接句 + 关键实体说明。

8.2 幻觉检测(基础)

  • 回答中实体不在任何上下文 → 标记“不确定”或减少该段输出。

8.3 引用与溯源

  • 每段引用格式:[章节: 第12回 / 块 3/5 / 范围 120~480]

九、配置扩展建议(RagServiceConfig

新增字段示例:

{
  "multiGranularityEnabled": true,
  "windowSize": 500,
  "windowOverlap": 60,
  "summaryEnabled": true,
  "attachChapterSummary": true,
  "keywordExtractionEnabled": true,
  "entityExtractionEnabled": true,
  "expansionEnabled": true,
  "expansionStrategy": "entity",
  "rerankMaxPassages": 16,
  "diversityPenalty": 0.15
}

十、演进实施阶段建议

阶段目标内容风险回滚
Phase 1精准度提升多粒度索引 + keywords + basic hybrid + rerank索引增多关闭 multiGranularity
Phase 2覆盖扩展Query 扩展 + PRF + 实体识别延迟上升关闭 expansion
Phase 3答案可信引用 + 幻觉检测 + Layered context复杂度提升简化上下文拼接
Phase 4深度优化语义边界 + 逆向向量 + 多模型资源消耗单模型回退
Phase 5图谱融合Graph-RAG + 关系检索图维护成本关闭 graphEnabled

十一、质量评估指标

指标说明目标值(示例)
Hit@K相关块出现在 TopK> 0.85
MRR平均倒数排名> 0.75
Diversity Rate高相似块占比< 0.25
Hallucination Rate未在上下文的实体占比< 0.10
Latency(ms)单次检索+组装< 800ms
Coverage回答引用上下文利用率> 0.55

十二、典型检索流水线伪代码

Query q = preprocess(userInput);
List<Result> dense = vectorStore.search(embed(q.text), topKDense);
List<Result> lexical = bm25.search(q.text, topKLex);
List<Result> merged = mergeScores(dense, lexical, wDense, wLex);
if (cfg.enableRerank) { merged = rerank(merged, cfg.rerankMaxPassages); }
List<Result> diversified = diversityFilter(merged, 0.92, cfg.diversityPenalty);
Context ctx = assembleContext(diversified, cfg.attachChapterSummary);
Answer ans = llmGenerate(q, ctx);
return attachSources(ans, diversified);

十三、索引阶段多粒度伪代码

for (Chapter ch : chapterSplitter.split(docText)) {
    String summary = cfg.summaryEnabled ? summarizer.summary(ch.text) : truncate(ch.text, 600);
    upsertVector(point(ch.id+"#summary", embed(summary), payloadSummary(ch)));
    for (Window w : windowMaker.make(ch.text, cfg.windowSize, cfg.windowOverlap)) {
        List<String> kw = cfg.keywordExtractionEnabled ? keywordExtractor.extract(w.text) : List.of();
        List<String> ents = cfg.entityExtractionEnabled ? entityExtractor.extract(w.text) : List.of();
        Map<String,Object> payload = basePayload(...);
        payload.put("keywords", kw);
        payload.put("entities", ents);
        upsertVector(point(w.id, embed(w.text), payload));
    }
}

十四、实战落地建议步骤(可直接开始)

  1. 新增配置字段 & 默认值(先不全做语义切分)。
  2. RagIndexService 增加窗口切分逻辑(可与章节循环一起)。
  3. 实现简易 KeywordExtractor(正则 + 词频 TopN)与 EntityExtractor(人名匹配)。
  4. 增加 Hybrid 合并:先写一个 ScoreMerger 接口,后续替换更复杂逻辑。
  5. 引入轻量 Rerank(若暂无跨编码器,用关键词+实体匹配得分)。
  6. 上下文组装:新增 ContextAssembler,自动补章节摘要与桥接句。
  7. 在回答输出附加来源引用结构。
  8. 埋点记录检索耗时 + 命中块数 + 平均块长度,定期调参。

十五、常见踩坑与解决方案

问题现象解决
向量条目膨胀磁盘/内存急剧增加控制窗口大小 + 冗余过滤 + 只向量化摘要或窗口其一临时回退
召回过宽输出一堆无关块提升关键词权重或缩小窗口、增加实体过滤
回答幻觉引用了不存在的剧情开启实体校验 + 减少上下文过度摘要
延迟上升Hybrid + Rerank 全开分阶段开关;限制 rerankMaxPassages
标题/章节丢失“第一回”被过滤改善章节去重逻辑(区分类别:卷 / 回 / 章)

十六、总结

从“章节分割”到“多粒度 RAG”是小说智能问答从入门到可生产的关键升级过程。通过:

  • 双层索引(章节摘要 + 窗口块)
  • Hybrid 检索 + Rerank
  • 标签/实体增强 + 多样性控制
  • 上下文分层组装 + 引用与幻觉检测
    可以显著提升回答的相关性、完整性与可信度。

后续若再结合 Graph-RAG(人物/地点/事件节点)与多模型融合,将进一步让系统具备“知识网络 + 语义定位”双能力。


十七、后续可扩展点(展望)

  1. 逆向问题生成向量:提升检索多样性。
  2. 在线学习:利用用户点击/纠正动态调权。
  3. 向量聚类预摘要:对海量章节预聚类,降低检索范围。
  4. Memory Summary:多轮对话稳定人物关系上下文。

欢迎在评论区交流你的实践疑问或改进思路。

如果文章对你有帮助,记得收藏 + 点赞,后续会继续分享 RAG 与小说语料处理进阶技巧。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值