Реализация Contextual Compression для RAG
Contextual Compression — техника постобработки retrieved документов, при которой из каждого чанка извлекается только та часть, которая релевантна конкретному запросу. Это снижает «шум» в контексте LLM, сокращает количество токенов и повышает faithfulness ответов.
Проблема без Contextual Compression
Стандартный RAG передаёт LLM полные чанки (512–1024 токена). Типичная ситуация: чанк содержит 600 токенов, из которых 80 токенов действительно отвечают на вопрос, остальные — нерелевантный контекст. Это:
- Увеличивает стоимость (больше input tokens)
- Снижает точность (LLM «теряется» в нерелевантном тексте)
- Уменьшает effective context window (меньше места для действительно важных чанков)
LLM-based 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-based 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
# Пайплайн: 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 токенов). После compression средний контекст уменьшился с 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.8с | 2.4с (+compression LLM) |
Сжатие снизило стоимость в 3.3× при росте faithfulness на 19%.
Сроки
- Реализация Contextual Compression: 2–3 дня
- Подбор threshold/компрессора: 2–3 дня
- Итого: 1 неделя







