核心模块(Modules)
数据连接(DataConnection)
检索器(Retrievers)
自定义检索器(CustomRetriever)

自定义检索器

概述

许多 LLM 应用涉及使用 Retriever 从外部数据源检索信息。

检索器负责针对给定的用户 query 检索一列相关的 Document。

检索到的文档通常会被格式化为提示,然后馈送到 LLM 中,让 LLM 使用其中的信息生成适当的响应(例如,基于知识库回答用户问题)。

接口

要创建你自己的检索器,你需要扩展 BaseRetriever 类并实现以下方法:

方法描述必需/可选
_get_relevant_documents获取与查询相关的文档。必需
_aget_relevant_documents实现以提供原生异步支持。可选

_get_relevant_documents 内部的逻辑可以涉及对数据库的任意调用,或使用 requests 对网络的调用。

提示:通过继承 BaseRetriever,你的检索器会自动成为 LangChain 的 Runnable,并开箱即用地获得标准的 Runnable 功能!

信息:你可以使用 RunnableLambda 或 RunnableGenerator 来实现检索器。将检索器实现为 BaseRetriever 而非 RunnableLambda(一个自定义的 runnable 函数)的主要好处在于,BaseRetriever 是 LangChain 中众所周知的实体,因此一些监控工具可能会为检索器实现专门的行为。另一个区别是,在某些 API 中,BaseRetriever 的行为会与 RunnableLambda 略有不同;例如,在 astream_events API 中,start 事件将是 on_retriever_start 而不是 on_chain_start。

示例

让我们实现一个玩具检索器,它返回所有文本中包含用户查询文本的文档。

from typing import List
 
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.documents import Document
from langchain_core.retrievers import BaseRetriever
 
 
class ToyRetriever(BaseRetriever):
    """A toy retriever that contains the top k documents that contain the user query.
 
    This retriever only implements the sync method _get_relevant_documents.
 
    If the retriever were to involve file access or network access, it could benefit
    from a native async implementation of `_aget_relevant_documents`.
 
    As usual, with Runnables, there's a default async implementation that's provided
    that delegates to the sync implementation running on another thread.
    """
 
    documents: List[Document]
    """List of documents to retrieve from."""
    k: int
    """Number of top results to return"""
 
    def _get_relevant_documents(
        self, query: str, *, run_manager: CallbackManagerForRetrieverRun
    ) -> List[Document]:
        """Sync implementations for retriever."""
        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
 
    # Optional: Provide a more efficient native implementation by overriding
    # _aget_relevant_documents
    # async def _aget_relevant_documents(
    #     self, query: str, *, run_manager: AsyncCallbackManagerForRetrieverRun
    # ) -> List [Document]:
    #     "" " Asynchronously get documents relevant to a query.
 
    #     Args:
    #         query: String to find relevant documents for
    #         run_manager: The callbacks handler to use
 
    #     Returns:
    #         List of relevant documents
    #     "" "

测试 🧪

documents = [
    Document(
        page_content="Dogs are great companions, known for their loyalty and friendliness.",
        metadata={"type": "dog", "trait": "loyalty"},
    ),
    Document(
        page_content="Cats are independent pets that often enjoy their own space.",
        metadata={"type": "cat", "trait": "independence"},
    ),
    Document(
        page_content="Goldfish are popular pets for beginners, requiring relatively simple care.",
        metadata={"type": "fish", "trait": "low maintenance"},
    ),
    Document(
        page_content="Parrots are intelligent birds capable of mimicking human speech.",
        metadata={"type": "bird", "trait": "intelligence"},
    ),
    Document(
        page_content="Rabbits are social animals that need plenty of space to hop around.",
        metadata={"type": "rabbit", "trait": "social"},
    ),
]
retriever = ToyRetriever(documents=documents, k=3)
retriever.invoke("that")
[Document(page_content='Cats are independent pets that often enjoy their own space.', metadata={'type': 'cat', 'trait': 'independence'}),
 Document(page_content='Rabbits are social animals that need plenty of space to hop around.', metadata={'type': 'rabbit', 'trait': 'social'})]

它是一个 runnable,因此会受益于标准的 Runnable 接口!🤩

await retriever.ainvoke("that")
[Document(page_content='Cats are independent pets that often enjoy their own space.', metadata={'type': 'cat', 'trait': 'independence'}),
 Document(page_content='Rabbits are social animals that need plenty of space to hop around.', metadata={'type': 'rabbit', 'trait': 'social'})]
retriever.batch(["dog", "cat"])
[[Document(page_content='Dogs are great companions, known for their loyalty and friendliness.', metadata={'type': 'dog', 'trait': 'loyalty'})],
 [Document(page_content='Cats are independent pets that often enjoy their own space.', metadata={'type': 'cat', 'trait': 'independence'})]]
async for event in retriever.astream_events("bar", version="v1"):
    print(event)
{'event': 'on_retriever_start', 'run_id': 'f96f268d-8383-4921-b175-ca583924d9ff', 'name': 'ToyRetriever', 'tags': [], 'metadata': {}, 'data': {'input': 'bar'}}
{'event': 'on_retriever_stream', 'run_id': 'f96f268d-8383-4921-b175-ca583924d9ff', 'tags': [], 'metadata': {}, 'name': 'ToyRetriever', 'data': {'chunk': []}}
{'event': 'on_retriever_end', 'name': 'ToyRetriever', 'run_id': 'f96f268d-8383-4921-b175-ca583924d9ff', 'tags': [], 'metadata': {}, 'data': {'output': []}}

贡献

我们非常感谢有趣的检索器贡献!

这里有一个检查清单,帮助确保你的贡献被添加到 LangChain:

文档:

  • 检索器为所有初始化参数包含 doc-string,因为这些会显示在 API 参考 (opens in a new tab) 中。
  • 模型的类 doc-string 包含指向检索器所使用的任何相关 API 的链接(例如,如果检索器从 wikipedia 检索,最好链接到 wikipedia API!)

测试:

  • 添加单元测试或集成测试,以验证 invoke 和 ainvoke 能正常工作。

优化:

如果检索器连接到外部数据源(例如 API 或文件),它几乎肯定会受益于原生异步优化!

  • 提供 _aget_relevant_documents 的原生异步实现(供 ainvoke 使用)