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

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

LangGraph 1.0+ shares the `create_agent` middleware mechanism with LangChain, so the three agent-level Compresr middlewares are re-exported from `compresr.integrations.langgraph` (Python) and `@compresr/sdk/integrations/langgraph` (TypeScript). The LangGraph package adds four LangGraph-native components on top: a state-graph node (`make_compresr_node`), a checkpoint serializer (`CompresrCheckpointSerializer`), a store wrapper (`CompresrStore`), and a multi-agent handoff tool (`compresr_handoff_tool`).

> **Both SDKs ship the full surface**
> The LangGraph integration is a peer of the LangChain one in both Python and TypeScript: same components, same defaults. Python uses snake_case classes; TypeScript uses camelCase factory functions for middleware and `new`-able classes for the other components.

## 1. Install

```bash
pip install "compresr[langgraph]"
export COMPRESR_API_KEY="cmp_..."
```

**TypeScript**

```bash
npm install @compresr/sdk @langchain/langgraph @langchain/core
export COMPRESR_API_KEY="cmp_..."
```

## 2. Agent-level middleware (re-exported)

The exact same `CompresrToolMiddleware`, `CompresrSummarizationMiddleware`, and `CompresrPromptMiddleware` covered on the [LangChain page](/docs/framework-integration/langchain) work inside LangGraph's `create_agent`. They are re-exported here for discovery:

```python
import os
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from compresr.integrations.langgraph import (
    CompresrToolMiddleware,
    CompresrSummarizationMiddleware,
    CompresrPromptMiddleware,
)

agent = create_agent(
    model=ChatOpenAI(model="gpt-4o-mini"),
    tools=[search_tool, fetch_url],
    middleware=[
        CompresrToolMiddleware(
            api_key=os.environ["COMPRESR_API_KEY"],
            query_arg="query",
        ),
        CompresrSummarizationMiddleware(
            api_key=os.environ["COMPRESR_API_KEY"],
            max_tokens_before_summary=8_000,
        ),
        CompresrPromptMiddleware(
            api_key=os.environ["COMPRESR_API_KEY"],
            max_tokens=16_000,
        ),
    ],
)
```

**TypeScript**

```typescript
import { createAgent } from 'langchain';
import { ChatOpenAI } from '@langchain/openai';
import {
  compresrToolMiddleware,
  compresrSummarizationMiddleware,
  compresrPromptMiddleware,
} from '@compresr/sdk/integrations/langgraph';

const agent = createAgent({
  model: new ChatOpenAI({ model: 'gpt-4o-mini' }),
  tools: [searchTool, fetchUrl],
  middleware: [
    compresrToolMiddleware({
      apiKey: process.env.COMPRESR_API_KEY!,
      queryArg: 'query',
    }),
    compresrSummarizationMiddleware({
      apiKey: process.env.COMPRESR_API_KEY!,
      maxTokensBeforeSummary: 8_000,
    }),
    compresrPromptMiddleware({
      apiKey: process.env.COMPRESR_API_KEY!,
      maxTokens: 16_000,
    }),
  ],
});
```

See the [LangChain doc](/docs/framework-integration/langchain) for the full middleware reference; every keyword argument is identical.

## 3. Compress a state field inside a custom `StateGraph`

For graphs you build with `StateGraph` directly (not the pre-built ReAct shape), use `make_compresr_node`. It returns a callable node that reads `state[context_key]`, compresses it, and writes the shortened text back into the same field.

```python
import os
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from compresr.integrations.langgraph import make_compresr_node

class ResearchState(TypedDict):
    question: str
    raw_notes: str          # the field we want to compress
    answer: str

compress_notes = make_compresr_node(
    api_key=os.environ["COMPRESR_API_KEY"],
    context_key="raw_notes",          # field to compress in state
    query_key="question",             # field to use as the query
    target_compression_ratio=0.5,
    min_tokens=200,
)

builder = StateGraph(ResearchState)
builder.add_node("gather", gather)
builder.add_node("compress_notes", compress_notes)
builder.add_node("answer", answer)
builder.add_edge(START, "gather")
builder.add_edge("gather", "compress_notes")
builder.add_edge("compress_notes", "answer")
builder.add_edge("answer", END)

graph = builder.compile()
result = graph.invoke({"question": "What changed in the Q3 forecast?", "raw_notes": "", "answer": ""})
```

**TypeScript**

```typescript
import { Annotation, END, START, StateGraph } from '@langchain/langgraph';
import { makeCompresrNode } from '@compresr/sdk/integrations/langgraph';

const GraphState = Annotation.Root({
  question: Annotation<string>(),
  raw_notes: Annotation<string>(),
  answer: Annotation<string>(),
});
type State = typeof GraphState.State;

const compressNotes = makeCompresrNode<State>({
  apiKey: process.env.COMPRESR_API_KEY!,
  contextKey: 'raw_notes',
  queryKey: 'question',
  targetCompressionRatio: 0.5,
  minTokens: 200,
});

const graph = new StateGraph(GraphState)
  .addNode('gather', gather)
  .addNode('compress_notes', compressNotes)
  .addNode('answer', answer)
  .addEdge(START, 'gather')
  .addEdge('gather', 'compress_notes')
  .addEdge('compress_notes', 'answer')
  .addEdge('answer', END)
  .compile();

const result = await graph.invoke({ question: '...', raw_notes: '', answer: '' });
```

### Options

| Python | TypeScript | Required? | Notes |
|---|---|---|---|
| `context_key` | `contextKey` | **Yes** | Name of the state field to compress. |
| `query_key` | `queryKey` | Recommended | Field whose value is used as the query; the compression model is query-aware. |
| `query` | `query` | n/a | Static query overriding extractor and `query_key`. |
| `query_extractor` | `queryExtractor` | n/a | `(state) -> Optional[str]`, custom extraction. |
| `target_compression_ratio` | `targetCompressionRatio` | n/a | Default `0.5`. |
| `min_tokens` | `minTokens` | n/a | Default `200`. Skip if field shorter than this. |
| `coarse` | `coarse` | n/a | Paragraph vs token level; server default. |
| `compression_model` | `compressionModel` | n/a | Default `"latte_v1"`. Pass `"{{DEFAULT_MODEL}}"` for the faster model. |
| `on_error` | `onError` | n/a | `"passthrough"` (default) or `"raise"`. |
| `api_key` / `base_url` / `client` | `apiKey` / `baseUrl` / `client` | n/a | Standard auth knobs. |

The node returns `{context_key: new_text}` when it actually changed the field and `{}` otherwise, so it composes cleanly with LangGraph's diff-merging state updates.

Python also exports `compresr_node` as an alias for `make_compresr_node`; TypeScript exports `compresrNode`.

## 4. Compress checkpoints: `CompresrCheckpointSerializer`

Long agent state can balloon at-rest storage: Postgres rewrites the whole TOAST row on every checkpoint, Redis pays for every byte. `CompresrCheckpointSerializer` is a `JsonPlusSerializer` subclass that walks the state on `dumps_typed`, finds long strings, and rewrites them as `{"__compresr__": true, "v": ""}` sentinels before encoding.

> **Lossy by design**
> This serializer is **one-way**: there is no decompression on `loads_typed`. Anything you compress at write time is what subsequent reads see. Use the `fields` allowlist in production to compress only the keys that carry bulk data (retrieved notes, scratchpads), not control state.

```python
from compresr.integrations.langgraph import CompresrCheckpointSerializer

serializer = CompresrCheckpointSerializer(
    api_key=os.environ["COMPRESR_API_KEY"],
    fields={"raw_notes", "scratchpad"},   # allowlist — recommended for prod
    min_tokens=500,                        # storage tier defaults higher than middleware
    target_compression_ratio=0.5,
)

# Wire into any checkpointer that accepts a custom serializer:
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver(conn, serde=serializer)
```

**TypeScript**

```typescript
import { CompresrCheckpointSerializer } from '@compresr/sdk/integrations/langgraph';

const serializer = new CompresrCheckpointSerializer({
  apiKey: process.env.COMPRESR_API_KEY!,
  fields: new Set(['raw_notes', 'scratchpad']),
  minTokens: 500,
  targetCompressionRatio: 0.5,
});

const [tag, encoded] = await serializer.dumpsTyped(state);
const decoded = serializer.loadsTyped([tag, encoded]) as Record<string, unknown>;
// Compressed values come back wrapped: decoded.raw_notes === { __compresr__: true, v: '<compressed>' }
```

Default `min_tokens` is `500` for the serializer (vs `200` for middleware); at-rest cost only matters above a certain payload size.

## 5. Compress at-rest store entries: `CompresrStore`

`CompresrStore` wraps any `BaseStore` (in-memory, Postgres, Redis, ...) and compresses long string fields on the write side. Reads pass through untouched, so anything you write through the wrapper is what subsequent `get` / `search` calls see. Same lossy contract as the checkpoint serializer.

```python
from langgraph.store.memory import InMemoryStore
from compresr.integrations.langgraph import CompresrStore

inner = InMemoryStore()
store = CompresrStore(
    inner,
    api_key=os.environ["COMPRESR_API_KEY"],
    fields={"retrieved_text"},
    min_tokens=500,
)

store.put(("users", "alice"), "notes", {"retrieved_text": article, "tag": "physics"})
# Read-side methods (get, search, delete, list_namespaces, async variants) are unchanged passthrough.
```

**TypeScript**

```typescript
import { InMemoryStore } from '@langchain/langgraph';
import { CompresrStore } from '@compresr/sdk/integrations/langgraph';

const inner = new InMemoryStore();
const store = new CompresrStore(inner, {
  apiKey: process.env.COMPRESR_API_KEY!,
  fields: new Set(['retrieved_text']),
  minTokens: 500,
});

await store.put(['users', 'alice'], 'notes', { retrieved_text: article, tag: 'physics' });
```

The wrapper exposes the full `BaseStore` surface: `put`, `get`, `delete`, `search`, `list_namespaces`, `batch`, plus async siblings (`aput`, `aget`, ...). Only writes are mutated; reads, deletes, searches, and namespace listings delegate straight to the inner store.

## 6. Compress payloads passed between agents: `compresr_handoff_tool`

In a supervisor / sub-agent architecture, the supervisor often passes a large `context` blob (retrieved docs, prior conversation) to a sub-agent via a handoff tool. `compresr_handoff_tool` returns a LangChain `BaseTool` that compresses `task_description` and `context` before emitting `Command(goto=agent_name, graph=Command.PARENT)`.

```python
from compresr.integrations.langgraph import compresr_handoff_tool

researcher_handoff = compresr_handoff_tool(
    "researcher",                          # positional: target agent name
    api_key=os.environ["COMPRESR_API_KEY"],
    min_tokens=100,
)

# Inside an agent loop:
tool_call = {
    "name": "transfer_to_researcher",
    "args": {
        "task_description": "Summarise the Transformer architecture.",
        "context": full_article,
    },
    "type": "tool_call",
    "id": "tc1",
}
cmd = researcher_handoff.invoke(tool_call)
# cmd.goto == "researcher", cmd.graph == Command.PARENT
# cmd.update["task_description"] and cmd.update["context"] are both compressed.
```

**TypeScript**

```typescript
import { compresrHandoffTool } from '@compresr/sdk/integrations/langgraph';

const researcherHandoff = compresrHandoffTool('researcher', {
  apiKey: process.env.COMPRESR_API_KEY!,
  minTokens: 100,
});

const cmd = await researcherHandoff.invoke({
  name: 'transfer_to_researcher',
  args: {
    task_description: 'Summarise the Transformer architecture.',
    context: fullArticle,
  },
  type: 'tool_call',
  id: 'tc1',
});
// cmd.goto === 'researcher'; cmd.update.task_description and cmd.update.context are both compressed.
```

The tool name is auto-generated as `f"transfer_to_{agent_name}"`. Both `task_description` and `context` are compressed with synthesised queries (`"task for {agent_name}"` and the task description itself), so the compression model always has a meaningful query even though it's query-specific.

## When this helps

- **ReAct agents with verbose tools**: `CompresrToolMiddleware` compresses each tool result before the next reasoning step.
- **Long-running graphs**: `CompresrSummarizationMiddleware` keeps the prompt bounded; `CompresrPromptMiddleware` caps the outbound budget regardless.
- **Multi-step workflows with bulky intermediate state**: `make_compresr_node` compresses a single field between two graph nodes.
- **Postgres / Redis-backed agents**: `CompresrCheckpointSerializer` and `CompresrStore` cut at-rest storage cost on the write path.
- **Supervisor → sub-agent topologies**: `compresr_handoff_tool` keeps the payload tight when control transfers.

## Related

- [LangChain](/docs/framework-integration/langchain): full reference for the three middlewares + `CompresrExtractor`.
- [Models](/docs/api-reference/models): `latte_v2` parameter semantics.
