Skip to main content

Zep AutoGen Integration

A comprehensive integration package that enables Zep to work seamlessly with Microsoft AutoGen agents, providing persistent conversation memory, knowledge graphs, and intelligent tool usage.

Installation

pip install zep-autogen

Quick Start

Basic Memory Integration

import asyncio
from zep_cloud.client import AsyncZep
from zep_autogen import ZepUserMemory
from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.openai import OpenAIChatCompletionClient


async def main():
    # Initialize Zep client
    zep_client = AsyncZep(api_key="your-zep-api-key")

    # Create Zep memory for your agent
    memory = ZepUserMemory(
        client=zep_client,
        user_id="user_123",
        thread_id="conversation_456"
    )

    # Create AutoGen agent with Zep memory
    agent = AssistantAgent(
        name="MemoryAwareAssistant",
        model_client=OpenAIChatCompletionClient(model="gpt-4.1-mini"),
        memory=[memory]  # Add Zep memory to the agent
    )

    # Your agent now has persistent memory across conversations!
    response = await agent.run(task="What's my name again?")
    print(response.messages[-1].content)


asyncio.run(main())

Tool-Equipped Agents

import asyncio
from zep_cloud.client import AsyncZep
from zep_autogen import create_search_graph_tool, create_add_graph_data_tool
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient


async def main():
    # Initialize Zep client
    zep_client = AsyncZep(api_key="your-zep-api-key")
    
    # Create tools bound to your graph
    search_tool = create_search_graph_tool(zep_client, graph_id="my_knowledge_base")
    add_tool = create_add_graph_data_tool(zep_client, graph_id="my_knowledge_base")

    # Create agent with tools and reflection
    agent = AssistantAgent(
        name="KnowledgeAssistant",
        model_client=OpenAIChatCompletionClient(model="gpt-5-mini"),
        tools=[search_tool, add_tool],
        system_message="You can search and add information to knowledge bases.",
        reflect_on_tool_use=True,  # Enables natural language responses
    )

    # Agent can autonomously use tools and provide natural responses
    await Console(agent.run_stream(task="Add this fact: Python is great for AI development"))
    await Console(agent.run_stream(task="What do you know about Python?"))


asyncio.run(main())

Features

  • Persistent Memory: Conversations and knowledge persist across agent sessions
  • Graph Memory: Store and retrieve structured knowledge using Zep's graph capabilities
  • Tool Integration: Pre-built AutoGen tools for search and data operations
  • Intelligent Retrieval: Zep's memory automatically provides relevant context
  • AutoGen Compatible: Seamlessly integrates with AutoGen's memory and tool interfaces
  • Natural Language Tool Use: Agents process tool results and provide conversational responses
  • Async Support: Full async/await support for modern applications
  • Type Safety: Fully typed with comprehensive type hints

The Zep memory loop, precisely

ZepUserMemory implements AutoGen's native Memory interface, which splits the memory loop into two hooks with different automaticity:

  • Context INJECTION is automatic. AutoGen calls update_context() before every model call. ZepUserMemory retrieves a Context Block from Zep (or runs your context_builder) and adds it to the model context as a system message. You never call this yourself.
  • PERSISTENCE is NOT automatic. AutoGen never calls add() for you -- your application must call it explicitly, typically once per user turn and once per assistant turn. This is AutoGen's design (the Memory protocol has no "after model responds" hook), not a limitation of this integration.

The canonical wiring for a full turn:

from autogen_core.memory import MemoryContent, MemoryMimeType

memory = ZepUserMemory(client=zep_client, user_id="user_123", thread_id="conversation_456")
agent = AssistantAgent(name="Assistant", model_client=model_client, memory=[memory])

user_text = "What's the weather like for my trip?"

# 1. Persist the user's turn (NOT automatic).
await memory.add(
    MemoryContent(
        content=user_text,
        mime_type=MemoryMimeType.TEXT,
        metadata={"type": "message", "role": "user"},
    )
)

# 2. Run the agent. update_context() fires automatically before the model call
#    and injects Zep's Context Block as a system message.
response = await agent.run(task=user_text)

# 3. Persist the assistant's reply (NOT automatic).
await memory.add(
    MemoryContent(
        content=str(response.messages[-1].content),
        mime_type=MemoryMimeType.TEXT,
        metadata={"type": "message", "role": "assistant"},
    )
)

If you skip step 1/3, the agent still gets Zep's existing Context Block on every turn (via update_context()), but that turn's messages are never written to Zep and will not be recallable later.

Lazy provisioning

The Zep user and thread are created lazily, on first use, by whichever of add() / update_context() runs first -- you do not have to pre-create them. Creation is idempotent and cached per ZepUserMemory instance. Pass first_name, last_name, and email to the constructor so Zep can anchor the user's identity node in the graph, and on_created to run one-time setup (ontology, custom instructions) the first time the user is actually created:

async def setup_new_user(zep, user_id: str) -> None:
    await zep.user.add_ontology(user_id=user_id, ...)  # example one-time setup

memory = ZepUserMemory(
    client=zep_client,
    user_id="user_123",
    thread_id="conversation_456",
    first_name="Jane",
    last_name="Smith",
    email="jane@example.com",
    on_created=setup_new_user,
)

This lazy path is hot-path-wrapped: a genuine provisioning failure (or an on_created hook failure) is logged and swallowed -- it never raises into add()/update_context(). If you want provisioning failures to surface loudly (e.g. during account onboarding, before the first turn), call ensure_user/ensure_thread directly, out-of-band:

from zep_autogen import ensure_user, ensure_thread

await ensure_user(zep_client, user_id="user_123", first_name="Jane", on_created=setup_new_user)
await ensure_thread(zep_client, thread_id="conversation_456", user_id="user_123")

ZepGraphMemory has no equivalent on_created hook: it is scoped to a standalone graph_id (e.g. a shared knowledge base), not a Zep user, so there is no per-user setup step to run. Create the graph out-of-band via client.graph.create(graph_id=...) if needed.

Custom context retrieval with context_builder

By default, update_context() retrieves context via thread.get_user_context(...). Pass context_builder to replace this with custom logic -- e.g. a filtered graph search, or a different graph entirely:

from zep_autogen.memory import ContextInput

async def my_builder(ctx: ContextInput) -> str | None:
    results = await ctx.zep.graph.search(
        user_id=ctx.user_id,
        query=ctx.user_message,
        scope="edges",
    )
    if not results.edges:
        return None
    return "\n".join(edge.fact for edge in results.edges)

memory = ZepUserMemory(
    client=zep_client,
    user_id="user_123",
    thread_id="conversation_456",
    context_builder=my_builder,
)

ctx.user_message is the last user-role message's text from the AutoGen model_context passed to update_context() ("" if none is found). If the builder raises, a warning is logged and context injection is skipped for that turn -- update_context() never raises. Unlike the ADK/Microsoft Agent Framework/Pydantic AI ports of this pattern, the builder here never runs concurrently with message persistence: AutoGen's Memory protocol calls update_context() (injection) and add() (persistence) as two separate, caller-controlled steps, so there is nothing to run the builder alongside.

Customizing the injected context template

The retrieved context (whether from the default retrieval or a context_builder) is wrapped in context_template before being added to the model context as a system message. Override it with your own wording, as long as it contains a literal {context} placeholder:

memory = ZepUserMemory(
    client=zep_client,
    user_id="user_123",
    thread_id="conversation_456",
    context_template="Relevant background:\n{context}",
)

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

Configuration

Environment Variables

# Required: Your Zep Cloud API key
export ZEP_API_KEY="your-zep-api-key"

Memory Classes

ZepUserMemory

For conversational memory that persists across threads:

  • client (AsyncZep): Your Zep client instance
  • user_id (str): Unique identifier for the user
  • thread_id (str, optional): Thread/conversation identifier
  • first_name, last_name, email (str, optional): Passed to user.add during lazy provisioning; helps Zep anchor the user's identity node in the graph
  • on_created (optional): Async hook run exactly once, only when the user is newly created during lazy provisioning
  • context_builder (optional): Async callable replacing the default context retrieval in update_context() -- see "Custom context retrieval" below
  • context_template (str, optional): Template wrapping injected context (default: DEFAULT_CONTEXT_TEMPLATE)

ZepGraphMemory

For knowledge graph storage and retrieval:

  • client (AsyncZep): Your Zep client instance
  • graph_id (str): Identifier for the knowledge graph

Tool Functions

create_search_graph_tool

Creates a search tool bound to a graph or user:

  • client (AsyncZep): Your Zep client instance
  • graph_id (str, optional): Graph to search (for general knowledge graphs)
  • user_id (str, optional): User to search (for user knowledge graphs)
  • pinned_params (dict, optional): Fix a graph.search parameter to a constant value, hiding it from the model's tool schema.
  • hidden_params (set, optional): Hide a parameter from the model's tool schema without pinning it -- Zep's own server-side default applies.
  • search_filters (dict, optional), bfs_origin_node_uuids (list, optional): constructor-only, never exposed to the model.
  • scope, limit (optional): Legacy back-compat aliases that pin (and hide) the corresponding parameter -- equivalent to pinned_params={"scope": ..., "limit": ...}.

Pin-or-expose schema. By default, the tool exposes five graph.search parameters to the model, each with a documented default:

Parameter Default Description
scope "edges" One of edges, nodes, episodes, observations, thread_summaries, auto
reranker "rrf" One of rrf, mmr, node_distance, episode_mentions, cross_encoder
limit 10 Maximum number of results
mmr_lambda None Diversity/relevance balance (only used when reranker="mmr")
center_node_uuid None Center node for reranker="node_distance"
# Expose everything to the model (default)
tool = create_search_graph_tool(zep_client, user_id="user_123")

# Pin scope and limit -- hidden from the model, always sent as given
tool = create_search_graph_tool(
    zep_client,
    user_id="user_123",
    pinned_params={"scope": "nodes", "limit": 5},
)

# Hide mmr_lambda from the schema without pinning it -- Zep's own default applies
tool = create_search_graph_tool(
    zep_client, user_id="user_123", hidden_params={"mmr_lambda"}
)

AutoGen's FunctionTool derives its JSON schema strictly from the wrapped Python function's typed signature -- there is no raw-JSON-schema constructor argument like some other frameworks provide. create_search_graph_tool implements pin-or-expose by dynamically building that signature: exposed parameters become real, typed parameters of the function AutoGen introspects, while pinned/hidden parameters are never part of the function's signature at all.

create_add_graph_data_tool

Creates a data addition tool bound to a graph or user:

  • client (AsyncZep): Your Zep client instance
  • graph_id (str, optional): Graph to add data to
  • user_id (str, optional): User to add data for

Size limits

Zep rejects over-long payloads with an HTTP 400. This integration truncates instead of letting the call fail, logging only the before/after lengths (never the content):

  • Thread messages (ZepUserMemory.add, message type): truncated to 4,000 characters (Zep's hard limit is 4,096).
  • Graph data (ZepGraphMemory.add, create_add_graph_data_tool): truncated to 9,900 characters, a safety margin under Zep's graph.add ceiling.

Examples

Memory Integration

Tool Integration

Multi-Agent with Shared Memory

# Multiple agents can share the same memory context
shared_memory = ZepUserMemory(
    client=zep_client,
    user_id="team_project", 
    thread_id="brainstorm_session"
)

researcher = AssistantAgent(
    name="Researcher",
    model_client=model_client,
    memory=[shared_memory]
)

writer = AssistantAgent(
    name="Writer",
    model_client=model_client, 
    memory=[shared_memory]
)

Advanced Usage

Graph Memory with Ontology

Define structured entities for better knowledge organization:

from zep_cloud.external_clients.ontology import EntityModel, EntityText
from pydantic import Field

class ProgrammingLanguage(EntityModel):
    paradigm: EntityText = Field(description="programming paradigm")  
    use_case: EntityText = Field(description="primary use cases")

# Set graph ontology
await zep_client.graph.set_ontology(
    entities={"ProgrammingLanguage": ProgrammingLanguage},
    edges={}
)

# Use graph memory with ontology
memory = ZepGraphMemory(
    client=zep_client,
    graph_id="tech_knowledge",
    search_filters=SearchFilters(
        node_labels=["ProgrammingLanguage"],
    ),
)

Tool Usage

agent = AssistantAgent(
    name="Assistant",
    model_client=model_client,
    tools=[search_tool],
    reflect_on_tool_use=True, 
)

# Use streaming console for tool visualization
await Console(agent.run_stream(task="Search for Python information"))

Development

Setup

# Install development dependencies
make install

# Run pre-commit checks (format, lint, type-check, test)
make pre-commit

Available Commands

  • make format - Format code with ruff
  • make lint - Run linting checks
  • make type-check - Run type checking with mypy
  • make test - Run tests
  • make all - Run all checks

Requirements

  • Python 3.11+
  • zep-cloud>=3.23.0
  • autogen-agentchat>=0.7.0
  • autogen-ext[azure,openai]>=0.7.0

License

MIT License - see LICENSE for details.

Support

Contributing

Contributions are welcome! Please see our Contributing Guide for details.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zep_autogen-1.2.1.tar.gz (46.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zep_autogen-1.2.1-py3-none-any.whl (28.2 kB view details)

Uploaded Python 3

File details

Details for the file zep_autogen-1.2.1.tar.gz.

File metadata

  • Download URL: zep_autogen-1.2.1.tar.gz
  • Upload date:
  • Size: 46.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for zep_autogen-1.2.1.tar.gz
Algorithm Hash digest
SHA256 089d85f71688c407bec672b34798dd99672b7a4194d05abf377ab35ec2150631
MD5 c0310f7f4d1bacfedcb1577134fde303
BLAKE2b-256 c60338d956d70b05b33f3c96cb453b39fd4b01ca1ae273ed26b174dd9bc351b6

See more details on using hashes here.

Provenance

The following attestation bundles were made for zep_autogen-1.2.1.tar.gz:

Publisher: release-integrations.yml on getzep/zep

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zep_autogen-1.2.1-py3-none-any.whl.

File metadata

  • Download URL: zep_autogen-1.2.1-py3-none-any.whl
  • Upload date:
  • Size: 28.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for zep_autogen-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6b88ad16bb43b6c3aece664cbabf6f1c33a9360a5087ebc3f751ed2ee05bce72
MD5 a1f48e5ac7c08e11babd9886186c9c17
BLAKE2b-256 d408f7bf8b6792ba2ac054e274bb55b02f1f5bc715a3b21e7cba76949c3b7f1a

See more details on using hashes here.

Provenance

The following attestation bundles were made for zep_autogen-1.2.1-py3-none-any.whl:

Publisher: release-integrations.yml on getzep/zep

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.2.1 This release

2 files

1.2.0

2 files

1.1.1

2 files

1.1.0

2 files

1.0.0

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page