测试18种RAG技术,找出最优方案(四)


HyDE

到目前为止,我们一直在直接对用户的查询或其转换版本进行嵌入处理。HyDE(Hypothetical Document Embedding,假设文档嵌入)则采用了不同的方法。它不对查询进行嵌入,而是对一个能回答该查询的假设文档进行嵌入。

HyDE工作流程

流程如下:

  1. 生成假设文档:使用LLM创建一个(若存在的话)能回答该查询的文档。

  2. 对假设文档进行嵌入:为这个假设文档创建嵌入,而不是原始查询。

  3. 检索:找到与假设文档嵌入相似的文档。

  4. 生成:使用检索到的文档(不是假设的那个!)来回答查询。

其核心思想是,一个完整的文档,即使是假设的,也比一个简短的查询具有更丰富的语义表达。这有助于弥合查询与嵌入空间中的文档之间的差距。

让我们看看它是如何工作的。首先,我们需要一个生成假设文档的函数。

我们使用generate_hypothetical_document来实现:

ini 复制代码
def generate_hypothetical_document(query, desired_length=1000):
    """
    生成一个能回答该查询的假设文档。
    """
    # 定义系统提示,指导模型如何生成文档
    system_prompt = f"""你是一名专业的文档创作者。
    给定一个问题,生成一份详细的文档来直接回答这个问题。
    该文档长度应约为{desired_length}个字符,并且要对该问题提供深入、
    翔实的回答。撰写时要让这份文档看起来像是来自该主题的权威来源。
    包含具体的细节、事实和解释。
    不要提及这是一份假设文档------直接撰写内容即可。"""

    # 定义包含查询的用户提示
    user_prompt = f"问题:{query}\n\n生成一份能完整回答该问题的文档:"
    
    # 向OpenAI API请求生成假设文档
    response = client.chat.completions.create(
        model="meta-llama/Llama-3.2-3B-Instruct",  # 指定要使用的模型
        messages=[
            {"role": "system", "content": system_prompt},  # 指导助手的系统消息
            {"role": "user", "content": user_prompt}  # 包含查询的用户消息
        ],
        temperature=0.1  # 设置响应生成的温度参数
    )
    
    # 返回生成的文档内容
    return response.choices[0].message.content

这个函数接收查询,并使用LLM虚构一个能回答该查询的文档。

现在,让我们将所有内容整合到hyde_rag函数中:

python 复制代码
def hyde_rag(query, vector_store, k=5, should_generate_response=True):
    """
    使用假设文档嵌入(Hypothetical Document Embedding)执行RAG。
    
    """
    print(f"\n=== 使用HyDE处理查询:{query} ===\n")
    
    # 步骤1:生成能回答该查询的假设文档
    print("生成假设文档...")
    hypothetical_doc = generate_hypothetical_document(query)
    print(f"生成的假设文档长度为{len(hypothetical_doc)}个字符")
    
    # 步骤2:为假设文档创建嵌入
    print("为假设文档创建嵌入...")
    hypothetical_embedding = create_embeddings([hypothetical_doc])[0]
    
    # 步骤3:基于假设文档检索相似的文本块
    print(f"检索{k}个最相似的文本块...")
    retrieved_chunks = vector_store.similarity_search(hypothetical_embedding, k=k)
    
    # 准备结果字典
    results = {
        "query": query,
        "hypothetical_document": hypothetical_doc,
        "retrieved_chunks": retrieved_chunks
    }
    
    # 步骤4:如果需要,生成响应
    if should_generate_response:
        print("生成最终响应...")
        response = generate_response(query, retrieved_chunks)
        results["response"] = response
    
    return results

hyde_rag函数现在的功能是:

  1. 生成假设文档。

  2. 为该文档(不是查询!)创建嵌入。

  3. 使用该嵌入进行检索。

  4. 像之前一样生成响应。

让我们运行它,看看生成的响应:

ini 复制代码
# 运行HyDE RAG
hyde_result = hyde_rag(query, vector_store)

# 评估。
evaluation_prompt = f"用户查询:{query}\nAI响应:\n{hyde_result['response']}\n真实响应:{reference_answer}\n{evaluate_system_prompt}"
evaluation_response = generate_response(evaluate_system_prompt, evaluation_prompt)
print(evaluation_response.choices[0].message.content)

### 输出
0.5

我们的评估分数约为0.5。

虽然HyDE是个巧妙的想法,但它并不总是更有效。在这个案例中,假设文档的方向可能与我们实际的文档集合略有不同,导致检索到的内容相关性较低。

这里的关键经验是,不存在单一的"最佳"RAG技术。不同的方法在不同的查询和不同的数据上表现更好。

融合检索

我们已经了解到,不同的检索方法各有优势。向量搜索擅长语义相似性,而关键词搜索则在查找精确匹配方面表现出色,如果我们能将它们结合起来会怎样?这就是Fusion RAG背后的理念。

融合 RAG工作流程

融合 RAG不局限于选择一种检索方法,而是两种都用,然后对结果进行合并和重新排序。这样我们就能同时捕捉语义含义和精确的关键词匹配。

我们实现的核心是fusion_retrieval函数。该函数同时执行基于向量和基于BM25的检索,对每种方法的分数进行归一化,使用加权公式将它们组合起来,然后根据组合后的分数对文档进行排序。

以下是融合检索的函数:

python 复制代码
import numpy as np

def fusion_retrieval(query, chunks, vector_store, bm25_index, k=5, alpha=0.5):
    """通过结合基于向量和BM25的搜索结果执行融合检索。"""
    
    # 为查询生成嵌入
    query_embedding = create_embeddings(query)

    # 执行向量搜索,并将结果存储在字典中(索引 -> 相似度分数)
    vector_results = {
        r["metadata"]["index"]: r["similarity"] 
        for r in vector_store.similarity_search_with_scores(query_embedding, len(chunks))
    }

    # 执行BM25搜索,并将结果存储在字典中(索引 -> BM25分数)
    bm25_results = {
        r["metadata"]["index"]: r["bm25_score"] 
        for r in bm25_search(bm25_index, chunks, query, len(chunks))
    }

    # 从向量存储中检索所有文档
    all_docs = vector_store.get_all_documents()

    # 使用向量分数和BM25分数的加权和计算每个文档的组合分数
    scores = [
        (i, alpha * vector_results.get(i, 0) + (1 - alpha) * bm25_results.get(i, 0)) 
        for i in range(len(all_docs))
    ]

    # 按组合分数降序对文档进行排序,并保留前k个结果
    top_docs = sorted(scores, key=lambda x: x[1], reverse=True)[:k]

    # 返回前k个文档,包含文本、元数据和组合分数
    return [
        {"text": all_docs[i]["text"], "metadata": all_docs[i]["metadata"], "score": s} 
        for i, s in top_docs
    ]

它融合了两种方法的优点:

  • 向量搜索:使用我们现有的create_embeddings和SimpleVectorStore来实现语义相似性检索。

  • BM25搜索:使用BM25算法(一种标准的信息检索技术)实现基于关键词的搜索。

  • 分数组合:将两种方法的分数结合起来,为我们提供一个统一的排名。

让我们运行完整的流程并生成响应:

ini 复制代码
# 首先,处理文档以创建文本块、向量存储和BM25索引
chunks, vector_store, bm25_index = process_document(pdf_path)

# 使用融合检索运行RAG
fusion_result = answer_with_fusion_rag(query, chunks, vector_store, bm25_index)
print(fusion_result["response"])

# 评估
evaluation_prompt = f"用户查询:{query}\nAI响应:\n{fusion_result['response']}\n真实响应:{reference_answer}\n{evaluate_system_prompt}"
evaluation_response = generate_response(evaluate_system_prompt, evaluation_prompt)
print(evaluation_response.choices[0].message.content)

###输出
AI响应的评估分数为0.83

最终分数为0.83。

Fusion RAG通常能给我们带来显著的提升,因为它结合了不同检索方法的优势。这就像有两位专家协同工作------一位擅长理解查询的含义,另一位擅长查找精确匹配。

多模态

到目前为止,我们只处理过文本信息。但很多信息都存在于图像、图表和示意图中。多模态RAG(Multi-Modal RAG)旨在解锁这些信息,并利用它们来优化我们的响应。

多模态工作流程

这里的关键变化如下:

  1. 提取文本和图像:我们从PDF中同时提取文本和图像。

  2. 生成图像描述:我们使用LLM(具体来说,是具备视觉能力的模型)为每个图像生成文本描述(字幕)。

  3. 创建嵌入(文本和描述):我们为文本块和图像描述都创建嵌入。

  4. 嵌入模型:在本笔记本中,我们使用BAAI/bge-en-icl嵌入模型。

  5. LLM模型:用于生成响应和图像描述的模型是llava-hf/llava-1.5--7b-hf。

通过这种方式,我们的向量存储既包含文本信息也包含视觉信息,并且我们可以跨这两种模态进行搜索。

下面我们定义process_document函数:

python 复制代码
def process_document(pdf_path, chunk_size=1000, chunk_overlap=200):
    """
    为多模态RAG处理文档。

    """
    # 创建用于存储提取图像的目录
    image_dir = "extracted_images"
    os.makedirs(image_dir, exist_ok=True)
    
    # 从PDF中提取文本和图像
    text_data, image_paths = extract_content_from_pdf(pdf_path, image_dir)
    
    # 对提取的文本进行分块
    chunked_text = chunk_text(text_data, chunk_size, chunk_overlap)
    
    # 处理提取的图像以生成描述
    image_data = process_images(image_paths)
    
    # 合并所有内容项(文本块和图像描述)
    all_items = chunked_text + image_data
    
    # 提取用于嵌入的内容
    contents = [item["content"] for item in all_items]
    
    # 为所有内容创建嵌入
    print("为所有内容创建嵌入...")
    embeddings = create_embeddings(contents)
    
    # 构建向量存储并添加带有嵌入的项
    vector_store = MultiModalVectorStore()
    vector_store.add_items(all_items, embeddings)
    
    # 准备包含文本块和图像描述数量的文档信息
    doc_info = {
        "text_count": len(chunked_text),
        "image_count": len(image_data),
        "total_items": len(all_items),
    }
    
    # 打印已添加项的摘要
    print(f"已向向量存储添加{len(all_items)}个项({len(chunked_text)}个文本块,{len(image_data)}个图像描述)")
    
    # 返回向量存储和文档信息
    return vector_store, doc_info

这个函数负责图像提取和描述生成,以及MultiModalVectorStore(多模态向量存储)的创建。

我们假设图像描述的效果相当不错(在实际场景中,你需要仔细评估描述的质量)。现在,让我们结合查询来整合所有内容:

ini 复制代码
# 处理文档以创建向量存储。我们为此准备了一个新的PDF
pdf_path = "data/attention_is_all_you_need.pdf"
vector_store, doc_info = process_document(pdf_path)

# 运行多模态RAG流程。这和之前非常相似!
result = query_multimodal_rag(query, vector_store)

# 评估。
evaluation_prompt = f"用户查询:{query}\nAI响应:\n{result['response']}\n真实响应:{reference_answer}\n{evaluate_system_prompt}"
evaluation_response = generate_response(evaluate_system_prompt, evaluation_prompt)
print(evaluation_response.choices[0].message.content)

### 输出
0.79

我们得到了约0.79的分数。

多模态RAG具有很大的潜力,特别是对于那些图像包含关键信息的文档。不过,它并没有超过我们目前看到的其他技术。

CRAG

到目前为止,我们的RAG系统相对被动。它们检索信息并生成响应。但如果检索到的信息质量很差呢?如果信息不相关、不完整甚至相互矛盾呢?Corrective RAG(CRAG,纠错型RAG)直面了这个问题。

CRAG工作流程

CRAG增加了一个关键步骤:评估。在初始检索后,它会检查检索到的文档的相关性。重要的是,它会根据评估结果采取不同的策略:

  • 高相关性:如果检索到的文档质量良好,则按常规流程进行。

  • 低相关性:如果检索到的文档质量很差,则转而使用网络搜索!

  • 中等相关性:如果文档质量一般,则结合文档信息和网络信息。

这种"纠错"机制使CRAG比标准RAG更稳健。它不只是寄希望于最好的结果,而是主动检查并调整。

让我们看看实际如何运作。我们将使用一个名为rag_with_compression的函数来实现。

ini 复制代码
# 运行CRAG
crag_result = rag_with_compression(pdf_path, query, compression_type="selective")

这个单一函数调用完成了很多工作:

  1. 初始检索:像往常一样检索文档。

  2. 相关性评估:为每个文档的查询相关性打分。

  3. 决策:决定是使用文档、进行网络搜索,还是两者结合。

  4. 响应生成:使用选定的知识源生成响应。

和往常一样,进行评估:

python 复制代码
# 评估。
evaluation_prompt = f"用户查询:{query}\nAI响应:\n{crag_result['response']}\n真实响应:{reference_answer}\n{evaluate_system_prompt}"
evaluation_response = generate_response(evaluate_system_prompt, evaluation_prompt)
print(evaluation_response.choices[0].message.content)

### 输出
0.824

我们得到的分数约为0.824。

CRAG具备检测并纠正检索失败的能力,这使其比标准RAG可靠得多。通过在必要时动态切换到网络搜索,它可以处理更广泛的查询,避免陷入不相关或信息不足的困境。这种"自我纠错"能力是迈向更稳健、更可信的RAG系统的重要一步。

结论

所测试的18种RAG技术代表了改善检索质量的多种方法,从简单的分块策略到Adaptive RAG等高级方法。

简单RAG提供了一个基准,更复杂的方法如层次索引(0.84)、Fusion(0.83)和CRAG(0.824)通过解决检索挑战的不同方面,显著优于简单RAG。

自适应RAG凭借根据查询类型智能选择检索策略,成为表现最佳的方法(0.86),这表明具有上下文感知能力的灵活系统能在多样化的信息需求中提供最佳结果。

相关推荐
用户5191495848452 小时前
Three.js实例化技术:高效渲染数千3D对象
人工智能·aigc
AI山脚学长2 小时前
Chrome 插件自动保存,断网崩溃也不怕!99% 表单党不知道的救命神器!
aigc
cos2 小时前
FE Bits Vol.3|CSS attr() 类型化进化,PostCSS 复盘 12 年
前端·css·aigc
逍岚子3 小时前
新闻搜索 MCP Server 开发秘籍:Python - SDK 携手 SerpApi,融入 Trae 不再难
llm·agent·mcp
堆栈future21 小时前
LangGraph实践-构建AI工作流:创建一本大模型应用开发书籍
langchain·llm·aigc
用户5191495848451 天前
利用Linux扩展文件属性隐藏恶意负载的技术分析
人工智能·aigc
用户5191495848451 天前
ADB-MCP:AI驱动的Adobe创意工具控制协议
人工智能·aigc
Mintopia1 天前
AI UI 数据展示:Chart.js / Recharts + AI 总结文本的艺术
前端·javascript·aigc
墨风如雪2 天前
Meta亮剑DINOv3:70亿参数的视觉巨兽,宣告一个时代的来临
aigc