三国演义向量检索实战:RAG 混合切分 + Qdrant + BGE(Recall@5=0.8 全流程)
摘要 本文针对长篇中文小说《三国演义》构建RAG问答系统,提出"章回识别+章节内滑动窗口"的混合切分策略,使用OpenAI兼容的BGE向量模型和Qdrant向量数据库。通过解耦数据集生成、向量入库和召回评测三个环节,形成可复现的工程闭环。重点解决了批量upsert兼容性、ID类型、维度一致性等关键工程问题,最终在15个经典问题测试集上达到Recall@5=0.8的效果。文章详细
Git 仓库: langchain4j-spring-agent/langchain4j-spring-ai/langchain4j-spring-ai-seg-flow
相关文章:RAG 增强与向量基础篇:继续搭建“模型 + 向量 + 会话 + 工具”协同底座
相关文章:零成本打造本地多引擎大模型与向量服务:Xinference 全栈部署 + 性能调优实战
技术栈:Spring、向量检索(VectorStore)、Hybrid 检索、分块策略、Rerank、Graph(预留)
关键词:三国演义 RAG 向量检索 Qdrant BGE OpenAI兼容 章回切分 滑动窗口 召回率 Recall@5
摘要
本文面向长篇中文小说(三国演义),构建以“章回识别 + 章节内滑动窗口”的混合切分策略数据集,使用 OpenAI 兼容的向量服务生成嵌入,落地 Qdrant 作为向量数据库,并以固定评测集衡量 Recall@5。我们将数据集、入库流水线与召回评测解耦,形成可复现、可对比的工程闭环,定位并修复了导致召回骤降的关键工程问题(批量 upsert 兼容、ID 类型、维度一致性与嵌入接口对齐)。最终在基线配置下,召回集达到 Recall@5 = 12/15(0.8)。
1. 背景与目标
- 业务目标:支持“按章节检索”的典故问答,如“草船借箭是哪一回?”、“桃园结义在哪一回?”。
- 技术目标:
- 产出可复用的数据集与评测集,支撑持续优化;
- 实施“章回标题 + 章节滑窗”的混合切分;
- 建立稳定、可复制的“入库—召回”流水线;
- 用 Recall@K 衡量效果,闭环工程问题。
2. 数据与切分方法
2.1 数据集
- 原文:三国演义.txt(简体中文)
- 输出表:
doc/aigc_chunk_store.csv - 主要字段:
document_name,chapter_title,chapter_index,chunk_index,text,start_offset,end_offset,strategy_chain,version,hash,created_at
2.2 切分策略(混合)
- 章回识别(rule.heading)
- 以“正文 第…回 …”为显著特征;
- 典型正则(示例):
^正文\s+?第[一二三四五六七八九十百千0-9]+[回章节].*$。
- 章节内滑动窗口(window.overlap)
- 目标:提升局部语义密度与命中率;
- 推荐参数(小说语料经验):
windowSize=128, overlap=32; - 产物:每章被细分为多个 chunk,
chunk_index递增。
- 元信息植入
- 每个 chunk 持久化章节元数据,便于命中判定与结果解释。
3. 系统与流程
3.1 组件与接口
- 向量服务(Embedding)
- OpenAI 兼容:
POST /v1/embeddings - 请求:
{"model":"...","input":"文本"};响应:{"data":[{"embedding":[...]}]}
- OpenAI 兼容:
- 向量库(Qdrant v1.16.2)
- 创建集合:
PUT /collections/{name}(vectors.size,vectors.distance) - Upsert:
PUT /collections/{name}/points(points/batch 兼容)
- 创建集合:
3.2 流程概述
- 数据集(CSV)由“章回正则 + 窗口切分”生成;
End2EndVectorPipelineTest:只做“读取 CSV → 生成向量 → ensureCollection → upsert”入库;SanguoRecallEvalTest:只做“加载评测集 → 生成查询向量 → Qdrant 检索 → 统计 Recall@K”。- 入库与评测解耦,便于独立定位问题与对比实验。
4. 关键实现细节
4.1 入库(End2EndVectorPipelineTest)
- 只负责入库,不做检索断言;
- 以“第一条向量”的实际维度创建/校验集合(
distance=Cosine); - Upsert 首选单点格式:
若返回{"points":[{"id":"<uuid>","vector":[...],"payload":{...}}]}missing field ids,自动 fallback 到 batch 格式:{"batch":{"ids":[...],"vectors":[...],"payloads":[...]}} - ID 统一用 UUID 字符串(Qdrant 支持无符号整型或 UUID)。
4.2 嵌入服务一致性
- 两个测试类统一:HTTP/1.1、
/v1/embeddings、同一model,解析data[0].embedding → List<Float>; - 集合维度与实际嵌入维度一致,避免“写入/检索维度不匹配”。
4.3 召回(SanguoRecallEvalTest)
- 评测文件:
doc/eval/sanguo-eval.json(数组/对象包装/JSONL 兼容); - 命中规则(宽松):
- 标题归一化(去“正文”、空白折叠、lowercase)后与
expects.chapterTitle等价/包含匹配; - 若标题不足,再以
accept关键词在payload.text中匹配(如“温酒”“华雄”“借箭”等)。
- 标题归一化(去“正文”、空白折叠、lowercase)后与
5. 评测设计
- 指标:Recall@5(每条 query 是否在 Top-5 中命中正确章节或关键词;最终
hits/total)。 - 评测集:15 个经典问句(桃园结义、草船借箭、隆中对、过五关斩六将、空城计等)。
- 控制变量:入库与评测严格使用同一 embeddings 端点、同一集合。
6. 实验结果与分析
- 早期问题:某批量入库路径导致 0/15(详见 §7);
- 修复后基线:全量入库 + 评测 → Recall@5 = 12/15(0.8);
- 典型命中:
- “草船借箭是哪一回?” → 第四十六回(用奇谋孔明借箭 …);
- “过五关斩六将讲的是哪一章?” → 第二十七回(美髯公千里走单骑 …)。
- 未命中(3/15)主要模式:
- 章节标题异形/标点差异,标题归一不足;
- 事件描述分散,窗口落点偏离;
- 问句缺少章节显著线索,纯语义未覆盖到目标片段。
7. 误差定位与工程修复
- 批量 upsert 与 Qdrant 版本兼容:
PUT /points使用points结构时某些版本/网关提示missing field ids;- 解决:检测该错误后自动回退至 batch 结构(
ids/vectors/payloads三并列)。
- ID 类型不合规:
- 自定义字符串(如
test-0)会被拒绝; - 解决:统一使用 UUID。
- 集合不存在或维度不匹配:
- 404 Not Found;或集合
size与实际嵌入维度不同导致召回异常; - 解决:入库前以第一条向量维度 ensureCollection,
distance=Cosine。
- 嵌入接口不一致:
- 端点未拼
/v1/embeddings或解析差异,导致“假向量”; - 解决:两端完全统一 HTTP/1.1 + OpenAI 兼容协议,固定解析
data[0].embedding。
- 窗口参数过粗:
512/80容易稀释事件密度;- 解决:小窗口
128/32更有利于典故命中,尤其是短问句场景。
8. 最佳实践与建议
- 维度一致性:以“实际嵌入维度”创建集合,不盲信配置;
- 单条 upsert 优先:保留 batch 作为兼容回退;
- Payload 富化:
documentName/chapterTitle/chapterIndex/text必备; - 切分双层结构:先章回粗切,再滑窗细切;
- 检索后处理:可做“章节归并”(相邻 chunk 合并显示);必要时引入轻量 reranker;
- 评测方法学:召回集包含事件别称与关键词;标题匹配与关键词匹配“双保险”。
9. 复现步骤(本地)
前置:
- Qdrant 运行在
http://localhost:6333- Embeddings 服务运行在
http://127.0.0.1:9997/v1/embeddings(OpenAI 兼容;示例模型:bge-large-zh-v1.5)
- 全量入库(仅入库,不检索)
- 运行
End2EndVectorPipelineTest:- 读取
doc/aigc_chunk_store.csv; - 以第一条向量维度 ensureCollection(Cosine);
- 逐条 upsert(失败自动回退到 batch)。
- 读取
- 召回评测
- 运行
SanguoRecallEvalTest:- 读取
doc/eval/sanguo-eval.json; - 逐条生成查询向量 → Qdrant 搜索 → 统计 Recall@5;
- 控制台打印未命中 Top3 预览,辅助误差定位。
- 读取
常见排障:
- 404 集合不存在 → 先跑入库;
missing field ids→ 确认已走 batch 回退;- 召回异常为 0 → 检查集合
size与实际向量维度是否一致;- 命中“跑偏” → 检查窗口参数是否过大、标题归一是否生效。
10. 结论与展望
本文基于“三国演义”场景,验证了“章回 + 滑窗”的混合切分策略,配合 OpenAI 兼容嵌入与 Qdrant,形成稳定、可复现的“入库—召回”流水线。对齐嵌入接口、统一维度与 ID、修复 upsert 兼容等工程细节后,召回达到 Recall@5 ≈ 0.8。
后续方向:
- 自适应窗口(按语义密度/标点分布动态设定 windowSize/overlap);
- 多粒度双塔(章节粗粒度 + 片段细粒度并发检索,合并排序);
- 语义增强(关键词/事件标签/NER),提升可解释性与鲁棒性;
- 流水线作业化(幂等重试、指标面板、离线 A/B)。
附:文件与对象对照
- 数据集:
doc/aigc_chunk_store.csv(章回 + 滑窗生成) - 召回集:
doc/eval/sanguo-eval.json(15 条经典问句) - 入库:
End2EndVectorPipelineTest(仅入库) - 召回:
SanguoRecallEvalTest(仅评测) - 关键字段:
documentName,chapterTitle,chapterIndex,text - 嵌入:
List<Float>;接口:OpenAI 兼容/v1/embeddings - 向量库:Qdrant v1.16.2;
distance=Cosine;集合维度=实际嵌入 size
更多推荐


所有评论(0)