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
- VectorStoreRetriever 检索器
VectorStoreRetriever 是 BaseRetriever 的子类,这是一个专门针对向量数据库的基础检索器,在 VectorStoreRetriever 的内部实现了 _get_relevant_documents() 方法,还定义了单独的属性:
- vectorstore:检索器归属的向量数据库。
- search_type:搜索类型。
- 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 应用开发中,可以对检索器配置好相应的选项,如果需要特定信息时传递运行配置即可,否则会运行默认配置信息。
为 聊天机器人架构运行流程图 添加上 动态配置 选项,更新后如下:

在这里插入图片描述
除此之外,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
- 内置检索器与自定义技巧
除了向量数据库检索器,在 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='猫咪在窗台上打盹,看起来非常可爱。')]
对于检索器这个模块来说,正常只需要了解 向量数据库检索器 的相关使用技巧,对于其他的一些检索器知道怎么根据文档使用即可,特别是针对网络/爬虫的检索,目前被更好的方案逐步代替——工具回调