在现代信息检索任务中,经常会遇到这样的问题:在构建文档存储系统时,我们难以预见未来可能面对的具体查询。这可能导致与查询相关的信息淹没在冗长而不相关的文本中。如果将整个文档直接传递给应用程序,不仅会导致较高的调用成本(如LLM调用费用),还可能降低响应质量。为了解决这一问题,上下文压缩(Contextual Compression)成为了一种有效的解决方案。
本文将介绍上下文压缩技术的原理以及如何实现它,并结合代码示例进行详细讲解,帮助开发者构建高效的检索系统。
一、技术背景介绍
上下文压缩的核心思想是在文档检索后,对结果文档进行基于查询上下文的压缩处理。其目标是:
- 减少文档内容冗余,只保留与查询相关的内容。
- 过滤不相关的文档,避免返回无关的信息。
实现上下文压缩需要以下两部分:
- 基础检索器(Base Retriever):负责初步检索文档。
- 文档压缩器(Document Compressor):对初始检索结果进行基于查询的内容压缩或文档过滤。
通过上下文压缩,可以显著提升检索系统的性能和响应质量。
二、核心原理解析
上下文压缩通过以下步骤实现:
- 输入查询并调用基础检索器,返回初步检索到的文档列表。
- 将这些文档传递给文档压缩器,基于查询上下文对文档内容进行精简或筛选。
- 返回压缩后的文档集合。
文档压缩器可以包含以下几种实现:
- 内容压缩:对文档内容进行精简,只保留与查询相关的部分。
- 文档过滤:直接移除与查询无关的文档。
- 嵌入向量过滤:基于嵌入相似度筛选文档。
三、代码实现演示
以下代码示例展示了如何使用ContextualCompressionRetriever
来实现上下文压缩。
示例1:使用基础检索器对文档进行初步检索
我们首先初始化一个基本的向量存储检索器,并加载一个示例文档 (“2023年国情咨文”)。
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter
# 加载文档并处理为小块
documents = TextLoader("state_of_the_union.txt").load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# 创建基础检索器
retriever = FAISS.from_documents(texts, OpenAIEmbeddings()).as_retriever()
# 初步检索结果
docs = retriever.invoke("What did the president say about Ketanji Brown Jackson")
for i, doc in enumerate(docs):
print(f"Document {i + 1}:\n{doc.page_content}\n{'-' * 50}")
在这个基础检索中,可能会发现返回的文档中包含了大量与查询无关的内容。
示例2:添加上下文压缩以精简文档内容
我们将使用ContextualCompressionRetriever
,并通过一个LLMChainExtractor
来仅提取与查询相关的信息。
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import OpenAI
# 创建文档压缩器
llm = OpenAI(temperature=0)
compressor = LLMChainExtractor.from_llm(llm)
# 创建上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor, base_retriever=retriever
)
# 压缩后的检索
compressed_docs = compression_retriever.invoke("What did the president say about Ketanji Jackson Brown")
for i, doc in enumerate(compressed_docs):
print(f"Compressed Document {i + 1}:\n{doc.page_content}\n{'-' * 50}")
输出仅包含与Ketanji Brown Jackson
相关的内容,大大提升了信息精准度。
示例3:使用嵌入过滤以提高检索效率
为了减少LLM调用带来的计算开销,可以使用嵌入向量相似度进行快速过滤。
from langchain.retrievers.document_compressors import EmbeddingsFilter
# 嵌入过滤器
embeddings = OpenAIEmbeddings()
embeddings_filter = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.76)
# 创建上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
base_compressor=embeddings_filter, base_retriever=retriever
)
# 基于嵌入过滤的压缩检索
compressed_docs = compression_retriever.invoke("What did the president say about Ketanji Jackson Brown")
for i, doc in enumerate(compressed_docs):
print(f"Compressed Document {i + 1}:\n{doc.page_content}\n{'-' * 50}")
这种方法执行速度更快,并减少了不必要的LLM调用。
四、应用场景分析
上下文压缩技术在以下场景中尤为适用:
- 长文档检索:如法律文件、研究论文等的查询。
- LLM成本优化:减少LLM调用的上下文传递开销。
- 高噪声数据检索:在数据存储中包含大量无关文档时,提升检索准确率。
五、实践建议
- 选择合适的压缩器:根据具体场景选择适配的文档压缩器,LLM压缩适用于高精度场景,而嵌入过滤则更高效。
- 调优过滤参数:如嵌入相似度阈值,根据查询需要动态调整。
- 管道组合:使用
DocumentCompressorPipeline
将多种压缩器组合以兼顾性能与准确度。
通过上下文压缩技术,可以显著提升检索系统的效率和响应质量。如果在实现过程中遇到问题,欢迎在评论区留言交流!