# https://compresr.ai/docs/guides/rag

> Human-readable page: https://compresr.ai/docs/guides/rag

A typical RAG pipeline retrieves top-k chunks, joins them, and stuffs them into the LLM prompt. Compresr adds one step in the middle: take those retrieved chunks, score them against the user's question, and pass only the spans that answer it to the LLM.

This guide covers the pipeline shape, **first-party integrations for LangChain and LlamaIndex**, and a raw vector-DB call for when you're not using either framework.

## Pipeline shape

```
User query
  -> Embed
  -> Vector DB retrieval (top-k chunks)
  -> Compresr (query = user query, context = joined chunks)
  -> LLM call with the compressed context
  -> Answer
```

You keep your existing vector DB, embedding model, and LLM. Compresr only filters the chunks before they hit the LLM. The `query` passed to Compresr is the same string the user typed; the `context` is the concatenation (or list, if using batch) of the retrieved chunks.

## With LangChain: `CompresrExtractor`

The SDK ships a first-party `BaseDocumentCompressor`; drop it straight into `ContextualCompressionRetriever`. It batches all eligible documents in a single Compresr call (up to 100 per batch) and tags each compressed document with `metadata["compresr"] = True`.

```python
import os
from langchain.retrievers import ContextualCompressionRetriever
from compresr.integrations.langchain import CompresrExtractor

extractor = CompresrExtractor(
    api_key=os.environ["COMPRESR_API_KEY"],
    target_compression_ratio=0.5,
    min_tokens=120,            # skip tiny chunks; default 200
    drop_below_min=False,       # set True to filter out empty results
)

compressing_retriever = ContextualCompressionRetriever(
    base_compressor=extractor,
    base_retriever=vectorstore.as_retriever(search_kwargs={"k": 20}),
)

docs = compressing_retriever.invoke("What was Q3 churn?")
```

**TypeScript**

```typescript
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression';
import { CompresrExtractor } from '@compresr/sdk/integrations/langchain';

const extractor = new CompresrExtractor({
  apiKey: process.env.COMPRESR_API_KEY!,
  targetCompressionRatio: 0.5,
  minTokens: 120,
  dropBelowMin: false,
});

const compressingRetriever = new ContextualCompressionRetriever({
  baseCompressor: extractor,
  baseRetriever: vectorstore.asRetriever({ k: 20 }),
});

const docs = await compressingRetriever.invoke('What was Q3 churn?');
```

**cURL**

```bash
# LangChain is a framework; cURL is not. Retrieve, join, then call the batch
# endpoint directly:
curl -X POST https://api.compresr.ai/api/compress/question-specific/batch \
  -H "X-API-Key: $COMPRESR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contexts": ["<chunk 1>", "<chunk 2>", "<chunk 3>"],
    "queries": "What was Q3 churn?",
    "compression_model_name": "{{DEFAULT_MODEL}}",
    "target_compression_ratio": 0.5
  }'
```

See the [LangChain integration page](/docs/framework-integration/langchain) for the full reference, including tool-output middleware (`CompresrToolMiddleware`), history compression (`CompresrSummarizationMiddleware`), and outbound-prompt budgeting (`CompresrPromptMiddleware`) for agent loops.

## With LlamaIndex: `CompresrNodePostprocessor`

`CompresrNodePostprocessor` is a `BaseNodePostprocessor`. Pass it to `as_query_engine` (or any `RetrieverQueryEngine`) and the retrieved nodes are compressed query-aware before synthesis ever sees them.

```python
import os
from llama_index.core import VectorStoreIndex
from compresr.integrations.llamaindex import CompresrNodePostprocessor

postprocessor = CompresrNodePostprocessor(
    api_key=os.environ["COMPRESR_API_KEY"],
    target_compression_ratio=0.5,
    min_tokens=120,            # skip tiny chunks; default 200
)

query_engine = index.as_query_engine(
    similarity_top_k=20,                    # retrieve aggressively
    node_postprocessors=[postprocessor],
)

response = query_engine.query("What was Q3 churn?")
```

**TypeScript**

```typescript
import { CompresrNodePostprocessor } from '@compresr/sdk/integrations/llamaindex';

const postprocessor = new CompresrNodePostprocessor({
  apiKey: process.env.COMPRESR_API_KEY!,
  targetCompressionRatio: 0.5,
  minTokens: 120,
});

const query = 'What was Q3 churn?';
const retriever = index.asRetriever({ similarityTopK: 20 });
const nodes = await retriever.retrieve(query);
const compressed = await postprocessor.postprocessNodes(nodes, { queryStr: query });

// Pass compressed nodes to your synthesizer / query engine.
```

**cURL**

```bash
# LlamaIndex is a framework. With cURL, retrieve outside, join the chunks, and
# call the batch endpoint:
curl -X POST https://api.compresr.ai/api/compress/question-specific/batch \
  -H "X-API-Key: $COMPRESR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contexts": ["<node 1>", "<node 2>"],
    "queries": "What was Q3 churn?",
    "compression_model_name": "{{DEFAULT_MODEL}}",
    "target_compression_ratio": 0.5
  }'
```

The postprocessor batches eligible nodes in a single Compresr call (up to 100 per batch), copies each `NodeWithScore` (originals untouched in the index), and writes new text via `node.set_content()`. `CompresrNodePostprocessor` also accepts `target_token` (an absolute token budget per node — translated to a ratio via `max(avg_tokens / target_token, 1.0)`) if you need to fit inside a hard context window rather than a relative ratio. See the [LlamaIndex integration page](/docs/framework-integration/llamaindex) for the full reference, including tool wrapping and memory-block compression.

## Direct vector DB (no framework)

If you're calling a vector DB directly (pgvector, Pinecone, Qdrant, Chroma, Weaviate), the shape is the same: retrieve, compress, pass to LLM. Use `compress_batch` / `compressBatch` to filter each retrieved chunk independently against the same user question in one call.

```python
# pgvector example.
question = "What was Q3 churn?"
embedding = embed(question)

rows = db.execute(
    "SELECT content FROM docs ORDER BY embedding <-> %s LIMIT 5",
    (embedding,),
).fetchall()

result = compresr.compress_batch(
    contexts=[row["content"] for row in rows],
    queries=question,                        # broadcast to all
    compression_model_name="{{DEFAULT_MODEL}}",
    target_compression_ratio=0.5,
)

joined = "\n\n".join(item.compressed_context for item in result.data.results)

answer = openai.chat.completions.create(
    model="gpt-5",
    messages=[
        {"role": "system", "content": joined},
        {"role": "user", "content": question},
    ],
)
```

**TypeScript**

```typescript
// Pinecone example.
const question = 'What was Q3 churn?';
const embedding = await embed(question);

const { matches } = await pinecone
  .index('docs')
  .query({ vector: embedding, topK: 5, includeMetadata: true });

const result = await compresr.compressBatch({
  contexts: matches.map((m) => m.metadata!.content as string),
  queries: question,
  compressionModelName: '{{DEFAULT_MODEL}}',
  targetCompressionRatio: 0.5,
});

if (!result.data) throw new Error('compression failed');
const joined = result.data.results
  .map((item) => item.compressed_context)
  .join('\n\n');

const answer = await openai.chat.completions.create({
  model: 'gpt-5',
  messages: [
    { role: 'system', content: joined },
    { role: 'user', content: question },
  ],
});
```

**cURL**

```bash
# After you have retrieved your chunks (in another script or tool):
curl -X POST https://api.compresr.ai/api/compress/question-specific/batch \
  -H "X-API-Key: $COMPRESR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contexts": ["<chunk 1>", "<chunk 2>", "<chunk 3>"],
    "queries": "What was Q3 churn?",
    "compression_model_name": "{{DEFAULT_MODEL}}",
    "target_compression_ratio": 0.5
  }'
```

The async twin is `compress_batch_async` (Python) — same signature. For LangChain LCEL / `RunnableSequence` setups, `CompresrExtractor` also implements `acompress_documents`.

### Per-chunk queries with `inputs=`

`contexts=` + `queries=` broadcasts one question across chunks. For hybrid search or multi-hop RAG where each chunk has its own query, use the pair form `inputs=[{"context": ..., "query": ...}, ...]`. Mutually exclusive with `contexts=`/`queries=`.

```python
result = compresr.compress_batch(
    inputs=[
        {"context": chunk_a, "query": "What was Q3 churn?"},
        {"context": chunk_b, "query": "Which segment drove it?"},
    ],
    compression_model_name="{{DEFAULT_MODEL}}",
    target_compression_ratio=0.5,
)
```

**TypeScript**

```typescript
const result = await compresr.compressBatch({
  inputs: [
    { context: chunkA, query: 'What was Q3 churn?' },
    { context: chunkB, query: 'Which segment drove it?' },
  ],
  compressionModelName: '{{DEFAULT_MODEL}}',
  targetCompressionRatio: 0.5,
});
```

**cURL**

```bash
curl -X POST https://api.compresr.ai/api/compress/question-specific/batch \
  -H "X-API-Key: $COMPRESR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "inputs": [
      {"context": "<chunk a>", "query": "What was Q3 churn?"},
      {"context": "<chunk b>", "query": "Which segment drove it?"}
    ],
    "compression_model_name": "{{DEFAULT_MODEL}}",
    "target_compression_ratio": 0.5
  }'
```

## Tips

- **Use the framework-native integration when you can.** `CompresrExtractor` (LangChain) and `CompresrNodePostprocessor` (LlamaIndex) handle batching, node cloning, partitioning by `min_tokens`, and error policy for you. Manual calls work but you reimplement that wiring.
- **Pick a ratio that matches your token budget.** `target_compression_ratio` has two regimes: `0 < r ≤ 1` removes that fraction of tokens (`0.5` = drop half); `r > 1` targets an Nx reduction (`4` = 4x smaller, `60` = 60x). The server hard-caps at `200`. Lighter ratios (around `0.3-0.5`) keep enough surrounding context for citation-style or extractive Q&A. Heavier settings (`0.7+`, or Nx like `4`) work for summarization-style answers. Start at `0.5`, measure on a held-out set, tune from there.
- **Put the compressed text in the system message.** It's reference material the model should ground its answer in, not a turn in the conversation. The user's actual question stays in the user message. See the [LLM provider recipes](/docs/framework-integration/llm-providers) for the exact slot per provider.
- **Filter each chunk independently with batch.** `compress_batch(contexts=[...], queries="single question")` filters each retrieved chunk against the same user question in one HTTP round trip, cheaper than `N` parallel `compress()` calls.

## When NOT to compress

- **Tiny contexts.** If your retrieved context is under ~500 tokens, the API call overhead isn't worth the savings; set a higher `min_tokens` to skip them automatically.
- **Tightly structured retrieval results** (JSON, tool outputs, schemas). Compresr is prose-optimized. For structured payloads you can turn on `heuristic_chunking` and `disable_placeholders` to reduce field-loss risk, or route tool outputs through `CompresrToolMiddleware` instead.

## Variable chunk sizes: `latte_v2` + dynamic ratios

If your corpus has highly variable chunk sizes (short FAQ snippets alongside long PDF pages), a single `target_compression_ratio` over- or under-compresses at the extremes. Set `compression_model_name="latte_v2"` and pass `dynamic=True` with `dynamic_min_ratio` / `dynamic_max_ratio` — the model picks a per-chunk ratio inside that window based on input size. `latte_v2`-only.

## Related

- [LangChain integration](/docs/framework-integration/langchain): full middleware + extractor reference.
- [LlamaIndex integration](/docs/framework-integration/llamaindex): postprocessor, tool wrapper, memory block.
- [Batch compression](/docs/guides/batch): full reference for `compress_batch`.
