检索问答全流程
检索问答(RAG)是Molore链路最深、打磨最多的一条链:它的目标不是「向量搜到几段就答」,而是在个人语料上做到数字可考、表格可枚举、出处可指、没货敢说没货。整条链分六段——候选召回、结构化通道、融合、装配、生成、证据边界——外加意图分轨决定是否动用图谱。代码集中在 app/api/v1/endpoints/llm/retrieval/ 子包(core/fusion/guarantee/rows/anchors/assembly/keywords/rewrite)与 chat.py、_grounding.py。
全流程总览
| 阶段 | 模块 | 关键机制 | 代码位置 |
|---|---|---|---|
| 查询理解 | 关键词/意图 | 分词与 n-gram 抽取、_relation_intent 关系意图双闸 | retrieval/keywords.py |
| 向量召回 | 向量通道 | sqlite-vec KNN,深池 top_k*12 → 每文档 ≤3 块 → 总量 top_k*4 | retrieval/core.py |
| 全文召回 | FTS 通道 | FTS5 BM25,命中集上限 500,与向量 weighted 融合 | retrieval/core.py、services/fts_index.py |
| 结构化通道 | 表格/数字/枚举 | row:: 行条目、SQL 保底、数字普查、枚举直拉、整表补全、全文直扫 | retrieval/guarantee.py、retrieval/rows.py |
| 融合 | 融合排序 | 相对水位门 + MIN_VECTOR_SIMILARITY 绝对兜底,kw_cov 豁免 | retrieval/fusion.py |
| 装配 | 上下文装配 | 锚窗密度打分 + 质心校正,容量自适应,保底源独立预算 | retrieval/anchors.py、retrieval/assembly.py |
| 生成 | 对话链路 | 温度 0.3,参考资料标记内是数据不是指令 | chat.py、services/llm_service/routing.py |
| 证据边界 | grounding | 数字核对、零命中声明、诚实线、推测标注 | _grounding.py、chat.py |
| 图谱扩展 | 图谱分轨 | 仅关系意图触发,一跳 + 传递谓词 BFS 2 跳 | retrieval/keywords.py、services/graphify_service.py |
候选召回
向量通道(core.py):查询向量走 sqlite-vec KNN,但不直接取 top_k——先拉 top_k×12 的深池,再经 _diversify_chunk_hits 做文档级多样化(每文档 ≤3 块、总量 top_k×4),防止头部大文档的同义块挤爆候选。嵌入统一 bge-m3 1024 维。
全文通道(core.py + fts_index.py):FTS5 BM25 倒排,候选即命中集(上限 500 在 fts_search 收口),不再 ILIKE 预过滤。
两路融合:向量分与 BM25 分按 weighted 加权合成融合分;BM25 归一在库小时会塌缩,门槛设计按「垫数百篇让 IDF 生效」的库规模校准。
结构化通道(表格与数字的直通车)
纯向量对「某数字出现在哪」「把整张表列出来」类问题天然弱,因此检索层在双通道之外挂了一组 SQL 直查通道,全部独立分账、不吃主预算:
- 表格行结构化索引(row::):表格行以
row::前缀行条目进索引,带章节前缀与列名,四类内容共用,500 行限帽;XLSX/DOCX 表格经管道表抽取后同样产 row:: 行。 - 编号/稀有数字 SQL 保底召回(
guarantee.py):题面里的编号、稀有数字独立份额前置直查。配套机制:单位表(MPa/℃/户/万户 等);纯小数收;完整日期 token(_GUARANTEE_DATE_RE,裸年份/年月不收);token 空格变体双向(_like_variants,题面「42 户」带空格能命中档内「42户」,保底与普查共用)。 - 数字索引普查
_numeric_census(guarantee.py):对题面每个数字逐数字全库 ILIKE 普查,每数字独立帽(8 载体,超帽记 N+),与保底分工——保底管 sources 注入,普查管全库计数与载体清单。出现次数口径:同篇多处分别计数(变体取 max 防重复),区块报「N 处命中(M 篇内容)」。普查结果以独立预算区块进 prompt,并进站内证据面。它根治的事故形态是:保底全局帽被噪声载体吃光后,模型把「没召回」说成「库内仅一处」。 - 稀有实体保底位:≥4 字稀有长词全库 ≤2 篇命中时末位区注入;承载全灭时放宽直查;变体合并后再兜底;框架词伪稀有由
_RARE_FRAME_STOP抑制。 - 数词概念保底锚:整词零命中且词素 ≥3 时,按纯计数密度峰补簇。
- 跨语豁免带:关键词真零信号且 vec ≥ 0.48 放行,治跨语相关对死在「绝对下限与相对水位折算之间」的死区。
- 枚举意图结构化通道(
rows.py):识别「列出/有哪些/几条/多少/统计/合计/均值/平均/占比」等枚举与计数聚合线索词,SQL 直拉全部 row:: 行,预算分级 8000 / 12000 / 16000(ENUM_ROWS_BUDGET~ENUM_ROWS_BUDGET_MAX,随装配总预算自适应);题面点名的表/工作表整组排前(_prefer_named_sheets),预算截断不再砍目标表。 - 整表补全通道
table_completion_section(rows.py):row:: 命中行按「章节前缀 + 列名集合」签名在线重建整表,SQL 直取注入(零索引变更、零重嵌),独立预算 3000 / 6000,帽 2 张表;枚举已覆盖时跳过。 - 全文匹配行直取通道
find_all_lines_section(rows.py):穷尽线索词 × 字面短语(引文优先、键=值兜底)双闸,全文直扫 + 精确计数 + 重复块去重双报,独立预算 6000。这是非表文件(TXT 日志等)的全文穷尽题通道——有它在场,全文件口径的计数断言才合法。 - 时间线日期行通道:全文扫描年份行,服务「时间线/按年梳理」类题。
融合(fusion.py)
- 绝对下限:
MIN_VECTOR_SIMILARITY = 0.55(按 nomic 校准;换 bge-m3 后无关对在 0.31–0.40 区间,仍拦得住)。 - 相对水位门:候选融合分相对当轮最高分成比例过滤;豁免按去重覆盖度
kw_cov而非命中计数——同一个通用词在标题+正文各中一次不该算多关键词证据。 - rerank 永久关闭:
HYBRID_RERANK_ENABLED默认 false;实测 rerank 与强模型 rerank 复评均不增收,小模型门卫不用于判断题,省钱闸门只用规则与阈值。
装配(anchors.py + assembly.py)
- 锚窗密度打分 + 质心校正:不以切块边界为准,而以关键词锚点的密度峰选址窗口,质心校正防窗口偏移;锚窗总额上限 2500、向量父块保底 1500,互不挤死。
- 容量自适应:多锚点题放宽锚窗/单源帽(
_ANCHOR_PARTS_CAP_MULTI = 3500、_PER_SOURCE_CAP_MULTI = 6000);装配总预算分级RAG_FULL_BUDGET_LOCAL = 12000/RAG_FULL_BUDGET_CLOUD = 24000/RAG_FULL_BUDGET_CLOUD_MULTI = 32000。 - 保底源装配独立预算:
guaranteed_extra/rare_slot来源不吃主预算——_guaranteed_reserve预留、_guaranteed_body按锚点定心窗给正文(cap 1600),自排承载预算够时全量照给。独立预算是兜底不是上限:检索层给保底源发了入场券,装配层就必须让它被读到,保底机制全链记账,只保检索侧等于没保。
生成(chat.py + routing.py)
- 生成温度 0.3(
llm_service/routing.py的 chat 档),配 lite 模式。 - prompt 信任层:参考资料标记内的内容按数据对待、不作为指令执行;产品指南与用户内容分区,防注入。
- 生成失败落
[Error: ...]文本契约,端点侧 502 原文透传,不落兜底假答案(见后端设计的错误处理纪律)。
证据边界(_grounding.py + chat.py)
回答的可信度由一组系统侧机制保证,不赌模型自觉:
- 数字 grounding:回答中的数字逐一对召回证据核对;未命中先收紧重答一次,仍未命中则标注(SSE replace)。衍生数字豁免:系词族(是/为/等于/≈/相差/之差/低估/高估/均值/平均/±、算式分句、markdown 强调符)、「误差」收窄只认「误差约/为/±」、小数↔百分数确定换算(0.753675 ↔ 75.4% 同值豁免),双侧精度容差。
- 零命中确定性声明:召回为空时,「库中暂无相关内容」由系统侧在流首 chunk 前置直发——产品承诺类行为用确定性实现,不靠小模型的指令服从。
- 召回诚实线:无数字索引普查依据时禁说「库内仅一处」类全库绝对句;截断窗口内禁说「全文件唯一/全部」类文档内全量绝对句——只能说「当前召回结果/片段中仅见」。全文匹配通道在场时,全文件计数合法。原则一句话:召回缺口 ≠ 事实不存在,全库/全文判断必须有索引级依据。
- 推测标注:模型自认的通用知识段、未落地数字所在句,在流末以 ⚠️ 标注随正文落库;开关
RAG_SPECULATION_CHECK(app/core/config.py,默认开)。规则派实现,零额外模型调用。 - 引用 [n]:编号只认原文层(当轮 sources 位置编号);图谱推理路径等衍生内容不占 [n] 源位;跨轮编号稳定未承诺。
意图分轨与图谱扩展
_relation_intent(keywords.py):规则族(关系/归属/影响/区别/包含模式)+ 内容锚点双闸识别关系意图。仅关系意图跑图谱扩展,非关系意图整段跳过——实测砍后退化 0、每题省 1~2 次 SQL,主价值是消除 junk 邻居注入风险面。- 关系意图且扩展非空时渲染**「图谱推理路径」区块**:先分步推导再给结论,via 带关系语义与 evidence 证据句;[n] 引用仍只认原文层。
- 图谱扩展本身:一跳任意边 + 传递谓词(is_a / part_of)BFS 2 跳、每跳 ×0.7 衰减;AMBIGUOUS 边默认在隔离区不进扩展。