Metadata-Version: 2.5
Name: zep-adk
Version: 1.0.0
Summary: Google ADK integration for Zep
Project-URL: Homepage, https://github.com/getzep/zep
Project-URL: Documentation, https://help.getzep.com
Project-URL: Repository, https://github.com/getzep/zep
Project-URL: Bug Tracker, https://github.com/getzep/zep/issues
Project-URL: Changelog, https://github.com/getzep/zep/blob/main/integrations/adk/python/CHANGELOG.md
Requires-Python: >=3.11
Requires-Dist: google-adk<3,>=1.19.0
Requires-Dist: typing-extensions>=4.0.0
Requires-Dist: zep-cloud<5,>=4.0.0
Provides-Extra: dev
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# Zep Google ADK Integration

A memory integration package that enables [Google ADK](https://github.com/google/adk-python) agents to leverage [Zep](https://getzep.com)'s long-term memory platform for persistent conversation storage and context-aware responses.

## Installation

```bash
pip install zep-adk
```

## Choosing a component

zep-adk ships the same set of capabilities across Python, TypeScript, and Go, though the exact symbol names differ per language's ADK idioms:

| Capability | Python | TypeScript | Go |
|---|---|---|---|
| guaranteed context injection | `ZepContextTool` | `ZepContextTool` or `createZepBeforeModelCallback` | `NewBeforeModelCallback` |
| assistant-turn persistence | `create_after_model_callback` | `createZepAfterModelCallback` | `NewAfterModelCallback` |
| explicit provisioning | `create_user`/`create_thread` | `createUser`/`createThread` | `CreateUser`/`CreateThread` |
| custom context block | `context_builder` | `contextBuilder` | `WithContextBuilder` |
| injection template | `context_template` | `contextTemplate` | `WithContextTemplate` |
| model-callable graph search (pin-or-expose, 6 scopes) | `ZepGraphSearchTool` | `ZepGraphSearchTool` | `NewGraphSearchTool` |
| ADK-native memory service | `ZepMemoryService` | `ZepMemoryService` | `NewMemoryService` |

Note: Go intentionally has no tool-based injection (callbacks are the Go-ADK-idiomatic hook).

## Identifiers

Zep v4 addresses every user, thread, and graph by a server-generated UUID. A v4
create call takes no client-chosen identifier, thus a v4 resource has no name.
The public API of this package takes `user_uuid` and `thread_uuid`.

Provision the Zep resources one time, read the UUIDs from the responses, and
store the UUIDs in your own database. The integration does not look an
identifier up at run time.

## Quick Start

Define one agent, shared across all users. Per-user identity is passed via ADK session state.

```python
import os
from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from zep_cloud.client import AsyncZep
from zep_adk import ZepContextTool, create_after_model_callback, create_user, create_thread

# Initialize Zep client
zep = AsyncZep(api_key=os.getenv("ZEP_API_KEY"))

# One shared agent definition
agent = Agent(
    name="my_agent",
    model="gemini-2.5-flash",
    instruction="You are a helpful assistant with long-term memory.",
    tools=[ZepContextTool(zep_client=zep)],
    after_model_callback=create_after_model_callback(zep_client=zep),
)

session_service = InMemorySessionService()
runner = Runner(agent=agent, app_name="my_app", session_service=session_service)

# Provision the Zep user and thread out-of-band, BEFORE the first turn --
# e.g. during account/session onboarding in your app. Zep generates the UUIDs.
user = await create_user(
    zep,
    first_name="Jane",
    last_name="Smith",
    email="jane@example.com",  # optional
)
thread = await create_thread(zep, user_uuid=user.uuid_)

# Store user.uuid_, user.graph_uuid, and thread.uuid_ in your own database, next
# to your own user ID, and put them into the ADK session state. This dict stands
# in for that database.
zep_user_uuids: dict[str, str] = {"user_123": user.uuid_}

await session_service.create_session(
    app_name="my_app",
    user_id="user_123",
    session_id="session_abc",
    state={
        "zep_user_uuid": user.uuid_,
        "zep_thread_uuid": thread.uuid_,
        "zep_graph_uuid": user.graph_uuid,
        "zep_first_name": "Jane",
        "zep_last_name": "Smith",
    },
)
```

## Session State Keys

Identity is resolved at runtime from ADK session state. Set the Zep UUIDs when you create the session:

| Key | Required | Default | Description |
|-----|----------|---------|-------------|
| `zep_user_uuid` | Yes | ADK `user_id` | The UUID of the Zep user. The ADK `user_id` is used only when it holds the Zep user UUID. |
| `zep_thread_uuid` | Yes | ADK `session_id` | The UUID of the Zep thread. The ADK `session_id` is used only when it holds the Zep thread UUID. |
| `zep_graph_uuid` | Optional | resolved from the user | The UUID of the graph of the user. `ZepGraphSearchTool` and `ZepMemoryService` resolve it through `user.get` when it is absent. |
| `zep_first_name` | Recommended | `"Anonymous"` | User's first name. Attached as the author name on persisted messages so Zep can anchor them to the user's identity node in the knowledge graph. |
| `zep_last_name` | Optional | `"User"` | User's last name. |

The user's name and email on the Zep user profile itself are set during
provisioning -- pass them to `create_user`, not session state.

## How It Works

The integration uses four components that work together to give your ADK agent persistent memory:

### create_user / create_thread

Explicit, out-of-band provisioning helpers. Call these once -- during onboarding, account creation, or before the first turn of a new conversation -- **before** the agent runs. Each calls the Zep v4 create method and returns the created object, so the caller reads `user.uuid_`, `user.graph_uuid`, and `thread.uuid_`. Every call creates a new resource, thus the helpers are not idempotent. Failures (auth, network, 5xx) raise, so misconfiguration is caught immediately rather than silently swallowed.

Run one-time per-user setup -- a per-user ontology, custom instructions, or summary instructions -- directly after `create_user` returns.

The ADK turn path (`ZepContextTool`) never creates users or threads itself -- it assumes they already exist.

### ZepContextTool

A `BaseTool` subclass that hooks into ADK's `process_llm_request()` lifecycle method (the same pattern ADK's own `PreloadMemoryTool` uses). On every LLM turn it:

1. **Extracts** the user's latest message from the invocation context.
2. **Resolves** the user's Zep identity from session state.
3. **Persists** the message to Zep -- via `thread.add_messages(return_context=True)` in a single API call by default, or in parallel with a custom `context_builder` for advanced use cases. Over-limit message content (> 4096 chars) is truncated before persisting rather than dropped.
4. **Injects** the returned context (facts, relationships, prior knowledge) into the LLM's system instructions, wrapped by a configurable `context_template` (default: `DEFAULT_CONTEXT_TEMPLATE`, using `<ZEP_CONTEXT>` tags).

The tool is never called by the model directly; it modifies the outgoing LLM request before it is sent. If persistence fails because the user or the thread does not exist, a warning naming `create_user`/`create_thread` is logged and the turn continues without Zep memory.

### create_after_model_callback

A factory function that returns an `after_model_callback` for persisting assistant responses to Zep. This ensures both sides of the conversation are stored in Zep's memory. The callback also resolves the thread UUID from session state at runtime.

Both `ZepContextTool` and `create_after_model_callback` include per-thread message deduplication to handle ADK's tool-use cycles, where the framework may call hooks multiple times per turn.

## ADK-native memory service

`ZepMemoryService` implements ADK's own `BaseMemoryService` extension point, so Zep can back ADK's built-in `load_memory`/`preload_memory` tools. Register it on the `Runner`.

ADK gives `search_memory` only the ADK `app_name` and `user_id`, and the ADK `user_id` is your own identifier, not a Zep UUID. Give the service a `resolve_user_uuid` callable that returns the Zep user UUID that you stored for that user. This example continues the Quick Start, so `zep`, `session_service`, and `zep_user_uuids` are already defined:

```python
from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.tools import load_memory
from zep_adk import ZepMemoryService


def resolve_user_uuid(app_name: str, user_id: str) -> str | None:
    # Read the Zep user UUID that you stored when you called create_user.
    return zep_user_uuids.get(user_id)


agent = Agent(
    name="my_agent",
    model="gemini-2.5-flash",
    instruction="You are a helpful assistant. Use load_memory to recall prior context when relevant.",
    tools=[load_memory],
)

runner = Runner(
    agent=agent,
    app_name="my_app",
    session_service=session_service,
    memory_service=ZepMemoryService(
        zep=zep,
        scope="edges",
        resolve_user_uuid=resolve_user_uuid,
    ),
)
```

`resolve_user_uuid` can also be an `async` function, for example one that reads your database. The constructor raises `ValueError` when you give neither `resolve_user_uuid` nor `graph_uuid`. When `resolve_user_uuid` returns `None` or a value that is not a UUID, the service logs an error and returns no memories.

When the model calls `load_memory`, ADK invokes `ZepMemoryService.search_memory(app_name=..., user_id=..., query=...)`. The service calls `resolve_user_uuid(app_name, user_id)` to get the Zep user UUID, resolves the graph UUID of that user, and runs the v4 search method for the configured scope (for example `zep.graph.search_edges(graph_uuid, query=query, page_size=limit)`) and maps each result into a `MemoryEntry` (the same result shapes -- edges, nodes, episodes, observations, thread_summaries, auto -- as `ZepGraphSearchTool`). A Zep failure is logged (as a warning, with lengths/counts only) and returns an empty result rather than raising into the agent.

`add_session_to_memory` is intentionally a no-op: Zep already ingests each turn live via `ZepContextTool` (or `thread.add_messages`) and `create_after_model_callback`, so re-ingesting the full session here would persist the same conversation into the graph twice.

**When to use this vs. `ZepContextTool`:** `ZepMemoryService` is ADK-native and model-opt-in -- the model decides, per turn, whether to call `load_memory`. `ZepContextTool` guarantees injection -- it runs on every LLM turn regardless of what the model decides. The two are complementary: keep `ZepContextTool` for always-on context, and add `ZepMemoryService` when you also want the model to be able to explicitly search for more via ADK's own memory tools, or when integrating with ADK code paths that expect a `memory_service` (e.g. evaluation harnesses).

## Adding Zep to an Existing Agent

If you already have an ADK agent serving all users, adding Zep memory requires only a few changes -- no restructuring needed:

1. **Add `ZepContextTool` to your agent's tools list:**

```python
from zep_adk import ZepContextTool

agent = Agent(
    name="my_existing_agent",
    model="gemini-2.5-flash",
    instruction="...",
    tools=[your_existing_tool, ZepContextTool(zep_client=zep)],
    after_model_callback=create_after_model_callback(zep_client=zep),
)
```

2. **Provision the Zep user and thread out-of-band** in your app/onboarding code, before the first turn:

```python
from zep_adk import create_user, create_thread

user = await create_user(zep, first_name=first_name, last_name=last_name)
thread = await create_thread(zep, user_uuid=user.uuid_)
```

3. **Include the Zep UUIDs and the user's name in session state** when creating sessions (you're already creating sessions -- just add the keys):

```python
await session_service.create_session(
    app_name="my_app",
    user_id=user_id,
    session_id=session_id,
    state={
        **your_existing_state,
        "zep_user_uuid": user.uuid_,
        "zep_thread_uuid": thread.uuid_,
        "zep_graph_uuid": user.graph_uuid,
        "zep_first_name": first_name,
        "zep_last_name": last_name,
    },
)
```

4. That's it. No factory function, no per-session agent instances.

## Migrating from Zep V3

Follow the [V4 migration guide](https://help.getzep.com/migrating-from-v3) for the general procedure: the nullable UUID columns, the one-time `lookup` backfill, and the order of the steps.

The changes that are specific to this package are:

- The package requires `zep-cloud>=4.0.0,<5`. There is no V3 compatibility layer.
- `ensure_user` is replaced by `create_user(client, *, first_name=None, last_name=None, email=None)`. The `user_id` parameter is removed. The function returns the `User`; store `user.uuid_` and `user.graph_uuid`.
- `ensure_thread` is replaced by `create_thread(client, *, user_uuid)`. The `thread_id` and `user_id` parameters are removed. The function returns the `Thread`; store `thread.uuid_`.
- The `on_created` parameter of `ensure_user` and the `UserSetupHook` type are removed. Run one-time user setup after `create_user` returns.
- The session-state key `zep_user_id` is now `zep_user_uuid`, and `zep_thread_id` is now `zep_thread_uuid`. The new optional key `zep_graph_uuid` takes the stored `user.graph_uuid`. `ZepContextTool`, `create_after_model_callback`, and `ZepGraphSearchTool` read these keys. They use the ADK `user_id` and `session_id` only when no key is set, so those values must then be the Zep UUIDs.
- `ContextInput.user_id` is now `ContextInput.user_uuid`, and `ContextInput.thread_id` is now `ContextInput.thread_uuid`. A custom `context_builder` must call the V4 SDK, for example `ctx.zep.graph.search_edges(graph_uuid, query=ctx.user_message)` in place of `ctx.zep.graph.search(user_id=ctx.user_id, ...)`.
- The `graph_id` parameter of `ZepGraphSearchTool` is now `graph_uuid`.
- `ZepMemoryService` now requires the new `resolve_user_uuid` parameter, which maps the ADK `app_name` and `user_id` to the stored Zep user UUID, or the new `graph_uuid` parameter, which gives a fixed graph UUID.

For an existing V3 user or thread, do not call `create_user` or `create_thread`. Get its UUID one time with the backfill in the migration guide, and store it.

## Changes in 0.3.0

- **Injected context is wrapped via a configurable template.** Pass `context_template` to `ZepContextTool` to override the wrapper around retrieved context (default: `DEFAULT_CONTEXT_TEMPLATE`, which still uses `<ZEP_CONTEXT>` tags):

  ```python
  from zep_adk import ZepContextTool, DEFAULT_CONTEXT_TEMPLATE

  tool = ZepContextTool(
      zep_client=zep,
      context_template="Relevant memory:\n{context}",
  )
  ```

  The template is rendered via plain string replacement (`template.replace("{context}", context_text)`), never `str.format` -- so context text or templates containing `{`, `}`, or `%` are always safe to inject.

- **The default injected context wording changed in 0.3.0.** Pre-0.3.0 hardcoded:

  ```text
  The following context is retrieved from Zep's long-term memory service. It contains relevant facts, relationships, and prior knowledge about the user. Use it to inform your responses.
  ```

  `DEFAULT_CONTEXT_TEMPLATE` (0.3.0+) now reads:

  ```text
  The following context is retrieved from Zep, the agent's long-term memory. It contains relevant facts, entities, and prior knowledge about the user. Use it to inform your responses.
  ```

  The `<ZEP_CONTEXT>...</ZEP_CONTEXT>` wrapper is unchanged. If you depend on the exact previous wording, pass it explicitly as `context_template`. This wording is canonical across zep-adk's Python, Go, and TypeScript implementations.

## Features

- **Shared-agent architecture** -- one Agent definition serves all users
- **Session-state-driven identity** -- per-user configuration via ADK's standard mechanism
- **Single round-trip** -- persist messages and retrieve context in one API call
- **Explicit out-of-band provisioning** -- `create_user`/`create_thread` provision Zep resources before the first turn and return the server-generated UUIDs
- **Per-thread deduplication** -- prevents double-persistence during tool-use cycles
- **Graceful error handling** -- Zep API failures are logged but never crash the agent
- **Context injection** -- Zep's knowledge graph context is injected as system instructions
- **On-demand graph search** -- `ZepGraphSearchTool` lets the model actively search the knowledge graph
- **Configurable graph search** -- pin parameters at construction or let the model choose
- **ADK-native memory service** -- `ZepMemoryService` implements `BaseMemoryService` so Zep backs ADK's own `load_memory`/`preload_memory` tools

## Configuration

### Environment Variables

```bash
# Required
export ZEP_API_KEY="your-zep-api-key"
export GOOGLE_API_KEY="your-google-api-key"
```

### Constructor Parameters

#### create_user

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `client` | `AsyncZep` | Yes | -- | Initialised Zep async client |
| `first_name` | `str` | No | `None` | User's first name |
| `last_name` | `str` | No | `None` | User's last name |
| `email` | `str` | No | `None` | User's email |

Returns the created `User`. Read `user.uuid_` and `user.graph_uuid` and store them. Raises on failures (auth, network, 5xx).

#### create_thread

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `client` | `AsyncZep` | Yes | -- | Initialised Zep async client |
| `user_uuid` | `str` | Yes | -- | The UUID of the Zep user that owns the thread |

Returns the created `Thread`. Read `thread.uuid_` and store it. Raises on failures (auth, network, 5xx).

#### ZepContextTool

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `zep_client` | `AsyncZep` | Yes | -- | Initialised Zep async client |
| `context_builder` | `ContextBuilder` | No | `None` | Custom async callable (receives a `ContextInput`) for context retrieval |
| `context_template` | `str` | No | `DEFAULT_CONTEXT_TEMPLATE` | Template wrapping retrieved context; must contain a literal `{context}` placeholder |
| `ignore_roles` | `list[str]` | No | `None` | Roles to exclude from graph ingestion |

#### create_after_model_callback

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `zep_client` | `AsyncZep` | Yes | -- | Initialised Zep async client |
| `assistant_name` | `str` | No | `"Assistant"` | Display name for the assistant in Zep |
| `ignore_roles` | `list[str]` | No | `None` | Roles to exclude from graph ingestion |

#### ZepGraphSearchTool

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `zep_client` | `AsyncZep` | Yes | -- | Initialised Zep async client |
| `graph_uuid` | `str` | No | `None` | Fixed graph UUID for shared-graph search |
| `name` | `str` | No | `"zep_graph_search"` | Tool name visible to the model |
| `description` | `str` | No | (default) | Tool description visible to the model |
| `search_filters` | `dict` | No | `None` | Zep search filters (constructor-only) |
| `bfs_origin_node_uuids` | `list[str]` | No | `None` | BFS seed node UUIDs (constructor-only) |
| `**pinned` | any | No | -- | Pin any search param: `scope`, `reranker`, `limit`, `mmr_lambda`, `center_node_uuid` |

Every search parameter is **tri-state**, set via `**pinned`:

| State | How to set it | Effect |
|-------|----------------|--------|
| **Pinned** | pass a concrete value, e.g. `scope="edges"` | Hidden from the model's tool schema. Always used, even if the model would have chosen differently. |
| **Hidden** | pass `None`, e.g. `mmr_lambda=None` | Hidden from the model's tool schema AND omitted from the search call entirely. Only optional parameters can be hidden this way. |
| **Exposed** (default) | omit the kwarg entirely | Included in the model's tool schema with the default below, so the model chooses a value per call. |

`scope` accepts:

| Scope | Result |
|-------|--------|
| `edges` (default) | facts and relationships |
| `nodes` | entities and their summaries |
| `episodes` | raw text data (unstructured text, messages, or JSON) |
| `observations` | derived memories |
| `thread_summaries` | incremental thread summaries |
| `auto` | Zep's own pre-assembled mix of results |

#### ZepMemoryService

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `zep` | `AsyncZep` | Yes | -- | Initialised Zep async client |
| `scope` | `str` | No | `"edges"` | Graph search scope used by `search_memory`. Same enum as `ZepGraphSearchTool`: `edges`, `nodes`, `episodes`, `observations`, `thread_summaries`, `auto` |
| `limit` | `int` | No | `None` | Maximum results per search. `None` omits the parameter so the Zep SDK applies its own default |
| `resolve_user_uuid` | `Callable[[str, str], str \| None]` (sync or async) | Yes, unless `graph_uuid` is set | `None` | Receives the ADK `app_name` and `user_id`, and returns the stored Zep user UUID. The ADK `user_id` is never used as a Zep UUID |
| `graph_uuid` | `str` | No | `None` | A fixed graph UUID. When set, every search targets that graph, and `resolve_user_uuid` is not called |

## Examples

See the [examples/](examples/) directory for complete working examples:

- **[basic_agent.py](examples/basic_agent.py)** -- Full example with fact seeding and memory recall using the shared-agent pattern

## Development

### Setup

```bash
git clone https://github.com/getzep/zep.git
cd integrations/adk/python
make install
```

### Commands

```bash
make format      # Format code with ruff
make lint        # Run linting checks
make type-check  # Run mypy type checking
make test        # Run test suite
make all         # Run all checks
make pre-commit  # Development workflow with auto-fixes
make ci          # Strict CI checks
```

## Requirements

- Python 3.11+
- `zep-cloud>=4.0.0,<5`
- `google-adk>=1.19.0,<3`

## Support

- [Zep Documentation](https://help.getzep.com)
- [Google ADK Documentation](https://google.github.io/adk-docs/)
- [GitHub Issues](https://github.com/getzep/zep/issues)

## License

Apache 2.0 - see [LICENSE](LICENSE) for details.

## Contributing

Contributions are welcome! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details.
