⛏️ 知识挖掘 / RAG
knowledge-mining-lab:从会话日志到知识库的全链路跑通
一、背景速览:把调研报告的"知识挖掘"章节变成能跑的代码
调研报告 《大模型场景知识体系建设》↗ §④ 讲了四条知识挖掘管道、§⑥ 讲了 Golden Set 评测——但都是方法论。本次实战把其中 最有性价比的一条链路(存量文档结构化 + 会话日志挖掘)完整写成代码并在本机跑通, 不依赖任何云服务和第三方包。
输入
4 份企业制度文档(Markdown)+ 24 通客服会话日志(JSONL,刻意埋入 PII、同义重复、闲聊、答不上来的问题)。
输出
18 个带元数据的知识块 + 14 条锚定过原文的 QA 知识条目 + 5 组带优先级的知识缺口清单 + 检索评测报告。
验证方式
16 条人工标注 Golden Set(含口语/错别字),双索引对照跑 Hit@1 / Hit@3 / MRR@3。
核心问题
会话挖掘出来的 QA,到底对检索有没有可测量的增益?(结论:有,Hit@1 0.938 → 1.0)
二、链路设计:六步流水线,每步一个脚本一份产物
| 步骤 | 脚本 | 做什么 | 关键参数/机制 |
|---|---|---|---|
| 1 解析分块 | step1_parse_chunk.py | Markdown → 带元数据知识块 | H2 章节为语义边界,超长按段落递归细分(400 token 上限 / 50 重叠);标题路径拼进检索文本 |
| 2 清洗脱敏 | step2_clean_pii.py | 会话 PII 脱敏 + 无效会话过滤 | 手机/身份证/邮箱/订单号四类正则;纯寒暄会话直接剔除 |
| 3 抽取去重 | step3_extract_dedup.py | 会话 → QA 候选;答不上来的分流成缺口信号 | 连续用户消息合并为一问;SimHash(64bit,阈值≤12)抓字面近拷贝 |
| 4 审核入库 | step4_curate_kb.py | 锚定校验 + 语义去重 + 缺口优先级 | 答案与制度原文 token 重合率 ≥0.35 才自动入库;同锚点 QA 合并为一条;缺口按 负反馈+转人工 加权排序 |
| 5 建索引 | step5_build_index.py | 文档块 + 挖掘 QA 双形态统一索引 | 内置 BM25(k1=1.5, b=0.75)+ 中文 bigram 分词;支持命令行即席查询 |
| 6 评测 | step6_eval.py | Golden Set 检索评测 | 命中判定到 文档#章节 级;chunk-only vs 混合索引双跑对照 |
设计取舍:LLM 不在关键路径上——step4 的同义问法扩写做成可选增强(设
DASHSCOPE_API_KEY 自动启用、调用失败自动降级),保证链路在无网络、无 key 的机器上也能一键跑通。
这也是 qwen-bench 验证过的"mock 可跑、有 key 真跑"模式。
三、环境与数据:脏数据是故意埋的
测试数据不是干净样本,四类"脏"各有埋点,用来验证对应环节真的在干活:
| 埋点 | 样本 | 考验的环节 |
|---|---|---|
| PII 泄露 | 手机号 ×2、身份证号 ×1、邮箱 ×1、订单号 ×2 | step2 脱敏正则 |
| 同义重复 | S001/S010/S019 三通都在问"没拆封能不能退" | step3 SimHash + step4 锚点聚合 |
| 闲聊噪声 | S005"在吗/没事发错了"、S015 找机器人讲笑话 | step2 无效会话过滤 |
| 知识缺口 | 货到付款、价保、以旧换新等 5 个文档没写的问题 | step3 分流 + step4 缺口优先级 |
目录结构
knowledge-mining-lab/
├── data/
│ ├── raw_docs/ # 4 份制度文档:退款/物流/会员/发票
│ ├── chat_logs.jsonl # 24 通客服会话(埋入四类脏数据)
│ └── golden_set.jsonl # 16 条人工标注评测集(标到 文档#章节)
├── km_lab/
│ ├── common.py # 中文 bigram 分词 + 内置 BM25
│ └── step1..step6 # 六步流水线(见上表)
├── out/ # 运行产物:chunks/kb/gaps/index/eval_report
└── run_all.py # 一键跑全链四、跑通步骤:两条命令
一键全链路
$ cd knowledge-mining-lab $ python3 run_all.py [step1] 解析 4 份文档 → 18 个知识块 → out/chunks.jsonl [step2] 输入 24 通会话 → 有效 22 通,剔除无效 2 通 ['S005', 'S015'] [step2] PII 脱敏命中:{'id_card': 1, 'phone': 2, 'email': 1, 'order_no': 2} [step3] 抽取 QA 候选 25 条:有效 20 条,缺口信号 5 条 [step4] 锚点聚合去重:合并 6 条同义 QA → 知识条目 14 条 [step4] 知识缺口 5 组(按优先级降序) [缺口] P4 可我用掉的积分比扣的多,现在是负的?… [缺口] P4 你们支持货到付款吗?… [step5] 索引构建完成:文档块 18 + 挖掘QA 14 = 32 个检索单元 [step6] 仅文档块索引 :Hit@1=0.938 Hit@3=1.0 MRR@3=0.969 [step6] 文档块+挖掘QA :Hit@1=1.0 Hit@3=1.0 MRR@3=1.0 ✓ 全链路完成,产物见 out/ 目录
即席查询验证索引
$ python3 -m km_lab.step5_build_index "满多少免运费" 即席查询:满多少免运费 37.71 [ qa ] KB-S008 满多少免运费来着 13.33 [chunk] membership-policy.md#1 会员等级 13.15 [chunk] shipping-policy.md#2 配送范围与运费
挖掘出的 QA(真实用户问法"满多少免运费来着")检索分远高于制度原文——这就是会话挖掘的直观价值。
五、实测结果:每个环节的账
| 环节 | 输入 | 输出 | 验证点 |
|---|---|---|---|
| step1 分块 | 4 份 Markdown | 18 块(71-229 token) | 全部带 doc/section/heading/version 元数据,无超限块 |
| step2 清洗 | 24 通会话 | 22 通有效 | 4 类 PII 6 处全部脱敏;S005/S015 两通闲聊被正确剔除 |
| step3 抽取 | 22 通会话 | QA 候选 20 + 缺口 5 | S006 连续两条用户消息正确合并为一问 |
| step4 入库 | 20 条候选 | 14 条知识条目 | 20/20 通过锚定校验;6 条同义 QA 被锚点聚合合并(含 S001/S010/S019 三胞胎) |
| step4 缺口 | 5 条信号 | 5 组带优先级 | 负反馈+转人工的"积分负数"排 P4 首位——正确反映业务疼痛 |
| step6 评测 | 16 条 Golden Set | 混合索引 Hit@1=1.0 | 对照实验拉开差距:chunk-only 在口语查询 G15 上 top1 排错 |
决定性的一条明细(G15):口语查询「快递好几天没动静了怎么办」——纯文档索引 top1 命中错误章节
refund-policy.md#4(拒收与退款,7.13 分);混合索引 top1 直接命中
KB-S018(挖掘自真实会话"物流5天没动了",15.29 分,归属 shipping-policy.md#3 正确)。
用户的话和文档的话不是一种话,会话挖掘补的就是这个 gap。
六、三个真实踩坑:全部被运行结果暴露,不是纸上推演
| # | 坑 | 暴露方式 | 修复 |
|---|---|---|---|
| 1 | 中文语境下正则 \b 失效:Python 里中文也算 \w,"号330102…"处没有词边界 |
首轮运行 id_card: 0, order_no: 0——身份证和订单号一个都没脱敏 |
改前后环视 (?<!\d)…(?!\d),命中变为 1/2 |
| 2 | SimHash 对短文本同义改写无效:只能抓字面近拷贝 | 三条同义"没拆封能退吗"实测汉明距离 19-31,与随机对(≈32)无法区分,去重 0 条 | 语义去重改为锚点聚合:同义问题答案必锚到同一制度章节,按 anchor_chunk 合并,成功合并 6 条 |
| 3 | chunk_id 解析切错分隔符:split(".")[0] 切到 ".md" 的点 |
挖掘 QA 评测归属全错,混合索引 Hit@1 假性暴跌 1.0 → 0.167(差点得出"挖掘有害"的错误结论) | 先按 # 切,再去小数后缀 |
坑 3 的教训最值钱:评测代码本身也会有 bug。如果当时直接相信 0.167 这个数字,
结论就会是"挖掘 QA 污染索引"——与真相完全相反。指标异常时先怀疑评测链路,再怀疑被评对象
(调研报告 §⑥ 说的"检索生成分开评"之外,还要加一条:评测器自己要先被验证)。
七、核心发现
发现 ①
会话挖掘的增益可测量:混合索引把 Hit@1 从 0.938 提到 1.0、MRR 从 0.969 提到 1.0——增量全部来自口语查询。文档写"物流查询与异常",用户说"没动静了",靠挖掘 QA 建桥。
发现 ②
SimHash ≠ 语义去重:短文本同义改写的汉明距离和随机文本没差别。知识库的正确去重键是"锚定到哪个知识点",不是"文字长得像不像"。
发现 ③
锚定校验(≥0.35 重合率)是自动入库的安全阀:答案锚不上制度原文就进人工复核,防止客服口误被固化。本轮 20/20 通过是因为模拟数据干净,真实数据会大量拦截。
发现 ④
知识缺口清单是免费副产品:转人工/答不上的 5 组问题自动带优先级输出(负反馈 +2、转人工 +1),直接就是下一轮文档建设 backlog。
八、接真实系统要改哪些位置
| 环节 | 本 Lab 实现 | 生产替换方案 |
|---|---|---|
| 数据源 | 本地 JSONL | step2 的 read_jsonl 换成客服系统会话表/工单 API 拉取;增量按天跑 |
| 文档解析 | Markdown 正则 | PDF/Word 换 RAGFlow DeepDoc 或云平台解析(报告 §⑤:表格密集文档是重灾区) |
| 检索 | 内置 BM25 | 加向量通道做混合检索(BM25 0.3 + 向量 0.6 + 元数据 0.1),Embedding 用百炼 text-embedding 或 BGE 本地 |
| 同义扩写/聚类 | 规则基线 + 可选 qwen-plus | 设 DASHSCOPE_API_KEY 即启用(代码已写好);QA 生成升级为 LLM 从会话直接抽取+改写 |
| 锚定校验 | token 重合率 ≥0.35 | 升级为 LLM-as-Judge 的 Faithfulness 判定(报告 §⑥ 的 Judge Prompt 模板可直接用) |
| 评测 | 16 条 Golden Set | 按 50/25/15/10 配比扩到 50-200 条,badcase 每周回填(报告 §⑥) |
复现入口:工具在
学习用/knowledge-mining-lab/,python3 run_all.py 一条命令复现本页全部数字;
README 里有踩坑详解与逐步说明。
九、生产级升级版:knowledge-mining-pipeline(八项工程化设施全部实测)
教学版跑通之后,同一条业务链路升级为生产级工程 knowledge-mining-pipeline/:
错误码体系(KM-1xxx~5xxx)+ 多环境配置(dev/test/prod 深合并 + 环境变量覆盖)+
轮转日志(prod 输出 JSON 行)+ 指标采集(p50/p95/吞吐/成功率/重试数)+
令牌桶限流 + 熔断器 + 指数退避重试 + 隔离区 + 质量门禁 + SQLite 两级断点续传。
| 容错设施 | 本机验证方式 | 实测结果(2026-07-30) |
|---|---|---|
| 坏数据隔离 | 数据里埋 3 条脏行(JSON 截断/缺字段/非法 role) | 全部进 quarantine(KM-2002×1、KM-2003×2),主流程不中断;隔离率 11.1% < 门禁 20% |
| 重试退避 | dev 环境 mock LLM 注入 35% 故障(超时/429/500) | curate_kb 重试 5-6 次全部恢复,成功率 1.0;429 尊重 Retry-After |
| 断点续传 | --fail-at curate_kb 第 5 条处注入失败 → --resume | 前 3 步跳过 + 条目级缓存命中 5 条(LLM 不重复调用),补跑后结果与全量一致 |
| 指纹失效 | 改评测门禁配置后重跑 | 指纹变化旧断点自动作废,防止"数据换了还用旧产物" |
| 发布线门禁 | KM__eval__min_hit3=1.01 环境变量覆盖 | 正确失败 [KM-5003],退出码 2,自动打印处置建议 |
| 三环境 | dev / test / prod 各跑一遍 | 全通;prod 文件日志为 JSON 行(采集器友好);业务结果与教学版完全一致 |
复现:
cd knowledge-mining-pipeline && python3 -m venv venv && ./venv/bin/pip install -r requirements.txt && ./venv/bin/python run.py --env dev;
故障演练与错误码手册见工程内 README.md / TROUBLESHOOTING.md。
设计取舍:mock 与真实 LLM 共用同一套重试/限流/熔断代码路径,容错链路在无 key 环境也能被真实演练。