12.Query改写

1.什么是Query改写?

前面聊了这么多检索技术,但有一个前提一直被忽略 ------ 用户得先提出一个好问题。 现实中用户是怎么提问的:

复制代码
用户输入:「那个接口怎么又挂了」真实意图:支付服务订单创建接口最近一次故障原因是什么

对检索器来说,"接口""挂了" 两个词的信息量几乎为零 ------Embedding 模型只能猜个大概方向,BM25 更是一头雾水。 很多线上项目最后发现,Embedding 没问题、向量库没问题、Rerank 没问题,真正的问题是用户不会提问。垃圾 query 进去,后续链路再强也白搭。

解决方案是 Query Rewrite(查询改写) ------ Query 改写,就是在搜索知识库前,先把用户原来的问题改成"更容易搜到资料"的问题。 在检索之前,先让 LLM 把用户的模糊问题改写成适合检索的明确查询。可以结合历史对话做指代消解("那个接口"→"支付服务订单创建接口"),也可以补充业务上下文。改写后的问题再送入后续的召回和精排流程。

Query Rewrite 在实践中往往是收益最高的单点优化之一,大部分 RAG 框架(LangChain、LlamaIndex 等)都内置了这一步。

2.怎么进行Query改写?有哪些方案?

  1. 首先不是所有的问题都需要query改写,只有模糊追问、口语表达、依赖上下文的问题才需要改写。比方说用户提的问题就像我们以前用搜索引擎查Bug的时间,错误码,错误信息,这种有关键信息的这种提问就是一个好的提问,这种就不需要改写问题,可以直接去搜。

  2. 如果用户的提问中带有明确的代词,那个", "这个", "它", "上次", "这次", "又", "那",这种很明显是需要通过聊天记录去找找这次代词是指的什么,那这种肯定是需要把历史聊天记录,当前用户提问交给模型进行query改写

  3. 如果用户提问的问题很短,5个字以下这种一般也是需要query改写的

python 复制代码
import re

def need_llm_judge(query: str) -> bool:
    # 1. 有错误码、订单号、接口路径:保留原问题,直接搜
    if re.search(r"ERR-\d+|ORD-\w+|/api/[\w/-]+", query):
        return False

    # 2. 有明显指代词:交给 LLM 判断
    refer_words = ["那个", "这个", "它", "上次", "这次", "又", "那"]
    if any(word in query for word in refer_words):
        return True

    # 3. 问题太短,通常信息不足:交给 LLM 判断
    if len(query) < 10:
        return True

    # 4. 其他完整问题:先直接搜
    return False
   
python 复制代码
if need_llm_judge(query):
    plan = plan_query(query, chat_history)  # 交给 LLM 判断
else:
    plan = QueryPlan(
        action="direct_search",
        rewritten_query=None,
        reason="规则判断:问题完整或包含精确关键词"
    )

下面是完整例子

python 复制代码
import os
import re
from pathlib import Path
from typing import Literal, Optional

from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field


# 无论你从哪个目录执行命令,都读取"本文件同级目录"的 .env。
load_dotenv(Path(__file__).with_name(".env"))


class QueryPlan(BaseModel):
   """LLM 必须返回的固定格式。"""

   action: Literal["direct_search", "rewrite", "clarify"] = Field(
       description=(
           "direct_search:问题完整,直接检索;"
           "rewrite:问题依赖历史,需要改写;"
           "clarify:信息不足,需要追问用户"
       )
   )
   rewritten_query: Optional[str] = Field(
       default=None,
       description="仅 action=rewrite 时填写;必须是可以单独检索的完整问题。",
   )
   reason: str = Field(description="简短说明为什么作出这个判断。")


def need_llm_judge(query: str) -> bool:
   """用便宜的规则筛选:只有不确定的情况才调用 LLM。"""

   # 错误码、订单号、接口路径等通常是精确检索词,保留原样直接搜索。
   exact_token_pattern = r"ERR-\d+|ORD-\w+|/api/[\w/-]+"
   if re.search(exact_token_pattern, query, flags=re.IGNORECASE):
       return False

   # 这些词常常说明当前问题依赖上一轮聊天,需要 LLM 进一步判断。
   refer_words = ["那个", "这个", "它", "上次", "这次", "又", "那"]
   if any(word in query for word in refer_words):
       return True

   # 很短的问题往往缺少对象,例如"怎么处理?"。
   return len(query) < 10


def build_query_router():
   """用 init_chat_model 创建模型,再用 create_agent 创建查询路由 Agent。"""

   if not os.getenv("CLOSEAI_API_KEY"):
       raise RuntimeError(
           "没有读取到 CLOSEAI_API_KEY。请在 .env 中填写后再运行。"
       )
       
   model = init_chat_model(
       model="gpt-5.4-mini",
       model_provider="openai",
       api_key=os.getenv("CLOSEAI_API_KEY"),
       base_url=os.getenv("CLOSEAI_BASE_URL"),
       temperature=0,  # 判断任务希望稳定,不希望每次随机变化。
   )

   # response_format=QueryPlan:要求 Agent 的最终结果必须符合 QueryPlan。
   # 因此 invoke() 的结果中会有 result["structured_response"]。
   return create_agent(
       model=model,
       tools=[],
       system_prompt="""
       你是企业知识库的检索查询路由器,不负责回答用户问题。

       你只能在以下三个 action 中选择一个:
       1. direct_search:当前问题脱离历史也完整清晰,可以直接检索。
       2. rewrite:当前问题依赖历史;请写出可独立检索的 rewritten_query。
       3. clarify:历史也无法确定用户的具体对象;不要猜测,要求用户补充。

       重要规则:
       - 不能编造历史里没有的业务名称、接口名、时间或事实。
       - 错误码、订单号、接口路径、产品名等精确词必须原样保留。
       - 不要回答问题,只输出 QueryPlan。
       """.strip(),
       response_format=QueryPlan,
   )


def plan_query(
   query: str,
   chat_history: str,
   query_router,
) -> QueryPlan:
   """调用 LLM,判断当前问题应该怎么进入检索流程。"""

   user_prompt = f"""
               最近对话历史:
               {chat_history or "(没有历史对话)"}

               当前用户问题:
               {query}
               """
   result = query_router.invoke({
       "messages": [{"role": "user", "content": user_prompt}]
   })

   # create_agent 的结构化输出放在 structured_response 这个 key 中。
   return result["structured_response"]


def decide_query_action(
   query: str,
   chat_history: str,
   query_router,
) -> QueryPlan:
   """整个入口:明显情况走规则;模糊情况才交给 LLM。"""

   if not need_llm_judge(query):
       return QueryPlan(
           action="direct_search",
           rewritten_query=None,
           reason="规则判断:问题完整,或包含错误码、订单号、接口路径等精确关键词。",
       )

   return plan_query(query, chat_history, query_router)


def show_case(title: str, query: str, history: str, query_router) -> None:
   """打印一组测试案例的输入和输出。"""

   plan = decide_query_action(query, history, query_router)
   print(f"\n{'=' * 60}\n案例:{title}")
   print(f"历史:{history or '(没有)'}")
   print(f"当前问题:{query}")
   print(f"判断结果:{plan.model_dump_json(indent=2)}")


def main() -> None:
   # 创建一个处理query重写的agent
   query_router = build_query_router()

   # 案例 1:有错误码,规则直接决定保留原问题检索,不调用 LLM。
   show_case(
       title="精确错误码:直接检索",
       query="ERR-1024 在订单创建时是什么意思?",
       history="",
       query_router=query_router,
   )

   # 案例 2:有"那个接口",LLM 从历史中补全对象并生成检索语句。
   show_case(
       title="依赖历史:改写",
       query="那个接口又挂了?",
       history="用户:支付服务的订单创建接口今天连续超时。",
       query_router=query_router,
   )

   # 案例 3:没有历史,LLM 不应猜测"这个"是什么,而应选择 clarify。
   show_case(
       title="信息不足:追问",
       query="这个怎么处理?",
       history="",
       query_router=query_router,
   )


if __name__ == "__main__":
   main()

其实普通的项目的刚开始做到这一步已经可以了,对于需要query改写的问题都是将原问题和改写后的问题一块去检索,这个时间由于两路检索,所以需要将检索的两路结果进行Rerank。

Rerank

相关推荐
洛阳泰山1 小时前
别再为做 AI 去学 Python 了!MaxKB4j:纯 Java 造的企业级 RAG + 工作流引擎,开箱即用
人工智能·开源·agent
武子康2 小时前
Video VAE 不是末端编解码器:5 维-潜网格-主模型 Token 的“表示合同“
人工智能·llm·agent
DigitalOcean4 小时前
Kimi K3 现已上线 DigitalOcean 无服务器推理
llm·agent·ai编程
tkevinjd4 小时前
MiniCode 项目详解7:Memory 记忆系统
大数据·python·搜索引擎·llm·agent
玉鸯4 小时前
旧概念还是新范式?Agent 框架集体"图化"背后的矛盾与必然
后端·llm·agent
倾颜5 小时前
断线之后,不要重跑 AI:在 POST + NDJSON 中实现可恢复 Agent 流
前端·后端·agent
新知图书6 小时前
3.2 技能的格式和工作机制(扣子编程)
人工智能·agent·ai agent·智能体
明明如月学长6 小时前
一个主持人管 5 档节目,他用 AI 把 1 小时的活压到 5 分钟
agent