langchain从入门到精通—— 检索器组件深入学习与使用技巧及自定义检索器

46 阅读7分钟

langchain从入门到精通(三十)—— 检索器组件深入学习与使用技巧及自定义检索器

1 . BaseRetriever 检索器基类【AI大模型教程】

在 LangChain 中,传递一段 query 并返回与这段文本相关联文档的组件被称为 检索器,并且 LangChain 为所有检索器设计了一个基类——BaseRetriever,该类继承了 RunnableSerializable,所以该类是一个 Runnable 可运行组件,支持使用 Runnable 组件的所有配置,在 BaseRetriever 下封装了一些通用的方法,类图如下

其中 get_relevance_documents() 方法将在 0.3.0 版本开始被遗弃(老版本非 Runnable 写法),使用检索器的技巧也非常简单,按照特定的规则创建好检索器后(通过 as_retriever() 或者 构造函数),调用 invoke() 方法即可。
并且针对所有 向量数据库,LangChain 都配置了 as_retriever() 方法,便于快捷将向量数据库转换成检索器,不同的检索器传递的参数会有所差异,需要查看源码或者查看文档搭配使用,例如下方是一个向量数据库检索器的使用示例:

import dotenvimport weaviatefrom langchain_core.runnables import ConfigurableFieldfrom langchain_openai import OpenAIEmbeddingsfrom langchain_weaviate import WeaviateVectorStorefrom weaviate.auth import AuthApiKeydotenv.load_dotenv()# 1.构建向量数据库db = WeaviateVectorStore(    client=weaviate.connect_to_wcs(        cluster_url="https://eftofnujtxqcsa0sn272jw.c0.us-west3.gcp.weaviate.cloud",        auth_credentials=AuthApiKey("xxxxxxxxxxxxx"),    ),    index_name="DatasetDemo",    text_key="text",    embedding=OpenAIEmbeddings(model="text-embedding-3-small"),)# 2.转换检索器retriever = db.as_retriever(    search_type="similarity_score_threshold",    search_kwargs={"k": 10, "score_threshold": 0.5},)# 3.执行基础相似性搜索similarity_documents = retriever.invoke("关于应用配置的接口有哪些?")print("相似性搜索: ", similarity_documents)print("内容长度:", len(similarity_documents))

输出内容:

相似性搜索:  [...]内容长度: 10
  1. VectorStoreRetriever 检索器

VectorStoreRetriever 是 BaseRetriever 的子类,这是一个专门针对向量数据库的基础检索器,在 VectorStoreRetriever 的内部实现了 _get_relevant_documents() 方法,还定义了单独的属性:

  1. vectorstore:检索器归属的向量数据库。
  2. search_type:搜索类型。
  3. search_kwargs:搜索参数。
    这些参数均来源于 as_retriever() 或者在实例化类时传递的参数,由于该组件是一个 Runnable 可运行组件,所以可以使用 .configurable_fields() 来修改类内部的参数。
    例如实现在运行时传递 search_type 与 search_kwargs,修改成 mmr 方法进行检索,并且返回 4 条数据, 代码示例:
import dotenvimport weaviatefrom langchain_core.runnables import ConfigurableFieldfrom langchain_openai import OpenAIEmbeddingsfrom langchain_weaviate import WeaviateVectorStorefrom weaviate.auth import AuthApiKeydotenv.load_dotenv()# 1.构建向量数据库db = WeaviateVectorStore(    client=weaviate.connect_to_wcs(        cluster_url="https://eftofnujtxqcsa0sn272jw.c0.us-west3.gcp.weaviate.cloud",        auth_credentials=AuthApiKey("xxxxxxxxxxxxxxxxxxxxxxxxxxxx"),    ),    index_name="DatasetDemo",    text_key="text",    embedding=OpenAIEmbeddings(model="text-embedding-3-small"),)# 2.转换检索器retriever = db.as_retriever(    search_type="similarity_score_threshold",    search_kwargs={"k": 10, "score_threshold": 0.5},).configurable_fields(    search_type=ConfigurableField(id="search_type"),    search_kwargs=ConfigurableField(id="search_kwargs"),)# 3.执行基础相似性搜索,并返回4条数据similarity_documents = retriever.with_config(configurable={    "search_type": "mmr",    "search_kwargs": {"k": 10},}).invoke("关于应用配置的接口有哪些?")print("相似性搜索: ", similarity_documents)print("内容长度:", len(similarity_documents))

输出内容:

相似性搜索:  [...]内容长度: 10

除了使用 .with_config() 传递运行时配置,也可以在执行 .invoke() 函数时传递 config,效果是一模一样的

similarity_documents = retriever.invoke(    "关于应用配置的接口有哪些?",     config={"configurable": {        "search_type": "mmr",        "search_kwargs": {"k": 8},    }})
```在 LCEL 表达式构建的链应用中,`.with_config()`可以通过链一起传递,或者是调用 .invoke() 函数是传递 config+configurable 属性完成对配置信息的替换,所以在 RAG 应用开发中,可以对检索器配置好相应的选项,如果需要特定信息时传递运行配置即可,否则会运行默认配置信息。  
为 聊天机器人架构运行流程图 添加上 动态配置 选项,更新后如下:  
![](http://cdn.zhipoai.cn/a3f99e59.jpg)

在这里插入图片描述

除此之外,Runnable 可运行组件的其他配置也可以轻松配置使用:  
1. 使用 .configurable\_alternatives() 来实现对 向量数据库检索器 的替换;  
2. 使用 .with\_retry() 来实现对出现错误的重试;  
3. 使用 .with\_fallbacks() 来实现对出现错误时的回退;  
4. 使用 .with\_listeners() 来实现对执行生命周期的监听;  
5. 使用 .bind() 来实现动态传递绑定时参数(不过对于 weaviate 向量数据库来说,目前没有效果)。  
.bind() 函数会将相应的数据传递给 .invoke() 的 kwargs 参数,但是在 weaviate 的 invoke() 中并没有用到 kwargs 参数,所以 .bind() 函数不会起到效果,要想传递搜索参数给 weaviate 的 .invoke() 方法只能通过 search\_kwargs 进行传递。

核心代码

```plaintext
def _get_relevant_documents(    self, query: str, *, run_manager: CallbackManagerForRetrieverRun) -> List[Document]:    if self.search_type == "similarity":        docs = self.vectorstore.similarity_search(query, **self.search_kwargs)    elif self.search_type == "similarity_score_threshold":        docs_and_similarities = (            self.vectorstore.similarity_search_with_relevance_scores(                query, **self.search_kwargs            )        )        docs = [doc for doc, _ in docs_and_similarities]    elif self.search_type == "mmr":        docs = self.vectorstore.max_marginal_relevance_search(            query, **self.search_kwargs        )    else:        raise ValueError(f"search_type of {self.search_type} not allowed.")    return docs
  1. 内置检索器与自定义技巧

除了向量数据库检索器,在 LangChain 内部还集成了大量的第三方检索器,这些检索器功能众多,涵盖:维基百科搜索、Weaviate 混合搜索、Zep 检索器、ES 搜索 等,链接:imooc-langchain.shortvar.com/docs/integr…
不过由于这些内置检索器开发的时间较早,绝大部分都是在 Runnable 与 LCEL 表达式出来之前发布的,部分检索器可能并不是 Runnable 可运行组件,使用方法也有差异,使用前需要查阅文档进行使用。
如果内置的检索器没法完成需求,可以考虑使用自定义检索器,比方接入一个可以快速检索 百度网站,或者 慕课网 课程的检索器。
在 LangChain 中实现自定义检索器的技巧其实非常简单,只需要继承 BaseRetriever 类,然后实现 _get_relevant_documents() 方法即可,从 query 到 list[document] 的逻辑全部都在这个函数内部实现,异步的方法也可以不需要实现,底层会委托同步方法来执行。
例如构建一个最基础的 通过内容进行查找 的自定义检索器,示例如下:

from typing import Listfrom langchain_core.callbacks import CallbackManagerForRetrieverRunfrom langchain_core.documents import Documentfrom langchain_core.retrievers import BaseRetrieverclass 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# 1.定义预设文档documents = [    Document(page_content="笨笨是一只很喜欢睡觉的猫咪", metadata={"page": 1}),    Document(page_content="我喜欢在夜晚听音乐,这让我感到放松。", metadata={"page": 2}),    Document(page_content="猫咪在窗台上打盹,看起来非常可爱。", metadata={"page": 3}),    Document(page_content="学习新技能是每个人都应该追求的目标。", metadata={"page": 4}),    Document(page_content="我最喜欢的食物是意大利面,尤其是番茄酱的那种。", metadata={"page": 5}),    Document(page_content="昨晚我做了一个奇怪的梦,梦见自己在太空飞行。", metadata={"page": 6}),    Document(page_content="我的手机突然关机了,让我有些焦虑。", metadata={"page": 7}),    Document(page_content="阅读是我每天都会做的事情,我觉得很充实。", metadata={"page": 8}),    Document(page_content="他们一起计划了一次周末的野餐,希望天气能好。", metadata={"page": 9}),    Document(page_content="我的狗喜欢追逐球,看起来非常开心。", metadata={"page": 10}),]# 2.创建检索器retriever = CustomRetriever(documents=documents, k=3)# 3.调用检索器获取搜索结果并打印retriever_documents = retriever.invoke("猫")print(retriever_documents)print(len(retriever_documents))

输出内容

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

对于检索器这个模块来说,正常只需要了解 向量数据库检索器 的相关使用技巧,对于其他的一些检索器知道怎么根据文档使用即可,特别是针对网络/爬虫的检索,目前被更好的方案逐步代替——工具回调