在LangChain框架中,检索器(Retriever)承担着从海量数据中快速定位相关信息的关键角色。其核心价值在于将用户输入与知识库中的内容进行匹配,为语言模型的推理提供上下文支持。例如,在问答系统中,检索器会根据用户提问从文档库中筛选出最相关的段落,避免语言模型因缺乏背景信息而生成不准确的回答。这种“检索 - 生成”的模式,有效提升了AI应用的准确性和实用性。
检索器与LangChain中的多个组件紧密协作。它依赖嵌入模型将文本数据转换为向量表示,以便进行语义层面的相似度计算;与向量数据库结合时,检索器通过向量数据库的索引机制实现高效检索;而在与语言模型配合时,检索器输出的相关文档会被整合到提示中,作为语言模型生成回答的参考依据。这种跨组件的协同工作,使得LangChain能够处理复杂的信息检索和知识问答任务。
LangChain检索器的设计旨在实现以下目标:
BaseRetriever
抽象基类LangChain中所有检索器的基类是BaseRetriever
,它定义了检索器必须实现的接口方法:
# langchain/schema.py
from abc import ABC, abstractmethod
from typing import Any, List
class BaseRetriever(ABC):
"""所有检索器的抽象基类"""
@abstractmethod
def get_relevant_documents(self, query: str) -> List[Any]:
"""根据查询获取相关文档
参数:
query: 输入的查询字符串
返回:
相关文档列表,文档类型根据具体实现而定
"""
pass
async def aget_relevant_documents(self, query: str) -> List[Any]:
"""异步获取相关文档(默认同步调用get_relevant_documents)
参数:
query: 输入的查询字符串
返回:
相关文档列表
"""
return self.get_relevant_documents(query)
get_relevant_documents
方法是检索器的核心接口,子类需实现该方法以完成具体的检索逻辑;aget_relevant_documents
方法提供了异步检索的接口,默认通过同步方法实现,方便在异步编程场景中使用。
以VectorStoreRetriever
为例,它继承自BaseRetriever
,实现了基于向量数据库的检索功能:
# langchain/vectorstores/base.py
from langchain.retrievers import BaseRetriever
from langchain.docstore.document import Document
from langchain.vectorstores.base import VectorStore
class VectorStoreRetriever(BaseRetriever):
def __init__(self, vectorstore: VectorStore, search_kwargs: dict = {}):
"""
参数:
vectorstore: 向量数据库实例
search_kwargs: 检索参数,如返回文档数量等
"""
self.vectorstore = vectorstore
self.search_kwargs = search_kwargs
def get_relevant_documents(self, query: str) -> List[Document]:
"""使用向量数据库进行相似性检索
参数:
query: 查询字符串
返回:
相关文档列表
"""
return self.vectorstore.similarity_search(query, **self.search_kwargs)
在这个子类中,通过初始化传入的向量数据库实例和检索参数,在get_relevant_documents
方法里调用向量数据库的similarity_search
方法,实现基于语义相似度的文档检索。
检索器支持通过多种方式进行配置和参数传递。以BM25Retriever
为例,展示其参数设置过程:
# langchain/retrievers/simple.py
from langchain.retrievers import BaseRetriever
from langchain.docstore.document import Document
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
import numpy as np
from typing import List
class BM25Retriever(BaseRetriever):
def __init__(self, texts: List[str], k: int = 4, tokenizer=None):
"""
参数:
texts: 用于构建索引的文本列表
k: 返回的相关文档数量
tokenizer: 分词器(可选)
"""
self.texts = texts
self.k = k
self.tokenizer = tokenizer
self._build_index()
def _build_index(self):
if self.tokenizer is None:
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = [Document(page_content=t) for t in text_splitter.split_text("\n".join(self.texts))]
else:
# 自定义分词逻辑
pass
embeddings = OpenAIEmbeddings()
self.vectorstore = FAISS.from_documents(docs, embeddings)
def get_relevant_documents(self, query: str) -> List[Document]:
"""使用BM25算法进行检索
参数:
query: 查询字符串
返回:
相关文档列表
"""
scores, indices = self.vectorstore.similarity_search_with_score(query, k=self.k)
return [self.vectorstore.docstore.search(i) for i in indices]
在初始化时,用户可以指定文本数据、返回文档数量、分词器等参数,这些参数会影响索引构建和检索过程,体现了LangChain检索器在配置上的灵活性。
LangChain支持多种向量数据库(如Chroma、FAISS、Weaviate等),检索器通过与向量数据库的集成实现高效检索。以Chroma
为例,VectorStoreRetriever
与其集成的过程如下:
# langchain/vectorstores/chroma.py
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.retrievers import VectorStoreRetriever
# 初始化嵌入模型
embeddings = OpenAIEmbeddings(openai_api_key="your_api_key")
# 初始化Chroma向量数据库
vectorstore = Chroma.from_texts(["文本1", "文本2", "文本3"], embeddings)
# 创建检索器
retriever = VectorStoreRetriever(vectorstore=vectorstore, search_kwargs={"k": 2})
在这个过程中,首先通过嵌入模型将文本转换为向量,然后利用Chroma.from_texts
方法将向量和原始文本存储到数据库中,并建立索引。检索器通过传入向量数据库实例和检索参数(如返回文档数量k
)完成初始化。
基于向量数据库的检索器主要通过计算向量之间的相似度(如余弦相似度、欧氏距离)来实现相关文档的检索。以FAISS
为例,其检索逻辑如下:
# langchain/vectorstores/faiss.py
import faiss
import numpy as np
from langchain.docstore.document import Document
from langchain.vectorstores.base import VectorStore
from langchain.embeddings.base import BaseEmbeddings
class FAISS(VectorStore):
def __init__(self, embedding_function: BaseEmbeddings, index: faiss.Index, docstore):
self.embedding_function = embedding_function
self.index = index
self.docstore = docstore
def similarity_search(self, query: str, k: int = 4) -> List[Document]:
"""进行相似性检索
参数:
query: 查询字符串
k: 返回的相关文档数量
返回:
相关文档列表
"""
query_vector = np.array([self.embedding_function.embed_query(query)])
distances, indices = self.index.search(query_vector, k)
return [self.docstore.search(i) for i in indices.flatten()]
在similarity_search
方法中,首先将查询文本转换为向量,然后使用FAISS
的索引对象进行搜索,获取与查询向量最相似的k
个文档的索引,最后从文档存储中取出对应的文档返回。
为了提高检索结果的质量,检索器通常会对结果进行排序和过滤。以VectorStoreRetriever
为例,其默认使用向量数据库的相似度得分进行排序:
class VectorStoreRetriever(BaseRetriever):
def get_relevant_documents(self, query: str) -> List[Document]:
documents = self.vectorstore.similarity_search(query, **self.search_kwargs)
# 基于相似度得分进行排序(向量数据库已默认实现)
return documents
def _filter_documents(self, documents: List[Document], min_score: float = 0.5) -> List[Document]:
"""过滤掉相似度得分低于阈值的文档
参数:
documents: 检索到的文档列表
min_score: 最小相似度得分阈值
返回:
过滤后的文档列表
"""
if hasattr(self.vectorstore, "similarity_search_with_score"):
scores, _ = self.vectorstore.similarity_search_with_score(query, **self.search_kwargs)
return [doc for doc, score in zip(documents, scores) if score >= min_score]
return documents
通过设置相似度得分阈值,检索器可以过滤掉相关性较低的文档,确保返回的结果更符合用户需求。
BM25(Okapi BM25)是一种经典的信息检索算法,LangChain通过BM25Retriever
类实现了该算法:
# langchain/retrievers/simple.py
import math
from typing import List, Dict
from langchain.docstore.document import Document
from langchain.retrievers import BaseRetriever
class BM25Retriever(BaseRetriever):
def __init__(self, texts: List[str], k: int = 4, b: float = 0.75, k1: float = 1.2):
"""
参数:
texts: 文本列表
k: 返回的文档数量
b: 文档长度归一化参数
k1: 词频调节参数
"""
self.texts = texts
self.k = k
self.b = b
self.k1 = k1
self._build_index()
def _build_index(self):
self.documents = [Document(page_content=t) for t in self.texts]
self.term_frequency = [self._calculate_term_frequency(doc.page_content) for doc in self.documents]
self.document_frequency = self._calculate_document_frequency()
self.document_length = [len(doc.page_content.split()) for doc in self.documents]
self.average_document_length = sum(self.document_length) / len(self.document_length)
def _calculate_term_frequency(self, text: str) -> Dict[str, int]:
"""计算文本中每个词的词频"""
words = text.split()
term_freq = {}
for word in words:
term_freq[word] = term_freq.get(word, 0) + 1
return term_freq
def _calculate_document_frequency(self) -> Dict[str, int]:
"""计算每个词在所有文档中的文档频率"""
doc_freq = {}
for doc in self.texts:
words = set(doc.split())
for word in words:
doc_freq[word] = doc_freq.get(word, 0) + 1
return doc_freq
def _score(self, query: str, doc_index: int) -> float:
"""计算查询与文档的BM25得分"""
score = 0
query_terms = query.split()
for term in query_terms:
if term in self.document_frequency:
idf = math.log((len(self.documents) - self.document_frequency[term] + 0.5) / (self.document_frequency[term] + 0.5))
tf = self.term_frequency[doc_index].get(term, 0)
numerator = tf * (self.k1 + 1)
denominator = tf + self.k1 * (1 - self.b + self.b * (self.document_length[doc_index] / self.average_document_length))
score += idf * (numerator / denominator)
return score
def get_relevant_documents(self, query: str) -> List[Document]:
"""使用BM25算法进行检索
参数:
query: 查询字符串
返回:
相关文档列表
"""
scores = [self._score(query, i) for i in range(len(self.documents))]
sorted_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)
return [self.documents[i] for i in sorted_indices[:self.k]]
BM25Retriever
通过构建词频、文档频率等索引,利用BM25公式计算查询与文档的相关性得分,最终返回得分最高的k
个文档。
TF-IDF(词频 - 逆文档频率)也是常用的检索算法,LangChain中可通过TfidfRetriever
实现:
# langchain/retrievers/simple.py
from sklearn.feature_extraction.text import TfidfVectorizer
from langchain.docstore.document import Document
from langchain.retrievers import BaseRetriever
from typing import List
class TfidfRetriever(BaseRetriever):
def __init__(self, texts: List[str], k: int = 4):
"""
参数:
texts: 文本列表
k: 返回的文档数量
"""
self.texts = texts
self.k = k
self._build_index()
def _build_index(self):
self.vectorizer = TfidfVectorizer()
self.tfidf_matrix = self.vectorizer.fit_transform(self.texts)
self.documents = [Document(page_content=t) for t in self.texts]
def get_relevant_documents(self, query: str) -> List[Document]:
"""使用TF-IDF算法进行检索
参数:
query: 查询字符串
返回:
相关文档列表
"""
query_vector = self.vectorizer.transform([query])
similarities = (self.tfidf_matrix * query_vector.T).toarray().flatten()
sorted_indices = similarities.argsort()[::-1]
return [self.documents[i] for i in sorted_indices[:self.k]]
TfidfRetriever
利用sklearn
的TfidfVectorizer
构建TF-IDF矩阵,通过计算查询向量与文档向量的相似度,返回最相关的文档。
在实际应用中,可将传统检索算法与向量检索相结合,以发挥两者的优势。例如,先使用BM25算法进行粗筛,快速过滤掉不相关的文档,再使用向量检索对剩余文档进行精细排序:
class HybridRetriever(BaseRetriever):
def __init__(self, bm25_texts: List[str], vectorstore, bm25_k: int = 10, vector_k: int = 4):
"""
参数:
bm25_texts: 用于BM25算法的文本列表
vectorstore: 向量数据库实例
bm25_k: BM25算法返回的文档数量
vector_k: 向量检索返回的文档数量
"""
self.bm25_retriever = BM25Retriever(bm25_texts, k=bm25_k)
self.vector_retriever = VectorStoreRetriever(vectorstore=vectorstore, search_kwargs={"k": vector_k})
def get_relevant_documents(self, query: str) -> List[Document]:
"""混合检索
参数:
query: 查询字符串
返回:
相关文档列表
"""
bm25_docs = self.bm25_retriever.get_relevant_documents(query)
bm25_texts = [doc.page_content for doc in bm25_docs]
vector_docs = self.vector_retriever.get_relevant_documents("\n".join(bm25_texts))
return vector_docs
这种混合检索策略能够在保证检索准确性的同时,提高检索效率。
当使用多个检索器或多次检索时,需要对结果进行合并与去重。LangChain提供了MergerRetriever
类实现此功能:
# langchain/retrievers/merger.py
from langchain.retrievers import BaseRetriever
from typing import List, Any
class MergerRetriever(BaseRetriever):
def __init__(self, retrievers: List[Base
当使用多个检索器或多次检索时,需要对结果进行合并与去重。LangChain提供了MergerRetriever
类实现此功能:
# langchain/retrievers/merger.py
from langchain.retrievers import BaseRetriever
from typing import List, Any
class MergerRetriever(BaseRetriever):
def __init__(self, retrievers: List[BaseRetriever], merge_strategy: str = "union"):
"""
参数:
retrievers: 检索器列表
merge_strategy: 合并策略,支持"union"(并集)、"intersection"(交集)和"weighted"(加权)
"""
self.retrievers = retrievers
self.merge_strategy = merge_strategy
def get_relevant_documents(self, query: str) -> List[Any]:
"""合并多个检索器的结果
参数:
query: 查询字符串
返回:
合并后的相关文档列表
"""
# 获取所有检索器的结果
all_results = [retriever.get_relevant_documents(query) for retriever in self.retrievers]
if self.merge_strategy == "union":
# 并集策略:合并所有结果并去重
unique_docs = {}
for results in all_results:
for doc in results:
# 使用文档内容作为唯一标识(实际应用中可能需要更健壮的标识)
unique_docs[doc.page_content] = doc
return list(unique_docs.values())
elif self.merge_strategy == "intersection":
# 交集策略:只保留所有检索器都返回的文档
if not all_results:
return []
result_sets = [set(doc.page_content for doc in results) for results in all_results]
common_content = set.intersection(*result_sets)
# 从第一个检索器的结果中获取完整文档对象
return [doc for doc in all_results[0] if doc.page_content in common_content]
elif self.merge_strategy == "weighted":
# 加权策略:根据检索器权重合并结果
doc_scores = {}
for i, results in enumerate(all_results):
weight = 1.0 / len(self.retrievers) # 简单平均权重,实际应用中可自定义
for doc in results:
if doc.page_content not in doc_scores:
doc_scores[doc.page_content] = (doc, 0.0)
score = doc_scores[doc.page_content][1] + weight
doc_scores[doc.page_content] = (doc, score)
# 按得分排序并返回
sorted_docs = sorted(doc_scores.values(), key=lambda x: x[1], reverse=True)
return [doc for doc, _ in sorted_docs]
else:
raise ValueError(f"不支持的合并策略: {self.merge_strategy}")
在MergerRetriever
中,根据不同的合并策略(并集、交集、加权)对多个检索器的结果进行处理,确保结果的唯一性和合理性。
多跳检索允许系统在多个步骤中逐步获取信息,最终回答复杂问题。LangChain通过MultiHopRetriever
类实现此功能:
# langchain/retrievers/multi_hop.py
from langchain.retrievers import BaseRetriever
from langchain.llms import BaseLLM
from langchain.prompts import PromptTemplate
from typing import List, Any
class MultiHopRetriever(BaseRetriever):
def __init__(self, retrievers: List[BaseRetriever], llm: BaseLLM, num_hops: int = 2):
"""
参数:
retrievers: 检索器列表,每个检索器对应一跳
llm: 语言模型,用于生成中间查询
num_hops: 跳数
"""
self.retrievers = retrievers
self.llm = llm
self.num_hops = num_hops
self.prompt_template = PromptTemplate(
input_variables=["query", "context"],
template="根据以下上下文信息,生成一个能够进一步回答问题的查询:\n"
"问题: {query}\n"
"上下文: {context}\n"
"下一跳查询:"
)
def get_relevant_documents(self, query: str) -> List[Any]:
"""执行多跳检索
参数:
query: 初始查询字符串
返回:
最终相关文档列表
"""
current_query = query
all_docs = []
for i in range(self.num_hops):
if i >= len(self.retrievers):
# 如果检索器数量不足,使用最后一个检索器
retriever = self.retrievers[-1]
else:
retriever = self.retrievers[i]
# 执行当前跳的检索
docs = retriever.get_relevant_documents(current_query)
all_docs.extend(docs)
if i < self.num_hops - 1:
# 生成下一跳的查询
context = "\n".join([doc.page_content for doc in docs])
prompt = self.prompt_template.format(query=query, context=context)
next_query = self.llm(prompt).strip()
current_query = next_query
return all_docs
MultiHopRetriever
通过多轮检索和语言模型的交互,逐步深化查询,获取更全面的信息。每一跳检索的结果作为下一跳的上下文,最终整合所有跳的结果。
条件检索允许根据特定条件筛选检索结果,LangChain通过FilterRetriever
类实现此功能:
# langchain/retrievers/filter.py
from langchain.retrievers import BaseRetriever
from langchain.docstore.document import Document
from typing import List, Callable, Any
class FilterRetriever(BaseRetriever):
def __init__(self, retriever: BaseRetriever, filter_fn: Callable[[Document], bool]):
"""
参数:
retriever: 基础检索器
filter_fn: 过滤函数,接受一个文档对象,返回布尔值
"""
self.retriever = retriever
self.filter_fn = filter_fn
def get_relevant_documents(self, query: str) -> List[Document]:
"""执行检索并过滤结果
参数:
query: 查询字符串
返回:
过滤后的相关文档列表
"""
docs = self.retriever.get_relevant_documents(query)
return [doc for doc in docs if self.filter_fn(doc)]
FilterRetriever
通过传入的过滤函数对基础检索器的结果进行筛选,只保留符合条件的文档。例如,可以根据文档的元数据、长度、时间戳等属性进行过滤。
为了提高检索结果的可用性,LangChain提供了SummaryRetriever
类,用于对检索结果进行摘要和提炼:
# langchain/retrievers/summary.py
from langchain.retrievers import BaseRetriever
from langchain.llms import BaseLLM
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from typing import List, Any
class SummaryRetriever(BaseRetriever):
def __init__(self, retriever: BaseRetriever, llm: BaseLLM):
"""
参数:
retriever: 基础检索器
llm: 语言模型,用于生成摘要
"""
self.retriever = retriever
self.llm = llm
self.summary_chain = LLMChain(
llm=llm,
prompt=PromptTemplate(
input_variables=["text"],
template="请对以下文本进行摘要:\n{text}\n\n摘要:"
)
)
def get_relevant_documents(self, query: str) -> List[Any]:
"""执行检索并生成摘要
参数:
query: 查询字符串
返回:
包含摘要的相关文档列表
"""
docs = self.retriever.get_relevant_documents(query)
for doc in docs:
# 为每个文档生成摘要
summary = self.summary_chain.run(text=doc.page_content)
doc.metadata["summary"] = summary
return docs
SummaryRetriever
在基础检索器的结果上,使用语言模型为每个文档生成摘要,并将摘要存储在文档的元数据中,方便用户快速了解文档内容。
查询预处理是提高检索准确性的重要步骤,LangChain提供了多种查询预处理方法。例如,QueryExpansionRetriever
通过语言模型扩展查询:
# langchain/retrievers/query_expansion.py
from langchain.retrievers import BaseRetriever
from langchain.llms import BaseLLM
from langchain.prompts import PromptTemplate
from typing import List, Any
class QueryExpansionRetriever(BaseRetriever):
def __init__(self, retriever: BaseRetriever, llm: BaseLLM):
"""
参数:
retriever: 基础检索器
llm: 语言模型,用于扩展查询
"""
self.retriever = retriever
self.llm = llm
self.expansion_prompt = PromptTemplate(
input_variables=["query"],
template="扩展以下查询,使其更具体但保持原意:\n{query}\n\n扩展后的查询:"
)
def get_relevant_documents(self, query: str) -> List[Any]:
"""扩展查询并执行检索
参数:
query: 原始查询字符串
返回:
相关文档列表
"""
# 扩展查询
expanded_query = self.llm(self.expansion_prompt.format(query=query)).strip()
# 使用扩展后的查询执行检索
return self.retriever.get_relevant_documents(expanded_query)
通过语言模型扩展查询,可以捕捉更多相关信息,提高检索召回率。
对于复杂查询,LangChain提供了QueryDecompositionRetriever
将其分解为多个简单查询:
# langchain/retrievers/query_decomposition.py
from langchain.retrievers import BaseRetriever
from langchain.llms import BaseLLM
from langchain.prompts import PromptTemplate
from typing import List, Any
class QueryDecompositionRetriever(BaseRetriever):
def __init__(self, retriever: BaseRetriever, llm: BaseLLM):
"""
参数:
retriever: 基础检索器
llm: 语言模型,用于分解查询
"""
self.retriever = retriever
self.llm = llm
self.decomposition_prompt = PromptTemplate(
input_variables=["query"],
template="将以下复杂查询分解为多个简单查询:\n{query}\n\n分解后的查询(每行一个):"
)
def get_relevant_documents(self, query: str) -> List[Any]:
"""分解查询并执行检索
参数:
query: 复杂查询字符串
返回:
相关文档列表
"""
# 分解查询
decomposition = self.llm(self.decomposition_prompt.format(query=query)).strip()
sub_queries = decomposition.split("\n")
# 执行每个子查询并合并结果
all_docs = []
for sub_query in sub_queries:
docs = self.retriever.get_relevant_documents(sub_query)
all_docs.extend(docs)
# 去重
unique_docs = {}
for doc in all_docs:
unique_docs[doc.page_content] = doc
return list(unique_docs.values())
通过将复杂查询分解为多个简单查询,可以提高检索的准确性和效率。
查询重写可以调整查询的表达方式,提高与文档的匹配度。LangChain通过QueryRewritingRetriever
实现此功能:
# langchain/retrievers/query_rewriting.py
from langchain.retrievers import BaseRetriever
from langchain.llms import BaseLLM
from langchain.prompts import PromptTemplate
from typing import List, Any
class QueryRewritingRetriever(BaseRetriever):
def __init__(self, retriever: BaseRetriever, llm: BaseLLM):
"""
参数:
retriever: 基础检索器
llm: 语言模型,用于重写查询
"""
self.retriever = retriever
self.llm = llm
self.rewriting_prompt = PromptTemplate(
input_variables=["query"],
template="重写以下查询,使其更适合检索:\n{query}\n\n重写后的查询:"
)
def get_relevant_documents(self, query: str) -> List[Any]:
"""重写查询并执行检索
参数:
query: 原始查询字符串
返回:
相关文档列表
"""
# 重写查询
rewritten_query = self.llm(self.rewriting_prompt.format(query=query)).strip()
# 使用重写后的查询执行检索
return self.retriever.get_relevant_documents(rewritten_query)
查询重写可以处理用户查询中的模糊表述、口语化表达等问题,提高检索的准确性。
为了提高检索效率和准确性,LangChain允许动态调整查询参数。例如,AdaptiveRetriever
会根据查询内容自动调整检索参数:
# langchain/retrievers/adaptive.py
from langchain.retrievers import BaseRetriever
from langchain.docstore.document import Document
from typing import List, Any
class AdaptiveRetriever(BaseRetriever):
def __init__(self, retriever: BaseRetriever, parameter_selector):
"""
参数:
retriever: 基础检索器
parameter_selector: 参数选择器,根据查询返回最佳参数
"""
self.retriever = retriever
self.parameter_selector = parameter_selector
def get_relevant_documents(self, query: str) -> List[Document]:
"""自适应调整参数并执行检索
参数:
query: 查询字符串
返回:
相关文档列表
"""
# 根据查询获取最佳参数
best_params = self.parameter_selector.select_params(query)
# 更新检索器参数
original_params = self.retriever.search_kwargs.copy()
self.retriever.search_kwargs.update(best_params)
try:
# 执行检索
docs = self.retriever.get_relevant_documents(query)
finally:
# 恢复原始参数
self.retriever.search_kwargs = original_params
return docs
AdaptiveRetriever
通过参数选择器根据查询内容动态调整检索参数,如返回文档数量、相似度阈值等,提高检索效果。
为提高检索效率,LangChain实现了缓存机制,避免重复查询。以CachedRetriever
为例:
# langchain/retrievers/cache.py
from langchain.retrievers import BaseRetriever
from langchain.docstore.document import Document
from typing import List, Any, Dict
import hashlib
class CachedRetriever(BaseRetriever):
def __init__(self, retriever: BaseRetriever, cache: Dict = None):
"""
参数:
retriever: 基础检索器
cache: 缓存对象,默认为空字典
"""
self.retriever = retriever
self.cache = cache or {}
def get_relevant_documents(self, query: str) -> List[Document]:
"""执行检索并使用缓存
参数:
query: 查询字符串
返回:
相关文档列表
"""
# 生成查询的哈希值作为缓存键
cache_key = hashlib.sha256(query.encode()).hexdigest()
# 检查缓存
if cache_key in self.cache:
return self.cache[cache_key]
# 执行实际检索
docs = self.retriever.get_relevant_documents(query)
# 缓存结果
self.cache[cache_key] = docs
return docs
CachedRetriever
通过哈希值对查询进行缓存,当相同查询再次出现时,直接从缓存中获取结果,减少了实际检索的开销。
为了提高并发处理能力,LangChain支持异步检索。以VectorStoreRetriever
为例,其异步实现如下:
# langchain/vectorstores/base.py
from langchain.retrievers import BaseRetriever
from langchain.docstore.document import Document
from langchain.vectorstores.base import VectorStore
from typing import List, Any
import asyncio
class VectorStoreRetriever(BaseRetriever):
def __init__(self, vectorstore: VectorStore, search_kwargs: dict = {}):
self.vectorstore = vectorstore
self.search_kwargs = search_kwargs
def get_relevant_documents(self, query: str) -> List[Document]:
return self.vectorstore.similarity_search(query, **self.search_kwargs)
async def aget_relevant_documents(self, query: str) -> List[Document]:
"""异步获取相关文档
参数:
query: 查询字符串
返回:
相关文档列表
"""
# 如果向量存储支持异步操作,直接调用
if hasattr(self.vectorstore, "asimilarity_search"):
return await self.vectorstore.asimilarity_search(query, **self.search_kwargs)
# 否则在异步线程池中执行同步操作
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, self.get_relevant_documents, query)
通过aget_relevant_documents
方法,VectorStoreRetriever
支持异步检索,在处理大量并发查询时能显著提高性能。
为了提高检索效率,LangChain支持批量处理多个查询。以BatchRetriever
为例:
# langchain/retrievers/batch.py
from langchain.retrievers import BaseRetriever
from langchain.docstore.document import Document
from typing import List, Any
import asyncio
class BatchRetriever(BaseRetriever):
def __init__(self, retriever: BaseRetriever, batch_size: int = 10):
"""
参数:
retriever: 基础检索器
batch_size: 批处理大小
"""
self.retriever = retriever
self.batch_size = batch_size
def get_relevant_documents(self, query: str) -> List[Document]:
"""单查询检索(直接调用基础检索器)
参数:
query: 查询字符串
返回:
相关文档列表
"""
return self.retriever.get_relevant_documents(query)
async def aget_relevant_documents(self, query: str) -> List[Document]:
"""单查询异步检索
参数:
query: 查询字符串
返回:
相关文档列表
"""
return await self.retriever.aget_relevant_documents(query)
async def aget_relevant_documents_batch(self, queries: List[str]) -> List[List[Document]]:
"""批量异步检索
参数:
queries: 查询字符串列表
返回:
每个查询对应的相关文档列表
"""
batches = [queries[i:i+self.batch_size] for i in range(0, len(queries), self.batch_size)]
all_results = []
for batch in batches:
# 并行处理批内的查询
tasks = [self.retriever.aget_relevant_documents(query) for query in batch]
batch_results = await asyncio.gather(*tasks)
all_results.extend(batch_results)
return all_results
BatchRetriever
通过aget_relevant_documents_batch
方法支持批量异步检索,将多个查询分成多个批次并行处理,提高了检索效率。
为了提高检索速度,LangChain支持对索引进行优化。例如,在向量数据库中,可以定期重新构建索引:
# langchain/vectorstores/faiss.py
import faiss
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
class OptimizedFAISS(FAISS):
def __init__(self, embedding_function, index, docstore):
super().__init__(embedding_function, index, docstore)
self.optimized = False
def optimize_index(self):
"""优化索引以提高检索速度"""
if not self.optimized:
# 转换为倒排索引以加速检索
if isinstance(self.index, faiss.IndexFlatL2) or isinstance(self.index, faiss.IndexFlatIP):
quantizer = faiss.IndexFlatL2(self.index.d)
nlist = 100 # 聚类中心数量
self.index = faiss.IndexIVFFlat(quantizer, self.index.d, nlist, faiss.METRIC_L2)
self.index.train(self.index.reconstruct_n(0, self.index.ntotal))
self.index.add(self.index.reconstruct_n(0, self.index.ntotal))
self.optimized = True
print("索引优化完成")
def similarity_search(self, query: str, k: int = 4) -> List[Document]:
"""在优化的索引上执行相似性检索"""
if not self.optimized:
self.optimize_index()
return super().similarity_search(query, k)
通过将扁平索引转换为倒排索引,可以显著提高大规模数据的检索速度。
在问答系统中,检索器扮演着关键角色,负责从知识库中获取相关信息。以下是一个简单的问答系统实现:
# langchain/examples/qa_system.py
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from langchain.llms import OpenAI
from langchain.chains import RetrievalQA
from langchain.document_loaders import TextLoader
# 加载文档
loader = TextLoader("example_data.txt")
documents = loader.load()
# 文本分割
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# 创建嵌入模型和向量数据库
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(texts, embeddings)
# 创建检索器
retriever = vectorstore.as_retriever()
# 创建问答链
qa = RetrievalQA.from_chain_type(
llm=OpenAI(),
chain_type="stuff",
retriever=retriever
)
# 回答问题
query = "LangChain检索器的工作原理是什么?"
answer = qa.run(query)
print(f"问题: {query}")
print(f"回答: {answer}")
在这个问答系统中,检索器从知识库中获取与问题相关的文档,然后由语言模型结合这些文档生成回答。
在文档分析与摘要中,检索器可用于获取相关文档片段。以下是一个文档摘要系统的实现:
# langchain/examples/document_summarization.py
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.llms import OpenAI
from langchain.chains.summarize import load_summarize_chain
from langchain.document_loaders import PyPDFLoader
# 加载PDF文档
loader = PyPDFLoader("example_pdf.pdf")
documents = loader.load()
# 文本分割
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
texts = text_splitter.split_documents(documents)
# 创建嵌入模型和向量数据库
embeddings = HuggingFaceEmbeddings()
vectorstore = FAISS.from_documents(texts, embeddings)
# 创建检索器
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# 获取与查询相关的文档
query = "文档的主要论点是什么?"
docs = retriever.get_relevant_documents(query)
# 创建摘要链
llm = OpenAI(temperature=0)
chain = load_summarize_chain(llm, chain_type="map_reduce")
# 生成摘要
summary = chain.run(docs)
print(f"查询: {query}")
print(f"摘要: {summary}")
在这个文档摘要系统中,检索器根据查询获取相关文档片段,然后由摘要链生成这些片段的摘要。
在聊天机器人中,检索器可用于获取历史对话中的相关信息,支持上下文感知的对话。以下是一个简单的聊天机器人实现:
# langchain/examples/chatbot.py
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
from langchain.llms import OpenAI
# 创建嵌入模型和向量数据库(假设已有历史对话)
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_texts(["历史对话1", "历史对话2"], embeddings)
# 创建检索器
retriever = vectorstore.as_retriever()
# 创建记忆
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# 创建对话链
qa = ConversationalRetrievalChain.from_llm(
OpenAI(temperature=0),
retriever,
memory=memory
)
# 聊天循环
while True:
user_input = input("你: ")
if user_input.lower() == "退出":
break
result = qa({"question": user_input})
print(f"机器人: {result['answer']}")
在这个聊天机器人中,检索器从历史对话中获取相关信息,帮助机器人生成更连贯、上下文感知的回答。
检索器还可用于代码搜索和智能编程助手。以下是一个简单的代码搜索实现:
# langchain/examples/code_search.py
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import DirectoryLoader
# 加载代码文件
loader = DirectoryLoader("./code_repo/", glob="**/*.py")
documents = loader.load()
# 文本分割
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# 创建嵌入模型和向量数据库
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(texts, embeddings)
# 创建检索器
retriever = vectorstore.as_retriever()
# 代码搜索
query = "如何实现一个异步HTTP请求?"
docs = retriever.get_relevant_documents(query)
# 显示搜索结果
print(f"搜索: {query}")
for i, doc in enumerate(docs[:3]): # 显示前3个结果
print(f"\n结果 {i+1}:")
print(f"文件: {doc.metadata['source']}")
print(f"内容: {doc.page_content[:200]}...") # 显示前200个字符
在这个代码搜索系统中,检索器可以根据自然语言查询找到相关的代码片段,为开发者提供参考。
LangChain允许开发者通过继承BaseRetriever
类来实现自定义检索器。以下是一个简单的示例:
# langchain/examples/custom_retriever.py
from langchain.retrievers import BaseRetriever
from langchain.docstore.document import Document
from typing import List, Any
import random
class CustomRetriever(BaseRetriever):
def __init__(self, documents: List[Document]):
"""
参数:
documents: 文档列表
"""
self.documents = documents
def get_relevant_documents(self, query: str) -> List[Document]:
"""自定义检索逻辑
参数:
query: 查询字符串
返回:
相关文档列表
"""
# 简单示例:随机返回3个文档
return random.sample(self.documents, min(3, len(self.documents)))
async def aget_relevant_documents(self, query: str) -> List[Document]:
"""异步自定义检索逻辑
参数:
query: 查询字符串
返回:
相关文档列表
"""
# 在异步线程池中执行同步操作
import asyncio
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, self.get_relevant_documents, query)
在实际应用中,开发者可以根据具体需求实现更复杂的检索逻辑,如基于规则的检索、基于机器学习模型的检索等。
LangChain支持通过插件系统集成第三方检索服务。例如,集成SerpAPI进行网络搜索:
# langchain/examples/serpapi_plugin.py
from langchain.retrievers import BaseRetriever
from langchain.docstore.document import Document
from typing import List, Any
import requests
import os
class SerpAPIRetriever(BaseRetriever):
def __init__(self, api_key: str = None, serpapi_params: dict = {}):
"""
参数:
api_key: SerpAPI API密钥
serpapi_params: SerpAPI请求参数
"""
self.api_key = api_key or os.environ.get("SERPAPI_API_KEY")
self.serpapi_params = serpapi_params
def get_relevant_documents(self, query: str) -> List[Document]:
"""使用SerpAPI进行网络搜索
参数:
query: 查询字符串
返回:
搜索结果文档列表
"""
params = {
"q": query,
"api_key": self.api_key,
**self.serpapi_params
}
response = requests.get("https://serpapi.com/search", params=params)
results = response.json()
documents = []
if "organic_results" in results:
for result in results["organic_results"]:
doc = Document(
page_content=result.get("snippet", ""),
metadata={
"title": result.get("title", ""),
"link": result.get("link", ""),
"source": "SerpAPI"
}
)
documents.append(doc)
return documents
通过这种方式,LangChain可以集成各种第三方检索服务,扩展其检索能力。
LangChain支持将多个检索器组合使用,以发挥不同检索器的优势。例如,结合向量检索和关键词检索:
# langchain/examples/composite_retriever.py
from langchain.retrievers import BaseRetriever, MergerRetriever
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.document_loaders import TextLoader
from typing import List, Any
# 加载文档
loader = TextLoader("example_data.txt")
documents = loader.load()
# 文本分割
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# 创建向量检索器
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(texts, embeddings)
vector_retriever = vectorstore.as_retriever()
# 创建关键词检索器
from langchain.retrievers import TFIDFRetriever
texts_for_tfidf = [doc.page_content for doc in texts]
tfidf_retriever = TFIDFRetriever.from_texts(texts_for_tfidf)
# 创建组合检索器
composite_retriever = MergerRetriever(
retrievers=[vector_retriever, tfidf_retriever],
merge_strategy="weighted" # 加权合并策略
)
# 执行检索
query = "LangChain的主要功能是什么?"
docs = composite_retriever.get_relevant_documents(query)
# 显示结果
print(f"查询: {query}")
for i, doc in enumerate(docs[:3]): # 显示前3个结果
print(f"\n结果 {i+1}:")
print(f"内容: {doc.page_content[:200]}...") # 显示前200个字符
通过组合不同类型的检索器,可以提高检索的准确性和召回率。
尽管LangChain检索器已经取得了显著进展,但仍面临一些挑战:
语义理解的局限性:虽然向量检索能够捕捉语义相似性,但对于一些复杂的语义关系和上下文理解仍存在不足。
大规模数据处理:随着数据量的不断增长,检索器在处理大规模数据时面临性能瓶颈,需要更高效的索引和检索算法。
多模态检索支持:目前LangChain检索器主要集中在文本检索,对于图像、音频等多模态数据的检索支持有限。
动态数据适应:对于实时更新的数据,检索器需要能够快速适应数据变化,保持检索性能。
未来,LangChain检索器可能会朝着以下方向发展:
增强语义理解能力:结合更先进的语言模型和知识图谱,提升检索器对复杂语义的理解能力。
分布式检索架构:采用分布式计算和索引技术,处理超大规模数据集,提高检索效率。
多模态检索集成:支持图像、音频、视频等多种模态的检索,实现真正的跨模态信息检索。
自适应检索策略:根据查询内容和用户反馈,自动调整检索策略和参数,提供更个性化的检索服务。
检索与生成的深度融合:进一步整合检索和生成技术,实现更智能、更连贯的信息处理和问答系统。
对于使用和扩展LangChain检索器的开发者,建议关注以下几点:
根据具体应用场景选择合适的检索器和检索策略,必要时组合使用多种检索器。
关注检索器的性能优化,合理设置缓存、批量处理和索引优化等。
利用LangChain的可扩展性,开发自定义检索器或集成第三方检索服务,满足特定需求。
在处理大规模数据时,考虑使用分布式检索架构和高效的向量数据库。
持续关注自然语言处理和信息检索领域的最新进展,将新技术应用到检索器的设计和实现中。
通过不断改进和扩展,LangChain检索器将能够更好地支持各种复杂的AI应用场景,为用户提供更高效、更准确的信息检索服务。