Git 仓库: langchain4j-spring-agent
相关文章:RAG 增强与向量基础篇:继续搭建“模型 + 向量 + 会话 + 工具”协同底座
相关文章:零成本打造本地多引擎大模型与向量服务:Xinference 全栈部署 + 性能调优实战
适用场景:中文长篇小说 / 文学语料的智能问答检索增强
技术栈:Spring、向量检索(VectorStore)、Hybrid 检索、分块策略、Rerank、Graph(预留)
一、写作背景
在构建面向中文长篇小说(如《红楼梦》《斗破苍穹》)的知识问答系统时,最初很多人会选择“按章节分割 + 向量检索”的简单方案。然而实践中很快遇到两个核心痛点:
- 章节粒度太粗:一个章节可能数千字甚至上万字,Embedding 稀释严重,相关问句只对应其中极小片段。
- 召回质量参差不齐:经常命中一堆不相关背景,或者就是找不到关键信息,导致回答漂移、幻觉增多。
本文将结合一个实际可运行的测试用例(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 系统应包含:
- 多粒度索引:章节摘要 + 窗口块(语义/滚动窗口)
- Hybrid 检索:Dense 向量 + BM25 关键词合并打分
- Rerank 阶段:跨编码器或轻量排序增强 TopK 相关度
- 上下文组装:命中窗口补章节摘要 + 相邻桥接句 → Layered Context
- 结果引用与幻觉检测:保证可追溯与事实一致性
下面展开关键策略与实施细节。
四、分块与索引增强策略(关键)
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));
}
}
十四、实战落地建议步骤(可直接开始)
- 新增配置字段 & 默认值(先不全做语义切分)。
- 在
RagIndexService增加窗口切分逻辑(可与章节循环一起)。 - 实现简易
KeywordExtractor(正则 + 词频 TopN)与EntityExtractor(人名匹配)。 - 增加 Hybrid 合并:先写一个 ScoreMerger 接口,后续替换更复杂逻辑。
- 引入轻量 Rerank(若暂无跨编码器,用关键词+实体匹配得分)。
- 上下文组装:新增
ContextAssembler,自动补章节摘要与桥接句。 - 在回答输出附加来源引用结构。
- 埋点记录检索耗时 + 命中块数 + 平均块长度,定期调参。
十五、常见踩坑与解决方案
| 问题 | 现象 | 解决 |
|---|---|---|
| 向量条目膨胀 | 磁盘/内存急剧增加 | 控制窗口大小 + 冗余过滤 + 只向量化摘要或窗口其一临时回退 |
| 召回过宽 | 输出一堆无关块 | 提升关键词权重或缩小窗口、增加实体过滤 |
| 回答幻觉 | 引用了不存在的剧情 | 开启实体校验 + 减少上下文过度摘要 |
| 延迟上升 | Hybrid + Rerank 全开 | 分阶段开关;限制 rerankMaxPassages |
| 标题/章节丢失 | “第一回”被过滤 | 改善章节去重逻辑(区分类别:卷 / 回 / 章) |
十六、总结
从“章节分割”到“多粒度 RAG”是小说智能问答从入门到可生产的关键升级过程。通过:
- 双层索引(章节摘要 + 窗口块)
- Hybrid 检索 + Rerank
- 标签/实体增强 + 多样性控制
- 上下文分层组装 + 引用与幻觉检测
可以显著提升回答的相关性、完整性与可信度。
后续若再结合 Graph-RAG(人物/地点/事件节点)与多模型融合,将进一步让系统具备“知识网络 + 语义定位”双能力。
十七、后续可扩展点(展望)
- 逆向问题生成向量:提升检索多样性。
- 在线学习:利用用户点击/纠正动态调权。
- 向量聚类预摘要:对海量章节预聚类,降低检索范围。
- Memory Summary:多轮对话稳定人物关系上下文。
欢迎在评论区交流你的实践疑问或改进思路。
如果文章对你有帮助,记得收藏 + 点赞,后续会继续分享 RAG 与小说语料处理进阶技巧。
2189

被折叠的 条评论
为什么被折叠?



