系列:100 天系统学习 AI Agent 开发
当前阶段:RAG、知识库与工具边界
今日目标:查询改写可以把口语化问题转成更适合检索的关键词、子问题或多路查询。
“这个怎么部署”不是一个完整查询
“这个”指哪个项目?部署到本地、容器还是云平台?用户想要安装命令,还是正在排查报错?如果直接把这六个字拿去搜,检索系统只能猜,召回一堆看似相关的部署页面。
Query Rewrite 的意义不是把句子润色得更正式,而是把隐含条件显式化,让检索器得到可执行查询。最危险的副作用也在这里:改写器可能替用户补了一个并不存在的条件,悄悄改变原意。
四种不同的改写动作
| 规范化 | 同义词、缩写、错别字 | “py 环境”→“Python 运行环境” | 专有名词被误改 |
| 补上下文 | 代词依赖当前会话 | “这个”→“当前讨论的 LangGraph 项目” | 引用对象解析错 |
| 拆子问题 | 一个问题含多个信息需求 | 环境、命令、常见错误 | 子问题失去共同约束 |
| 多路查询 | 用户表达与文档措辞可能不同 | “恢复任务”“checkpoint resume” | 查询过多引入噪声和成本 |
改写前应先判断信息是否足够。缺少关键实体时,最好的 rewrite 是不改写,先追问。
把一句话拆成三条可检索查询
假设会话已明确对象为“某个 Python Agent 项目”,目标是“本地部署”。在这个前提下,可以得到:
如果对象和环境没有被确认,上述内容只能作为模板,不能直接当真实查询。结构化输出可以把“已知”和“未知”分开:
{
"original_query": "这个怎么部署",
"resolved_context": {
"target": null,
"environment": null,
"version": null
},
"missing_fields": ["target", "environment"],
"decision": "ask_user",
"rewritten_queries": []
}
信息齐全后才生成查询:
{
"original_query": "这个怎么部署",
"resolved_context": {
"target": "example-agent",
"environment": "local",
"version": "2.4"
},
"decision": "search",
"rewritten_queries": [
"example-agent 2.4 本地部署 环境依赖",
"example-agent 2.4 本地安装 启动命令",
"example-agent 2.4 本地启动 常见错误"
]
}
一条防止意图漂移的流程
#mermaid-svg-5ky4nVGfeob5qEDI{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5ky4nVGfeob5qEDI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5ky4nVGfeob5qEDI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5ky4nVGfeob5qEDI .error-icon{fill:#552222;}#mermaid-svg-5ky4nVGfeob5qEDI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5ky4nVGfeob5qEDI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5ky4nVGfeob5qEDI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5ky4nVGfeob5qEDI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5ky4nVGfeob5qEDI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5ky4nVGfeob5qEDI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5ky4nVGfeob5qEDI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5ky4nVGfeob5qEDI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5ky4nVGfeob5qEDI .marker.cross{stroke:#333333;}#mermaid-svg-5ky4nVGfeob5qEDI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5ky4nVGfeob5qEDI p{margin:0;}#mermaid-svg-5ky4nVGfeob5qEDI .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-5ky4nVGfeob5qEDI .cluster-label text{fill:#333;}#mermaid-svg-5ky4nVGfeob5qEDI .cluster-label span{color:#333;}#mermaid-svg-5ky4nVGfeob5qEDI .cluster-label span p{background-color:transparent;}#mermaid-svg-5ky4nVGfeob5qEDI .label text,#mermaid-svg-5ky4nVGfeob5qEDI span{fill:#333;color:#333;}#mermaid-svg-5ky4nVGfeob5qEDI .node rect,#mermaid-svg-5ky4nVGfeob5qEDI .node circle,#mermaid-svg-5ky4nVGfeob5qEDI .node ellipse,#mermaid-svg-5ky4nVGfeob5qEDI .node polygon,#mermaid-svg-5ky4nVGfeob5qEDI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5ky4nVGfeob5qEDI .rough-node .label text,#mermaid-svg-5ky4nVGfeob5qEDI .node .label text,#mermaid-svg-5ky4nVGfeob5qEDI .image-shape .label,#mermaid-svg-5ky4nVGfeob5qEDI .icon-shape .label{text-anchor:middle;}#mermaid-svg-5ky4nVGfeob5qEDI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5ky4nVGfeob5qEDI .rough-node .label,#mermaid-svg-5ky4nVGfeob5qEDI .node .label,#mermaid-svg-5ky4nVGfeob5qEDI .image-shape .label,#mermaid-svg-5ky4nVGfeob5qEDI .icon-shape .label{text-align:center;}#mermaid-svg-5ky4nVGfeob5qEDI .node.clickable{cursor:pointer;}#mermaid-svg-5ky4nVGfeob5qEDI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5ky4nVGfeob5qEDI .arrowheadPath{fill:#333333;}#mermaid-svg-5ky4nVGfeob5qEDI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5ky4nVGfeob5qEDI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5ky4nVGfeob5qEDI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5ky4nVGfeob5qEDI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5ky4nVGfeob5qEDI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5ky4nVGfeob5qEDI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5ky4nVGfeob5qEDI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5ky4nVGfeob5qEDI .cluster text{fill:#333;}#mermaid-svg-5ky4nVGfeob5qEDI .cluster span{color:#333;}#mermaid-svg-5ky4nVGfeob5qEDI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5ky4nVGfeob5qEDI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5ky4nVGfeob5qEDI rect.text{fill:none;stroke-width:0;}#mermaid-svg-5ky4nVGfeob5qEDI .icon-shape,#mermaid-svg-5ky4nVGfeob5qEDI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5ky4nVGfeob5qEDI .icon-shape p,#mermaid-svg-5ky4nVGfeob5qEDI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5ky4nVGfeob5qEDI .icon-shape .label rect,#mermaid-svg-5ky4nVGfeob5qEDI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5ky4nVGfeob5qEDI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5ky4nVGfeob5qEDI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5ky4nVGfeob5qEDI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
偏移
覆盖
原始问题 + 会话上下文
提取实体与约束
关键字段齐全?
向用户追问
生成单路或多路查询
保留原问题并执行检索
检查召回是否覆盖原意
回退原查询或再次追问
基于证据回答
原始问题必须进入 trace。只保存改写结果,失败时无法判断是用户表达含糊、上下文解析错,还是检索本身有问题。
怎么评测改写好不好
不要给改写句子打“更自然”的分,而要看它是否改善检索:
| 有明确对象的部署问题 | 待运行 | 待运行 | 待审查 | 比较 Top K |
| 缺少对象 | 不应搜索 | 不应搜索 | 应追问 | 测路由 |
| 指定旧版本 | 待运行 | 待运行 | 必须保留版本 | 测约束 |
| 专有名词拼写相近 | 待运行 | 待运行 | 不得擅自替换 | 测实体保护 |
有些短查询已经足够精确,例如错误码、函数名、订单号,不必为了使用技术而改写。多路查询也要限制数量并去重,否则延迟和噪声会同时上升。
我的阶段性判断
好的 Query Rewrite 更像一个谨慎的翻译员:把口语转成检索语言,但不替用户做主。它要敢于保留未知、敢于追问,也要把每次补充条件的来源说清。下一篇进入 RAG 评测后,我会把“意图保持”与“检索提升”拆成两项,而不是只看最终答案。
面试官会追问:Query Rewrite 会不会把用户意思改错?
会,所以改写不能只追求“更专业”。我会让改写器同时输出 original_intent、rewritten_queries、assumptions 和 must_keep_entities;订单号、产品型号、时间范围等实体必须原样保留。信息不足时生成澄清问题,而不是偷偷补一个假设。
评测时准备三类样本:口语省略、上下文指代、带精确实体的混合查询。除了检索 Recall,还要检查“意图保持率”。例如“它支持私有部署吗”若上一轮讨论的是 Qdrant,改写必须包含 Qdrant;若上下文里有两个产品,就应该追问。
面试回答可以总结为:Query Rewrite 是受约束的检索适配器,不是自由发挥的文案润色器。
今日检查清单
- 永远保留 original_query 和上下文来源
- 缺关键实体时先追问,不靠模型脑补
- 版本、权限、租户等硬约束不能被改写掉
- 用同一问题集比较改写前后检索命中
- 多路查询有数量、去重和成本限制

