Реалізація Contextual Compression для RAG
Contextual Compression — техніка постобробки видобутих документів, при якій з кожного фрагменту видобувається лише та частина, яка релевантна конкретному запиту. Це зменшує «шум» у контексті LLM, скорочує кількість токенів та підвищує достовірність відповідей.
Проблема без Contextual Compression
Стандартний RAG передає LLM повні фрагменти (512–1024 токени). Типова ситуація: фрагмент містить 600 токенів, з яких 80 токенів дійсно відповідають на питання, решта — нерелевантний контекст. Це:
- Збільшує вартість (більше input tokens)
- Знижує точність (LLM «теряється» у нерелевантному тексті)
- Зменшує effective context window (менше місця для дійсно важливих фрагментів)
LLM-базована Contextual Compression
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import ChatOpenAI
# Компресор на основі LLM
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectorstore.as_retriever(search_kwargs={"k": 8}),
)
compressed_docs = compression_retriever.invoke(
"Який порядок узгодження договорів?"
)
# Кожен документ містить лише релевантний фрагмент
for doc in compressed_docs:
print(len(doc.page_content), "chars (vs оригіналу ~2000)")
Embedding-базований Compressor (EmbeddingsFilter)
Більш швидкий та дешевший варіант — фільтрація за косинусною подібністю:
from langchain.retrievers.document_compressors import EmbeddingsFilter
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
embeddings_filter = EmbeddingsFilter(
embeddings=embeddings,
similarity_threshold=0.76, # Фільтруємо документи нижче порогу
)
filtering_retriever = ContextualCompressionRetriever(
base_compressor=embeddings_filter,
base_retriever=vectorstore.as_retriever(search_kwargs={"k": 8}),
)
Pipeline: Compression + Reranking
from langchain.retrievers.document_compressors import DocumentCompressorPipeline
from langchain_community.document_transformers import EmbeddingsRedundantFilter
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
# Pipeline: EmbeddingFilter → RedundantFilter → LLMExtractor
cross_encoder = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-large")
reranker = CrossEncoderReranker(model=cross_encoder, top_n=3)
compressor_pipeline = DocumentCompressorPipeline(
transformers=[
EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.75),
EmbeddingsRedundantFilter(embeddings=embeddings), # Видаляємо дублікати
reranker, # Ранжуємо залишилися
]
)
pipeline_retriever = ContextualCompressionRetriever(
base_compressor=compressor_pipeline,
base_retriever=vectorstore.as_retriever(search_kwargs={"k": 10}),
)
Практичний кейс: стиснення контексту для техдокументації
Завдання: асистент для технічних мануалів (фрагменти ~800 токенів). Після стиснення середній контекст зменшився з 4800 до 1200 токенів на запит.
| Метрика | Без Compression | З Compression (LLM) |
|---|---|---|
| Input tokens/запит | 5200 | 1450 |
| Faithfulness (RAGAS) | 0.79 | 0.94 |
| Answer Relevancy | 0.81 | 0.89 |
| Вартість (GPT-4o-mini) | 1× | 0.3× |
| Latency | 1.8s | 2.4s (+compression LLM) |
Стиснення зменшило вартість у 3.3× при поліпшенні достовірності на 19%.
Часові рамки
- Реалізація Contextual Compression: 2–3 дні
- Підбір threshold/компресора: 2–3 дні
- Всього: 1 тиждень







