01 / 背景与目标
企业客服需要从自有资料中找到答案,同时处理多轮追问、租户数据隔离,以及无法回答时的人工接管。项目覆盖资料接入、混合检索、精排、证据约束回答、转人工与会话持久化,每次优化都在固定测试集上对照记录。
02 / 核心实现
BM25 + 向量混合召回
OpenSearch 同时维护 BM25 与 512 维向量索引,在搜索管道中用 RRF 融合两路结果,并按租户字段过滤。
CPU 上的 INT8 精排
bge-reranker-v2-m3 导出为 ONNX INT8,对 8 个候选精排,向回答模型提供 5 个证据块;有界信号量限制并发推理,超出的请求排队。
按复杂度分级路由
LangGraph 编排分析、检索与回答节点。Router V2 让意图明确的单轮问题走确定性快速通道,其余单轮问题走最小结构化路由,带历史的多轮对话保留完整上下文分析,异常时自动回退。
多租户并发一致性
会话按租户与会话 ID 隔离并带版本写入,冲突返回 409;知识变更会递增缓存版本,写缓存时在同一 SQLite 事务内校验,防止旧回答回流;知识源操作用文件锁跨进程互斥。
追问与转人工闭环
同一问题首次未命中时追问,再次未命中转人工,计数随会话持久化。用户也可主动提交人工处理:申请与最近 40 条会话写入租户数据库后经 SMTP 通知,失败最多重试 3 次,重启后继续补发。
03 / 技术取舍
为什么用 RRF 融合
BM25 擅长产品名与专有术语,向量召回覆盖口语化改写。两路分数量纲不同,RRF 只依赖排名,无需额外归一化。
为什么是 8 个候选、并发上限 4
精排是 CPU 上最重的一步。8 个候选在测试集上保持 Hit@5 91.11%,重排 P95 约 1.08 s;10 个请求的突发压测中,并发上限 1 / 2 / 4 的吞吐为 5.70 / 7.39 / 8.30 次/秒,因此默认取 4。
为什么先规则、后模型
多数单轮问题意图明确,确定性规则即可完成路由,省下一次模型调用;复杂多轮仍交给上下文分析器处理指代与改写,并保留一键回滚开关。
为什么先校验证据再输出
回答先确认证据状态与支持资料编号,再开放正文流式输出,避免先输出没有依据的内容。
为什么允许部分回答
资料只覆盖问题的一部分时,先回答有依据的部分,再说明需要人工确认的内容,而不是整题转人工;仍禁止补充资料以外的金额、时限或处理方式。29 条用例各跑 3 遍,应答题误转人工从 3/75 降到 0/75。
为什么精排后补齐相邻块
答案所需的说明可能落在命中块前后。精排后为命中块补齐前后各一块,限定同租户、同来源、同内容版本、同章节,合并后不超过 1400 字;扩展失败时回退原结果。
04 / 处理流程
- 分析问题并选择路由
- BM25 与向量双路召回
- RRF 融合、CrossEncoder 精排、补齐相邻块
- 基于 5 个证据块生成完整或部分回答
- 资料不足先追问,再次未命中转人工
- NDJSON 流式返回并持久化会话
05 / 验证与数据
| 项目 | 结果 |
|---|---|
| 召回质量 | Hit@5 91.11%,MRR 0.8063(重复 3 轮) |
| 重排耗时 | P50 1003 ms,P95 1085 ms(ONNX INT8,8 个候选) |
| 完整答案 P95(并发 1 / 5 / 10) | 7.12 / 9.37 / 11.61 s;Router V2 初版为 14.30 / 14.69 / 18.63 s |
| 回答质量与接口 | 三档并发质量通过率均为 80.95%,接口错误率 0% |
| Reranker 并发上限 1 / 2 / 4 | 吞吐 5.70 / 7.39 / 8.30 次/秒,重排完成 P95 1.67 / 1.34 / 1.19 s |
| 转人工改前 / 改后(29 条用例 × 3 遍) | 应答题误转人工 3/75 → 0/75;部分回答用例 1/6 → 6/6;先追问再转人工 0/9 → 7/9;接口错误均为 0 |
| 离线回归 | 561 项通过,5 项真实模型测试跳过 |
召回与延迟数据来自仓库内的 63 条客服测试集,在 Ryzen 7 7840H 本机测得;转人工对照为 2026-09-28 的真实模型评测,改前改后使用同一知识库快照与模型配置、单并发。评分脚本按路由与关键词判定,不等于事实正确率,仍有失败用例;完整答案延迟主要来自模型生成,仍在优化。
06 / 当前边界
数据来自离线测试集与本机评测,不等同于线上效果;检索与回答质量会随知识库、模型和配置变化。