# https://compresr.ai/docs/framework-integration/llamaindex

> Human-readable page: https://compresr.ai/docs/framework-integration/llamaindex

The Compresr SDK ships first-party LlamaIndex integrations under `compresr.integrations.llamaindex` (Python) and `@compresr/sdk/integrations/llamaindex` (TypeScript). They cover the three places LlamaIndex users typically burn tokens: retrieved nodes feeding a query engine, verbose `FunctionTool` outputs in an agent, and long-running chat history under the Memory API. Every class is a drop-in subclass of the canonical LlamaIndex base, so they slot into existing pipelines without touching the rest of your graph.

## 1. Install

```bash
pip install "compresr[llamaindex]"
# CompresrMemoryBlock needs llama-index-core>=0.12; the extras only pin >=0.11,
# so a fresh install may land on 0.11.x and the memory import will fail —
# bump llama-index-core>=0.12 yourself if so.
export COMPRESR_API_KEY="cmp_..."
```

**TypeScript**

```bash
npm install @compresr/sdk llamaindex
export COMPRESR_API_KEY="cmp_..."
```

## 2. Compress retrieved nodes (query engines)

`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

index = VectorStoreIndex.from_documents(docs)

postprocessor = CompresrNodePostprocessor(
    api_key=os.environ["COMPRESR_API_KEY"],
    target_compression_ratio=4,      # 4× compression, keep ~25%
    min_tokens=120,                   # skip nodes shorter than this
)

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

response = query_engine.query("What changed in the Q3 forecast?")
```

**TypeScript**

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

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

// Apply to retrieved nodes — either inside a custom query engine or directly:
const out = await postprocessor.postprocessNodes(nodes, { queryStr: 'What changed in the Q3 forecast?' });
// queryStr (camelCase) or query_str (snake_case) both accepted on the bundle.
```

The postprocessor copies each `NodeWithScore` (originals are untouched in the index), partitions by `min_tokens`, batches the eligible nodes into slices of 100 and issues one Compresr call per slice, and writes new text on the copies via `node.set_content()` (falling back to `node.text =` and finally to `metadata["compresr_compressed"]` with a warning).

### Options

| Python | TypeScript | Default | Notes |
|---|---|---|---|
| `compression_model` | `compressionModel` | `"{{SDK_DEFAULT_MODEL}}"` | Default `"{{SDK_DEFAULT_MODEL}}"`. |
| `target_compression_ratio` | `targetCompressionRatio` | `0.5` | `0 < r ≤ 1` removal fraction; `r > 1` Nx target. |
| `target_token` | `targetToken` | n/a | Alternative: absolute output budget per node. Overrides ratio (`ratio = max(avg_chunk_tokens / target_token, 1.0)`). When `target_token` > current tokens the ratio pins to `1.0` (no compression is applied). |
| `min_tokens` | `minTokens` | `200` | Skip nodes shorter than this. Estimator uses `tiktoken` when available; otherwise falls back to `chars/4`. |
| `coarse` | `coarse` | `None` | When `None`, defers to backend default (paragraph-level). |
| `query` | `query` | n/a | Override the query string (otherwise pulled from `QueryBundle.query_str`). |
| `on_error` | `onError` | `"passthrough"` | Fail-open by default. |
| `api_key` / `base_url` / `client` | `apiKey` / `baseUrl` / `client` | n/a | Standard auth knobs. |

If the query can't be resolved, the postprocessor logs a warning and passes nodes through unchanged; `latte_v1` requires a query (`latte_v2` treats it as optional).

## 3. Compress an agent tool's output

`wrap_tool_with_compresr` / `wrapToolWithCompresr` takes a `FunctionTool` (or tool-like duck type in TypeScript) and returns a new one whose return value is compressed transparently. The Python version preserves the original tool's `name`, `description`, `fn_schema`, and `return_direct` via `FunctionTool.from_defaults(...)`.

```python
import os
from llama_index.core.tools import FunctionTool
from compresr.integrations.llamaindex import wrap_tool_with_compresr

def fetch_article(url: str) -> str:
    """Fetch the article body at the given URL."""
    ...

tool = FunctionTool.from_defaults(fn=fetch_article)

compressed_tool = wrap_tool_with_compresr(
    tool,
    api_key=os.environ["COMPRESR_API_KEY"],
    target_compression_ratio=4,
    query_arg="url",   # or query_extractor=lambda kwargs: kwargs["topic"]
    min_tokens=200,
)

# Plug compressed_tool into any LlamaIndex agent that takes a list of tools.
```

**TypeScript**

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

const wikiTool = {
  metadata: { name: 'wiki_lookup', description: 'Wikipedia lookup' },
  async call({ query }: { query: string }) {
    return fetchWikipedia(query);
  },
};

const wrapped = wrapToolWithCompresr(wikiTool, {
  apiKey: process.env.COMPRESR_API_KEY!,
  targetCompressionRatio: 4,
  queryArg: 'query',
  minTokens: 200,
});
```

If the wrapped function returns anything other than a string, it's passed through unchanged. Python's wrapper also automatically wires an async branch when the source tool exposes `async_fn`. The TypeScript wrapper currently overrides only `.call` — tools that expose `.acall` bypass compression on the async path.

Query resolution: static `query` wins if set; otherwise `query_extractor(args)` is called if provided; otherwise, and only if you set neither, the wrapper consults `args[query_arg]` when `query_arg` is set (strict: if the named key is missing, the resolver returns `None` and does NOT fall back), or smart-picks from common keys (`query`, `question`, `search_query`, `q`, `prompt`, `input`, `text`) when `query_arg` is unset.

## 4. Compress chat history (Memory API)

`CompresrMemoryBlock` is a `BaseMemoryBlock[str]`; register it on `Memory.from_defaults` and the long-running buffer is compressed via Compresr when the memory layer needs to free tokens.

> **Python and TypeScript compress at different moments**
> Python compresses inside `atruncate()`, the Memory layer's truncation hook. TypeScript compresses inside `get()`, since LlamaIndex.TS's `BaseMemoryBlock` has no `atruncate`. The observable output (a single compressed `system` message containing the history) is identical.

```python
import os
from llama_index.core.memory import Memory
from compresr.integrations.llamaindex import CompresrMemoryBlock

memory = Memory.from_defaults(
    token_limit=8_000,
    memory_blocks=[
        CompresrMemoryBlock(
            api_key=os.environ["COMPRESR_API_KEY"],
            target_token=2_000,         # target output-token budget
            min_tokens=200,
        ),
    ],
)

chat_engine = index.as_chat_engine(memory=memory)
response = chat_engine.chat("And what about the regional split?")
```

**TypeScript**

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

const block = new CompresrMemoryBlock({
  apiKey: process.env.COMPRESR_API_KEY!,
  targetToken: 2_000,
  minTokens: 200,
});

// Buffer additions are uncompressed; get() returns a single compressed system message
// once the buffer crosses minTokens.
await block.put([
  { role: 'user', content: 'Tell me about Transformers.' },
  { role: 'assistant', content: longExplanation },
]);
const memoryMessages = await block.get();
```

When the buffer overflows, the block calls Compresr with `{{SDK_DEFAULT_MODEL}}`, using the last `user:` line of the buffer as the query (or `"conversation history"` if no user line exists). Override with `query=` for a fixed query string, or set `target_token` for a target output-token budget (best-effort — translated to a compression ratio, not a hard cap).

Other fields on `CompresrMemoryBlock`: `name` (default `"compresr_compressed_history"`), `priority` (default `2`), `target_compression_ratio` (default `0.5`), `coarse` (default `None`), `on_error` (default `"passthrough"`).

## When this helps

- **High-recall retrieval**: `similarity_top_k=20+` plus `CompresrNodePostprocessor` keeps the synthesis prompt tight without forcing you to throw away nodes.
- **Tool-heavy agents**: `wrap_tool_with_compresr` shaves verbose tool outputs (web pages, API dumps, search hits) down to what's relevant for the user's question.
- **Long-running chat**: `CompresrMemoryBlock` keeps multi-turn chat under the token cap without an extra LLM-summary call.

## Related

- [LangChain](/docs/framework-integration/langchain): equivalent middlewares for LangChain 1.0+ agents and retrievers.
- [Models](/docs/api-reference/models): `latte_v2` parameter semantics.
- [RAG guide](/docs/guides/rag): the underlying retrieve → compress → answer pipeline.
