langchain-RAG高级检索策略学习总结

22 阅读8分钟

RAG高级检索策略学习总结

1. 自定义检索器

这是什么

自定义检索器是通过继承LangChain的BaseRetriever基类,实现_get_relevant_documents方法来创建的个性化检索器。这允许开发者根据自己的业务逻辑和需求,自定义文档检索的方式。

这个有什么用

  • 实现特定的检索逻辑,如关键词匹配、模糊搜索等
  • 整合多种数据源的检索能力
  • 添加业务规则和过滤条件
  • 灵活控制返回文档的数量和质量

示例代码

from typing import List
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.documents import Document
from langchain_core.retrievers import BaseRetriever

class CustomRetriever(BaseRetriever):
    documents: List[Document]
    k: int

    def _get_relevant_documents(
        self, query: str, *, run_manager: CallbackManagerForRetrieverRun
    ) -> List[Document]:
        """根据传入的query,获取相关联的文档列表"""
        matching_documents = []
        for document in self.documents:
            if len(matching_documents) >= self.k:
                return matching_documents
            if query.lower() in document.page_content.lower():
                matching_documents.append(document)
        return matching_documents

# 使用示例
documents = [
    Document(page_content="笨笨是一只很喜欢睡觉的猫咪", metadata={"page": 1}),
    Document(page_content="猫咪在窗台上打盹,看起来非常可爱。", metadata={"page": 3}),
]

retriever = CustomRetriever(documents=documents, k=3)
results = retriever.invoke("猫")

2. Multi-Query多查询策略

这是什么

Multi-Query检索器使用大语言模型自动生成多个不同角度的查询,然后基于这些查询进行检索,最后合并所有结果。这是一种提高检索召回率和准确性的策略。

这个有什么用

  • 解决用户查询表述不准确的问题
  • 从多个角度理解同一个问题
  • 提高检索的召回率,找到更多相关文档
  • 减少因查询措辞不当导致的检索失败

示例代码

import weaviate
from langchain_classic.retrievers import MultiQueryRetriever
from langchain_openai import ChatOpenAI
from langchain_weaviate import WeaviateVectorStore
from weaviate.auth import AuthApiKey

# 1. 构建向量数据库与检索器
db = WeaviateVectorStore(
    client=weaviate.connect_to_wcs(
        cluster_url="your_cluster_url",
        auth_credentials=AuthApiKey("your_api_key"),
    ),
    index_name="DatasetDemo",
    text_key="text",
    embedding=QianfanEmbeddingsEndpoint(model="embedding-v1"),
)
retriever = db.as_retriever(search_type="mmr")

# 2. 创建多查询检索器
multi_query_retriever = MultiQueryRetriever.from_llm(
    retriever=retriever,
    llm=ChatOpenAI(model="moonshot-v1-8k", temperature=0),
    include_original=True,  # 是否包含原始查询
)

# 3. 执行检索
docs = multi_query_retriever.invoke("关于LLMOps应用配置的文档有哪些")

3. RAG多查询结果融合策略

这是什么

RAG Fusion是在Multi-Query基础上,使用RRF(Reciprocal Rank Fusion,倒数排名融合)算法来智能合并多个查询结果的策略。它不仅去重,还根据文档在不同查询结果中的排名进行加权排序。

这个有什么用

  • 消除多个查询结果中的重复文档
  • 根据文档的排名位置智能计算相关性得分
  • 融合多个查询的优势,提高最终结果质量
  • 避免简单去重导致的信息丢失

示例代码

from typing import List
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.documents import Document
from langchain_classic.retrievers import MultiQueryRetriever
from langchain_core.load import dumps, loads

class RAGFusionRetriever(MultiQueryRetriever):
    """RAG多查询结果融合策略检索器"""
    k: int = 4  # 返回前k个文档

    def retrieve_documents(
            self, queries: List[str], run_manager: CallbackManagerForRetrieverRun
    ) -> List[List]:
        """重写检索文档函数,返回嵌套列表"""
        documents = []
        for query in queries:
            docs = self.retriever.invoke(
                query, config={"callbacks": run_manager.get_child()}
            )
            documents.append(docs)
        return documents

    def unique_union(self, documents: List[List]) -> List[Document]:
        """使用RRF算法来去重合并对应的文档"""
        fused_result = {}

        # RRF算法:根据排名计算得分
        for docs in documents:
            for rank, doc in enumerate(docs):
                doc_str = dumps(doc)
                if doc_str not in fused_result:
                    fused_result[doc_str] = 0
                # 计算得分:排名越前,得分越高
                fused_result[doc_str] += 1 / (rank + 60)

        # 按得分降序排序,返回前k个
        reranked_results = [
            (loads(doc), score)
            for doc, score in sorted(fused_result.items(), key=lambda x: x[1], reverse=True)
        ]

        return [item[0] for item in reranked_results[:self.k]]

# 使用方式与MultiQueryRetriever相同
rag_fusion_retriever = RAGFusionRetriever.from_llm(
    retriever=retriever,
    llm=ChatOpenAI(model="moonshot-v1-8k", temperature=0),
)
docs = rag_fusion_retriever.invoke("查询内容")

4. 问题分解策略

这是什么

问题分解策略将复杂的用户问题分解成多个独立的子问题,然后对每个子问题分别进行检索和回答,最后将所有子问题的答案整合起来形成最终答案。

这个有什么用

  • 处理复杂、多方面的查询问题
  • 提高对复合问题的回答准确性
  • 逐步构建答案,每个子问题都可以获得独立的上下文
  • 避免单一查询无法覆盖所有相关信息的缺陷

示例代码

from operator import itemgetter
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI

# 1. 构建问题分解链
decomposition_prompt = ChatPromptTemplate.from_template(
    "你是一个乐于助人的AI助理,可以针对一个输入问题生成多个相关的子问题。\n"
    "目标是将输入问题分解成一组可以独立回答的子问题或者子任务。\n"
    "生成与以下问题相关的多个搜索查询:{question}\n"
    "并使用换行符进行分割,输出(3个子问题/子查询):"
)

decomposition_chain = (
    {"question": RunnablePassthrough()}
    | decomposition_prompt
    | ChatOpenAI(model="moonshot-v1-8k", temperature=0)
    | StrOutputParser()
    | (lambda x: x.strip().split("\n"))
)

# 2. 构建迭代问答链
prompt = ChatPromptTemplate.from_template("""这是你需要回答的问题:
---
{question}
---

这是所有可用的背景问题和答案对:
---
{qa_pairs}
---

这是与问题相关的额外背景信息:
---
{context}
---""")

chain = (
    {
        "question": itemgetter("question"),
        "qa_pairs": itemgetter("qa_pairs"),
        "context": itemgetter("question") | retriever,
    }
    | prompt
    | ChatOpenAI(model="moonshot-v1-8k", temperature=0)
    | StrOutputParser()
)

# 3. 循环处理所有子问题
question = "关于LLMOps应用配置的文档有哪些"
sub_questions = decomposition_chain.invoke(question)

qa_pairs = ""
for sub_question in sub_questions:
    answer = chain.invoke({"question": sub_question, "qa_pairs": qa_pairs})
    qa_pair = f"Question: {sub_question}\nAnswer: {answer}\n\n"
    qa_pairs += qa_pair
    print(f"问题: {sub_question}")
    print(f"答案: {answer}")

5. 少量示例模板(Few-Shot Prompting)

这是什么

Few-Shot提示模板通过在提示中提供少量示例来引导大语言模型生成符合预期的输出。它结合了ChatPromptTemplateFewShotChatMessagePromptTemplate,使模型能够通过示例理解任务模式。

这个有什么用

  • 提高模型对特定任务的理解能力
  • 减少对复杂prompt工程的需求
  • 通过示例快速定义输出格式和风格
  • 适用于文本转换、计算、分类等各种任务

示例代码

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
from langchain_openai import ChatOpenAI

# 1. 构建示例模板与示例
example_prompt = ChatPromptTemplate.from_messages([
    ("human", "{question}"),
    ("ai", "{answer}"),
])

examples = [
    {"question": "帮我计算下2+2等于多少?", "answer": "4"},
    {"question": "帮我计算下2+3等于多少?", "answer": "5"},
    {"question": "帮我计算下20*15等于多少?", "answer": "300"},
]

# 2. 构建少量示例提示模板
few_shot_prompt = FewShotChatMessagePromptTemplate(
    example_prompt=example_prompt,
    examples=examples,
)

# 3. 构建最终提示模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个可以计算复杂数学问题的聊天机器人"),
    few_shot_prompt,
    ("human", "{question}"),
])

# 4. 创建链并调用
llm = ChatOpenAI(model="moonshot-v1-8k", temperature=0)
chain = prompt | llm | StrOutputParser()

result = chain.invoke("帮我计算下14*15等于多少")
print(result)

6. 回答回退策略检索器

这是什么

回答回退(Step-Back)检索器是一种策略,它会将用户的具体问题"回退"到一个更通用或更基础的问题,然后用这个回退问题进行检索。这种方法基于这样的理念:有时从更宏观的角度搜索,能找到更全面的相关信息。

这个有什么用

  • 解决过于具体的问题导致检索结果过少的问题
  • 从更宽泛的角度获取背景信息
  • 通过Few-Shot示例学习如何生成合适的回退问题
  • 提高对复杂、抽象问题的检索效果

示例代码

from typing import List
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.documents import Document
from langchain_core.language_models import BaseLanguageModel
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
from langchain_core.retrievers import BaseRetriever
from langchain_core.runnables import RunnablePassthrough

class StepBackRetriever(BaseRetriever):
    """回答回退检索器"""
    retriever: BaseRetriever
    llm: BaseLanguageModel

    def _get_relevant_documents(
            self, query: str, *, run_manager: CallbackManagerForRetrieverRun
    ) -> List[Document]:
        # 1. 构建少量示例提示模板
        examples = [
            {"input": "慕课网上有关于AI应用开发的课程吗?", "output": "慕课网上有哪些课程?"},
            {"input": "慕小课出生在哪个国家?", "output": "慕小课的人生经历是什么样的?"},
            {"input": "司机可以开快车吗?", "output": "司机可以做什么?"},
        ]
        example_prompt = ChatPromptTemplate.from_messages([
            ("human", "{input}"),
            ("ai", "{output}"),
        ])
        few_shot_prompt = FewShotChatMessagePromptTemplate(
            examples=examples,
            example_prompt=example_prompt,
        )

        # 2. 构建生成回退问题的模板
        prompt = ChatPromptTemplate.from_messages([
            ("system",
             "你是一个世界知识的专家。你的任务是回退问题,将问题改述为更一般或者前置问题,这样更容易回答,请参考示例来实现。"),
            few_shot_prompt,
            ("human", "{question}"),
        ])

        # 3. 构建链应用,生成回退问题并执行检索
        chain = (
                {"question": RunnablePassthrough()}
                | prompt
                | self.llm
                | StrOutputParser()
                | self.retriever
        )

        return chain.invoke(query)

# 使用示例
step_back_retriever = StepBackRetriever(
    retriever=retriever,
    llm=ChatOpenAI(model="moonshot-v1-8k", temperature=0),
)

documents = step_back_retriever.invoke("人工智能会让世界发生翻天覆地的变化吗?")

总结

这些RAG高级检索策略各有特点,可以根据实际场景选择或组合使用:

  1. 自定义检索器:适用于需要特殊检索逻辑的场景
  2. Multi-Query:适用于查询表述不准确、需要多角度检索的场景
  3. RAG Fusion:适用于需要智能合并多查询结果的场景
  4. 问题分解:适用于复杂、多方面的复合问题
  5. Few-Shot:适用于需要引导模型理解特定输出格式的场景
  6. 回退策略:适用于问题过于具体导致检索效果不佳的场景

这些策略可以灵活组合,例如在问题分解策略中使用Multi-Query检索器,或在RAG Fusion中应用回退策略,以达到最佳的检索效果。