使用基本的相似性搜索大概能解决你80%的相关检索工作,但对于那些相似性搜索失败的边缘情况该如 何解决呢?这一章节我们将介绍几种检索方法,以及解决检索边缘情况的技巧,让我们一起开始学习 吧!
向量数据库检索
pip install -Uq lark
相似性检索(Similarity Search)
from langchain.vectorstores import Chroma
from langchain.embeddings.openai import OpenAIEmbeddings
persist_directory_chinese = 'docs/chroma/matplotlib/'
embedding = OpenAIEmbeddings()
vectordb_chinese = Chroma(
persist_directory=persist_directory_chinese,
embedding_function=embedding
)
print(vectordb_chinese._collection.count())
texts_chinese = [
"""毒鹅膏菌(Amanita phalloides)具有大型且引人注目的地上(epigeous)子实体
(basidiocarp)""",
"""一种具有大型子实体的蘑菇是毒鹅膏菌(Amanita phalloides)。某些品种全白。""",
"""A. phalloides,又名死亡帽,是已知所有蘑菇中最有毒的一种。""",
]
smalldb_chinese = Chroma.from_texts(texts_chinese, embedding=embedding)
question_chinese = "告诉我关于具有大型子实体的全白色蘑菇的信息"
相似性搜索,设置 k=2 ,只返回两个最相关的文档。
smalldb_chinese.similarity_search(question_chinese, k=2)
解决多样性:最大边际相关性(MMR)
MMR 的基本思想是同时考量查询与文档的相关度,以及文档之间的相似度。相关度确保返回结果对查询 高度相关,相似度则鼓励不同语义的文档被包含进结果集。具体来说,它计算每个候选文档与查询的相 关度,并减去与已经选入结果集的文档的相似度。这样更不相似的文档会有更高的得分。 总之,MMR 是解决检索冗余问题、提供多样性结果的一种简单高效的算法。它平衡了相关性和多样性, 适用于对多样信息需求较强的应用场景
smalldb_chinese.max_marginal_relevance_search(question_chinese,k=2, fetch_k=3)
还记得在上一节中我们介绍了两种向量数据在查询时的失败场景吗?当向量数据库中存在相同的文档 时,而用户的问题又与这些重复的文档高度相关时,向量数据库会出现返回重复文档的情况。现在我们 就可以运用Langchain的 max_marginal_relevance_search 来解决这个问题: 我们首先看看前两个文档,只看前几个字符,可以看到它们是相同的。
这里如果我们使用相似查询,会得到两个重复的结果,读者可以自己尝试一下,这里不再展示。我们可 以使用 MMR 得到不一样的结果。
docs_mmr_chinese = vectordb_chinese.max_marginal_relevance_search(question_chinese,k=3)
print(docs_mmr_chinese[0].page_content[:100])
print(docs_mmr_chinese[1].page_content[:100])
解决特殊性:使用元数据
在上一节课中,关于失败的应用场景我们还提出了一个问题,是询问了关于文档中某一讲的问题,但得 到的结果中也包括了来自其他讲的结果。这是我们所不希望看到的结果,之所以产生这样的结果是因为 当我们向向量数据库提出问题时,数据库并没有很好的理解问题的语义,所以返回的结果不如预期。要 解决这个问题,我们可以通过过滤元数据的方式来实现精准搜索,当前很多向量数据库都支持对 (metadata)的操作:
question_chinese = "他们在第二讲中对SeaTunnel说了些什么?"
# 我们以手动的方式来解决这个问题,我们会指定一个元数据过滤器filter
docs_chinese = vectordb_chinese.similarity_search(
question_chinese,
k=3,
filter={"source":"./data/seatunnel.pdf"}
)
for d in docs_chinese:
print(d.metadata)
解决特殊性:在元数据中使用自查询检索器(LLM辅助检索)
在上例中,我们手动设置了过滤参数 filter 来过滤指定文档。但这种方式不够智能,需要人工指定过滤条 件。如何自动从用户问题中提取过滤信息呢? LangChain提供了SelfQueryRetriever模块,它可以通过语言模型从问题语句中分析出:
- 向量搜索的查询字符串(search term)
- 过滤文档的元数据条件(Filter)
以“除了维基百科,还有哪些健康网站”为例,SelfQueryRetriever可以推断出“除了维基百科”表示需要过滤的 条件,即排除维基百科的文档。
它使用语言模型自动解析语句语义,提取过滤信息,无需手动设置。这种基于理解的元数据过滤更加智能方 便,可以自动处理更复杂的过滤逻辑。 掌握利用语言模型实现自动化过滤的技巧,可以大幅降低构建针对性问答系统的难度。这种自抽取查询的 方法使检索更加智能和动态。
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo
metadata_field_info_chinese = [
AttributeInfo(
name="source",
description="The lecture the chunk is from, should be one of `./data/seatunnel.pdf`, `./data/seatunnel1.pdf`",
type="string",
),
AttributeInfo(
name="page",
description="The page from the lecture",
type="integer",
),
]
document_content_description_chinese = "Matplotlib 课堂讲义"
retriever_chinese = SelfQueryRetriever.from_llm(
llm,
vectordb_chinese,
document_content_description_chinese,
metadata_field_info_chinese,
verbose=True
)
question_chinese = "他们在第二讲中对seatunnel做了些什么?"
for d in docs_chinese:
print(d.metadata)
其他技巧:压缩
在使用向量检索获取相关文档时,直接返回整个文档片段可能带来资源浪费,因为实际相关的只是文档 的一小部分。为改进这一点,LangChain提供了一种“ 压缩”检索机制。其工作原理是,先使用标准向量 检索获得候选文档,然后基于查询语句的语义,使用语言模型压缩这些文档,只保留与问题相关的部分。 例如,对“蘑菇的营养价值”这个查询,检索可能返回整篇有关蘑菇的长文档。经压缩后,只提取文档中与 “营养价值”相关的句子。
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
def pretty_print_docs(docs):
print(f"\n{'-' * 100}\n".join([f"Document {i+1}:\n\n" + d.page_content for i, d in enumerate(docs)]))
compressor = LLMChainExtractor.from_llm(llm) # 压缩器
compression_retriever_chinese = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectordb_chinese.as_retriever()
)
# 对源文档进行压缩
question_chinese = "seatunnel是什么?"
compressed_docs_chinese = compression_retriever_chinese.get_relevant_documents(question_chinese)
pretty_print_docs(compressed_docs_chinese)
结合各种技术
为了去掉结果中的重复文档,我们在从向量数据库创建检索器时,可以将搜索类型设置为 MMR 。
compression_retriever_chinese = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectordb_chinese.as_retriever(search_type = "mmr")
)
question_chinese = "seatunnel是什么?"
compressed_docs_chinese = compression_retriever_chinese.get_relevant_documents(question_chinese)
pretty_print_docs(compressed_docs_chinese)
其他类型的检索
值得注意的是,vetordb 并不是唯一一种检索文档的工具。 LangChain 还提供了其他检索文档的方式, 例如: TF-IDF 或 SVM 。
from langchain.retrievers import SVMRetriever
from langchain.retrievers import TFIDFRetriever
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 加载PDF
loader_chinese = PyPDFLoader("./data/seatunnel.pdf")
pages_chinese = loader_chinese.load()
all_page_text_chinese = [p.page_content for p in pages_chinese]
joined_page_text_chinese = " ".join(all_page_text_chinese)
# 分割文本
text_splitter_chinese = RecursiveCharacterTextSplitter(chunk_size = 1500,chunk_overlap = 150)
splits_chinese = text_splitter_chinese.split_text(joined_page_text_chinese)
# 检索
svm_retriever = SVMRetriever.from_texts(splits_chinese, embedding)
tfidf_retriever = TFIDFRetriever.from_texts(splits_chinese)
question_chinese = "这门课的主要主题是什么?"
docs_svm_chinese = svm_retriever.get_relevant_documents(question_chinese)
print(docs_svm_chinese[0])
question_chinese = "seatunnel是什么?"
docs_tfidf_chinese = tfidf_retriever.get_relevant_documents(question_chinese)
print(docs_tfidf_chinese[0])