Skip to content

Commit c08ee1e

Browse files
danielchalefclaude
andauthored
Add zep-langgraph integration (#524)
* Add zep-langgraph integration Zep memory for LangGraph (Python). Leads with node/tool helpers (the pattern Zep's own LangGraph guide and mem0 use), plus a hybrid-delegate BaseStore. - Node/tool helpers (PRIMARY): a context helper around thread.get_user_context to inject a Context Block into the system prompt, a persistence helper around thread.add_messages, and prebuilt graph.search tools — usable in StateGraph / create_react_agent. - ZepStore(BaseStore) (SECONDARY): implements only batch/abatch; hybrid-delegate wraps a backing KV store (InMemoryStore by default) for exact-key get/put/ delete/list, and routes search to Zep graph.search (semantic). Ingestion is async (no graph read-after-write within a turn) — documented. - Depends on langgraph>=1.2.5 and zep-cloud>=3.23.0. Python >=3.11. - README, SETUP, example, 95 mock tests, Makefile, CHANGELOG. CI filter added. Verified: ruff + ruff format + mypy + pytest (95 passed). See integrations/SPIKE_FINDINGS.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix zep-langgraph review findings: search pagination, message limits, example - examples/react_agent.py: drop temperature=0 (gpt-5 reasoning models reject it, 400ing SETUP.md's first documented command); match the README's ChatOpenAI(model="gpt-5"). - store.py ZepStore.search: honor SearchOp.offset (slice), clamp limit to [1, 50] (Zep's graph.search ceiling) with a warning, log a clear warning when a BaseStore filter is passed (Zep uses typed search_filters instead of ignoring it silently), and fix search_scope="auto" returning zero items by reading the materialized context block. Fetch offset+limit rows since Zep has no server-side offset. - persistence.py: apply the 4096-char truncation guard to native Zep Message objects (the README/example path previously bypassed it and would 400), via a shared _truncate_message_content helper; chunk persist_messages / persist_messages_sync to <=30 messages per add_messages call (Zep's cap), requesting return_context only on the final chunk. Warnings log lengths/counts only -- no message content/PII. Tests: limit clamp, offset honored, auto-scope context item, filter warning, oversize native Message truncated (+ no mutation / passthrough), >30 messages chunked (async + sync), context-on-last-chunk only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix chunked persist abandoning remaining chunks; declare langchain-core dep persist_messages / persist_messages_sync: on a chunk-level add_messages failure, continue to the remaining chunks (best-effort) instead of returning early and silently dropping them. The warning now reports the failing chunk index and total chunk count (counts only, no PII). Adds regression tests asserting all chunks are attempted after a mid-batch failure. pyproject.toml: add explicit langchain-core>=1.0 dependency. langchain_core is imported directly (tools, persistence, context, store) but was only pulled in transitively via langgraph; uv resolves it to 1.x. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d1a41e3 commit c08ee1e

19 files changed

Lines changed: 3177 additions & 0 deletions

.github/workflows/test-integrations.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ jobs:
3636
- 'integrations/livekit/python/**'
3737
ag2:
3838
- 'integrations/ag2/python/**'
39+
langgraph:
40+
- 'integrations/langgraph/python/**'
3941
pydantic-ai:
4042
- 'integrations/pydantic-ai/python/**'
4143
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Changelog
2+
3+
## 0.1.0 (2026-06-16)
4+
5+
### Added
6+
7+
- **Node / tool helpers (primary path):**
8+
- `get_zep_context` / `get_zep_context_sync` — fetch a thread's Context Block
9+
via `thread.get_user_context`.
10+
- `build_system_message` / `build_system_message_sync` — fold the Context Block
11+
and base instructions into a LangChain `SystemMessage` for prompt injection.
12+
- `format_context_block` — combine base instructions with a Context Block.
13+
- `persist_messages` / `persist_messages_sync` — persist a conversation turn via
14+
`thread.add_messages`; accept LangChain or Zep messages, with optional
15+
`return_context` to fold persist + retrieve into one round-trip.
16+
- `to_zep_message` / `to_zep_messages` — convert LangChain messages to Zep
17+
messages (role mapping, multimodal-content flattening, length truncation).
18+
- `create_graph_search_tool` / `create_graph_search_tool_sync` — prebuilt
19+
LangChain `StructuredTool` over `graph.search`, ready for `create_react_agent`.
20+
- **`ZepStore` (secondary path):** a hybrid-delegate
21+
`langgraph.store.base.BaseStore`. Implements only the two abstract methods
22+
(`batch` / `abatch`); delegates exact-key `get` / `put` / `delete` /
23+
`list_namespaces` to a backing KV store (default `InMemoryStore`), ingests every
24+
`put` into Zep (`graph.add` with `type="json"`), and routes `search` to Zep
25+
semantic `graph.search`. Configurable namespace→target resolver, search scope,
26+
ingestion toggle, and backing-store search merge.
27+
- Graceful error handling throughout: a Zep failure is logged and never crashes
28+
the host agent.
29+
- Mock-based test suite, two runnable examples (`react_agent.py`,
30+
`store_agent.py`), README, and SETUP guide.
31+
32+
### Notes
33+
34+
- Targets the Zep V3 SDK (`zep-cloud>=3.23.0`) and `langgraph>=1.2.5`.
35+
- Zep ingestion is asynchronous — there is no read-after-write of graph facts
36+
within a turn.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Makefile for zep-langgraph development
2+
3+
.PHONY: help install format lint type-check test test-cov clean build all
4+
5+
# Default target
6+
help:
7+
@echo "Available commands:"
8+
@echo " install - Install package and dependencies in development mode"
9+
@echo " format - Format code with ruff"
10+
@echo " lint - Run linting checks"
11+
@echo " type-check - Run type checking with mypy"
12+
@echo " test - Run tests"
13+
@echo " test-cov - Run tests with coverage report"
14+
@echo " all - Run format, lint, type-check, and test"
15+
@echo " build - Build the package"
16+
@echo " clean - Clean build artifacts"
17+
18+
# Install package in development mode
19+
install:
20+
uv sync --extra dev
21+
22+
# Format code
23+
format:
24+
uv run ruff format .
25+
26+
# Run linting checks
27+
lint:
28+
uv run ruff check .
29+
30+
# Fix linting issues automatically
31+
lint-fix:
32+
uv run ruff check --fix .
33+
34+
# Run type checking
35+
type-check:
36+
uv run mypy src/
37+
38+
# Run tests
39+
test:
40+
uv run pytest tests/ -v
41+
42+
# Run tests with coverage
43+
test-cov:
44+
uv run pytest tests/ -v --cov=zep_langgraph --cov-report=term-missing --cov-report=xml
45+
46+
# Run all checks (the order matters: format first, then lint, then type-check, then test)
47+
all: format lint type-check test
48+
49+
# Build the package
50+
build:
51+
uv build
52+
53+
# Clean build artifacts
54+
clean:
55+
rm -rf dist/
56+
rm -rf build/
57+
rm -rf *.egg-info/
58+
rm -rf .pytest_cache/
59+
rm -rf .mypy_cache/
60+
rm -rf .ruff_cache/
61+
find . -type d -name __pycache__ -exec rm -rf {} +
62+
find . -type f -name "*.pyc" -delete
63+
rm -f coverage.xml
64+
rm -f .coverage
65+
66+
# Development workflow - run this before committing
67+
pre-commit: lint-fix format lint type-check test
68+
@echo "All checks passed! Ready to commit."
69+
70+
# CI workflow - strict checks without auto-fixing
71+
ci: lint type-check test
72+
@echo "CI checks passed!"
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Zep LangGraph Integration
2+
3+
Give [LangGraph](https://github.com/langchain-ai/langgraph) agents durable,
4+
cross-session memory backed by [Zep](https://www.getzep.com)'s temporal Context
5+
Graph. The package ships two layers:
6+
7+
- **Node / tool helpers (primary)** — call Zep directly inside your graph nodes:
8+
inject the user's Context Block into the system prompt, persist each turn, and
9+
expose a graph-search tool. This matches Zep's own LangGraph guide.
10+
- **`ZepStore` (secondary)** — a hybrid-delegate
11+
[`BaseStore`](https://langchain-ai.github.io/langgraph/reference/store/) for
12+
`create_react_agent(store=...)` and langmem's memory tools.
13+
14+
## Installation
15+
16+
```bash
17+
pip install zep-langgraph
18+
```
19+
20+
See [SETUP.md](SETUP.md) for creating a Zep account, getting an API key, and
21+
running the example end to end.
22+
23+
## Quick Start (primary path)
24+
25+
Inject Zep context with a `prompt` callable, expose a graph-search tool, and
26+
persist each turn. Identity (the user, thread, and the user's real name) is yours
27+
to manage — create the Zep user and thread out-of-band before the first turn.
28+
29+
```python
30+
import os
31+
from langchain_core.messages import AIMessage, HumanMessage
32+
from langchain_openai import ChatOpenAI
33+
from langgraph.prebuilt import create_react_agent
34+
from zep_cloud import Message
35+
from zep_cloud.client import AsyncZep
36+
from zep_langgraph import build_system_message, create_graph_search_tool, persist_messages
37+
38+
zep = AsyncZep(api_key=os.environ["ZEP_API_KEY"])
39+
40+
async def prompt(state):
41+
system = await build_system_message(
42+
zep, thread_id="thread-1", base_instructions="You are a helpful assistant."
43+
)
44+
return [system, *state["messages"]]
45+
46+
agent = create_react_agent(
47+
model=ChatOpenAI(model="gpt-5"),
48+
tools=[create_graph_search_tool(zep, user_id="user-1")],
49+
prompt=prompt,
50+
)
51+
52+
result = await agent.ainvoke({"messages": [HumanMessage(content="Where do I work?")]})
53+
reply = result["messages"][-1]
54+
await persist_messages(
55+
zep,
56+
thread_id="thread-1",
57+
messages=[Message(role="user", content="Where do I work?", name="Alice Smith"), reply],
58+
)
59+
```
60+
61+
A complete runnable version is in
62+
[examples/react_agent.py](examples/react_agent.py).
63+
64+
## How It Works
65+
66+
The Zep loop is the same everywhere: **create user → create thread → add
67+
messages → retrieve context**. This package wraps each step as a helper you call
68+
from inside a graph node.
69+
70+
### Context injection — `build_system_message` / `get_zep_context`
71+
72+
`thread.get_user_context(thread_id)` returns a token-efficient **Context Block**
73+
assembled from the *entire user graph* (the thread only scopes what is relevant
74+
right now). `build_system_message` fetches it and folds it into a
75+
`SystemMessage` together with your base instructions, ready to prepend to the
76+
model's message list. `get_zep_context` returns just the raw block.
77+
78+
Implemented in [src/zep_langgraph/context.py](src/zep_langgraph/context.py).
79+
80+
### Persistence — `persist_messages`
81+
82+
Wraps `thread.add_messages`. Accepts LangChain `BaseMessage` objects (converted
83+
automatically — `human``user`, `ai``assistant`, …) or native Zep `Message`
84+
objects, flattens multimodal content to text, truncates over-long messages, and
85+
maps names so Zep can resolve identity. Pass `return_context=True` to fold
86+
persist + retrieve into one round-trip.
87+
88+
Implemented in
89+
[src/zep_langgraph/persistence.py](src/zep_langgraph/persistence.py).
90+
91+
### On-demand search — `create_graph_search_tool`
92+
93+
Returns a LangChain `StructuredTool` over `graph.search`. Bind it to a model or
94+
pass it to `create_react_agent(tools=[...])` and the model decides when to search
95+
the graph. The target (`user_id` for a personal graph, `graph_id` for a shared
96+
standalone graph) and the search parameters (`scope`, `reranker`, `limit`) are
97+
fixed at construction so the model only supplies the query.
98+
99+
Implemented in [src/zep_langgraph/tools.py](src/zep_langgraph/tools.py).
100+
101+
### `ZepStore` — a `BaseStore` for the langmem audience
102+
103+
`BaseStore` is LangGraph's cross-thread long-term-memory interface;
104+
`create_react_agent(store=...)` and langmem's
105+
`create_manage_memory_tool` / `create_search_memory_tool` require one. Zep is a
106+
temporal knowledge graph, not a KV store, so `ZepStore` uses a **hybrid-delegate**
107+
design: a backing KV `BaseStore` (default `InMemoryStore`) serves exact-key
108+
`get` / `put` / `delete` / `list_namespaces` faithfully and synchronously, while
109+
every `put` is *also* ingested into Zep and `search` is routed to Zep's semantic
110+
`graph.search`. Only the two abstract methods (`batch` / `abatch`) are
111+
implemented; everything else is inherited and delegates to them.
112+
113+
```python
114+
from zep_langgraph import ZepStore
115+
116+
store = ZepStore(zep) # default backing store: InMemoryStore
117+
await store.aput(("memories", "user-1"), "m1", {"text": "Alice works at Acme."})
118+
item = await store.aget(("memories", "user-1"), "m1") # exact-key, synchronous
119+
hits = await store.asearch(("memories", "user-1"), query="where does Alice work?")
120+
```
121+
122+
> **Zep ingestion is asynchronous.** A value written with `put` is available
123+
> immediately for exact-key `get` (served by the backing store), but its
124+
> extracted facts are **not** instantly returned by `search` — there is no
125+
> read-after-write of graph facts within a turn. `ZepStore` is the long-term
126+
> memory layer, not the checkpointer, so graph execution and short-term state are
127+
> unaffected.
128+
129+
Implemented in [src/zep_langgraph/store.py](src/zep_langgraph/store.py); see
130+
[examples/store_agent.py](examples/store_agent.py).
131+
132+
## Public API
133+
134+
| Symbol | Kind | Purpose |
135+
|--------|------|---------|
136+
| `get_zep_context` / `get_zep_context_sync` | async / sync fn | Fetch the Context Block for a thread |
137+
| `build_system_message` / `build_system_message_sync` | async / sync fn | Build a `SystemMessage` with the Context Block |
138+
| `format_context_block` | fn | Combine base instructions with a Context Block |
139+
| `persist_messages` / `persist_messages_sync` | async / sync fn | Persist a turn (LangChain or Zep messages) |
140+
| `to_zep_message` / `to_zep_messages` | fn | Convert LangChain messages to Zep messages |
141+
| `create_graph_search_tool` / `create_graph_search_tool_sync` | fn | Build a `graph.search` `StructuredTool` |
142+
| `ZepStore` | class | Hybrid-delegate `BaseStore` |
143+
144+
Both an `AsyncZep` (async helpers, recommended) and a synchronous `Zep` client
145+
are supported. Reuse a single client instance.
146+
147+
## Error Handling
148+
149+
Every helper handles Zep failures gracefully: context retrieval and persistence
150+
log a warning and return `None`/an empty result, the search tool returns an error
151+
string, and `ZepStore` keeps serving KV operations from its backing store. **A
152+
Zep failure never crashes the host agent.**
153+
154+
## Configuration
155+
156+
```bash
157+
export ZEP_API_KEY="your-zep-api-key"
158+
export OPENAI_API_KEY="your-openai-api-key" # for the example's model
159+
```
160+
161+
## Examples
162+
163+
- [examples/react_agent.py](examples/react_agent.py)`create_react_agent` with
164+
Zep context injection, the graph-search tool, and per-turn persistence.
165+
- [examples/store_agent.py](examples/store_agent.py)`ZepStore` as a
166+
`BaseStore`, showing the KV round-trip and Zep-routed semantic search.
167+
168+
## Development
169+
170+
```bash
171+
git clone https://github.com/getzep/zep.git
172+
cd zep/integrations/langgraph/python
173+
make install # uv sync --extra dev
174+
make format # ruff format .
175+
make lint # ruff check .
176+
make type-check # mypy src/
177+
make test # pytest tests/ -v
178+
make all # all of the above
179+
make build # uv build
180+
```
181+
182+
## Requirements
183+
184+
- Python 3.11+
185+
- `zep-cloud>=3.23.0`
186+
- `langgraph>=1.2.5` (pulls in `langchain-core`)
187+
188+
## Support
189+
190+
- [Zep Documentation](https://help.getzep.com)
191+
- [Zep LangGraph Guide](https://help.getzep.com/langgraph-memory)
192+
- [LangGraph Documentation](https://langchain-ai.github.io/langgraph/)
193+
- [GitHub Issues](https://github.com/getzep/zep/issues)
194+
195+
## License
196+
197+
Apache 2.0 — see [LICENSE](../../../LICENSE) for details.
198+
199+
## Contributing
200+
201+
Contributions are welcome! Please see our
202+
[Contributing Guide](../../../CONTRIBUTING.md) for details.

0 commit comments

Comments
 (0)