多查询(Multi Query)、RAG融合(RAG-Fusion)、分解(Decomposition)、回退(Step Back)、HyDE(Hypothetical Document Embeddings)。
这些方法的目的都是将问题转换成一种更适合用于检索的形式。

如下图所示:
在这里插入图片描述
我将以系列的方式讲述如何使用。

下面我们先介绍多查询(Multi Query)方法

在这里插入图片描述

以下代码的核心功能是 基于 LangChain 构建一个多查询 RAG(检索增强生成)系统,用于从网页中提取医学相关内容,并根据用户问题提供精准答案。

代码流程概览

  1. 爬取网页数据并进行向量化索引

    • 访问指定网页并提取核心内容(如 main_box 里的文本)。
    • 对文本进行切分,使其适应向量检索。
    • 将文本转换为向量并存储到 FAISS 向量数据库中,方便后续检索。
  2. 构造多角度查询(Multi-Query Retrieval)

    • 让大模型(LLM)对用户问题生成 5 个不同的改写版本,增加检索的召回率。
    • 用改写后的问题去向量数据库查询,提高找到相关文档的概率。
  3. RAG(检索增强生成)

    • 从 FAISS 向量数据库中检索出最相关的文本。
    • 让 LLM 结合检索到的内容,回答用户的问题。

第一部分:网页数据爬取和索引

1.1 加载网页内容

import bs4
from langchain_community.document_loaders import WebBaseLoader

loader = WebBaseLoader(
    web_paths=("http://www.chealth.org.cn/mon/drugs/article/MB145014258.html",),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            class_=("main_box")  
        )
    ),
)
blog_docs = loader.load()

📌 解释

  • 这里使用 WebBaseLoaderhttp://www.chealth.org.cn/mon/drugs/article/MB145014258.html 爬取网页内容。
  • bs_kwargs 参数指定了 main_box 这个 HTML 类,表示我们只提取网页中这个部分的文本,过滤掉不相关内容(如广告、导航栏等)。

1.2 文本切分

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=300,  
    chunk_overlap=50  
)

splits = text_splitter.split_documents(blog_docs)

📌 解释

  • 网页可能包含大量文本,直接存储不利于高效检索,因此 将文本切成 300 字的段落,并且设置 50 字的重叠,避免信息丢失。
  • RecursiveCharacterTextSplitter 是 LangChain 提供的 按字符长度递归切分 的工具,适用于中文文本。

1.3 文本向量化 & FAISS 向量数据库存储

from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceBgeEmbeddings

model_name = "BAAI/bge-small-zh-v1.5"
model_kwargs = {"device": "cpu"}
encode_kwargs = {"normalize_embeddings": True}

hf_embeddings = HuggingFaceBgeEmbeddings(
    model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs
)

vectorstore = FAISS.from_documents(documents=splits, 
                                    embedding=hf_embeddings)

retriever = vectorstore.as_retriever()

📌 解释

  • 使用 HuggingFaceBgeEmbeddings(中文向量化模型) 把文本转换成向量表示,方便后续的语义检索。
  • 用 FAISS 构建向量数据库,这样我们可以高效地基于相似度进行搜索。

第二部分:多角度查询(Multi-Query)

2.1 让 LLM 生成多个查询版本

from langchain.prompts import ChatPromptTemplate

template = """你是一个 AI 语言模型助手。
你的任务是生成给定用户问题的五个不同版本,用于从向量数据库中检索相关文档。
通过从多个角度生成用户问题,你的目标是帮助用户克服基于距离的相似度搜索的一些局限性。
请用换行符分隔这些替代问题。 原始问题是: {question}"""

prompt_perspectives = ChatPromptTemplate.from_template(template)

from langchain_groq import ChatGroq
from langchain_core.output_parsers import StrOutputParser

generate_queries = (
    prompt_perspectives 
    | ChatGroq(temperature=0)  
    | StrOutputParser()  
    | (lambda x: x.split("\n"))  
)

generate_queries.invoke("异戊巴比妥的成人常用量是多少?")

📌 解释

  • 这部分代码让 LLM 重新生成 5 个不同版本的问题,帮助我们从多个角度去检索。
  • 例如,如果原始问题是:
    "异戊巴比妥的成人常用量是多少?"
    
    LLM 可能会生成如下问题:
    1. 异戊巴比妥的推荐剂量是多少?
    2. 成年人每天可以服用多少异戊巴比妥?
    3. 异戊巴比妥的安全剂量范围是什么?
    4. 异戊巴比妥的常见用法是什么?
    5. 医生通常如何给患者开异戊巴比妥?
    

2.2 通过多个查询检索文档

from langchain.load import dumps, loads

def get_unique_union(documents: list[list]):
    """ Unique union of retrieved docs """
    flattened_docs = [dumps(doc) for sublist in documents for doc in sublist]
    unique_docs = list(set(flattened_docs))
    return [loads(doc) for doc in unique_docs]

# 运行多查询检索
question = "异戊巴比妥的成人常用量是多少?"
retrieval_chain = generate_queries | retriever.map() | get_unique_union
docs = retrieval_chain.invoke({"question":question})
len(docs)

📌 解释

  • 这里的 retriever.map() 表示 每个生成的问题都会在 FAISS 数据库里执行检索
  • get_unique_union() 作用是 去重,避免重复的文档。

第三部分:RAG(检索增强生成)

from operator import itemgetter
from langchain_core.runnables import RunnablePassthrough

template = """请根据以下提供的上下文回答问题:

上下文:
{context}

问题:
{question}

请基于上述上下文,提供清晰、准确的答案。如果无法从上下文中找到答案,请回答 "无法从上下文中找到相关信息"。
"""

prompt = ChatPromptTemplate.from_template(template)

llm = ChatGroq(temperature=0)

final_rag_chain = (
    {"context": retrieval_chain, 
     "question": itemgetter("question")} 
    | prompt
    | llm
    | StrOutputParser()
)

final_rag_chain.invoke({"question":question})

📌 解释

  • 这部分代码让 LLM 结合检索到的文档内容回答问题
  • retrieval_chain 返回上下文,让 LLM 只根据检索到的文档回答,避免胡编乱造(Hallucination)。

完整流程示例

假设用户问:

"异戊巴比妥的成人常用量是多少?"

系统会:

  1. 爬取网页内容,提取 main_box 里的文本。
  2. 把文本向量化并存储到 FAISS 数据库。
  3. 让 LLM 生成 5 个改写版本的问题,提高召回率。
  4. 用这 5 个问题去数据库检索,获取相关文档。
  5. LLM 读取检索到的文本,并 基于事实回答
    "根据提供的医学文献,异戊巴比妥的成人常用剂量为 100mg,每日 1-2 次。"
    

总结

🚀 这段代码的目标是

  • 多角度查询 提高检索精准度。
  • 结合 LLM + 向量数据库 构建 RAG,确保答案基于可靠来源。
  • 适用于医学、法律等需要精准回答的领域
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐