0Pricing
AI Prompt Engineering · 课时

查询改写与 HyDE

提高检索召回率。

查询改写与 HyDE 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Prompt Engineering 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Prompt Engineering 课程共包含 4 节课。

查询是薄弱环节

检索质量受查询限制。原始用户查询通常很短、含义模糊、充满代词,或者措辞与文档不同。查询转换会在检索前重塑查询,以提高召回率和精确率。

这是相较于朴素 RAG 成本最低、杠杆效应最高的升级方式之一:修正查询,下游每个阶段都会受益。

def transform_query(raw, history):
    # Resolve references, expand, decompose, or hypothesize
    # BEFORE embedding and retrieving.
    return rewrite(raw, history)

查询重写

最简单的转换方式是:让 LLM 将用户查询重写为更清晰、自包含且便于检索的形式。它可以修正拼写错误、展开缩写,并删除对话噪声。

这在多轮对话中尤其重要,因为如果没有上下文,最新消息可能无法理解(“第二个怎么样?”)。

def rewrite_query(raw):
    prompt = (
        'Rewrite the user question into a clear, standalone search '
        'query optimized for document retrieval. Keep key entities.\n'
        'Question: ' + raw
    )
    return llm(prompt, temperature=0).strip()

对话式凝缩

在聊天式 RAG 中,请将对话和新一轮消息 condense 成一个独立问题。否则,代词和省略号会使嵌入失去意义,检索也会崩溃。

请传入之前的对话轮次,以便重写器将“它”“那个”“前一个”等内容解析为检索器能够匹配的明确实体。

def condense(history, follow_up):
    prompt = (
        'Given the conversation, rewrite the follow-up as a standalone '
        'question with all references resolved.\nConversation:\n' +
        render(history) + '\nFollow-up: ' + follow_up
    )
    return llm(prompt, temperature=0).strip()

查询扩展

扩展会生成同义词、相关术语或替代表述,以扩大词汇和语义覆盖范围。它可以应对词汇不匹配:文档使用 invalidate,而用户使用 revoke。

请为检索进行扩展,然后使用结果并集进行检索;不要向用户展示扩展后的形式。请注意过度扩展,否则可能会引入偏离主题的结果。

def expand(query, n=3):
    prompt = (
        'List ' + str(n) + ' alternative phrasings of this query using '
        'synonyms and domain terms, one per line.\n' + query
    )
    variants = parse_lines(llm(prompt, temperature=0.5))
    return [query] + variants

多查询检索

生成多个多样化的改写版本,分别进行检索,然后融合结果列表(倒数排名融合)。不同的措辞会找出不同的相关片段;融合可以结合它们的优势,提升召回的稳定性。

这种方法以增加额外检索调用为代价,换取对任一单一糟糕措辞的稳健性。

def multi_query(raw, n=4):
    queries = expand(raw, n)
    rankings = [dense_retrieve(q, 30) for q in queries]
    fused = rrf(*rankings)
    return dedup(fused)

查询分解

复杂的多跳问题需要分解为多个子问题,分别检索后再进行组合。例如,“收购 X 的那家公司中,哪位 CEO 比……年长?”无法通过一次检索得到答案。

请进行分解,针对每个子问题检索,然后让生成器组合收集到的证据。

def decompose_and_retrieve(question):
    subs = parse_lines(llm(
        'Break this into independent sub-questions, one per line.\n' +
        question, temperature=0))
    evidence = {s: rerank(s, dense_retrieve(s, 30))[:3] for s in subs}
    return evidence  # generator synthesizes the final answer

HyDE:核心思想

HyDE(假设文档嵌入,Gao 等人,2022)改变了问题的处理方式。它不再对简短查询进行嵌入,而是要求 LLM生成一份假设的答案文档,然后对该文档进行嵌入,并使用它进行检索。

假设文档与真实文档处于相同的语体中,因此其嵌入会更接近真实答案,从而解决查询与文档之间的不匹配。

def hyde(query):
    hypo = llm(
        'Write a short passage that would answer this question, as if '
        'from a reference document.\nQuestion: ' + query,
        temperature=0.3)
    return dense_retrieve_by_vector(embed(hypo), k=30)  # embed the answer

HyDE 为什么能提高召回率

问题与其答案的措辞不同;而问题与虚构但合理的答案,其措辞会与真实答案相似。HyDE 利用 LLM 的生成先验来弥合这一差距,即使假设文档包含事实错误也没有关系。

假设文档是否正确并不十分重要:它只需要具备正确的词汇和结构,就能在嵌入空间中接近真实文档。

# Robustness: average several hypothetical docs to reduce variance
def hyde_avg(query, n=3):
    vecs = [embed(llm(HYDE_PROMPT + query, temperature=0.5))
            for _ in range(n)]
    centroid = sum(vecs) / n
    return dense_retrieve_by_vector(centroid, 30)

HyDE 的权衡与失效模式

HyDE 会在检索前增加一次 LLM 生成,因此会增加延迟和成本;它还可能产生幻觉,生成偏离主题的假设文档,从而降低模型对小众查询或分布外查询的召回率,因为模型对这些内容一无所知。

可以将 HyDE 向量检索与原始查询检索结合并进行融合,从而缓解这一问题,使糟糕的假设文档无法完全拖垮召回率。

def hyde_hybrid(query):
    a = dense_retrieve_by_vector(embed(hyde_doc(query)), 30)
    b = dense_retrieve(query, 30)          # raw-query fallback
    return dedup(rrf(a, b))                 # robust to bad hypotheticals

选择与组合技术

这些转换方式彼此互补。聊天场景使用对话凝缩,词汇缺口使用扩展/多查询,多跳问题使用分解,查询与文档语体不匹配时使用HyDE。许多生产系统会先进行凝缩,再使用 HyDE,然后与原始查询融合,最后进行重排序。

每增加一种转换,就会增加一次 LLM 调用,因此请根据查询类型决定是否启用,而不要始终全部运行。

def route_transform(query, history, qtype):
    q = condense(history, query) if history else query
    if qtype == 'multi_hop': return decompose_and_retrieve(q)
    if qtype == 'mismatch':  return hyde_hybrid(q)
    if qtype == 'vocab_gap': return multi_query(q)
    return dense_retrieve(q, 30)

评估变换

在无信息泄漏的数据集上,以相对于原始查询在检索召回率@k和最终答案准确率方面的提升来衡量每个变换。跟踪增加的延迟和 LLM 成本,只保留收益足以抵消成本的变换。

请注意:如果一个变换虽然提高了召回率,却引入了干扰项,那么除非与重排序和压缩结合使用,否则可能会降低答案准确率。

def eval_transform(name, fn, eval_set):
    return {
        'recall@10': recall_at_k(eval_set, retriever=fn, k=10),
        'answer_acc': answer_accuracy(eval_set, retriever=fn),
        'extra_latency_ms': transform_latency(fn),
    }

快速检查

请思考一下,为什么 HyDE 尽管假设答案并不完美,仍然有效。

回顾

要点:

  • 检索受查询限制;对查询进行变换是成本低且效果显著的 RAG 升级方式。
  • 重写和凝练使聊天查询成为独立查询;扩展和多查询可以弥补词汇缺口。
  • 分解通过针对每个子问题进行检索来处理多跳问题。
  • HyDE 嵌入假设答案,以弥合问题与文档之间的语体不匹配;无需保证假设答案正确。
  • 按查询类型组合变换,与原始查询融合以提高稳健性,并评估召回率、答案准确率、延迟和成本。

常见问题解答

「查询改写与 HyDE」课时是免费的吗?

是的 — 「查询改写与 HyDE」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Prompt Engineering 课程的其余内容,请升级到 CoddyKit PRO。 AI Prompt Engineering 课程共包含 4 节课。

「查询改写与 HyDE」这节课中我会学到什么?

提高检索召回率。 你通过在浏览器中直接运行的动手代码来练习 AI Prompt Engineering,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 AI Prompt Engineering 需要有经验吗?

无需任何先前经验。CoddyKit 上的 AI Prompt Engineering 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「查询改写与 HyDE」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 AI Prompt Engineering 课中编写并运行代码吗?

能。每节 AI Prompt Engineering 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 超越朴素 RAG
  2. 对检索到的文本块重新排序
  3. 上下文压缩
  4. 查询改写与 HyDE
← 返回 AI Prompt Engineering