一、问题背景
know-engine 在执行 RAG 之前,会先经过 KnowEngineQueryTransformer 对用户 Query 做 LLM 改写: - 简洁化、抽象化、错别字纠正、车型标准化、上下文补全; - 改写后的 Query 会作为唯一入参,分发到所有 ContentRetriever: - ES 向量检索(语义召回) - ES 全文检索(关键词召回) - ES 混合检索 - SQL 结构化检索(NL2SQL) - 多路召回结果再经 RRF + Reranker 输出 Top-K。
二、问题表现
LLM 给出干净的改写结果后,代码额外拼接了"用户Id + 当前时间"再分发出去,主要是为了让SQL生成的时候更加准确。比如查询用户信息或者时间相关信息用得上。
String
// ❌ 把无关上下文硬塞进 query
String
由此引发的现象: | 现象 | 描述 | | --- | --- | | 向量召回偏移 | embedding 包含 "用户Id是: 123321"、 | | 全文检索打分异常 | ES 的 BM25 把"用户/时间"也作为查询词命中文档中偶发出现的"用户"等字,召回大量无关片段 |
根因分析
一条 Query 在 know-engine 里实际承担两类职责: 1. 语义检索类(向量 / 全文 / 混合)——只关心"用户在问什么",无关信息越少越好; 2. NL2SQL 类(结构化检索)——除了问题本身,还需要"我是谁、现在几点"才能生成正确的 SQL(例如 WHERE user_id = 123321 AND expire_at > NOW())。 之前的实现把"NL2SQL 才需要的业务上下文"硬编码到了 QueryTransformer 的输出里,导致这份"被污染"的 Query 也流向了向量/全文检索 → embedding 与 BM25 全部跑偏 → 召回不准。
解决方案
按"谁需要谁拼接"的原则,做职责下沉:让 QueryTransformer 的输出回归纯净的语义结果,把业务上下文拼接收敛到只有 NL2SQL 才会经过的 KnowEngineSqlDatabaseContentRetriever。
1. QueryTransformer 输出回归纯净
// ✅ 改写结果直接作为下游 query,不再附加任何无关上下文
String
log
Query
带来的好处是,向量/全文检索拿到的是 LLM 已经规整过的纯净问题,召回质量回归预期;
2. 业务上下文下沉到 SQL 检索器
只有 SQL 检索器(走 NL2SQL)需要"用户Id / 当前时间"才能生成正确的 SQL,因此仅在该 Retriever 内部、调用底层 SqlDatabaseContentRetriever 之前做局部拼接:
public
query
results
log
}
要点: - query 是方法参数(局部变量),重赋值不会污染外层 KnowEngineQueryTransformer 返回的 Query 对象,更不会传到其它 Retriever; - 异常分支中拿到的仍是上层传入的纯净 query,降级走知识库检索时同样不受污染。