@freeCodeCamp: 当用户的问题与答案措辞不同时,RAG系统可能会遗漏相关文档。在本指南中,@S…

X AI KOLs Timeline 工具

摘要

本指南介绍了HyDE(假设文档嵌入)技术,该技术通过在搜索知识库之前生成并嵌入一个假设答案来改进RAG检索,并提供了带有生产环境防护措施的Python实现。

RAG系统在用户的问题与答案措辞不同时,可能会遗漏相关文档。 在本指南中,@SameerKShukla 解释了HyDE如何通过在搜索知识库之前生成并嵌入一个假设答案来改进检索。 您将了解该技术的工作原理,用Python实现它,并添加生产环境防护措施。 https://freecodecamp.org/news/what-is-hyde-how-to-improve-rag-with-hypothetical-documents/…
查看原文
查看缓存全文

缓存时间: 2026/07/25 08:03

RAG 系统可能会因用户提问与答案表述方式不同而遗漏相关文档。

在本指南中,@SameerKShukla 解释了 HyDE 如何通过在搜索知识库之前生成并嵌入一个假设性答案来改进检索。

你将了解该技术的工作原理,用 Python 实现它,并添加生产级防护措施。

https://freecodecamp.org/news/what-is-hyde-how-to-improve-rag-with-hypothetical-documents/…


什么是 HyDE?如何利用假设文档改进 RAG

来源:https://www.freecodecamp.org/news/what-is-hyde-how-to-improve-rag-with-hypothetical-documents/ 什么是 HyDE?如何利用假设文档改进 RAG检索增强生成(RAG)已成为构建大型语言模型应用最广泛的方法之一。

RAG 系统不会要求 LLM 完全基于训练数据作答,而是从外部知识库中检索相关信息,并将其作为上下文提供给模型。

基本思路很简单:

  • 将用户问题转换为嵌入向量。
  • 在向量数据库中搜索语义相似的文档片段。
  • 将检索到的片段传递给 LLM。
  • 基于这些片段生成答案。

图1:检索增强生成(RAG)工作流但这个看似简单的过程存在一个重大缺陷:用户提问与包含答案的文档可能表述差异巨大。

用户可能会问:

为什么我的 AWS Glue 作业在处理数百万条记录后速度显著变慢?

知识库中的相关文档可能写道:

当 Spark 执行器经历过多 Shuffle 操作、分区倾斜、内存压力或频繁磁盘溢出时,可能导致性能下降。

查询和文档描述的是同一问题,但使用的词汇、结构及详细程度不同。因此,直接查询嵌入可能无法使它们在嵌入空间中足够接近。

这正是 Hypothetical Document Embeddings(HyDE)要解决的问题。

目录

  • 先决条件 (https://www.freecodecamp.org/news/what-is-hyde-how-to-improve-rag-with-hypothetical-documents/#heading-prerequisites)
  • 什么是 HyDE? (https://www.freecodecamp.org/news/what-is-hyde-how-to-improve-rag-with-hypothetical-documents/#heading-what-is-hyde)
  • HyDE 的机制 (https://www.freecodecamp.org/news/what-is-hyde-how-to-improve-rag-with-hypothetical-documents/#heading-the-mechanics-of-hyde)
  • 最小实现 (https://www.freecodecamp.org/news/what-is-hyde-how-to-improve-rag-with-hypothetical-documents/#heading-minimal-implementation)
  • 为什么幻觉不会自动破坏 HyDE (https://www.freecodecamp.org/news/what-is-hyde-how-to-improve-rag-with-hypothetical-documents/#heading-why-hallucination-doesnt-automatically-break-hyde)
  • 生产防护措施 (https://www.freecodecamp.org/news/what-is-hyde-how-to-improve-rag-with-hypothetical-documents/#heading-production-guardrails)
  • 总结 (https://www.freecodecamp.org/news/what-is-hyde-how-to-improve-rag-with-hypothetical-documents/#heading-summary)

先决条件

为充分利用本文,你需要了解并具备以下条件。

需要了解:

  • 基本熟悉 RAG 及其用途 (https://www.freecodecamp.org/news/rag-explained-simply-with-a-real-project/)。
  • 概念层面理解向量嵌入的工作原理。
  • 掌握 Python 使用经验。

需要准备:

  • 本地 Python 环境,已安装 numpy、sentence-transformers 和 Anthropic。
  • 如需运行 HyDE 代码示例,需 Anthropic API 密钥(可在 console.anthropic.com (http://console.anthropic.com/) 获取)。

HyDE 代表 Hypothetical Document Embeddings。该技术很简单。查询时,提示 LLM 生成一个能回答用户问题的假设文档,嵌入该文档而非查询本身,然后用其向量搜索索引。这就是全部思路。其余全是工程问题。

50c5909c-c7fa-4c92-bf11-c7a1b3411142
图2:HyDE 流程

假设文档不被视为最终答案。它仅作为用户查询与知识库存档之间的桥梁。

这一区分至关重要。

生成的文档可能包含错误细节。但这不一定是失败,因为系统不会直接将其呈现给用户。其目的是为所寻求的信息生成更丰富的语义表示。

原始的 HyDE 方法使用语言模型生成假设文档,并使用无监督密集检索器将这些文档映射到嵌入空间。嵌入作为搜索指令,从语料库中检索真实文档。

为什么 HyDE 有效

直觉是几何性的。密集检索器将文本投影到语义空间,两段文本的相似度由其向量夹角余弦值衡量。

当你嵌入一个问题并将其与一段文字比较时,你测量的是两种文本形状之间的角度,而这些文本本就不应接近。嵌入模型训练时是将语义相近的文本放在一起,而非将问题与答案靠近。它们的几何结构不同。

HyDE 通过使比较双方形状相同来弥合这一差距。假设段落与真实文档处于向量空间中的同一邻域,因为它使用相同的语域、词汇和详细程度写成。现在,向量搜索是在答案之间进行比较,而非问题与答案,相似度信号更清晰。

这便是整个机制。其余一切——提示工程、模型选择、缓存——都基于这一几何事实。

HyDE 的机制

首先,假设用户提问:为什么我的 Lambda 函数在长时间未被调用后响应变慢?

然后用简短提示问 LLM:“请写一段技术文档段落来回答这个问题。”

LLM 回复类似内容:

“AWS Lambda 会回收空闲一段时间的执行环境。当函数再次被调用时,会发生冷启动,包括设置运行时和加载依赖项。这会在空闲期后的首次调用中增加额外延迟。”

现在嵌入生成的段落。不是原始问题——而是段落。

使用该嵌入搜索向量存储。假设段落格式类似真实文档,因此关于冷启动的 AWS 真实文档现在在向量空间中彼此靠近。

接下来,取 top-k 检索到的文档,连同原始用户问题一起传递给生成器。生成器使用检索到的真实文档作答。假设文档被丢弃。

LLM 被使用了两次,但任务不同:一次将查询重写为文档,一次使用检索到的文档回答问题。第一次调用成本低且风险小。第二次才是关键。

图3:朴素 RAG 与 HyDE 流水线的比较。图3:朴素 RAG 与 HyDE 流水线的比较。

最小实现

朴素 RAG 可能如下所示:

`` import numpy as np from sentence_transformers import SentenceTransformer

collection = [ “AWS Lambda reclaims idle execution environments after a period of inactivity, causing a cold start on the next invocation that includes runtime bootstrap and dependency loading.”, “Apache Airflow schedules tasks using a directed acyclic graph, where each node represents a unit of work.”, “AWS Glue crawlers infer schemas from source data and populate the Glue Data Catalog automatically.”, “Amazon Bedrock exposes foundation models behind a single API and handles provisioning transparently.”, “DynamoDB partitions data across nodes using the partition key, which determines physical placement.”, ]

embedder = SentenceTransformer(“all-MiniLM-L6-v2”) collection_embeddings = embedder.encode(collection, normalize_embeddings=True)

def retrieve(query: str, k: int = 2) -> list[str]: query_embedding = embedder.encode(query, normalize_embeddings=True) scores = collection_embeddings @ query_embedding top_k = np.argsort(scores)[::-1][:k] return [collection[i] for i in top_k]

query = “Why does my Lambda function take longer to respond when it hasn’t been called in a while?” for passage in retrieve(query): print(passage) ``

在这个示例集合中,它很可能在 rank 1 返回正确段落。当扩展到五万篇文档且查询存在真实变化时,正确段落的排名会逐渐下降。

需要注意的一行是 retrieve 函数中的 embedder.encode(query, ...)。这就是原始问题变为向量的地方,也是 HyDE 改变的行。

在 HyDE 变体中,改动仅在于一个函数:

`` import numpy as np from anthropic import Anthropic from sentence_transformers import SentenceTransformer

collection. 在生产中,这是你的向量存储。

collection = [ “AWS Lambda reclaims idle execution environments after a period of inactivity, causing a cold start on the next invocation that includes runtime bootstrap and dependency loading.”, “Apache Airflow schedules tasks using a directed acyclic graph, where each node represents a unit of work.”, “AWS Glue crawlers infer schemas from source data and populate the Glue Data Catalog automatically.”, “Amazon Bedrock exposes foundation models behind a single API and handles provisioning transparently.”, “DynamoDB partitions data across nodes using the partition key, which determines physical placement.”, ]

embedder = SentenceTransformer(“all-MiniLM-L6-v2”) corpus_embeddings = embedder.encode(collection, normalize_embeddings=True)

client = Anthropic()

HyDE:生成一个假设答案,对其进行嵌入,然后搜索。

HYDE_PROMPT = ( “Write a short passage from technical documentation that would answer “ “the following question. Write in the register of official docs: “ “declarative, precise, no hedging. Do not include the question itself. “ “Passage only, two to four sentences.\n\n” “Question: {query}” )

def generate_hypothetical(query: str) -> str: “”“让 LLM 编写一段虚构的技术文档段落来回答查询。”“” message = client.messages.create( model=“claude-haiku-4-5”, max_tokens=200, messages=[ {“role”: “user”, “content”: HYDE_PROMPT.format(query=query)} ], ) return message.content[0].text

def retrieve_hyde(query: str, k: int = 2) -> list[str]: “”“生成假设段落,嵌入它,然后使用该向量进行搜索。”“” hypothetical = generate_hypothetical(query) hyde_embedding = embedder.encode(hypothetical, normalize_embeddings=True) scores = corpus_embeddings @ hyde_embedding top_k_indices = np.argsort(scores)[::-1][:k] return [collection[i] for i in top_k_indices]

if name == “main”: query = ( “Why does my Lambda function take longer to respond “ “when it hasn’t been called in a while?” ) for passage in retrieve_hyde(query): print(passage) ``

这就是整个技术。多了一次 LLM 调用、一个额外函数,其余部分与基线完全相同。假设文本在嵌入后被丢弃,永远不进入生成器。

朴素基线直接对问题向量化,并在集合向量上执行余弦相似度搜索。正是 embedder.encode(query, ...) 这一行代码,将问题向量化为问题形状而非答案形状,这也是本文讨论的检索质量问题的唯一根源。

HyDE 方法的不同之处仅在于一点:在嵌入之前,让 LLM 生成一小段技术文档风格的文本回答该问题,然后计算该文本的向量,而非原始问题。其他一切保持不变——相同的嵌入模型、余弦相似度搜索和 top-k 选择。

这段假设文本仅用于生成搜索向量,不做他用。区别不在于检索方法本身,而在于改变了待比较文本的形状。

为什么幻觉不会自动破坏 HyDE

起初,HyDE 似乎自相矛盾。为什么一个系统会通过让语言模型在检索事实之前生成信息来改进事实检索?

答案是 HyDE 将生成的文档作为检索表示,而非可信知识。

假设用户问:7月18日数据库宕机的成因是什么?LLM 不可能从私有事故报告中知道实际原因。它只能编造。

所以它可能会说:

“7月18日的数据库宕机是由主副本上的故障转移配置错误引起的,导致依赖服务出现级联连接超时。工程师通过将流量重路由到辅助区域并重建连接池恢复了服务。”

这段话完全是虚构的。真正原因可能是磁盘故障、糟糕的部署、证书过期等任何情况。但看这段话包含的词:宕机、故障转移、副本、级联超时、连接池、辅助区域。这些恰好是你真实事故总结报告中会出现的词,无论实际原因是什么。

数据库宕机的事故报告彼此听起来相似。它们共享词汇、语域和结构,无论具体根因是什么。

LLM 生成的段落还可能涉及连接饱和、锁竞争、存储延迟、部署失败或资源耗尽。其中某些细节可能错误,但这不重要。每个这样的术语仍会将嵌入拉向真实事故分析、根因报告、数据库指标和事后总结文档所处的相似邻域。

当你嵌入那段虚构段落时,向量落在真实事故总结所在邻域。向量搜索检索到正确的事故总结。然后生成器才读取实际文档并产生真实答案。

假设在事实上是错误的,但在形状上是正确的。形状是嵌入所见的。事实是检索文档所提供的。

这里的真正风险不在于幻觉本身,而在于你如何处理它。如果系统错误地将假设文档作为检索到的证据传递给最终答案生成器,那么虚构的内容就会到达用户。

缓解措施是架构性的,而非统计性的:将假设严格限制在检索步骤内,绝不让它泄漏到生成上下文中。下一节将详细讨论这一点。

生产防护措施

HyDE 在检索路径中引入了一个 LLM,这带来了新的工程问题。以下是一些可以添加的生产防护措施,使其更安全可靠:

应用超时和回退

如果假设生成缓慢或失败,降级为朴素检索,而不是阻塞用户。

`` def retrieve_with_fallback(query: str, k: int = 2) -> list[str]: try: hypothetical = generate_hypothetical(query) search_vector = embedder.encode(hypothetical, normalize_embeddings=True) except Exception: logger.exception(

相似文章