上一篇拆了 Query 改写的链路:四步+两道短路。这篇补核心对照——六条规则对应六种问法,一类问题开一剂药。

为什么先分类再改写

病症不同,药不同。不分类一刀切地「请改写用户问题」,模型容易改错方向:把该放行的独立问题改花,把对比句「归纳」掉一个实体。我的做法是把六规则和六类型全部写进 system prompt,模型每步输出 JSON {type, rewritten}——先判型,再按规则改。

真实 prompt 关键段(query-rewrite.service.ts,节选):

先判断 query 类型(从以下选一个):
- context_dependent: 上下文依赖型(需看历史才能理解指代)
- comparative:       对比型(A 和 B 哪个好)
- vague_reference:   模糊指代型(那个东西怎么用)
- multi_intent:      多意图型(同时问多个事)
- rhetorical:        反问型(难道不是...吗)
- standalone:        独立型(不依赖历史,表达清晰)

只输出 JSON,不要解释,不要 markdown:
{"type": "类型", "rewritten": "改写后的完整query"}

模型 deepseek-chat,temperature: 0.1(改写要确定性),response_format: json_object。

六组配对:症状 → 药

类型症状用药
context_dependent「那第二步呢」,缺指代缺主语指代消解 + 省略补全:从 history 把实体拉回来
comparative一句问两个东西的区别模糊词具体化,双实体必须同时保留——两个实体就是检索词,去掉任一个都伤召回
vague_reference「好用吗」「怎么弄」模糊词具体化:口语翻译成可打分的明确意图
multi_intent一句话问三件事多意图聚焦:拆子问题分开检索,整句塞给向量语义直接漂
rhetorical「这都查不出来?」反问转意图:情绪化反问转陈述式再去查,原话进检索几乎 0 命中
standalone句子成分齐全放行不改。改写成分齐全的独立问题,反而容易改坏

婚假实测(同一组数据贯穿两篇):

输入:「申请要怎么做?有没有步骤」+ 2 条婚假相关历史
输出:{"type": "context_dependent",
       "rewritten": "婚假申请的具体步骤和流程是什么"}
耗时 709ms / 565 token
BM25 命中:0 条 → 11 条(索引 docs=38, vocab=1936)

一条横六型的护栏:不编造实体

prompt 第 6 条规则单独拎出来说,因为它是事故源头:

6. 不要添加历史中不存在的信息,不要编造实体

rewritten 里只能出现 history 和原问里出现过的实体——用户没提婚假,就不能「猜」他要问婚假。编造实体 = 检索奔向错误文档,比不改更糟。

对比型还有一个专门的坑:模型爱把「A 和 B 的区别」「归纳」成只讲 A。所以规则里写死双实体必须保留,这条救过一次召回。

宁可保守:失败全回 null

改写失败、超时、JSON 不合法、改写结果和原话一样——一律 return null,上层降级用原 query,链路永不因改写挂掉。判型成 standalone 或直接跳过(无历史 / 启发式不含指代信号)都走同一个出口。

保守方向的选择题:改坏了捞错文档 vs 不改多捞几条垃圾——后者至少模型还能自己消化,前者直接把错误资料塞进上下文。所以宁可少改。

速查表(贴墙版)

上下文依赖 → 指代消解 + 省略补全
对比       → 具体化 + 保留双实体
模糊       → 模糊词具体化
多意图     → 拆分逐条检
反问       → 转陈述再查
独立       → 放行不改
公共护栏:不编造实体 + 异常回原话

改改写本质是「先承认用户的问法会带来什么麻烦,再决定怎么改」。无差别套一句「请优化这个问题」的 prompt,最容易跑偏。

本篇最早于 2026 年 10 月 15 日发布于小红书,此为含完整代码与数据的博客版。