LangChain快速筑基(带代码)P7-LLM外部数据检索Retrievers

引言

在上一篇中,我们成功使用嵌入模型转换文本为向量并存储至向量数据库chroma中。 并且演示了如何加载已经存在的chroma数据库。对数据库进行数据检索时,使用的chroma实例的similarity_search()方法。 LangChain封装的向量数据库类往往还有一个.as_retriever()方法,可以直接将其转换为一个 VectorStoreRetriever。 为什么需要Retriever,而不是直接使用的chroma实例的similarity_search这样的方法呢?

为了统一标准(解耦),定义与数据源交互的方式。

Retriever

Retriever (更具体地说是 BaseRetriever 接口) 定义了一个标准的与数据源交互的方式,即通过 invoke(query) (或旧版的 get_relevant_documents(query)) 方法来获取相关文档。 VectorStoreRetriever 是最常见的Retriever实现,可以通过search_type参数指定检索策略。search_kwargs参数来配置检索行为。 默认search_type="similarity", 执行标准的相似性搜索,返回与查询最相似的 k 个结果。

python 复制代码
retriever = vector_store.as_retriever(
    search_type="similarity", # 默认
    search_kwargs={
        "k": 5,  # 返回最相似的 5 个文档
        "filter": {"category": "framework_intro"} # 根据元数据过滤
        # "score_threshold": 0.7 # 仅当使用支持阈值的 search_type 时
    }
)
  • k: 指定返回多少个最相关的文档。
  • filter: 一个字典,用于根据文档的元数据进行过滤。这个过滤的语法和支持程度取决于底层的向量数据库 (例如,Chroma, FAISS, Pinecone 的过滤能力和语法可能不同)。LangChain 尝试提供一个通用的 filter 参数,并将其转换为底层数据库的特定查询。
  • score_threshold: (配合 search_type="similarity_score_threshold") 只返回相似度得分高于某个阈值的文档。
  • 特定于向量数据库的参数: 有些向量数据库有自己独特的搜索参数,也可以通过 search_kwargs 传递。

search_type="mmr"(Maximal Marginal Relevance - 最大边际相关性):

  • 目标:在保证相关性的同时,增加结果的多样性。它首先找到与查询最相似的文档,然后迭代地选择那些与查询相关但与已选文档不相似的文档。
  • 参数 (通过 search_kwargs):
    • k: 最终返回的文档数量。
    • fetch_k: 初始阶段获取的文档数量(通常大于 k),MMR算法将从这些文档中进行选择。
    • lambda_mult: 控制多样性的参数 (0 到 1 之间)。0 表示最大化多样性,1 表示最大化相似性。
python 复制代码
retriever_mmr = vector_store.as_retriever(
    search_type="mmr",
    search_kwargs={'k': 3, 'fetch_k': 10, 'lambda_mult': 0.5}
)

Demo

继续使用上一篇创建过的./my_chroma_data

python 复制代码
import os
from langchain_huggingface import HuggingFaceEmbeddings # 使用新的导入
from langchain_chroma import Chroma # 使用新的导入
from langchain_core.documents import Document # 如果需要手动创建文档用于测试

# --- 1. 初始化嵌入模型 (与之前相同) ---
def initialize_embedding_model():
    print("\n--- 初始化嵌入模型 ---")
    model_name = "BAAI/bge-small-zh-v1.5"
    model_kwargs = {'device': 'cuda'} # 或者 'cuda' 如果 GPU 可用
    encode_kwargs = {'normalize_embeddings': True}
    try:
        embeddings = HuggingFaceEmbeddings(
            model_name=model_name,
            model_kwargs=model_kwargs,
            encode_kwargs=encode_kwargs
        )
        print(f"HuggingFace 嵌入模型 '{model_name}' 初始化成功。")
        return embeddings
    except Exception as e:
        print(f"初始化嵌入模型时出错: {e}")
        return None

# --- 2. 加载已持久化的 Chroma 数据库 ---
def load_chroma_vector_store(embedding_model, persist_directory="./my_chroma_data"):
    if not embedding_model:
        print("嵌入模型未初始化,无法加载 Chroma 数据库。")
        return None
    if not os.path.exists(persist_directory) or not os.listdir(persist_directory): # 检查目录是否存在且不为空
        print(f"持久化目录 '{persist_directory}' 不存在或为空,无法加载。")
        print("请先运行创建数据库的脚本。")
        return None

    print(f"\n--- 从 '{persist_directory}' 加载已存在的 Chroma 数据库 ---")
    try:
        vector_store = Chroma(
            persist_directory=persist_directory,
            embedding_function=embedding_model,
            collection_name="my_knowledge_base" # 确保与创建时一致
        )
        print("Chroma 数据库加载成功。")
        return vector_store
    except Exception as e:
        print(f"加载 Chroma 数据库时出错: {e}")
        return None

# --- 3. 从 VectorStore 创建和使用 Retriever ---
def use_retriever_from_vector_store(vector_store_instance):
    if not vector_store_instance:
        print("向量数据库实例未提供,无法创建 Retriever。")
        return

    print("\n--- 从 VectorStore 创建 Retriever ---")

    # 3.1 基本的相似性搜索 Retriever
    # .as_retriever() 是最常用的方法
    # search_kwargs 可以用来配置底层的搜索行为,如 k (返回数量) 和 filter
    retriever_simple = vector_store_instance.as_retriever(
        search_kwargs={"k": 2} # 默认检索2个最相关的文档
    )
    print("已创建基本的相似性搜索 Retriever (k=2)。")

    query1 = "LangChain的价值主张是什么?"
    print(f"\n使用 Retriever 检索查询: '{query1}'")
    # Retriever 使用 .invoke() 方法 (LCEL 标准)
    # 或者旧版的 .get_relevant_documents()
    retrieved_docs1 = retriever_simple.invoke(query1)
    print(f"为查询 '{query1}' 检索到 {len(retrieved_docs1)} 个文档:")
    for i, doc in enumerate(retrieved_docs1):
        print(f"  文档 {i+1} (来自: {doc.metadata.get('source', 'N/A')}): '{doc.page_content[:100]}...'")

    # 3.2 Retriever with Metadata Filtering
    retriever_filtered = vector_store_instance.as_retriever(
        search_kwargs={
            "k": 1,
            "filter": {"category": "llm_provider"} # 只检索 category 为 'llm_provider' 的文档
        }
    )
    print("\n已创建带元数据过滤的 Retriever (k=1, category='llm_provider')。")

    query2 = "DeepSeek公司是做什么的?" # 这个查询更可能匹配 'llm_provider' 类别
    print(f"\n使用过滤 Retriever 检索查询: '{query2}'")
    retrieved_docs2 = retriever_filtered.invoke(query2)
    print(f"为查询 '{query2}' (过滤后) 检索到 {len(retrieved_docs2)} 个文档:")
    for i, doc in enumerate(retrieved_docs2):
        print(f"  文档 {i+1} (元数据: {doc.metadata}): '{doc.page_content[:100]}...'")


    # 3.3 Retriever with MMR (Maximal Marginal Relevance) for diversity
    # MMR 尝试在与查询相关的同时,最大化结果集的多样性。
    # 这需要向量数据库支持 MMR 搜索。Chroma 支持它。
    retriever_mmr = vector_store_instance.as_retriever(
        search_type="mmr", # 指定搜索类型为 MMR
        search_kwargs={
            "k": 3,             # 获取的文档总数
            "fetch_k": 10,      # MMR 算法从多少个初始最相似的文档中选择 (应 >= k)
            "lambda_mult": 0.5  # 控制多样性与相关性的平衡 (0-1之间,0.5是平衡,接近1更看重相关性,接近0更看重多样性)
        }
    )
    print("\n已创建带 MMR 的 Retriever (k=3, fetch_k=10, lambda_mult=0.5)。")
    query3 = "LangChain有什么用?" # 一个比较泛的问题
    print(f"\n使用 MMR Retriever 检索查询: '{query3}'")
    retrieved_docs3 = retriever_mmr.invoke(query3)
    print(f"为查询 '{query3}' (MMR) 检索到 {len(retrieved_docs3)} 个文档:")
    for i, doc in enumerate(retrieved_docs3):
        print(f"  文档 {i+1} (元数据: {doc.metadata}): '{doc.page_content[:100]}...'")

    # 3.4 Retriever with Similarity Score Threshold
    # 只返回相似度得分达到一定阈值的文档。
    # 注意:得分的含义和阈值的设置取决于向量数据库和相似度度量。
    # Chroma 使用距离(默认 L2),所以得分越小越相似。
    # LangChain 的 SimilarityThresholdRetriever 是一个更通用的包装器,
    # 但 VectorStoreRetriever 也可以通过 search_kwargs 来尝试实现。
    # 对于 Chroma,你可能需要直接用 similarity_search_with_score 然后自己过滤,
    # 或者看 as_retriever 是否能直接支持 score_threshold。
    # 更稳妥的方式是使用 `SimilarityThresholdRetriever` 包装一个 `VectorStore`
    from langchain.retrievers import SelfQueryRetriever # 只是为了展示,下面用一个更简单的
    from langchain.retrievers import ContextualCompressionRetriever # 只是为了展示
    # 对于基于得分阈值的检索,通常 VectorStoreRetriever 本身通过 search_kwargs 可能不直接支持 "score_threshold"
    # 我们通常会用 `similarity_search_with_relevance_scores` (如果可用) 或 `similarity_search_with_score` 然后手动过滤
    # 或者使用专门的 Retriever 包装器。

    # 让我们演示一个简单的基于 search_kwargs 的 k 和 filter
    # 如果想用得分阈值,Chroma 的 as_retriever 可能通过 search_type="similarity_score_threshold" 支持,
    # 或者需要更底层的配置。
    # LangChain 的 VectorStoreRetriever 文档说明 search_type 可以是 'similarity_score_threshold'
    try:
        retriever_score_thresh = vector_store_instance.as_retriever(
            search_type="similarity_score_threshold",
            search_kwargs={
                "k": 3,
                "score_threshold": 0.3 # 这里的 score_threshold 意义取决于 Chroma 的配置和相似度函数
                                        # 对于 Chroma 默认的 L2 距离,小的表示更相似。
                                        # 所以 0.3 可能是一个相对严格的(非常相似的)阈值。
                                        # 如果是余弦相似度,则可能需要 0.8 或更高。
                                        # 你需要试验这个值。
            }
        )
        print(f"\n已创建带相似度得分阈值的 Retriever (k=3, score_threshold=0.3,具体效果依赖 Chroma 配置)。")
        query4 = "介绍一下文本分割"
        print(f"\n使用得分阈值 Retriever 检索查询: '{query4}'")
        retrieved_docs4 = retriever_score_thresh.invoke(query4)
        print(f"为查询 '{query4}' (得分阈值) 检索到 {len(retrieved_docs4)} 个文档:")
        for i, doc in enumerate(retrieved_docs4):
            print(f"  文档 {i+1} (元数据: {doc.metadata}): '{doc.page_content[:100]}...'")
    except Exception as e:
        print(f"创建或使用得分阈值 Retriever 时出错 (这可能表示 Chroma 的 LangChain 包装器对该 search_type 的支持有限或配置不当): {e}")


if __name__ == '__main__':
    # 1. 初始化嵌入模型
    embeddings = initialize_embedding_model()

    if embeddings:
        # 2. 加载向量数据库
        # 确保你的 "./my_chroma_data" 目录存在并且包含之前创建的数据
        # 如果是第一次运行,你需要先运行创建和填充数据库的脚本
        chroma_vector_store = load_chroma_vector_store(embeddings, persist_directory="./my_chroma_data")

        if chroma_vector_store:
            # 3. 使用 Retriever
            use_retriever_from_vector_store(chroma_vector_store)
            print("\nRetriever 演示完成。")
            # 下一步就是将这些 Retriever 集成到 RAG 链中,与 LLM 结合起来!
        else:
            print("未能加载向量数据库,无法继续进行 Retriever 演示。")
    else:
        print("嵌入模型初始化失败,无法继续。")
相关推荐
MrGaoGang16 小时前
AI应用开发:LangGraph+MCP
前端·人工智能·langchain
西部荒野子20 小时前
LangChain.js 中的 Runnable 系统
langchain
大尾巴青年1 天前
06 一分钟搞懂langchain的Agent是如何工作的
langchain·llm
敲键盘的小夜猫2 天前
LangChain核心之Runnable接口底层实现
langchain
疯狂的小强呀2 天前
基于langchain的简单RAG的实现
python·langchain·rag检索增强
用户711283928472 天前
LangChain(三) LCEL
人工智能·langchain
啾啾大学习2 天前
LangChain快速筑基(带代码)P3-连续对话Memory
langchain
啾啾大学习2 天前
LangChain快速筑基(带代码)P0-DeepSeek对话与联网搜索
langchain
啾啾大学习2 天前
LangChain快速筑基(带代码)P1-输入控制与输出解析
langchain