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 切分策略(混合)

  1. 章回识别(rule.heading)
  • 以“正文 第…回 …”为显著特征;
  • 典型正则(示例):^正文\s+?第[一二三四五六七八九十百千0-9]+[回章节].*$
  1. 章节内滑动窗口(window.overlap)
  • 目标:提升局部语义密度与命中率;
  • 推荐参数(小说语料经验):windowSize=128, overlap=32
  • 产物:每章被细分为多个 chunk,chunk_index 递增。
  1. 元信息植入
  • 每个 chunk 持久化章节元数据,便于命中判定与结果解释。

3. 系统与流程

3.1 组件与接口

  • 向量服务(Embedding)
    • OpenAI 兼容:POST /v1/embeddings
    • 请求:{"model":"...","input":"文本"};响应:{"data":[{"embedding":[...]}]}
  • 向量库(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 中匹配(如“温酒”“华雄”“借箭”等)。

5. 评测设计

  • 指标:Recall@5(每条 query 是否在 Top-5 中命中正确章节或关键词;最终 hits/total)。
  • 评测集:15 个经典问句(桃园结义、草船借箭、隆中对、过五关斩六将、空城计等)。
  • 控制变量:入库与评测严格使用同一 embeddings 端点、同一集合。

6. 实验结果与分析

  • 早期问题:某批量入库路径导致 0/15(详见 §7);
  • 修复后基线:全量入库 + 评测 → Recall@5 = 12/15(0.8)
  • 典型命中:
    • “草船借箭是哪一回?” → 第四十六回(用奇谋孔明借箭 …);
    • “过五关斩六将讲的是哪一章?” → 第二十七回(美髯公千里走单骑 …)。
  • 未命中(3/15)主要模式:
    • 章节标题异形/标点差异,标题归一不足;
    • 事件描述分散,窗口落点偏离;
    • 问句缺少章节显著线索,纯语义未覆盖到目标片段。

7. 误差定位与工程修复

  1. 批量 upsert 与 Qdrant 版本兼容:
  • PUT /points 使用 points 结构时某些版本/网关提示 missing field ids
  • 解决:检测该错误后自动回退至 batch 结构(ids/vectors/payloads 三并列)。
  1. ID 类型不合规:
  • 自定义字符串(如 test-0)会被拒绝;
  • 解决:统一使用 UUID。
  1. 集合不存在或维度不匹配:
  • 404 Not Found;或集合 size 与实际嵌入维度不同导致召回异常;
  • 解决:入库前以第一条向量维度 ensureCollection,distance=Cosine
  1. 嵌入接口不一致:
  • 端点未拼 /v1/embeddings 或解析差异,导致“假向量”;
  • 解决:两端完全统一 HTTP/1.1 + OpenAI 兼容协议,固定解析 data[0].embedding
  1. 窗口参数过粗:
  • 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
  1. 全量入库(仅入库,不检索)
  • 运行 End2EndVectorPipelineTest
    • 读取 doc/aigc_chunk_store.csv
    • 以第一条向量维度 ensureCollection(Cosine);
    • 逐条 upsert(失败自动回退到 batch)。
  1. 召回评测
  • 运行 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
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐