RAG 查询改写完全指南:从一次"用户随口一问就检索跑偏、多轮对话直接失忆"看懂 Query Rewriting

2024 年我做一个 RAG 知识库问答检索这一步我自以为已经做得不错了文档分好了块检索也加了重排序。可检索的入口第一版我做得很省事用户在对话框里问什么我就把那句话原样拿去做向量检索。本地我自己测了测真不错我问数据库连接池怎么配置它准准地检索到讲连接池配置的那一段。我心里很踏实RAG 检索嘛用户问什么我就拿这句话去检索不就行了。可等这个问答真正上线面对真实用户五花八门的问法和多轮对话一串问题冒了出来。第一种最先把我打懵用户不会像我这样端端正正地提问他们用口语用很随意的说法那个连接的东西咋整啊我拿这句去检索捞回来的全是不相关的。第二种最致命多轮对话里用户第一轮问 React 怎么做状态管理第二轮接着问那它和 Vue 比呢这个它指的是 React可我直接拿那它和 Vue 比呢去检索检索系统根本不知道它是什么这一轮直接失忆。第三种用户一句话里塞了好几个问题这个功能怎么配置报错了怎么排查性能还能怎么优化我把整句拿去检索结果哪一段都命中得不好。第四种最隐蔽用户在问一个问题而文档在陈述一个答案两者措辞天差地别向量距离被生生拉远。我盯着这一连串问题想了很久才彻底想明白第一版错在我以为用户问什么我就拿什么去检索。这句话默认用户敲进对话框的那句话就是一个好的检索 query。可它不是。用户在对话框里敲下的那句话是说给人听的不是为检索优化的它口语它含指代它夹着多个问题它还和文档的措辞严重错位。真正可靠的 RAG 检索在拿 query 去检索这个动作之前必须先有一个把用户的话改写成好 query 的阶段。本文从头梳理为什么直接用原始提问是错的查询规范化怎么做多轮指代怎么消解复合问题怎么拆查询扩展与 HyDE 是什么以及改写延迟改写质量失败兜底这些把查询环节真正做对要避开的坑。

2024 年我做一个 RAG 知识库问答,检索这一步我自以为已经做得不错了——文档分好了块,检索也加了重排序。可检索的入口,第一版我做得很省事:用户在对话框里问什么,我就把那句话原样拿去做向量检索。本地我自己测了测——真不错:我问"数据库连接池怎么配置",它准准地检索到讲连接池配置的那一段。我心里很踏实:"RAG 检索嘛,用户问什么,我就拿这句话去检索,不就行了。"可等这个问答真正上线、面对真实用户五花八门的问法和多轮对话,一串问题冒了出来。第一种最先把我打懵:用户不会像我这样"端端正正地提问",他们用口语、用很随意的说法——"那个连接的东西咋整啊",我拿这句去检索,捞回来的全是不相关的。第二种最致命:多轮对话里,用户第一轮问"React 怎么做状态管理",第二轮接着问"那它和 Vue 比呢"——这个""指的是 React,可我直接拿"那它和 Vue 比呢"去检索,检索系统根本不知道"它"是什么,这一轮直接失忆。第三种:用户一句话里塞了好几个问题——"这个功能怎么配置、报错了怎么排查、性能还能怎么优化",我把整句拿去检索,结果哪一段都命中得不好。第四种最隐蔽:用户在"问一个问题",而文档在"陈述一个答案",两者措辞天差地别,向量距离被生生拉远。我盯着这一连串问题想了很久才彻底想明白,第一版错在一个根本的认知上:我以为"用户问什么,我就拿什么去检索"。这句话默认"用户敲进对话框的那句话,就是一个好的检索 query"。可它不是用户在对话框里敲下的那句话,是说给"人"听的,不是为"检索"优化的——它口语、它含指代、它夹着多个问题、它还和文档的措辞严重错位。真正可靠的 RAG 检索,在"拿 query 去检索"这个动作之前,必须先有一个"把用户的话改写成好 query"的阶段。真正做好 RAG 的查询环节,核心不是"把原始提问直接送进检索",而是理解原始提问不是好 query、先做查询规范化和指代消解、把复合问题拆开、必要时用查询扩展和 HyDE 补上"提问"与"答案"之间的鸿沟。这篇文章就把 RAG 的查询改写梳理一遍:为什么"直接用原始提问"是错的、查询规范化怎么做、多轮指代怎么消解、复合问题怎么拆、查询扩展与 HyDE 是什么,以及改写延迟、改写质量、失败兜底这些把查询环节真正做对要避开的坑。

问题背景

先把那串问题的现象和我的误判讲清楚,后面所有的设计都是冲着纠正这个误判去的。

现象:把用户的原始提问直接拿去检索之后,上线冒出一串问题:用户用口语、随意的说法提问,检索全跑偏;多轮对话里,第二轮问题带着"它/这个"等指代,检索系统完全无法理解,这一轮直接失忆;用户一句话塞多个问题,整句去检索哪段都命中不好;用户在"提问"、文档在"陈述",措辞错位拉远了向量距离

我当时的错误认知:"用户问什么,我就拿这句话原样去检索就行了。"

真相:用户在对话框里敲下的那句话,和"一个适合检索的 query",是两样东西。用户的话是说给人听的:它可以口语、可以省略、可以含糊,因为人能结合语境去理解。可向量检索不会理解语境——它只是把你给的那串文字编码成向量,去找距离最近的文档。所以,你给它的 query 越规范、越完整、越贴近文档的表述,它就检索得越准。这中间缺的那一环,就是查询改写(Query Rewriting / Query Transformation):在检索发生之前,先用一个(通常是轻量的)LLM 调用,把用户的原话加工成一个或多个好 query——口语的给它规范化,含指代的给它补全,夹多问的给它拆开,和文档错位的给它做扩展或 HyDE。没有这一环,你的检索系统再强,也只是在用一个糟糕的 query 努力工作——它的上限,从一开始就被那句没加工的原话锁死了。

要把 RAG 的查询改写做对,需要几块认知:

  • 为什么"直接用原始提问"是错的——用户的话不是好 query;
  • 查询规范化——把口语提问翻译成检索友好的语句;
  • 指代消解——把多轮对话里的"它/这个"补全成独立问题;
  • 复合问题拆解——把一句多问拆成多个子查询;
  • 查询扩展、HyDE、改写兜底这些工程坑怎么处理。

一、为什么"用户问什么就检索什么"是错的

先把这件最根本的事钉死:向量检索是一个没有"语境"的匹配器。你给它一句话,它就把这句话变成一个向量,去文档堆里找距离最近的几个——它不知道这是第几轮对话,不知道"它"指的是上文哪个词,不知道这句话其实在问三件事。人在读用户提问时,会自动补全、自动联想、自动拆分,所以你觉得"这句话问得很清楚";但检索器读到的,只是一串孤零零的字符。你把一句"对人很清楚、对检索很模糊"的话直接喂给它,它检索不准,不是它无能,而是你交给它的原料,本来就不合格。

下面这段代码,就是我那个"上线就跑偏"的第一版检索入口:

# 反面教材:用户输入什么,就原样拿去做向量检索
def answer(user_input, collection):
    # 直接把用户对话框里的原话,当成检索 query
    q_vec = embed(user_input)
    hits = collection.query(query_embeddings=[q_vec], n_results=5)
    context = "\n".join(hits["documents"][0])
    return llm_answer(user_input, context)
    # 破绽一:用户的口语化提问,和文档措辞差异大,检索易跑偏。
    # 破绽二:多轮对话里的"它/这个",检索系统完全无法理解。
    # 破绽三:一句话塞多个问题,没有任何一段能同时命中。

这段代码在我自己本地测试时表现不错,因为我是"懂行的人在提问"——我会下意识地把问题问得完整、规范、单一,恰好就是检索器喜欢的样子。它的问题不在代码本身,而在一个被忽略的前提:它默认"用户都会像我一样规范地提问"。可真实用户只会按"和人聊天"的习惯说话。于是那串问题就有了解释:口语提问检索跑偏,是因为"咋整啊"这种词在文档里根本不存在,向量匹配无从下手;多轮对话失忆,是因为"它"是一个空指针,检索器无法解引用;复合问题命中差,是因为没有任何一段文档会同时讲全三件事。问题的根子清楚了:做好 RAG 查询环节的工程量,全在"承认用户的原话只是原料、必须先加工成好 query"之后——你不加工,检索器就只能拿着一句模糊的话硬找。先从最基础的加工——查询规范化——说起。

二、查询规范化:把口语提问翻译成好 query

查询改写里最基础的一步,是查询规范化:把用户那句口语的、随意的、可能还带情绪的提问,改写成一句规范、清晰、贴近文档表述的检索语句。做这件事,靠的是一次轻量的 LLM 调用——给它一个明确的改写指令:

# 查询规范化:用一个轻量 LLM 调用,把口语提问改写成检索友好的 query
REWRITE_PROMPT = """把下面这句用户的口语化提问,改写成一句
适合做知识库检索的、规范清晰的查询。只输出改写后的查询本身。

用户提问:{raw}
改写查询:"""


def normalize_query(raw_query):
    """把'那个连接的东西咋整啊'这类口语,改写成规范 query。"""
    prompt = REWRITE_PROMPT.format(raw=raw_query)
    rewritten = llm_complete(prompt, max_tokens=64).strip()
    return rewritten or raw_query        # 改写结果为空,就退回原句

经过这一步,"那个连接的东西咋整啊"就可能被改写成"数据库连接池的配置方法"——后者用词规范、贴近文档,向量检索一下子就找得准了。这里要把握一个分寸:改写是为了让检索更准,不能改变用户的原意。所以改写指令里要明确约束"只规范化表述,不要自行增删问题的核心诉求"。这里的认知要点是:查询规范化的本质,是一次"翻译"——把"用户的语言"翻译成"文档的语言"。用户的语言是口语的、生活化的;文档的语言是书面的、术语化的。这两种语言之间存在一道天然的鸿沟,而向量检索本身跨不过这道沟。规范化,就是在检索之前,先派一个翻译把话译过去。它不改变说话人想问什么,只改变这句话的"表述形式"。规范化解决了"一句话本身"的问题,但多轮对话还有一个更难的问题——"这句话依赖上文"。

三、指代消解:把"它/这个"补全成独立问题

开头第二个问题——"多轮对话直接失忆"——是查询改写里最关键、也最容易被漏掉的一环。多轮对话里,用户会大量使用指代:"它"、"这个"、"那种方式"、"刚才说的"。这些词对人毫无障碍,因为人记得上文;可对检索器是致命的,因为它只看到当前这一句。解法是指代消解:把对话历史 + 当前问题一起交给 LLM,让它改写出一个"不依赖上文也能独立理解"的问题(standalone question):

# 多轮对话:把历史对话 + 当前问题,重写成一个"不依赖上文"的独立问题
CONDENSE_PROMPT = """根据下面的对话历史,把用户的最新问题,
改写成一个意思完整、不依赖上文也能看懂的独立问题。
把其中的"它/这个/那个"等指代,替换成具体所指。

对话历史:
{history}

最新问题:{question}
独立问题:"""


def condense_question(history, question):
    """把'那它和 Vue 比呢'补全成'React 和 Vue 的状态管理对比'。"""
    hist_text = "\n".join(f"{role}: {content}" for role, content in history)
    prompt = CONDENSE_PROMPT.format(history=hist_text, question=question)
    return llm_complete(prompt, max_tokens=128).strip()

经过这一步,用户第二轮那句残缺的"那它和 Vue 比呢",就会被补全成完整的"React 和 Vue 在状态管理上的对比"——这才是一个能拿去检索的、自包含的 query。这里有个实践要点:对话历史不必全塞进去,通常最近几轮就够了——塞太多既增加成本,也容易让 LLM 被很久以前的话题带偏。这里的认知要点是:指代消解,是把"对话"和"检索"这两套不同的记忆模型对接起来。对话天然是有记忆的、连续的,每一句都站在前文的肩膀上;而检索天然是无记忆的、孤立的,每一次都从零开始。指代消解做的事,就是在每一轮,把对话那条连续的记忆,"压缩"进当前这一个问题里,让这个问题即使被单独拎出来,也依然完整。少了这一步,你的多轮 RAG,本质上是一个每轮都失忆的单轮 RAG。问题补全成独立的了,但如果它本身就包含好几个问题呢?

四、复合问题拆解:把一句多问拆成多个子查询

开头第三个问题——"一句话塞多个问题,哪段都命中不好"——根子在于:向量检索擅长匹配"一个聚焦的问题",而不擅长匹配"一团混合的诉求"。当用户问"这个功能怎么配置、报错了怎么排查、性能怎么优化"时,这其实是三个独立的问题,而知识库里大概率没有任何一段同时讲全这三件事。解法是复合问题拆解:先让 LLM 判断这是不是个复合问题,是就拆成子问题列表:

import json

# 复合问题拆解:让 LLM 判断是不是多个问题,是就拆成子问题列表
SPLIT_PROMPT = """判断下面的用户问题是否包含多个独立子问题。
如果是,拆成数组;如果只是一个问题,数组里就放它自己。
只输出 JSON 数组,例如:["子问题1", "子问题2"]

用户问题:{question}
"""


def split_question(question):
    """把'怎么配置、怎么排查、怎么优化'拆成三个独立子问题。"""
    raw = llm_complete(SPLIT_PROMPT.format(question=question),
                       max_tokens=256)
    try:
        subs = json.loads(raw)
        return subs if isinstance(subs, list) and subs else [question]
    except json.JSONDecodeError:
        return [question]               # 解析失败就当单个问题处理

拆出子问题之后,要让每个子问题各自去检索一遍,再把结果去重合并成一个候选池——这样三个子问题各自最相关的那几段,就都被网了进来:

# 把多个子查询各自检索,再把结果去重合并成一个候选池
def retrieve_multi(sub_queries, collection, per_query=5):
    """每个子查询各检索一批,合并去重 —— 复合问题才能被答全。"""
    seen, merged = set(), []
    for sq in sub_queries:
        hits = collection.query(
            query_embeddings=[embed(sq)], n_results=per_query)
        for doc in hits["documents"][0]:
            if doc not in seen:         # 不同子查询会捞到重复段,去重
                seen.add(doc)
                merged.append(doc)
    return merged

下面这张图,把从用户原始提问到最终上下文的完整改写流程串起来:

这里的认知要点是:复合问题拆解的精髓,是承认"检索的最小单位是一个聚焦的问题"。一个混合了三件事的提问,对检索器来说不是"一个难问题",而是"三个被搅在一起、谁也匹配不好的问题"。拆解,就是把这团乱麻解开,让每一根线各自去找它该找的那一段。它和前面的规范化、消解一脉相承——都是在为检索器"准备好原料",只不过这次准备的,是"把一份原料分成几份"。到这里,主链路的改写已经完整了,但还有一类问题——"提问和答案措辞错位"——要靠两个进阶手段来补。

五、查询扩展与 HyDE:补上"提问"和"答案"的鸿沟

开头第四个问题——"用户在提问,文档在陈述,措辞错位"——是一个更微妙的鸿沟。同一件事,"怎么解决跨域问题"(提问)和"配置 CORS 响应头即可允许跨域"(答案),用词差异很大,向量距离自然被拉远。第一个补救手段是查询扩展(多查询):让 LLM 把一个 query 换几种措辞重新表达,多个变体一起检索,总有一种措辞能对上文档:

# 查询扩展:让 LLM 把一个 query 改写成多个措辞不同、意思相同的 query
EXPAND_PROMPT = """把下面的查询,换 3 种不同的措辞重新表达,
意思保持一致。每行一个,只输出改写结果。

查询:{query}
"""


def expand_query(query):
    """一个问题多种问法 —— 覆盖文档可能采用的不同表述。"""
    raw = llm_complete(EXPAND_PROMPT.format(query=query), max_tokens=200)
    variants = [line.strip() for line in raw.splitlines() if line.strip()]
    return [query] + variants           # 原始 query 也保留在内

第二个、也是更巧妙的手段,是 HyDE(Hypothetical Document Embeddings,假设性文档嵌入)。它的思路非常反直觉:既然"问题"和"文档"措辞对不上,那就别拿问题去检索——先让 LLM 假装写一段答案,再拿这段"假设答案"去检索。因为假设答案和真实文档,同为"陈述句",措辞天然更接近:

# HyDE:先让 LLM"假装"写一段答案,再用这段假设答案去检索
# 原理:答案和文档同为"陈述句",向量距离比"问题"更近
HYDE_PROMPT = """针对下面的问题,写一段简短的、像知识库文档一样的
假设性回答。不必保证完全正确,重在贴近文档的表述风格。

问题:{question}
假设回答:"""


def hyde_retrieve(question, collection, n_results=5):
    """用 LLM 生成的假设答案做检索,跨越'提问'与'答案'的措辞鸿沟。"""
    hypo_answer = llm_complete(HYDE_PROMPT.format(question=question),
                               max_tokens=200)
    hits = collection.query(
        query_embeddings=[embed(hypo_answer)], n_results=n_results)
    return hits["documents"][0]

这里的认知要点是:查询扩展和 HyDE,瞄准的是同一个鸿沟——"用户怎么问"和"文档怎么写"之间的措辞落差。查询扩展用"广撒网"来跨越它:同一个意思多说几遍,总有一遍能对上。HyDE 则用"换赛道"来跨越它:干脆不在"问题空间"里找,先跳到"答案空间",再用答案去匹配答案。它们都不是必需品——只有当你发现检索的瓶颈确实是"措辞错位"时,才值得引入,因为它们都要额外的 LLM 调用,是用成本换召回。主链路和进阶手段都讲完了,最后是几个真正上规模后才会撞见的工程坑。

六、工程坑:改写延迟、改写质量与失败兜底

五块设计之外,还有几个工程坑,不处理就会让查询改写要么拖慢响应、要么帮倒忙、要么把整个检索拖垮坑 1:改写要调 LLM,有延迟和成本,要做缓存。每一次改写都是一次 LLM 调用,实打实地增加首字延迟。对策有两个:一是用又快又便宜的小模型做改写(改写是简单任务,不必动用最强模型);二是对相同的"提问 + 对话历史"缓存改写结果:

import hashlib

_rewrite_cache = {}

# 改写要调一次 LLM,有延迟和成本 —— 对相同输入缓存改写结果
def cached_rewrite(raw_query, history):
    """相同的'提问 + 对话历史',改写结果可直接复用。"""
    key = hashlib.md5(
        (raw_query + "||" + repr(history)).encode()).hexdigest()
    if key in _rewrite_cache:
        return _rewrite_cache[key]
    result = condense_question(history, raw_query)
    _rewrite_cache[key] = result
    return result

坑 2:改写会失败,必须有兜底。改写依赖 LLM,而 LLM 可能超时、可能返回一堆解释而非干净的结果、可能把问题改得面目全非。这里的铁律是:改写只能"锦上添花",绝不能因为它出问题,就让整个检索挂掉。所以改写一旦失败或结果异常,要果断回退到用户的原始提问——原始提问虽然不完美,但至少能用:

# 改写要调 LLM,LLM 可能超时、可能返回乱七八糟的东西
# 兜底原则:改写只能"锦上添花",绝不能因它失败而让整个检索挂掉
def safe_rewrite(raw_query, history):
    """改写失败 / 超时 / 结果异常时,一律回退到用户原始提问。"""
    try:
        rewritten = condense_question(history, raw_query)
        # 改写结果为空,或长得离谱,都视为不可信,回退原句
        if not rewritten or len(rewritten) > 200:
            return raw_query
        return rewritten
    except (TimeoutError, ValueError):
        return raw_query                # 出任何问题,原始提问照样能用

坑 3:改写有没有用,要用数据说话。"我觉得加了改写检索变准了"是没有意义的——改写也可能帮倒忙(比如把一个本来清楚的问题改歪了)。要准备一个带标准答案的测试集,A/B 对比"直接检索"和"改写后检索"的命中率:

# 改写到底有没有用?要用带标准答案的测试集量化对比
def eval_rewrite(test_set, collection):
    """对比'直接检索'与'改写后检索'的命中率,用数据决定是否上改写。"""
    raw_hit, rw_hit = 0, 0
    for question, history, answer_doc in test_set:
        # 路线 A:原始提问直接检索
        a = collection.query(query_embeddings=[embed(question)],
                             n_results=5)["documents"][0]
        # 路线 B:改写后再检索
        rw = safe_rewrite(question, history)
        b = collection.query(query_embeddings=[embed(rw)],
                             n_results=5)["documents"][0]
        raw_hit += answer_doc in a
        rw_hit += answer_doc in b
    n = len(test_set)
    return {"raw_hit_rate": raw_hit / n, "rewrite_hit_rate": rw_hit / n}

坑 4:别给每个问题都套上全套改写。规范化、消解、拆解、扩展、HyDE——不是每个问题都需要全上。一个本来就规范、单一、不含指代的问题,套上全套改写只会徒增延迟和成本,甚至改出错。更聪明的做法是先做一次轻量判断:是多轮才做消解,是复合才做拆解,措辞错位严重才上 HyDE——按需改写,而不是无脑全套

关键概念速查

概念 / 手段 说明
查询改写 检索前把用户原始提问转换成更适合检索的 query
查询规范化 把口语化、随意的提问改写成规范清晰的检索语句
指代消解 多轮对话中把"它/这个"补全为具体所指
独立问题 不依赖对话上文也能完整理解的自包含问题
复合问题拆解 把一句话里的多个子问题拆成多个独立子查询
多查询检索 每个子查询各自检索,结果去重合并成候选池
查询扩展 把一个 query 改写成多个同义变体,覆盖不同表述
HyDE 先让 LLM 生成假设答案,用答案而非问题去检索
改写兜底 改写失败或结果异常时回退到用户原始提问
改写评测 用测试集对比改写前后的命中率,用数据决策

避坑清单

  1. 用户的原始提问不是好 query,检索前要先做查询改写。
  2. 口语化提问要规范化,翻译成贴近文档表述的检索语句。
  3. 多轮对话必须做指代消解,把"它/这个"补全成独立问题。
  4. 一句话里的多个子问题要拆开,分别检索再合并。
  5. 不同子查询会捞到重复段落,合并候选时务必去重。
  6. 用查询扩展生成多种问法,覆盖文档可能的不同表述。
  7. 提问与答案措辞错位严重时,用 HyDE 以假设答案去检索。
  8. 改写要调 LLM,有延迟和成本,对相同输入做缓存。
  9. 改写失败或结果异常,一律回退原始提问,绝不让检索挂掉。
  10. 改写有没有用要用测试集量化,别给每个问题都套全套。

总结

回头看那串"口语提问跑偏、多轮对话失忆、复合问题命中差、提问与答案错位"的问题,以及我后来在查询环节接连踩的坑,最该记住的不是某一个改写技巧,而是我动手前那个想当然的判断——"用户问什么,我就拿这句话原样去检索"。这句话错在它把"用户敲进对话框的话"和"一个好的检索 query"划上了等号。我以为用户的提问,就是现成的检索输入。可我忽略了一件事:用户的那句话,是说给"人"听的——它默认听者有记忆、能联想、会补全、懂语境。而向量检索,恰恰一样都没有你把一句"为人优化"的话,直接喂给一个"没有语境"的检索器,它检索不准,是必然的——你交给它的,从来就不是一份合格的原料。

所以做好 RAG 的查询环节,真正的工程量不在"调用一次向量库的 query"那一行代码上。那一行,谁都会写。真正的工程量,在于你要承认"用户的原话只是原料,得先加工成好 query",并据此在检索之前补上一整道改写工序:用户的话口语、随意,你就做查询规范化,把它翻译成文档的语言;多轮对话里满是指代,你就做指代消解,把它补全成独立问题;一句话塞了好几问,你就把它拆成子问题,各自检索再合并;提问和答案措辞错位,你就用查询扩展或 HyDE 去跨越那道沟;改写本身会失败,你就给它兜底,出问题就回退原话。这篇文章的几节,其实就是顺着这条线展开的:先想清楚"直接用原始提问"为什么错,再讲规范化怎么做、指代怎么消解、复合问题怎么拆、扩展和 HyDE 怎么用,最后是改写延迟、失败兜底、按需改写这几个把查询环节做扎实的工程细节。

你会发现,RAG 的查询改写,和现实里"一家大医院的分诊台"完全相通。患者挂号时,往往只会含含糊糊地说一句"我这儿不太舒服"。一家没有分诊台的医院会怎么做?它让患者揣着这句模糊的话,自己随便闯进一个科室(这就是拿原始提问直接检索)。结果呢?患者说的"不舒服"跟科室墙上挂的专业病种名词对不上,挂号员一脸茫然(这就是口语提问检索跑偏);患者说"还是上次那个老毛病",可新来的医生根本不知道"上次"是什么(这就是多轮对话失忆);患者一口气说了头疼、咳嗽、还失眠,被塞进一个科室,哪样都看不透(这就是复合问题命中差)。而一家有分诊台的医院怎么做?患者一进门,分诊护士先问清楚:具体哪儿不舒服、什么症状、多久了(这就是查询规范化);调出他的病历,搞清"老毛病"到底是什么(这就是结合对话历史做指代消解);发现他同时有几种症状,就分别开出几张不同科室的单子(这就是复合问题拆解)。同样的患者、同样的医院,可前者让患者在科室间来回碰壁,后者几分钟就把他精准送到该去的地方——差别不在医生水平,只在大门口那个"先问清、再分诊"的环节

最后想说,RAG 的查询环节做没做对,差距永远不会在"开发者自己本地一测就很准"时暴露——本地是"懂行的人在提问",你会下意识地把问题问得规范、完整、单一,恰好就是检索器最喜欢的样子,你会觉得"把用户输入拿去检索"已经是全部。它只在真实的、用户用大白话提问、动不动就多轮追问、还爱一口气问好几件事的环境里才显形。那时候它会用最伤体验的方式给你结账:做不好,你的问答会因为一句口语就检索跑偏,会在多轮对话里一问"那它呢"就彻底失忆,用户多问一句反而答得更差;而做了,你的检索会稳稳地拿到一个加工好的 query:口语被翻译成了规范表述,指代被补全成了完整问题,复合问题被拆开各自命中。所以别等"用户抱怨它听不懂人话"找上门,在你写下那行向量库 query 的时候就该想清楚:我送进去的不该是用户那句"为人优化"的原话,而该是一个"为检索优化"的好 query——它规范化了吗、指代消解了吗、复合问题拆开了吗,这一道道改写工序,我是不是都替它走完了?这些问题有了答案,你的 RAG 才不只是一个"问得规范就答得对"的演示,而是一套真正听得懂用户大白话、扛得住多轮追问的可靠问答系统

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

数据库慢查询优化完全指南:从一次"数据量一大接口就卡死、加了索引却没用"看懂慢查询治理

2026-5-22 2:05:00

技术教程

Docker 镜像优化完全指南:从一次"镜像几个 G、改一行代码全部重新构建"看懂镜像瘦身

2026-5-22 2:17:14

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索