# Welcome to Zep!
Connect your AI coding assistant to Zep's docs: [MCP server & llms.txt](/coding-with-llms)
Zep is a context engineering platform that systematically assembles personalized context—user preferences, traits, and business data—for reliable agent applications. Zep combines agent memory, Graph RAG, and context assembly capabilities to deliver comprehensive personalized context that reduces hallucinations and improves accuracy.
Learn about Zep's context engineering platform, temporal knowledge graphs, and agent memory capabilities.
Get up and running with Zep in minutes, whether you code in Python, TypeScript, or Go.
Discover practical recipes and patterns for common use cases with Zep.
Comprehensive API documentation for Zep's SDKs in Python, TypeScript, and Go.
Migrate from Mem0 to Zep in minutes.
Learn about Graphiti, Zep's open-source temporal knowledge graph framework.
# Key Concepts
> Understanding Zep's context engineering platform and temporal knowledge graphs.
Looking to just get coding? Check out our [Quickstart](/quickstart).
Zep is a context engineering platform that systematically assembles personalized context—user preferences, traits, and business data—for reliable agent applications. Zep combines Graph RAG, agent memory, and context assembly capabilities to deliver comprehensive personalized context that reduces hallucinations and improves accuracy.
| Concept | Description | Docs |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| Knowledge Graph | Zep's unified knowledge store for agents. Nodes represent entities, edges represent facts/relationships. The graph updates dynamically in response to new data. | [Docs](/understanding-the-graph) |
| Zep's Context Block | Optimized string containing a user summary and facts from the knowledge graph most relevant to the current thread. Also contains dates when facts became valid and invalid. Provide this to your chatbot as context. | [Docs](/retrieving-context#zeps-context-block) |
| Fact Invalidation | When new data invalidates a prior fact, the time the fact became invalid is stored on that fact's edge in the knowledge graph. | [Docs](/facts) |
| JSON/text/message | Types of data that can be ingested into the knowledge graph. Can represent business data, documents, chat messages, emails, etc. | [Docs](/adding-data-to-the-graph) |
| Custom Entity/Edge Types | Feature allowing use of Pydantic-like classes to customize creation/retrieval of entities and relations in the knowledge graph. | [Docs](/customizing-graph-structure#custom-entity-and-edge-types) |
| Graph | Represents an arbitrary knowledge graph for storing up-to-date knowledge about an object or system. For storing up-to-date knowledge about a user, a user graph should be used. | [Docs](/graph-overview) |
| User Graph | Special type of graph for storing personalized context for a user of your application. | [Docs](/users) |
| User | A user in Zep represents a user of your application, and has its own User Graph and thread history. | [Docs](/users) |
| Threads | Conversation threads of a user. By default, all messages added to any thread of that user are ingested into that user's graph. | [Docs](/threads) |
| `graph.add` & `thread.add_messages` | Methods for adding data to a graph and user graph respectively. | [Docs](/adding-data-to-the-graph) [Docs](/adding-messages) |
| `graph.search` & `thread.get_user_context` | Low level and high level methods respectively for retrieving from the knowledge graph. | [Docs](/searching-the-graph) [Docs](/retrieving-context) |
| User Summary Instructions | Customize how Zep generates entity summaries for users in their knowledge graph. Up to 5 custom instructions per user to guide summary generation. | [Docs](/users#user-summary-instructions) |
| Agentic Tool | Use Zep's context retrieval methods as agentic tools, enabling your agent to query for relevant information from the user's knowledge graph. | [Docs](/quickstart#use-zep-as-an-agentic-tool) |
# Zep use cases
> Common applications for Zep's context engineering platform.
| Use case | Purpose | Implementation |
| ----------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Dynamic Graph RAG | Provide your agent with up-to-date knowledge of an object/system | Add/stream all relevant data to a Graph ([docs](/adding-data-to-the-graph)), chunking first if needed ([docs](/adding-data-to-the-graph#data-size-limit-and-chunking)), and retrieve from the graph by constructing a custom context block ([docs](/cookbook/advanced-context-block-construction)) |
| Agent memory | Provide your agent with up-to-date knowledge of a user | Add/stream user messages and user business data to a User Graph ([docs](/adding-messages)), and retrieve user context as the context block returned from `thread.get_user_context` ([docs](/retrieving-context)), and provide this context block to your agent before responding |
| Voice agents | Provide up-to-date knowledge with extremely low latency to a voice agent | Similar to other implementations, except incorporating latency optimizations ([docs](/performance)) |
# What is context engineering?
> The discipline of assembling all necessary information for reliable agent applications.
Context Engineering is the discipline of assembling all necessary information, instructions, and tools around a LLM to help it accomplish tasks reliably. Unlike simple prompt engineering, context engineering involves building dynamic systems that provide the right information in the right format so LLMs can perform consistently.
The core challenge: LLMs are stateless and only know what's in their immediate context window. Context engineering bridges this gap by systematically providing relevant background knowledge, user history, business data, and tool outputs.
Using [business data and/or user chat histories](/concepts#business-data-vs-chat-message-data), Zep automatically constructs a [temporal knowledge graph](/graph-overview) to reflect the state of an object/system or a user. The knowledge graph contains entities, relationships, and facts related to your object/system or user. As facts change or are superseded, [Zep updates the graph](/concepts#managing-changes-in-facts-over-time) to reflect their new state. Through systematic context engineering, Zep provides your agent with the comprehensive information needed to deliver personalized responses and solve problems. This reduces hallucinations, improves accuracy, and reduces the cost of LLM calls.
# How Zep fits into your application
> Understanding how Zep integrates with your application architecture.
Your application sends Zep business data (JSON, unstructured text) and/or messages. Business data sources may include CRM applications, emails, billing data, or conversations on other communication platforms like Slack.
Zep automatically fuses this data together on a temporal knowledge graph, building a holistic view of the object/system or user and the relationships between entities. Zep offers a number of APIs for [adding and retrieving context](/retrieving-context). In addition to populating a prompt with Zep's engineered context, Zep's search APIs can be used to build [agentic tools](/concepts#using-zep-as-an-agentic-tool).
The example below shows Zep's context block resulting from a call to `thread.get_user_context()`. This is Zep's engineered context block that can be added to your prompt and contains a user summary and facts relevant to the current conversation with a user. For more about the temporal context of facts, see [Managing changes in facts over time](/concepts#managing-changes-in-facts-over-time).
## Context Block
[Zep's Context Block](/retrieving-context#zeps-context-block) is Zep's engineered context string containing a user summary and relevant facts for the thread. It is always present in the result of `thread.get_user_context()`
call and can be optionally [received with the response of `thread.add_messages()` call](/performance#get-the-context-block-sooner).
The Context Block provides low latency (P95 \< 200ms) while preserving detailed information from the user's graph. Read more about Zep's Context Block [here](/retrieving-context#zeps-context-block).
```python Python
# Get context for the thread
user_context = client.thread.get_user_context(thread_id=thread_id)
# Access the context block (for use in prompts)
context_block = user_context.context
print(context_block)
```
```typescript TypeScript
// Get context for the thread
const userContext = await client.thread.getUserContext(threadId);
// Access the context block (for use in prompts)
const contextBlock = userContext.context;
console.log(contextBlock);
```
```go Go
import (
"context"
v3 "github.com/getzep/zep-go/v3"
)
// Get context for the thread
userContext, err := client.Thread.GetUserContext(context.TODO(), threadId, nil)
if err != nil {
log.Fatal("Error getting context:", err)
}
// Access the context block (for use in prompts)
contextBlock := userContext.Context
fmt.Println(contextBlock)
```
The Context Block includes a user summary and relevant facts:
```text
# This is the user summary
Emily Painter is a user with account ID Emily0e62 who uses digital art tools for creative work. She maintains an active account with the service, though has recently experienced technical issues with the Magic Pen Tool. Emily values reliable payment processing and seeks prompt resolution for account-related issues. She expects clear communication and efficient support when troubleshooting technical problems.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
- Emily is experiencing issues with logging in. (2024-11-14 02:13:19+00:00 - present)
- User account Emily0e62 has a suspended status due to payment failure. (2024-11-14 02:03:58+00:00 - present)
- user has the id of Emily0e62 (2024-11-14 02:03:54 - present)
- The failed transaction used a card with last four digits 1234. (2024-09-15 00:00:00+00:00 - present)
- The reason for the transaction failure was 'Card expired'. (2024-09-15 00:00:00+00:00 - present)
- user has the name of Emily Painter (2024-11-14 02:03:54 - present)
- Account Emily0e62 made a failed transaction of 99.99. (2024-07-30 00:00:00+00:00 - 2024-08-30 00:00:00+00:00)
```
You can then include this context in your system prompt:
| MessageType | Content |
| ----------- | ------------------------------------------------------ |
| `System` | Your system prompt
`{Zep context block}` |
| `Assistant` | An assistant message stored in Zep |
| `User` | A user message stored in Zep |
| ... | ... |
| `User` | The latest user message |
# Retrieval philosophy
> Understanding Zep's approach to optimizing for recall and latency.
Zep's retrieval system is designed with two primary goals: **high recall** and **low latency**. This is a deliberate architectural choice that differs from systems optimized for precision.
## Understanding recall vs. precision
Think of recall and precision as two different ways to measure retrieval quality:
* **Recall** measures completeness: "Did we find all the relevant information?"
* **Precision** measures accuracy: "Is everything we returned actually relevant?"
In practical terms:
* **High recall** means you get all the relevant results, but might also get some less relevant ones
* **High precision** means everything returned is highly relevant, but you might miss important information
## The tradeoff in practice
| Approach | What You Get | What You Risk | Best For |
| ---------------------------------------- | --------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------- |
| **Optimize for Recall** (Zep's approach) | All relevant facts, plus some less relevant results | Larger context with some noise | Agents that need complete information to make decisions; real-time applications |
| **Optimize for Precision** | Only highly relevant results | Missing critical facts that could cause task failure | Use cases where context size is severely constrained; manual review workflows |
### Example scenario
User query: *"What did we discuss about the Q2 marketing budget?"*
| Retrieval Approach | Results Returned | Outcome |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Recall-Optimized** (Zep) | • Q2 marketing budget discussion ✓ • Related Q2 sales projections ✓ • Q3 budget planning mention ⚠️ • Q2 hiring costs mentioning marketing ⚠️ | Agent has complete context, including tangentially related information. Can successfully answer follow-up questions about budget revisions. |
| **Precision-Optimized** | • Q2 marketing budget discussion ✓ • Related Q2 sales projections ✓ | Clean, focused results, but **missing** a separate conversation about budget revisions that didn't explicitly mention "marketing budget." Agent may provide incomplete information. |
## Why recall over precision?
Agents need comprehensive context to make informed decisions. Missing a critical fact can cause an agent to fail its task or provide incorrect information. By optimizing for recall, Zep ensures that relevant information is available to the agent, even if that means returning more results than strictly necessary.
The underlying principle: it's better to provide complete information and let the agent or downstream LLM filter what's relevant than to risk omitting something important.
## Why latency matters
Real-time applications like conversational AI, live customer support, and interactive agents require fast responses. Zep's retrieval architecture is optimized to return results in milliseconds, enabling seamless user experiences without perceptible delays.
## Tuning the recall-precision tradeoff
The recall-optimized approach described here is how Zep is tuned **out of the box**. However, Zep provides several mechanisms to adjust this tradeoff for different use cases:
* **Limit search results**: Control the maximum number of results returned
* **Apply filters**: Narrow retrieval to specific time ranges, Entity and/or Edge labels, or other criteria
* **Adjust search parameters**: Fine-tune ranking and relevance thresholds
These controls allow you to shift toward precision when your application demands it, while maintaining Zep's fast retrieval performance.
## Balancing context size
While recall is our priority, Zep does consider token count when returning results. We balance the size of the resulting context with the goal of providing complete information, but when in doubt, we err on the side of ensuring your agent has what it needs to succeed.
# Users and User Graphs
> Understanding user management and knowledge graph integration in Zep
## Overview
A User represents an individual interacting with your application. Each User can have multiple Threads associated with them, allowing you to track and manage their interactions over time. Additionally, each user has an associated User Graph which stores the context for that user.
## Users
The unique identifier for each user is their `UserID`. This can be any string value, such as a username, email address, or UUID.
**Users Enable Simple User Privacy Management**
Deleting a User will delete all Threads and thread artifacts associated with that User with a single API call, making it easy to handle Right To Be Forgotten requests.
### Ensuring Your User Data Is Correctly Mapped to the Knowledge Graph
Adding your user's `email`, `first_name`, and `last_name` ensures that chat messages and business data are correctly mapped to the user node in the Zep knowledge graph.
For example, if business data contains your user's email address, it will be related directly to the user node.
You can associate rich business context with a User:
* `user_id`: A unique identifier of the user that maps to your internal User ID.
* `email`: The user's email.
* `first_name`: The user's first name.
* `last_name`: The user's last name.
## User Graphs
Each user has an associated User Graph that stores their context across all threads. This graph-based context system provides several important capabilities:
### Cross-Thread Context Integration
The knowledge graph does not separate the data from different threads, but integrates the data together to create a unified picture of the user. So the `thread.get_user_context` method doesn't return context derived only from that thread, but instead returns whatever user-level context is most relevant to that thread, based on the thread's most recent messages.
This means that insights and information learned in one conversation thread are automatically available in all other threads for the same user, creating a coherent and continuous context experience.
### Privacy and RTBF Capabilities
When you delete a user, all associated data is removed:
* All threads belonging to that user
* All thread artifacts (messages, metadata)
* The entire user graph and all knowledge extracted from conversations
This single-operation approach makes it simple to handle Right To Be Forgotten (RTBF) requests and comply with privacy regulations.
### Default Ontology for User Graphs
User graphs utilize Zep's default ontology, consisting of default entity types and default edge types that affect how the graph is built. You can read more about default and custom graph ontology in the [Customizing Graph Structure](/customizing-graph-structure) guide.
Each user graph comes with default entity and edge types that help classify and structure information extracted from conversations. You can also disable the default entity and edge types for specific users if you need precise control over your graph structure.
### The User Node
**User summary and the user node**
Each user has a single unique user node in their graph representing the user themselves. The user summary generated from user summary instructions lives on this user node. You can retrieve the user node and its summary using the `get_node` method described in the SDK reference.
The user node serves as a central hub in the knowledge graph, connecting all information about that user. It stores a high-level summary of the user that can be customized through [User Summary Instructions](/user-summary-instructions).
## Next Steps
Now that you understand how Users and User Graphs work together, you can:
* Learn about [Threads](/threads) and how they relate to users
* Discover how to [add messages to threads](/adding-messages)
* Learn how to [retrieve context for your agent](/retrieving-context)
* Explore [customizing user summaries](/user-summary-instructions)
* Understand more about [Graph Concepts](/graph-overview)
# Threads
> Understanding conversation threading in Zep
## Overview
Threads represent a conversation. Each User can have multiple threads, and each thread is a sequence of chat messages.
Chat messages are added to threads using [`thread.add_messages`](/adding-messages), which both adds those messages to the thread history and ingests those messages into the user-level knowledge graph. The user knowledge graph contains data from all of that user's threads to create an integrated understanding of the user.
## Relationship Between Users and Threads
`threadIds` are arbitrary identifiers that you can map to relevant business objects in your app, such as users or a conversation a user might have with your app. Before you create a thread, make sure you have created a user first.
## Automatic Cache Warming
When you create a new thread, Zep automatically warms the cache for that user's graph data in the background. This optimization improves query latency for graph operations on newly created threads by pre-loading the user's data into the hot cache tier.
The warming operation runs asynchronously and does not block the thread creation response. No additional action is required on your part—this happens automatically whenever you create a thread for a user with an existing graph.
For more information about Zep's multi-tier caching architecture and manual cache warming, see [Warming the User Cache](/performance#warming-the-user-cache).
## Next Steps
Now that you understand how Threads work, you can:
* Learn about [Users and User Graphs](/users-and-user-graphs)
* Discover how to [add messages to threads](/adding-messages)
* Learn how to [retrieve context for your agent](/retrieving-context)
* Understand more about [Graph Concepts](/graph-overview)
# Graph Overview
Zep's temporal knowledge graph powers its context engineering capabilities, including agent memory and Graph RAG. Zep's graph is built on [Graphiti](/graphiti/graphiti/overview), Zep's open-source temporal graph library, which is fully integrated into Zep. Developers do not need to interact directly with Graphiti or understand its underlying implementation.
A knowledge graph is a network of interconnected facts, such as *"Kendra loves
Adidas shoes."* Each fact is a *"triplet"* represented by two entities, or
nodes (*"Kendra", "Adidas shoes"*), and their relationship, or edge
(*"loves"*).
Knowledge Graphs have been explored extensively for information retrieval.
What makes Zep unique is its ability to autonomously build temporal knowledge graphs
while handling changing relationships and maintaining historical context.
Zep automatically constructs a temporal knowledge graph for each of your users. The knowledge graph contains entities, relationships, and facts related to your user, while automatically handling changing relationships and facts over time.
Here's an example of how Zep might extract graph data from a chat message, and then update the graph once new information is available:
Each node and edge contains certain attributes - notably, a fact is always stored as an edge attribute. There are also datetime attributes for when the fact becomes valid and when it becomes invalid.
## Graph Data Structure
Zep's graph database stores data in three main types:
1. Entity edges (edges): Represent relationships between nodes and include semantic facts representing the relationship between the edge's nodes.
2. Entity nodes (nodes): Represent entities extracted from episodes, containing summaries of relevant information.
3. Episodic nodes (episodes): Represent raw data stored in Zep, either through chat history or the `graph.add` endpoint.
## Working with the Graph
To learn more about interacting with Zep's graph, refer to the following sections:
* [Adding Data to the Graph](./adding-data-to-the-graph.mdx): Learn how to add new data to the graph.
* [Reading Data from the Graph](./reading-data-from-the-graph.mdx): Discover how to retrieve information from the graph.
* [Searching the Graph](./searching-the-graph.mdx): Explore techniques for efficiently searching the graph.
These guides will help you leverage the full power of Zep's knowledge graph in your applications.
# Zep vs Graph RAG
> How Zep compares to traditional GraphRAG approaches
While traditional GraphRAG excels at static document summarization, Zep is designed for dynamic and frequently updated datasets with continuous data updates, temporal fact tracking, and sub-second query latency. This makes Zep particularly suitable for providing an agent with up-to-date knowledge about an object/system or user.
| Aspect | GraphRAG | Zep |
| ---------------------- | ------------------------------------- | ------------------------------------------------ |
| Primary Use | Static document summarization | Dynamic data management |
| Data Handling | Batch-oriented processing | Continuous, incremental updates |
| Knowledge Structure | Entity clusters & community summaries | Episodic data, semantic entities, communities |
| Retrieval Method | Sequential LLM summarization | Hybrid semantic, keyword, and graph-based search |
| Adaptability | Low | High |
| Temporal Handling | Basic timestamp tracking | Explicit bi-temporal tracking |
| Contradiction Handling | LLM-driven summarization judgments | Temporal edge invalidation |
| Query Latency | Seconds to tens of seconds | Typically sub-second latency |
| Custom Entity Types | No | Yes, customizable |
| Scalability | Moderate | High, optimized for large datasets |
# Facts
> Precise, time-stamped information capturing detailed relationships about specific events
## Overview
Facts are precise and time-stamped information stored on [edges](/sdk-reference/graph/edge/get) that capture detailed relationships about specific events. They include `valid_at` and `invalid_at` timestamps, ensuring temporal accuracy and preserving a clear history of changes over time.
## How Zep Updates Facts
When incorporating new data, Zep looks for existing nodes and edges in the graph and decides whether to add new nodes/edges or to update existing ones. An update could mean updating an edge (for example, indicating the previous fact is no longer valid).
Here's an example of how Zep might extract graph data from a chat message, and then update the graph once new information is available:
As shown in the example above, when Kendra initially loves Adidas shoes but later is angry that the shoes broke and states a preference for Puma shoes, Zep attempts to invalidate the fact that Kendra loves Adidas shoes and creates two new facts: "Kendra's Adidas shoes broke" and "Kendra likes Puma shoes".
Zep also looks for dates in all ingested data, such as the timestamp on a chat message or an article's publication date, informing how Zep sets the edge attributes. This assists your agent in reasoning with time.
## The Four Fact Timestamps
Each fact stored on an edge includes four different timestamp attributes that track the lifecycle of that information:
| Edge attribute | Example |
| :-------------- | :---------------------------------------------- |
| **created\_at** | The time Zep learned that the user got married |
| **valid\_at** | The time the user got married |
| **invalid\_at** | The time the user got divorced |
| **expired\_at** | The time Zep learned that the user got divorced |
The `valid_at` and `invalid_at` attributes for each fact are then included in Zep's Context Block which is given to your agent:
```text
# format: FACT (Date range: from - to)
User account Emily0e62 has a suspended status due to payment failure. (2024-11-14 02:03:58+00:00 - present)
```
## Adding or Deleting Facts
Facts are generated as part of the ingestion process. If you follow the directions for [adding data to the graph](/adding-business-data), new facts will be created.
Deleting facts is handled by deleting data from the graph. Facts will be deleted when you [delete the edge](/working-with-graphs/deleting-data-from-the-graph) they exist on.
## Choosing Between Facts and Summaries
Zep does not recommend relying solely on summaries for grounding LLM responses. While summaries provide a high-level overview, the [Context Block](/retrieving-context#zeps-context-block) should be used since it includes relevant facts (each with valid and invalid timestamps). This ensures that conversations are based on up-to-date and contextually accurate information.
# Summaries
> Contextualized entity histories that update with new information
## Overview
Every entity (node) in a Zep knowledge graph comes with a summary. The summary contains a summarized history of the facts and relationships that involve that entity.
Every time there is a new fact involving an existing entity, the summary is updated using the previous summary and the new information. This incremental update process ensures that entity summaries remain current and contextually relevant as the graph evolves.
## Summaries vs Facts
Summaries are used in the [default Zep context block](/retrieving-context#zeps-context-block). The default context block contains both facts and entity summaries because they represent different kinds of information.
Facts are granular information snippets. They capture specific, discrete pieces of knowledge with precise temporal validity.
Summaries are rich, entity-focused contextualized histories. They provide an aggregated narrative of an entity's involvement across multiple facts and relationships.
Using both leads to better performance for most use cases. The combination provides both breadth and depth of context for grounding LLM responses.
# Quick Start Guide
> Integrate Zep into your AI application in minutes
Zep is a context engineering platform that delivers the right contextual information to your AI agents at the right time. Through a temporal knowledge graph, Zep assembles relevant context from chat history, business data, and user behavior—enabling agents to make better decisions with accurate, up-to-date information. With a simple three-line API and sub-200ms retrieval latency, Zep helps you build personalized, reliable AI agents without extensive context pipeline engineering.
Get started with the example in the video using:
```bash
git clone https://github.com/getzep/zep.git
cd zep/examples/python/agent-memory-full-example
```
This guide shows you how to integrate Zep into your AI application to provide personalized context for every user interaction. You'll learn how to ingest user messages and business data, then retrieve assembled context that includes user preferences, traits, and relevant facts—all optimized for your LLM's context window.
Looking for a more in-depth understanding? Check out our [Key Concepts](/concepts) page.
Migrating from Mem0? Check out our [Mem0 Migration](/mem0-to-zep) guide.
## Install the Zep SDK
Set up your Python project, ideally with [a virtual environment](https://medium.com/@vkmauryavk/managing-python-virtual-environments-with-uv-a-comprehensive-guide-ac74d3ad8dff), and then:
```bash pip
pip install zep-cloud
```
```bash uv
uv pip install zep-cloud
```
Set up your TypeScript project and then:
```bash npm
npm install @getzep/zep-cloud
```
```bash yarn
yarn add @getzep/zep-cloud
```
```bash pnpm
pnpm install @getzep/zep-cloud
```
Set up your Go project and then:
```bash
go get github.com/getzep/zep-go/v3
```
## Initialize the Zep client
After [creating a Zep account](https://app.getzep.com/), obtaining an API key, and setting the API key as an environment variable, initialize the client once at application startup and reuse it throughout your application.
```python Python
import os
from zep_cloud.client import Zep
API_KEY = os.environ.get('ZEP_API_KEY')
client = Zep(
api_key=API_KEY,
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const API_KEY = process.env.ZEP_API_KEY;
const client = new ZepClient({
apiKey: API_KEY,
});
```
```go Go
import (
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(os.Getenv("ZEP_API_KEY")),
)
```
```
ZEP_API_KEY=your_api_key_here
```
## Create a Zep user for each of your users
Whenever users are created in your application, you need to trigger the creation of a Zep user. Make sure to include at least their first name, and ideally also their last name and email to ensure correct identification of the user in future messages. We recommend setting the Zep user ID equal to your internal user ID.
**Backfilling existing users:** For existing users, you will need to run a one-time migration to create a user for each of the existing users (simply loop through and call `user.add` for each).
Provide at least the first name and ideally the last name when calling `user.add` to ensure Zep correctly associates the user with references in your data. If needed, add this information later using the [update user](/sdk-reference/user/update) method.
```python Python
from zep_cloud.client import Zep
client = Zep(api_key=API_KEY)
# You can choose any user ID, but we recommend using your internal user ID
user_id = "your_internal_user_id"
new_user = client.user.add(
user_id=user_id,
email="jane.smith@example.com",
first_name="Jane",
last_name="Smith",
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// You can choose any user ID, but we recommend using your internal user ID
const userId = "your_internal_user_id";
const user = await client.user.add({
userId: userId,
email: "jane.smith@example.com",
firstName: "Jane",
lastName: "Smith",
});
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(option.WithAPIKey(apiKey))
// You can choose any user ID, but we recommend using your internal user ID
userID := "your_internal_user_id"
newUser, err := client.User.Add(context.TODO(), &zep.CreateUserRequest{
UserID: userID,
Email: zep.String("jane.smith@example.com"),
FirstName: zep.String("Jane"),
LastName: zep.String("Smith"),
})
if err != nil {
log.Fatalf("Failed to add user: %v", err)
}
```
## Create a Zep thread for each of your threads
Whenever a user starts a new conversation with your agent, you need to trigger the creation of a Zep thread. Learn more about [adding messages](/adding-messages).
**Backfilling prior conversations:** For prior conversations, you will need to run a one-time migration to create Zep threads for those conversations and add the prior messages to the respective Zep threads. You can loop through messages and call `thread.add_messages` for each, or use our [batch processing method](/adding-batch-data#adding-batch-message-data-to-threads) for faster concurrent processing.
```python Python
client = Zep(
api_key=API_KEY,
)
thread_id = uuid.uuid4().hex # A new thread identifier
client.thread.create(
thread_id=thread_id,
user_id=user_id,
)
```
```typescript TypeScript
const client = new ZepClient({
apiKey: API_KEY,
});
const threadId: string = uuid.v4(); // Generate a new thread identifier
await client.thread.create({
threadId: threadId,
userId: userId,
});
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
"github.com/google/uuid"
)
client := zepclient.NewClient(option.WithAPIKey(apiKey))
threadID := uuid.New().String() // Generate a new thread identifier
_, err := client.Thread.Create(context.TODO(), &zep.CreateThreadRequest{
ThreadID: threadID,
UserID: userID,
})
if err != nil {
log.Fatalf("Failed to create thread: %v", err)
}
```
## Add incoming user messages to Zep
When a new user message comes in, add the user message to Zep, providing the user's name in the message if possible.
It is important to provide the name of the user in the name field if possible, to help with graph construction.
```python Python
from zep_cloud.client import Zep
from zep_cloud.types import Message
zep_client = Zep(
api_key=API_KEY,
)
messages = [
Message(
name="Jane Smith",
role="user",
content="Who was Octavia Butler?",
)
]
response = zep_client.thread.add_messages(thread_id, messages=messages)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
import type { Message } from "@getzep/zep-cloud/api";
const zepClient = new ZepClient({
apiKey: API_KEY,
});
const messages: Message[] = [
{ name: "Jane Smith", role: "user", content: "Who was Octavia Butler?" },
];
const response = await zepClient.thread.addMessages(threadId, { messages });
```
```go Go
import (
v3 "github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
)
zepClient := zepclient.NewClient(
option.WithAPIKey(""),
)
response, err := zepClient.Thread.AddMessages(
context.TODO(),
"threadId",
&v3.AddThreadMessagesRequest{
Messages: []*v3.Message{
{
Name: v3.String("Jane Smith"),
Role: "user",
Content: "Who was Octavia Butler?",
},
},
},
)
```
## Add streaming business data to Zep
Beyond chat messages, you can provide Zep with additional context about your users by sending business data directly to their knowledge graphs. This includes user interactions with your application, transactions, support tickets, emails, transcripts—essentially any information that gives context about the user and can be represented as text.
Use the `graph.add` method to send structured, semi-structured, or unstructured text data to Zep. Include a reference to the user—their full name, user ID, or both—so Zep can correctly associate the data with the user in their knowledge graph. Read more about [adding business data](/adding-business-data).
Any text can be sent to Zep—structured JSON, semi-structured logs, or plain text descriptions. The example below shows a JSON event, but you could also send `"User Jane Smith listened to 'Bohemian Rhapsody' by Queen"` as plain text. See [Adding business data](/adding-business-data) for more data type options.
```python Python
from zep_cloud.client import Zep
import json
client = Zep(api_key=API_KEY)
# Example: User listened to a song in your application
event_data = {
"user_id": "user123",
"user_name": "Jane Smith",
"event_type": "song_played",
"song_title": "Bohemian Rhapsody",
"artist": "Queen",
"duration_seconds": 354
}
client.graph.add(
user_id="user123",
type="json",
data=json.dumps(event_data)
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({ apiKey: API_KEY });
// Example: User listened to a song in your application
const eventData = {
user_id: "user123",
user_name: "Jane Smith",
event_type: "song_played",
song_title: "Bohemian Rhapsody",
artist: "Queen",
duration_seconds: 354
};
await client.graph.add({
userId: "user123",
type: "json",
data: JSON.stringify(eventData)
});
```
```go Go
import (
"context"
"encoding/json"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(option.WithAPIKey(apiKey))
// Example: User listened to a song in your application
eventData := map[string]interface{}{
"user_id": "user123",
"user_name": "Jane Smith",
"event_type": "song_played",
"song_title": "Bohemian Rhapsody",
"artist": "Queen",
"duration_seconds": 354,
}
jsonBytes, err := json.Marshal(eventData)
if err != nil {
log.Fatalf("Failed to marshal JSON: %v", err)
}
userID := "user123"
_, err = client.Graph.Add(context.TODO(), &zep.AddDataRequest{
UserID: &userID,
Type: zep.GraphDataTypeJSON,
Data: string(jsonBytes),
})
if err != nil {
log.Fatalf("Failed to add event data: %v", err)
}
```
## Retrieve Zep context block
After adding the user message to the thread and before generating the AI response, retrieve the Zep context block, which will contain the most relevant information to the user's message from the user's knowledge graph.
### Use the default context block
Zep's default Context Block is an optimized, automatically assembled string that combines semantic search, full text search, and breadth first search to return context that is highly relevant to the user's current conversation slice, utilizing the past two messages.
The Context Block provides low latency (P95 \< 200ms) while preserving detailed information from the user's graph.
```python Python
# Get context for the thread
user_context = client.thread.get_user_context(thread_id=thread_id)
# Access the context block (for use in prompts)
context_block = user_context.context
print(context_block)
```
```typescript TypeScript
// Get context for the thread
const userContext = await client.thread.getUserContext(threadId);
// Access the context block (for use in prompts)
const contextBlock = userContext.context;
console.log(contextBlock);
```
```go Go
import (
"context"
v3 "github.com/getzep/zep-go/v3"
)
// Get context for the thread
userContext, err := client.Thread.GetUserContext(context.TODO(), threadId, nil)
if err != nil {
log.Fatal("Error getting context:", err)
}
// Access the context block (for use in prompts)
contextBlock := userContext.Context
fmt.Println(contextBlock)
```
The Context Block includes a user summary and relevant facts:
```text
# This is the user summary
Emily Painter is a user with account ID Emily0e62 who uses digital art tools for creative work. She maintains an active account with the service, though has recently experienced technical issues with the Magic Pen Tool. Emily values reliable payment processing and seeks prompt resolution for account-related issues. She expects clear communication and efficient support when troubleshooting technical problems.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
- Emily is experiencing issues with logging in. (2024-11-14 02:13:19+00:00 - present)
- User account Emily0e62 has a suspended status due to payment failure. (2024-11-14 02:03:58+00:00 - present)
- user has the id of Emily0e62 (2024-11-14 02:03:54 - present)
- The failed transaction used a card with last four digits 1234. (2024-09-15 00:00:00+00:00 - present)
- The reason for the transaction failure was 'Card expired'. (2024-09-15 00:00:00+00:00 - present)
- user has the name of Emily Painter (2024-11-14 02:03:54 - present)
- Account Emily0e62 made a failed transaction of 99.99. (2024-07-30 00:00:00+00:00 - 2024-08-30 00:00:00+00:00)
```
### Use a custom context block
Using [custom context templates](/context-templates), you can easily design your own custom context block type and retrieve that from the `thread.get_user_context()` method instead.
#### Create your custom context template
Create your custom context template for your Zep project and save the template ID. See the [Context Templates](/context-templates) guide for more information on template syntax and variables.
```python Python
from zep_cloud import Zep
client = Zep(api_key="YOUR_API_KEY")
client.context.create_context_template(
template_id="customer-support",
template="""# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=10}
# KEY ENTITIES
%{entities limit=5}"""
)
```
```typescript TypeScript
import { Zep } from "@getzep/zep-cloud";
const client = new Zep({ apiKey: "YOUR_API_KEY" });
await client.context.createContextTemplate({
templateId: "customer-support",
template: `# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=10}
# KEY ENTITIES
%{entities limit=5}`
});
```
```go Go
import (
"context"
zep "github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/context"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey("YOUR_API_KEY"),
)
_, err := client.CreateContextTemplate(
context.TODO(),
&zep.CreateContextTemplateRequest{
TemplateID: "customer-support",
Template: `# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=10}
# KEY ENTITIES
%{entities limit=5}`,
},
)
```
#### Retrieve custom context block using thread.get\_user\_context()
Retrieve your custom context block using the `thread.get_user_context()` method, passing in your template ID.
```python Python
from zep_cloud import Zep
client = Zep(api_key="YOUR_API_KEY")
user_context = client.thread.get_user_context(
thread_id="thread_id",
template_id="customer-support"
)
context_block = user_context.context
```
```typescript TypeScript
import { Zep } from "@getzep/zep-cloud";
const client = new Zep({ apiKey: "YOUR_API_KEY" });
const userContext = await client.thread.getUserContext("thread_id", {
templateId: "customer-support"
});
const contextBlock = userContext.context;
```
```go Go
import (
"context"
zep "github.com/getzep/zep-go/v3"
threadclient "github.com/getzep/zep-go/v3/thread/client"
"github.com/getzep/zep-go/v3/option"
)
client := threadclient.NewClient(
option.WithAPIKey("YOUR_API_KEY"),
)
templateID := "customer-support"
userContext, err := client.GetUserContext(
context.TODO(),
"thread_id",
&zep.ThreadGetUserContextRequest{
TemplateID: &templateID,
},
)
contextBlock := userContext.Context
```
## Add context block to agent context window
As outlined in our [retrieval philosophy](/retrieval-philosophy), Zep optimizes for high recall over precision, meaning we err on the side of including more results even if some are less relevant. Most agents will automatically reference only the most relevant information when responding to the user message.
Once you've retrieved the Context Block, you can include this string in your agent's context window.
### Option 1: Add context block to system prompt
You can append the context block directly to your system prompt. Note that this means the system prompt dynamically updates on every chat turn.
| MessageType | Content |
| ----------- | ------------------------------------------------------ |
| `System` | Your system prompt
`{Zep context block}` |
| `Assistant` | An assistant message stored in Zep |
| `User` | A user message stored in Zep |
| ... | ... |
| `User` | The latest user message |
### Option 2: Append context block as "context message"
Dynamically updating the system prompt on every chat turn has the downside of preventing [prompt caching](https://platform.openai.com/docs/guides/prompt-caching) with LLM providers. In order to reap the benefits of prompt caching while still adding a new Zep context block in every chat, you can append the context block as a "context message" (technically a tool message) just after the user message in the chat history. On each new chat turn, remove the prior context message and replace it with the new one. This allows everything before the context message to be cached.
| MessageType | Content |
| ----------- | -------------------------------------- |
| `System` | Your system prompt (static, cacheable) |
| `Assistant` | An assistant message stored in Zep |
| `User` | A user message stored in Zep |
| ... | ... |
| `User` | The latest user message |
| `Tool` | `{Zep context block}` |
## Add assistant response to Zep
After generating the assistant response, add it to Zep to continue building the user's knowledge graph.
```python Python
from zep_cloud.types import Message
messages = [
Message(
name="AI Assistant",
role="assistant",
content="Octavia Butler was an influential American science fiction writer...",
)
]
response = zep_client.thread.add_messages(thread_id, messages=messages)
```
```typescript TypeScript
import type { Message } from "@getzep/zep-cloud/api";
const messages: Message[] = [
{
name: "AI Assistant",
role: "assistant",
content: "Octavia Butler was an influential American science fiction writer...",
},
];
const response = await zepClient.thread.addMessages(threadId, { messages });
```
```go Go
import (
v3 "github.com/getzep/zep-go/v3"
)
assistantName := "AI Assistant"
messages := []*v3.Message{
{
Name: &assistantName,
Role: "assistant",
Content: "Octavia Butler was an influential American science fiction writer...",
},
}
response, err := zepClient.Thread.AddMessages(
context.TODO(),
threadId,
&v3.AddThreadMessagesRequest{
Messages: messages,
},
)
```
## Next steps
Now that you've integrated Zep into your application, you can explore additional features:
* **[Customize graph structure to your domain](/customizing-graph-structure)** - Define custom entity and edge types to structure domain-specific information.
* **[Add user interactions and metadata](/adding-data-to-the-graph)** - Any ongoing user interactions or one-time user profile information can be added to the user's knowledge graph.
* **[Custom context templates](/context-templates)** - Design custom context block formats tailored to your application's needs.
* **[User summary instructions](/users#user-summary-instructions)** - Customize how Zep generates summaries of user data in their knowledge graph.
# Evaluate Zep for Your Use Case
> Run end-to-end context evaluations using Zep's evaluation framework
This guide shows you how to use Zep's evaluation harness to systematically test your context implementation.
## Why use the evaluation harness?
With this evaluation harness, you can:
* **Evaluate Zep's performance for your use case**: Test how well Zep retrieves relevant information and answers questions specific to your domain and conversation patterns.
* **Systematically experiment with Zep ontologies, search strategies, and other capabilities**: Compare different configurations to optimize retrieval accuracy and response quality.
* **Develop a suite of tests that can be run in CI**: Continuously evaluate your application for regressions, ensuring that changes to your data model or Zep configuration don't degrade context retrieval performance over time.
The harness provides objective metrics for context completeness and answer accuracy, enabling data-driven decisions about context configuration and search strategies.
## Steps
### Clone the Zep repository
Clone the [Zep repository](https://github.com/getzep/zep/tree/main) that includes the evaluation harness:
```bash
git clone https://github.com/getzep/zep.git
cd zep/zep-eval-harness
```
### Set up your environment
Install UV package manager for macOS/Linux:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
For other platforms, visit the [UV installation guide](https://docs.astral.sh/uv/).
Install all required dependencies using UV:
```bash
uv sync
```
Set up your API keys by copying the example file and adding your keys:
```bash
cp .env.example .env
```
Edit `.env` and add your keys:
```bash
ZEP_API_KEY=your_zep_api_key_here
OPENAI_API_KEY=your_openai_api_key_here
```
Get your Zep API key at [app.getzep.com](https://app.getzep.com) and OpenAI API key at [platform.openai.com/api-keys](https://platform.openai.com/api-keys).
### Write down 3-5 example interactions
**Most important step**: This is the most critical part of the evaluation process. Take time to write down 3-5 specific examples that showcase how you want your agent to behave once it has context. These examples will be dropped into an AI prompt in the next step to automatically generate your evaluation data.
For each example, simply note what the user asks and what the agent should respond with:
```
1. User: "What is my dog's name?"
Agent: "Max"
2. User: "When is my vet appointment?"
Agent: "Friday at Dr. Peterson's clinic"
3. User: "What training classes did I sign up for?"
Agent: "Puppy training classes at PetSmart, Saturdays at 9am"
```
### Use an AI coding assistant to update the test data
Use Cursor, Copilot, Claude Code, or another AI coding assistant to automatically update the test files based on your examples.
Provide this prompt to your AI assistant:
```text
Please update the files in the zep-eval-harness to match the example interactions below.
Important: Use the existing user in `data/users.json` (user_id: zep_eval_test_user_001).
Do not create or modify any users.
First, read the README.md file in the zep-eval-harness directory to understand the
best practices for creating conversations and test cases.
Step 1 - Generate test cases first:
For `data/test_cases/`:
- Create one file: `zep_eval_test_user_001_tests.json`
- Follow the format and structure of the existing test case files
- Generate exactly 10 test cases based on my example interactions, expanding on them
with variations and related questions
- Write clear golden_answer text that specifies what information must be present
in a correct response
- Follow the best practices in the README for designing fair test cases
Step 2 - Generate conversations that contain the answers:
For `data/conversations/`:
- Create exactly 5 conversation files named `zep_eval_test_user_001_conv_001.json`
through `zep_eval_test_user_001_conv_005.json`
- Each conversation file should contain exactly 6 messages (alternating user/assistant)
- Follow the format and structure of the existing conversation files
- CRITICAL: Ensure that the information needed to answer ALL test questions from
Step 1 is present somewhere across these 5 conversations
- Spread the information naturally across the conversations
- Make conversations feel natural and contextual
- Follow the best practices in the README for conversation design
Here are my 3-5 example interactions:
[PASTE YOUR 3-5 EXAMPLE INTERACTIONS HERE]
```
### Run the ingestion script
Load your test conversations into Zep:
```bash
uv run zep_ingest.py
```
The ingestion process creates numbered run directories (e.g., `1_20251103T123456`) containing manifest files that document created users, thread IDs, and configuration details.
For ingestion with a custom ontology:
```bash
uv run zep_ingest.py --custom-ontology
```
### Wait for graph processing to complete
After ingestion completes, the knowledge graph needs time to process all messages and extract facts, entities, and relationships. Graph processing happens sequentially to preserve the temporal sequence of events.
**Processing time**: 5-10 seconds per message. With 5 conversations of 6 messages each (30 messages total), expect processing to take approximately 2.5-5 minutes.
You can monitor processing status in the Zep dashboard or wait for the recommended time before proceeding to evaluation.
### Run the evaluation script
Execute the evaluation pipeline:
```bash
uv run zep_evaluate.py
```
To evaluate a specific run:
```bash
uv run zep_evaluate.py 1
```
The script processes each test question through four automated steps:
1. **Search**: Query Zep's knowledge graph using a cross-encoder reranker to retrieve relevant information
2. **Evaluate context**: Assess whether the retrieved information is sufficient to answer the test question (produces the primary metric: COMPLETE, PARTIAL, or INSUFFICIENT)
3. **Generate response**: Use GPT-4o-mini with the retrieved context to generate an answer
4. **Grade answer**: Evaluate the generated response against the golden answer using GPT-4o (produces the secondary metric: CORRECT or WRONG)
The context completeness evaluation (step 2) is the primary metric as it measures Zep's core capability: retrieving relevant information. The answer grading (step 4) is secondary since it also depends on the LLM's ability to use that context.
Results are saved to `runs/{run_number}/evaluation_results_{timestamp}.json`.
### Interpret your results
The evaluation results include overall accuracy on the test questions and detailed per-test breakdown. Look at these key metrics:
* **Context completeness**: Whether Zep retrieved all necessary information (COMPLETE, PARTIAL, or INSUFFICIENT). This is your primary indicator of Zep's retrieval performance.
* **Answer accuracy**: Whether the generated answer matched your golden answer criteria (CORRECT or WRONG). This measures both retrieval and generation quality.
* **Per-user breakdown**: Performance metrics for each user to identify patterns.
* **Detailed test results**: Individual test case results with retrieved context, generated answers, and the LLM judge's reasoning.
The script prints overall scores and saves detailed results including which questions the agent answered correctly versus missed, along with the LLM judge's reasoning for each evaluation.
### Review results and iterate
Look at the evaluation results to identify any missed questions. For each incorrect answer:
1. Check if the conversation data contains the necessary information
2. Verify the golden\_answer is clear and specific
3. Review the retrieved context in the results JSON to understand what Zep found
4. Adjust your conversations or test questions as needed
If context is consistently incomplete, consider adjusting your data ingestion strategy, search parameters, or graph configuration.
Iterate by modifying your data files, then re-run the ingestion and evaluation scripts.
## Next steps
Once you have the basic evaluation working, consider these next steps:
* **Add more examples and variations**: Expand your test set with additional examples and variations of existing scenarios to cover more edge cases.
* **Evaluate Zep's performance with your existing agent**: Once you've validated Zep's retrieval capabilities with the evaluation harness, integrate Zep into your existing agent and evaluate end-to-end performance. Create test cases based on real user conversations from your application to reflect actual usage patterns. This helps you understand how Zep performs in your complete system, including your agent's prompt engineering, tool calling, and response generation.
* **Define a custom ontology for your domain**: Create entity and edge types tailored to your specific use case for better knowledge graph structure and retrieval. Use an AI coding assistant to define custom types based on your conversation data:
```text
Based on the conversations in `data/conversations/`, help me define a custom ontology
for my domain in `ontology.py`.
Please create entity types (classes) and edge types (relationships) that are specific
to my use case and conversation patterns. Follow these guidelines:
- Entity types should be domain-specific (e.g., for healthcare: Patient, Diagnosis,
Medication; for e-commerce: Product, Order, CustomerIssue)
- Include 1-2 key attributes per entity type using EntityText fields
- Entity names should contain specific values for better semantic search
- Edge types should model the key relationships in my domain
- Add descriptive docstrings explaining when to use each type
Look at the existing `ontology.py` file for the structure and format to follow.
Here's a summary of my use case and domain:
[DESCRIBE YOUR USE CASE AND DOMAIN]
```
After updating `ontology.py`, run ingestion with the custom ontology flag:
```bash
uv run zep_ingest.py --custom-ontology
```
Learn more about [customizing graph structure](/customizing-graph-structure).
* **Add background data**: Ingest a larger dataset before your test conversations to evaluate retrieval performance when relevant information is buried in a larger knowledge graph.
* **Test with JSON and unstructured data**: Add JSON documents, transcripts, or business data alongside conversations, then create test questions that require retrieving this non-conversational data. See [Adding Data to the Graph](/adding-data-to-the-graph).
* **Tune search strategy and graph parameters**: Experiment with different rerankers, search scopes, and graph creation settings like [ignoring assistant messages](/adding-messages#ignore-assistant-messages) to optimize performance for your use case. You can customize the evaluation parameters in `zep_evaluate.py`:
```python
# Search limits
FACTS_LIMIT = 20 # Number of edges to return
ENTITIES_LIMIT = 10 # Number of nodes to return
EPISODES_LIMIT = 0 # Disabled by default
# Reranker options: cross_encoder (default), rrf, or mmr
```
# Documentation MCP server
> Give AI coding assistants real-time access to Zep's documentation.
Zep's Documentation MCP server enables AI assistants to search and retrieve information from Zep's complete documentation in real-time.
**Server details:**
* URL: `docs-mcp.getzep.com`
* Type: Search-based with HTTP transport
* Capabilities: Real-time documentation search and retrieval
The `/sse` endpoint is deprecated and will be removed soon. Please update to the new `/mcp` endpoint with HTTP transport.
## Setting up the MCP server
Add the HTTP server using the CLI:
```bash
claude mcp add zep-docs https://docs-mcp.getzep.com/mcp
```
Create `.cursor/mcp.json` in your project or `~/.cursor/mcp.json` globally:
```json
{
"mcpServers": {
"zep-docs": {
"url": "https://docs-mcp.getzep.com/mcp"
}
}
}
```
Enable MCP servers in Cursor settings, then add and enable the zep-docs server.
Configure your MCP client with HTTP transport:
```
URL: https://docs-mcp.getzep.com/mcp
```
## Using the MCP server
Once configured, AI assistants can automatically:
* Search Zep concepts and features
* Find code examples and tutorials
* Access current API documentation
* Retrieve troubleshooting information
# llms.txt
> Standardized documentation files for AI coding assistants.
Zep publishes standardized `llms.txt` files containing essential information for AI coding assistants. These files follow the [llms.txt specification](https://llmstxt.org/) and provide structured documentation that AI tools can consume directly.
The files include:
* Core concepts and architecture
* Usage patterns and examples
* API reference summaries
* Best practices and troubleshooting
* Framework integration examples
## Accessing llms.txt
Zep provides two versions of the llms.txt file:
**Standard version** (recommended for most use cases):
```
https://help.getzep.com/llms.txt
```
**Comprehensive version** (for advanced use cases):
```
https://help.getzep.com/llms-full.txt
```
The standard version contains curated essentials, while the comprehensive version includes complete documentation but is much larger. Most AI assistants work better with the standard version due to context limitations.
# zepctl CLI Reference
> Command-line interface for administering Zep projects
`zepctl` is a command-line interface for administering Zep projects. It provides comprehensive access to Zep's context engineering platform, enabling you to manage users, threads, knowledge graphs, and data operations from the terminal.
## Installation
### Homebrew (macOS/Linux)
```bash
brew tap getzep/zepctl https://github.com/getzep/zepctl.git
brew install zepctl
```
### Binary Download
Download the appropriate binary for your platform from the [releases page](https://github.com/getzep/zepctl/releases).
**macOS users:** If you see "zepctl cannot be opened because the developer cannot be verified", run:
```bash
xattr -d com.apple.quarantine /path/to/zepctl
```
## Quick Start
```bash
# Configure your API key (you will be prompted to enter it securely)
zepctl config add-profile production
# Verify connection
zepctl project get
# List users
zepctl user list
```
## Authentication
### Environment Variables
| Variable | Description |
| ------------- | ---------------------------------------------------- |
| `ZEP_API_KEY` | API key for authentication |
| `ZEP_API_URL` | API endpoint URL (default: `https://api.getzep.com`) |
| `ZEP_PROFILE` | Override current profile |
| `ZEP_OUTPUT` | Default output format |
### Configuration File
Location: `~/.zepctl/config.yaml`
```yaml
current-profile: production
profiles:
- name: production
# API keys are stored securely in the system keychain
- name: development
api-url: https://api.dev.getzep.com # Optional: only if using non-default URL
defaults:
output: table
page-size: 50
```
API keys are stored in the system keychain (macOS Keychain, Windows Credential Manager, or Linux Secret Service) rather than in the config file. For CI/CD environments without keychain access, use the `ZEP_API_KEY` environment variable.
## Global Flags
| Flag | Short | Description |
| ----------- | ----- | ---------------------------------------------- |
| `--api-key` | `-k` | Override API key |
| `--api-url` | | Override API URL |
| `--profile` | `-p` | Use specific profile |
| `--output` | `-o` | Output format: `table`, `json`, `yaml`, `wide` |
| `--quiet` | `-q` | Suppress non-essential output |
| `--verbose` | `-v` | Enable verbose output |
| `--help` | `-h` | Display help |
## Commands
### config
Manage zepctl configuration including profiles and defaults.
```bash
# View current configuration
zepctl config view
# List all profiles
zepctl config get-profiles
# Switch active profile
zepctl config use-profile
# Add a new profile (prompts for API key)
zepctl config add-profile [--api-url URL]
# Remove a profile
zepctl config delete-profile [--force]
```
### project
Get project information.
```bash
zepctl project get
```
### user
Manage users in your Zep project.
```bash
# List users
zepctl user list [--page N] [--page-size N]
# Get user details
zepctl user get
# Create a new user
zepctl user create [--email EMAIL] [--first-name NAME] [--last-name NAME] \
[--metadata JSON] [--metadata-file PATH]
# Update an existing user
zepctl user update [--email EMAIL] [--first-name NAME] [--last-name NAME] \
[--metadata JSON] [--metadata-file PATH]
# Delete a user (includes all associated data)
zepctl user delete [--force]
# List user threads
zepctl user threads
# Get user graph node
zepctl user node
```
Deleting a user removes all associated threads, graph data, and knowledge. This supports RTBF (Right to Be Forgotten) compliance.
### thread
Manage conversation threads.
```bash
# List all threads
zepctl thread list [--page N] [--page-size N] [--order-by FIELD] [--asc]
# Create a new thread
zepctl thread create --user
# Get thread messages
zepctl thread get [--last N]
# Delete a thread
zepctl thread delete [--force]
# List thread messages
zepctl thread messages [--last N] [--limit N]
# Add messages to a thread
zepctl thread add-messages --file messages.json [--batch] [--wait]
zepctl thread add-messages --stdin [--batch] [--wait]
# Get thread context
zepctl thread context
```
#### List Flags
| Flag | Description |
| ------------- | ------------------------------------------------------------------ |
| `--page` | Page number (default: 1) |
| `--page-size` | Results per page (default: 50) |
| `--order-by` | Order by field: `created_at`, `updated_at`, `user_id`, `thread_id` |
| `--asc` | Sort in ascending order (default: descending) |
#### Message Format
When adding messages via `--file` or `--stdin`, use this JSON format:
```json
{
"messages": [
{
"role": "user",
"name": "Alice",
"content": "Hello, I need help with my account"
},
{
"role": "assistant",
"content": "I'd be happy to help!"
}
]
}
```
### graph
Manage knowledge graphs.
```bash
# List all graphs
zepctl graph list [--page N] [--page-size N]
# Create a new graph
zepctl graph create
# Delete a graph
zepctl graph delete [--force]
# Clone a graph
zepctl graph clone --source-user USER_ID --target-user NEW_USER_ID
zepctl graph clone --source-graph GRAPH_ID --target-graph NEW_GRAPH_ID
# Add data to a graph
zepctl graph add --type text --data "User prefers dark mode"
zepctl graph add --user --type json --file data.json
zepctl graph add --user --batch --file episodes.json --wait
# Add a fact triple to a graph
zepctl graph add-fact --user --fact "Alice knows Bob" --fact-name KNOWS \
--source-node "Alice" --target-node "Bob"
zepctl graph add-fact --graph --fact "Alice works at Acme" --fact-name WORKS_AT \
--source-node "Alice" --target-node "Acme" \
--source-attrs '{"role": "engineer"}' --edge-attrs '{"since": "2020"}' \
--target-attrs '{"industry": "tech"}'
# Search a graph
zepctl graph search "query" --user --scope edges
zepctl graph search "query" --graph --scope nodes --limit 20
zepctl graph search "query" --user --property-filter "status:=:active"
zepctl graph search "query" --user --date-filter "created_at:>:2024-01-01"
```
#### Add Data Flags
| Flag | Description |
| --------- | ------------------------------------------------------ |
| `--type` | Data type: `text`, `json`, `message` (default: `text`) |
| `--data` | Inline data string |
| `--file` | Path to data file |
| `--stdin` | Read data from stdin |
| `--user` | Add to user graph |
| `--batch` | Enable batch processing |
| `--wait` | Wait for ingestion to complete |
#### Add Fact Flags
| Flag | Description |
| ---------------- | -------------------------------------------------- |
| `--user` | Add to user graph |
| `--graph` | Add to standalone graph |
| `--fact` | The fact relating the two nodes (required) |
| `--fact-name` | Edge name, should be UPPER\_SNAKE\_CASE (required) |
| `--source-node` | Source node name (required) |
| `--target-node` | Target node name (required) |
| `--valid-at` | When the fact becomes true (ISO 8601) |
| `--invalid-at` | When the fact stops being true (ISO 8601) |
| `--source-attrs` | Source node attributes as JSON |
| `--edge-attrs` | Edge attributes as JSON |
| `--target-attrs` | Target node attributes as JSON |
#### Search Flags
| Flag | Description |
| ----------------------- | ----------------------------------------------------------------------- |
| `--user` | Search user graph |
| `--graph` | Search standalone graph |
| `--scope` | Search scope: `edges`, `nodes`, `episodes` (default: `edges`) |
| `--limit` | Maximum results (default: 10) |
| `--reranker` | Reranker: `rrf`, `mmr`, `cross_encoder` |
| `--mmr-lambda` | MMR diversity/relevance balance (0-1) |
| `--min-score` | Minimum relevance score |
| `--node-labels` | Comma-separated node labels to include |
| `--edge-types` | Comma-separated edge types to include |
| `--exclude-node-labels` | Comma-separated node labels to exclude |
| `--exclude-edge-types` | Comma-separated edge types to exclude |
| `--property-filter` | Property filter (repeatable): `property:op:value` or `property:IS NULL` |
| `--date-filter` | Date filter (repeatable): `field:op:date` or `field:IS NULL` |
#### Property Filter Syntax
Property filters allow filtering by node/edge attributes:
```bash
--property-filter "property_name:operator:value"
--property-filter "property_name:IS NULL"
--property-filter "property_name:IS NOT NULL"
```
Supported operators: `=`, `==`, `<>`, `!=`, `>`, `<`, `>=`, `<=`, `IS NULL`, `IS NOT NULL`
Values are automatically parsed as boolean (`true`/`false`), integer, float, or string.
#### Date Filter Syntax
Date filters allow filtering by temporal fields:
```bash
--date-filter "field:operator:date"
--date-filter "field:IS NULL"
--date-filter "field:IS NOT NULL"
```
Supported fields: `created_at`, `valid_at`, `invalid_at`, `expired_at`
#### Batch Episode Format
```json
{
"episodes": [
{"type": "text", "data": "User prefers morning meetings"},
{"type": "json", "data": "{\"preference\": \"dark_mode\"}"},
{"type": "message", "data": "Alice: I love hiking on weekends"}
]
}
```
### node
Manage graph nodes.
```bash
# List nodes
zepctl node list --user [--limit N] [--cursor UUID]
zepctl node list --graph
# Get node details
zepctl node get
# Get node edges
zepctl node edges
# Get node episodes
zepctl node episodes
# Delete a node
zepctl node delete [--force]
```
### edge
Manage graph edges (facts/relationships).
```bash
# List edges
zepctl edge list --user [--limit N] [--cursor UUID]
zepctl edge list --graph
# Get edge details
zepctl edge get
# Delete an edge
zepctl edge delete [--force]
```
### episode
Manage graph episodes (source data).
```bash
# List episodes
zepctl episode list --user [--last N]
zepctl episode list --graph
# Get episode details
zepctl episode get
# Get episode mentions
zepctl episode mentions
# Delete an episode
zepctl episode delete [--force]
```
### task
Monitor async operations (batch imports, cloning, etc.).
```bash
# Get task status
zepctl task get
# Wait for task completion
zepctl task wait [--timeout 5m] [--poll-interval 1s]
```
### ontology
Manage graph schema definitions.
```bash
# Get ontology definitions
zepctl ontology get
# Set ontology from file
zepctl ontology set --file ontology.yaml
```
#### Ontology File Format
```yaml
entities:
Customer:
description: "A customer of the business"
fields:
tier:
description: "Customer tier level"
account_number:
description: "Customer account number"
Product:
description: "A product or service"
fields:
sku:
description: "Product SKU"
edges:
PURCHASED:
description: "Customer purchased a product"
source_types: [Customer]
target_types: [Product]
INTERESTED_IN:
description: "Customer expressed interest"
```
### summary-instructions
Manage user summary instructions.
```bash
# List instructions
zepctl summary-instructions list [--user USER_ID]
# Add instructions
zepctl summary-instructions add --name NAME --instruction "Text" [--user USER_IDS]
zepctl summary-instructions add --name NAME --file instructions.txt [--user USER_IDS]
# Delete instructions
zepctl summary-instructions delete [--force] [--user USER_IDS]
```
## Examples
### Export All Users
```bash
zepctl user list -o json | jq '.users[].user_id'
```
### Bulk User Creation
```bash
cat users.json | jq -c '.[]' | while read user; do
zepctl user create $(echo $user | jq -r '.user_id') \
--email "$(echo $user | jq -r '.email')" \
--first-name "$(echo $user | jq -r '.first_name')"
done
```
### Migrate User Data
```bash
# Clone user graph to test environment
zepctl graph clone --source-user prod_user_123 --target-user test_user_123
# Verify clone
zepctl node list --user test_user_123 -o json | jq '.nodes | length'
```
### Monitor Batch Import
```bash
# Start batch import
TASK_ID=$(zepctl graph add --user user_123 --batch --file data.json -o json | jq -r '.task_id')
# Wait for completion
zepctl task wait $TASK_ID --timeout 10m
```
### Delete User (RTBF Compliance)
```bash
# Preview what will be deleted
zepctl user get $USER_ID
zepctl user threads $USER_ID
# Delete user and all associated data
zepctl user delete $USER_ID --force
```
### Search with Advanced Filters
```bash
# Search with cross-encoder reranking
zepctl graph search "critical decisions" --user user_123 --reranker cross_encoder
# Search nodes excluding certain labels
zepctl graph search "product" --graph graph_456 --scope nodes \
--exclude-node-labels "Assistant,Document"
# Search with property filters
zepctl graph search "query" --user user_123 \
--property-filter "status:=:active" \
--property-filter "age:>:30"
# Search for edges with null validity dates
zepctl graph search "query" --user user_123 \
--date-filter "valid_at:IS NULL" \
--date-filter "created_at:>:2024-01-01"
# Combine multiple filter types
zepctl graph search "preferences" --user user_123 \
--node-labels "Person,Product" \
--property-filter "verified:=:true" \
--date-filter "expired_at:IS NULL"
```
## Output Formats
All commands support multiple output formats via the `--output` flag:
| Format | Description |
| ------- | -------------------------------------- |
| `table` | Human-readable table (default) |
| `json` | JSON output for scripting |
| `yaml` | YAML output |
| `wide` | Extended table with additional columns |
```bash
# JSON output for scripting
zepctl user list -o json
# YAML output
zepctl user get user_123 -o yaml
```
## Shell Completions
Enable tab completion for commands, flags, and arguments.
### Bash
Requires the `bash-completion` package.
```bash
# Load completions in current session
source <(zepctl completion bash)
# Install permanently (Linux)
zepctl completion bash > /etc/bash_completion.d/zepctl
# Install permanently (macOS with Homebrew)
zepctl completion bash > $(brew --prefix)/etc/bash_completion.d/zepctl
```
### Zsh
```bash
# Enable completions if not already configured
echo "autoload -U compinit; compinit" >> ~/.zshrc
# Load completions in current session
source <(zepctl completion zsh)
# Install permanently (Linux)
zepctl completion zsh > "${fpath[1]}/_zepctl"
# Install permanently (macOS with Homebrew)
zepctl completion zsh > $(brew --prefix)/share/zsh/site-functions/_zepctl
```
### Fish
```bash
# Load completions in current session
zepctl completion fish | source
# Install permanently
zepctl completion fish > ~/.config/fish/completions/zepctl.fish
```
### PowerShell
```powershell
# Load completions in current session
zepctl completion powershell | Out-String | Invoke-Expression
# Install permanently (add to your PowerShell profile)
zepctl completion powershell > zepctl.ps1
# Then add `. /path/to/zepctl.ps1` to your profile
```
Start a new shell session after installing completions for changes to take effect.
# Debugging
> Debug workflow execution logs for graph operations
Zep provides detailed debugging capabilities to help you troubleshoot and optimize your graph operations. Debug logging captures detailed workflow execution logs that can be invaluable for understanding how your data flows through the system.
## Enabling Debug Logging
Debug logging is enabled from the Project Settings page in your Zep dashboard. Once enabled, debug logging will be active for 60 minutes.
> **Important**: Debug logging will be active for the next 60 minutes. During this time, all workflow executions will have detailed logs captured. You can view these logs in the thread logs dialog for any thread that runs during this period.
## Accessing Debug Logs
Debug logs are available from episode lists for both individual users and graph-wide operations.
**Note**: Debug logs are not supported when adding data or messages in batch operations.
## Viewing Debug Logs
To view debug logs for a specific episode:
1. Navigate to the episode list (either from a user or graph view)
2. Find the episode you want to debug
3. Click on the **Actions** menu for that episode
4. Select **Debug Logs**
This will open a detailed view of the workflow execution logs for that specific episode, showing you:
* Step-by-step execution flow
* Processing timestamps
* Error messages and stack traces
* Performance metrics
* Data transformation details
## Best Practices for Debug Logging
* **Enable selectively**: Only enable debug logging when actively troubleshooting to avoid unnecessary overhead
* **Time-limited threads**: Debug logging automatically disables after 60 minutes to prevent performance impact
* **Review promptly**: Review debug logs within 24 hours. Stale debug logs are removed after 24 hours.
# Overview
> Methods for adding data to Zep to build user knowledge graphs
Zep builds knowledge graphs from the data you provide. There are several methods for adding context to Zep, each suited for different data types and use cases.
## Available methods
| Method | Data Type | Best For |
| ------------------------------------------------- | ----------------------------- | --------------------------------------------------------- |
| [**Adding messages**](/adding-messages) | Chat messages | Real-time conversation history from your agent |
| [**Adding business data**](/adding-business-data) | Text, JSON, or message format | Documents, API responses, emails, or other business data |
| [**Adding batch data**](/adding-batch-data) | Multiple episodes | Historical backfills, document collections, large imports |
## Customizing how context is built
After adding data, Zep processes it to build knowledge graphs. You can customize this process using the options in [Customizing context](/customizing-context).
# Adding Messages
> Learn how to add chat history and messages to Zep.
You can add both messages and business data to User Graphs.
## Adding Messages
Add your chat history to Zep using the `thread.add_messages` method. `thread.add_messages` is thread-specific and expects data in chat message format, including a `name` (e.g., user's real name), `role` (AI, human, tool), and message `content`. Zep stores the chat history and builds a user-level knowledge graph from the messages.
For best results, add chat history to Zep on every chat turn. That is, add both the AI and human messages in a single operation and in the order that the messages were created.
It is important to provide the name of the user in the name field if possible, to help with graph construction. It's also helpful to provide a meaningful name for the assistant in its name field.
### Basic example
The example below adds messages to Zep for the user in the given thread:
```python Python
from zep_cloud.client import Zep
from zep_cloud.types import Message
zep_client = Zep(
api_key=API_KEY,
)
messages = [
Message(
name="Jane",
role="user",
content="Who was Octavia Butler?",
)
]
response = zep_client.thread.add_messages(thread_id, messages=messages)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
import type { Message } from "@getzep/zep-cloud/api";
const zepClient = new ZepClient({
apiKey: API_KEY,
});
const messages: Message[] = [
{ name: "Jane", role: "user", content: "Who was Octavia Butler?" },
];
const response = await zepClient.thread.addMessages(threadId, { messages });
```
```go Go
import (
v3 "github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
)
zepClient := zepclient.NewClient(
option.WithAPIKey(""),
)
response, err := zepClient.Thread.AddMessages(
context.TODO(),
"threadId",
&v3.AddThreadMessagesRequest{
Messages: []*v3.Message{
{
Name: v3.String("Jane"),
Role: "user",
Content: "Who was Octavia Butler?",
},
},
},
)
```
You can find additional arguments to `thread.add_messages` in the [SDK reference](/sdk-reference/thread/add-messages). Notably, for latency sensitive applications, you can set `return_context` to true which will make `thread.add_messages` return a context block in the way that `thread.get_user_context` does (discussed below).
### Ignore assistant messages
You can also pass in a list of roles to ignore when adding messages to a User Graph using the `ignore_roles` argument. For example, you may not want assistant messages to be added to the user graph; providing the assistant messages in the `thread.add_messages` call while setting `ignore_roles` to include "assistant" will make it so that only the user messages are ingested into the graph, but the assistant messages are still used to contextualize the user messages. This is important in case the user message itself does not have enough context, such as the message "Yes." Additionally, the assistant messages will still be added to the thread's message history.
```python Python
response = zep_client.thread.add_messages(
thread_id,
messages=messages,
ignore_roles=["assistant"]
)
```
```typescript TypeScript
const response = await zepClient.thread.addMessages(threadId, {
messages,
ignoreRoles: ["assistant"]
});
```
```go Go
response, err := zepClient.Thread.AddMessages(
context.TODO(),
"threadId",
&v3.AddThreadMessagesRequest{
Messages: messages,
IgnoreRoles: []string{"assistant"},
},
)
```
### Creating messages with metadata
Messages can have metadata attached to store additional information like sentiment scores, source identifiers, processing flags, or other custom data. Metadata is preserved when getting threads, individual messages, and when searching episodes.
Message metadata is currently supported only for thread messages. Messages added via the `graph.add` API do not support metadata. Zep does not support filtering or searching over message metadata.
You can attach metadata when creating messages by including a `metadata` field in your message objects:
```python Python
from zep_cloud.client import Zep
from zep_cloud.types import Message
zep_client = Zep(
api_key=API_KEY,
)
messages = [
Message(
name="Jane",
role="user",
content="I need help with my account.",
metadata={
"sentiment": "frustrated",
"source": "mobile_app",
"priority": "high"
}
)
]
response = zep_client.thread.add_messages(thread_id, messages=messages)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
import type { Message } from "@getzep/zep-cloud/api";
const zepClient = new ZepClient({
apiKey: API_KEY,
});
const messages: Message[] = [
{
name: "Jane",
role: "user",
content: "I need help with my account.",
metadata: {
sentiment: "frustrated",
source: "mobile_app",
priority: "high"
}
},
];
const response = await zepClient.thread.addMessages(threadId, { messages });
```
```go Go
import (
v3 "github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
)
zepClient := zepclient.NewClient(
option.WithAPIKey(""),
)
response, err := zepClient.Thread.AddMessages(
context.TODO(),
"threadId",
&v3.AddThreadMessagesRequest{
Messages: []*v3.Message{
{
Name: v3.String("Jane"),
Role: "user",
Content: "I need help with my account.",
Metadata: map[string]interface{}{
"sentiment": "frustrated",
"source": "mobile_app",
"priority": "high",
},
},
},
},
)
```
### Updating message metadata
You can update the metadata of an existing message using the message UUID. This is useful for adding or modifying metadata after a message has been created, such as updating sentiment analysis results or processing status.
```python Python
from zep_cloud.client import Zep
zep_client = Zep(
api_key=API_KEY,
)
# Update message metadata
updated_message = zep_client.thread.message.update(
message_uuid="message-uuid-here",
metadata={
"sentiment": "positive",
"resolved": True,
"resolution_time": "2m 30s"
}
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const zepClient = new ZepClient({
apiKey: API_KEY,
});
// Update message metadata
const updatedMessage = await zepClient.thread.message.update(
"message-uuid-here",
{
metadata: {
sentiment: "positive",
resolved: true,
resolutionTime: "2m 30s"
}
}
);
```
```go Go
import (
"context"
v3 "github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
"github.com/getzep/zep-go/v3/thread"
)
zepClient := zepclient.NewClient(
option.WithAPIKey(""),
)
// Update message metadata
updatedMessage, err := zepClient.Thread.Message.Update(
context.TODO(),
"message-uuid-here",
&thread.ThreadMessageUpdate{
Metadata: map[string]interface{}{
"sentiment": "positive",
"resolved": true,
"resolution_time": "2m 30s",
},
},
)
if err != nil {
// Handle error
}
```
### Setting message timestamps
When creating messages via the API, you should provide the `created_at` timestamp in RFC3339 format. The `created_at` timestamp represents the time when the message was originally sent by the user. Setting the `created_at` timestamp is important to ensure the user's knowledge graph has accurate temporal understanding of user history (since this time is used in our fact invalidation process).
```python Python
from zep_cloud.client import Zep
from zep_cloud.types import Message
zep_client = Zep(
api_key=API_KEY,
)
messages = [
Message(
created_at="2025-06-01T13:11:12Z",
name="Jane",
role="user",
content="What's the weather like today?",
)
]
response = zep_client.thread.add_messages(thread_id, messages=messages)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
import type { Message } from "@getzep/zep-cloud/api";
const zepClient = new ZepClient({
apiKey: API_KEY,
});
const messages: Message[] = [
{
createdAt: "2025-06-01T13:11:12Z",
name: "Jane",
role: "user",
content: "What's the weather like today?"
},
];
const response = await zepClient.thread.addMessages(threadId, { messages });
```
```go Go
import (
v3 "github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
)
zepClient := zepclient.NewClient(
option.WithAPIKey(""),
)
response, err := zepClient.Thread.AddMessages(
context.TODO(),
"threadId",
&v3.AddThreadMessagesRequest{
Messages: []*v3.Message{
{
CreatedAt: v3.String("2025-06-01T13:11:12Z"),
Name: v3.String("Jane"),
Role: "user",
Content: "What's the weather like today?",
},
},
},
)
```
### Message limits
When adding messages to a thread, there are limits on both the number of messages and message size:
* **Messages per call**: You can add at most 30 messages in a single `thread.add_messages` call
* **Message size limit**: Each message can be at most 4,096 characters
If you exceed these limits, the API will return a 400 Bad Request error. If you need to add more than 30 messages or have messages exceeding the character limits, you'll need to split them across multiple API calls or truncate the content accordingly. Our additional recommendations include:
* Have users attach documents rather than paste them into the message, and then process documents separately with `graph.add`
* Reduce the max message size for your users to match our max message size
* Optional: allow users to paste in documents with an auto detection algorithm that turns it into an attachment as opposed to part of the message
### Check when messages are finished processing
You can use the message UUIDs from the response to poll the messages and check when they are finished processing:
```python
response = zep_client.thread.add_messages(thread_id, messages=messages)
message_uuids = response.message_uuids
```
An example of this can be found in the [check data ingestion status cookbook](/cookbook/check-data-ingestion-status).
## Adding Business Data
You can also add JSON or unstructured text to a User Graph using our [Graph API](/adding-business-data).
## Customizing Graph Creation
Zep offers two ways to customize how context is created. You can read more about these features at their guide pages:
* [**Custom entity and edge types**](/customizing-graph-structure#custom-entity-and-edge-types): Feature allowing use of Pydantic-like classes to customize creation/retrieval of entities and relations in the knowledge graph.
* [**User summary instructions**](/users#user-summary-instructions): Customize how Zep generates entity summaries for users in their knowledge graph with up to 5 custom instructions per user.
# Adding Business Data
> Add structured and unstructured business data directly to user graphs
## Overview
Requests to add data to the same graph are completed sequentially to ensure the graph is built correctly, and processing may be slow for large datasets. Use [batch ingestion](/adding-batch-data) when adding large datasets such as backfills or document collections.
In addition to persisting chat history to Zep's knowledge graph, Zep offers the capability to add data directly to the graph.
Zep supports three distinct data types: message, text, and JSON.
The message type is ideal for adding data in the form of chat messages that are not directly associated with a Zep [Thread's](/threads) chat history. This encompasses any communication with a designated speaker, such as emails or previous chat logs.
The text type is designed for raw text data without a specific speaker attribution. This category includes content from internal documents, wiki articles, or company handbooks. It's important to note that Zep does not process text directly from links or files.
The JSON type may be used to add any JSON document to Zep. This may include REST API responses or JSON-formatted business data.
You can add data to a graph by specifying a `graph_id`, or to a user graph by providing a `user_id`.
## Adding Message Data
Here's an example demonstrating how to add message data to the graph:
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
message = "Paul (user): I went to Eric Clapton concert last night"
new_episode = client.graph.add(
user_id="user123", # Optional: You can use graph_id instead of user_id
type="message", # Specify type as "message"
data=message
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const message = "User: I really enjoy working with TypeScript and React";
const newEpisode = await client.graph.add({
userId: "user123", // Optional: You can use graphId instead of userId
type: "message",
data: message
});
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
message := "Paul (user): I went to Eric Clapton concert last night"
userID := "user123"
newEpisode, err := client.Graph.Add(context.TODO(), &zep.AddDataRequest{
UserID: &userID, // Optional: You can use GraphID instead of UserID
Type: zep.GraphDataTypeMessage,
Data: message,
})
if err != nil {
log.Fatalf("Failed to add message data: %v", err)
}
```
## Adding Text Data
Here's an example demonstrating how to add text data to the graph:
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
new_episode = client.graph.add(
user_id="user123", # Optional: You can use graph_id instead of user_id
type="text", # Specify type as "text"
data="The user is an avid fan of Eric Clapton"
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const newEpisode = await client.graph.add({
userId: "user123", // Optional: You can use graphId instead of userId
type: "text",
data: "The user is interested in machine learning and artificial intelligence"
});
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
userID := "user123"
newEpisode, err := client.Graph.Add(context.TODO(), &zep.AddDataRequest{
UserID: &userID, // Optional: You can use GraphID instead of UserID
Type: zep.GraphDataTypeText,
Data: "The user is an avid fan of Eric Clapton",
})
if err != nil {
log.Fatalf("Failed to add text data: %v", err)
}
```
## Adding JSON Data
Here's an example demonstrating how to add JSON data to the graph:
```python Python
from zep_cloud.client import Zep
import json
client = Zep(
api_key=API_KEY,
)
json_data = {"name": "Eric Clapton", "age": 78, "genre": "Rock"}
json_string = json.dumps(json_data)
new_episode = client.graph.add(
user_id=user_id, # Optional: You can use graph_id instead of user_id
type="json",
data=json_string,
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const jsonString = '{"name": "Eric Clapton", "age": 78, "genre": "Rock"}';
const newEpisode = await client.graph.add({
userId: userId, // Optional: You can use graphId instead of userId
type: "json",
data: jsonString,
});
```
```go Go
import (
"context"
"encoding/json"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
jsonData := map[string]interface{}{
"name": "Eric Clapton",
"age": 78,
"genre": "Rock",
}
jsonBytes, err := json.Marshal(jsonData)
if err != nil {
log.Fatalf("Failed to marshal JSON: %v", err)
}
jsonString := string(jsonBytes)
userID := "user123"
newEpisode, err := client.Graph.Add(context.TODO(), &zep.AddDataRequest{
UserID: &userID, // Optional: You can use GraphID instead of UserID
Type: zep.GraphDataTypeJSON,
Data: jsonString,
})
if err != nil {
log.Fatalf("Failed to add JSON data: %v", err)
}
```
## Data Size Limit and Chunking
The `graph.add` endpoint has a data size limit of 10,000 characters when adding data to the graph. If you need to add a document which is more than 10,000 characters, we recommend chunking the document as well as using Anthropic's contextualized retrieval technique. We have an example of this [here](https://blog.getzep.com/building-a-russian-election-interference-knowledge-graph/#:~:text=Chunking%20articles%20into%20multiple%20Episodes%20improved%20our%20results%20compared%20to%20treating%20each%20article%20as%20a%20single%20Episode.%20This%20approach%20generated%20more%20detailed%20knowledge%20graphs%20with%20richer%20node%20and%20edge%20extraction%2C%20while%20single%2DEpisode%20processing%20produced%20only%20high%2Dlevel%2C%20sparse%20graphs.). This example uses Graphiti, but the same patterns apply to Zep as well.
Additionally, we recommend using relatively small chunk sizes, so that Zep is able to capture all of the entities and relationships within a chunk. Using a larger chunk size may result in some entities and relationships not being captured.
## Managing Your Data on the Graph
The `graph.add` method returns the [episode](/graphiti/adding-episodes) that was created in the graph from adding that data. You can use this to maintain a mapping between your data and its corresponding episode in the graph and to delete specific data from the graph using the [delete episode](/deleting-data-from-the-graph#delete-an-episode) method.
# Adding batch data
> Efficiently add large amounts of data to your graph
The batch add method enables efficient concurrent processing of large amounts of data to your graph. This experimental feature is designed for scenarios where you need to add multiple episodes quickly, such as backfills, document collections, or historical data imports.
This is an experimental feature. While faster than sequential processing, batch ingestion may result in slightly different graph structure compared to sequential processing due to the concurrent nature of the operation.
## How batch processing works
The batch add method processes episodes concurrently for improved performance while still preserving temporal relationships between episodes. Unlike sequential processing where episodes are handled one at a time, batch processing can handle up to 20 episodes simultaneously.
The batch method works with data with a temporal dimension such as evolving chat histories and can process up to 20 episodes at a time of mixed types (text, json, message).
## When to use batch processing
Batch processing is ideal for:
* Historical data backfills
* Document collection imports
* Large datasets where processing speed is prioritized
* Data with a temporal dimension
Batch processing works for all types of data, including data with a temporal dimension such as evolving chat histories.
## Usage example
The batch add method returns an array of episodes, each containing a `task_id` that can be used to track the processing status of the batch operation. All episodes in a batch share the same `task_id`.
```python Python
from zep_cloud.client import Zep
from zep_cloud import EpisodeData
import json
client = Zep(
api_key=API_KEY,
)
episodes = [
EpisodeData(
data="This is an example text episode.",
type="text"
),
EpisodeData(
data=json.dumps({"name": "Eric Clapton", "age": 78, "genre": "Rock"}),
type="json"
),
EpisodeData(
data="User: I really enjoyed the concert last night",
type="message"
)
]
result = client.graph.add_batch(episodes=episodes, graph_id=graph_id)
# Each episode in the result contains a task_id for tracking batch processing
task_id = result[0].task_id
print(f"Batch task ID: {task_id}")
```
```typescript TypeScript
import { ZepClient, EpisodeData } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const episodes: EpisodeData[] = [
{
data: "This is an example text episode.",
type: "text"
},
{
data: JSON.stringify({ name: "Eric Clapton", age: 78, genre: "Rock" }),
type: "json"
},
{
data: "User: I really enjoyed the concert last night",
type: "message"
}
];
const result = await client.graph.addBatch({ graphId, episodes });
// Each episode in the result contains a task_id for tracking batch processing
const taskId = result[0].taskId;
console.log(`Batch task ID: ${taskId}`);
```
```go Go
import (
"context"
"encoding/json"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
)
jsonData, _ := json.Marshal(map[string]interface{}{
"name": "Eric Clapton",
"age": 78,
"genre": "Rock",
})
batchReq := &v3.AddDataBatchRequest{
Episodes: []*v3.EpisodeData{
{
Data: "This is an example text episode.",
Type: v3.GraphDataTypeText,
},
{
Data: string(jsonData),
Type: v3.GraphDataTypeJSON,
},
{
Data: "User: I really enjoyed the concert last night",
Type: v3.GraphDataTypeMessage,
},
},
GraphID: &graphID,
}
result, err := client.Graph.AddBatch(context.TODO(), batchReq)
if err != nil {
log.Fatalf("Failed to add batch episodes: %v", err)
}
// Each episode in the result contains a task_id for tracking batch processing
taskID := result[0].TaskID
log.Printf("Batch task ID: %s", *taskID)
```
## Tracking batch processing status
Use the `task_id` returned from batch operations to poll for completion status using `client.task.get()`. This allows you to check when the entire batch has finished processing. See the [Check Data Ingestion Status](/cookbook/check-data-ingestion-status#checking-operation-status-with-task-polling) recipe for a complete example of task polling.
## Adding batch message data to threads
In addition to adding batch data to your graph, you can add batch message data directly into user threads. The `thread.add_messages_batch` method returns a `task_id` at the root level of the response object that can be used to track the batch processing status. This functionality is important when you want to maintain the structure of threads for your user data, which can affect how the `thread.get_user_context()` method works since it relies on the past messages of a given thread.
The `thread.add_messages_batch` operation supports a maximum of 30 messages per batch.
```python Python
from zep_cloud import Zep
from zep_cloud.types import Message, RoleType
client = Zep(api_key=API_KEY)
# Create multiple messages for batch addition
messages = [
Message(
content="Hello, I need help with my account",
role="user",
name="customer"
),
Message(
content="I'd be happy to help you with your account. What specific issue are you experiencing?",
role="assistant"
),
Message(
content="I can't access my dashboard and keep getting an error",
role="user",
name="customer"
),
Message(
content="Let me help you troubleshoot that. Can you tell me what error message you're seeing?",
role="assistant"
)
]
# Add messages in batch to create/populate a thread
response = client.thread.add_messages_batch(
thread_id="your_thread_id",
messages=messages,
return_context=True
)
# The task_id is at the root level of the response
task_id = response.task_id
print(f"Thread batch task ID: {task_id}")
```
```typescript TypeScript
import { ZepClient, AddThreadMessagesRequest } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const request: AddThreadMessagesRequest = {
messages: [
{
content: "Hello, I need help with my account",
role: "user",
name: "customer"
},
{
content: "I'd be happy to help you with your account. What specific issue are you experiencing?",
role: "assistant"
},
{
content: "I can't access my dashboard and keep getting an error",
role: "user",
name: "customer"
},
{
content: "Let me help you troubleshoot that. Can you tell me what error message you're seeing?",
role: "assistant"
}
],
returnContext: true
};
// Use addMessagesBatch for concurrent processing (useful for data migrations)
const response = await client.thread.addMessagesBatch("your_thread_id", request);
// The task_id is at the root level of the response
const taskId = response.taskId;
console.log(`Thread batch task ID: ${taskId}`);
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
)
ctx := context.Background()
// Add messages in batch to create/populate a thread
response, err := client.Thread.AddMessagesBatch(ctx, "your_thread_id", &zep.AddThreadMessagesRequest{
Messages: []*zep.Message{
{
Content: "Hello, I need help with my account",
Role: "user",
Name: zep.String("customer"),
},
{
Content: "I'd be happy to help you with your account. What specific issue are you experiencing?",
Role: "assistant",
},
{
Content: "I can't access my dashboard and keep getting an error",
Role: "user",
Name: zep.String("customer"),
},
{
Content: "Let me help you troubleshoot that. Can you tell me what error message you're seeing?",
Role: "assistant",
},
},
ReturnContext: zep.Bool(true),
})
if err != nil {
// Handle error
}
// The task_id is at the root level of the response
taskID := response.TaskID
log.Printf("Thread batch task ID: %s", *taskID)
```
## Important details
* Maximum of 20 episodes per batch
* Episodes can be of mixed types (text, json, message)
* As an experimental feature, may produce slightly different graph structure compared to sequential processing
* Each episode still respects the 10,000 character limit
## Data size and chunking
The same data size limits apply to batch processing as sequential processing. Each episode in the batch is limited to 10,000 characters. For larger documents, chunk them into smaller episodes before adding to the batch.
For chunking strategies and best practices, see the [data size limit and chunking section](/adding-data-to-the-graph#data-size-limit-and-chunking) in the main adding data guide.
# Overview
> Methods for assembling context from user knowledge graphs
After adding data to Zep, you need to retrieve relevant context to provide to your agent. Zep offers three methods for assembling context, each with different levels of control over queries and formatting.
## Available methods
| Method | Query Control | Format Control | Best For |
| ----------------------------------------------------------------- | --------------------------- | -------------- | ----------------------------------------------- |
| [**Zep's context block**](/retrieving-context#zeps-context-block) | Automatic (last 2 messages) | Fixed | Most use cases - optimized relevance and format |
| [**Context templates**](/context-templates) | Automatic (last 2 messages) | Custom | Consistent custom formatting across threads |
| [**Advanced construction**](/advanced-context-block-construction) | Full control | Full control | Maximum flexibility with custom queries |
## Choosing a method
**Use Zep's context block** when you want the simplest integration with optimized defaults. This works well for most conversational agents.
**Use context templates** when you need consistent custom formatting but want Zep to handle relevance detection. This is useful for structured contexts (customer support, sales) where you always want specific sections.
**Use advanced construction** when you need full control over what context is retrieved and how it's formatted. This is ideal for complex applications with custom entity types or specialized retrieval logic.
# Retrieving Context
> Learn how to retrieve relevant context from a User Graph.
Zep provides three methods for retrieving context from a User Graph, each offering different levels of control and customization.
## Choosing a retrieval method
| Method | Query Control | Format Control | Graph Types | Best For |
| ------------------------------------------------------------------------------- | --------------------------- | -------------- | -------------------------------- | ---------------------------------------------------------- |
| [**Zep's Context Block**](#zeps-context-block) | Automatic (last 2 messages) | Fixed | User graphs only | Most use cases - automatic relevance with optimized format |
| [**Custom Context Templates**](#custom-context-templates) | Automatic (last 2 messages) | Custom | User graphs only | Consistent custom formatting across threads/users |
| [**Advanced Context Block Construction**](#advanced-context-block-construction) | Full control | Full control | User graphs or standalone graphs | Maximum flexibility - custom queries and formats |
***
## Zep's Context Block
Zep's Context Block is an optimized, automatically assembled string that you can directly provide as context to your agent. The Context Block combines semantic search, full text search, and breadth first search to return context that is highly relevant to the user's current conversation slice, utilizing the past two messages.
The Context Block is returned by the `thread.get_user_context()` method. This method uses the latest messages of the *given thread* to search the (entire) User Graph and then returns the search results in the form of the Context Block.
Note that although `thread.get_user_context()` only requires a thread ID, it is able to return context derived from any thread of that user. The thread is just used to determine what's relevant.
The Context Block provides low latency (P95 \< 200ms) while preserving detailed information from the user's graph.
**Deprecated: mode parameter and summarized context**
The `mode` parameter is no longer supported. Previously, Zep offered a "summarized" context mode that used an LLM to condense context into a shorter format. However, we were unable to achieve the low latency required for real-time agent interactions with this approach. The Context Block now returns a user summary and structured facts in a detailed format optimized for both performance and information preservation.
### Retrieving the Context Block
```python Python
# Get context for the thread
user_context = client.thread.get_user_context(thread_id=thread_id)
# Access the context block (for use in prompts)
context_block = user_context.context
print(context_block)
```
```typescript TypeScript
// Get context for the thread
const userContext = await client.thread.getUserContext(threadId);
// Access the context block (for use in prompts)
const contextBlock = userContext.context;
console.log(contextBlock);
```
```go Go
import (
"context"
v3 "github.com/getzep/zep-go/v3"
)
// Get context for the thread
userContext, err := client.Thread.GetUserContext(context.TODO(), threadId, nil)
if err != nil {
log.Fatal("Error getting context:", err)
}
// Access the context block (for use in prompts)
contextBlock := userContext.Context
fmt.Println(contextBlock)
```
### Context Block Format
The Context Block returns a user summary along with relevant facts in a structured format:
```text
# This is the user summary
Emily Painter is a user with account ID Emily0e62 who uses digital art tools for creative work. She maintains an active account with the service, though has recently experienced technical issues with the Magic Pen Tool. Emily values reliable payment processing and seeks prompt resolution for account-related issues. She expects clear communication and efficient support when troubleshooting technical problems.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
- Emily is experiencing issues with logging in. (2024-11-14 02:13:19+00:00 - present)
- User account Emily0e62 has a suspended status due to payment failure. (2024-11-14 02:03:58+00:00 - present)
- user has the id of Emily0e62 (2024-11-14 02:03:54 - present)
- The failed transaction used a card with last four digits 1234. (2024-09-15 00:00:00+00:00 - present)
- The reason for the transaction failure was 'Card expired'. (2024-09-15 00:00:00+00:00 - present)
- user has the name of Emily Painter (2024-11-14 02:03:54 - present)
- Account Emily0e62 made a failed transaction of 99.99. (2024-07-30 00:00:00+00:00 - 2024-08-30 00:00:00+00:00)
```
### Getting the Context Block Sooner
You can get the Context Block sooner by passing in the `return_context=True` flag to the `thread.add_messages()` method. Read more about this in our [performance guide](/performance#get-the-context-block-sooner).
## Custom Context Templates
You can customize the format of the Context Block by using [context templates](/context-templates). Templates allow you to define how context data is structured and presented while keeping Zep's automatic relevance detection.
To use a template, pass the `template_id` parameter when retrieving context:
```python Python
from zep_cloud import Zep
client = Zep(api_key="YOUR_API_KEY")
# Create a custom template
client.context.create_context_template(
template_id="customer-support",
template="""# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=10}
# KEY ENTITIES
%{entities limit=5}"""
)
# Use the template to retrieve context
user_context = client.thread.get_user_context(
thread_id="thread_id",
template_id="customer-support"
)
context_block = user_context.context
```
```typescript TypeScript
import { Zep } from "@getzep/zep-cloud";
const client = new Zep({ apiKey: "YOUR_API_KEY" });
// Create a custom template
await client.context.createContextTemplate({
templateId: "customer-support",
template: `# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=10}
# KEY ENTITIES
%{entities limit=5}`
});
// Use the template to retrieve context
const userContext = await client.thread.getUserContext("thread_id", {
templateId: "customer-support"
});
const contextBlock = userContext.context;
```
```go Go
import (
"context"
zep "github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/context"
threadclient "github.com/getzep/zep-go/v3/thread/client"
"github.com/getzep/zep-go/v3/option"
)
contextClient := zepclient.NewClient(
option.WithAPIKey("YOUR_API_KEY"),
)
threadClient := threadclient.NewClient(
option.WithAPIKey("YOUR_API_KEY"),
)
// Create a custom template
_, err := contextClient.CreateContextTemplate(
context.TODO(),
&zep.CreateContextTemplateRequest{
TemplateID: "customer-support",
Template: `# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=10}
# KEY ENTITIES
%{entities limit=5}`,
},
)
// Use the template to retrieve context
templateID := "customer-support"
userContext, err := threadClient.GetUserContext(
context.TODO(),
"thread_id",
&zep.ThreadGetUserContextRequest{
TemplateID: &templateID,
},
)
contextBlock := userContext.Context
```
See the [Context Templates](/context-templates) guide to learn how to create and manage templates.
## Advanced Context Block Construction
For maximum control over context retrieval, see our [Advanced Context Block Construction](/cookbook/advanced-context-block-construction) cookbook. This approach lets you directly [search the graph](/searching-the-graph) and assemble results with complete control over search queries, parameters, and formatting.
## Using Context
### Provide the Context Block in Your System Prompt
Once you've retrieved the [Context Block](#zeps-context-block), used a [custom context template](/context-templates), or [constructed your own context block](/cookbook/advanced-context-block-construction), you can include this string in your system prompt:
| MessageType | Content |
| ----------- | ------------------------------------------------------ |
| `System` | Your system prompt
`{Zep context block}` |
| `Assistant` | An assistant message stored in Zep |
| `User` | A user message stored in Zep |
| ... | ... |
| `User` | The latest user message |
### Provide the Last 4 to 6 Messages of the Thread
You should also include the last 4 to 6 messages of the thread when calling your LLM provider. Because Zep's ingestion can take a few minutes, the context block may not include information from the last few messages; and so the context block acts as the "long-term context," and the last few messages serve as the raw, short-term context.
# Context templates
> Create reusable templates to customize the format of your context blocks.
Context templates allow you to customize how context is formatted and returned when calling `thread.get_user_context()`. Templates are reusable configurations that you set once for your project and can use across all your threads. They let you specify what information goes into your context block and how much of it to include, while Zep handles the automatic relevance detection and retrieval.
## Why use context templates
Context templates let you define a custom context format once and reuse it across all threads and users in your project. They provide a balance between simplicity and control:
* **More control than**: Default Context Block (customize format/structure)
* **Less control than**: Advanced construction with graph search (cannot customize search query)
* **Best for**: When you need consistent custom formatting but want automatic relevance detection
See [Choosing a retrieval method](/retrieving-context#choosing-a-retrieval-method) for a comparison of all three methods.
## Define a template
Templates use variables to specify what data to include. Available variables:
* `%{edges}` - Graph edges (facts/relationships)
* `%{entities}` - Graph entities (nodes)
* `%{episodes}` - Episode data
* `%{user_summary}` - User summary information
Variables (except `user_summary`) accept optional parameters:
* `limit=N` - Limit number of results (max 1000)
* `types=[type1,type2]` - Filter by entity or edge types
* `include_attributes=true/false` - Include/exclude attributes
Example template definition:
```
# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=10}
# KEY ENTITIES
%{entities limit=5 types=[person,organization]}
```
## Create a template
Create a new template with a unique template ID and template content. Templates are validated when created—Zep checks for valid variable names, proper bracket balancing, valid parameter syntax, and limit values within range.
```python Python
from zep_cloud import Zep
client = Zep(api_key="YOUR_API_KEY")
client.context.create_context_template(
template_id="customer-support",
template="""# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=10}
# KEY ENTITIES
%{entities limit=5}"""
)
```
```typescript TypeScript
import { Zep } from "@getzep/zep-cloud";
const client = new Zep({ apiKey: "YOUR_API_KEY" });
await client.context.createContextTemplate({
templateId: "customer-support",
template: `# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=10}
# KEY ENTITIES
%{entities limit=5}`
});
```
```go Go
import (
"context"
zep "github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/context"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey("YOUR_API_KEY"),
)
_, err := client.CreateContextTemplate(
context.TODO(),
&zep.CreateContextTemplateRequest{
TemplateID: "customer-support",
Template: `# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=10}
# KEY ENTITIES
%{entities limit=5}`,
},
)
```
## Use a template
Pass the `template_id` parameter when retrieving context:
```python Python
from zep_cloud import Zep
client = Zep(api_key="YOUR_API_KEY")
user_context = client.thread.get_user_context(
thread_id="thread_id",
template_id="customer-support"
)
context_block = user_context.context
```
```typescript TypeScript
import { Zep } from "@getzep/zep-cloud";
const client = new Zep({ apiKey: "YOUR_API_KEY" });
const userContext = await client.thread.getUserContext("thread_id", {
templateId: "customer-support"
});
const contextBlock = userContext.context;
```
```go Go
import (
"context"
zep "github.com/getzep/zep-go/v3"
threadclient "github.com/getzep/zep-go/v3/thread/client"
"github.com/getzep/zep-go/v3/option"
)
client := threadclient.NewClient(
option.WithAPIKey("YOUR_API_KEY"),
)
templateID := "customer-support"
userContext, err := client.GetUserContext(
context.TODO(),
"thread_id",
&zep.ThreadGetUserContextRequest{
TemplateID: &templateID,
},
)
contextBlock := userContext.Context
```
## Resulting context block
When you use the template above, Zep returns a formatted context block like this:
```text
# CUSTOMER PROFILE
Emily Johnson is a long-time enterprise customer who has been using the platform since March 2024. She is the technical lead for TechCorp Solutions' API integration team and prefers asynchronous communication methods. Emily has shown strong interest in advanced features like SSO integration and webhook capabilities.
# RECENT INTERACTIONS
- (2024-11-15 14:23:00 - present) [CONTACTED_SUPPORT_ABOUT]
- (2024-11-10 09:15:00 - present) [UPGRADED_TO]
- (2024-11-08 16:45:00 - present) [REQUESTED_DOCUMENTATION_FOR]
- (2024-11-05 11:00:00 - present) [ATTENDED]
- (2024-11-01 13:30:00 - present) [INQUIRED_ABOUT]
# KEY ENTITIES
- { name: TechCorp Solutions, types: [organization,company], summary: Emily's company with 500+ employees, currently on Enterprise plan since November 2024 }
- { name: API Integration Team, types: [organization,team], summary: Emily's department responsible for integrating external APIs and managing technical implementations }
- { name: Enterprise Plan, types: [product,subscription], summary: Premium subscription tier with advanced features including SSO, webhooks, and priority support }
- { name: John Smith, types: [person,colleague], summary: Emily's technical team member who collaborates on API integration projects }
- { name: Sarah Chen, types: [person,account_manager], summary: Emily's dedicated account manager who handles enterprise support and feature requests }
```
## Update a template
Update an existing template's content:
```python Python
from zep_cloud import Zep
client = Zep(api_key="YOUR_API_KEY")
client.context.update_context_template(
template_id="customer-support",
template="""# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=20}
# ACCOUNT DETAILS
%{entities types=[subscription,payment_method]}
# TEAM MEMBERS
%{entities limit=10 types=[person]}"""
)
```
```typescript TypeScript
import { Zep } from "@getzep/zep-cloud";
const client = new Zep({ apiKey: "YOUR_API_KEY" });
await client.context.updateContextTemplate("customer-support", {
template: `# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=20}
# ACCOUNT DETAILS
%{entities types=[subscription,payment_method]}
# TEAM MEMBERS
%{entities limit=10 types=[person]}`
});
```
```go Go
import (
"context"
zep "github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/context"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey("YOUR_API_KEY"),
)
_, err := client.UpdateContextTemplate(
context.TODO(),
"customer-support",
&zep.UpdateContextTemplateRequest{
Template: `# CUSTOMER PROFILE
%{user_summary}
# RECENT INTERACTIONS
%{edges limit=20}
# ACCOUNT DETAILS
%{entities types=[subscription,payment_method]}
# TEAM MEMBERS
%{entities limit=10 types=[person]}`,
},
)
```
## Read a template
Retrieve a specific template by its ID or list all templates:
```python Python
from zep_cloud import Zep
client = Zep(api_key="YOUR_API_KEY")
# Get a specific template
template = client.context.get_context_template(template_id="customer-support")
# List all templates
templates = client.context.list_context_templates()
```
```typescript TypeScript
import { Zep } from "@getzep/zep-cloud";
const client = new Zep({ apiKey: "YOUR_API_KEY" });
// Get a specific template
const template = await client.context.getContextTemplate("customer-support");
// List all templates
const templates = await client.context.listContextTemplates();
```
```go Go
import (
"context"
zepclient "github.com/getzep/zep-go/v3/context"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey("YOUR_API_KEY"),
)
// Get a specific template
template, err := client.GetContextTemplate(context.TODO(), "customer-support")
// List all templates
templates, err := client.ListContextTemplates(context.TODO())
```
## Delete a template
Delete a template when it's no longer needed:
```python Python
from zep_cloud import Zep
client = Zep(api_key="YOUR_API_KEY")
client.context.delete_context_template(template_id="customer-support")
```
```typescript TypeScript
import { Zep } from "@getzep/zep-cloud";
const client = new Zep({ apiKey: "YOUR_API_KEY" });
await client.context.deleteContextTemplate("customer-support");
```
```go Go
import (
"context"
zepclient "github.com/getzep/zep-go/v3/context"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey("YOUR_API_KEY"),
)
err := client.DeleteContextTemplate(context.TODO(), "customer-support")
```
# Advanced Context Block Construction
This guide covers building context blocks from scratch using graph search for maximum customization. See [Choosing a retrieval method](/retrieving-context#choosing-a-retrieval-method) for a comparison of all three context retrieval approaches.
When [searching the graph](/searching-the-graph) instead of [using Zep's Context Block](/retrieving-context#zeps-context-block), you need to use the search results to create a custom context block. In this recipe, we will demonstrate how to build a custom Context Block using the [graph search API](/searching-the-graph). We will also use the [custom entity and edge types feature](/customizing-graph-structure#custom-entity-and-edge-types), though using this feature is optional.
# Add data
First, we define our [custom entity and edge types](/customizing-graph-structure#definition-1), create a user, and add some example data:
```python
import uuid
from zep_cloud import Message
from zep_cloud.external_clients.ontology import EntityModel, EntityText, EdgeModel, EntityBoolean
from zep_cloud import EntityEdgeSourceTarget
from pydantic import Field
class Restaurant(EntityModel):
"""
Represents a specific restaurant.
"""
cuisine_type: EntityText = Field(description="The cuisine type of the restaurant, for example: American, Mexican, Indian, etc.", default=None)
dietary_accommodation: EntityText = Field(description="The dietary accommodation of the restaurant, if any, for example: vegetarian, vegan, etc.", default=None)
class RestaurantVisit(EdgeModel):
"""
Represents the fact that the user visited a restaurant.
"""
restaurant_name: EntityText = Field(description="The name of the restaurant the user visited", default=None)
class DietaryPreference(EdgeModel):
"""
Represents the fact that the user has a dietary preference or dietary restriction.
"""
preference_type: EntityText = Field(description="Preference type of the user: anything, vegetarian, vegan, peanut allergy, etc.", default=None)
allergy: EntityBoolean = Field(description="Whether this dietary preference represents a user allergy: True or false", default=None)
client.graph.set_ontology(
entities={
"Restaurant": Restaurant,
},
edges={
"RESTAURANT_VISIT": (
RestaurantVisit,
[EntityEdgeSourceTarget(source="User", target="Restaurant")]
),
"DIETARY_PREFERENCE": (
DietaryPreference,
[EntityEdgeSourceTarget(source="User")]
),
}
)
messages_thread1 = [
Message(content="Take me to a lunch place", role="user", name="John Doe"),
Message(content="How about Panera Bread, Chipotle, or Green Leaf Cafe, which are nearby?", role="assistant", name="Assistant"),
Message(content="Do any of those have vegetarian options? I’m vegetarian", role="user", name="John Doe"),
Message(content="Yes, Green Leaf Cafe has vegetarian options", role="assistant", name="Assistant"),
Message(content="Let’s go to Green Leaf Cafe", role="user", name="John Doe"),
Message(content="Navigating to Green Leaf Cafe", role="assistant", name="Assistant"),
]
messages_thread2 = [
Message(content="Take me to dessert", role="user", name="John Doe"),
Message(content="How about getting some ice cream?", role="assistant", name="Assistant"),
Message(content="I can't have ice cream, I'm lactose intolerant, but I'm craving a chocolate chip cookie", role="user", name="John Doe"),
Message(content="Sure, there's Insomnia Cookies nearby.", role="assistant", name="Assistant"),
Message(content="Perfect, let's go to Insomnia Cookies", role="user", name="John Doe"),
Message(content="Navigating to Insomnia Cookies.", role="assistant", name="Assistant"),
]
user_id = f"user-{uuid.uuid4()}"
client.user.add(user_id=user_id, first_name="John", last_name="Doe", email="john.doe@example.com")
thread1_id = f"thread-{uuid.uuid4()}"
thread2_id = f"thread-{uuid.uuid4()}"
client.thread.create(thread_id=thread1_id, user_id=user_id)
client.thread.create(thread_id=thread2_id, user_id=user_id)
client.thread.add_messages(thread_id=thread1_id, messages=messages_thread1, ignore_roles=["assistant"])
client.thread.add_messages(thread_id=thread2_id, messages=messages_thread2, ignore_roles=["assistant"])
```
```typescript
import { entityFields, EntityType, EdgeType } from "@getzep/zep-cloud/wrapper/ontology";
import { v4 as uuidv4 } from "uuid";
import type { Message } from "@getzep/zep-cloud/api";
const RestaurantSchema: EntityType = {
description: "Represents a specific restaurant.",
fields: {
cuisine_type: entityFields.text("The cuisine type of the restaurant, for example: American, Mexican, Indian, etc."),
dietary_accommodation: entityFields.text("The dietary accommodation of the restaurant, if any, for example: vegetarian, vegan, etc."),
},
};
const RestaurantVisit: EdgeType = {
description: "Represents the fact that the user visited a restaurant.",
fields: {
restaurant_name: entityFields.text("The name of the restaurant the user visited"),
},
sourceTargets: [
{ source: "User", target: "Restaurant" },
],
};
const DietaryPreference: EdgeType = {
description: "Represents the fact that the user has a dietary preference or dietary restriction.",
fields: {
preference_type: entityFields.text("Preference type of the user: anything, vegetarian, vegan, peanut allergy, etc."),
allergy: entityFields.boolean("Whether this dietary preference represents a user allergy: True or false"),
},
sourceTargets: [
{ source: "User" },
],
};
await client.graph.setOntology(
{
Restaurant: RestaurantSchema,
},
{
RESTAURANT_VISIT: RestaurantVisit,
DIETARY_PREFERENCE: DietaryPreference,
}
);
const messagesthread1: Message[] = [
{ content: "Take me to a lunch place", role: "user", name: "John Doe" },
{ content: "How about Panera Bread, Chipotle, or Green Leaf Cafe, which are nearby?", role: "assistant", name: "Assistant" },
{ content: "Do any of those have vegetarian options? I’m vegetarian", role: "user", name: "John Doe" },
{ content: "Yes, Green Leaf Cafe has vegetarian options", role: "assistant", name: "Assistant" },
{ content: "Let’s go to Green Leaf Cafe", role: "user", name: "John Doe" },
{ content: "Navigating to Green Leaf Cafe", role: "assistant", name: "Assistant" },
];
const messagesthread2: Message[] = [
{ content: "Take me to dessert", role: "user", name: "John Doe" },
{ content: "How about getting some ice cream?", role: "assistant", name: "Assistant" },
{ content: "I can't have ice cream, I'm lactose intolerant, but I'm craving a chocolate chip cookie", role: "user", name: "John Doe" },
{ content: "Sure, there's Insomnia Cookies nearby.", role: "assistant", name: "Assistant" },
{ content: "Perfect, let's go to Insomnia Cookies", role: "user", name: "John Doe" },
{ content: "Navigating to Insomnia Cookies.", role: "assistant", name: "Assistant" },
];
let userId = `user-${uuidv4()}`;
await client.user.add({ userId, firstName: "John", lastName: "Doe", email: "john.doe@example.com" });
const thread1Id = `thread-${uuidv4()}`;
const thread2Id = `thread-${uuidv4()}`;
await client.thread.create({ threadId: thread1Id, userId });
await client.thread.create({ threadId: thread2Id, userId });
await client.thread.addMessages(thread1Id, { messages: messagesthread1, ignoreRoles: ["assistant"] });
await client.thread.addMessages(thread2Id, { messages: messagesthread2, ignoreRoles: ["assistant"] });
```
```go
import (
"github.com/getzep/zep-go/v3"
"github.com/google/uuid"
)
type Restaurant struct {
zep.BaseEntity `name:"Restaurant" description:"Represents a specific restaurant."`
CuisineType string `description:"The cuisine type of the restaurant, for example: American, Mexican, Indian, etc." json:"cuisine_type,omitempty"`
DietaryAccommodation string `description:"The dietary accommodation of the restaurant, if any, for example: vegetarian, vegan, etc." json:"dietary_accommodation,omitempty"`
}
type RestaurantVisit struct {
zep.BaseEdge `name:"RESTAURANT_VISIT" description:"Represents the fact that the user visited a restaurant."`
RestaurantName string `description:"The name of the restaurant the user visited" json:"restaurant_name,omitempty"`
}
type DietaryPreference struct {
zep.BaseEdge `name:"DIETARY_PREFERENCE" description:"Represents the fact that the user has a dietary preference or dietary restriction."`
PreferenceType string `description:"Preference type of the user: anything, vegetarian, vegan, peanut allergy, etc." json:"preference_type,omitempty"`
Allergy bool `description:"Whether this dietary preference represents a user allergy: True or false" json:"allergy,omitempty"`
}
_, err = client.Graph.SetOntology(
ctx,
[]zep.EntityDefinition{
Restaurant{},
},
[]zep.EdgeDefinitionWithSourceTargets{
{
EdgeModel: RestaurantVisit{},
SourceTargets: []zep.EntityEdgeSourceTarget{
{
Source: zep.String("User"),
Target: zep.String("Restaurant"),
},
},
},
{
EdgeModel: DietaryPreference{},
SourceTargets: []zep.EntityEdgeSourceTarget{
{
Source: zep.String("User"),
},
},
},
},
)
if err != nil {
fmt.Printf("Error setting ontology: %v\n", err)
return
}
messagesthread1 := []zep.Message{
{Content: "Take me to a lunch place", Role: "user", Name: zep.String("John Doe")},
{Content: "How about Panera Bread, Chipotle, or Green Leaf Cafe, which are nearby?", Role: "assistant", Name: zep.String("Assistant")},
{Content: "Do any of those have vegetarian options? I'm vegetarian", Role: "user", Name: zep.String("John Doe")},
{Content: "Yes, Green Leaf Cafe has vegetarian options", Role: "assistant", Name: zep.String("Assistant")},
{Content: "Let's go to Green Leaf Cafe", Role: "user", Name: zep.String("John Doe")},
{Content: "Navigating to Green Leaf Cafe", Role: "assistant", Name: zep.String("Assistant")},
}
messagesthread2 := []zep.Message{
{Content: "Take me to dessert", Role: "user", Name: zep.String("John Doe")},
{Content: "How about getting some ice cream?", Role: "assistant", Name: zep.String("Assistant")},
{Content: "I can't have ice cream, I'm lactose intolerant, but I'm craving a chocolate chip cookie", Role: "user", Name: zep.String("John Doe")},
{Content: "Sure, there's Insomnia Cookies nearby.", Role: "assistant", Name: zep.String("Assistant")},
{Content: "Perfect, let's go to Insomnia Cookies", Role: "user", Name: zep.String("John Doe")},
{Content: "Navigating to Insomnia Cookies.", Role: "assistant", Name: zep.String("Assistant")},
}
userID := "user-" + uuid.NewString()
userReq := &zep.CreateUserRequest{
UserID: userID,
FirstName: zep.String("John"),
LastName: zep.String("Doe"),
Email: zep.String("john.doe@example.com"),
}
_, err = client.User.Add(ctx, userReq)
if err != nil {
fmt.Printf("Error creating user: %v\n", err)
return
}
thread1ID := "thread-" + uuid.NewString()
thread2ID := "thread-" + uuid.NewString()
thread1Req := &zep.CreateThreadRequest{
threadID: thread1ID,
UserID: userID,
}
thread2Req := &zep.CreateThreadRequest{
threadID: thread2ID,
UserID: userID,
}
_, err = client.Thread.Create(ctx, thread1Req)
if err != nil {
fmt.Printf("Error creating thread 1: %v\n", err)
return
}
_, err = client.Thread.Create(ctx, thread2Req)
if err != nil {
fmt.Printf("Error creating thread 2: %v\n", err)
return
}
msgPtrs1 := make([]*zep.Message, len(messagesthread1))
for i := range messagesthread1 {
msgPtrs1[i] = &messagesthread1[i]
}
addReq1 := &zep.AddThreadMessagesRequest{
Messages: msgPtrs1,
IgnoreRoles: []zep.RoleType{
zep.RoleTypeAssistantRole,
},
}
_, err = client.Thread.AddMessages(ctx, thread1ID, addReq1)
if err != nil {
fmt.Printf("Error adding messages to thread 1: %v\n", err)
return
}
msgPtrs2 := make([]*zep.Message, len(messagesthread2))
for i := range messagesthread2 {
msgPtrs2[i] = &messagesthread2[i]
}
addReq2 := &zep.AddThreadMessagesRequest{
Messages: msgPtrs2,
IgnoreRoles: []zep.RoleType{
zep.RoleTypeAssistantRole,
},
}
_, err = client.Thread.AddMessages(ctx, thread2ID, addReq2)
if err != nil {
fmt.Printf("Error adding messages to thread 2: %v\n", err)
return
}
```
# Example 1: Basic custom context block
## Search
For a basic custom context block, we search the graph for edges and nodes relevant to our custom query string, which typically represents a user message. Note that the default [Context Block](/retrieving-context#zeps-context-block) returned by `thread.get_user_context` uses the past few messages as the query instead.
These searches can be performed in parallel to reduce latency, using our [async Python client](/quickstart#initialize-the-client), TypeScript promises, or goroutines.
```python
query = "Find some food around here"
search_results_nodes = client.graph.search(
query=query,
user_id=user_id,
scope='nodes',
reranker='cross_encoder',
limit=10
)
search_results_edges = client.graph.search(
query=query,
user_id=user_id,
scope='edges',
reranker='cross_encoder',
limit=10
)
```
```typescript
let query = "Find some food around here";
const searchResultsNodes = await client.graph.search({
userId: userId,
query: query,
scope: "nodes",
reranker: "cross_encoder",
limit: 10,
});
const searchResultsEdges = await client.graph.search({
userId: userId,
query: query,
scope: "edges",
reranker: "cross_encoder",
limit: 10,
});
```
```go
import (
"github.com/getzep/zep-go/v2/graph"
)
query := "Find some food around here"
searchResultsNodes, err := client.Graph.Search(
ctx,
&zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: query,
Scope: zep.GraphSearchScopeNodes.Ptr(),
Reranker: zep.RerankerCrossEncoder.Ptr(),
Limit: zep.Int(10),
},
)
if err != nil {
fmt.Printf("Error searching graph (nodes): %v\n", err)
return
}
searchResultsEdges, err := client.Graph.Search(
ctx,
&zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: query,
Scope: zep.GraphSearchScopeEdges.Ptr(),
Reranker: zep.RerankerCrossEncoder.Ptr(),
Limit: zep.Int(10),
},
)
if err != nil {
fmt.Printf("Error searching graph (edges): %v\n", err)
return
}
```
## Build the context block
Using the search results and a few helper functions, we can build the context block. Note that for nodes, we typically want to unpack the node name and node summary, and for edges we typically want to unpack the fact and the temporal validity information:
```python
from zep_cloud import EntityEdge, EntityNode
CONTEXT_STRING_TEMPLATE = """
FACTS and ENTITIES represent relevant context to the current conversation.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
# NOTE: Facts ending in "present" are currently valid (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - present)" means Jane currently prefers coffee with milk)
# Facts with a past end date used to be valid but are NOT CURRENTLY VALID (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - 2024-06-20 14:00:00)" means Jane no longer prefers coffee with milk)
{facts}
# These are the most relevant entities
# ENTITY_NAME: entity summary
{entities}
"""
def format_fact(edge: EntityEdge) -> str:
valid_at = edge.valid_at if edge.valid_at is not None else "date unknown"
invalid_at = edge.invalid_at if edge.invalid_at is not None else "present"
formatted_fact = f" - {edge.fact} (Date range: {valid_at} - {invalid_at})"
return formatted_fact
def format_entity(node: EntityNode) -> str:
formatted_entity = f" - {node.name}: {node.summary}"
return formatted_entity
def compose_context_block(edges: list[EntityEdge], nodes: list[EntityNode]) -> str:
facts = [format_fact(edge) for edge in edges]
entities = [format_entity(node) for node in nodes]
return CONTEXT_STRING_TEMPLATE.format(facts='\n'.join(facts), entities='\n'.join(entities))
edges = search_results_edges.edges
nodes = search_results_nodes.nodes
context_block = compose_context_block(edges, nodes)
print(context_block)
```
```typescript
import type { EntityEdge, EntityNode } from "@getzep/zep-cloud/api";
const CONTEXT_STRING_TEMPLATE_1 = `FACTS and ENTITIES represent relevant context to the current conversation.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
# NOTE: Facts ending in "present" are currently valid (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - present)" means Jane currently prefers coffee with milk)
# Facts with a past end date used to be valid but are NOT CURRENTLY VALID (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - 2024-06-20 14:00:00)" means Jane no longer prefers coffee with milk)
{facts}
# These are the most relevant entities
# ENTITY_NAME: entity summary
{entities}
`;
function formatFact(edge: EntityEdge): string {
const validAt = edge.validAt ?? "date unknown";
const invalidAt = edge.invalidAt ?? "present";
return ` - ${edge.fact} (Date range: ${validAt} - ${invalidAt})`;
}
function formatEntity(node: EntityNode): string {
return ` - ${node.name}: ${node.summary}`;
}
function composeContextBlock1(edges: EntityEdge[], nodes: EntityNode[]): string {
const facts = edges.map(formatFact).join('\n');
const entities = nodes.map(formatEntity).join('\n');
return CONTEXT_STRING_TEMPLATE_1
.replace('{facts}', facts)
.replace('{entities}', entities);
}
const edges: EntityEdge[] = searchResultsEdges.edges ?? [];
const nodes: EntityNode[] = searchResultsNodes.nodes ?? [];
const contextBlock1 = composeContextBlock1(edges, nodes);
console.log(contextBlock1);
```
```go
import (
"strings"
)
const CONTEXT_STRING_TEMPLATE_1 = `FACTS and ENTITIES represent relevant context to the current conversation.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
# NOTE: Facts ending in "present" are currently valid (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - present)" means Jane currently prefers coffee with milk)
# Facts with a past end date used to be valid but are NOT CURRENTLY VALID (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - 2024-06-20 14:00:00)" means Jane no longer prefers coffee with milk)
{facts}
# These are the most relevant entities
# ENTITY_NAME: entity summary
{entities}
`
formatFact := func(edge *zep.EntityEdge) string {
validAt := "date unknown"
if edge.ValidAt != nil && *edge.ValidAt != "" {
validAt = *edge.ValidAt
}
invalidAt := "present"
if edge.InvalidAt != nil && *edge.InvalidAt != "" {
invalidAt = *edge.InvalidAt
}
return fmt.Sprintf(" - %s (Date range: %s - %s)", edge.Fact, validAt, invalidAt)
}
formatEntity := func(node *zep.EntityNode) string {
return fmt.Sprintf(" - %s: %s", node.Name, node.Summary)
}
composeContextBlock1 := func(edges []*zep.EntityEdge, nodes []*zep.EntityNode) string {
var facts []string
for _, edge := range edges {
facts = append(facts, formatFact(edge))
}
var entities []string
for _, node := range nodes {
entities = append(entities, formatEntity(node))
}
result := strings.ReplaceAll(CONTEXT_STRING_TEMPLATE_1, "{facts}", strings.Join(facts, "\n"))
result = strings.ReplaceAll(result, "{entities}", strings.Join(entities, "\n"))
return result
}
edges := searchResultsEdges.Edges
nodes := searchResultsNodes.Nodes
contextBlock1 := composeContextBlock1(edges, nodes)
fmt.Println(contextBlock1)
```
```text
FACTS and ENTITIES represent relevant context to the current conversation.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
# NOTE: Facts ending in "present" are currently valid (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - present)" means Jane currently prefers coffee with milk)
# Facts with a past end date used to be valid but are NOT CURRENTLY VALID (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - 2024-06-20 14:00:00)" means Jane no longer prefers coffee with milk)
- User wants to go to dessert (Date range: 2025-06-16T02:17:25Z - present)
- John Doe wants to go to a lunch place (Date range: 2025-06-16T02:17:25Z - present)
- John Doe said 'Perfect, let's go to Insomnia Cookies' indicating he will visit Insomnia Cookies. (Date range: 2025-06-16T02:17:25Z - present)
- John Doe said 'Let’s go to Green Leaf Cafe' indicating intention to visit (Date range: 2025-06-16T02:17:25Z - present)
- John Doe is craving a chocolate chip cookie (Date range: 2025-06-16T02:17:25Z - present)
- John Doe states that he is vegetarian. (Date range: 2025-06-16T02:17:25Z - present)
- John Doe is lactose intolerant (Date range: 2025-06-16T02:17:25Z - present)
# These are the most relevant entities
# ENTITY_NAME: entity summary
- lunch place: The entity is a lunch place, but no specific details about its cuisine or dietary accommodations are provided.
- dessert: The entity 'dessert' refers to a preference related to sweet courses typically served at the end of a meal. The context indicates that the user has expressed an interest in going to a dessert place, but no specific dessert or place has been named. The entity is categorized as a Preference and Entity, but no additional attributes are provided or inferred from the messages.
- Green Leaf Cafe: Green Leaf Cafe is a restaurant that offers vegetarian options, making it suitable for vegetarian diners.
- user: The user is John Doe, with the email john.doe@example.com. He has shown interest in visiting Green Leaf Cafe, which offers vegetarian options, and has also expressed a preference for lactose-free options, craving a chocolate chip cookie. The user has decided to go to Insomnia Cookies.
- vegetarian: The user is interested in lunch places such as Panera Bread, Chipotle, and Green Leaf Cafe. They are specifically looking for vegetarian options at these restaurants.
- chocolate chip cookie: The entity is a chocolate chip cookie, which the user desires as a snack. The user is lactose intolerant and cannot have ice cream, but is craving a chocolate chip cookie.
- Insomnia Cookies: Insomnia Cookies is a restaurant that offers cookies, including chocolate chip cookies. The user is interested in a dessert and has chosen to go to Insomnia Cookies. No specific cuisine type or dietary accommodations are mentioned in the messages.
- lactose intolerant: The entity is a preference indicating lactose intolerance, which is a dietary restriction that prevents the individual from consuming lactose, a sugar found in milk and dairy products. The person is specifically craving a chocolate chip cookie but cannot have ice cream due to lactose intolerance.
- John Doe: The user is John Doe, with user ID user-34c7a6c1-ded6-4797-9620-8b80a5e7820f, email john.doe@example.com, and role user. He inquired about nearby lunch options and vegetarian choices, and expressed a preference for a chocolate chip cookie due to lactose intolerance.
```
# Example 2: Utilizing custom entity and edge types
## Search
For a custom context block that uses custom entity and edge types, we perform multiple searches (with our custom query string) filtering to the custom entity or edge type we want to include in the context block:
These searches can be performed in parallel to reduce latency, using our [async Python client](/quickstart#initialize-the-client), TypeScript promises, or goroutines.
```python
query = "Find some food around here"
search_results_restaurant_visits = client.graph.search(
query=query,
user_id=user_id,
scope='edges',
search_filters={
"edge_types": ["RESTAURANT_VISIT"]
},
reranker='cross_encoder',
limit=10
)
search_results_dietary_preferences = client.graph.search(
query=query,
user_id=user_id,
scope='edges',
search_filters={
"edge_types": ["DIETARY_PREFERENCE"]
},
reranker='cross_encoder',
limit=10
)
search_results_restaurants = client.graph.search(
query=query,
user_id=user_id,
scope='nodes',
search_filters={
"node_labels": ["Restaurant"]
},
reranker='cross_encoder',
limit=10
)
```
```typescript
query = "Find some food around here";
const searchResultsRestaurantVisits = await client.graph.search({
query,
userId: userId,
scope: "edges",
searchFilters: {
edgeTypes: ["RESTAURANT_VISIT"]
},
reranker: "cross_encoder",
limit: 10,
});
const searchResultsDietaryPreferences = await client.graph.search({
query,
userId: userId,
scope: "edges",
searchFilters: {
edgeTypes: ["DIETARY_PREFERENCE"]
},
reranker: "cross_encoder",
limit: 10,
});
const searchResultsRestaurants = await client.graph.search({
query,
userId: userId,
scope: "nodes",
searchFilters: {
nodeLabels: ["Restaurant"]
},
reranker: "cross_encoder",
limit: 10,
});
```
```go
query := "Find some food around here"
searchFiltersRestaurantVisits := zep.SearchFilters{EdgeTypes: []string{"RESTAURANT_VISIT"}}
searchResultsRestaurantVisits, err := client.Graph.Search(
ctx,
&zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: query,
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &searchFiltersRestaurantVisits,
Reranker: zep.RerankerCrossEncoder.Ptr(),
Limit: zep.Int(10),
},
)
if err != nil {
fmt.Printf("Error searching graph (RESTAURANT_VISIT edges): %v\n", err)
return
}
searchFiltersDietaryPreferences := zep.SearchFilters{EdgeTypes: []string{"DIETARY_PREFERENCE"}}
searchResultsDietaryPreferences, err := client.Graph.Search(
ctx,
&zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: query,
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &searchFiltersDietaryPreferences,
Reranker: zep.RerankerCrossEncoder.Ptr(),
Limit: zep.Int(10),
},
)
if err != nil {
fmt.Printf("Error searching graph (DIETARY_PREFERENCE edges): %v\n", err)
return
}
searchFiltersRestaurants := zep.SearchFilters{NodeLabels: []string{"Restaurant"}}
searchResultsRestaurants, err := client.Graph.Search(
ctx,
&zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: query,
Scope: zep.GraphSearchScopeNodes.Ptr(),
SearchFilters: &searchFiltersRestaurants,
Reranker: zep.RerankerCrossEncoder.Ptr(),
Limit: zep.Int(10),
},
)
if err != nil {
fmt.Printf("Error searching graph (Restaurant nodes): %v\n", err)
return
}
```
## Build the context block
Using the search results and a few helper functions, we can compose the context block. Note that in this example, we focus on unpacking the custom attributes of the nodes and edges, but this is a design choice that you can experiment with for your use case.
Note also that we designed the context block template around the custom entity and edge types that we are unpacking into the context block:
```python
from zep_cloud import EntityEdge, EntityNode
CONTEXT_STRING_TEMPLATE = """
PREVIOUS_RESTAURANT_VISITS, DIETARY_PREFERENCES, and RESTAURANTS represent relevant context to the current conversation.
# These are the most relevant restaurants the user has previously visited
# format: restaurant_name: RESTAURANT_NAME
{restaurant_visits}
# These are the most relevant dietary preferences of the user, whether they represent an allergy, and their valid date ranges
# format: allergy: True/False; preference_type: PREFERENCE_TYPE (Date range: from - to)
{dietary_preferences}
# These are the most relevant restaurants the user has discussed previously
# format: name: RESTAURANT_NAME; cuisine_type: CUISINE_TYPE; dietary_accommodation: DIETARY_ACCOMMODATION
{restaurants}
"""
def format_edge_with_attributes(edge: EntityEdge, include_timestamps: bool = True) -> str:
attrs_str = '; '.join(f"{k}: {v}" for k, v in sorted(edge.attributes.items()))
if include_timestamps:
valid_at = edge.valid_at if edge.valid_at is not None else "date unknown"
invalid_at = edge.invalid_at if edge.invalid_at is not None else "present"
return f" - {attrs_str} (Date range: {valid_at} - {invalid_at})"
return f" - {attrs_str}"
def format_node_with_attributes(node: EntityNode) -> str:
attributes = {k: v for k, v in node.attributes.items() if k != "labels"}
attrs_str = '; '.join(f"{k}: {v}" for k, v in sorted(attributes.items()))
base = f" - name: {node.name}; {attrs_str}"
return base
def compose_context_block(restaurant_visit_edges: list[EntityEdge], dietary_preference_edges: list[EntityEdge], restaurant_nodes: list[EntityNode]) -> str:
restaurant_visits = [format_edge_with_attributes(edge, include_timestamps=False) for edge in restaurant_visit_edges]
dietary_preferences = [format_edge_with_attributes(edge, include_timestamps=True) for edge in dietary_preference_edges]
restaurant_nodes = [format_node_with_attributes(node) for node in restaurant_nodes]
return CONTEXT_STRING_TEMPLATE.format(restaurant_visits='\n'.join(restaurant_visits), dietary_preferences='\n'.join(dietary_preferences), restaurants='\n'.join(restaurant_nodes))
restaurant_visit_edges = search_results_restaurant_visits.edges
dietary_preference_edges = search_results_dietary_preferences.edges
restaurant_nodes = search_results_restaurants.nodes
context_block = compose_context_block(restaurant_visit_edges, dietary_preference_edges, restaurant_nodes)
print(context_block)
```
```typescript
import type { EntityEdge, EntityNode } from "@getzep/zep-cloud/api";
const CONTEXT_STRING_TEMPLATE_2 = `PREVIOUS_RESTAURANT_VISITS, DIETARY_PREFERENCES, and RESTAURANTS represent relevant context to the current conversation.
# These are the most relevant restaurants the user has previously visited
# format: restaurant_name: RESTAURANT_NAME
{restaurant_visits}
# These are the most relevant dietary preferences of the user, whether they represent an allergy, and their valid date ranges
# format: allergy: True/False; preference_type: PREFERENCE_TYPE (Date range: from - to)
{dietary_preferences}
# These are the most relevant restaurants the user has discussed previously
# format: name: RESTAURANT_NAME; cuisine_type: CUISINE_TYPE; dietary_accommodation: DIETARY_ACCOMMODATION
{restaurants}
`;
function formatEdgeWithAttributes(edge: EntityEdge, includeTimestamps = true): string {
const attrs = Object.entries(edge.attributes ?? {})
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => `${k}: ${v}`)
.join('; ');
if (includeTimestamps) {
const validAt = edge.validAt ?? "date unknown";
const invalidAt = edge.invalidAt ?? "present";
return ` - ${attrs} (Date range: ${validAt} - ${invalidAt})`;
}
return ` - ${attrs}`;
}
function formatNodeWithAttributes(node: EntityNode): string {
const attributes = Object.entries(node.attributes ?? {})
.filter(([k]) => k !== "labels")
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => `${k}: ${v}`)
.join('; ');
return ` - name: ${node.name}; ${attributes}`;
}
function composeContextBlock2(
restaurantVisitEdges: EntityEdge[],
dietaryPreferenceEdges: EntityEdge[],
restaurantNodes: EntityNode[]
): string {
const restaurantVisits = restaurantVisitEdges.map(e => formatEdgeWithAttributes(e, false)).join('\n');
const dietaryPreferences = dietaryPreferenceEdges.map(e => formatEdgeWithAttributes(e, true)).join('\n');
const restaurants = restaurantNodes.map(n => formatNodeWithAttributes(n)).join('\n');
return CONTEXT_STRING_TEMPLATE_2
.replace('{restaurant_visits}', restaurantVisits)
.replace('{dietary_preferences}', dietaryPreferences)
.replace('{restaurants}', restaurants);
}
const restaurantVisitEdges: EntityEdge[] = searchResultsRestaurantVisits.edges ?? [];
const dietaryPreferenceEdges: EntityEdge[] = searchResultsDietaryPreferences.edges ?? [];
const restaurantNodes: EntityNode[] = searchResultsRestaurants.nodes ?? [];
const contextBlock2 = composeContextBlock2(restaurantVisitEdges, dietaryPreferenceEdges, restaurantNodes);
console.log(contextBlock2);
```
```go
import (
"strings"
)
const CONTEXT_STRING_TEMPLATE_2 = `PREVIOUS_RESTAURANT_VISITS, DIETARY_PREFERENCES, and RESTAURANTS represent relevant context to the current conversation.
# These are the most relevant restaurants the user has previously visited
# format: restaurant_name: RESTAURANT_NAME
{restaurant_visits}
# These are the most relevant dietary preferences of the user, whether they represent an allergy, and their valid date ranges
# format: allergy: True/False; preference_type: PREFERENCE_TYPE (Date range: from - to)
{dietary_preferences}
# These are the most relevant restaurants the user has discussed previously
# format: name: RESTAURANT_NAME; cuisine_type: CUISINE_TYPE; dietary_accommodation: DIETARY_ACCOMMODATION
{restaurants}
`
formatEdgeWithAttributes := func(edge *zep.EntityEdge, includeTimestamps bool) string {
attrs := make([]string, 0)
for _, k := range []string{"allergy", "preference_type", "restaurant_name"} {
if v, ok := edge.Attributes[k]; ok {
attrs = append(attrs, fmt.Sprintf("%s: %v", k, v))
}
}
attrsStr := strings.Join(attrs, "; ")
if includeTimestamps {
validAt := "date unknown"
if edge.ValidAt != nil && *edge.ValidAt != "" {
validAt = *edge.ValidAt
}
invalidAt := "present"
if edge.InvalidAt != nil && *edge.InvalidAt != "" {
invalidAt = *edge.InvalidAt
}
return fmt.Sprintf(" - %s (Date range: %s - %s)", attrsStr, validAt, invalidAt)
}
return fmt.Sprintf(" - %s", attrsStr)
}
formatNodeWithAttributes := func(node *zep.EntityNode) string {
attrs := make([]string, 0)
for k, v := range node.Attributes {
if k == "labels" {
continue
}
attrs = append(attrs, fmt.Sprintf("%s: %v", k, v))
}
attrsStr := strings.Join(attrs, "; ")
return fmt.Sprintf(" - name: %s; %s", node.Name, attrsStr)
}
composeContextBlock2 := func(restaurantVisitEdges []*zep.EntityEdge, dietaryPreferenceEdges []*zep.EntityEdge, restaurantNodes []*zep.EntityNode) string {
restaurantVisits := make([]string, 0)
for _, edge := range restaurantVisitEdges {
restaurantVisits = append(restaurantVisits, formatEdgeWithAttributes(edge, false))
}
dietaryPreferences := make([]string, 0)
for _, edge := range dietaryPreferenceEdges {
dietaryPreferences = append(dietaryPreferences, formatEdgeWithAttributes(edge, true))
}
restaurants := make([]string, 0)
for _, node := range restaurantNodes {
restaurants = append(restaurants, formatNodeWithAttributes(node))
}
result := strings.ReplaceAll(CONTEXT_STRING_TEMPLATE_2, "{restaurant_visits}", strings.Join(restaurantVisits, "\n"))
result = strings.ReplaceAll(result, "{dietary_preferences}", strings.Join(dietaryPreferences, "\n"))
result = strings.ReplaceAll(result, "{restaurants}", strings.Join(restaurants, "\n"))
return result
}
restaurantVisitEdges := searchResultsRestaurantVisits.Edges
dietaryPreferenceEdges := searchResultsDietaryPreferences.Edges
restaurantNodes := searchResultsRestaurants.Nodes
contextBlock2 := composeContextBlock2(restaurantVisitEdges, dietaryPreferenceEdges, restaurantNodes)
fmt.Println(contextBlock2)
```
```text
PREVIOUS_RESTAURANT_VISITS, DIETARY_PREFERENCES, and RESTAURANTS represent relevant context to the current conversation.
# These are the most relevant restaurants the user has previously visited
# format: restaurant_name: RESTAURANT_NAME
- restaurant_name: Insomnia Cookies
- restaurant_name: Green Leaf Cafe
# These are the most relevant dietary preferences of the user, whether they represent an allergy, and their valid date ranges
# format: allergy: True/False; preference_type: PREFERENCE_TYPE (Date range: from - to)
- allergy: False; preference_type: vegetarian (Date range: 2025-06-16T02:17:25Z - present)
- allergy: False; preference_type: lactose intolerance (Date range: 2025-06-16T02:17:25Z - present)
# These are the most relevant restaurants the user has discussed previously
# format: name: RESTAURANT_NAME; cuisine_type: CUISINE_TYPE; dietary_accommodation: DIETARY_ACCOMMODATION
- name: Green Leaf Cafe; dietary_accommodation: vegetarian
- name: Insomnia Cookies;
```
# Example 3: Basic custom context block with BFS
## Search
For a more advanced custom context block, we can enhance the search results by using Breadth-First Search (BFS) to make them more relevant to the user's recent history. In this example, we retrieve the past several [episodes](/graphiti/graphiti/adding-episodes) and use those episode IDs as the BFS node IDs. We use BFS here to make the search results more relevant to the user's recent history. You can read more about how BFS works in the [Breadth-First Search section](/searching-the-graph#breadth-first-search-bfs) of our searching the graph documentation.
These searches can be performed in parallel to reduce latency, using our [async Python client](/quickstart#initialize-the-client), TypeScript promises, or goroutines.
```python
query = "Find some food around here"
episodes = client.graph.episode.get_by_user_id(
user_id=user_id,
lastn=10
).episodes
episode_uuids = [episode.uuid_ for episode in episodes if episode.role_type == 'user']
search_results_nodes = client.graph.search(
query=query,
user_id=user_id,
scope='nodes',
reranker='cross_encoder',
limit=10,
bfs_origin_node_uuids=episode_uuids
)
search_results_edges = client.graph.search(
query=query,
user_id=user_id,
scope='edges',
reranker='cross_encoder',
limit=10,
bfs_origin_node_uuids=episode_uuids
)
```
```typescript
let query = "Find some food around here";
let episodeResponse = await client.graph.episode.getByUserId(userId, { lastn: 10 });
let episodeUuids = (episodeResponse.episodes || [])
.filter((episode) => episode.roleType === "user")
.map((episode) => episode.uuid);
const searchResultsNodes = await client.graph.search({
userId: userId,
query: query,
scope: "nodes",
reranker: "cross_encoder",
limit: 10,
bfsOriginNodeUuids: episodeUuids,
});
const searchResultsEdges = await client.graph.search({
userId: userId,
query: query,
scope: "edges",
reranker: "cross_encoder",
limit: 10,
bfsOriginNodeUuids: episodeUuids,
});
```
```go
import (
"github.com/getzep/zep-go/v2/graph"
)
query := "Find some food around here"
response, err := client.Graph.Episode.GetByUserID(
ctx,
userID,
&graph.EpisodeGetByUserIDRequest{
Lastn: zep.Int(10),
},
)
if err != nil {
fmt.Printf("Error getting episodes: %v\n", err)
return
}
var episodeUUIDs1 []string
for _, episode := range response.Episodes {
if episode.RoleType != nil && *episode.RoleType == zep.RoleTypeUserRole {
episodeUUIDs1 = append(episodeUUIDs1, episode.UUID)
}
}
searchResultsNodes, err := client.Graph.Search(
ctx,
&zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: query,
Scope: zep.GraphSearchScopeNodes.Ptr(),
Reranker: zep.RerankerCrossEncoder.Ptr(),
Limit: zep.Int(10),
BfsOriginNodeUUIDs: episodeUUIDs1,
},
)
if err != nil {
fmt.Printf("Error searching graph (nodes): %v\n", err)
return
}
searchResultsEdges, err := client.Graph.Search(
ctx,
&zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: query,
Scope: zep.GraphSearchScopeEdges.Ptr(),
Reranker: zep.RerankerCrossEncoder.Ptr(),
Limit: zep.Int(10),
BfsOriginNodeUUIDs: episodeUUIDs1,
},
)
if err != nil {
fmt.Printf("Error searching graph (edges): %v\n", err)
return
}
```
## Build the context block
Using the search results and a few helper functions, we can build the context block. Note that for nodes, we typically want to unpack the node name and node summary, and for edges we typically want to unpack the fact and the temporal validity information:
```python
from zep_cloud import EntityEdge, EntityNode
CONTEXT_STRING_TEMPLATE = """
FACTS and ENTITIES represent relevant context to the current conversation.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
# NOTE: Facts ending in "present" are currently valid (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - present)" means Jane currently prefers coffee with milk)
# Facts with a past end date used to be valid but are NOT CURRENTLY VALID (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - 2024-06-20 14:00:00)" means Jane no longer prefers coffee with milk)
{facts}
# These are the most relevant entities
# ENTITY_NAME: entity summary
{entities}
"""
def format_fact(edge: EntityEdge) -> str:
valid_at = edge.valid_at if edge.valid_at is not None else "date unknown"
invalid_at = edge.invalid_at if edge.invalid_at is not None else "present"
formatted_fact = f" - {edge.fact} (Date range: {valid_at} - {invalid_at})"
return formatted_fact
def format_entity(node: EntityNode) -> str:
formatted_entity = f" - {node.name}: {node.summary}"
return formatted_entity
def compose_context_block(edges: list[EntityEdge], nodes: list[EntityNode]) -> str:
facts = [format_fact(edge) for edge in edges]
entities = [format_entity(node) for node in nodes]
return CONTEXT_STRING_TEMPLATE.format(facts='\n'.join(facts), entities='\n'.join(entities))
edges = search_results_edges.edges
nodes = search_results_nodes.nodes
context_block = compose_context_block(edges, nodes)
print(context_block)
```
```typescript
import type { EntityEdge, EntityNode } from "@getzep/zep-cloud/api";
const CONTEXT_STRING_TEMPLATE_1 = `FACTS and ENTITIES represent relevant context to the current conversation.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
# NOTE: Facts ending in "present" are currently valid (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - present)" means Jane currently prefers coffee with milk)
# Facts with a past end date used to be valid but are NOT CURRENTLY VALID (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - 2024-06-20 14:00:00)" means Jane no longer prefers coffee with milk)
{facts}
# These are the most relevant entities
# ENTITY_NAME: entity summary
{entities}
`;
function formatFact(edge: EntityEdge): string {
const validAt = edge.validAt ?? "date unknown";
const invalidAt = edge.invalidAt ?? "present";
return ` - ${edge.fact} (Date range: ${validAt} - ${invalidAt})`;
}
function formatEntity(node: EntityNode): string {
return ` - ${node.name}: ${node.summary}`;
}
function composeContextBlock1(edges: EntityEdge[], nodes: EntityNode[]): string {
const facts = edges.map(formatFact).join('\n');
const entities = nodes.map(formatEntity).join('\n');
return CONTEXT_STRING_TEMPLATE_1
.replace('{facts}', facts)
.replace('{entities}', entities);
}
const edges: EntityEdge[] = searchResultsEdges.edges ?? [];
const nodes: EntityNode[] = searchResultsNodes.nodes ?? [];
const contextBlock1 = composeContextBlock1(edges, nodes);
console.log(contextBlock1);
```
```go
import (
"strings"
)
const CONTEXT_STRING_TEMPLATE_1 = `FACTS and ENTITIES represent relevant context to the current conversation.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
# NOTE: Facts ending in "present" are currently valid (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - present)" means Jane currently prefers coffee with milk)
# Facts with a past end date used to be valid but are NOT CURRENTLY VALID (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - 2024-06-20 14:00:00)" means Jane no longer prefers coffee with milk)
{facts}
# These are the most relevant entities
# ENTITY_NAME: entity summary
{entities}
`
formatFact := func(edge *zep.EntityEdge) string {
validAt := "date unknown"
if edge.ValidAt != nil && *edge.ValidAt != "" {
validAt = *edge.ValidAt
}
invalidAt := "present"
if edge.InvalidAt != nil && *edge.InvalidAt != "" {
invalidAt = *edge.InvalidAt
}
return fmt.Sprintf(" - %s (Date range: %s - %s)", edge.Fact, validAt, invalidAt)
}
formatEntity := func(node *zep.EntityNode) string {
return fmt.Sprintf(" - %s: %s", node.Name, node.Summary)
}
composeContextBlock1 := func(edges []*zep.EntityEdge, nodes []*zep.EntityNode) string {
var facts []string
for _, edge := range edges {
facts = append(facts, formatFact(edge))
}
var entities []string
for _, node := range nodes {
entities = append(entities, formatEntity(node))
}
result := strings.ReplaceAll(CONTEXT_STRING_TEMPLATE_1, "{facts}", strings.Join(facts, "\n"))
result = strings.ReplaceAll(result, "{entities}", strings.Join(entities, "\n"))
return result
}
edges := searchResultsEdges.Edges
nodes := searchResultsNodes.Nodes
contextBlock1 := composeContextBlock1(edges, nodes)
fmt.Println(contextBlock1)
```
```text
FACTS and ENTITIES represent relevant context to the current conversation.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
# NOTE: Facts ending in "present" are currently valid (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - present)" means Jane currently prefers coffee with milk)
# Facts with a past end date used to be valid but are NOT CURRENTLY VALID (e.g., "Jane prefers her coffee with milk (2024-01-15 10:30:00 - 2024-06-20 14:00:00)" means Jane no longer prefers coffee with milk)
- User wants to go to dessert (Date range: 2025-06-16T02:17:25Z - present)
- John Doe wants to go to a lunch place (Date range: 2025-06-16T02:17:25Z - present)
- John Doe said 'Perfect, let's go to Insomnia Cookies' indicating he will visit Insomnia Cookies. (Date range: 2025-06-16T02:17:25Z - present)
- John Doe said 'Let's go to Green Leaf Cafe' indicating intention to visit (Date range: 2025-06-16T02:17:25Z - present)
- John Doe is craving a chocolate chip cookie (Date range: 2025-06-16T02:17:25Z - present)
- John Doe states that he is vegetarian. (Date range: 2025-06-16T02:17:25Z - present)
- John Doe is lactose intolerant (Date range: 2025-06-16T02:17:25Z - present)
# These are the most relevant entities
# ENTITY_NAME: entity summary
- lunch place: The entity is a lunch place, but no specific details about its cuisine or dietary accommodations are provided.
- dessert: The entity 'dessert' refers to a preference related to sweet courses typically served at the end of a meal. The context indicates that the user has expressed an interest in going to a dessert place, but no specific dessert or place has been named. The entity is categorized as a Preference and Entity, but no additional attributes are provided or inferred from the messages.
- Green Leaf Cafe: Green Leaf Cafe is a restaurant that offers vegetarian options, making it suitable for vegetarian diners.
- user: The user is John Doe, with the email john.doe@example.com. He has shown interest in visiting Green Leaf Cafe, which offers vegetarian options, and has also expressed a preference for lactose-free options, craving a chocolate chip cookie. The user has decided to go to Insomnia Cookies.
- vegetarian: The user is interested in lunch places such as Panera Bread, Chipotle, and Green Leaf Cafe. They are specifically looking for vegetarian options at these restaurants.
- chocolate chip cookie: The entity is a chocolate chip cookie, which the user desires as a snack. The user is lactose intolerant and cannot have ice cream, but is craving a chocolate chip cookie.
- Insomnia Cookies: Insomnia Cookies is a restaurant that offers cookies, including chocolate chip cookies. The user is interested in a dessert and has chosen to go to Insomnia Cookies. No specific cuisine type or dietary accommodations are mentioned in the messages.
- lactose intolerant: The entity is a preference indicating lactose intolerance, which is a dietary restriction that prevents the individual from consuming lactose, a sugar found in milk and dairy products. The person is specifically craving a chocolate chip cookie but cannot have ice cream due to lactose intolerance.
- John Doe: The user is John Doe, with user ID user-34c7a6c1-ded6-4797-9620-8b80a5e7820f, email john.doe@example.com, and role user. He inquired about nearby lunch options and vegetarian choices, and expressed a preference for a chocolate chip cookie due to lactose intolerance.
```
# Example 4: Using user summary in context block
## Get user node
You can retrieve the user node and use its summary to create a simple, personalized context block. This approach is particularly useful when you want to include high-level user information generated from [user summary instructions](/users#user-summary-instructions).
**About the user node**
Each user has a single unique user node in their graph representing the user themselves. The user summary generated from user summary instructions lives on this user node. When you call `client.user.get_node()`, you are retrieving this special node that contains the user's summary.
```python Python
from zep_cloud.client import Zep
client = Zep(api_key=API_KEY)
# Get the user node and extract the summary
user_node_response = client.user.get_node(user_id=user_id)
user_summary = user_node_response.node.summary if user_node_response.node else None
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Get the user node and extract the summary
const userNodeResponse = await client.user.getNode(userId);
const userSummary = userNodeResponse.node?.summary;
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(option.WithAPIKey(apiKey))
// Get the user node and extract the summary
userNodeResponse, err := client.User.GetNode(context.TODO(), userID)
if err != nil {
log.Fatalf("Failed to get user node: %v", err)
}
var userSummary string
if userNodeResponse.Node != nil && userNodeResponse.Node.Summary != nil {
userSummary = *userNodeResponse.Node.Summary
}
```
## Build the context block
Using the user summary, you can create a simple context block that provides personalized user information:
```python Python
# Build a simple context block with user summary
context_block = f"""USER_SUMMARY represents relevant context about the user.
# This is a high-level summary of the user
{user_summary if user_summary else "No user summary available"}
"""
print(context_block)
```
```typescript TypeScript
// Build a simple context block with user summary
const contextBlock = `USER_SUMMARY represents relevant context about the user.
# This is a high-level summary of the user
${userSummary || "No user summary available"}
`;
console.log(contextBlock);
```
```go Go
import "fmt"
// Build a simple context block with user summary
summaryText := userSummary
if summaryText == "" {
summaryText = "No user summary available"
}
contextBlock := fmt.Sprintf(`USER_SUMMARY represents relevant context about the user.
# This is a high-level summary of the user
%s
`, summaryText)
fmt.Println(contextBlock)
```
```text
USER_SUMMARY represents relevant context about the user.
# This is a high-level summary of the user
John Doe is a software engineer who enjoys hiking and photography. He is vegetarian and lactose intolerant. He prefers detailed technical discussions and values efficiency in communication. He has requested that the AI provide concise answers with code examples when discussing programming topics.
```
# Overview
> Guide how Zep builds context from your data
Zep automatically extracts entities, relationships, and summaries from the data you add. You can customize this process to better fit your domain and use case.
## Available options
| Option | Purpose | Scope |
| ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | --------------------------------- |
| [**Default entity and edge types**](/customizing-graph-structure#default-entity-and-edge-types) | Built-in classifications for common entities and relationships | Applied to user graphs by default |
| [**Custom entity and edge types**](/customizing-graph-structure#custom-entity-and-edge-types) | Domain-specific data structures with custom attributes | Project-wide or per-user/graph |
| [**User summary instructions**](/user-summary-instructions) | Customize how user summaries are generated | Project-wide or per-user |
## When to customize
**Custom ontology** is an often recommended feature that improves results for most production use cases. When a custom ontology is defined, Zep's extraction process focuses on the entity and relationship types most important to your domain, often producing better results than defaults alone.
**Customize summaries when**:
* Default summary questions don't capture information relevant to your use case
* You need summaries focused on specific aspects (professional, preferences, interaction style)
# Customizing Graph Structure
Zep enables the use of rich, domain-specific data structures in graphs through Entity Types and Edge Types, replacing generic graph nodes and edges with detailed models.
Zep classifies newly created nodes/edges as one of the default or custom types or leaves them unclassified. For example, a node representing a preference is classified as a Preference node, and attributes specific to that type are automatically populated. You may restrict graph queries to nodes/edges of a specific type, such as Preference.
The default entity and edge types are applied to user graphs (not all graphs) by default, but you may define additional custom types as needed.
Each node/edge is classified as a single type only. Multiple classifications are not supported.
## Why use custom ontology
Custom entity and edge types improve graph construction by focusing extraction on the most important entities and relationships for your domain. When a custom ontology is defined, Zep's extraction process prioritizes these types, resulting in more relevant and structured data for your use case.
Even if Zep's default types cover your basic needs, defining a custom ontology tailored to your domain often produces better results because:
* Extraction focuses on entities and relationships that matter most to your application
* Graph structure aligns more closely with your domain model
* Retrieved context becomes more relevant and actionable
## Default Entity and Edge Types
### Definition
Zep provides default entity and edge types that are automatically applied to user graphs (not all graphs). These types help classify and structure the information extracted from conversations.
You can view the exact definition for the default ontology [here](https://github.com/getzep/zep/blob/main/ontology/default_ontology.py).
#### Default Entity Types
The default entity types are:
* **User**: A Zep user specified by role in chat messages. There can only be a single User entity.
* **Assistant**: Represents the AI assistant in the conversation. This entity is a singleton.
* **Preference**: Entities mentioned in contexts expressing user preferences, choices, opinions, or selections. This classification is prioritized over most other classifications.
* **Location**: A physical or virtual place where activities occur or entities exist. Use this classification only after checking if the entity fits other more specific types.
* **Event**: A time-bound activity, occurrence, or experience.
* **Object**: A physical item, tool, device, or possession. Use this classification only as a last resort after checking other types.
* **Topic**: A subject of conversation, interest, or knowledge domain. Use this classification only as a last resort after checking other types.
* **Organization**: A company, institution, group, or formal entity.
* **Document**: Information content in various forms.
#### Default Edge Types
The default edge types are:
* **LOCATED\_AT**: Represents that an entity exists or occurs at a specific location. Connects any entity to a Location.
* **OCCURRED\_AT**: Represents that an event happened at a specific time or location. Connects an Event to any entity.
Default entity and edge types apply to user graphs. All nodes and edges in any user graph will be classified into one of these types or none.
### Adding Data
When we add data to the graph, default entity and edge types are automatically created:
```python
from zep_cloud.types import Message
message = {
"name": "John Doe",
"role": "user",
"content": "I really like pop music, and I don't like metal",
}
client.thread.add_messages(thread_id=thread_id, messages=[Message(**message)])
```
```typescript
const messages = [{
name: "John Doe",
role: "user",
content: "I really like pop music, and I don't like metal",
}];
await client.thread.addMessages(threadId, {messages: messages});
```
```go
userName := "John Doe"
messages := []*v3.Message{
{
Name: &userName,
Content: "I really like pop music, and I don't like metal",
Role: "user",
},
}
// Add the messages to the graph
_, err = zepClient.Thread.AddMessages(
context.TODO(),
threadId,
&v3.AddThreadMessagesRequest{
Messages: messages,
},
)
if err != nil {
log.Fatal("Error adding messages:", err)
}
```
### Searching
When searching nodes in the graph, you may provide a list of types to filter the search by. The provided types are ORed together. Search results will only include nodes that satisfy one of the provided types:
```python
search_results = client.graph.search(
user_id=user_id,
query="the user's music preferences",
scope="nodes",
search_filters={
"node_labels": ["Preference"]
}
)
for i, node in enumerate(search_results.nodes):
preference = node.attributes
print(f"Preference {i+1}:{preference}")
```
```typescript
const searchResults = await client.graph.search({
userId: userId,
query: "the user's music preferences",
scope: "nodes",
searchFilters: {
nodeLabels: ["Preference"],
},
});
if (searchResults.nodes && searchResults.nodes.length > 0) {
for (let i = 0; i < searchResults.nodes.length; i++) {
const node = searchResults.nodes[i];
const preference = node.attributes;
console.log(`Preference ${i + 1}: ${JSON.stringify(preference)}`);
}
}
```
```go
searchFilters := v3.SearchFilters{NodeLabels: []string{"Preference"}}
searchResults, err := client.Graph.Search(
ctx,
&v3.GraphSearchQuery{
UserID: v3.String(userID),
Query: "the user's music preferences",
Scope: v3.GraphSearchScopeNodes.Ptr(),
SearchFilters: &searchFilters,
},
)
if err != nil {
log.Fatal("Error searching graph:", err)
}
for i, node := range searchResults.Nodes {
// Convert attributes map to JSON for pretty printing
attributesJSON, err := json.MarshalIndent(node.Attributes, "", " ")
if err != nil {
log.Fatal("Error marshaling attributes:", err)
}
fmt.Printf("Preference %d:\n%s\n\n", i+1, string(attributesJSON))
}
```
```text
Preference 1: {'category': 'Music', 'description': 'Pop Music is a genre of music characterized by its catchy melodies and widespread appeal.', 'labels': ['Entity', 'Preference']}
Preference 2: {'category': 'Music', 'description': 'Metal Music is a genre of music characterized by its heavy sound and complex compositions.', 'labels': ['Entity', 'Preference']}
```
### Disabling Default Ontology
In some cases, you may want to disable the default entity and edge types for specific users and only use custom types you define. You can do this by setting the `disable_default_ontology` flag when creating or updating a user.
When `disable_default_ontology` is set to `true`:
* Only custom entity and edge types you define will be used for classification
* The default entity and edge types (User, Assistant, Preference, Location, etc.) will not be applied
* Nodes and edges will only be classified as your custom types or remain unclassified
This is useful when you need precise control over your graph structure and want to ensure only domain-specific types are used.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
# Create a user with default ontology disabled
user = client.user.add(
user_id=user_id,
first_name="John",
last_name="Doe",
email="john.doe@example.com",
disable_default_ontology=True
)
# Or update an existing user to disable default ontology
client.user.update(
user_id=user_id,
disable_default_ontology=True
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Create a user with default ontology disabled
const user = await client.user.add({
userId: userId,
firstName: "John",
lastName: "Doe",
email: "john.doe@example.com",
disableDefaultOntology: true
});
// Or update an existing user to disable default ontology
await client.user.update(userId, {
disableDefaultOntology: true
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
// Create a user with default ontology disabled
user, err := client.User.Add(
context.TODO(),
&zep.CreateUserRequest{
UserID: userID,
FirstName: zep.String("John"),
LastName: zep.String("Doe"),
Email: zep.String("john.doe@example.com"),
DisableDefaultOntology: zep.Bool(true),
},
)
// Or update an existing user to disable default ontology
_, err = client.User.Update(
context.TODO(),
userID,
&zep.UpdateUserRequest{
DisableDefaultOntology: zep.Bool(true),
},
)
```
## Custom Entity and Edge Types
Start with fewer, more generic custom types with minimal fields and simple definitions, then incrementally add complexity as needed. This functionality requires prompt engineering and iterative optimization of the class and field descriptions, so it's best to start simple.
### Definition
In addition to the default entity and edge types, you may specify your own custom entity and custom edge types. You need to provide a description of the type and a description for each of the fields. The syntax for this is different for each language.
You may not create more than 10 custom entity types and 10 custom edge types when setting ontology for a specific graph. The same limit of 10 custom entity types and 10 custom edge types also applies when setting ontology project-wide. The limit of 10 custom entity types does not include the default types. Each model may have up to 10 fields.
When creating custom entity or edge types, you may not use the following attribute names (including in Go struct tags), as they conflict with default node attributes: `uuid`, `name`, `graph_id`, `name_embedding`, `summary`, and `created_at`.
Including attributes on custom entity and edge types is an advanced feature designed for precision context engineering where you only want to utilize specific field values when constructing your context block. [See here for an example](cookbook/advanced-context-block-construction#example-2-utilizing-custom-entity-and-edge-types). Many agent memory use cases can be solved with node summaries and facts alone. Custom attributes should only be added when you need structured field values for precise context retrieval rather than general conversational context.
```python
from zep_cloud.external_clients.ontology import EntityModel, EntityText, EdgeModel, EntityBoolean
from pydantic import Field
class Restaurant(EntityModel):
"""
Represents a specific restaurant.
"""
cuisine_type: EntityText = Field(description="The cuisine type of the restaurant, for example: American, Mexican, Indian, etc.", default=None)
dietary_accommodation: EntityText = Field(description="The dietary accommodation of the restaurant, if any, for example: vegetarian, vegan, etc.", default=None)
class Audiobook(EntityModel):
"""
Represents an audiobook entity.
"""
genre: EntityText = Field(description="The genre of the audiobook, for example: self-help, fiction, nonfiction, etc.", default=None)
class RestaurantVisit(EdgeModel):
"""
Represents the fact that the user visited a restaurant.
"""
restaurant_name: EntityText = Field(description="The name of the restaurant the user visited", default=None)
class AudiobookListen(EdgeModel):
"""
Represents the fact that the user listened to or played an audiobook.
"""
audiobook_title: EntityText = Field(description="The title of the audiobook the user listened to or played", default=None)
class DietaryPreference(EdgeModel):
"""
Represents the fact that the user has a dietary preference or dietary restriction.
"""
preference_type: EntityText = Field(description="Preference type of the user: anything, vegetarian, vegan, peanut allergy, etc.", default=None)
allergy: EntityBoolean = Field(description="Whether this dietary preference represents a user allergy: True or false", default=None)
```
```typescript
import { entityFields, EntityType, EdgeType } from "@getzep/zep-cloud";
const RestaurantSchema: EntityType = {
description: "Represents a specific restaurant.",
fields: {
cuisine_type: entityFields.text("The cuisine type of the restaurant, for example: American, Mexican, Indian, etc."),
dietary_accommodation: entityFields.text("The dietary accommodation of the restaurant, if any, for example: vegetarian, vegan, etc."),
},
};
const AudiobookSchema: EntityType = {
description: "Represents an audiobook entity.",
fields: {
genre: entityFields.text("The genre of the audiobook, for example: self-help, fiction, nonfiction, etc."),
},
};
const RestaurantVisit: EdgeType = {
description: "Represents the fact that the user visited a restaurant.",
fields: {
restaurant_name: entityFields.text("The name of the restaurant the user visited"),
},
sourceTargets: [
{ source: "User", target: "Restaurant" },
],
};
const AudiobookListen: EdgeType = {
description: "Represents the fact that the user listened to or played an audiobook.",
fields: {
audiobook_title: entityFields.text("The title of the audiobook the user listened to or played"),
},
sourceTargets: [
{ source: "User", target: "Audiobook" },
],
};
const DietaryPreference: EdgeType = {
description: "Represents the fact that the user has a dietary preference or dietary restriction.",
fields: {
preference_type: entityFields.text("Preference type of the user: anything, vegetarian, vegan, peanut allergy, etc."),
allergy: entityFields.boolean("Whether this dietary preference represents a user allergy: True or false"),
},
sourceTargets: [
{ source: "User" },
],
};
```
```go
type Restaurant struct {
zep.BaseEntity `name:"Restaurant" description:"Represents a specific restaurant."`
CuisineType string `description:"The cuisine type of the restaurant, for example: American, Mexican, Indian, etc." json:"cuisine_type,omitempty"`
DietaryAccommodation string `description:"The dietary accommodation of the restaurant, if any, for example: vegetarian, vegan, etc." json:"dietary_accommodation,omitempty"`
}
type Audiobook struct {
zep.BaseEntity `name:"Audiobook" description:"Represents an audiobook entity."`
Genre string `description:"The genre of the audiobook, for example: self-help, fiction, nonfiction, etc." json:"genre,omitempty"`
}
type RestaurantVisit struct {
zep.BaseEdge `name:"RESTAURANT_VISIT" description:"Represents the fact that the user visited a restaurant."`
RestaurantName string `description:"The name of the restaurant the user visited" json:"restaurant_name,omitempty"`
}
type AudiobookListen struct {
zep.BaseEdge `name:"AUDIOBOOK_LISTEN" description:"Represents the fact that the user listened to or played an audiobook."`
AudiobookTitle string `description:"The title of the audiobook the user listened to or played" json:"audiobook_title,omitempty"`
}
type DietaryPreference struct {
zep.BaseEdge `name:"DIETARY_PREFERENCE" description:"Represents the fact that the user has a dietary preference or dietary restriction."`
PreferenceType string `description:"Preference type of the user: anything, vegetarian, vegan, peanut allergy, etc." json:"preference_type,omitempty"`
Allergy bool `description:"Whether this dietary preference represents a user allergy: True or false" json:"allergy,omitempty"`
}
```
### Setting Entity and Edge Types
You can set these custom entity and edge types as the graph ontology for your current Zep project. The ontology can be applied either project-wide to all users and graphs, or targeted to specific users and graphs only.
#### Setting Types Project Wide
When no user IDs or graph IDs are provided, the ontology is set for the entire project. All users and graphs within the project will use this ontology. Note that for custom edge types, you can require the source and destination nodes to be a certain type, or allow them to be any type:
```python
from zep_cloud import EntityEdgeSourceTarget
client.graph.set_ontology(
entities={
"Restaurant": Restaurant,
"Audiobook": Audiobook,
},
edges={
"RESTAURANT_VISIT": (
RestaurantVisit,
[EntityEdgeSourceTarget(source="User", target="Restaurant")]
),
"AUDIOBOOK_LISTEN": (
AudiobookListen,
[EntityEdgeSourceTarget(source="User", target="Audiobook")]
),
"DIETARY_PREFERENCE": (
DietaryPreference,
[EntityEdgeSourceTarget(source="User")]
),
}
)
```
```typescript
await client.graph.setOntology(
{
Restaurant: RestaurantSchema,
Audiobook: AudiobookSchema,
},
{
RESTAURANT_VISIT: RestaurantVisit,
AUDIOBOOK_LISTEN: AudiobookListen,
DIETARY_PREFERENCE: DietaryPreference,
}
);
```
```go
_, err = client.Graph.SetOntology(
ctx,
[]zep.EntityDefinition{
Restaurant{},
Audiobook{},
},
[]zep.EdgeDefinitionWithSourceTargets{
{
EdgeModel: RestaurantVisit{},
SourceTargets: []zep.EntityEdgeSourceTarget{
{
Source: zep.String("User"),
Target: zep.String("Restaurant"),
},
},
},
{
EdgeModel: AudiobookListen{},
SourceTargets: []zep.EntityEdgeSourceTarget{
{
Source: zep.String("User"),
Target: zep.String("Audiobook"),
},
},
},
{
EdgeModel: DietaryPreference{},
SourceTargets: []zep.EntityEdgeSourceTarget{
{
Source: zep.String("User"),
},
},
},
},
)
if err != nil {
fmt.Printf("Error setting ontology: %v\n", err)
return
}
```
#### Setting Types For Specific Graphs
You can also set the ontology for specific users and/or graphs by providing user IDs and graph IDs. When these parameters are provided, the ontology will only apply to the specified users and graphs, while other users and graphs in the project will continue using the previously set ontology (whether that was due to a project-wide setting of ontology or due to a graph-specific setting of ontology):
```python
from zep_cloud import EntityEdgeSourceTarget
await client.graph.set_ontology(
user_ids=["user_1234", "user_5678"],
graph_ids=["graph_1234", "graph_5678"],
entities={
"Restaurant": Restaurant,
"Audiobook": Audiobook,
},
edges={
"RESTAURANT_VISIT": (
RestaurantVisit,
[EntityEdgeSourceTarget(source="User", target="Restaurant")]
),
"AUDIOBOOK_LISTEN": (
AudiobookListen,
[EntityEdgeSourceTarget(source="User", target="Audiobook")]
),
"DIETARY_PREFERENCE": (
DietaryPreference,
[EntityEdgeSourceTarget(source="User")]
),
}
)
```
```typescript
await client.graph.setOntology(
{
Restaurant: RestaurantSchema,
Audiobook: AudiobookSchema,
},
{
RESTAURANT_VISIT: RestaurantVisit,
AUDIOBOOK_LISTEN: AudiobookListen,
DIETARY_PREFERENCE: DietaryPreference,
},
{
userIds: ["user_1234", "user_5678"],
graphIds: ["graph_1234", "graph_5678"],
}
);
```
```go
_, err := client.Graph.SetOntology(
ctx,
[]zep.EntityDefinition{
Restaurant{},
Audiobook{},
},
[]zep.EdgeDefinitionWithSourceTargets{
{
EdgeModel: RestaurantVisit{},
SourceTargets: []zep.EntityEdgeSourceTarget{
{
Source: zep.String("User"),
Target: zep.String("Restaurant"),
},
},
},
{
EdgeModel: AudiobookListen{},
SourceTargets: []zep.EntityEdgeSourceTarget{
{
Source: zep.String("User"),
Target: zep.String("Audiobook"),
},
},
},
{
EdgeModel: DietaryPreference{},
SourceTargets: []zep.EntityEdgeSourceTarget{
{
Source: zep.String("User"),
},
},
},
},
zep.ForUsers([]string{"user_1234", "user_5678"}),
zep.ForGraphs([]string{"graph_1234", "graph_5678"}),
)
if err != nil {
fmt.Printf("Error setting ontology: %v\n", err)
return
}
```
### Adding Data
Now, when you add data to the graph, new nodes and edges are classified into exactly one of the overall set of entity or edge types respectively, or no type:
```python
from zep_cloud import Message
import uuid
messages_thread1 = [
Message(content="Take me to a lunch place", role="user", name="John Doe"),
Message(content="How about Panera Bread, Chipotle, or Green Leaf Cafe, which are nearby?", role="assistant", name="Assistant"),
Message(content="Do any of those have vegetarian options? I'm vegetarian", role="user", name="John Doe"),
Message(content="Yes, Green Leaf Cafe has vegetarian options", role="assistant", name="Assistant"),
Message(content="Let's go to Green Leaf Cafe", role="user", name="John Doe"),
Message(content="Navigating to Green Leaf Cafe", role="assistant", name="Assistant"),
]
messages_thread2 = [
Message(content="Play the 7 habits of highly effective people", role="user", name="John Doe"),
Message(content="Playing the 7 habits of highly effective people", role="assistant", name="Assistant"),
]
user_id = f"user-{uuid.uuid4()}"
client.user.add(user_id=user_id, first_name="John", last_name="Doe", email="john.doe@example.com")
thread1_id = f"thread-{uuid.uuid4()}"
thread2_id = f"thread-{uuid.uuid4()}"
client.thread.create(thread_id=thread1_id, user_id=user_id)
client.thread.create(thread_id=thread2_id, user_id=user_id)
client.thread.add_messages(thread_id=thread1_id, messages=messages_thread1, ignore_roles=["assistant"])
client.thread.add_messages(thread_id=thread2_id, messages=messages_thread2, ignore_roles=["assistant"])
```
```typescript
import { v4 as uuidv4 } from "uuid";
import type { Message } from "@getzep/zep-cloud/api";
const messagesThread1: Message[] = [
{ content: "Take me to a lunch place", role: "user", name: "John Doe" },
{ content: "How about Panera Bread, Chipotle, or Green Leaf Cafe, which are nearby?", role: "assistant", name: "Assistant" },
{ content: "Do any of those have vegetarian options? I'm vegetarian", role: "user", name: "John Doe" },
{ content: "Yes, Green Leaf Cafe has vegetarian options", role: "assistant", name: "Assistant" },
{ content: "Let's go to Green Leaf Cafe", role: "user", name: "John Doe" },
{ content: "Navigating to Green Leaf Cafe", role: "assistant", name: "Assistant" },
];
const messagesThread2: Message[] = [
{ content: "Play the 7 habits of highly effective people", role: "user", name: "John Doe" },
{ content: "Playing the 7 habits of highly effective people", role: "assistant", name: "Assistant" },
];
let userId = `user-${uuidv4()}`;
await client.user.add({ userId, firstName: "John", lastName: "Doe", email: "john.doe@example.com" });
const thread1Id = `thread-${uuidv4()}`;
const thread2Id = `thread-${uuidv4()}`;
await client.thread.create({ threadId: thread1Id, userId });
await client.thread.create({ threadId: thread2Id, userId });
await client.thread.addMessages(thread1Id, { messages: messagesThread1, ignoreRoles: ["assistant"] });
await client.thread.addMessages(thread2Id, { messages: messagesThread2, ignoreRoles: ["assistant"] });
```
```go
messagesThread1 := []zep.Message{
{Content: "Take me to a lunch place", Role: "user", Name: zep.String("John Doe")},
{Content: "How about Panera Bread, Chipotle, or Green Leaf Cafe, which are nearby?", Role: "assistant", Name: zep.String("Assistant")},
{Content: "Do any of those have vegetarian options? I'm vegetarian", Role: "user", Name: zep.String("John Doe")},
{Content: "Yes, Green Leaf Cafe has vegetarian options", Role: "assistant", Name: zep.String("Assistant")},
{Content: "Let's go to Green Leaf Cafe", Role: "user", Name: zep.String("John Doe")},
{Content: "Navigating to Green Leaf Cafe", Role: "assistant", Name: zep.String("Assistant")},
}
messagesThread2 := []zep.Message{
{Content: "Play the 7 habits of highly effective people", Role: "user", Name: zep.String("John Doe")},
{Content: "Playing the 7 habits of highly effective people", Role: "assistant", Name: zep.String("Assistant")},
}
userID := "user-" + uuid.NewString()
userReq := &zep.CreateUserRequest{
UserID: userID,
FirstName: zep.String("John"),
LastName: zep.String("Doe"),
Email: zep.String("john.doe@example.com"),
}
_, err := client.User.Add(ctx, userReq)
if err != nil {
fmt.Printf("Error creating user: %v\n", err)
return
}
thread1ID := "thread-" + uuid.NewString()
thread2ID := "thread-" + uuid.NewString()
thread1Req := &zep.CreateThreadRequest{
ThreadID: thread1ID,
UserID: userID,
}
thread2Req := &zep.CreateThreadRequest{
ThreadID: thread2ID,
UserID: userID,
}
_, err = client.Thread.Create(ctx, thread1Req)
if err != nil {
fmt.Printf("Error creating thread 1: %v\n", err)
return
}
_, err = client.Thread.Create(ctx, thread2Req)
if err != nil {
fmt.Printf("Error creating thread 2: %v\n", err)
return
}
msgPtrs1 := make([]*zep.Message, len(messagesThread1))
for i := range messagesThread1 {
msgPtrs1[i] = &messagesThread1[i]
}
addReq1 := &zep.AddThreadMessagesRequest{
Messages: msgPtrs1,
IgnoreRoles: []zep.RoleType{
zep.RoleTypeAssistantRole,
},
}
_, err = client.Thread.AddMessages(ctx, thread1ID, addReq1)
if err != nil {
fmt.Printf("Error adding messages to thread 1: %v\n", err)
return
}
msgPtrs2 := make([]*zep.Message, len(messagesThread2))
for i := range messagesThread2 {
msgPtrs2[i] = &messagesThread2[i]
}
addReq2 := &zep.AddThreadMessagesRequest{
Messages: msgPtrs2,
IgnoreRoles: []zep.RoleType{
zep.RoleTypeAssistantRole,
},
}
_, err = client.Thread.AddMessages(ctx, thread2ID, addReq2)
if err != nil {
fmt.Printf("Error adding messages to thread 2: %v\n", err)
return
}
```
### Retrieving Custom Types
Once you've created custom entity and edge types in your graph, you'll typically want to retrieve information filtered by these specific types. There are two primary approaches:
1. **Context Templates** (Recommended): Use context templates to create reusable context blocks that automatically filter by type. This is ideal when you want consistent formatting with automatic relevance detection.
2. **Graph Search**: Use direct graph search for more granular control over queries and when you need dynamic, query-based retrieval.
#### Using Context Templates
Context templates provide a convenient way to create context blocks that filter by your custom entity and edge types. You define a template once and Zep automatically retrieves and formats the relevant information.
Context templates support the `types` parameter on `%{entities}` and `%{edges}` variables to filter by your custom types:
```python
# Create a template that filters by custom types
client.context.create_context_template(
template_id="restaurant-audiobook-context",
template="""# USER PREFERENCES
%{edges types=[DIETARY_PREFERENCE] limit=5}
# RESTAURANT VISITS
%{edges types=[RESTAURANT_VISIT] limit=10}
# RESTAURANTS
%{entities types=[Restaurant] limit=5}
# AUDIOBOOKS
%{entities types=[Audiobook] limit=5}
# AUDIOBOOK LISTENING HISTORY
%{edges types=[AUDIOBOOK_LISTEN] limit=10}"""
)
# Use the template to get context
results = client.thread.get_user_context(
thread_id=thread1_id,
template_id="restaurant-audiobook-context"
)
print(results.context)
```
```typescript
// Create a template that filters by custom types
await client.context.createContextTemplate({
templateId: "restaurant-audiobook-context",
template: `# USER PREFERENCES
%{edges types=[DIETARY_PREFERENCE] limit=5}
# RESTAURANT VISITS
%{edges types=[RESTAURANT_VISIT] limit=10}
# RESTAURANTS
%{entities types=[Restaurant] limit=5}
# AUDIOBOOKS
%{entities types=[Audiobook] limit=5}
# AUDIOBOOK LISTENING HISTORY
%{edges types=[AUDIOBOOK_LISTEN] limit=10}`
});
// Use the template to get context
const results = await client.thread.getUserContext(thread1Id, {
templateId: "restaurant-audiobook-context"
});
console.log(results.context);
```
```go
// Create a template that filters by custom types
_, err = client.Context.CreateContextTemplate(
ctx,
&zep.CreateContextTemplateRequest{
TemplateID: "restaurant-audiobook-context",
Template: `# USER PREFERENCES
%{edges types=[DIETARY_PREFERENCE] limit=5}
# RESTAURANT VISITS
%{edges types=[RESTAURANT_VISIT] limit=10}
# RESTAURANTS
%{entities types=[Restaurant] limit=5}
# AUDIOBOOKS
%{entities types=[Audiobook] limit=5}
# AUDIOBOOK LISTENING HISTORY
%{edges types=[AUDIOBOOK_LISTEN] limit=10}`,
},
)
if err != nil {
fmt.Printf("Error creating context template: %v\n", err)
return
}
// Use the template to get context
templateID := "restaurant-audiobook-context"
results, err := client.Thread.GetUserContext(
ctx,
thread1ID,
&zep.ThreadGetUserContextRequest{
TemplateID: &templateID,
},
)
if err != nil {
fmt.Printf("Error getting user context: %v\n", err)
return
}
fmt.Println(results.Context)
```
The resulting context block will automatically include only the specified types, formatted according to your template:
```text
# USER PREFERENCES
- (2024-11-20 10:30:00 - present) [DIETARY_PREFERENCE] { preference_type: vegetarian, allergy: false }
# RESTAURANT VISITS
- (2024-11-20 10:35:00 - present) [RESTAURANT_VISIT] { restaurant_name: Green Leaf Cafe }
# RESTAURANTS
- { name: Green Leaf Cafe, types: [Entity,Restaurant], summary: Green Leaf Cafe is a restaurant that offers vegetarian options, attributes: { dietary_accommodation: vegetarian } }
# AUDIOBOOKS
- { name: 7 habits of highly effective people, types: [Entity,Audiobook], summary: '7 habits of highly effective people' is an audiobook }
# AUDIOBOOK LISTENING HISTORY
- (2024-11-20 10:40:00 - present) [AUDIOBOOK_LISTEN] { audiobook_title: 7 habits of highly effective people }
```
Learn more about context templates in the [Context Templates](/context-templates) documentation.
#### Using Graph Search
For more granular control or dynamic queries, you can use graph search to filter by entity or edge types. Graph search is ideal when you need to specify custom search queries, want direct access to individual nodes or edges, or are building dynamic filtering based on runtime conditions.
Search for entities (nodes) by filtering on custom entity types using the `node_labels` parameter:
```python
search_results_restaurants = client.graph.search(
user_id=user_id,
query="Take me to a restaurant",
scope="nodes",
search_filters={
"node_labels": ["Restaurant"]
},
limit=1,
)
node = search_results_restaurants.nodes[0]
print(f"Node name: {node.name}")
print(f"Node labels: {node.labels}")
print(f"Cuisine type: {node.attributes.get('cuisine_type')}")
print(f"Dietary accommodation: {node.attributes.get('dietary_accommodation')}")
```
```typescript
let searchResults = await client.graph.search({
userId: userId,
query: "Take me to a restaurant",
scope: "nodes",
searchFilters: { nodeLabels: ["Restaurant"] },
limit: 1,
});
if (searchResults.nodes && searchResults.nodes.length > 0) {
const node = searchResults.nodes[0];
console.log(`Node name: ${node.name}`);
console.log(`Node labels: ${node.labels}`);
console.log(`Cuisine type: ${node.attributes?.cuisine_type}`);
console.log(`Dietary accommodation: ${node.attributes?.dietary_accommodation}`);
}
```
```go
searchFiltersRestaurants := zep.SearchFilters{NodeLabels: []string{"Restaurant"}}
searchResultsRestaurants, err := client.Graph.Search(
ctx,
&zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "Take me to a restaurant",
Scope: zep.GraphSearchScopeNodes.Ptr(),
SearchFilters: &searchFiltersRestaurants,
Limit: zep.Int(1),
},
)
if err != nil {
fmt.Printf("Error searching graph: %v\n", err)
return
}
if len(searchResultsRestaurants.Nodes) > 0 {
node := searchResultsRestaurants.Nodes[0]
fmt.Printf("Node name: %s\n", node.Name)
fmt.Printf("Node labels: %v\n", node.Labels)
fmt.Printf("Cuisine type: %v\n", node.Attributes["cuisine_type"])
fmt.Printf("Dietary accommodation: %v\n", node.Attributes["dietary_accommodation"])
}
```
```text
Node name: Green Leaf Cafe
Node labels: Entity,Restaurant
Cuisine type: undefined
Dietary accommodation: vegetarian
```
Search for edges by filtering on custom edge types using the `edge_types` parameter:
```python
search_results_visits = client.graph.search(
user_id=user_id,
query="Take me to a restaurant",
scope="edges",
search_filters={
"edge_types": ["RESTAURANT_VISIT"]
},
limit=1,
)
edge = search_results_visits.edges[0]
print(f"Edge fact: {edge.fact}")
print(f"Edge type: {edge.name}")
print(f"Restaurant name: {edge.attributes.get('restaurant_name')}")
```
```typescript
searchResults = await client.graph.search({
userId: userId,
query: "Take me to a restaurant",
scope: "edges",
searchFilters: { edgeTypes: ["RESTAURANT_VISIT"] },
limit: 1,
});
if (searchResults.edges && searchResults.edges.length > 0) {
const edge = searchResults.edges[0];
console.log(`Edge fact: ${edge.fact}`);
console.log(`Edge type: ${edge.name}`);
console.log(`Restaurant name: ${edge.attributes?.restaurant_name}`);
}
```
```go
searchFiltersVisits := zep.SearchFilters{EdgeTypes: []string{"RESTAURANT_VISIT"}}
searchResultsVisits, err := client.Graph.Search(
ctx,
&zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "Take me to a restaurant",
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &searchFiltersVisits,
Limit: zep.Int(1),
},
)
if err != nil {
fmt.Printf("Error searching graph: %v\n", err)
return
}
if len(searchResultsVisits.Edges) > 0 {
edge := searchResultsVisits.Edges[0]
var visit RestaurantVisit
err := zep.UnmarshalEdgeAttributes(edge.Attributes, &visit)
if err != nil {
fmt.Printf("Error converting edge to RestaurantVisit struct: %v\n", err)
} else {
fmt.Printf("Edge fact: %s\n", edge.Fact)
fmt.Printf("Edge type: %s\n", edge.Name)
fmt.Printf("Restaurant name: %s\n", visit.RestaurantName)
}
}
```
```text
Edge fact: User John Doe is going to Green Leaf Cafe
Edge type: RESTAURANT_VISIT
Restaurant name: Green Leaf Cafe
```
You can provide multiple types in search filters, and the types will be ORed together. For example, searching with `edge_types: ["DIETARY_PREFERENCE", "RESTAURANT_VISIT"]` will return edges matching either type.
### Important Notes/Tips
Some notes regarding custom entity and edge types:
* The `set_ontology` method overwrites any previously defined custom entity and edge types, so the set of custom entity and edge types is always the list of types provided in the last `set_ontology` method call
* The overall set of entity and edge types for a project includes both the custom entity and edge types you set and the default entity and edge types
* You can overwrite the default entity and edge types by providing custom types with the same names
* Changing the custom entity or edge types will not update previously created nodes or edges. The classification and attributes of existing nodes and edges will stay the same. The only thing that can change existing classifications or attributes is adding data that provides new information.
* When creating custom entity or edge types, avoid using the following attribute names (including in Go struct tags), as they conflict with default attributes: `uuid`, `name`, `graph_id`, `name_embedding`, `summary`, and `created_at`
* **Any custom entity or edge is required to have at least one custom property defined**
* **Tip**: Design custom entity types to represent entities/nouns, and design custom edge types to represent relationships/verbs. Otherwise, your type might be represented in the graph as an edge more often than as a node or vice versa.
* **Tip**: If you have overlapping entity or edge types (e.g. 'Hobby' and 'Hiking'), you can prioritize one type over another by mentioning which to prioritize in the entity or edge type descriptions
# User Summary Instructions
> Customize how Zep generates entity summaries for users in their knowledge graph
Get started with the example in the video using:
```bash
git clone https://github.com/getzep/zep.git
cd zep/examples/python/user-summary-instructions-example
```
## Overview
User summary instructions customize how Zep generates the entity summary for each user in their knowledge graph. You can create up to 5 custom instructions per user, set of users, or project-wide. Each instruction consists of a `name` (unique identifier) and `text` (the instruction content, maximum 100 characters).
**User summary and the user node**
Each user has a single unique user node in their graph representing the user themselves. The user summary generated from these instructions lives on this user node. You can retrieve the user node and its summary using the `get_node` method shown in the SDK reference.
## Default instructions
Zep applies the following default instructions to generate user summaries when no custom instructions are specified:
1. What are the user's key personal and lifestyle details?
2. What are the user's important relationships or social connections?
3. What does the user do for work, study, or main pursuits?
4. What are the user's preferences, values, and recurring goals?
5. What procedural or interaction instructions has the user given for how the AI should assist them?
These default instructions ensure comprehensive user summaries that capture essential information across personal, professional, and interaction contexts.
## Custom instructions
Instructions are managed through dedicated methods that allow you to add, list, and delete them. You can apply instructions to specific users by providing user IDs, or set them as project-wide defaults by omitting user IDs.
**Best practices for writing instructions**: Instructions should be focused and specific, designed to elicit responses that can be answered in a sentence or two. Phrasing instructions as questions is often an effective way to get accurate and succinct responses.
User summary instructions do not apply when using `graph.add_batch()` or `thread.add_messages_batch()`.
```python Python
from zep_cloud.client import Zep
from zep_cloud.types import UserInstruction
client = Zep(api_key=API_KEY)
# Add instructions for specific users
client.user.add_user_summary_instructions(
instructions=[
UserInstruction(
name="professional_background",
text="What are the user's key professional skills and career achievements?",
)
],
user_ids=[user_id],
)
# Add project-wide default instructions (applied to all users without custom instructions)
client.user.add_user_summary_instructions(
instructions=[
UserInstruction(
name="communication_style",
text="How does the user prefer to receive information and assistance?",
)
],
)
# List instructions for a user
instructions = client.user.list_user_summary_instructions(user_id=user_id)
# Delete specific instructions for a user
client.user.delete_user_summary_instructions(
instruction_names=["professional_background"],
user_ids=[user_id],
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Add instructions for specific users
await client.user.addUserSummaryInstructions({
instructions: [
{
name: "professional_background",
text: "What are the user's key professional skills and career achievements?",
}
],
userIds: [userId],
});
// Add project-wide default instructions (applied to all users without custom instructions)
await client.user.addUserSummaryInstructions({
instructions: [
{
name: "communication_style",
text: "How does the user prefer to receive information and assistance?",
}
],
});
// List instructions for a user
const instructions = await client.user.listUserSummaryInstructions({
userId: userId,
});
// Delete specific instructions for a user
await client.user.deleteUserSummaryInstructions({
instructionNames: ["professional_background"],
userIds: [userId],
});
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(option.WithAPIKey(apiKey))
// Add instructions for specific users
_, err := client.User.AddUserSummaryInstructions(context.TODO(), &zep.ApidataAddUserInstructionsRequest{
Instructions: []*zep.UserInstruction{
{
Name: "professional_background",
Text: "What are the user's key professional skills and career achievements?",
},
},
UserIDs: []string{userID},
})
if err != nil {
log.Fatalf("Failed to add user summary instructions: %v", err)
}
// Add project-wide default instructions (applied to all users without custom instructions)
_, err = client.User.AddUserSummaryInstructions(context.TODO(), &zep.ApidataAddUserInstructionsRequest{
Instructions: []*zep.UserInstruction{
{
Name: "communication_style",
Text: "How does the user prefer to receive information and assistance?",
},
},
})
if err != nil {
log.Fatalf("Failed to add default instructions: %v", err)
}
// List instructions for a user
instructions, err := client.User.ListUserSummaryInstructions(context.TODO(), &zep.ApidataListUserInstructionsRequest{
UserID: userID,
})
if err != nil {
log.Fatalf("Failed to list instructions: %v", err)
}
// Delete specific instructions for a user
_, err = client.User.DeleteUserSummaryInstructions(context.TODO(), &zep.ApidataDeleteUserInstructionsRequest{
InstructionNames: []string{"professional_background"},
UserIDs: []string{userID},
})
if err != nil {
log.Fatalf("Failed to delete instructions: %v", err)
}
```
## Utilizing user summary
User summaries are automatically included in [Zep's Context Block](/retrieving-context#zeps-context-block). You can toggle whether the user summary is included in the Context Block on the Projects page of the web app. Accounts created before November 10, 2025 will need to enable this setting manually, while new accounts have it enabled by default.
Alternatively, you can build a custom context block by retrieving the user node and including its summary. See [this example](/cookbook/advanced-context-block-construction#example-4-using-user-summary-in-context-block) for a complete implementation.
# Create Graph
> Create standalone graphs for advanced use cases
## Overview
While most use cases benefit from user-centric graphs that automatically integrate with users and threads, Zep also supports creating standalone graphs. Standalone graphs are useful for specific scenarios where you need independent knowledge graphs that aren't tied to individual users.
## When to Use Standalone Graphs
Consider using standalone graphs when you need to:
* Create shared knowledge bases across multiple users
* Build domain-specific knowledge graphs independent of user context
* Maintain separate graphs for testing or experimentation
* Implement custom graph architectures for specialized use cases
For most applications, we recommend using user graphs (created automatically when you add a user) as they provide seamless integration with Zep's context retrieval systems.
## Creating a Standalone Graph
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
# Create a standalone graph with a custom ID
result = client.graph.create(
graph_id="my_custom_graph"
)
print(f"Created graph with ID: {result.graph_id}")
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Create a standalone graph with a custom ID
const result = await client.graph.create({
graphId: "my_custom_graph"
});
console.log(`Created graph with ID: ${result.graphId}`);
```
```go Go
import (
"context"
"fmt"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
// Create a standalone graph with a custom ID
graphID := "my_custom_graph"
result, err := client.Graph.Create(context.TODO(), &zep.CreateGraphRequest{
GraphID: &graphID,
})
if err != nil {
log.Fatalf("Failed to create graph: %v", err)
}
fmt.Printf("Created graph with ID: %s\n", *result.GraphID)
```
## Working with Standalone Graphs
Once created, you can work with standalone graphs using the same methods as user graphs:
* [Add data to the graph](/adding-business-data) by specifying `graph_id` instead of `user_id`
* [Search the graph](/working-with-graphs/searching-the-graph) for relevant information
* [Read data from the graph](/working-with-graphs/reading-data-from-the-graph) to inspect nodes and edges
* [Delete data from the graph](/working-with-graphs/deleting-data-from-the-graph) when needed
* [Clone the graph](/working-with-graphs/cloning-graphs) to create copies
## Customizing Graph Structure
Standalone graphs can be customized with entity and edge types just like user graphs. See [Customizing Graph Structure](/customizing-graph-structure) for details on how to define custom ontologies for your graph.
## Next Steps
* Learn how to [add business data](/adding-business-data) to your graph
* Explore [graph search capabilities](/working-with-graphs/searching-the-graph)
* Understand how to [customize graph structure](/customizing-graph-structure)
# Searching the Graph
Graph search results should be used in conjunction with [Advanced Context Block Construction](/cookbook/advanced-context-block-construction) to create rich, contextual prompts for AI models. Custom context blocks allow you to format and structure the retrieved graph information, combining search results with conversation history and other relevant data to provide comprehensive context for your AI applications.
Learn how to integrate graph search results into your context generation workflow for more effective AI interactions.
## Introduction
Zep's graph search provides powerful hybrid search capabilities that combine semantic similarity with BM25 full-text search to find relevant information across your knowledge graph. This approach leverages the best of both worlds: semantic understanding for conceptual matches and full-text search for exact term matching. Additionally, you can optionally enable breadth-first search to bias results toward information connected to specific starting points in your graph.
### How It Works
* **Semantic similarity**: Converts queries into embeddings to find conceptually similar content
* **BM25 full-text search**: Performs traditional keyword-based search for exact matches
* **Breadth-first search** (optional): Biases results toward information connected to specified starting nodes, useful for contextual relevance
* **Hybrid results**: Combines and reranks results using sophisticated algorithms
### Graph Concepts
* **Nodes**: Connection points representing entities (people, places, concepts) discussed in conversations or added via the Graph API
* **Edges**: Relationships between nodes containing specific facts and interactions
The example below demonstrates a simple search:
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
search_results = client.graph.search(
user_id=user_id,
query=query,
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const searchResults = await client.graph.search({
userId: userId,
query: query,
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: query,
})
```
Keep queries short: they are truncated at 400 characters. Long queries may increase latency without improving search quality.
Break down complex searches into smaller, targeted queries. Use precise, contextual queries rather than generic ones
## Configurable Parameters
Zep provides extensive configuration options to fine-tune search behavior and optimize results for your specific use case:
| Parameter | Type | Description | Default | Required |
| ---------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- |
| `graph_id` | string | Search within a graph | - | Yes\* |
| `user_id` | string | Search within a user graph | - | Yes\* |
| `query` | string | Search text (max 400 characters) | - | Yes |
| `scope` | string | Search target: `"edges"`, `"nodes"`, or `"episodes"` | `"edges"` | No |
| `reranker` | string | Reranking method: `"rrf"`, `"mmr"`, `"node_distance"`, `"episode_mentions"`, or `"cross_encoder"` | `"rrf"` | No |
| `limit` | integer | Maximum number of results to return | `10` | No |
| `mmr_lambda` | float | MMR diversity vs relevance balance (0.0-1.0) | - | No† |
| `center_node_uuid` | string | Center node for distance-based reranking | - | No‡ |
| `search_filters` | object | Filter by entity types (`node_labels`), edge types (`edge_types`), exclude entity types (`exclude_node_labels`), exclude edge types (`exclude_edge_types`), custom properties (`property_filters`), or timestamps (`created_at`, `expired_at`, `invalid_at`, `valid_at`§) | - | No |
| `bfs_origin_node_uuids` | array | Node UUIDs to seed breadth-first searches from | - | No |
| ~~`min_fact_rating`~~ (deprecated) | double | ~~The minimum rating by which to filter relevant facts (range: 0.0-1.0). Can only be used when using `scope="edges"`~~ | - | No |
\*Either `user_id` OR `graph_id` is required
†Required when using `mmr` reranker\
‡Required when using `node_distance` reranker\
§Timestamp filtering only applies to edge scope searches
## Search Scopes
Zep supports three different search scopes, each optimized for different types of information retrieval:
### Edges (Default)
Edges represent individual relationships and facts between entities in your graph. They contain specific interactions, conversations, and detailed information. Edge search is ideal for:
* Finding specific details or conversations
* Retrieving precise facts about relationships
* Getting granular information about interactions
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
# Edge search (default scope)
search_results = client.graph.search(
user_id=user_id,
query="What did John say about the project?",
scope="edges", # Optional - this is the default
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Edge search (default scope)
const searchResults = await client.graph.search({
userId: userId,
query: "What did John say about the project?",
scope: "edges", // Optional - this is the default
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
// Edge search (default scope)
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "What did John say about the project?",
Scope: zep.GraphSearchScopeEdges.Ptr(), // Optional - this is the default
})
```
### Nodes
Nodes are connection points in the graph that represent entities. Each node maintains a summary of facts from its connections (edges), providing a comprehensive overview. Node search is useful for:
* Understanding broader context around entities
* Getting entity summaries and overviews
* Finding all information related to a specific person, place, or concept
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
search_results = client.graph.search(
graph_id=graph_id,
query="John Smith",
scope="nodes",
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const searchResults = await client.graph.search({
graphId: graphId,
query: "John Smith",
scope: "nodes",
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
GraphID: zep.String(graphID),
Query: "John Smith",
Scope: zep.GraphSearchScopeNodes.Ptr(),
})
```
### Episodes
Episodes represent individual messages or chunks of data sent to Zep. Episode search allows you to find relevant episodes based on their content, making it ideal for:
* Finding specific messages or data chunks related to your query
* Discovering when certain topics were mentioned
* Retrieving relevant individual interactions
* Understanding the context of specific messages
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
search_results = client.graph.search(
user_id=user_id,
query="project discussion",
scope="episodes",
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const searchResults = await client.graph.search({
userId: userId,
query: "project discussion",
scope: "episodes",
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "project discussion",
Scope: zep.GraphSearchScopeEpisodes.Ptr(),
})
```
## Rerankers
Zep provides multiple reranking algorithms to optimize search results for different use cases. Each reranker applies a different strategy to prioritize and order results:
### RRF (Reciprocal Rank Fusion)
Reciprocal Rank Fusion is the default reranker that intelligently combines results from both semantic similarity and BM25 full-text search. It merges the two result sets by considering the rank position of each result in both searches, creating a unified ranking that leverages the strengths of both approaches.
**When to use**: RRF is ideal for most general-purpose search scenarios where you want balanced results combining conceptual understanding with exact keyword matching.
**Score interpretation**: RRF scores combine semantic similarity and keyword matching by summing reciprocal ranks (1/rank) from both search methods, resulting in higher scores for results that perform well in both approaches. Scores don't follow a fixed 0-1 scale but rather reflect the combined strength across both search types, with higher values indicating better overall relevance.
### MMR (Maximal Marginal Relevance)
Maximal Marginal Relevance addresses a common issue in similarity searches: highly similar top results that don't add diverse information to your context. MMR reranks results to balance relevance with diversity, promoting varied but still relevant results over redundant similar ones.
**When to use**: Use MMR when you need diverse information for comprehensive context, such as generating summaries, answering complex questions, or avoiding repetitive results.
**Required parameter**: `mmr_lambda` (0.0-1.0) - Controls the balance between relevance (1.0) and diversity (0.0). A value of 0.5 provides balanced results.
**Score interpretation**: MMR scores balance relevance with diversity based on your mmr\_lambda setting, meaning a moderately relevant but diverse result may score higher than a highly relevant but similar result. Interpret scores relative to your lambda value: with lambda=0.5, moderate scores may indicate valuable diversity rather than poor relevance.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
search_results = client.graph.search(
user_id=user_id,
query="project status",
reranker="mmr",
mmr_lambda=0.5, # Balance diversity vs relevance
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const searchResults = await client.graph.search({
userId: userId,
query: "project status",
reranker: "mmr",
mmrLambda: 0.5, // Balance diversity vs relevance
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "project status",
Reranker: zep.GraphSearchQueryRerankerMmr.Ptr(),
MmrLambda: zep.Float64(0.5), // Balance diversity vs relevance
})
```
### Cross Encoder
`cross_encoder` uses a specialized neural model that jointly analyzes the query and each search result together, rather than analyzing them separately. This provides more accurate relevance scoring by understanding the relationship between the query and potential results in a single model pass.
**When to use**: Use cross encoder when you need the highest accuracy in relevance scoring and are willing to trade some performance for better results. Ideal for critical searches where precision is paramount.
**Trade-offs**: Higher accuracy but slower performance compared to other rerankers.
**Score interpretation**: Cross encoder scores follow a sigmoid curve (`0-1` range) where highly relevant results cluster near the top with scores that decay rapidly as relevance decreases. You'll typically see a sharp drop-off between truly relevant results (higher scores) and less relevant ones, making it easy to set meaningful relevance thresholds.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
search_results = client.graph.search(
user_id=user_id,
query="critical project decision",
reranker="cross_encoder",
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const searchResults = await client.graph.search({
userId: userId,
query: "critical project decision",
reranker: "cross_encoder",
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "critical project decision",
Reranker: zep.GraphSearchQueryRerankerCrossEncoder.Ptr(),
})
```
### Episode Mentions
`episode_mentions` reranks search results based on how frequently nodes or edges have been mentioned across all episodes, including both conversational episodes (chat history) and episodes created via `graph.add`. Results that appear more often across these episodes are prioritized, reflecting their importance and relevance.
**When to use**: Use episode mentions when you want to surface information that has been frequently referenced across conversations or data uploads. Useful for understanding recurring themes, important topics, or frequently mentioned entities across all your graph data.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
search_results = client.graph.search(
user_id=user_id,
query="team feedback",
reranker="episode_mentions",
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const searchResults = await client.graph.search({
userId: userId,
query: "team feedback",
reranker: "episode_mentions",
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "team feedback",
Reranker: zep.GraphSearchQueryRerankerEpisodeMentions.Ptr(),
})
```
### Node Distance
`node_distance` reranks search results based on graph proximity, prioritizing results that are closer (fewer hops) to a specified center node. This spatial approach to relevance is useful for finding information contextually related to a specific entity or concept.
**When to use**: Use node distance when you want to find information specifically related to a particular entity, person, or concept in your graph. Ideal for exploring the immediate context around a known entity.
**Required parameter**: `center_node_uuid` - The UUID of the node to use as the center point for distance calculations.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
search_results = client.graph.search(
user_id=user_id,
query="recent activities",
reranker="node_distance",
center_node_uuid=center_node_uuid,
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const searchResults = await client.graph.search({
userId: userId,
query: "recent activities",
reranker: "node_distance",
centerNodeUuid: centerNodeUuid,
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "recent activities",
Reranker: zep.GraphSearchQueryRerankerNodeDistance.Ptr(),
CenterNodeUuid: zep.String(centerNodeUuid),
})
```
### Reranker Score
Graph search results include a reranker score that provides a measure of relevance for each returned result. This score is available when using any reranker and is returned on any node, edge, or episode from `graph.search`. The reranker score can be used to manually filter results to only include those above a certain relevance threshold, allowing for more precise control over search result quality.
The interpretation of the score depends on which reranker is used. For example, when using the `cross_encoder` reranker, the score follows a sigmoid curve with the score decaying rapidly as relevance decreases. For more information about the score field in the response, see the [SDK reference](https://help.getzep.com/sdk-reference/graph/search#response.body.edges.score).
#### Relevance Score
When using the `cross_encoder` reranker, search results include an additional `relevance` field alongside the `score` field. The `relevance` field is a rank-aligned score in the range \[0, 1] derived from the existing sigmoid-distributed `score` to improve interpretability and thresholding.
**Key characteristics:**
* Range: \[0, 1]
* Only populated when using the `cross_encoder` reranker
* Preserves the ranking order produced by Zep's reranker
* Not a probability; it is a monotonic transform of `score` to reduce saturation near 1
* Use `relevance` for sorting, filtering, and analytics
The `relevance` field provides a more intuitive metric for evaluating search result quality compared to the raw `score`, making it easier to set meaningful thresholds and analyze results.
## Search Filters
Zep allows you to filter search results by specific entity types or edge types, enabling more targeted searches within your graph.
### Entity Type Filtering
Filter search results to only include nodes of specific entity types. This is useful when you want to focus on particular kinds of entities (e.g., only people, only companies, only locations).
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
search_results = client.graph.search(
user_id=user_id,
query="software engineers",
scope="nodes",
search_filters={
"node_labels": ["Person", "Company"]
}
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const searchResults = await client.graph.search({
userId: userId,
query: "software engineers",
scope: "nodes",
searchFilters: {
nodeLabels: ["Person", "Company"]
}
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
searchFilters := zep.SearchFilters{NodeLabels: []string{"Person", "Company"}}
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "software engineers",
Scope: zep.GraphSearchScopeNodes.Ptr(),
SearchFilters: &searchFilters,
})
```
### Edge Type Filtering
Filter search results to only include edges of specific relationship types. This helps you find particular kinds of relationships or interactions between entities.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
search_results = client.graph.search(
user_id=user_id,
query="project collaboration",
scope="edges",
search_filters={
"edge_types": ["WORKS_WITH", "COLLABORATES_ON"]
}
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const searchResults = await client.graph.search({
userId: userId,
query: "project collaboration",
scope: "edges",
searchFilters: {
edgeTypes: ["WORKS_WITH", "COLLABORATES_ON"]
}
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
searchFilters := zep.SearchFilters{EdgeTypes: []string{"WORKS_WITH", "COLLABORATES_ON"}}
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "project collaboration",
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &searchFilters,
})
```
### Exclusion Filters
Exclusion filters allow you to exclude specific entity types or edge types from your search results. This is useful when you want to filter out certain types of information while keeping all others.
#### Excluding Node Labels
Exclude specific entity types from node or edge search results. When searching edges, nodes connected to the edges are also checked against exclusion filters.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
# Exclude certain entity types from results
search_results = client.graph.search(
user_id=user_id,
query="project information",
scope="nodes",
search_filters={
"exclude_node_labels": ["Assistant", "Document"]
}
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Exclude certain entity types from results
const searchResults = await client.graph.search({
userId: userId,
query: "project information",
scope: "nodes",
searchFilters: {
excludeNodeLabels: ["Assistant", "Document"]
}
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
// Exclude certain entity types from results
searchFilters := zep.SearchFilters{ExcludeNodeLabels: []string{"Assistant", "Document"}}
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "project information",
Scope: zep.GraphSearchScopeNodes.Ptr(),
SearchFilters: &searchFilters,
})
```
#### Excluding Edge Types
Exclude specific edge types from search results. This helps you filter out certain kinds of relationships while keeping all others.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
# Exclude certain edge types from results
search_results = client.graph.search(
user_id=user_id,
query="user activities",
scope="edges",
search_filters={
"exclude_edge_types": ["LOCATED_AT", "OCCURRED_AT"]
}
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Exclude certain edge types from results
const searchResults = await client.graph.search({
userId: userId,
query: "user activities",
scope: "edges",
searchFilters: {
excludeEdgeTypes: ["LOCATED_AT", "OCCURRED_AT"]
}
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
// Exclude certain edge types from results
searchFilters := zep.SearchFilters{ExcludeEdgeTypes: []string{"LOCATED_AT", "OCCURRED_AT"}}
searchResults, err := client.Graph.Search(context.TODO(), &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "user activities",
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &searchFilters,
})
```
Exclusion filters can be combined with inclusion filters (`node_labels` and `edge_types`). When both are specified, results must match the inclusion criteria AND not match any exclusion criteria.
### Property Filtering
Filter search results based on custom attributes stored on nodes and edges. Property filters apply to both node attributes and edge attributes, enabling flexible querying across your graph.
**Supported Comparison Operators:**
| Operator | Description | Requires Value |
| ------------- | ------------------------------- | -------------- |
| `=` | Equal to | Yes |
| `<>` | Not equal to | Yes |
| `>` | Greater than | Yes |
| `<` | Less than | Yes |
| `>=` | Greater than or equal | Yes |
| `<=` | Less than or equal | Yes |
| `IS NULL` | Property is null or not set | No |
| `IS NOT NULL` | Property exists and is not null | No |
```python Python
from zep_cloud.client import Zep
from zep_cloud.types import SearchFilters, PropertyFilter
client = Zep(
api_key=API_KEY,
)
# Filter by property values
search_results = client.graph.search(
user_id=user_id,
query="team members",
scope="edges",
search_filters=SearchFilters(
property_filters=[
PropertyFilter(
comparison_operator="=",
property_name="department",
property_value="Engineering"
),
PropertyFilter(
comparison_operator=">",
property_name="level",
property_value=3
)
]
)
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Filter by property values
const searchResults = await client.graph.search({
userId: userId,
query: "team members",
scope: "edges",
searchFilters: {
propertyFilters: [
{
comparisonOperator: "=",
propertyName: "department",
propertyValue: "Engineering"
},
{
comparisonOperator: ">",
propertyName: "level",
propertyValue: 3
}
]
}
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
searchResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "team members",
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &zep.SearchFilters{
PropertyFilters: []*zep.PropertyFilter{
{
ComparisonOperator: zep.ComparisonOperatorEquals,
PropertyName: "department",
PropertyValue: "Engineering",
},
{
ComparisonOperator: zep.ComparisonOperatorGreaterThan,
PropertyName: "level",
PropertyValue: 3,
},
},
},
})
```
#### Checking for Null Values
The `IS NULL` and `IS NOT NULL` operators allow you to filter based on whether a property exists. When using these operators, omit the `property_value` parameter.
```python Python
from zep_cloud.client import Zep
from zep_cloud.types import SearchFilters, PropertyFilter
client = Zep(
api_key=API_KEY,
)
# Find edges where a property is NOT set
search_results = client.graph.search(
user_id=user_id,
query="incomplete records",
scope="edges",
search_filters=SearchFilters(
property_filters=[
PropertyFilter(
comparison_operator="IS NULL",
property_name="end_date"
# property_value is omitted for IS NULL
)
]
)
)
# Find edges where a property IS set
search_results = client.graph.search(
user_id=user_id,
query="active employees",
scope="edges",
search_filters=SearchFilters(
property_filters=[
PropertyFilter(
comparison_operator="IS NOT NULL",
property_name="manager_id"
# property_value is omitted for IS NOT NULL
)
]
)
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Find edges where a property is NOT set
const incompleteResults = await client.graph.search({
userId: userId,
query: "incomplete records",
scope: "edges",
searchFilters: {
propertyFilters: [
{
comparisonOperator: "IS NULL",
propertyName: "endDate"
// propertyValue is omitted for IS NULL
}
]
}
});
// Find edges where a property IS set
const activeResults = await client.graph.search({
userId: userId,
query: "active employees",
scope: "edges",
searchFilters: {
propertyFilters: [
{
comparisonOperator: "IS NOT NULL",
propertyName: "managerId"
// propertyValue is omitted for IS NOT NULL
}
]
}
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
// Find edges where a property is NOT set
incompleteResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "incomplete records",
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &zep.SearchFilters{
PropertyFilters: []*zep.PropertyFilter{
{
ComparisonOperator: zep.ComparisonOperatorIsNull,
PropertyName: "end_date",
// PropertyValue is omitted for IS NULL
},
},
},
})
// Find edges where a property IS set
activeResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "active employees",
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &zep.SearchFilters{
PropertyFilters: []*zep.PropertyFilter{
{
ComparisonOperator: zep.ComparisonOperatorIsNotNull,
PropertyName: "manager_id",
// PropertyValue is omitted for IS NOT NULL
},
},
},
})
```
For standard comparison operators (`=`, `<>`, `>`, `<`, `>=`, `<=`), the `property_value` parameter is required. For `IS NULL` and `IS NOT NULL` operators, the `property_value` should be omitted since the value is implicitly known (null).
### Datetime Filtering
Filter search results based on timestamps, enabling temporal-based queries to find information from specific time periods. This feature allows you to search for content based on four different timestamp types, each serving a distinct purpose in tracking the lifecycle of facts in your knowledge graph.
Datetime filtering only applies to edge scope searches. When using `scope="nodes"` or `scope="episodes"`, datetime filter values are ignored and have no effect on search results.
**Available Timestamp Types:**
| Timestamp | Description | Example Use Case |
| ------------ | ---------------------------------------------------- | ------------------------------------------------------ |
| `created_at` | The time when Zep learned the fact was true | Finding when information was first added to the system |
| `valid_at` | The real world time that the fact started being true | Identifying when a relationship or state began |
| `invalid_at` | The real world time that the fact stopped being true | Finding when a relationship or state ended |
| `expired_at` | The time that Zep learned that the fact was false | Tracking when information was marked as outdated |
For example, for the fact "Alice is married to Bob":
* `valid_at`: The time they got married
* `invalid_at`: The time they got divorced
* `created_at`: The time Zep learned they were married
* `expired_at`: The time Zep learned they were divorced
**Logic Behavior:**
* **Outer array/list**: Uses OR logic - any condition graph can match
* **Inner array/list**: Uses AND logic - all conditions within a graph must match
In the example below, results are returned if they match:
* (created >= 2025-07-01 AND created \< 2025-08-01) OR (created \< 2025-05-01)
**Date Format**: All dates must be in ISO 8601 format with timezone (e.g., "2025-07-01T20:57:56Z")
**Comparison Operators:**
| Operator | Description | Requires Date |
| ------------- | -------------------- | ------------- |
| `=` | Equal to | Yes |
| `<>` | Not equal to | Yes |
| `>` | After | Yes |
| `<` | Before | Yes |
| `>=` | On or after | Yes |
| `<=` | On or before | Yes |
| `IS NULL` | Timestamp is not set | No |
| `IS NOT NULL` | Timestamp is set | No |
```python Python
from zep_cloud.client import Zep
from zep_cloud.types import SearchFilters, DateFilter
client = Zep(
api_key=API_KEY,
)
# Search for edges created in July 2025 OR before May 2025
search_results = client.graph.search(
user_id=user_id,
query="project discussions",
scope="edges",
search_filters=SearchFilters(
created_at=[
# First condition graph (AND logic within)
[DateFilter(comparison_operator=">=", date="2025-07-01T20:57:56Z"),
DateFilter(comparison_operator="<", date="2025-08-01T20:57:56Z")],
# Second condition graph (OR logic with first graph)
[DateFilter(comparison_operator="<", date="2025-05-01T20:57:56Z")],
]
)
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Search for edges created in July 2025 OR before May 2025
const searchResults = await client.graph.search({
userId: userId,
query: "project discussions",
scope: "edges",
searchFilters: {
createdAt: [
// First condition graph (AND logic within)
[
{comparisonOperator: ">=", date: "2025-07-01T20:57:56Z"},
{comparisonOperator: "<", date: "2025-08-01T20:57:56Z"}
],
// Second condition graph (OR logic with first graph)
[
{comparisonOperator: "<", date: "2025-05-01T20:57:56Z"}
]
]
}
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
// Search for edges created in July 2025 OR before May 2025
searchResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "project discussions",
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &zep.SearchFilters{
CreatedAt: [][]*zep.DateFilter{
// First condition graph (AND logic within)
{
{
ComparisonOperator: zep.ComparisonOperatorGreaterThanEqual,
Date: "2025-07-01T20:57:56Z",
},
{
ComparisonOperator: zep.ComparisonOperatorLessThan,
Date: "2025-08-01T20:57:56Z",
},
},
// Second condition graph (OR logic with first graph)
{
{
ComparisonOperator: zep.ComparisonOperatorLessThan,
Date: "2025-05-01T20:57:56Z",
},
},
},
},
})
```
#### Checking for Null Timestamps
The `IS NULL` and `IS NOT NULL` operators allow you to filter edges based on whether a timestamp field is set. When using these operators, omit the `date` parameter.
```python Python
from zep_cloud.client import Zep
from zep_cloud.types import SearchFilters, DateFilter
client = Zep(
api_key=API_KEY,
)
# Find edges that have never been invalidated (invalid_at is not set)
search_results = client.graph.search(
user_id=user_id,
query="current facts",
scope="edges",
search_filters=SearchFilters(
invalid_at=[
[DateFilter(comparison_operator="IS NULL")]
# date is omitted for IS NULL
]
)
)
# Find edges that have an expiration date set
search_results = client.graph.search(
user_id=user_id,
query="temporary facts",
scope="edges",
search_filters=SearchFilters(
expired_at=[
[DateFilter(comparison_operator="IS NOT NULL")]
# date is omitted for IS NOT NULL
]
)
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Find edges that have never been invalidated (invalid_at is not set)
const currentFacts = await client.graph.search({
userId: userId,
query: "current facts",
scope: "edges",
searchFilters: {
invalidAt: [
[{ comparisonOperator: "IS NULL" }]
// date is omitted for IS NULL
]
}
});
// Find edges that have an expiration date set
const temporaryFacts = await client.graph.search({
userId: userId,
query: "temporary facts",
scope: "edges",
searchFilters: {
expiredAt: [
[{ comparisonOperator: "IS NOT NULL" }]
// date is omitted for IS NOT NULL
]
}
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
// Find edges that have never been invalidated (invalid_at is not set)
currentFacts, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "current facts",
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &zep.SearchFilters{
InvalidAt: [][]*zep.DateFilter{
{
{ComparisonOperator: zep.ComparisonOperatorIsNull},
// Date is omitted for IS NULL
},
},
},
})
// Find edges that have an expiration date set
temporaryFacts, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "temporary facts",
Scope: zep.GraphSearchScopeEdges.Ptr(),
SearchFilters: &zep.SearchFilters{
ExpiredAt: [][]*zep.DateFilter{
{
{ComparisonOperator: zep.ComparisonOperatorIsNotNull},
// Date is omitted for IS NOT NULL
},
},
},
})
```
For standard comparison operators (`=`, `<>`, `>`, `<`, `>=`, `<=`), the `date` parameter is required. For `IS NULL` and `IS NOT NULL` operators, the `date` should be omitted since the value is implicitly known (null).
**Common Use Cases:**
* **Date Range Filtering**: Find facts from specific time periods using any timestamp type
* **Recent Activity**: Search for edges created or expired after a certain date using `>=` operator
* **Historical Data**: Find older information using `<` or `<=` operators on any timestamp
* **Validity Period Analysis**: Use `valid_at` and `invalid_at` together to find facts that were true during specific periods
* **Audit Trail**: Use `created_at` and `expired_at` to track when your system learned about changes
* **Complex Temporal Queries**: Combine multiple date conditions across different timestamp types
* **Find current/valid facts**: Filter for edges where `invalid_at` IS NULL to find facts that are still valid
* **Find temporary facts**: Filter for edges where `expired_at` IS NOT NULL to find facts with expiration dates
* **Find facts without validity periods**: Filter for edges where `valid_at` IS NULL to find facts without explicit start dates
## ~~Filtering by Fact Rating~~ (deprecated)
~~The `min_fact_rating` parameter allows you to filter search results based on the relevancy rating of facts stored in your graph edges. When specified, all facts returned will have at least the minimum rating value you set.~~
~~This parameter accepts values between 0.0 and 1.0, where higher values indicate more relevant facts. By setting a minimum threshold, you can ensure that only highly relevant facts are included in your search results.~~
~~**Important**: The `min_fact_rating` parameter can only be used when searching with `scope="edges"` as fact ratings are associated with edge relationships.~~
~~Read more about [fact ratings and how they work](/facts#rating-facts-for-relevancy).~~
## Breadth-First Search (BFS)
The `bfs_origin_node_uuids` parameter enables breadth-first searches starting from specified nodes, which helps make search results more relevant to recent context. This is particularly useful when combined with recent episode IDs to bias search results toward information connected to recent conversations. You can pass episode IDs as BFS node IDs because episodes are represented as nodes under the hood.
**When to use**: Use BFS when you want to find information that's contextually connected to specific starting points in your graph, such as recent episodes or important entities.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
# Get recent episodes to use as BFS origin points
episodes = client.graph.episode.get_by_user_id(
user_id=user_id,
lastn=10
).episodes
episode_uuids = [episode.uuid_ for episode in episodes if episode.role == 'user']
# Search with BFS starting from recent episodes
search_results = client.graph.search(
user_id=user_id,
query="project updates",
scope="edges",
bfs_origin_node_uuids=episode_uuids,
limit=10
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Get recent episodes to use as BFS origin points
const episodeResponse = await client.graph.episode.getByUserId(userId, { lastn: 10 });
const episodeUuids = (episodeResponse.episodes || [])
.filter((episode) => episode.role === "user")
.map((episode) => episode.uuid);
// Search with BFS starting from recent episodes
const searchResults = await client.graph.search({
userId: userId,
query: "project updates",
scope: "edges",
bfsOriginNodeUuids: episodeUuids,
limit: 10,
});
```
```go Go
import (
"context"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(API_KEY),
)
// Get recent episodes to use as BFS origin points
response, err := client.Graph.Episode.GetByUserID(
ctx,
userID,
&zep.EpisodeGetByUserIDRequest{
Lastn: zep.Int(10),
},
)
var episodeUUIDs []string
for _, episode := range response.Episodes {
if episode.Role != nil && *episode.Role == zep.RoleTypeUserRole {
episodeUUIDs = append(episodeUUIDs, episode.UUID)
}
}
// Search with BFS starting from recent episodes
searchResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "project updates",
Scope: zep.GraphSearchScopeEdges.Ptr(),
BfsOriginNodeUUIDs: episodeUUIDs,
Limit: zep.Int(10),
})
```
# Reading Data from the Graph
Zep provides APIs to read Edges, Nodes, and Episodes from the graph. These elements can be retrieved individually using their `UUID`, or as lists associated with a specific `user_id` or `graph_id`. The latter method returns all objects within the user's or graph's graph.
Examples of each retrieval method are provided below.
## Reading Edges
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
edge = client.graph.edge.get(edgeUuid)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const edge = await client.graph.edge.get(edgeUuid);
```
```go Go
package main
import (
"context"
"fmt"
"log"
"github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
func main() {
ctx := context.Background()
zepClient := client.NewClient(option.WithAPIKey(apiKey))
edge, err := zepClient.Graph.Edge.Get(ctx, edgeUUID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Edge: %+v\n", edge)
}
```
## Reading Nodes
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
node = client.graph.node.get_by_user(userUuid)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const node = await client.graph.node.getByUser(userUuid);
```
```go Go
package main
import (
"context"
"fmt"
"log"
"github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
func main() {
ctx := context.Background()
zepClient := client.NewClient(option.WithAPIKey(apiKey))
nodes, err := zepClient.Graph.Node.GetByUserID(ctx, userUUID, nil)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Nodes: %+v\n", nodes)
}
```
## Reading Episodes
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
episode = client.graph.episode.get_by_graph_id(graph_uuid)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const episode = client.graph.episode.getByGraphId(graph_uuid);
```
```go Go
package main
import (
"context"
"fmt"
"log"
"github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
func main() {
ctx := context.Background()
zepClient := client.NewClient(option.WithAPIKey(apiKey))
episodes, err := zepClient.Graph.Episode.GetByGraphID(ctx, graphUUID, nil)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Episodes: %+v\n", episodes)
}
```
# Deleting Data from the Graph
## Delete an Edge
Here's how to delete an edge from a graph:
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
client.graph.edge.delete(uuid_="your_edge_uuid")
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
await client.graph.edge.delete("your_edge_uuid");
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
ctx := context.TODO()
zepClient := client.NewClient(option.WithAPIKey(apiKey))
_, err := zepClient.Graph.Edge.Delete(ctx, "your_edge_uuid")
if err != nil {
log.Fatal("Error deleting edge:", err)
}
```
Note that when you delete an edge, it never deletes the associated nodes, even if it means there will be a node with no edges.
## Delete a Node
Here's how to delete a node from a graph:
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
client.graph.node.delete(uuid_="your_node_uuid")
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
await client.graph.node.delete("your_node_uuid");
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
ctx := context.TODO()
zepClient := client.NewClient(option.WithAPIKey(apiKey))
_, err := zepClient.Graph.Node.Delete(ctx, "your_node_uuid")
if err != nil {
log.Fatal("Error deleting node:", err)
}
```
Deleting a node will also delete all edges connected to that node. This is a cascading delete operation - the node and all its relationships are permanently removed from the graph.
## Delete an Episode
Deleting an episode does not regenerate the names or summaries of nodes shared with other episodes. This episode information may still exist within these nodes. If an episode invalidates a fact, and the episode is deleted, the fact will remain marked as invalidated.
When you delete an [episode](/graphiti/graphiti/adding-episodes), it will delete all the edges associated with it, and it will delete any nodes that are only attached to that episode. Nodes that are also attached to another episode will not be deleted.
Here's how to delete an episode from a graph:
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
client.graph.episode.delete(uuid_="episode_uuid")
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
await client.graph.episode.delete("episode_uuid");
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
ctx := context.TODO()
zepClient := client.NewClient(option.WithAPIKey(apiKey))
_, err := zepClient.Graph.Episode.Delete(ctx, "episode_uuid")
if err != nil {
log.Fatal("Error deleting episode:", err)
}
```
## Delete a Thread
Deleting a thread removes all episodes associated with that thread. This triggers a cascading effect on the graph.
When a thread is deleted, each associated episode is removed. For each episode:
* **Edges** are deleted if they were created by that specific episode
* **Nodes** are deleted only if no other episodes reference them
This design preserves graph integrity. If multiple conversations mention the same entity or establish the same relationship, deleting one thread will not remove data that other threads contributed to the graph.
Here's how to delete a thread:
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
client.thread.delete(thread_id="your_thread_id")
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
await client.thread.delete("your_thread_id");
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
ctx := context.TODO()
zepClient := client.NewClient(option.WithAPIKey(apiKey))
_, err := zepClient.Thread.Delete(ctx, "your_thread_id")
if err != nil {
log.Fatal("Error deleting thread:", err)
}
```
# Cloning Graphs
> Create complete copies of graphs with new identifiers for testing, migration, or templating
## Overview
The `graph.clone` method allows you to create complete copies of graphs with new identifiers. This is useful for scenarios like creating test copies of user data, migrating user graphs to new identifiers, or setting up template graphs for new users.
The clone operation returns a `task_id` that can be used to track the cloning progress. See the [Check Data Ingestion Status](/cookbook/check-data-ingestion-status#checking-operation-status-with-task-polling) recipe for how to poll task status.
The target graph must not exist - they will be created as part of the cloning operation. If no target ID is provided, one will be auto-generated and returned in the response.
## Clone a Graph
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
# Clone a user graph to a new user ID
result = client.graph.clone(
source_user_id="user_123",
target_user_id="user_123_copy" # Optional - will be auto-generated if not provided
)
print(f"Cloned graph to user: {result.user_id}")
print(f"Clone task ID: {result.task_id}")
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Clone a user graph to a new user ID
const result = await client.graph.clone({
sourceUserId: "user_123",
targetUserId: "user_123_copy" // Optional - will be auto-generated if not provided
});
console.log(`Cloned graph to user: ${result.userId}`);
console.log(`Clone task ID: ${result.taskId}`);
```
```go Go
import (
"context"
"fmt"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
// Clone a user graph to a new user ID
sourceUserID := "user_123"
targetUserID := "user_123_copy" // Optional - will be auto-generated if not provided
result, err := client.Graph.Clone(context.TODO(), &v3.CloneGraphRequest{
SourceUserID: &sourceUserID,
TargetUserID: &targetUserID,
})
if err != nil {
log.Fatalf("Failed to clone graph: %v", err)
}
fmt.Printf("Cloned graph to user: %s\n", *result.UserID)
fmt.Printf("Clone task ID: %s\n", *result.TaskID)
```
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
# Clone a graph to a new graph ID
result = client.graph.clone(
source_graph_id="graph_456",
target_graph_id="graph_456_copy" # Optional - will be auto-generated if not provided
)
print(f"Cloned graph to graph: {result.graph_id}")
print(f"Clone task ID: {result.task_id}")
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Clone a graph to a new graph ID
const result = await client.graph.clone({
sourceGraphId: "graph_456",
targetGraphId: "graph_456_copy" // Optional - will be auto-generated if not provided
});
console.log(`Cloned graph to graph: ${result.graphId}`);
console.log(`Clone task ID: ${result.taskId}`);
```
```go Go
import (
"context"
"fmt"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
// Clone a graph to a new graph ID
sourceGraphID := "graph_456"
targetGraphID := "graph_456_copy" // Optional - will be auto-generated if not provided
result, err := client.Graph.Clone(context.TODO(), &v3.CloneGraphRequest{
SourceGraphID: &sourceGraphID,
TargetGraphID: &targetGraphID,
})
if err != nil {
log.Fatalf("Failed to clone graph: %v", err)
}
fmt.Printf("Cloned graph to graph: %s\n", *result.GraphID)
fmt.Printf("Clone task ID: %s\n", *result.TaskID)
```
## Key Behaviors and Limitations
* **Target Requirements**: The target user or graph must not exist and will be created during the cloning operation
* **Auto-generation**: If no target ID is provided, Zep will auto-generate one and return it in the response
* **Node Modification**: The central user entity node in the cloned graph is updated with the new user ID, and all references in the node summary are updated accordingly
# Adding Fact Triplets
> Manually specify fact/node triplets to add structured relationships to your graph
## Overview
You can add manually specified fact/node triplets to the graph. You need only specify the fact, the target node name, and the source node name. Zep will then create a new corresponding edge and nodes, or use an existing edge/nodes if they exist and seem to represent the same nodes or edge you send as input. And if this new fact invalidates an existing fact, it will mark the existing fact as invalid and add the new fact triplet.
The `add_fact_triple` method returns a `task_id` that can be used to track the processing status of the operation. See the [Check Data Ingestion Status](/cookbook/check-data-ingestion-status#checking-operation-status-with-task-polling) recipe for how to poll task status.
## Adding a Fact Triplet
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
result = client.graph.add_fact_triple(
user_id=user_id, # Optional: You can use graph_id instead of user_id
fact="Paul met Eric",
fact_name="MET",
target_node_name="Eric Clapton",
source_node_name="Paul",
)
# The result includes a task_id for tracking processing status
task_id = result.task_id
print(f"Fact triple task ID: {task_id}")
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const result = await client.graph.addFactTriple({
userId: userId, // Optional: You can use graphId instead of userId
fact: "Paul met Eric",
factName: "MET",
targetNodeName: "Eric Clapton",
sourceNodeName: "Paul",
});
// The result includes a task_id for tracking processing status
const taskId = result.taskId;
console.log(`Fact triple task ID: ${taskId}`);
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
userID := "user123"
sourceNodeName := "Paul"
result, err := client.Graph.AddFactTriple(context.TODO(), &v3.AddTripleRequest{
UserID: &userID, // Optional: You can use GraphID instead of UserID
Fact: "Paul met Eric",
FactName: "MET",
TargetNodeName: "Eric Clapton",
SourceNodeName: &sourceNodeName,
})
if err != nil {
log.Fatalf("Failed to add fact triple: %v", err)
}
// The result includes a task_id for tracking processing status
taskID := result.TaskID
log.Printf("Fact triple task ID: %s", *taskID)
```
## Advanced Options
### Setting Node and Edge Attributes
You can attach custom scalar attributes to nodes and edges when creating fact triplets. This allows you to store additional metadata that can later be used for filtering in graph searches.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
result = client.graph.add_fact_triple(
user_id=user_id,
fact="Alice manages the Engineering team",
fact_name="MANAGES",
source_node_name="Alice",
target_node_name="Engineering Team",
source_node_attributes={
"department": "Engineering",
"level": 5,
"active": True
},
target_node_attributes={
"team_size": 12,
"founded_year": 2020
},
edge_attributes={
"role": "Team Lead",
"since": "2023-01-15"
}
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
const result = await client.graph.addFactTriple({
userId: userId,
fact: "Alice manages the Engineering team",
factName: "MANAGES",
sourceNodeName: "Alice",
targetNodeName: "Engineering Team",
sourceNodeAttributes: {
department: "Engineering",
level: 5,
active: true
},
targetNodeAttributes: {
teamSize: 12,
foundedYear: 2020
},
edgeAttributes: {
role: "Team Lead",
since: "2023-01-15"
}
});
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
userID := "user123"
sourceNodeName := "Alice"
targetNodeName := "Engineering Team"
result, err := client.Graph.AddFactTriple(context.TODO(), &zep.AddTripleRequest{
UserID: &userID,
Fact: "Alice manages the Engineering team",
FactName: "MANAGES",
SourceNodeName: &sourceNodeName,
TargetNodeName: &targetNodeName,
SourceNodeAttributes: map[string]interface{}{
"department": "Engineering",
"level": 5,
"active": true,
},
TargetNodeAttributes: map[string]interface{}{
"team_size": 12,
"founded_year": 2020,
},
EdgeAttributes: map[string]interface{}{
"role": "Team Lead",
"since": "2023-01-15",
},
})
if err != nil {
log.Fatalf("Failed to add fact triple: %v", err)
}
```
Attribute values must be scalar types: string, number, boolean, or null. Nested objects and arrays are not supported.
### Specifying Node UUIDs
You can also specify existing node UUIDs and other temporal metadata. See the [associated SDK reference](/sdk-reference/graph/add-fact-triple) for complete details on all available parameters.
When passing `source_node_uuid` or `target_node_uuid`, the specified nodes must already exist in the graph. Providing UUIDs for non-existent nodes will result in an error.
# Check Data Ingestion Status
Data added to Zep is processed asynchronously and can take a few seconds to a few minutes to finish processing. This recipe shows how to check whether data upload operations are finished processing.
Zep provides two methods for checking data ingestion status:
* **Task polling** (recommended for async operations): Use `client.task.get()` to check the status of batch operations, clone operations, and fact triple additions
* **Episode polling**: Use `graph.episode.get()` to check individual episode processing status
## Checking Operation Status with Task Polling
When using operations that return a `task_id`, you can poll for completion status using `client.task.get()`. The following operations return a `task_id`:
* `graph.add_batch()` - Batch episode additions
* `thread.add_messages_batch()` - Batch message additions to threads
* `graph.clone()` - Graph cloning operations
* `graph.add_fact_triple()` - Custom fact/node triplet additions
This is the recommended approach for these operations as it provides a single status check for the entire operation.
First, let's create a user:
```python
import os
import uuid
import time
from dotenv import find_dotenv, load_dotenv
from zep_cloud.client import Zep
from zep_cloud import EpisodeData
load_dotenv(dotenv_path=find_dotenv())
client = Zep(api_key=os.environ.get("ZEP_API_KEY"))
uuid_value = uuid.uuid4().hex[:4]
user_id = "-" + uuid_value
client.user.add(
user_id=user_id,
first_name="John",
last_name="Doe",
email="john.doe@example.com"
)
```
```typescript
import { ZepClient, EpisodeData } from "@getzep/zep-cloud";
import * as dotenv from "dotenv";
import { v4 as uuidv4 } from 'uuid';
// Load environment variables
dotenv.config();
const client = new ZepClient({ apiKey: process.env.ZEP_API_KEY || "" });
const uuidValue = uuidv4().substring(0, 4);
const userId = "-" + uuidValue;
async function main() {
// Add user
await client.user.add({
userId: userId,
firstName: "John",
lastName: "Doe",
email: "john.doe@example.com"
});
```
```go
package main
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
"github.com/google/uuid"
"github.com/joho/godotenv"
)
func main() {
// Load .env file
err := godotenv.Load()
if err != nil {
fmt.Println("Warning: Error loading .env file:", err)
}
// Get API key from environment variable
apiKey := os.Getenv("ZEP_API_KEY")
if apiKey == "" {
fmt.Println("ZEP_API_KEY environment variable is not set")
return
}
// Initialize Zep client
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
// Create a UUID
uuidValue := strings.ToLower(uuid.New().String()[:4])
userID := "-" + uuidValue
ctx := context.Background()
// Add a user
userRequest := &zep.CreateUserRequest{
UserID: zep.String(userID),
FirstName: zep.String("John"),
LastName: zep.String("Doe"),
Email: zep.String("john.doe@example.com"),
}
_, err = client.User.Add(ctx, userRequest)
if err != nil {
fmt.Printf("Error creating user: %v\n", err)
return
}
```
Now, let's add a batch of episodes to the graph. The response includes a `task_id` in each episode that we can use to check the processing status:
```python
# Add batch data to the graph
episodes = [
EpisodeData(
data="The user is an avid fan of Eric Clapton",
type="text"
),
EpisodeData(
data="The user attended a concert last night",
type="text"
),
EpisodeData(
data="The user plays guitar as a hobby",
type="text"
)
]
batch_result = client.graph.add_batch(
graph_id=user_id,
episodes=episodes
)
# Get the task_id from the first episode (all episodes in a batch share the same task_id)
task_id = batch_result[0].task_id
print(f"Batch processing task ID: {task_id}")
```
```typescript
// Add batch data to the graph
const episodes: EpisodeData[] = [
{
data: "The user is an avid fan of Eric Clapton",
type: "text"
},
{
data: "The user attended a concert last night",
type: "text"
},
{
data: "The user plays guitar as a hobby",
type: "text"
}
];
const batchResult = await client.graph.addBatch({
graphId: userId,
episodes: episodes
});
// Get the task_id from the first episode (all episodes in a batch share the same task_id)
const taskId = batchResult[0].taskId;
console.log(`Batch processing task ID: ${taskId}`);
```
```go
// Add batch data to the graph
episodes := []*zep.EpisodeData{
{
Data: "The user is an avid fan of Eric Clapton",
Type: zep.GraphDataTypeText,
},
{
Data: "The user attended a concert last night",
Type: zep.GraphDataTypeText,
},
{
Data: "The user plays guitar as a hobby",
Type: zep.GraphDataTypeText,
},
}
batchResult, err := client.Graph.AddBatch(ctx, &zep.AddDataBatchRequest{
GraphID: zep.String(userID),
Episodes: episodes,
})
if err != nil {
fmt.Printf("Error adding batch data: %v\n", err)
return
}
// Get the task_id from the first episode (all episodes in a batch share the same task_id)
taskID := batchResult[0].TaskID
fmt.Printf("Batch processing task ID: %s\n", *taskID)
```
Now we can poll the task status using `client.task.get()` to check when the entire batch has finished processing:
```python
# Poll the task status until completion
while True:
task = client.task.get(task_id=task_id)
if task.status == "completed":
print("Batch processing completed successfully")
break
elif task.status == "failed":
print(f"Batch processing failed: {task.error}")
break
print(f"Batch processing status: {task.status}")
time.sleep(1)
```
```typescript
// Poll the task status until completion
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
while (true) {
const task = await client.task.get(taskId);
if (task.status === "completed") {
console.log("Batch processing completed successfully");
break;
} else if (task.status === "failed") {
console.log(`Batch processing failed: ${task.error}`);
break;
}
console.log(`Batch processing status: ${task.status}`);
await sleep(1000);
}
```
```go
// Poll the task status until completion
for {
task, err := client.Task.Get(ctx, *taskID)
if err != nil {
fmt.Printf("Error getting task: %v\n", err)
return
}
if task.Status == "completed" {
fmt.Println("Batch processing completed successfully")
break
} else if task.Status == "failed" {
fmt.Printf("Batch processing failed: %v\n", task.Error)
break
}
fmt.Printf("Batch processing status: %s\n", task.Status)
time.Sleep(1 * time.Second)
}
```
Once the batch is complete, you can search for the data that was added:
```python
search_results = client.graph.search(
user_id=user_id,
query="Eric Clapton",
scope="nodes",
limit=1,
reranker="cross_encoder",
)
print(search_results.nodes)
```
```typescript
const searchResults = await client.graph.search({
userId: userId,
query: "Eric Clapton",
scope: "nodes",
limit: 1,
reranker: "cross_encoder"
});
console.log(searchResults.nodes);
}
main().catch(error => console.error("Error:", error));
```
```go
searchResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "Eric Clapton",
Scope: zep.GraphSearchScopeNodes.Ptr(),
Limit: zep.Int(1),
Reranker: zep.RerankerCrossEncoder.Ptr(),
})
if err != nil {
fmt.Printf("Error searching graph: %v\n", err)
return
}
fmt.Println(searchResults.Nodes)
}
```
## Checking Individual Episode Status with Episode Polling
For single episode operations or when you need to check the status of individual episodes, you can use the `graph.episode.get()` method. This approach is useful when adding data one episode at a time.
First, let's create a user:
```python
import os
import uuid
import time
from dotenv import find_dotenv, load_dotenv
from zep_cloud.client import Zep
load_dotenv(dotenv_path=find_dotenv())
client = Zep(api_key=os.environ.get("ZEP_API_KEY"))
uuid_value = uuid.uuid4().hex[:4]
user_id = "-" + uuid_value
client.user.add(
user_id=user_id,
first_name = "John",
last_name = "Doe",
email="john.doe@example.com"
)
```
```typescript
import { ZepClient } from "@getzep/zep-cloud";
import * as dotenv from "dotenv";
import { v4 as uuidv4 } from 'uuid';
// Load environment variables
dotenv.config();
const client = new ZepClient({ apiKey: process.env.ZEP_API_KEY || "" });
const uuidValue = uuidv4().substring(0, 4);
const userId = "-" + uuidValue;
async function main() {
// Add user
await client.user.add({
userId: userId,
firstName: "John",
lastName: "Doe",
email: "john.doe@example.com"
});
```
```go
package main
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
"github.com/google/uuid"
"github.com/joho/godotenv"
)
func main() {
// Load .env file
err := godotenv.Load()
if err != nil {
fmt.Println("Warning: Error loading .env file:", err)
// Continue execution as environment variables might be set in the system
}
// Get API key from environment variable
apiKey := os.Getenv("ZEP_API_KEY")
if apiKey == "" {
fmt.Println("ZEP_API_KEY environment variable is not set")
return
}
// Initialize Zep client
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
// Create a UUID
uuidValue := strings.ToLower(uuid.New().String()[:4])
// Create user ID
userID := "-" + uuidValue
// Create context
ctx := context.Background()
// Add a user
userRequest := &zep.CreateUserRequest{
UserID: zep.String(userID),
FirstName: zep.String("John"),
LastName: zep.String("Doe"),
Email: zep.String("john.doe@example.com"),
}
_, err = client.User.Add(ctx, userRequest)
if err != nil {
fmt.Printf("Error creating user: %v\n", err)
return
}
```
Now, let's add some data and immediately try to search for that data; because data added to Zep is processed asynchronously and can take a few seconds to a few minutes to finish processing, our search results do not have the data we just added:
```python
episode = client.graph.add(
user_id=user_id,
type="text",
data="The user is an avid fan of Eric Clapton"
)
search_results = client.graph.search(
user_id=user_id,
query="Eric Clapton",
scope="nodes",
limit=1,
reranker="cross_encoder",
)
print(search_results.nodes)
```
```typescript
// Add episode to graph
const episode = await client.graph.add({
userId: userId,
type: "text",
data: "The user is an avid fan of Eric Clapton"
});
// Search for nodes related to Eric Clapton
const searchResults = await client.graph.search({
userId: userId,
query: "Eric Clapton",
scope: "nodes",
limit: 1,
reranker: "cross_encoder"
});
console.log(searchResults.nodes);
```
```go
// Add a new episode to the graph
episode, err := client.Graph.Add(ctx, &zep.AddDataRequest{
GraphID: zep.String(userID),
Type: zep.GraphDataTypeText.Ptr(),
Data: zep.String("The user is an avid fan of Eric Clapton"),
})
if err != nil {
fmt.Printf("Error adding episode to graph: %v\n", err)
return
}
// Search for the data
searchResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "Eric Clapton",
Scope: zep.GraphSearchScopeNodes.Ptr(),
Limit: zep.Int(1),
Reranker: zep.RerankerCrossEncoder.Ptr(),
})
if err != nil {
fmt.Printf("Error searching graph: %v\n", err)
return
}
fmt.Println(searchResults.Nodes)
```
```text
None
```
We can check the status of the episode to see when it has finished processing, using the episode returned from the `graph.add` method and the `graph.episode.get` method:
```python
while True:
episode = client.graph.episode.get(
uuid_=episode.uuid_,
)
if episode.processed:
print("Episode processed successfully")
break
print("Waiting for episode to process...")
time.sleep(1)
```
```typescript
// Check if episode is processed
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
let processedEpisode = await client.graph.episode.get(episode.uuid);
while (!processedEpisode.processed) {
console.log("Waiting for episode to process...");
await sleep(1000); // Sleep for 1 second
processedEpisode = await client.graph.episode.get(episode.uuid);
}
console.log("Episode processed successfully");
```
```go
// Wait for the episode to be processed
for {
episodeStatus, err := client.Graph.Episode.Get(
ctx,
episode.UUID,
)
if err != nil {
fmt.Printf("Error getting episode: %v\n", err)
return
}
if episodeStatus.Processed != nil && *episodeStatus.Processed {
fmt.Println("Episode processed successfully")
break
}
fmt.Println("Waiting for episode to process...")
time.Sleep(1 * time.Second)
}
```
```text
Waiting for episode to process...
Waiting for episode to process...
Waiting for episode to process...
Waiting for episode to process...
Waiting for episode to process...
Episode processed successfully
```
Now that the episode has finished processing, we can search for the data we just added, and this time we get a result:
```python
search_results = client.graph.search(
user_id=user_id,
query="Eric Clapton",
scope="nodes",
limit=1,
reranker="cross_encoder",
)
print(search_results.nodes)
```
```typescript
// Search again after processing
const finalSearchResults = await client.graph.search({
userId: userId,
query: "Eric Clapton",
scope: "nodes",
limit: 1,
reranker: "cross_encoder"
});
console.log(finalSearchResults.nodes);
}
// Execute the main function
main().catch(error => console.error("Error:", error));
```
```go
// Search again after processing
searchResults, err = client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String(userID),
Query: "Eric Clapton",
Scope: zep.GraphSearchScopeNodes.Ptr(),
Limit: zep.Int(1),
Reranker: zep.RerankerCrossEncoder.Ptr(),
})
if err != nil {
fmt.Printf("Error searching graph: %v\n", err)
return
}
fmt.Println(searchResults.Nodes)
}
```
```text
[EntityNode(attributes={'category': 'Music', 'labels': ['Entity', 'Preference']}, created_at='2025-04-05T00:17:59.66565Z', labels=['Entity', 'Preference'], name='Eric Clapton', summary='The user is an avid fan of Eric Clapton.', uuid_='98808054-38ad-4cba-ba07-acd5f7a12bc0', graph_id='6961b53f-df05-48bb-9b8d-b2702dd72045')]
```
# Add User Specific Business Data to User Graphs
This guide demonstrates how to add user-specific business data to a user's knowledge graph. We'll create a user, fetch their business data, and add it to their graph.
First, we will initialize our client and create a new user:
```python Python
# Initialize the Zep client
zep_client = Zep(api_key=API_KEY)
# Add one example user
user_id_zep = uuid.uuid4().hex
zep_client.user.add(
user_id=user_id_zep,
email="cookbook@example.com"
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
import { randomUUID } from "crypto";
// Initialize the Zep client
const client = new ZepClient({ apiKey: API_KEY });
// Add one example user
const userIdZep = randomUUID().replace(/-/g, "");
await client.user.add({
userId: userIdZep,
email: "cookbook@example.com"
});
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
"github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
"github.com/google/uuid"
)
// Initialize the Zep client
zepClient := client.NewClient(option.WithAPIKey(API_KEY))
// Add one example user
userIDZep := uuid.New().String()
user, err := zepClient.User.Add(
context.TODO(),
&zep.CreateUserRequest{
UserID: userIDZep,
Email: zep.String("cookbook@example.com"),
},
)
if err != nil {
log.Fatalf("Error: %v", err)
}
```
Then, we will fetch and format the user's business data. Note that the functionality to fetch a users business data will depend on your codebase.
Also note that you could make your Zep user IDs equal to whatever internal user IDs you use to make things easier to manage. Generally, Zep user IDs, thread IDs, Graph IDs, etc. can be arbitrary strings, and can map to your app's data schema.
```python Python
# Define the function to fetch user business data
def get_user_business_data(user_id_business):
# This function returns JSON data for the given user
# This would vary based on your codebase
return {}
# Placeholder for business user id
user_id_business = "placeholder_user_id" # This would vary based on your codebase
# Retrieve the user-specific business data
user_data_json = get_user_business_data(user_id_business)
# Convert the business data to a string
json_string = json.dumps(user_data_json)
```
```typescript TypeScript
// Define the function to fetch user business data
function getUserBusinessData(userIdBusiness: string): Record {
// This function returns JSON data for the given user
// This would vary based on your codebase
return {};
}
// Placeholder for business user id
const userIdBusiness = "placeholder_user_id"; // This would vary based on your codebase
// Retrieve the user-specific business data
const userDataJson = getUserBusinessData(userIdBusiness);
// Convert the business data to a string
const jsonString = JSON.stringify(userDataJson);
```
```go Go
import (
"encoding/json"
)
// Define the function to fetch user business data
func getUserBusinessData(userIDBusiness string) map[string]interface{} {
// This function returns JSON data for the given user
// This would vary based on your codebase
return map[string]interface{}{}
}
// Placeholder for business user id
userIDBusiness := "placeholder_user_id" // This would vary based on your codebase
// Retrieve the user-specific business data
userDataJSON := getUserBusinessData(userIDBusiness)
// Convert the business data to a string
jsonBytes, err := json.Marshal(userDataJSON)
if err != nil {
log.Fatalf("Error: %v", err)
}
jsonString := string(jsonBytes)
```
Lastly, we will add the formatted data to the user's graph using the [graph API](/adding-data-to-the-graph):
```python Python
# Add the JSON data to the user's graph
zep_client.graph.add(
user_id=user_id_zep,
type="json",
data=json_string,
)
```
```typescript TypeScript
// Add the JSON data to the user's graph
await client.graph.add({
userId: userIdZep,
type: "json",
data: jsonString,
});
```
```go Go
// Add the JSON data to the user's graph
episode, err := zepClient.Graph.Add(
context.TODO(),
&zep.AddDataRequest{
UserID: zep.String(userIDZep),
Type: zep.GraphDataTypeJSON,
Data: jsonString,
},
)
if err != nil {
log.Fatalf("Error: %v", err)
}
```
Here, we use `type="json"`, but the graph API also supports `type="text"` and `type="message"`. The `type="text"` option is useful for adding background information that is in unstructured text such as internal documents or web copy. The `type="message"` option is useful for adding data that is in a message format but is not your user's chat history, such as emails. [Read more about this here](/adding-data-to-the-graph).
Also, note that when adding data to the graph, you should consider the size of the data you are adding and our payload limits. [Read more about this here](/docs/performance/performance-best-practices#optimizing-memory-operations).
You have now successfully added user-specific business data to a user's knowledge graph, which can be used alongside chat history to create comprehensive user context.
# Share context across users using graphs
In this recipe, we will demonstrate how to share context across different users by utilizing graphs. We will set up a user thread, add graph-specific data, and integrate the OpenAI client to show how to use both user and graph context to enhance the context of a chatbot.
First, we initialize the Zep client, create a user, and create a thread:
```python
# Initialize the Zep client
zep_client = Zep(api_key="YOUR_API_KEY") # Ensure your API key is set appropriately
# Add one example user
user_id = uuid.uuid4().hex
zep_client.user.add(
user_id=user_id,
first_name="Alice",
last_name="Smith",
email="alice.smith@example.com"
)
# Create a new thread for the user
thread_id = uuid.uuid4().hex
zep_client.thread.create(
thread_id=thread_id,
user_id=user_id,
)
```
```typescript
import { ZepClient } from "@getzep/zep-cloud";
import { randomUUID } from "crypto";
// Initialize the Zep client
const zepClient = new ZepClient({ apiKey: "YOUR_API_KEY" });
// Add one example user
const userId = randomUUID().replace(/-/g, "");
await zepClient.user.add({
userId: userId,
firstName: "Alice",
lastName: "Smith",
email: "alice.smith@example.com"
});
// Create a new thread for the user
const threadId = randomUUID().replace(/-/g, "");
await zepClient.thread.create({
threadId: threadId,
userId: userId
});
```
```go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
"github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
"github.com/google/uuid"
)
// Initialize the Zep client
zepClient := client.NewClient(option.WithAPIKey("YOUR_API_KEY"))
// Add one example user
userId := uuid.New().String()
_, err := zepClient.User.Add(context.Background(), &zep.CreateUserRequest{
UserID: userId,
FirstName: zep.String("Alice"),
LastName: zep.String("Smith"),
Email: zep.String("alice.smith@example.com"),
})
if err != nil {
log.Fatalf("Error: %v", err)
}
// Create a new thread for the user
threadId := uuid.New().String()
_, err = zepClient.Thread.Create(context.Background(), &zep.CreateThreadRequest{
ThreadID: threadId,
UserID: userId,
})
if err != nil {
log.Fatalf("Error: %v", err)
}
```
Next, we create a new graph and add structured business data to the graph, in the form of a JSON string. This step uses the [Graphs API](/graph-overview).
```python
graph_id = uuid.uuid4().hex
zep_client.graph.create(graph_id=graph_id)
product_json_data = [
{
"type": "Sedan",
"gas_mileage": "25 mpg",
"maker": "Toyota"
},
# ... more cars
]
json_string = json.dumps(product_json_data)
zep_client.graph.add(
graph_id=graph_id,
type="json",
data=json_string,
)
```
```typescript
const graphId = randomUUID().replace(/-/g, "");
await zepClient.graph.create({ graphId: graphId });
const productJsonData = [
{
type: "Sedan",
gas_mileage: "25 mpg",
maker: "Toyota"
},
// ... more cars
];
const jsonString = JSON.stringify(productJsonData);
await zepClient.graph.add({
graphId: graphId,
type: "json",
data: jsonString
});
```
```go
import "encoding/json"
graphId := uuid.New().String()
_, err = zepClient.Graph.Create(context.Background(), &zep.CreateGraphRequest{
GraphID: graphId,
})
if err != nil {
log.Fatalf("Error: %v", err)
}
productJsonData := []map[string]string{
{
"type": "Sedan",
"gas_mileage": "25 mpg",
"maker": "Toyota",
},
// ... more cars
}
jsonBytes, err := json.Marshal(productJsonData)
if err != nil {
log.Fatalf("Error: %v", err)
}
jsonString := string(jsonBytes)
_, err = zepClient.Graph.Add(context.Background(), &zep.AddDataRequest{
GraphID: &graphId,
Type: zep.GraphDataTypeJSON,
Data: jsonString,
})
if err != nil {
log.Fatalf("Error: %v", err)
}
```
Finally, we initialize the OpenAI client and define a `chatbot_response` function that retrieves user and graph context, constructs a system/developer message, and generates a contextual response. This leverages the [Threads API](/retrieving-context#zeps-context-block), [graph API](/searching-the-graph), and the OpenAI chat completions endpoint.
```python
# Initialize the OpenAI client
oai_client = OpenAI()
def chatbot_response(user_message, thread_id):
# Retrieve user context
user_context = zep_client.thread.get_user_context(thread_id)
# Search the graph using the user message as the query
results = zep_client.graph.search(graph_id=graph_id, query=user_message, scope="edges")
relevant_graph_edges = results.edges
product_context_block = "Below are some facts related to our car inventory that may help you respond to the user: \n"
for edge in relevant_graph_edges:
product_context_block += f"{edge.fact}\n"
# Combine context blocks for the developer message
developer_message = f"You are a helpful chat bot assistant for a car sales company. Answer the user's message while taking into account the following background information:\n{user_context.context}\n{product_context_block}"
# Generate a response using the OpenAI API
completion = oai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "developer", "content": developer_message},
{"role": "user", "content": user_message}
]
)
response = completion.choices[0].message.content
# Add the conversation to the thread
messages = [
Message(name="Alice", role="user", content=user_message),
Message(name="AI assistant", role="assistant", content=response)
]
zep_client.thread.add_messages(thread_id, messages=messages)
return response
```
```typescript
import OpenAI from "openai";
// Initialize the OpenAI client
const oaiClient = new OpenAI();
async function chatbotResponse(userMessage: string, threadId: string): Promise {
// Retrieve user context
const userContext = await zepClient.thread.getUserContext(threadId);
// Search the graph using the user message as the query
const results = await zepClient.graph.search({
graphId: graphId,
query: userMessage,
scope: "edges"
});
const relevantGraphEdges = results.edges || [];
let productContextBlock = "Below are some facts related to our car inventory that may help you respond to the user: \n";
for (const edge of relevantGraphEdges) {
productContextBlock += `${edge.fact}\n`;
}
// Combine context blocks for the developer message
const developerMessage = `You are a helpful chat bot assistant for a car sales company. Answer the user's message while taking into account the following background information:\n${userContext.context}\n${productContextBlock}`;
// Generate a response using the OpenAI API
const completion = await oaiClient.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "developer", content: developerMessage },
{ role: "user", content: userMessage }
]
});
const response = completion.choices[0].message.content || "";
// Add the conversation to the thread
await zepClient.thread.addMessages(threadId, {
messages: [
{ name: "Alice", role: "user", content: userMessage },
{ name: "AI assistant", role: "assistant", content: response }
]
});
return response;
}
```
```go
import (
"context"
"log"
"github.com/sashabaranov/go-openai"
)
// Initialize the OpenAI client
oaiClient := openai.NewClient("YOUR_OPENAI_API_KEY")
func chatbotResponse(userMessage, threadId string) (string, error) {
ctx := context.Background()
// Retrieve user context
userContext, err := zepClient.Thread.GetUserContext(ctx, threadId, &zep.ThreadGetUserContextRequest{})
if err != nil {
return "", err
}
// Search the graph using the user message as the query
results, err := zepClient.Graph.Search(ctx, &zep.GraphSearchQuery{
GraphID: &graphId,
Query: userMessage,
Scope: zep.GraphSearchScopeEdges.Ptr(),
})
if err != nil {
return "", err
}
relevantGraphEdges := results.Edges
productContextBlock := "Below are some facts related to our car inventory that may help you respond to the user: \n"
for _, edge := range relevantGraphEdges {
productContextBlock += edge.Fact + "\n"
}
// Combine context blocks for the developer message
developerMessage := "You are a helpful chat bot assistant for a car sales company. Answer the user's message while taking into account the following background information:\n" +
userContext.Context + "\n" + productContextBlock
// Generate a response using the OpenAI API
completion, err := oaiClient.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: openai.GPT4oMini,
Messages: []openai.ChatCompletionMessage{
{
Role: "developer",
Content: developerMessage,
},
{
Role: "user",
Content: userMessage,
},
},
})
if err != nil {
return "", err
}
response := completion.Choices[0].Message.Content
// Add the conversation to the thread
_, err = zepClient.Thread.AddMessages(ctx, threadId, &zep.AddThreadMessagesRequest{
Messages: []*zep.Message{
{
Name: zep.String("Alice"),
Role: zep.RoleTypeUserRole,
Content: userMessage,
},
{
Name: zep.String("AI assistant"),
Role: zep.RoleTypeAssistantRole,
Content: response,
},
},
})
if err != nil {
return "", err
}
return response, nil
}
```
This recipe demonstrated how to share context across users by utilizing graphs with Zep. We set up user threads, added structured graph data, and integrated the OpenAI client to generate contextual responses, providing a robust approach to context sharing across different users.
# Get Most Relevant Facts for an Arbitrary Query
In this recipe, we demonstrate how to retrieve the most relevant facts from the knowledge graph using an arbitrary search query.
First, we perform a [search](/searching-the-graph) on the knowledge graph using a sample query:
```python
from zep_cloud.client import Zep
zep_client = Zep(api_key=API_KEY)
results = zep_client.graph.search(user_id="some user_id", query="Some search query", scope="edges")
```
```typescript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({ apiKey: process.env.ZEP_API_KEY || "" });
const results = await client.graph.search({
userId: "some user_id",
query: "Some search query",
scope: "edges"
});
```
```go
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
func main() {
ctx := context.Background()
client := zepclient.NewClient(
option.WithAPIKey(os.Getenv("ZEP_API_KEY")),
)
results, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
UserID: zep.String("some user_id"),
Query: "Some search query",
Scope: zep.GraphSearchScopeEdges.Ptr(),
})
if err != nil {
log.Fatalf("Error: %v", err)
}
```
Then, we get the edges from the search results and construct our fact list. We also include the temporal validity data to each fact string:
```python
# Build list of formatted facts
relevant_edges = results.edges
formatted_facts = []
for edge in relevant_edges:
valid_at = edge.valid_at if edge.valid_at is not None else "date unknown"
invalid_at = edge.invalid_at if edge.invalid_at is not None else "present"
formatted_fact = f"{edge.fact} (Date range: {valid_at} - {invalid_at})"
formatted_facts.append(formatted_fact)
# Print the results
print("\nFound facts:")
for fact in formatted_facts:
print(f"- {fact}")
```
```typescript
// Build list of formatted facts
const relevantEdges = results.edges || [];
const formattedFacts: string[] = [];
for (const edge of relevantEdges) {
const validAt = edge.validAt ?? "date unknown";
const invalidAt = edge.invalidAt ?? "present";
const formattedFact = `${edge.fact} (Date range: ${validAt} - ${invalidAt})`;
formattedFacts.push(formattedFact);
}
// Print the results
console.log("\nFound facts:");
for (const fact of formattedFacts) {
console.log(`- ${fact}`);
}
```
```go
// Build list of formatted facts
relevantEdges := results.Edges
var formattedFacts []string
for _, edge := range relevantEdges {
validAt := "date unknown"
if edge.ValidAt != nil {
validAt = *edge.ValidAt
}
invalidAt := "present"
if edge.InvalidAt != nil {
invalidAt = *edge.InvalidAt
}
formattedFact := fmt.Sprintf("%s (Date range: %s - %s)", edge.Fact, validAt, invalidAt)
formattedFacts = append(formattedFacts, formattedFact)
}
// Print the results
fmt.Println("\nFound facts:")
for _, fact := range formattedFacts {
fmt.Printf("- %s\n", fact)
}
}
```
We demonstrated how to retrieve the most relevant facts for an arbitrary query using the Zep client. Adjust the query and parameters as needed to tailor the search for your specific use case.
# Find Facts Relevant to a Specific Node
Below, we will go through how to retrieve facts which are related to a specific node in a Zep knowledge graph. First, we will go through some methods for determining the UUID of the node you are interested in. Then, we will go through some methods for retrieving the facts related to that node.
If you are interested in the user's node specifically, we have a convenience method that [returns the user's node](/users#get-the-user-node) which includes the UUID.
An easy way to determine the UUID for other nodes is to use the graph explorer in the [Zep Web app](https://app.getzep.com/).
You can also programmatically retrieve all the nodes for a given user using our [get nodes by user API](/sdk-reference/graph/node/get-by-user-id), and then manually examine the nodes and take note of the UUID of the node of interest:
```python Python
# Initialize the Zep client
zep_client = Zep(api_key=API_KEY)
nodes = zep_client.graph.node.get_by_user_id(user_id="some user ID")
print(nodes)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
// Initialize the Zep client
const client = new ZepClient({ apiKey: API_KEY });
const nodes = await client.graph.node.getByUserId("some user ID", {});
console.log(nodes);
```
```go Go
import (
"context"
"fmt"
"log"
"github.com/getzep/zep-go/v3"
"github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
// Initialize the Zep client
zepClient := client.NewClient(option.WithAPIKey(API_KEY))
nodes, err := zepClient.Graph.Node.GetByUserID(
context.TODO(),
"some user ID",
&zep.GraphNodesRequest{},
)
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Println(nodes)
```
```python Python
center_node_uuid = "your chosen center node UUID"
```
```typescript TypeScript
const centerNodeUuid = "your chosen center node UUID";
```
```go Go
centerNodeUUID := "your chosen center node UUID"
```
Lastly, if your user has a lot of nodes to look through, you can narrow down the search by only looking at the nodes relevant to a specific query, using our [graph search API](/searching-the-graph):
```python Python
results = zep_client.graph.search(
user_id="some user ID",
query="shoe", # To help narrow down the nodes you have to manually search
scope="nodes"
)
relevant_nodes = results.nodes
print(relevant_nodes)
```
```typescript TypeScript
const results = await client.graph.search({
userId: "some user ID",
query: "shoe", // To help narrow down the nodes you have to manually search
scope: "nodes"
});
const relevantNodes = results.nodes;
console.log(relevantNodes);
```
```go Go
results, err := zepClient.Graph.Search(
context.TODO(),
&zep.GraphSearchQuery{
UserID: zep.String("some user ID"),
Query: "shoe", // To help narrow down the nodes you have to manually search
Scope: zep.GraphSearchScopeNodes.Ptr(),
},
)
if err != nil {
log.Fatalf("Error: %v", err)
}
relevantNodes := results.Nodes
fmt.Println(relevantNodes)
```
```python Python
center_node_uuid = "your chosen center node UUID"
```
```typescript TypeScript
const centerNodeUuid = "your chosen center node UUID";
```
```go Go
centerNodeUUID := "your chosen center node UUID"
```
The most straightforward way to get facts related to your node is to retrieve all facts that are connected to your chosen node using the [get edges by user API](/sdk-reference/graph/edge/get-by-user-id):
```python Python
edges = zep_client.graph.edge.get_by_user_id(user_id="some user ID")
connected_edges = [edge for edge in edges if edge.source_node_uuid == center_node_uuid or edge.target_node_uuid == center_node_uuid]
relevant_facts = [edge.fact for edge in connected_edges]
```
```typescript TypeScript
const edges = await client.graph.edge.getByUserId("some user ID", {});
const connectedEdges = edges.filter(
edge => edge.sourceNodeUuid === centerNodeUuid || edge.targetNodeUuid === centerNodeUuid
);
const relevantFacts = connectedEdges.map(edge => edge.fact);
```
```go Go
edges, err := zepClient.Graph.Edge.GetByUserID(
context.TODO(),
"some user ID",
&zep.GraphEdgesRequest{},
)
if err != nil {
log.Fatalf("Error: %v", err)
}
var connectedEdges []*zep.EntityEdge
for _, edge := range edges {
if edge.SourceNodeUUID == centerNodeUUID || edge.TargetNodeUUID == centerNodeUUID {
connectedEdges = append(connectedEdges, edge)
}
}
var relevantFacts []string
for _, edge := range connectedEdges {
relevantFacts = append(relevantFacts, edge.Fact)
}
```
You can also retrieve facts relevant to your node by using the [graph search API](/searching-the-graph) with the node distance re-ranker:
```python Python
results = zep_client.graph.search(
user_id="some user ID",
query="some query",
reranker="node_distance",
center_node_uuid=center_node_uuid,
)
relevant_edges = results.edges
relevant_facts = [edge.fact for edge in relevant_edges]
```
```typescript TypeScript
const results = await client.graph.search({
userId: "some user ID",
query: "some query",
reranker: "node_distance",
centerNodeUuid: centerNodeUuid,
});
const relevantEdges = results.edges;
const relevantFacts = relevantEdges?.map(edge => edge.fact) || [];
```
```go Go
results, err := zepClient.Graph.Search(
context.TODO(),
&zep.GraphSearchQuery{
UserID: zep.String("some user ID"),
Query: "some query",
Reranker: zep.GraphSearchRerankerNodeDistance.Ptr(),
CenterNodeUUID: zep.String(centerNodeUUID),
},
)
if err != nil {
log.Fatalf("Error: %v", err)
}
relevantEdges := results.Edges
var relevantFacts []string
for _, edge := range relevantEdges {
relevantFacts = append(relevantFacts, edge.Fact)
}
```
In this recipe, we went through how to retrieve facts which are related to a specific node in a Zep knowledge graph. We first went through some methods for determining the UUID of the node you are interested in. Then, we went through some methods for retrieving the facts related to that node.
# Agent Domain Knowledge
> Provide your agent with searchable knowledge graphs built from your data
This guide shows you how to give your agent searchable knowledge graphs built from any text data using Zep's Graph capabilities. Zep allows you to build knowledge graphs from unstructured text, JSON, or messages—including emails, Slack messages, transcripts, [chunked](/adding-data-to-the-graph#data-size-limit-and-chunking) documents, and inventory data. [Unlike traditional RAG systems](/docs/building-searchable-graphs/zep-vs-graph-rag), Zep is designed for evolving, streaming data that continuously updates your agent's knowledge.
Looking for a more in-depth understanding? Check out our [Key Concepts](/concepts) page.
## Install the Zep SDK
Set up your Python project, ideally with [a virtual environment](https://medium.com/@vkmauryavk/managing-python-virtual-environments-with-uv-a-comprehensive-guide-ac74d3ad8dff), and then:
```bash pip
pip install zep-cloud
```
```bash uv
uv pip install zep-cloud
```
Set up your TypeScript project and then:
```bash npm
npm install @getzep/zep-cloud
```
```bash yarn
yarn add @getzep/zep-cloud
```
```bash pnpm
pnpm install @getzep/zep-cloud
```
Set up your Go project and then:
```bash
go get github.com/getzep/zep-go/v3
```
## Initialize the Zep client
After [creating a Zep account](https://app.getzep.com/), obtaining an API key, and setting the API key as an environment variable, initialize the client once at application startup and reuse it throughout your application.
```python Python
import os
from zep_cloud.client import Zep
API_KEY = os.environ.get('ZEP_API_KEY')
client = Zep(
api_key=API_KEY,
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const API_KEY = process.env.ZEP_API_KEY;
const client = new ZepClient({
apiKey: API_KEY,
});
```
```go Go
import (
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(os.Getenv("ZEP_API_KEY")),
)
```
```
ZEP_API_KEY=your_api_key_here
```
## Create a graph
Before adding data, you need to create a standalone graph. This gives you an independent knowledge graph that isn't tied to individual users—useful for shared knowledge bases, domain-specific graphs, or specialized use cases.
```python Python
from zep_cloud.client import Zep
client = Zep(
api_key=API_KEY,
)
# Create a standalone graph with a custom ID
result = client.graph.create(
graph_id="my_custom_graph"
)
print(f"Created graph with ID: {result.graph_id}")
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Create a standalone graph with a custom ID
const result = await client.graph.create({
graphId: "my_custom_graph"
});
console.log(`Created graph with ID: ${result.graphId}`);
```
```go Go
import (
"context"
"fmt"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
// Create a standalone graph with a custom ID
graphID := "my_custom_graph"
result, err := client.Graph.Create(context.TODO(), &zep.CreateGraphRequest{
GraphID: &graphID,
})
if err != nil {
log.Fatalf("Failed to create graph: %v", err)
}
fmt.Printf("Created graph with ID: %s\n", *result.GraphID)
```
## Add streaming data to Zep
Zep excels at building knowledge graphs from streaming data that evolves over time. You can add any unstructured text, JSON, or message data to build your knowledge graph. Common examples include:
* Customer support conversations (emails, chat logs, Slack messages)
* Meeting transcripts and notes
* [Chunked](/adding-data-to-the-graph#data-size-limit-and-chunking) documents and knowledge base articles
* Inventory data and business records (JSON format)
* Any ongoing communication or evolving business data
**Zep is optimized for evolving data, not static RAG.** While you can add static documents, Zep's knowledge graph construction shines when tracking relationships and facts that change over time.
**One-time data uploads:** If you have existing data to backfill (such as a set of documents or historical data), you can add it using a one-time data migration. Simply loop through your data and call `graph.add` for each item, or use our [batch processing method](/adding-batch-data) for faster concurrent processing.
Zep supports three data types when adding data to a graph:
### Message data
Use message data when you have communications with designated speakers, such as emails or chat logs. See our [Adding Data to the Graph](/adding-data-to-the-graph#adding-message-data) guide for details.
```python Python
from zep_cloud.client import Zep
client = Zep(api_key=API_KEY)
# Add message data to a graph
message = "Sarah (customer): I need help configuring my API keys for production"
new_episode = client.graph.add(
graph_id="customer-support",
type="message",
data=message
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Add message data to a graph
const message = "Sarah (customer): I need help configuring my API keys for production";
const newEpisode = await client.graph.add({
graphId: "customer-support",
type: "message",
data: message
});
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
message := "Sarah (customer): I need help configuring my API keys for production"
graphID := "customer-support"
newEpisode, err := client.Graph.Add(context.TODO(), &zep.AddDataRequest{
GraphID: &graphID,
Type: zep.GraphDataTypeMessage,
Data: message,
})
if err != nil {
log.Fatalf("Failed to add message data: %v", err)
}
```
### Text data
Use text data for raw text without speaker attribution, like internal documents or wiki articles. See our [Adding Data to the Graph](/adding-data-to-the-graph#adding-text-data) guide for details.
```python Python
from zep_cloud.client import Zep
client = Zep(api_key=API_KEY)
# Add text data to a graph
text_data = "Production API keys must be configured with rate limiting enabled."
new_episode = client.graph.add(
graph_id="company-knowledge",
type="text",
data=text_data
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Add text data to a graph
const textData = "Production API keys must be configured with rate limiting enabled.";
const newEpisode = await client.graph.add({
graphId: "company-knowledge",
type: "text",
data: textData
});
```
```go Go
import (
"context"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
textData := "Production API keys must be configured with rate limiting enabled."
graphID := "company-knowledge"
newEpisode, err := client.Graph.Add(context.TODO(), &zep.AddDataRequest{
GraphID: &graphID,
Type: zep.GraphDataTypeText,
Data: textData,
})
if err != nil {
log.Fatalf("Failed to add text data: %v", err)
}
```
### JSON data
Use JSON data for structured business data, REST API responses, or any JSON-formatted records. See our [Adding Data to the Graph](/adding-data-to-the-graph#adding-json-data) guide for details.
```python Python
from zep_cloud.client import Zep
import json
client = Zep(api_key=API_KEY)
# Add JSON data to a graph
json_data = {
"product": {
"id": "prod_123",
"name": "Enterprise Plan",
"features": ["Priority Support", "Custom Integration", "99.9% SLA"],
"price": 299
}
}
new_episode = client.graph.add(
graph_id="product-catalog",
type="json",
data=json.dumps(json_data)
)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Add JSON data to a graph
const jsonData = {
product: {
id: "prod_123",
name: "Enterprise Plan",
features: ["Priority Support", "Custom Integration", "99.9% SLA"],
price: 299
}
};
const newEpisode = await client.graph.add({
graphId: "product-catalog",
type: "json",
data: JSON.stringify(jsonData)
});
```
```go Go
import (
"context"
"encoding/json"
"log"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
// Add JSON data to a graph
type Product struct {
ID string `json:"id"`
Name string `json:"name"`
Features []string `json:"features"`
Price int `json:"price"`
}
jsonData := map[string]Product{
"product": {
ID: "prod_123",
Name: "Enterprise Plan",
Features: []string{"Priority Support", "Custom Integration", "99.9% SLA"},
Price: 299,
},
}
jsonBytes, err := json.Marshal(jsonData)
if err != nil {
log.Fatalf("Failed to marshal JSON: %v", err)
}
graphID := "product-catalog"
newEpisode, err := client.Graph.Add(context.TODO(), &zep.AddDataRequest{
GraphID: &graphID,
Type: zep.GraphDataTypeJSON,
Data: string(jsonBytes),
})
if err != nil {
log.Fatalf("Failed to add JSON data: %v", err)
}
```
## Retrieve Zep context block
After adding data to your knowledge graph and before generating the AI response, you need to construct a custom context block from graph search results. Unlike user-specific context retrieval, knowledge graphs require you to manually search the graph and build the context block.
**Why context block construction?**
Knowledge graphs don't have the concept of threads or conversation history, so you need to explicitly search for relevant information and format it into a context block. This gives you full control over what information is included and how it's structured.
To build a custom context block, you'll:
1. Search the graph for relevant edges (facts) and nodes (entities) using your query
2. Format the search results into a structured context block
3. Include this context block in your agent's prompt
See our [Advanced Context Block Construction](/cookbook/advanced-context-block-construction) guide for complete examples, helper functions, and best practices for building custom context blocks from graph search results.
### Constructed context block example
Here's a simplified example of searching a knowledge graph and building a context block:
```python Python
from zep_cloud.client import Zep
client = Zep(api_key=API_KEY)
# Search for relevant edges (facts) and nodes (entities)
query = "What are the API key configuration requirements?"
edge_results = client.graph.search(
graph_id="company-knowledge",
query=query,
scope="edges",
limit=10
)
node_results = client.graph.search(
graph_id="company-knowledge",
query=query,
scope="nodes",
limit=5
)
# Build context block from results
facts = "\n".join([f" - {edge.fact}" for edge in edge_results.edges])
entities = "\n".join([f" - {node.name}: {node.summary}" for node in node_results.nodes])
context_block = f"""# These are relevant facts from the knowledge base
{facts}
# These are relevant entities from the knowledge base
{entities}
"""
print(context_block)
```
```typescript TypeScript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({
apiKey: API_KEY,
});
// Search for relevant edges (facts) and nodes (entities)
const query = "What are the API key configuration requirements?";
const edgeResults = await client.graph.search({
graphId: "company-knowledge",
query: query,
scope: "edges",
limit: 10
});
const nodeResults = await client.graph.search({
graphId: "company-knowledge",
query: query,
scope: "nodes",
limit: 5
});
// Build context block from results
const facts = edgeResults.edges.map(edge => ` - ${edge.fact}`).join("\n");
const entities = nodeResults.nodes.map(node => ` - ${node.name}: ${node.summary}`).join("\n");
const contextBlock = `# These are relevant facts from the knowledge base
${facts}
# These are relevant entities from the knowledge base
${entities}
`;
console.log(contextBlock);
```
```go Go
import (
"context"
"fmt"
"strings"
"github.com/getzep/zep-go/v3"
zepclient "github.com/getzep/zep-go/v3/client"
"github.com/getzep/zep-go/v3/option"
)
client := zepclient.NewClient(
option.WithAPIKey(apiKey),
)
// Search for relevant edges (facts) and nodes (entities)
query := "What are the API key configuration requirements?"
graphID := "company-knowledge"
limit := 10
edgeResults, err := client.Graph.Search(context.TODO(), &v3.GraphSearchQuery{
GraphID: &graphID,
Query: query,
Scope: v3.GraphSearchScopeEdges.Ptr(),
Limit: &limit,
})
if err != nil {
log.Fatal("Error searching edges:", err)
}
nodeLimit := 5
nodeResults, err := client.Graph.Search(context.TODO(), &v3.GraphSearchQuery{
GraphID: &graphID,
Query: query,
Scope: v3.GraphSearchScopeNodes.Ptr(),
Limit: &nodeLimit,
})
if err != nil {
log.Fatal("Error searching nodes:", err)
}
// Build context block from results
var facts []string
for _, edge := range edgeResults.Edges {
facts = append(facts, fmt.Sprintf(" - %s", edge.Fact))
}
var entities []string
for _, node := range nodeResults.Nodes {
entities = append(entities, fmt.Sprintf(" - %s: %s", node.Name, *node.Summary))
}
contextBlock := fmt.Sprintf(`# These are relevant facts from the knowledge base
%s
# These are relevant entities from the knowledge base
%s
`, strings.Join(facts, "\n"), strings.Join(entities, "\n"))
fmt.Println(contextBlock)
```
For production use cases, refer to the [Advanced Context Block Construction](/cookbook/advanced-context-block-construction) guide, which includes:
* Helper functions for formatting edges and nodes
* Breadth-first search integration for recent context
* Custom entity and edge type filtering
* Temporal validity information handling
* User summary integration
## Add context block to agent context window
Once you've retrieved the Context Block, you can include this string in your agent's context window.
### Option 1: Add context block to system prompt
You can append the context block directly to your system prompt. Note that this means the system prompt dynamically updates on every chat turn.
| MessageType | Content |
| ----------- | ------------------------------------------------------ |
| `System` | Your system prompt
`{Zep context block}` |
| `Assistant` | An assistant message stored in Zep |
| `User` | A user message stored in Zep |
| ... | ... |
| `User` | The latest user message |
### Option 2: Append context block as "context message"
Dynamically updating the system prompt on every chat turn has the downside of preventing [prompt caching](https://platform.openai.com/docs/guides/prompt-caching) with LLM providers. In order to reap the benefits of prompt caching while still adding a new Zep context block in every chat, you can append the context block as a "context message" (technically a tool message) just after the user message in the chat history. On each new chat turn, remove the prior context message and replace it with the new one. This allows everything before the context message to be cached.
| MessageType | Content |
| ----------- | -------------------------------------- |
| `System` | Your system prompt (static, cacheable) |
| `Assistant` | An assistant message stored in Zep |
| `User` | A user message stored in Zep |
| ... | ... |
| `User` | The latest user message |
| `Tool` | `{Zep context block}` |
## Next steps
Now that you've learned how to give your agent knowledge through graph capabilities, you can explore additional features:
* **[Customize graph structure to your domain](/customizing-graph-structure)** - Define custom entity and edge types to structure domain-specific information.
* **[Advanced context block construction](/cookbook/advanced-context-block-construction)** - Learn advanced techniques for building optimized context blocks with helper functions, BFS integration, and custom type filtering.
* **[Searching the graph](/searching-the-graph)** - Explore advanced search capabilities and customizable parameters for querying your knowledge graphs.
# Performance Optimization Guide
> Best practices for optimizing Zep performance in production
This guide covers best practices for optimizing Zep's performance in production environments.
## Reuse the Zep SDK Client
The Zep SDK client maintains an HTTP connection pool that enables connection reuse, significantly reducing latency by avoiding the overhead of establishing new connections. To optimize performance:
* Create a single client instance and reuse it across your application
* Avoid creating new client instances for each request or function
* Consider implementing a client singleton pattern in your application
* For serverless environments, initialize the client outside the handler function
## Optimizing Context Operations
The `thread.add_messages` and `thread.get_user_context` methods are optimized for conversational messages and low-latency retrieval. For optimal performance:
* Use `graph.add` for larger documents, tool outputs, or business data (up to 10,000 characters per call)
* Consider chunking large documents before adding them to the graph
* Remove unnecessary metadata or content before persistence
* For bulk document ingestion, process documents in parallel while respecting rate limits
```python
# Recommended for conversations
zep_client.thread.add_messages(
thread_id="thread_123",
message={
"role": "user",
"name": "Alice",
"content": "What's the weather like today?"
}
)
# Recommended for large documents
await zep_client.graph.add(
data=document_content, # Your chunked document content
user_id=user_id, # Or graph_id
type="text" # Can be "text", "message", or "json"
)
```
### Get the Context Block sooner
You can request the Context Block directly in the response to the `thread.add_messages()` call.
This optimization eliminates the need for a separate `thread.get_user_context()` call.
Read more about our [Context Block](/retrieving-context#zeps-context-block).
In this scenario you can pass in the `return_context=True` flag to the `thread.add_messages()` method.
Zep will perform a user graph search right after persisting the data and return the context relevant to the recently added messages.
```python Python
memory_response = await zep_client.thread.add_messages(
thread_id=thread_id,
messages=messages,
return_context=True
)
context = memory_response.context
```
```typescript TypeScript
const memoryResponse = await zepClient.thread.addMessages(threadId, {
messages: messages,
returnContext: true
});
const context = memoryResponse.context;
```
```go Go
memoryResponse, err := zepClient.Thread.AddMessages(
context.TODO(),
threadId,
&zep.AddThreadMessagesRequest{
Messages: messages,
ReturnContext: zep.Bool(true),
},
)
if err != nil {
// handle error
}
contextBlock := memoryResponse.Context
```
Read more in the
[Thread SDK Reference](/sdk-reference/thread/add-messages)
### Searching the Graph Sooner
Instead of using `thread.get_user_context`, you might want to [search the graph](/searching-the-graph) directly with custom parameters and construct your own [custom context block](/cookbook/advanced-context-block-construction). When doing this, you can search the graph and add data to the graph concurrently.
```python
import asyncio
from zep_cloud.client import AsyncZep
from zep_cloud.types import Message
client = AsyncZep(api_key="your_api_key")
async def add_and_retrieve_from_zep(messages):
# Concatenate message content to create query string
query = " ".join([msg.content for msg in messages])
# Execute all operations concurrently
add_result, edges_result, nodes_result = await asyncio.gather(
client.thread.add_messages(
thread_id=thread_id,
messages=messages
),
client.graph.search(
user_id=user_id,
query=query,
scope="edges"
),
client.graph.search(
user_id=user_id,
query=query,
scope="nodes"
)
)
return add_result, edges_result, nodes_result
```
You would then need to construct a custom context block using the search results. Learn more about [customizing your context block](/cookbook/advanced-context-block-construction).
## Optimizing Search Queries
Zep uses hybrid search combining semantic similarity and BM25 full-text search. For optimal performance:
* Keep your queries concise. Queries are automatically truncated to 8,192 tokens (approximately 32,000 Latin characters)
* Longer queries may not improve search quality and will increase latency
* Consider breaking down complex searches into smaller, focused queries
* Use specific, contextual queries rather than generic ones
Best practices for search:
* Keep search queries concise and specific
* Structure queries to target relevant information
* Use natural language queries for better semantic matching
* Consider the scope of your search (graphs versus user graphs)
```python
# Recommended - concise query
results = await zep_client.graph.search(
user_id=user_id, # Or graph_id
query="project requirements discussion"
)
# Not recommended - overly long query
results = await zep_client.graph.search(
user_id=user_id,
query="very long text with multiple paragraphs..." # Will be truncated
)
```
## Warming the User Cache
Zep has a multi-tier retrieval architecture. The highest tier is a "hot" cache where a user's context retrieval is fastest. After several hours of no activity, a user's data will be moved to a lower tier.
You can hint to Zep that a retrieval may be made soon, allowing Zep to move user data into cache ahead of this retrieval. A good time to do this is when a user logs in to your service or opens your app.
```python Python
# Warm the user's cache when they log in
client.user.warm(user_id=user_id)
```
```typescript TypeScript
// Warm the user's cache when they log in
await client.user.warm(userId);
```
```go Go
// Warm the user's cache when they log in
_, err := client.User.Warm(context.TODO(), userId)
if err != nil {
log.Printf("Error warming user cache: %v", err)
}
```
Read more in the
[User SDK Reference](/sdk-reference/user/warm)
## Summary
* Reuse Zep SDK client instances to optimize connection management
* Use appropriate methods for different types of content (`thread.add_messages` for conversations, `graph.add` for large documents)
* Keep search queries focused and under the token limit for optimal performance
* Warm the user cache when users log in or open your app for faster retrieval
# Adding JSON Best Practices
> Best practices for preparing JSON data for ingestion into Zep
Adding JSON to Zep without adequate preparation can lead to unexpected results. For instance, adding a large JSON without dividing it up can lead to a graph with very few nodes. Below, we go over what type of JSON works best with Zep, and techniques you can use to ensure your JSON fits these criteria.
## Key Criteria
At a high level, ingestion of JSON into Zep works best when these criteria are met:
1. **JSON is not too large**: Large JSON should be divided into pieces, adding each piece separately to Zep.
2. **JSON is not deeply nested**: Deeply nested JSON (more than 3 to 4 levels) should be flattened while preserving information.
3. **JSON is understandable in isolation**: The JSON should include all the information needed to understand the data it represents. This might mean adding descriptions or understandable attribute names where relevant.
4. **JSON represents a unified entity**: The JSON should ideally represent a unified entity, with ID, name, and description fields. Zep treats the JSON as a whole as a "first class entity", creating branching entities off of the main JSON entity from the JSON's attributes.
## JSON that is too large
### JSON with too many attributes
**Recommendation**: Split up the properties among several instances of the object. Each instance should duplicate the `id`, `name`, and `description` fields, or similar fields that tie each chunk to the same object, and then have 3 to 4 additional properties.
### JSON with too many list elements
**Recommendation**: Split up the list into its elements, ensuring you add additional fields to contextualize each element if needed. For instance, if the key of the list is "cars", then you should add a field which indicates that the list item is a car.
### JSON with large strings
**Recommendation**: A very long string might be better added to the graph as unstructured text instead of JSON. You may need to add a sentence or two to contextualize the unstructured text with respect to the rest of the JSON, since they would be added separately. And if it is very long, you would want to employ document chunking methods, such as described by Anthropic [here](https://www.anthropic.com/news/contextual-retrieval).
## JSON that is deeply nested
**Recommendation**: For each deeply nested value In the JSON, create a flattened JSON piece for that value specifically. For instance, if your JSON alternates between dictionaries and lists for 5 to 6 levels with a single value at the bottom, then the flattened version would have an attribute for the value, and an attribute to convey any information from each of the keys from the original JSON.
## JSON that is not understandable in isolation
**Recommendation**: Add descriptions or helpful/interpretable attribute names where relevant.
## JSON that is not a unified entity
**Recommendation**: Add an `id`, `name`, and `description` field to the JSON. Additionally, if the JSON essentially represents two or more objects, split it up.
## Dealing with a combination of the above
**Recommendation**: First, deal with the fact that the JSON is too large and/or too deeply nested by iteratively applying these recommendations (described above) from the top down: splitting up attributes, splitting up lists, flattening deeply nested JSON, splitting out any large text documents. For example, if your JSON has a lot of attributes and one of those attributes is a long list, then you should first split up the JSON by the attributes, and then split up the JSON piece that contains the long list by splitting the list elements.
After applying the iterative transformations, you should have a list of candidate JSON, each of which is not too large or too deeply nested. As the last step, you should ensure that each JSON in the list is understandable in isolation and represents a unified entity by applying the recommendations above.
# Role-Based Access Control
Available to [Enterprise Plan](https://www.getzep.com/pricing) customers only.
## Overview
Role-based access control (RBAC) lets you grant the right level of access to each teammate while keeping sensitive account actions limited to trusted users. RBAC grants permissions through roles, and every member can hold multiple assignments across the account and individual projects.
## Scopes and authorizations
RBAC permissions are evaluated at two scopes:
* **Account scope:** Covers organization-wide settings such as member management, billing, and account-level API keys, along with full access to every project.
* **Project scope:** Grants permissions for a single project, including its data plane, collaborators, and project-specific API keys, without exposing other projects or global settings.
Authorizations are grouped into the following capability areas. These appear in the dashboard when you review role details.
* `account.view.readonly` — View account-level configuration, billing status, and usage.
* `rbac.account.manage` — Create, update, or delete account-scoped role assignments, including promoting additional Account Owners.
* `rbac.project.manage` — Manage project-scoped assignments and project-level resources (API keys, data ingestion, deletion) for the projects a member administers.
## Roles
The role catalog includes account-wide roles and project-scoped roles. Assignments can be combined so that, for example, a teammate can be an Account Admin and a Project Viewer on a sensitive project.
Non-Enterprise plans can assign Account Owner and Account Admin roles. Upgrade to Enterprise to unlock project-level roles and granular account roles (Billing Admin, Account Viewer, Project Creator).
### Account-level roles
| Role | Scope | Intended for | Key authorizations |
| ------------------- | ------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Account Owner** | Account | Founders, security administrators | `account.view.readonly`, `rbac.account.manage`, `rbac.project.manage` Manage billing and plan settings. Create, update, and archive projects. Rotate account and project API keys. Assign or revoke any role, including other Account Owners. |
| **Account Admin** | Account | Day-to-day operators who run projects | `account.view.readonly`, `rbac.project.manage` Create and manage projects and API keys. Ingest or delete memory, documents, and graph data. Assign and revoke project-scoped roles for any project. Cannot remove the last Account Owner or change billing ownership. |
| **Billing Admin** | Account | Finance or procurement partners | `billing.manage` View invoices and update payment details. No access to project data or member management. |
| **Account Viewer** | Account | Compliance and audit reviewers | `account.view.readonly`, `project.data.read`, `apikey.view` Read account details, project metadata, and API keys. Cannot make configuration changes. |
| **Project Creator** | Account | Builders who bootstrap new projects | `project.create` Create new projects from the dashboard. No access to existing projects unless separately assigned. |
### Project-level roles
| Role | Scope | Intended for | Key authorizations |
| ------------------ | ------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Project Admin** | Project | Team leads who manage a single project | `rbac.project.manage` for the assigned project only. Invite or remove project collaborators. Create and rotate project API keys. Ingest and delete project data. |
| **Project Editor** | Project | Data engineers or agents that need write access | Read and write all project data, including memory, documents, and graph content. Use project API keys to ingest or delete data. Cannot assign roles or manage API keys. |
| **Project Viewer** | Project | Analysts, auditors, or embedded stakeholders | View project configuration, usage, threads, documents, and graph content. Run read-only queries and exports. Cannot ingest, delete, or manage API keys. |
## Managing role assignments
* Use the **Settings ▸ Members** page in the Zep Dashboard to add or remove roles. Search for an existing member or invite a new teammate, then assign any combination of account and project roles.
* Filter by project to focus on project-scoped roles, or view the full access matrix to understand overlapping assignments.
* Every member must have at least one Account Owner assigned. Attempts to delete the final Account Owner are rejected.
* The dashboard prevents duplicate assignments for the same member, scope, and project.
* Removing a role hides it from the active list but keeps the history available; you can restore access later by adding the role again.
# Audit Logging
> Track and monitor account activity with enterprise audit logging
Audit Logging is available exclusively on Zep Enterprise plans. [Contact us](https://www.getzep.com/enterprise/) to learn more about Enterprise features.
Audit logging provides a comprehensive record of actions performed by team members through the Zep web dashboard. This feature helps enterprises meet compliance requirements, investigate security incidents, and maintain visibility into team activity.
Audit logs track member actions in the web application only. API requests and SDK calls made by your applications are not included in audit logs.
## What events are logged
Zep automatically captures events across several categories:
| Category | Events |
| ------------------- | ------------------------------------------------------------------------ |
| **Authentication** | Login, logout, session created, failed login attempts |
| **Members** | Invitations sent, members joined, members removed, role changes |
| **API Keys** | Keys created, revoked, or updated |
| **Projects** | Projects created, updated, or deleted |
| **Access Control** | RBAC bindings created or deleted, permission denied events |
| **Data Operations** | Threads, users, and graphs created, deleted, or viewed via the dashboard |
| **Settings** | Account or project settings updated |
## Viewing audit logs
Access audit logs from your account settings:
1. Navigate to **Account Settings** in the Zep dashboard
2. Select **Audit Logs** from the sidebar
3. Browse the chronological list of events
Each log entry displays:
* **Timestamp** - When the event occurred (with relative and absolute time)
* **Actor** - The team member who performed the action
* **Action** - The type of event that occurred
* **Resource** - The affected resource (project, user, thread, etc.)
Click any row to expand it and view additional details including request ID, IP address, and user agent.
## Filtering and searching
Use the filter controls to narrow down audit logs:
* **Time range** - Filter by predefined ranges (1 hour, 24 hours, 7 days, 30 days) or set a custom range
* **Actor** - Filter by the team member who performed the action
* **Action** - Filter by specific event types
* **Resource type** - Filter by the type of resource affected
Filters can be combined to locate specific events. The results update automatically as you apply filters.
## Event details
Each audit event captures:
| Field | Description |
| ---------------- | ---------------------------------------------------------- |
| `timestamp` | When the event occurred (millisecond precision) |
| `actor_email` | Email of the user who performed the action |
| `actor_role` | Role of the actor at the time of the event |
| `principal_type` | Type of actor (typically `member` for team member actions) |
| `action` | The specific action performed |
| `resource_type` | Type of resource affected |
| `resource_id` | Unique identifier of the affected resource |
| `project_uuid` | Project context (if applicable) |
| `request_id` | Unique identifier for the request |
| `client_ip` | IP address of the client |
| `user_agent` | Client user agent string |
## Data retention
Audit logs are retained according to the following schedule:
* **Dashboard access**: 30 days of audit logs are available for viewing and filtering in the web dashboard
* **Cold storage (Enterprise)**: 1 year of audit logs are retained in cold storage for all Enterprise customers
* **Extended retention (HIPAA BAA)**: 7 years of audit logs are retained for Enterprise customers who have signed a HIPAA Business Associate Agreement
Events are available immediately after they occur with no delay.
# HIPAA compliance
> Guidelines for using Zep in HIPAA-compliant applications
When building healthcare applications that handle protected health information (PHI), following proper data handling practices is essential. This guide covers the requirements for using Zep in HIPAA-compliant environments.
## Understanding HIPAA compliance with Zep
The Health Insurance Portability and Accountability Act (HIPAA) sets standards for protecting sensitive patient health information. When using Zep as part of a healthcare application, you must ensure that identifiers used within the system do not expose PHI.
Zep offers Business Associate Agreements (BAAs) for Enterprise customers. A BAA is a contract that establishes the responsibilities of each party regarding PHI and is required when a covered entity works with a service provider that may access PHI.
Zep can sign BAAs for Enterprise customers. [Contact our Enterprise team](https://www.getzep.com/enterprise) to learn more about HIPAA-compliant deployments and BAA options.
## Identifier requirements
User IDs, thread IDs, and graph IDs in Zep must never contain personally identifiable information (PII). This requirement is critical for HIPAA compliance and good security hygiene in general.
### Why this matters
Identifiers are used throughout the system for logging, debugging, and operational purposes. If these identifiers contain PII such as names, email addresses, or medical record numbers, this information could be inadvertently exposed in logs, error messages, or analytics data.
### Required approach
Use opaque, randomly generated identifiers that have no inherent meaning:
```python
import uuid
# Good - opaque identifier with no PII
user_id = str(uuid.uuid4()) # e.g., "550e8400-e29b-41d4-a716-446655440000"
thread_id = str(uuid.uuid4())
graph_id = str(uuid.uuid4())
# Bad - contains PII
user_id = "john.doe@hospital.com" # Contains email
user_id = "patient-12345" # Contains medical record number
thread_id = "session-jane-smith" # Contains name
```
### Identifier requirements
| Identifier type | Requirement |
| --------------- | ---------------------------------------------------------------------------------------------------- |
| User ID | Use UUIDs or internal system identifiers. Do not use email addresses, names, or patient identifiers. |
| Thread ID | Use UUIDs. Do not embed user information or session details that could identify a person. |
| Graph ID | Use UUIDs or descriptive names that do not contain PII. |
## Mapping identifiers
Maintain a secure mapping between your opaque Zep identifiers and your internal user records in your own database:
```python
# In your application database, maintain a mapping
# Your internal patient record links to the opaque Zep user ID
patient_record = {
"internal_patient_id": "MRN-12345",
"name": "Jane Doe",
"zep_user_id": "550e8400-e29b-41d4-a716-446655440000" # Opaque ID
}
# Use the opaque ID when interacting with Zep
zep_client.user.add(user_id=patient_record["zep_user_id"])
```
This approach ensures that even if Zep identifiers are logged or exposed, no PHI is compromised.
## Summary
* Use opaque, randomly generated identifiers (UUIDs) for user IDs, thread IDs, and graph IDs
* Never embed PII such as names, emails, or medical record numbers in identifiers
* Maintain secure mappings between Zep identifiers and internal records in your own systems
* Contact Zep about [Enterprise plans](https://www.getzep.com/enterprise) for BAA options and HIPAA-compliant deployments
# Bring Your Own Key (BYOK)
Early access only. Contact your Zep account team to enable BYOK for your workspace.
Available to [Enterprise Plan](https://www.getzep.com/pricing) customers only.
## Overview
Bring Your Own Key (BYOK) gives you full control over the encryption keys that protect your data at rest in Zep Cloud. Instead of relying on provider-managed keys, you generate and manage a Customer Managed Key (CMK) in your own AWS KMS account. Zep uses that key—under a narrowly scoped, auditable permission—to encrypt and decrypt the data that belongs to your organization.
Key highlights:
* **Customer-controlled encryption:** You can rotate, revoke, or disable your CMK at any time, immediately gating access to your encrypted data.
* **Envelope encryption model:** Zep uses your CMK to derive short-lived data encryption keys (DEKs) for each tenant and storage layer, ensuring strong isolation without adding latency to live requests.
* **Comprehensive auditability:** All KMS usage is logged in your AWS CloudTrail. Zep maintains matching provider-side audit logs for shared visibility and compliance reporting.
* **Separation of duties:** Operational staff cannot access both encrypted data and the keys required to decrypt it. Access requires multi-party approvals and is fully logged.
## Getting started
1. **Provision a CMK in AWS KMS.** Use an AWS account you control and enable automatic rotation if required by your policies.
2. **Configure a minimal KMS policy.** Grant Zep’s BYOK service permissions to generate and decrypt data keys on your behalf. The policy is limited to your tenant scope and can be revoked at any time.
3. **Share the CMK ARN with Zep.** Your account team will coordinate a secure exchange and validate connectivity in a non-production environment before rollout.
4. **Monitor key usage.** Enable CloudTrail logging for your CMK. Zep recommends creating alerts for unusual patterns, such as unexpected decrypt attempts or access from unfamiliar regions.
5. **Roll out to production.** Zep will migrate your tenant to BYOK-backed encryption with no downtime. You retain ongoing control through KMS aliases and policy changes.
## FAQ
**Can Zep access my data in plaintext?**\
Routine operations do not require manual access to plaintext data. Automated services decrypt data within isolated, audited environments. In exceptional cases—such as a customer-approved incident investigation—access is governed by strict separation of duties, multi-party approvals, and comprehensive logging. You retain the ability to disable your CMK, which immediately blocks further decryption.
**What happens if I disable or delete my CMK?**\
All encrypted data becomes unreadable. This is by design: the key is the final arbiter of access. Ensure you have internal procedures for emergency restores before disabling or deleting a key.
**Does BYOK introduce latency?**\
No. Zep caches derived data encryption keys securely in memory, so encryption and decryption happen without additional round trips to AWS KMS during live traffic.
**Can I rotate keys without downtime?**\
Yes. You can enable automatic rotation in AWS KMS. Key versions created through rotation are honored automatically, and data encryption keys are re-wrapped in the background. Disabling the key immediately revokes access.
**Is BYOK applied to every data store?**\
Yes. All persistent storage and backups for your tenant use envelope encryption derived from your CMK. Stateless services process data in memory and never persist plaintext content.
**Where is my data stored?**\
Customer data remains within the AWS regions operated by Zep. Data in motion is encrypted with TLS 1.3, and at rest it is encrypted using keys derived from your CMK.
**How do I audit KMS activity?**\
Review the AWS CloudTrail logs generated in your account. Every encrypt, decrypt, and key management action involving your CMK is recorded. Zep maintains corresponding provider-side logs that can be shared under NDA for compliance reviews.
**Who is responsible for key lifecycle management?**\
You own the CMK, including rotation, revocation, and IAM policy management. Zep monitors for key state changes and will notify your administrators if a key action affects service availability.
# Bring Your Own LLM (BYOM)
Available to [Enterprise Plan](https://www.getzep.com/pricing) customers only.
## Overview
Bring Your Own LLM (BYOM) lets you connect your existing contracts with model providers such as OpenAI, Anthropic, and Google to Zep Cloud. You keep using Zep’s orchestration, memory, and security controls while routing inference through credentials you manage. This approach ensures:
* **Contract continuity:** Apply your negotiated pricing, quotas, and compliance commitments with each LLM vendor.
* **Data governance:** Enforce provider-specific policies for data usage, retention, and residency.
* **Operational flexibility:** Configure the best vendor or model for each project, including fallbacks for high availability.
## Getting started
1. **Collect provider credentials.** Obtain API keys or service accounts for your chosen vendors. Each Zep project can use a different set of credentials, enabling separation between environments.
2. **Add credentials in the Zep dashboard.** Navigate to **Settings ▸ LLM Providers** within a project, select a vendor, and paste the credential. Zep stores the secret securely in an encrypted secrets manager within your project scope.
3. **(Optional) Supply a customer-managed KMS key.** If you require customer-controlled encryption, provide a KMS ARN with `kms:Encrypt`, `kms:Decrypt`, and `kms:DescribeKey` permissions granted to Zep’s runtime roles. Zep validates the key with a test encrypt/decrypt during setup.
4. **Select default and fallback models.** Choose a primary model for the project. Optionally configure fallbacks to maintain continuity if the primary vendor rate limits or experiences an outage.
5. **Monitor usage and quotas.** Use project analytics to track call volume by provider. Configure per-provider rate limits to enforce budget or vendor restrictions.
## FAQ
**Does Zep store our provider keys in its databases?**\
No. Keys are stored securely in an encrypted secrets manager. Values are decrypted in memory only when needed and are never written to Zep databases.
**Can we use different vendors or models per project?**\
Yes. Each project maintains its own provider configuration, including defaults and fallbacks. This is useful for isolating production from staging or testing providers side by side.
**Can we prevent vendors from training on our data?**\
Yes. Use the vendor endpoints and contractual controls that disable data retention or training. Zep routes requests accordingly and sets the necessary flags in each call.
**How is usage billed?**\
You receive invoices from Zep for Zep services only. LLM inference charges come directly from your vendors under your existing contract and pricing.
**What happens if a key is compromised or needs rotation?**\
Add a new credential in the dashboard, mark it as active, then disable the previous one. Requests start using the new credential immediately; no downtime is required.
**How does BYOM affect observability?**\
Requests are tagged by project and provider, so you can attribute usage and costs. Rate limits can be applied per provider to protect budgets and enforce quotas.
# Webhooks
> Receive real-time notifications when events occur in Zep
Webhooks allow your application to receive real-time notifications when specific events occur in Zep, such as when an episode finishes processing or a batch ingestion completes. Instead of polling for changes, Zep pushes event data directly to your server as HTTP POST requests.
Webhooks are only available on certain plans. See the [Zep pricing page](https://www.getzep.com/pricing) for details.
## Why use webhooks
Webhooks enable event-driven architectures where your application reacts immediately to changes:
* **Episode processed notifications:** Trigger downstream processing when new data is added to a graph
* **Batch completion alerts:** Know when large data imports finish so you can start using the data
* **Reduced polling:** Eliminate the need to continuously check for updates
## Setting up webhooks
Webhooks are configured per project within the Webhooks page in the Zep Dashboard sidebar.
### Navigate to webhooks
Open the Webhooks page from the sidebar in the Zep Dashboard.
### Create an endpoint
Add a new endpoint by providing:
* **Endpoint URL:** The HTTPS URL on your server that will receive webhook events
* **Subscribed events:** Select which events you want to receive (e.g., `episode.processed`, `ingest.batch.completed`)
### Save your signing secret
After creating an endpoint, you'll see a signing secret. Copy and securely store this secret—you'll need it to verify that incoming webhooks are genuinely from Zep.
## Available events
| Event | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------- |
| `episode.processed` | Fired when an episode finishes processing and is added to the graph |
| `ingest.batch.completed` | Fired when a batch ingestion operation completes, includes the list of episode UUIDs from the batch |
Additional events will be added in the future.
## Webhook payload schema
Every webhook payload includes the following fields:
| Field | Description |
| ------------- | --------------------------------------------------- |
| `event_name` | The type of event (e.g., `episode.processed`) |
| `account_id` | Your Zep account identifier |
| `project_uid` | The project where the event occurred |
| `graph_id` | The graph identifier\* |
| `user_id` | The user identifier\* |
| `graph_type` | Either `user` or `graph`, indicating the graph type |
\*Only one of `graph_id` or `user_id` will be present in a given payload, depending on the graph type.
## Receiving webhooks
Your webhook endpoint must:
* Accept HTTP POST requests
* Return a `2xx` status code (200-299) within 15 seconds to acknowledge receipt
* Disable CSRF protection for the webhook route if your framework enables it by default
```python Python
from flask import Flask, request
app = Flask(__name__)
@app.route("/webhooks/zep", methods=["POST"])
def handle_webhook():
payload = request.get_data(as_text=True)
headers = request.headers
# Verify the webhook signature (see next section)
# Process the event
event = request.json
event_name = event.get("event_name")
if event_name == "episode.processed":
# Handle episode processed event
pass
elif event_name == "ingest.batch.completed":
# Handle batch completion event
pass
return "", 200
```
```typescript TypeScript
import express from "express";
const app = express();
// Important: Use raw body for signature verification
app.post("/webhooks/zep", express.raw({ type: "application/json" }), (req, res) => {
const payload = req.body.toString();
const headers = req.headers;
// Verify the webhook signature (see next section)
// Process the event
const event = JSON.parse(payload);
const eventName = event.event_name;
if (eventName === "episode.processed") {
// Handle episode processed event
} else if (eventName === "ingest.batch.completed") {
// Handle batch completion event
}
res.status(200).send();
});
```
```go Go
package main
import (
"encoding/json"
"io"
"net/http"
)
func handleWebhook(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading body", http.StatusBadRequest)
return
}
// Verify the webhook signature (see next section)
// Process the event
var event map[string]interface{}
json.Unmarshal(payload, &event)
eventName := event["event_name"].(string)
switch eventName {
case "episode.processed":
// Handle episode processed event
case "ingest.batch.completed":
// Handle batch completion event
}
w.WriteHeader(http.StatusOK)
}
```
## Verifying webhook signatures
Verifying webhook signatures is **highly recommended**. Without verification, attackers could send fake HTTP POST requests to your endpoint, potentially causing your application to process fraudulent data.
Zep signs every webhook with a cryptographic signature using your endpoint's signing secret. Verifying this signature ensures that:
* The webhook genuinely came from Zep
* The payload hasn't been tampered with in transit
### Using the Svix libraries (recommended)
Zep uses [Svix](https://www.svix.com/) to manage webhooks. The easiest way to verify signatures is with the Svix client libraries.
First, install the Svix library:
```bash Python
pip install svix
```
```bash TypeScript
npm install svix
```
```bash Go
go get github.com/svix/svix-webhooks/go
```
Then verify incoming webhooks:
```python Python
from svix.webhooks import Webhook
# Your signing secret from the Zep Dashboard
WEBHOOK_SECRET = "whsec_..."
def verify_webhook(payload: str, headers: dict) -> dict:
wh = Webhook(WEBHOOK_SECRET)
# This will raise an exception if verification fails
return wh.verify(payload, headers)
# In your webhook handler:
@app.route("/webhooks/zep", methods=["POST"])
def handle_webhook():
payload = request.get_data(as_text=True)
headers = {
"svix-id": request.headers.get("svix-id"),
"svix-timestamp": request.headers.get("svix-timestamp"),
"svix-signature": request.headers.get("svix-signature"),
}
try:
event = verify_webhook(payload, headers)
# Process the verified event
return "", 200
except Exception as e:
print(f"Webhook verification failed: {e}")
return "", 400
```
```typescript TypeScript
import { Webhook } from "svix";
// Your signing secret from the Zep Dashboard
const WEBHOOK_SECRET = "whsec_...";
function verifyWebhook(payload: string, headers: Record): any {
const wh = new Webhook(WEBHOOK_SECRET);
// This will throw if verification fails
return wh.verify(payload, headers);
}
// In your webhook handler:
app.post("/webhooks/zep", express.raw({ type: "application/json" }), (req, res) => {
const payload = req.body.toString();
const headers = {
"svix-id": req.headers["svix-id"] as string,
"svix-timestamp": req.headers["svix-timestamp"] as string,
"svix-signature": req.headers["svix-signature"] as string,
};
try {
const event = verifyWebhook(payload, headers);
// Process the verified event
res.status(200).send();
} catch (err) {
console.error("Webhook verification failed:", err);
res.status(400).send();
}
});
```
```go Go
package main
import (
"io"
"net/http"
svix "github.com/svix/svix-webhooks/go"
)
// Your signing secret from the Zep Dashboard
var webhookSecret = "whsec_..."
func handleWebhook(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading body", http.StatusBadRequest)
return
}
wh, err := svix.NewWebhook(webhookSecret)
if err != nil {
http.Error(w, "Error creating webhook verifier", http.StatusInternalServerError)
return
}
err = wh.Verify(payload, r.Header)
if err != nil {
http.Error(w, "Webhook verification failed", http.StatusBadRequest)
return
}
// Process the verified event
w.WriteHeader(http.StatusOK)
}
```
The verification process requires the **raw request body** exactly as received. Many web frameworks automatically parse JSON bodies, which can break signature verification. Make sure to access the raw body before any parsing middleware runs.
### Manual verification
If you prefer not to use the Svix libraries, you can verify signatures manually using HMAC-SHA256.
Every webhook includes three headers for verification:
* `svix-id`: Unique message identifier
* `svix-timestamp`: Unix timestamp (seconds since epoch)
* `svix-signature`: Base64-encoded signatures (may include multiple, comma-separated)
### Construct the signed content
Concatenate the `svix-id`, `svix-timestamp`, and the raw request body, separated by periods:
```
{svix-id}.{svix-timestamp}.{raw-body}
```
### Calculate the expected signature
Use HMAC-SHA256 with your signing secret (base64-decoded, excluding the `whsec_` prefix) to sign the content:
```javascript
const crypto = require('crypto');
const signedContent = `${svixId}.${svixTimestamp}.${rawBody}`;
const secret = "whsec_...";
const secretBytes = Buffer.from(secret.split('_')[1], "base64");
const expectedSignature = crypto
.createHmac('sha256', secretBytes)
.update(signedContent)
.digest('base64');
```
### Compare signatures
The `svix-signature` header may contain multiple signatures prefixed with version numbers (e.g., `v1,abc123`). Remove the version prefix and compare against your calculated signature.
Use constant-time string comparison to prevent timing attacks.
### Validate the timestamp
Compare the `svix-timestamp` against your server's current time. Reject webhooks with timestamps more than 5 minutes old to prevent replay attacks.
For more details on manual verification, see the [Svix documentation on manual verification](https://docs.svix.com/receiving/verifying-payloads/how-manual).
## Managing webhooks
The Webhooks tab in the Zep Dashboard provides several management features:
* **Disable/Enable:** Temporarily stop receiving events without deleting your endpoint configuration
* **Activity logs:** View the history of webhook deliveries and their status
* **Replay messages:** Re-send failed webhooks for debugging or recovery
* **Rate limiting:** Configure throttling to control the rate of incoming webhooks
* **Delete:** Remove an endpoint entirely
Webhook configuration is **project-specific**. Each project has its own set of webhook endpoints and subscriptions. If you have multiple projects, you'll need to configure webhooks separately for each one.
Changes to webhook configuration (including creating, updating, or deleting endpoints) take **5-10 minutes to propagate** and take effect. This delay is due to a caching mechanism that ensures optimal performance across Zep's infrastructure.
## Pricing
Webhook deliveries consume credits. See the [Zep pricing page](https://www.getzep.com/pricing) for costs.
## Best practices
* **Always verify signatures:** Treat unverified webhooks as potentially malicious
* **Respond quickly:** Return a `2xx` response within 15 seconds to avoid timeout retries
* **Process asynchronously:** If handling takes longer than a few seconds, acknowledge receipt immediately and process the event in a background job
* **Handle duplicates:** Webhooks may occasionally be delivered more than once; use the `svix-id` header to deduplicate
* **Monitor failures:** Check the activity logs in the Dashboard to identify and fix delivery issues
## Further reading
For additional information on consuming webhooks:
* [Why verify webhooks](https://docs.svix.com/receiving/verifying-payloads/why) - Security considerations explained
* [Svix webhook verification libraries](https://docs.svix.com/receiving/verifying-payloads/how) - Full library documentation
* [Manual verification guide](https://docs.svix.com/receiving/verifying-payloads/how-manual) - Detailed manual verification steps
# LangGraph Memory Example
> LangGraph is a library created by LangChain for building stateful, multi-agent applications. This example demonstrates using Zep for LangGraph agent memory.
A complete Notebook example of using Zep for LangGraph Memory may be found in the [Zep Python SDK Repository](https://github.com/getzep/zep/blob/main/examples/python/langgraph-agent/agent.ipynb).
The following example demonstrates building an agent using LangGraph. Zep is used to personalize agent responses based on information learned from prior conversations.
The agent implements:
* persistance of new chat turns to Zep and recall of relevant Facts using the most recent messages.
* an in-memory MemorySaver to maintain agent state. We use this to add recent chat history to the agent prompt. As an alternative, you could use Zep for this.
You should consider truncating MemorySaver's chat history as by default LangGraph state grows unbounded. We've included this in our example below. See the LangGraph documentation for insight.
## Install dependencies
```shell
pip install zep-cloud langchain-openai langgraph ipywidgets python-dotenv
```
## Configure Zep
Ensure that you've configured the following API keys in your environment. We're using Zep's Async client here, but we could also use the non-async equivalent.
```bash
ZEP_API_KEY=your_zep_api_key_here
OPENAI_API_KEY=your_openai_api_key_here
```
```python
import os
import uuid
import logging
from typing import Annotated, TypedDict
from zep_cloud.client import AsyncZep
from zep_cloud import Message
from langchain_core.messages import AIMessage, SystemMessage, HumanMessage, trim_messages
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, START, StateGraph, add_messages
from langgraph.prebuilt import ToolNode
# Optional: Load environment variables from .env file
# from dotenv import load_dotenv
# load_dotenv()
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Initialize Zep client
zep = AsyncZep(api_key=os.environ.get('ZEP_API_KEY'))
```
## Define State and Setup Tools
First, define the state structure for our LangGraph agent:
```python
class State(TypedDict):
messages: Annotated[list, add_messages]
first_name: str
last_name: str
thread_id: str
user_name: str
```
## Using Zep's Search as a Tool
These are examples of simple Tools that search Zep for facts (from edges) or nodes. Since LangGraph tools don't automatically receive the full graph state, we create a function that returns configured tools for a specific user:
```python
def create_zep_tools(user_name: str):
"""Create Zep search tools configured for a specific user."""
@tool
async def search_facts(query: str, limit: int = 5) -> list[str]:
"""Search for facts in all conversations had with a user.
Args:
query (str): The search query.
limit (int): The number of results to return. Defaults to 5.
Returns:
list: A list of facts that match the search query.
"""
result = await zep.graph.search(
user_id=user_name, query=query, limit=limit, scope="edges"
)
facts = [edge.fact for edge in result.edges or []]
if not facts:
return ["No facts found for the query."]
return facts
@tool
async def search_nodes(query: str, limit: int = 5) -> list[str]:
"""Search for nodes in all conversations had with a user.
Args:
query (str): The search query.
limit (int): The number of results to return. Defaults to 5.
Returns:
list: A list of node summaries for nodes that match the search query.
"""
result = await zep.graph.search(
user_id=user_name, query=query, limit=limit, scope="nodes"
)
summaries = [node.summary for node in result.nodes or []]
if not summaries:
return ["No nodes found for the query."]
return summaries
return [search_facts, search_nodes]
# We'll create the actual tools after we have a user_name
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
```
## Chatbot Function Explanation
The chatbot uses Zep to provide context-aware responses. Here's how it works:
1. **Context Retrieval**: It retrieves relevant facts for the user's current conversation (thread). Zep uses the most recent messages to determine what facts to retrieve.
2. **System Message**: It constructs a system message incorporating the facts retrieved in 1., setting the context for the AI's response.
3. **Message Persistence**: After generating a response, it asynchronously adds the user and assistant messages to Zep. New Facts are created and existing Facts updated using this new information.
4. **Messages in State**: We use LangGraph state to store the most recent messages and add these to the Agent prompt. We limit the message list to the most recent 3 messages for demonstration purposes.
We could also use Zep to recall the chat history, rather than LangGraph's MemorySaver.
See [`thread.get_user_context`](/sdk-reference/thread/get-user-context) in the Zep SDK documentation.
```python
async def chatbot(state: State):
memory = await zep.thread.get_user_context(state["thread_id"])
system_message = SystemMessage(
content=f"""You are a compassionate mental health bot and caregiver. Review information about the user and their prior conversation below and respond accordingly.
Keep responses empathetic and supportive. And remember, always prioritize the user's well-being and mental health.
{memory.context}"""
)
messages = [system_message] + state["messages"]
response = await llm.ainvoke(messages)
# Add the new chat turn to the Zep graph
messages_to_save = [
Message(
role="user",
name=state["first_name"] + " " + state["last_name"],
content=state["messages"][-1].content,
),
Message(role="assistant", content=response.content),
]
await zep.thread.add_messages(
thread_id=state["thread_id"],
messages=messages_to_save,
)
# Truncate the chat history to keep the state from growing unbounded
# In this example, we going to keep the state small for demonstration purposes
# We'll use Zep's Facts to maintain conversation context
state["messages"] = trim_messages(
state["messages"],
strategy="last",
token_counter=len,
max_tokens=3,
start_on="human",
end_on=("human", "tool"),
include_system=True,
)
logger.info(f"Messages in state: {state['messages']}")
return {"messages": [response]}
```
## Setting up the Agent
This function creates a complete LangGraph agent configured for a specific user. This approach allows us to properly configure the tools with the user context:
```python
def create_agent(user_name: str):
"""Create a LangGraph agent configured for a specific user."""
# Create tools configured for this user
tools = create_zep_tools(user_name)
tool_node = ToolNode(tools)
llm_with_tools = ChatOpenAI(model="gpt-4o-mini", temperature=0).bind_tools(tools)
# Update the chatbot function to use the configured LLM
async def chatbot_with_tools(state: State):
memory = await zep.thread.get_user_context(state["thread_id"])
system_message = SystemMessage(
content=f"""You are a compassionate mental health bot and caregiver. Review information about the user and their prior conversation below and respond accordingly.
Keep responses empathetic and supportive. And remember, always prioritize the user's well-being and mental health.
{memory.context}"""
)
messages = [system_message] + state["messages"]
response = await llm_with_tools.ainvoke(messages)
# Add the new chat turn to the Zep graph
messages_to_save = [
Message(
role="user",
name=state["first_name"] + " " + state["last_name"],
content=state["messages"][-1].content,
),
Message(role="assistant", content=response.content),
]
await zep.thread.add_messages(
thread_id=state["thread_id"],
messages=messages_to_save,
)
# Truncate the chat history to keep the state from growing unbounded
state["messages"] = trim_messages(
state["messages"],
strategy="last",
token_counter=len,
max_tokens=3,
start_on="human",
end_on=("human", "tool"),
include_system=True,
)
logger.info(f"Messages in state: {state['messages']}")
return {"messages": [response]}
# Define the function that determines whether to continue or not
async def should_continue(state, config):
messages = state["messages"]
last_message = messages[-1]
# If there is no function call, then we finish
if not last_message.tool_calls:
return "end"
# Otherwise if there is, we continue
else:
return "continue"
# Build the graph
graph_builder = StateGraph(State)
memory = MemorySaver()
graph_builder.add_node("agent", chatbot_with_tools)
graph_builder.add_node("tools", tool_node)
graph_builder.add_edge(START, "agent")
graph_builder.add_conditional_edges("agent", should_continue, {"continue": "tools", "end": END})
graph_builder.add_edge("tools", "agent")
return graph_builder.compile(checkpointer=memory)
```
Our LangGraph agent graph is illustrated below.

## Running the Agent
We generate a unique user name and thread id, add these to Zep, and create our configured agent:
```python
first_name = "Daniel"
last_name = "Chalef"
user_name = first_name + uuid.uuid4().hex[:4]
thread_id = uuid.uuid4().hex
# Create user and thread in Zep
await zep.user.add(user_id=user_name, first_name=first_name, last_name=last_name)
await zep.thread.create(thread_id=thread_id, user_id=user_name)
# Create the agent configured for this user
graph = create_agent(user_name)
def extract_messages(result, user_name):
output = ""
for message in result["messages"]:
if isinstance(message, AIMessage):
name = "assistant"
else:
name = user_name
output += f"{name}: {message.content}\n"
return output.strip()
async def graph_invoke(
message: str,
first_name: str,
last_name: str,
user_name: str,
thread_id: str,
ai_response_only: bool = True,
):
r = await graph.ainvoke(
{
"messages": [HumanMessage(content=message)],
"first_name": first_name,
"last_name": last_name,
"thread_id": thread_id,
"user_name": user_name,
},
config={"configurable": {"thread_id": thread_id}},
)
if ai_response_only:
return r["messages"][-1].content
else:
return extract_messages(r, user_name)
```
Let's test the agent with a few messages:
```python
r = await graph_invoke(
"Hi there?",
first_name,
last_name,
user_name,
thread_id,
)
print(r)
```
> Hello! How are you feeling today? I'm here to listen and support you.
```python
r = await graph_invoke(
"""
I'm fine. But have been a bit stressful lately. Mostly work related.
But also my dog. I'm worried about her.
""",
first_name,
last_name,
user_name,
thread_id,
)
print(r)
```
> I'm sorry to hear that you've been feeling stressed. Work can be a significant source of pressure, and it sounds like your dog might be adding to that stress as well. If you feel comfortable sharing, what specifically has been causing you stress at work and with your dog? I'm here to help you through it.
## Viewing The Context Value
```python
memory = await zep.thread.get_user_context(thread_id=thread_id)
print(memory.context)
```
The context value will look something like this:
```text
FACTS and ENTITIES represent relevant context to the current conversation.
# These are the most relevant facts and their valid date ranges
# format: FACT (Date range: from - to)
- Daniel99db is worried about his sick dog. (2025-01-24 02:11:54 - present)
- Daniel Chalef is worried about his sick dog. (2025-01-24 02:11:54 - present)
- The assistant asks how the user is feeling. (2025-01-24 02:11:51 - present)
- Daniel99db has been a bit stressful lately due to his dog. (2025-01-24 02:11:53 - present)
- Daniel99db has been a bit stressful lately due to work. (2025-01-24 02:11:53 - present)
- Daniel99db is a user. (2025-01-24 02:11:51 - present)
- user has the id of Daniel99db (2025-01-24 02:11:50 - present)
- user has the name of Daniel Chalef (2025-01-24 02:11:50 - present)
# These are the most relevant entities
# ENTITY_NAME: entity summary
- worried: Daniel Chalef (Daniel99db) is feeling stressed lately, primarily due to work-related issues and concerns about his sick dog, which has made him worried.
- Daniel99db: Daniel99db, or Daniel Chalef, is currently experiencing stress primarily due to work-related issues and concerns about his sick dog. Despite these challenges, he has shown a desire for interaction by initiating conversations, indicating his openness to communication.
- sick: Daniel Chalef, also known as Daniel99db, is feeling stressed lately, primarily due to work-related issues and concerns about his sick dog. He expresses worry about his dog's health.
- Daniel Chalef: Daniel Chalef, also known as Daniel99db, has been experiencing stress recently, primarily related to work issues and concerns about his sick dog. Despite this stress, he has been feeling generally well and has expressed a desire to connect with others, as indicated by his friendly greeting, "Hi there?".
- dog: Daniel99db, also known as Daniel Chalef, mentioned that he has been feeling a bit stressed lately, which is related to both work and his dog.
- work: Daniel Chalef, also known as Daniel99db, has been experiencing stress lately, primarily related to work.
- feeling: The assistant initiates a conversation by asking how the user is feeling today, indicating a willingness to listen and provide support.
```
```python
r = await graph_invoke(
"She ate my shoes which were expensive.",
first_name,
last_name,
user_name,
thread_id,
)
print(r)
```
> That sounds really frustrating, especially when you care so much about your belongings and your dog's health. It's tough when pets get into things they shouldn't, and it can add to your stress. How are you feeling about that situation? Are you able to focus on her health despite the shoe incident?
Let's now test whether the Agent is correctly grounded with facts from the prior conversation.
```python
r = await graph_invoke(
"What are we talking about?",
first_name,
last_name,
user_name,
thread_id,
)
print(r)
```
> We were discussing your concerns about your dog being sick and the situation with her eating your expensive shoes. It sounds like you're dealing with a lot right now, and I want to make sure we're addressing what's on your mind. If there's something else you'd like to talk about or if you want to share more about your dog, I'm here to listen.
Let's go even further back to determine whether context is kept by referencing a user message that is not currently in the Agent State. Zep will retrieve Facts related to the user's job.
```python
r = await graph_invoke(
"What have I said about my job?",
first_name,
last_name,
user_name,
thread_id,
)
print(r)
```
> You've mentioned that you've been feeling a bit stressed lately, primarily due to work-related issues. If you'd like to share more about what's been going on at work or how it's affecting you, I'm here to listen and support you.
# Autogen memory integration
> Add persistent memory to Microsoft Autogen agents using the zep-autogen package.
The `zep-autogen` package provides seamless integration between Zep and Microsoft Autogen agents. Choose between [user-specific conversation memory](/users) or structured [knowledge graph memory](/graph-overview) for intelligent context retrieval.
## Install dependencies
```bash pip
pip install zep-autogen zep-cloud autogen-core autogen-agentchat
```
```bash uv
uv add zep-autogen zep-cloud autogen-core autogen-agentchat
```
```bash poetry
poetry add zep-autogen zep-cloud autogen-core autogen-agentchat
```
## Environment setup
Set your API keys as environment variables:
```bash
export ZEP_API_KEY="your_zep_api_key"
export OPENAI_API_KEY="your_openai_api_key"
```
## Memory types
**User Memory**: Stores conversation history in [user threads](/users) with automatic context injection\
**Knowledge Graph Memory**: Maintains structured knowledge with [custom entity models](/customizing-graph-structure)
## User memory
### Step 1: Setup required imports
```python
import os
import uuid
import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_core.memory import MemoryContent, MemoryMimeType
from zep_cloud.client import AsyncZep
from zep_autogen import ZepUserMemory
```
### Step 2: Initialize client and create user
```python
# Initialize Zep client
zep_client = AsyncZep(api_key=os.environ.get("ZEP_API_KEY"))
user_id = f"user_{uuid.uuid4().hex[:16]}"
thread_id = f"thread_{uuid.uuid4().hex[:16]}"
# Create user (required before using memory)
try:
await zep_client.user.add(
user_id=user_id,
email="alice@example.com",
first_name="Alice"
)
except Exception as e:
print(f"User might already exist: {e}")
# Create thread (required for conversation memory)
try:
await zep_client.thread.create(thread_id=thread_id, user_id=user_id)
except Exception as e:
print(f"Thread creation failed: {e}")
```
### Step 3: Create memory with configuration
```python
# Create user memory with configuration
memory = ZepUserMemory(
client=zep_client,
user_id=user_id,
thread_id=thread_id
)
```
### Step 4: Create agent with memory
```python
# Create agent with Zep memory
agent = AssistantAgent(
name="MemoryAwareAssistant",
model_client=OpenAIChatCompletionClient(
model="gpt-4.1-mini",
api_key=os.environ.get("OPENAI_API_KEY")
),
memory=[memory],
system_message="You are a helpful assistant with persistent memory."
)
```
### Step 5: Store messages and run conversations
```python
# Helper function to store messages with proper metadata
async def add_message(message: str, role: str, name: str = None):
"""Store a message in Zep memory following AutoGen standards."""
metadata = {"type": "message", "role": role}
if name:
metadata["name"] = name
await memory.add(MemoryContent(
content=message,
mime_type=MemoryMimeType.TEXT,
metadata=metadata
))
# Example conversation with memory persistence
user_message = "My name is Alice and I love hiking in the mountains."
print(f"User: {user_message}")
# Store user message
await add_message(user_message, "user", "Alice")
# Run agent - it will automatically retrieve context via update_context()
response = await agent.run(task=user_message)
agent_response = response.messages[-1].content
print(f"Agent: {agent_response}")
# Store agent response
await add_message(agent_response, "assistant")
```
**Automatic Context Injection**: ZepUserMemory automatically injects relevant conversation history and context via the `update_context()` method. The agent receives up to 10 recent messages plus context from Zep's Context Block.
## Knowledge graph memory
### Step 1: Define custom entity models
```python
from zep_autogen.graph_memory import ZepGraphMemory
from zep_cloud.external_clients.ontology import EntityModel, EntityText
from pydantic import Field
# Define entity models using Pydantic
class ProgrammingLanguage(EntityModel):
"""A programming language entity."""
paradigm: EntityText = Field(
description="programming paradigm (e.g., object-oriented, functional)",
default=None
)
use_case: EntityText = Field(
description="primary use cases for this language",
default=None
)
class Framework(EntityModel):
"""A software framework or library."""
language: EntityText = Field(
description="the programming language this framework is built for",
default=None
)
purpose: EntityText = Field(
description="primary purpose of this framework",
default=None
)
```
### Step 2: Setup graph with ontology
```python
from zep_cloud import SearchFilters
# Set ontology first
await zep_client.graph.set_ontology(
entities={
"ProgrammingLanguage": ProgrammingLanguage,
"Framework": Framework,
}
)
# Create graph
graph_id = f"graph_{uuid.uuid4().hex[:16]}"
try:
await zep_client.graph.create(
graph_id=graph_id,
name="Programming Knowledge Graph"
)
print(f"Created graph: {graph_id}")
except Exception as e:
print(f"Graph creation failed: {e}")
```
### Step 3: Initialize graph memory with filters
```python
# Create graph memory with search configuration
graph_memory = ZepGraphMemory(
client=zep_client,
graph_id=graph_id,
search_filters=SearchFilters(
node_labels=["ProgrammingLanguage", "Framework"]
),
facts_limit=20, # Max facts in context injection (default: 20)
entity_limit=5 # Max entities in context injection (default: 5)
)
```
### Step 4: Add data and wait for indexing
```python
# Add structured knowledge
await graph_memory.add(MemoryContent(
content="Python is excellent for data science and AI development",
mime_type=MemoryMimeType.TEXT,
metadata={"type": "data"} # "data" stores in graph, "message" stores as episode
))
# Wait for graph processing (required)
print("Waiting for graph indexing...")
await asyncio.sleep(30) # Allow time for knowledge extraction
**Graph Memory Context Injection**: ZepGraphMemory automatically retrieves the last 2 episodes from the graph and uses their content to query for relevant facts (up to `facts_limit`) and entities (up to `entity_limit`). This context is injected as a system message during agent interactions.
```
### Step 5: Create agent with graph memory
```python
# Create agent with graph memory
agent = AssistantAgent(
name="GraphMemoryAssistant",
model_client=OpenAIChatCompletionClient(model="gpt-4.1-mini"),
memory=[graph_memory],
system_message="You are a technical assistant with programming knowledge."
)
```
## Tools integration
Zep tools allow agents to search and add data directly to memory storage with manual control and structured responses.
**Important**: Tools must be bound to either `graph_id` OR `user_id`, not both. This determines whether they operate on knowledge graphs or user graphs.
### Tool function parameters
**Search Tool Parameters**:
* `query`: str (required) - Search query text
* `limit`: int (optional, default 10) - Maximum results to return
* `scope`: str (optional, default "edges") - Search scope: "edges", "nodes", "episodes"
**Add Tool Parameters**:
* `data`: str (required) - Content to store
* `data_type`: str (optional, default "text") - Data type: "text", "json", "message"
### User graph tools
```python
from zep_autogen import create_search_graph_tool, create_add_graph_data_tool
# Create tools bound to user graph
search_tool = create_search_graph_tool(zep_client, user_id=user_id)
add_tool = create_add_graph_data_tool(zep_client, user_id=user_id)
# Agent with user graph tools
agent = AssistantAgent(
name="UserKnowledgeAssistant",
model_client=OpenAIChatCompletionClient(model="gpt-4.1-mini"),
tools=[search_tool, add_tool],
system_message="You can search and add data to the user's knowledge graph.",
reflect_on_tool_use=True # Enables tool usage reflection
)
```
### Knowledge graph tools
```python
# Create tools bound to knowledge graph
search_tool = create_search_graph_tool(zep_client, graph_id=graph_id)
add_tool = create_add_graph_data_tool(zep_client, graph_id=graph_id)
# Agent with knowledge graph tools
agent = AssistantAgent(
name="KnowledgeGraphAssistant",
model_client=OpenAIChatCompletionClient(model="gpt-4.1-mini"),
tools=[search_tool, add_tool],
system_message="You can search and add data to the knowledge graph.",
reflect_on_tool_use=True
)
```
## Query memory
Both memory types support direct querying with different scope parameters.
### User memory queries
```python
# Query user conversation history
results = await memory.query("What does Alice like?", limit=5)
# Process different result types
for result in results.results:
content = result.content
metadata = result.metadata
if 'edge_name' in metadata:
# Fact/relationship result
print(f"Fact: {content}")
print(f"Relationship: {metadata['edge_name']}")
print(f"Valid: {metadata.get('valid_at', 'N/A')} - {metadata.get('invalid_at', 'present')}")
elif 'node_name' in metadata:
# Entity result
print(f"Entity: {metadata['node_name']}")
print(f"Summary: {content}")
else:
# Episode/message result
print(f"Message: {content}")
print(f"Role: {metadata.get('episode_role', 'unknown')}")
print(f"Source: {metadata.get('source')}\n")
```
### Graph memory queries
```python
# Query knowledge graph with scope control
facts_results = await graph_memory.query(
"Python frameworks",
limit=10,
scope="edges" # "edges" (facts), "nodes" (entities), "episodes" (messages)
)
print(f"Found {len(facts_results.results)} facts about Python frameworks:")
for result in facts_results.results:
print(f"- {result.content}")
entities_results = await graph_memory.query(
"programming languages",
limit=5,
scope="nodes"
)
print(f"\nFound {len(entities_results.results)} programming language entities:")
for result in entities_results.results:
entity_name = result.metadata.get('node_name', 'Unknown')
print(f"- {entity_name}: {result.content}")
```
### Search result structure
```json
{
"content": "fact text",
"metadata": {
"source": "graph" | "user_graph",
"edge_name": "relationship_name",
"edge_attributes": {...},
"created_at": "timestamp",
"valid_at": "timestamp",
"invalid_at": "timestamp",
"expired_at": "timestamp"
}
}
```
```json
{
"content": "entity_name:\n entity_summary",
"metadata": {
"source": "graph" | "user_graph",
"node_name": "entity_name",
"node_attributes": {...},
"created_at": "timestamp"
}
}
```
```json
{
"content": "episode_content",
"metadata": {
"source": "graph" | "user_graph",
"episode_type": "source_type",
"episode_role": "role_type",
"episode_name": "role_name",
"created_at": "timestamp"
}
}
```
## Memory vs tools comparison
**Memory Objects** (ZepUserMemory/ZepGraphMemory):
* Automatic context injection via `update_context()`
* Attached to agent's memory list
* Transparent operation - happens automatically
* Better for consistent memory across interactions
**Function Tools** (search/add tools):
* Manual control - agent decides when to use
* More explicit and observable operations
* Better for specific search/add operations
* Works with AutoGen's tool reflection features
* Provides structured return values
**Note**: Both approaches can be combined - using memory for automatic context and tools for explicit operations.
# LiveKit voice agents
> Add persistent memory to LiveKit voice agents using the zep-livekit package.
The `zep-livekit` package provides seamless integration between Zep and LiveKit voice agents. Choose between [user-specific conversation memory](/users) or structured [knowledge graph memory](/graph-overview) for intelligent context retrieval in real-time voice interactions.
## Install dependencies
```bash pip
pip install zep-livekit zep-cloud "livekit-agents[openai,silero]>=1.0.0"
```
```bash uv
uv add zep-livekit zep-cloud "livekit-agents[openai,silero]>=1.0.0"
```
```bash poetry
poetry add zep-livekit zep-cloud "livekit-agents[openai,silero]>=1.0.0"
```
**Version Requirements**: This integration requires LiveKit Agents v1.0+ (not v0.x). The examples use the current AgentSession API pattern introduced in v1.0.
## Environment setup
Set your API keys as environment variables:
```bash
export ZEP_API_KEY="your_zep_api_key"
export OPENAI_API_KEY="your_openai_api_key"
export LIVEKIT_URL="your_livekit_url"
export LIVEKIT_API_KEY="your_livekit_api_key"
export LIVEKIT_API_SECRET="your_livekit_secret"
```
## Agent types
**ZepUserAgent**: Uses [user threads](/users) for conversation memory with automatic context injection\
**ZepGraphAgent**: Accesses structured knowledge through [custom entity models](/customizing-graph-structure)
## User memory agent
### Step 1: Setup required imports
```python
import asyncio
import logging
import os
from livekit import agents
from livekit.agents import AutoSubscribe, JobContext, WorkerOptions, cli
from livekit.plugins import openai, silero
from zep_cloud.client import AsyncZep
from zep_livekit import ZepUserAgent
```
### Step 2: Initialize Zep client and create user
```python
async def entrypoint(ctx: JobContext):
# Initialize Zep client
zep_client = AsyncZep(api_key=os.environ.get("ZEP_API_KEY"))
# Create unique user and thread IDs
participant_name = ctx.room.remote_participants[0].name or "User"
user_id = f"livekit_{participant_name}_{ctx.room.name}"
thread_id = f"thread_{ctx.room.name}"
# Create user in Zep (if not exists)
try:
await zep_client.user.add(
user_id=user_id,
first_name=participant_name
)
except Exception as e:
logging.info(f"User might already exist: {e}")
# Create thread for conversation memory
try:
await zep_client.thread.create(thread_id=thread_id, user_id=user_id)
except Exception as e:
logging.info(f"Thread might already exist: {e}")
```
### Step 3: Create agent with memory
```python
# Create agent session with components
session = agents.AgentSession(
stt=openai.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=openai.TTS(),
vad=silero.VAD.load(),
)
# Create Zep memory agent with enhanced configuration
zep_agent = ZepUserAgent(
zep_client=zep_client,
user_id=user_id,
thread_id=thread_id,
user_message_name=participant_name,
assistant_message_name="Assistant",
instructions="You are a helpful voice assistant with persistent memory. "
"Remember details from previous conversations and reference them naturally."
)
# Start the session with the agent
await session.start(agent=zep_agent, room=ctx.room)
```
### Step 4: Run the voice assistant
```python
# Voice assistant will now have persistent memory
logging.info("Voice assistant with Zep memory is running")
# Keep the session running
await session.aclose()
```
**Automatic Memory Integration**: ZepUserAgent automatically captures voice conversation turns and injects relevant context from previous conversations, enabling natural continuity across voice sessions.
## ZepUserAgent configuration
The `ZepUserAgent` supports several parameters for customizing memory behavior:
```python
zep_agent = ZepUserAgent(
zep_client=zep_client,
user_id="user_123",
thread_id="thread_456",
user_message_name="Alice", # Name attribution for user messages
assistant_message_name="Assistant", # Name attribution for AI messages
instructions="Custom system instructions for the agent"
)
```
**Parameters:**
* `user_message_name`: Optional name for attributing user messages in Zep memory
* `assistant_message_name`: Optional name for attributing assistant messages in Zep memory
* `instructions`: System instructions that override the default LiveKit agent instructions
## Knowledge graph agent
### Step 1: Define custom entity models
```python
from zep_cloud.external_clients.ontology import EntityModel, EntityText
from pydantic import Field
class Person(EntityModel):
"""A person entity for voice interactions."""
role: EntityText = Field(
description="person's role or profession",
default=None
)
interests: EntityText = Field(
description="topics the person is interested in",
default=None
)
class Topic(EntityModel):
"""A conversation topic or subject."""
category: EntityText = Field(
description="category of the topic",
default=None
)
importance: EntityText = Field(
description="importance level of this topic to the user",
default=None
)
```
### Step 2: Setup graph with ontology
```python
from zep_livekit import ZepGraphAgent
async def setup_graph_agent(ctx: JobContext):
zep_client = AsyncZep(api_key=os.environ.get("ZEP_API_KEY"))
# Set ontology for structured knowledge
await zep_client.graph.set_ontology(
entities={
"Person": Person,
"Topic": Topic,
}
)
# Create knowledge graph
graph_id = f"livekit_graph_{ctx.room.name}"
try:
await zep_client.graph.create(
graph_id=graph_id,
name="LiveKit Voice Knowledge Graph"
)
except Exception as e:
logging.info(f"Graph might already exist: {e}")
```
### Step 3: Create graph memory agent
```python
# Create agent session with components
session = agents.AgentSession(
stt=openai.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=openai.TTS(),
vad=silero.VAD.load(),
)
# Create Zep graph agent
zep_agent = ZepGraphAgent(
zep_client=zep_client,
graph_id=graph_id,
facts_limit=15, # Max facts in context
entity_limit=8, # Max entities in context
search_filters={
"node_labels": ["Person"] # Filter to Person entities only
},
instructions="You are a knowledgeable voice assistant. Use the provided "
"context about entities and facts to give informed responses."
)
# Start the session with the graph agent
await session.start(agent=zep_agent, room=ctx.room)
```
**Search Filters**: The `search_filters` parameter allows you to constrain which entities the agent considers when retrieving context. Use `node_labels` to filter by specific entity types defined in your ontology.
**Graph Memory Context**: ZepGraphAgent automatically extracts structured knowledge from voice conversations and injects relevant facts and entities as context for more intelligent responses.
## Room-based memory isolation
LiveKit rooms provide natural memory isolation boundaries:
```python
# Each room gets its own memory context
room_name = ctx.room.name
user_id = f"livekit_user_{room_name}"
thread_id = f"thread_{room_name}"
graph_id = f"graph_{room_name}"
# Memory is isolated per room/session
zep_agent = ZepUserAgent(
zep_client=zep_client,
user_id=user_id,
thread_id=thread_id,
user_message_name="User",
assistant_message_name="Assistant"
)
```
## Voice-specific considerations
**Turn Management**: Voice conversations have different turn dynamics than text chat. Zep automatically handles:
* Overlapping speech detection
* Turn boundary identification
* Context window management for real-time responses
**Memory Persistence**: Key voice interaction details are preserved:
* Speaker identification
* Conversation topics and themes
* User preferences expressed through voice
* Temporal relationships between topics
## Complete example
```python
import asyncio
import logging
import os
from livekit import agents
from livekit.agents import AutoSubscribe, JobContext, WorkerOptions, cli
from livekit.plugins import openai, silero
from zep_cloud.client import AsyncZep
from zep_livekit import ZepUserAgent
async def entrypoint(ctx: JobContext):
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
# Setup Zep integration
zep_client = AsyncZep(api_key=os.environ.get("ZEP_API_KEY"))
participant_name = ctx.room.remote_participants[0].name or "User"
user_id = f"livekit_{participant_name}_{ctx.room.name}"
thread_id = f"thread_{ctx.room.name}"
# Create user and thread
try:
await zep_client.user.add(user_id=user_id, first_name=participant_name)
await zep_client.thread.create(thread_id=thread_id, user_id=user_id)
except Exception:
pass # Already exists
# Create agent session
session = agents.AgentSession(
stt=openai.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=openai.TTS(),
vad=silero.VAD.load(),
)
# Create voice assistant with Zep memory
zep_agent = ZepUserAgent(
zep_client=zep_client,
user_id=user_id,
thread_id=thread_id,
user_message_name=participant_name,
assistant_message_name="Assistant",
instructions="You are a helpful voice assistant with persistent memory. "
"Remember details from previous conversations."
)
# Start the session with the agent
await session.start(agent=zep_agent, room=ctx.room)
logging.info("Voice assistant with Zep memory is running")
await session.aclose()
if __name__ == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
```
## Learn more
* [LiveKit Agents Documentation](https://docs.livekit.io/agents/)
* [Zep LiveKit Integration Repository](https://github.com/getzep/zep/tree/main/integrations/python/zep_livekit)
* [LiveKit Python SDK](https://github.com/livekit/python-sdks)
# CrewAI integration
> Add persistent context and knowledge graphs to CrewAI agents
CrewAI agents equipped with Zep's context layer can maintain context across conversations, access shared knowledge bases, and make more informed decisions. This integration provides persistent context storage and intelligent knowledge retrieval for your CrewAI workflows.
## Core benefits
* **Persistent Context**: Conversations and knowledge persist across sessions
* **Context-Aware Agents**: Agents automatically retrieve relevant context during execution
* **Dual Storage**: User-specific context and shared organizational knowledge
* **Intelligent Tools**: Search and data addition tools for dynamic knowledge management
## How External Memory Works in CrewAI
External memory in CrewAI operates automatically during crew execution, providing seamless context retrieval and persistence across tasks and executions.
### Automatic Memory Operations
| Operation | When It Happens | What Gets Stored/Retrieved |
| ---------------- | -------------------------- | ---------------------------------------------------------------- |
| Memory Retrieval | At the start of each task | Relevant context based on task description + accumulated context |
| Memory Storage | After each task completion | Task output text + metadata (task description, messages) |
**Key Behaviors:**
* **Automatic Retrieval**: When an agent starts a task, CrewAI automatically queries external memory using the query "\{task.description} \{context}" to find relevant historical context
* **Automatic Storage**: When an agent completes a task, CrewAI automatically saves the task output to external memory (if external memory is configured)
* **Cross-Execution Persistence**: External memory persists between crew runs, enabling agents to learn from previous executions
* **Manual Operations**: Developers can also manually add data to external memory or query it directly using the storage interface
This automatic behavior means that once you configure Zep as your external memory provider, your CrewAI agents will seamlessly build context from past interactions and contribute new learnings without additional code.
## Installation
```bash
pip install zep-crewai
```
Requires Python 3.10+, Zep CrewAI >=1.1.1, CrewAI >=0.186.0, and a Zep Cloud API key. Get your API key from [app.getzep.com](https://app.getzep.com).
Set up your environment variables:
```bash
export ZEP_API_KEY="your-zep-api-key"
```
## Storage types
### User storage
Use `ZepUserStorage` for personal context and conversation history:
```python Python
import os
from zep_cloud.client import Zep
from zep_crewai import ZepUserStorage
from crewai import Agent, Crew, Task
from crewai.memory.external.external_memory import ExternalMemory
# Initialize Zep client
zep_client = Zep(api_key=os.getenv("ZEP_API_KEY"))
# Create user and thread
zep_client.user.add(user_id="alice_123", first_name="Alice")
zep_client.thread.create(user_id="alice_123", thread_id="project_456")
# Set up user storage
user_storage = ZepUserStorage(
client=zep_client,
user_id="alice_123",
thread_id="project_456"
)
# Create crew with user memory
crew = Crew(
agents=[your_agent],
tasks=[your_task],
external_memory=ExternalMemory(storage=user_storage)
)
```
User storage automatically routes data:
* **Messages** (`type: "message"`) → Thread API for conversation context
* **JSON/Text** (`type: "json"` or `type: "text"`) → User Graph for preferences
### Graph storage
Use `ZepGraphStorage` for organizational knowledge that multiple agents can access:
```python Python
from zep_crewai import ZepGraphStorage
# Create a graph first
graph = zep_client.graph.create(
graph_id="company_knowledge",
name="Company Knowledge Graph",
description="Shared organizational knowledge and insights."
)
# Create graph storage for shared knowledge
graph_storage = ZepGraphStorage(
client=zep_client,
graph_id="company_knowledge",
search_filters={"node_labels": ["Technology", "Project"]}
)
# Create crew with graph memory
crew = Crew(
agents=[your_agent],
tasks=[your_task],
external_memory=ExternalMemory(storage=graph_storage)
)
```
## Tool integration
Equip your agents with Zep tools for dynamic knowledge management:
```python Python
from zep_crewai import create_search_tool, create_add_data_tool
# Create tools for user storage
user_search_tool = create_search_tool(zep_client, user_id="alice_123")
user_add_tool = create_add_data_tool(zep_client, user_id="alice_123")
# Create tools for graph storage
graph_search_tool = create_search_tool(zep_client, graph_id="knowledge_base")
graph_add_tool = create_add_data_tool(zep_client, graph_id="knowledge_base")
# Create agent with tools
knowledge_agent = Agent(
role="Knowledge Assistant",
goal="Manage and retrieve information efficiently",
tools=[user_search_tool, graph_add_tool],
backstory="You help maintain and search through relevant knowledge",
llm="gpt-4o-mini"
)
```
**Tool parameters:**
Search tool:
* `query`: Natural language search query
* `limit`: Maximum results (default: 10)
* `scope`: Search scope - "edges", "nodes", "episodes", or "all"
Add data tool:
* `data`: Content to store (text, JSON, or message)
* `data_type`: Explicit type - "text", "json", or "message"
## Advanced patterns
### Structured data with ontologies
Define entity models for better knowledge organization:
```python Python
from pydantic import Field
from zep_cloud.external_clients.ontology import EntityModel, EntityText
class ProjectEntity(EntityModel):
status: EntityText = Field(description="project status")
priority: EntityText = Field(description="priority level")
team_size: EntityText = Field(description="team size")
# Set graph ontology
zep_client.graph.set_ontology(
graph_id="projects",
entities={"Project": ProjectEntity},
edges={}
)
# Use graph with structured entities
graph_storage = ZepGraphStorage(
client=zep_client,
graph_id="projects",
search_filters={"node_labels": ["Project"]},
facts_limit=20,
entity_limit=5
)
```
### Multi-agent with mixed storage
Combine user and graph storage for comprehensive memory:
```python Python
# Personal assistant with user-specific memory
personal_storage = ZepUserStorage(
client=zep_client,
user_id="alice_123",
thread_id="conversation_456"
)
personal_agent = Agent(
role="Personal Assistant",
tools=[create_search_tool(zep_client, user_id="alice_123")],
backstory="You know Alice's preferences and conversation history"
)
# Team coordinator with shared knowledge
team_storage = ZepGraphStorage(
client=zep_client,
graph_id="team_knowledge"
)
team_agent = Agent(
role="Team Coordinator",
tools=[create_search_tool(zep_client, graph_id="team_knowledge")],
backstory="You maintain the team's shared knowledge base"
)
# Create crews with different storage types
personal_crew = Crew(
agents=[personal_agent],
tasks=[personal_task],
external_memory=ExternalMemory(storage=personal_storage)
)
team_crew = Crew(
agents=[team_agent],
tasks=[team_task],
external_memory=ExternalMemory(storage=team_storage)
)
```
### Research and curation workflow
Agents can search existing knowledge and add new discoveries:
```python Python
# Research agent with search capabilities
researcher = Agent(
role="Research Analyst",
goal="Analyze information from multiple knowledge sources",
tools=[
create_search_tool(zep_client, user_id="alice_123"),
create_search_tool(zep_client, graph_id="research_data")
],
backstory="You analyze both personal and organizational data"
)
# Knowledge curator with write access
curator = Agent(
role="Knowledge Curator",
goal="Maintain the organization's knowledge base",
tools=[
create_search_tool(zep_client, graph_id="knowledge_base"),
create_add_data_tool(zep_client, graph_id="knowledge_base")
],
backstory="You maintain and organize company knowledge"
)
# Task that demonstrates search and add workflow
research_task = Task(
description="""Research current trends in AI frameworks:
1. Search existing knowledge about AI frameworks
2. Identify gaps in current knowledge
3. Add new insights to the knowledge base""",
expected_output="Summary of research findings and new knowledge added",
agent=curator
)
```
## Configuration options
### ZepUserStorage parameters
* `client`: Zep client instance (required)
* `user_id`: User identifier (required)
* `thread_id`: Thread identifier (optional, enables conversation context)
* `search_filters`: Filter search results by node labels or attributes
* `facts_limit`: Maximum facts for context (default: 20)
* `entity_limit`: Maximum entities for context (default: 5)
### ZepGraphStorage parameters
* `client`: Zep client instance (required)
* `graph_id`: Graph identifier (required)
* `search_filters`: Filter by node labels (e.g., `{"node_labels": ["Technology"]}`)
* `facts_limit`: Maximum facts for context (default: 20)
* `entity_limit`: Maximum entities for context (default: 5)
## Memory routing
The integration automatically routes different data types to appropriate storage:
```python Python
# Messages go to thread (if thread_id is configured)
external_memory.save(
"How can I help you today?",
metadata={"type": "message", "role": "assistant", "name": "Helper"}
)
# JSON data goes to graph
external_memory.save(
'{"project": "Alpha", "status": "active", "budget": 50000}',
metadata={"type": "json"}
)
# Text data goes to graph
external_memory.save(
"Project Alpha requires Python and React expertise",
metadata={"type": "text"}
)
```
## Complete example
Here's a complete example showing personal assistance with conversation memory:
```python Python
import os
import time
from zep_cloud.client import Zep
from zep_crewai import ZepUserStorage
from crewai import Agent, Crew, Task, Process
from crewai.memory.external.external_memory import ExternalMemory
# Initialize Zep
zep_client = Zep(api_key=os.getenv("ZEP_API_KEY"))
# Set up user and thread
user_id = "alice_123"
thread_id = "project_planning"
zep_client.user.add(user_id=user_id, first_name="Alice")
zep_client.thread.create(user_id=user_id, thread_id=thread_id)
# Configure user storage
user_storage = ZepUserStorage(
client=zep_client,
user_id=user_id,
thread_id=thread_id
)
external_memory = ExternalMemory(storage=user_storage)
# Store conversation context
external_memory.save(
"I need help planning our Q4 product roadmap with focus on mobile features",
metadata={"type": "message", "role": "user", "name": "Alice"}
)
external_memory.save(
'{"budget": 500000, "team_size": 12, "deadline": "Q4 2024"}',
metadata={"type": "json"}
)
# Allow time for indexing
time.sleep(20)
# Create agent with memory
planning_agent = Agent(
role="Product Planning Assistant",
goal="Help create data-driven product roadmaps",
backstory="You understand Alice's preferences and project context",
llm="gpt-4o-mini"
)
planning_task = Task(
description="Create a Q4 roadmap based on Alice's requirements and context",
expected_output="Detailed roadmap with timeline and resource allocation",
agent=planning_agent
)
# Execute with automatic memory retrieval
crew = Crew(
agents=[planning_agent],
tasks=[planning_task],
process=Process.sequential,
external_memory=external_memory
)
result = crew.kickoff()
# Save results back to memory
external_memory.save(
str(result),
metadata={"type": "message", "role": "assistant", "name": "Planning Agent"}
)
```
## Best practices
### Storage selection
* **Use ZepUserStorage** for personal preferences, conversation history, and user-specific context
* **Use ZepGraphStorage** for shared knowledge, organizational data, and collaborative information
### Memory management
* **Set up ontologies** for structured data organization
* **Use search filters** to target specific node types and improve relevance
* **Combine storage types** for comprehensive memory coverage
### Tool usage
* **Bind tools** to specific users or graphs at creation time
* **Use search scope "all"** sparingly as it's more expensive
* **Add data with appropriate types** (message, json, text) for correct routing
* **Limit search results** appropriately to avoid context bloat
## Next steps
* Explore [customizing graph structure](/customizing-graph-structure) for advanced knowledge organization
* Learn about [searching the graph](/searching-the-graph) and how to search the graph
* See [code examples](https://github.com/getzep/zep/tree/main/integrations/python/zep_crewai/examples) for additional patterns
# NVIDIA NeMo Agent Toolkit
> Use Zep for automatic memory in NVIDIA NeMo Agent Toolkit agents.
## What is NeMo Agent Toolkit?
[NVIDIA NeMo Agent Toolkit](https://github.com/NVIDIA/NeMo-Agent-Toolkit) (NAT) is a framework-agnostic library for building AI agents. It uses a configuration-driven approach where you define agents, tools, and workflows in YAML files. NAT works alongside existing frameworks like LangChain and LlamaIndex, adding capabilities like memory and observability without modifying your agent code.
## Zep integration
The Zep integration for NAT uses the **automatic memory wrapper** — a general-purpose wrapper that adds memory capabilities to any NAT agent. Rather than requiring agents to explicitly call memory tools, the wrapper intercepts agent invocations and handles memory operations transparently.
This approach guarantees that all conversations are captured and relevant context is retrieved, regardless of which agent type you use or how the agent is implemented.
## Why use automatic memory
Traditional tool-based memory requires agents to explicitly invoke memory tools, which can be unreliable. The auto memory wrapper provides:
* **Guaranteed capture** of all user messages and agent responses
* **Automatic retrieval** of relevant context before each agent call
* **Zero agent configuration** — memory operations happen transparently
* **Universal compatibility** with any agent type (ReAct, ReWOO, Tool Calling, Reasoning)
## Install dependencies
```bash pip
pip install nvidia-nat-zep-cloud
```
```bash uv
uv add nvidia-nat-zep-cloud
```
```bash poetry
poetry add nvidia-nat-zep-cloud
```
**Package information:**
* **Package**: `nvidia-nat-zep-cloud`
* **Python**: `>=3.11, <3.13`
## Quick start
### Set your API key
```bash
export ZEP_API_KEY="your-zep-api-key"
```
### Configure Zep memory
Create a configuration file that defines the Zep memory backend and wraps your agent with automatic memory:
```yaml
memory:
zep_memory:
_type: nat.plugins.zep_cloud/zep_memory
llm:
nim_llm:
_type: nim
model_name: meta/llama-3.3-70b-instruct
functions:
my_react_agent:
_type: react_agent
llm_name: nim_llm
tool_names: [calculator]
workflow:
_type: auto_memory_agent
inner_agent_name: my_react_agent
memory_name: zep_memory
llm_name: nim_llm
```
This configuration wraps a ReAct agent with automatic memory. Every user message and agent response is captured in Zep, and relevant context is retrieved before each agent call.
## How it works
The auto memory wrapper intercepts agent invocations and handles memory operations in this sequence:
1. **User message received** — incoming message captured
2. **Memory retrieval** — relevant context fetched from Zep and injected as a system message
3. **User message stored** — message saved to Zep's thread memory
4. **Agent invocation** — wrapped agent processes request with memory context
5. **Response stored** — agent response saved to Zep
6. **Response returned** — final response sent to user
The wrapped agent is unaware of memory operations — it simply receives enriched context and produces responses.
## Configuration reference
### Required parameters
| Parameter | Description |
| ------------------ | ----------------------------------------------- |
| `inner_agent_name` | Name of the agent function to wrap |
| `memory_name` | Name of the memory backend (e.g., `zep_memory`) |
| `llm_name` | Name of the LLM for memory operations |
### Optional feature flags
All flags default to `true`:
| Parameter | Description |
| ------------------------------------ | --------------------------------------------- |
| `save_user_messages_to_memory` | Store user messages in Zep |
| `retrieve_memory_for_every_response` | Fetch relevant context before each agent call |
| `save_ai_messages_to_memory` | Store agent responses in Zep |
### Zep-specific parameters
Configure memory retrieval and storage behavior:
```yaml
workflow:
_type: auto_memory_agent
inner_agent_name: my_react_agent
memory_name: zep_memory
llm_name: nim_llm
search_params:
mode: "summary" # "basic" (fast) or "summary" (comprehensive)
top_k: 5 # Number of memory results to retrieve
add_params:
ignore_roles: ["assistant"] # Roles to exclude from graph memory
```
**Search modes:**
* `basic` — fast retrieval, P95 latency under 200ms
* `summary` — comprehensive retrieval including summaries and context
## Multi-tenant memory isolation
Zep automatically isolates memory by user. User IDs are extracted in this priority:
1. **`user_manager.get_id()`** — production with custom auth middleware (recommended)
2. **`X-User-ID` HTTP header** — testing without middleware
3. **`"default_user"`** — fallback for local development
For production deployments, implement a custom `user_manager` that extracts user IDs from your authentication system.
## Full configuration example
```yaml
telemetry:
tracer:
_type: phoenix
llm:
nim_llm:
_type: nim
model_name: meta/llama-3.3-70b-instruct
temperature: 0.0
max_tokens: 1024
memory:
zep_memory:
_type: nat.plugins.zep_cloud/zep_memory
function_groups:
calculator:
- add
- subtract
- multiply
- divide
functions:
my_react_agent:
_type: react_agent
llm_name: nim_llm
tool_names: [calculator]
system_prompt: "You are a helpful assistant with memory capabilities."
workflow:
_type: auto_memory_agent
inner_agent_name: my_react_agent
memory_name: zep_memory
llm_name: nim_llm
# Feature flags
save_user_messages_to_memory: true
retrieve_memory_for_every_response: true
save_ai_messages_to_memory: true
# Zep-specific parameters
search_params:
mode: "summary"
top_k: 5
add_params:
ignore_roles: ["assistant"]
```
## Wrapping different agent types
The auto memory wrapper works with any NeMo agent type:
```yaml
functions:
my_agent:
_type: react_agent
llm_name: nim_llm
tool_names: [calculator, search]
workflow:
_type: auto_memory_agent
inner_agent_name: my_agent
memory_name: zep_memory
llm_name: nim_llm
```
```yaml
functions:
my_agent:
_type: tool_calling_agent
llm_name: nim_llm
tool_names: [web_search]
workflow:
_type: auto_memory_agent
inner_agent_name: my_agent
memory_name: zep_memory
llm_name: nim_llm
```
```yaml
functions:
my_agent:
_type: rewoo_agent
llm_name: nim_llm
tool_names: [calculator]
workflow:
_type: auto_memory_agent
inner_agent_name: my_agent
memory_name: zep_memory
llm_name: nim_llm
```
## Resources
* [NeMo Agent Toolkit GitHub Repository](https://github.com/NVIDIA/NeMo-Agent-Toolkit)
* [PyPI Package](https://pypi.org/project/nvidia-nat-zep-cloud/)
# February 2026 deprecation wave
> Guide to the February 2026 deprecation wave
This page covers four deprecation categories that take effect in February 2026.
## V2 SDK deprecation
The V2 SDK is being deprecated in favor of the V3 SDK. This involves several naming and architectural changes to make the API clearer and more consistent.
### Key changes
**Sessions → Threads**
In V2, you worked with sessions to manage conversation history. In V3, these are now called threads.
**Groups → Standalone Graphs**
V2's groups have been replaced with graphs in V3. The name "groups" was confusing, as these were actually arbitrary knowledge graphs that could hold any kind of knowledge. Using these to hold knowledge/context for a group of users was just one possible use case.
In V3, there are two types of graphs:
* **User graphs**: Automatically created for each user to store their personal knowledge
* **Standalone graphs**: Created explicitly via `graph.create()`, functionally equivalent to V2 group graphs, used as arbitrary knowledge graphs for any purpose
**Message role structure changes**
The message role structure has been updated:
* `role_type` is now called `role`
* `role` is now called `name`
**Removed session features**
The following session-related methods have been removed:
* `session.end` / `sessions.end` - No replacement needed, not necessary to end threads in Zep
* `session.classify` - Feature removed, no replacement
* `session.extract` - Feature removed, use external structured output services
* `session.synthesize_question` - Feature removed, no replacement
* `session.search` / `sessions.search` / `memory.search` - Use the default context block or search the graph directly
### Migration table
| V2 Method/Variable/Term | V3 Method/Variable/Term |
| -------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `memory.get(session_id)` | `thread.get_user_context(thread_id)` |
| `memory.get_session(session_id)` | `thread.get(thread_id, limit=..., cursor=..., lastn=...)` |
| `memory.add_session` | `thread.create` |
| `memory.add` | `thread.add_messages` |
| `memory.delete` | `thread.delete` |
| `memory.list_sessions` | `thread.list_all` |
| `memory.get_session_messages` | `thread.get` |
| `memory.get_session_message(session_id, message_uuid)` | `graph.episode.get(uuid_=message_uuid)` |
| `memory.update_message_metadata(session_id, message_uuid, metadata)` | `thread.message.update(message_uuid, metadata={...})` |
| `memory.end_sessions` | Loop through `thread.delete(thread_id)` for each thread |
| `memory.add_session_facts` | `thread.add_messages()`, `graph.add_fact_triple()`, or `graph.add()` |
| `memory.search_sessions` | `graph.search()` or `thread.get_user_context()` |
| `group.add` | `graph.create` |
| `group.get_all_groups` | `graph.list_all` |
| `group.get` | `graph.get` |
| `group.delete` | `graph.delete` |
| `group.update` | `graph.update` |
| `session` | `thread` |
| `session_id` | `thread_id` |
| `group` | `graph` |
| `group_id` | `graph_id` |
| `role_type` | `role` |
| `role` | `name` |
| `memory.search_memory` | Use `thread.get_user_context()` or `graph.search()` |
| `memory.update_session` | No direct equivalent - thread metadata has been removed |
| `user.get_sessions` | `user.get_threads` |
| `message.get` (by UUID) | `graph.episode.get(uuid_=episode_uuid)` |
| `message.update` | `thread.message.update(message_uuid, metadata={...})` |
| `group.get_edges` | `graph.edge.get_by_graph_id(graph_id)` using standalone graphs |
| `group.get_nodes` | `graph.node.get_by_graph_id(graph_id)` using standalone graphs |
| `group.get_episodes` | `graph.episode.get_by_graph_id(graph_id)` using standalone graphs |
| `graph.episode.get_by_group_id(group_id, ...)` | `graph.episode.get_by_graph_id(graph_id, lastn=...)` |
| `user.get_facts` | Use `thread.get_user_context()` |
| `session.get_facts` | Use `thread.get_user_context()` |
| `group.get_facts` | Use `graph.search()` |
| `fact.get` | `graph.edge.get(uuid_="uuid")` |
| `fact.delete` | `graph.edge.delete(uuid)` |
| V2 Method/Variable/Term | V3 Method/Variable/Term |
| ---------------------------------------------------------------- | ----------------------------------------------------------------- |
| `memory.get(sessionId)` | `thread.getUserContext(threadId)` |
| `memory.getSession(sessionId)` | `thread.get(threadId, { limit: ..., cursor: ..., lastn: ... })` |
| `memory.addSession` | `thread.create` |
| `memory.add` | `thread.addMessages` |
| `memory.delete` | `thread.delete` |
| `memory.listSessions` | `thread.listAll` |
| `memory.getSessionMessages` | `thread.get` |
| `memory.getSessionMessage(sessionId, messageUuid)` | `graph.episode.get(messageUuid)` |
| `memory.updateMessageMetadata(sessionId, messageUuid, metadata)` | `thread.message.update(messageUuid, { metadata: {...} })` |
| `memory.endSessions` | Loop through `thread.delete(threadId)` for each thread |
| `memory.addSessionFacts` | `thread.addMessages()`, `graph.addFactTriple()`, or `graph.add()` |
| `memory.searchSessions` | `graph.search()` or `thread.getUserContext()` |
| `group.add` | `graph.create` |
| `group.getAllGroups` | `graph.listAll` |
| `group.get` | `graph.get` |
| `group.delete` | `graph.delete` |
| `group.update` | `graph.update` |
| `session` | `thread` |
| `sessionId` | `threadId` |
| `group` | `graph` |
| `groupId` | `graphId` |
| `roleType` | `role` |
| `role` | `name` |
| `memory.searchMemory` | Use `thread.getUserContext()` or `graph.search()` |
| `memory.updateSession` | No direct equivalent - thread metadata has been removed |
| `user.getSessions` | `user.getThreads` |
| `message.get` (by UUID) | `graph.episode.get(episodeUuid)` |
| `message.update` | `thread.message.update(messageUuid, { metadata: {...} })` |
| `group.getEdges` | `graph.edge.getByGraphId(graphId)` using standalone graphs |
| `group.getNodes` | `graph.node.getByGraphId(graphId)` using standalone graphs |
| `group.getEpisodes` | `graph.episode.getByGraphId(graphId)` using standalone graphs |
| `graph.episode.getByGroupId(groupId, ...)` | `graph.episode.getByGraphId(graphId, { lastn: ... })` |
| `user.getFacts` | Use `thread.getUserContext()` |
| `session.getFacts` | Use `thread.getUserContext()` |
| `group.getFacts` | Use `graph.search()` |
| `fact.get` | `graph.edge.get({ uuid: "uuid" })` |
| `fact.delete` | `graph.edge.delete(edgeUuid)` |
| V2 Method/Variable/Term | V3 Method/Variable/Term |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `Memory.Get(sessionId)` | `Thread.GetUserContext(threadId, &v3.ThreadGetUserContextRequest{})` |
| `Memory.GetSession(sessionId)` | `Thread.Get(threadId, &v3.ThreadGetRequest{Limit: ..., Cursor: ..., Lastn: ...})` |
| `Memory.AddSession` | `Thread.Create` |
| `Memory.Add` | `Thread.AddMessages` |
| `Memory.Delete` | `Thread.Delete` |
| `Memory.ListSessions` | `Thread.ListAll` |
| `Memory.GetSessionMessages` | `Thread.Get` |
| `Memory.GetSessionMessage(sessionId, messageUUID)` | `Graph.Episode.Get(messageUUID)` |
| `Memory.UpdateMessageMetadata(sessionId, messageUUID, metadata)` | `Thread.Message.Update(messageUUID, &thread.ThreadMessageUpdate{Metadata: ...})` |
| `Memory.EndSessions` | Loop through `Thread.Delete(context.TODO(), threadID)` for each thread |
| `Memory.AddSessionFacts` | `Thread.AddMessages()`, `Graph.AddFactTriple()`, or `Graph.Add()` |
| `Memory.SearchSessions` | `Graph.Search()` or `Thread.GetUserContext()` |
| `Group.Add` | `Graph.Create` |
| `Group.GetAllGroups` | `Graph.ListAll` |
| `Group.Get` | `Graph.Get` |
| `Group.Delete` | `Graph.Delete` |
| `Group.Update` | `Graph.Update` |
| `session` | `thread` |
| `SessionID` | `ThreadID` |
| `group` | `graph` |
| `GroupID` | `GraphID` |
| `RoleType` | `Role` |
| `Role` | `Name` |
| `Memory.SearchMemory` | Use `Thread.GetUserContext()` or `Graph.Search()` |
| `Memory.UpdateSession` | No direct equivalent - thread metadata has been removed |
| `User.GetSessions` | `User.GetThreads` |
| `Message.Get` (by UUID) | `Graph.Episode.Get(episodeUUID)` |
| `Message.Update` | `Thread.Message.Update(messageUUID, metadata)` |
| `Group.GetEdges` | `Graph.Edge.GetByGraphID(graphID)` using standalone graphs |
| `Group.GetNodes` | `Graph.Node.GetByGraphID(graphID)` using standalone graphs |
| `Group.GetEpisodes` | `Graph.Episode.GetByGraphID(graphID)` using standalone graphs |
| `Graph.Episode.GetByGroupID(groupID, ...)` | `Graph.Episode.GetByGraphID(graphID, &graph.EpisodeGetByGraphIDRequest{Lastn: ...})` |
| `User.GetFacts` | Use `Thread.GetUserContext()` |
| `Session.GetFacts` | Use `Thread.GetUserContext()` |
| `Group.GetFacts` | Use `Graph.Search()` |
| `Fact.Get` | `Graph.Edge.Get(edgeUUID)` |
| `Fact.Delete` | `Graph.Edge.Delete(edgeUUID)` |
## Fact rating deprecation
Fact ratings are being deprecated entirely. This includes:
* The `minRating` query parameter
* The `fact_rating_instruction` field on users, sessions, groups, and graphs
* The `min_fact_rating` field in graph search queries
* Methods for retrieving facts directly by rating
### What to use instead
**For customizing what facts are extracted**
Use custom ontology and/or custom user summary instructions to guide fact extraction. These provide more precise control over what information Zep extracts and stores.
**For retrieving relevant facts**
Use the default Zep context block via `getUserContext` / `get_user_context`, or create custom context templates. These methods return the most relevant facts based on semantic similarity, full text search, and graph-based search methods rather than an arbitrary rating threshold. Custom context templates allow filtering to the most relevant custom entity or edge types for your domain. See [Retrieving Context](/retrieving-context) for details.
### Migration table
| Deprecated | Replacement |
| ----------------------------------- | ------------------------------------------------ |
| `minRating` query parameter | Remove - use context block relevance instead |
| `fact_rating_instruction` field | Use custom ontology or user summary instructions |
| `min_fact_rating` in search queries | Remove - rely on default relevance ranking |
## Mode parameter deprecation
The `mode` parameter on `getUserContext` / `get_user_context` is being deprecated. Previously this parameter could be set to "summary" or "basic" to control how context was returned. The summarization logic has been removed in favor of a fast, structured context format.
### What to do
Remove the `mode` parameter from your `getUserContext` calls. The context block now returns a structured format with user summary and structured facts. See [Retrieving Context](/retrieving-context) for details on the new format.
### Migration table
| Deprecated | Replacement |
| ---------------------------------------------------- | ------------------------------------ |
| `thread.get_user_context(thread_id, mode="summary")` | `thread.get_user_context(thread_id)` |
| `thread.get_user_context(thread_id, mode="basic")` | `thread.get_user_context(thread_id)` |
| Deprecated | Replacement |
| ------------------------------------------------------ | --------------------------------- |
| `thread.getUserContext(threadId, { mode: "summary" })` | `thread.getUserContext(threadId)` |
| `thread.getUserContext(threadId, { mode: "basic" })` | `thread.getUserContext(threadId)` |
| Deprecated | Replacement |
| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `Thread.GetUserContext(threadId, &v3.ThreadGetUserContextRequest{Mode: &modeSummary})` | `Thread.GetUserContext(threadId, &v3.ThreadGetUserContextRequest{})` |
| `Thread.GetUserContext(threadId, &v3.ThreadGetUserContextRequest{Mode: &modeBasic})` | `Thread.GetUserContext(threadId, &v3.ThreadGetUserContextRequest{})` |
## Min score parameter deprecation
The `min_score` parameter in graph search queries is being deprecated. This parameter was used to filter search results by a minimum relevance score threshold.
### What to do
Remove the `min_score` parameter from your `graph.search()` calls. Zep returns a re-ranker score with search results that you can use to manually filter results if needed. However, there is no minimum re-ranker score parameter because the interpretation of the re-ranker score can vary dramatically depending on which re-ranker is used. For more information about re-ranker scores, see [Searching the Graph - Reranker Score](/searching-the-graph#reranker-score).
You should rely on the default relevance ranking rather than filtering by an arbitrary score threshold.
### Migration table
| Deprecated | Replacement |
| ----------------------------------------- | ------------------------------------------ |
| `min_score` parameter in `graph.search()` | Remove - rely on default relevance ranking |
# Mem0 Migration
> How to migrate from Mem0 to Zep
Zep is a context engineering platform for AI agents that unifies chat and business data into a dynamic [temporal knowledge graph](/v3/concepts#the-knowledge-graph) for each user. It tracks entities, relationships, and facts as they evolve, enabling you to build prompts with only the most relevant information—reducing hallucinations, improving recall, and lowering LLM costs.
Zep provides high-level APIs like `thread.get_user_context` and deep search with `graph.search`, supports custom entity/edge types, hybrid search, and granular graph updates. Mem0, by comparison, offers basic add/get/search APIs and an optional graph, but lacks built-in data unification, ontology customization, temporal fact management, and fine-grained graph control.
Got lots of data to migrate? [Contact us](mailto:sales@getzep.com) for a discount and increased API limits.
## Zep's context model in one minute
### Unified customer record
* Messages sent via [`thread.add_messages`](/adding-messages) go straight into the user's knowledge graph; business objects (JSON, docs, e-mails, CRM rows) flow in through [`graph.add`](graph/adding-data-to-the-graph.mdx). Zep automatically deduplicates entities and keeps every fact's *valid* and *invalid* dates so you always see the latest truth.
### Domain-depth ontology
* You can define Pydantic-style **[custom entity and edge classes](graph/customizing-graph-structure.mdx)** so the graph speaks your business language (Accounts, Policies, Devices, etc.).
### Temporal facts
* Every edge stores when a fact was created, became valid, was invalidated, and (optionally) expired.
### Hybrid & granular search
* [`graph.search`](graph/searching-the-graph.mdx) supports [hybrid BM25 + semantic queries, graph search](graph/searching-the-graph.mdx), with pluggable rerankers (RRF, MMR, cross-encoder) and can target nodes, edges, episodes, or everything at once.
## How Zep differs from Mem0
| Capability | **Zep** | **Mem0** |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| **Business-data ingestion** | Native via [`graph.add`](graph/adding-data-to-the-graph.mdx) (JSON or text); [business facts merge with user graph](/v3/concepts#business-data-vs-chat-message-data) | No direct ingestion API; business data must be rewritten as "memories" or loaded into external graph store |
| **Knowledge-graph storage** | Built-in [temporal graph](/v3/concepts#managing-changes-in-facts-over-time); zero infra for developers | Optional "Graph Memory" layer that *requires* Neo4j/Memgraph and extra config |
| **Custom ontology** | First-class [entity/edge type system](graph/customizing-graph-structure.mdx) | Not exposed; relies on generic nodes/relationships |
| **Fact life-cycle (valid/invalid)** | [Automatic and queryable](/v3/concepts#managing-changes-in-facts-over-time) | Not documented / not supported |
| **User summary customization** | [User summary instructions](users.mdx#user-summary-instructions) customize entity summaries per user | Not available |
| **Search** | [Hybrid vector + graph search](graph/searching-the-graph.mdx) with multiple rerankers | Vector search with filters; basic Cypher queries if graph layer enabled |
| **Graph CRUD** | Full [node/edge CRUD](graph/deleting-data-from-the-graph.mdx) & [bulk episode ingest](graph/adding-data-to-the-graph.mdx) | Add/Delete memories; no low-level edge ops |
| **Context block** | [Auto-generated, temporal, prompt-ready](/retrieving-context#zeps-context-block) | You assemble snippets manually from `search` output |
| **LLM integration** | Returns [ready-made context](/retrieving-context#zeps-context-block); easily integrates with agentic tools | Returns raw strings you must format |
## SDK support
Zep offers Python, TypeScript, and Go SDKs. See [Installation Instructions](./quickstart.mdx) for more details.
## Migrating your code
### Basic flows
| **What you do in Mem0** | **Do this in Zep** |
| ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client.add(messages, user_id=ID)` → stores conversation snippets | `zep.thread.add_messages(thread_id, messages=[...])` – keeps chat sequence **and** updates graph |
| `client.add("json...", user_id=ID)` (not really supported) | `zep.graph.add(data=, type="json", user_id=user_id)` – drop raw business records right in |
| `client.search(query, user_id=ID)` – vector+filter search | *Easy path*: `zep.thread.get_user_context(thread_id)` returns the `user_context.context` + recent messages *Deep path*: `zep.graph.search(query="...", user_id=user_id, reranker="rrf")` |
| `client.get_all(user_id=ID)` – list memories | `zep.graph.node.get_by_user_id(user_id)` to get all nodes, or `zep.graph.edge.get_by_user_id(user_id)` to get all edges |
| `client.update(memory_id, ...)` / `delete` | `zep.graph.edge.delete(uuid_="edge_uuid")` or `zep.graph.episode.delete(uuid_="episode_uuid")` for granular edits. Facts may not be updated directly; new data automatically invalidates old. |
### Practical tips
* **Thread mapping:** Map Mem0's `user_id` → Zep `user_id`, and create `thread_id` per conversation thread.
* **Business objects:** Convert external records to JSON or text and feed them through `graph.add`; Zep will handle entity linking automatically.
* **Prompting:** Replace your custom "summary builder" with the context block; it already embeds temporal ranges and entity summaries.
* **Summary customization:** Use [user summary instructions](users.mdx#user-summary-instructions) to guide how Zep generates entity summaries for each user, tailoring the context to your specific use case.
* **Search tuning:** Start with the default `rrf` reranker; switch to `mmr`, `node_distance`, `cross_encoder`, or `episode_mentions` when you need speed or precision tweaks.
## Side-by-side SDK cheat-sheet
| **Operation** | Mem0 Method (Python) | Zep Method (Python) | Notes |
| ------------------------ | ------------------------------ | ------------------------------------------------------------------------------------ | --------------------------------------------- |
| Add chat messages | `m.add(messages, user_id=...)` | `zep.thread.add_messages(thread_id, messages)` | Zep expects *ordered* AI + user msgs per turn |
| Add business record | *n/a* (work-around) | `zep.graph.add(data, type, user_id=user_id)` | Direct ingestion of JSON/text |
| Retrieve context | `m.search(query,... )` | `zep.thread.get_user_context(thread_id)` | Zep auto-selects facts; no prompt assembly |
| Semantic / hybrid search | `m.search(query, ...)` | `zep.graph.search(query="...", user_id=user_id, reranker=...)` | Multiple rerankers, node/edge scopes |
| List memories | `m.get_all(user_id)` | `zep.graph.node.get_by_user_id(user_id)` or `zep.graph.edge.get_by_user_id(user_id)` | Get all nodes or edges for a user |
| Update fact | `m.update(id, ...)` | *Not directly supported* - add new data to supersede | Facts are temporal; new data invalidates old |
| Delete fact | `m.delete(id)` | `zep.graph.edge.delete(uuid_="edge_uuid")` | Episode deletion removes associated edges |
| Customize user summaries | *not supported* | `zep.user.add_user_summary_instructions(instructions=[...], user_ids=[user_id])` | Up to 5 custom instructions per user |
## Where to dig deeper
* [**Quickstart**](quickstart.mdx)
* [**Graph Search guide**](graph/searching-the-graph.mdx)
* [**Entity / Edge customization**](graph/customizing-graph-structure.mdx)
* [**User summary instructions**](users.mdx#user-summary-instructions)
* **Graph CRUD**: [Reading from the Graph](graph/reading-data-from-the-graph.mdx) | [Adding to the Graph](graph/adding-data-to-the-graph.mdx) | [Deleting from the Graph](graph/deleting-data-from-the-graph.mdx)
For any questions, ping the Zep Discord or contact your account manager. Happy migrating!
# FAQ
## Is there a free version of Zep Cloud?
Yes - Zep offers a free tier. See [Pricing](https://www.getzep.com/pricing) for more information.
## What is the API URL for Zep Cloud?
The API URL for Zep Cloud is `https://api.getzep.com`. Note that you do not need to specify the API URL when using the Cloud SDKs.
If a service requests the Zep URL, it is possible it's only compatible with the Zep Community Edition service.
## Does Zep Cloud support multiple spoken languages?
We have official multilingual support on our roadmap, enabling the creation of graphs in a user's own language. Currently, graphs are not explicitly created in the user's language. However, Zep should work well today with any language, provided you're using a multilingual LLM and your own prompts explicitly state that responses to the user should be in their language.
## Does Zep handle emojis? Should I convert them to unicode characters before adding them to Zep?
Yes, Zep can handle emojis directly. Send the emojis as-is in unicode strings—there's no need to encode them. In fact, it's preferable that they remain unencoded.
## I can't join my company project, because I have already created an account. What should I do?
You will need to delete your account and then accept the invitation from your company.
## How well does Zep scale?
Zep supports many millions of users per account and retrieval performance is not impacted by dataset size. Retrieving/searching the graph scales in near constant time with the size of the graph. Zep's Metered Billing Plan is subject to rate limits on both API requests and processing concurrency.
## Is there a limit on the number of graphs I can create?
No, there is no limit on the number of graphs you can create.
## Is there a limit on the size of the graph?
No, there is no limit on the size of the graph.
## Can I use Zep to replace RAG over static documents?
Zep can be used for retrieval for static documents just like RAG or GraphRAG, although this is not what Zep was designed for. Zep was designed for dynamic, changing data, which RAG and GraphRAG were not designed to do.
## How does the retrieval work for thread.get\_user\_context under the hood?
`thread.get_user_context` does a `graph.search` on nodes, edges, and episodes using the [MMR reranker](/searching-the-graph#mmr-maximal-marginal-relevance). It uses the most recent message as the search query. In addition, it does a `BFS` on the 4 most recent episodes (so it finds all nodes, edges, and episodes created by the 4 most recent episodes and all nodes and edges 2 connections deep).
All of those search results are then used as candidate results which are reranked by the [MMR reranker](/searching-the-graph#mmr-maximal-marginal-relevance). The MMR reranker will compare each search result with the most recent 4 messages to determine how relevant that result is to the current conversation.
## I am seeing information duplicated between different node summaries. Is this normal?
This is a normal and intended feature of Zep. Node summaries are intended to be standalone summaries of the node, which often means describing the relationships that that node has to other nodes. Those same relationships are likely to appear in the summaries of those other nodes.
## Should I use nodes, edges, or episodes when searching the graph and creating a context string?
You can use any combination of nodes, edges, and episodes. There is not a one size fits all solution, and you will likely need to experiment with different approaches to get the best performance for your use case.
## Where is the data stored? What if my client needs it stored in the EU?
We only offer US data residency currently.
## Can I self host Zep? What happened to Zep Community Edition?
Zep Community Edition, which allows you to host Zep locally, is deprecated and no longer supported. See our [announcement post here](https://blog.getzep.com/announcing-a-new-direction-for-zeps-open-source-strategy/).
The alternatives we offer include:
* [Zep Cloud](https://www.getzep.com/): Our hosted solution
* [Graphiti](https://github.com/getzep/graphiti): The open source knowledge graph that powers Zep Cloud
## How do I get Zep to work with n8n?
The Zep n8n integration is no longer supported. We recommend using Zep's SDKs directly instead, see [here](https://help.getzep.com/quickstart).
## Why aren't my episodes processing?
Sometimes episodes may appear to not be processing when they are actually processing slowly. Typically, episodes process in less than 10 seconds, but occasionally they can take a few minutes. Additionally, if you add multiple episodes to a single graph simultaneously, they must process sequentially, which can take time if there are many episodes.
Please confirm the following:
* Are you adding multiple episodes to a single graph all at once? If so, how many? Multiply the number of episodes you are adding to a single graph by 10 seconds for an average case time estimate, or by a few minutes for a worst case time estimate.
* If the above is the case, within the web app, find the most recently processed episode and then look at the next unprocessed episode. Confirm whether that episode remains unprocessed after waiting at least 3-4 minutes (the worst-case processing time). If you see this episode process after some waiting, then your episodes are processing, it just may take some time.
* If neither of the above applies, reach out to our support team on [Discord](https://discord.com/invite/W8Kw6bsgXQ) and let them know what you are seeing.
## How do I get the playground to work with my own data?
The playground is not meant to work with custom data. Instead the playground showcases Zep's functionality with demo data. In order to create a graph with your own custom data, you need to use the Zep SDKs. See our [Quickstart](/quickstart).
## How do I add messages or manage context for a group chat with multiple people?
In order to add messages for a group chat to a Zep knowledge graph, you need to use the `graph.add` method with `type = message` as opposed to `thread.add_messages` (which uses `graph.add` with `type = message` under the hood). You need to use the `graph.add` method so that you are not associating the chat with a single user. Then, to retrieve context, you need to search the graph and assemble a custom context block ([see cookbook example](/cookbook/advanced-context-block-construction)).
## What's the difference between Zep and Graphiti?
See our detailed comparison: [Zep vs Graphiti](/zep-vs-graphiti)
# Zep vs Graphiti
> Understanding the key differences between Zep and Graphiti
| Aspect | Zep | Graphiti |
| ---------------------------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| **What they are** | Fully managed platform for context engineering | Open-source graph framework |
| **User & conversation management** | Built-in users, threads, and message storage | Build your own |
| **Retrieval & performance** | Pre-configured, production-ready retrieval with sub-200ms performance at scale | Custom implementation required; performance depends on your setup |
| **Developer tools** | Dashboard with graph visualization, debug logs, API logs; SDKs for Python, TypeScript, and Go | Build your own tools |
| **Enterprise features** | SLAs, support, security guarantees | Self-managed |
| **Deployment** | Fully managed or in your cloud | Self-hosted only |
## When to choose which
**Choose Zep** if you want a turnkey, enterprise-grade platform with security, performance, and support baked in.
**Choose Graphiti** if you want a flexible OSS core and you're comfortable building/operating the surrounding system.
# Get threads
GET https://api.getzep.com/api/v2/threads
Returns all threads.
Reference: https://help.getzep.com/sdk-reference/thread/get-threads
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get threads
version: endpoint_thread.getThreads
paths:
/threads:
get:
operationId: get-threads
summary: Get threads
description: Returns all threads.
tags:
- - subpackage_thread
parameters:
- name: page_number
in: query
description: Page number for pagination, starting from 1
required: false
schema:
type: integer
- name: page_size
in: query
description: Number of threads to retrieve per page.
required: false
schema:
type: integer
- name: order_by
in: query
description: >-
Field to order the results by: created_at, updated_at, user_id,
thread_id.
required: false
schema:
type: string
- name: asc
in: query
description: 'Order direction: true for ascending, false for descending.'
required: false
schema:
type: boolean
responses:
'200':
description: List of threads
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.ThreadListResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.Thread:
type: object
properties:
created_at:
type: string
project_uuid:
type: string
thread_id:
type: string
user_id:
type: string
uuid:
type: string
apidata.ThreadListResponse:
type: object
properties:
response_count:
type: integer
threads:
type: array
items:
$ref: '#/components/schemas/apidata.Thread'
total_count:
type: integer
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.thread.list_all(
page_number=1,
page_size=1,
order_by="order_by",
asc=True,
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.thread.listAll({
pageNumber: 1,
pageSize: 1,
orderBy: "order_by",
asc: true
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Thread.ListAll(
context.TODO(),
&v3.ThreadListAllRequest{
PageNumber: v3.Int(
1,
),
PageSize: v3.Int(
1,
),
OrderBy: v3.String(
"order_by",
),
Asc: v3.Bool(
true,
),
},
)
```
# Start a new thread.
POST https://api.getzep.com/api/v2/threads
Content-Type: application/json
Start a new thread.
Reference: https://help.getzep.com/sdk-reference/thread/start-a-new-thread
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Start a new thread.
version: endpoint_thread.startANewThread
paths:
/threads:
post:
operationId: start-a-new-thread
summary: Start a new thread.
description: Start a new thread.
tags:
- - subpackage_thread
parameters: []
responses:
'201':
description: The thread object.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.Thread'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Thread
content:
application/json:
schema:
$ref: '#/components/schemas/models.CreateThreadRequest'
components:
schemas:
models.CreateThreadRequest:
type: object
properties:
thread_id:
type: string
description: The unique identifier of the thread.
user_id:
type: string
description: The unique identifier of the user associated with the thread
required:
- thread_id
- user_id
apidata.Thread:
type: object
properties:
created_at:
type: string
project_uuid:
type: string
thread_id:
type: string
user_id:
type: string
uuid:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.thread.create(
thread_id="thread_id",
user_id="user_id",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.thread.create({
threadId: "thread_id",
userId: "user_id"
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Thread.Create(
context.TODO(),
&v3.CreateThreadRequest{
ThreadID: "thread_id",
UserID: "user_id",
},
)
```
# Delete thread
DELETE https://api.getzep.com/api/v2/threads/{threadId}
Deletes a thread.
Reference: https://help.getzep.com/sdk-reference/thread/delete-thread
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Delete thread
version: endpoint_thread.deleteThread
paths:
/threads/{threadId}:
delete:
operationId: delete-thread
summary: Delete thread
description: Deletes a thread.
tags:
- - subpackage_thread
parameters:
- name: threadId
in: path
description: The ID of the thread for which memory should be deleted.
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.thread.delete(
thread_id="threadId",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.thread.delete("threadId");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Thread.Delete(
context.TODO(),
"threadId",
)
```
# Get user context
GET https://api.getzep.com/api/v2/threads/{threadId}/context
Returns most relevant context from the user graph (including memory from any/all past threads) based on the content of the past few messages of the given thread.
Reference: https://help.getzep.com/sdk-reference/thread/get-user-context
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get user context
version: endpoint_thread.getUserContext
paths:
/threads/{threadId}/context:
get:
operationId: get-user-context
summary: Get user context
description: >-
Returns most relevant context from the user graph (including memory from
any/all past threads) based on the content of the past few messages of
the given thread.
tags:
- - subpackage_thread
parameters:
- name: threadId
in: path
description: The ID of the current thread (for which context is being retrieved).
required: true
schema:
type: string
- name: minRating
in: query
description: >-
Deprecated, this field will be removed in a future release. The
minimum rating by which to filter relevant facts.
required: false
schema:
type: number
format: double
- name: template_id
in: query
description: Optional template ID to use for custom context rendering.
required: false
schema:
type: string
- name: mode
in: query
description: >-
Deprecated, this field will be removed in a future release. Defaults
to summary mode. Use basic for lower latency
required: false
schema:
$ref: '#/components/schemas/ThreadsThreadIdContextGetParametersMode'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.ThreadContextResponse'
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
ThreadsThreadIdContextGetParametersMode:
type: string
enum:
- value: basic
- value: summary
default: summary
apidata.ThreadContextResponse:
type: object
properties:
context:
type: string
description: >-
Context block containing relevant facts, entities, and
messages/episodes from the user graph. Meant to be replaced in the
system prompt on every chat turn.
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.thread.get_user_context(
thread_id="threadId",
min_rating=1.1,
template_id="template_id",
mode="basic",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.thread.getUserContext("threadId", {
minRating: 1.1,
templateId: "template_id",
mode: "basic"
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Thread.GetUserContext(
context.TODO(),
"threadId",
&v3.ThreadGetUserContextRequest{
MinRating: v3.Float64(
1.1,
),
TemplateID: v3.String(
"template_id",
),
Mode: v3.ThreadGetUserContextRequestModeBasic,
},
)
```
# Get messages of a thread
GET https://api.getzep.com/api/v2/threads/{threadId}/messages
Returns messages for a thread.
Reference: https://help.getzep.com/sdk-reference/thread/get-messages-of-a-thread
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get messages of a thread
version: endpoint_thread.getMessagesOfAThread
paths:
/threads/{threadId}/messages:
get:
operationId: get-messages-of-a-thread
summary: Get messages of a thread
description: Returns messages for a thread.
tags:
- - subpackage_thread
parameters:
- name: threadId
in: path
description: Thread ID
required: true
schema:
type: string
- name: limit
in: query
description: Limit the number of results returned
required: false
schema:
type: integer
- name: cursor
in: query
description: Cursor for pagination
required: false
schema:
type: integer
- name: lastn
in: query
description: >-
Number of most recent messages to return (overrides limit and
cursor)
required: false
schema:
type: integer
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.ThreadMessageListResponse'
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
apidata.ThreadMessage:
type: object
properties:
content:
type: string
description: The content of the message.
created_at:
type: string
description: The timestamp of when the message was created.
metadata:
type: object
additionalProperties:
description: Any type
description: The metadata associated with the message.
name:
type: string
description: >-
Customizable name of the sender of the message (e.g., "john",
"sales_agent").
processed:
type: boolean
description: Whether the message has been processed.
role:
$ref: '#/components/schemas/apidata.RoleType'
description: The role of message sender (e.g., "user", "system").
uuid:
type: string
description: The unique identifier of the message.
required:
- content
- role
apidata.ThreadMessageListResponse:
type: object
properties:
messages:
type: array
items:
$ref: '#/components/schemas/apidata.ThreadMessage'
description: A list of message objects.
row_count:
type: integer
description: The number of messages returned.
total_count:
type: integer
description: The total number of messages.
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.thread.get(
thread_id="threadId",
limit=1,
cursor=1,
lastn=1,
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.thread.get("threadId", {
limit: 1,
cursor: 1,
lastn: 1
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Thread.Get(
context.TODO(),
"threadId",
&v3.ThreadGetRequest{
Limit: v3.Int(
1,
),
Cursor: v3.Int(
1,
),
Lastn: v3.Int(
1,
),
},
)
```
# Add messages to a thread
POST https://api.getzep.com/api/v2/threads/{threadId}/messages
Content-Type: application/json
Add messages to a thread.
Reference: https://help.getzep.com/sdk-reference/thread/add-messages-to-a-thread
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Add messages to a thread
version: endpoint_thread.addMessagesToAThread
paths:
/threads/{threadId}/messages:
post:
operationId: add-messages-to-a-thread
summary: Add messages to a thread
description: Add messages to a thread.
tags:
- - subpackage_thread
parameters:
- name: threadId
in: path
description: The ID of the thread to which messages should be added.
required: true
schema:
type: string
responses:
'200':
description: >-
An object, optionally containing user context retrieved for the last
thread message
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.AddThreadMessagesResponse'
'500':
description: Internal Server Error
content: {}
requestBody:
description: An object representing the thread messages to be added.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.AddThreadMessagesRequest'
components:
schemas:
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
apidata.ThreadMessage:
type: object
properties:
content:
type: string
description: The content of the message.
created_at:
type: string
description: The timestamp of when the message was created.
metadata:
type: object
additionalProperties:
description: Any type
description: The metadata associated with the message.
name:
type: string
description: >-
Customizable name of the sender of the message (e.g., "john",
"sales_agent").
processed:
type: boolean
description: Whether the message has been processed.
role:
$ref: '#/components/schemas/apidata.RoleType'
description: The role of message sender (e.g., "user", "system").
uuid:
type: string
description: The unique identifier of the message.
required:
- content
- role
apidata.AddThreadMessagesRequest:
type: object
properties:
ignore_roles:
type: array
items:
$ref: '#/components/schemas/apidata.RoleType'
description: >-
Optional list of role types to ignore when adding messages to graph
memory.
The message itself will still be added, retained and used as context
for messages
that are added to a user's graph.
messages:
type: array
items:
$ref: '#/components/schemas/apidata.ThreadMessage'
description: >-
A list of message objects, where each message contains a role and
content.
return_context:
type: boolean
description: >-
Optionally return context block relevant to the most recent
messages.
required:
- messages
apidata.AddThreadMessagesResponse:
type: object
properties:
context:
type: string
message_uuids:
type: array
items:
type: string
task_id:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Message, Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.thread.add_messages(
thread_id="threadId",
messages=[
Message(
content="content",
role="norole",
)
],
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.thread.addMessages("threadId", {
messages: [{
content: "content",
role: "norole"
}]
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Thread.AddMessages(
context.TODO(),
"threadId",
&v3.AddThreadMessagesRequest{
Messages: []*v3.Message{
&v3.Message{
Content: "content",
Role: v3.RoleTypeNoRole,
},
},
},
)
```
# Add messages to a thread in batch
POST https://api.getzep.com/api/v2/threads/{threadId}/messages-batch
Content-Type: application/json
Add messages to a thread in batch mode. This will process messages concurrently, which is useful for data migrations.
Reference: https://help.getzep.com/sdk-reference/thread/add-messages-to-a-thread-in-batch
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Add messages to a thread in batch
version: endpoint_thread.addMessagesToAThreadInBatch
paths:
/threads/{threadId}/messages-batch:
post:
operationId: add-messages-to-a-thread-in-batch
summary: Add messages to a thread in batch
description: >-
Add messages to a thread in batch mode. This will process messages
concurrently, which is useful for data migrations.
tags:
- - subpackage_thread
parameters:
- name: threadId
in: path
description: The ID of the thread to which messages should be added.
required: true
schema:
type: string
responses:
'200':
description: >-
An object, optionally containing user context retrieved for the last
thread message
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.AddThreadMessagesResponse'
'500':
description: Internal Server Error
content: {}
requestBody:
description: An object representing the thread messages to be added.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.AddThreadMessagesRequest'
components:
schemas:
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
apidata.ThreadMessage:
type: object
properties:
content:
type: string
description: The content of the message.
created_at:
type: string
description: The timestamp of when the message was created.
metadata:
type: object
additionalProperties:
description: Any type
description: The metadata associated with the message.
name:
type: string
description: >-
Customizable name of the sender of the message (e.g., "john",
"sales_agent").
processed:
type: boolean
description: Whether the message has been processed.
role:
$ref: '#/components/schemas/apidata.RoleType'
description: The role of message sender (e.g., "user", "system").
uuid:
type: string
description: The unique identifier of the message.
required:
- content
- role
apidata.AddThreadMessagesRequest:
type: object
properties:
ignore_roles:
type: array
items:
$ref: '#/components/schemas/apidata.RoleType'
description: >-
Optional list of role types to ignore when adding messages to graph
memory.
The message itself will still be added, retained and used as context
for messages
that are added to a user's graph.
messages:
type: array
items:
$ref: '#/components/schemas/apidata.ThreadMessage'
description: >-
A list of message objects, where each message contains a role and
content.
return_context:
type: boolean
description: >-
Optionally return context block relevant to the most recent
messages.
required:
- messages
apidata.AddThreadMessagesResponse:
type: object
properties:
context:
type: string
message_uuids:
type: array
items:
type: string
task_id:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Message, Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.thread.add_messages_batch(
thread_id="threadId",
messages=[
Message(
content="content",
role="norole",
)
],
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.thread.addMessagesBatch("threadId", {
messages: [{
content: "content",
role: "norole"
}]
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Thread.AddMessagesBatch(
context.TODO(),
"threadId",
&v3.AddThreadMessagesRequest{
Messages: []*v3.Message{
&v3.Message{
Content: "content",
Role: v3.RoleTypeNoRole,
},
},
},
)
```
# Updates a message.
PATCH https://api.getzep.com/api/v2/messages/{messageUUID}
Content-Type: application/json
Updates a message.
Reference: https://help.getzep.com/sdk-reference/thread/message/updates-a-message
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Updates a message.
version: endpoint_.updatesAMessage
paths:
/messages/{messageUUID}:
patch:
operationId: updates-a-message
summary: Updates a message.
description: Updates a message.
tags:
- []
parameters:
- name: messageUUID
in: path
description: The UUID of the message.
required: true
schema:
type: string
responses:
'200':
description: The updated message.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.ThreadMessage'
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: The updates.
content:
application/json:
schema:
$ref: '#/components/schemas/models.ThreadMessageUpdate'
components:
schemas:
models.ThreadMessageUpdate:
type: object
properties:
metadata:
type: object
additionalProperties:
description: Any type
required:
- metadata
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
apidata.ThreadMessage:
type: object
properties:
content:
type: string
description: The content of the message.
created_at:
type: string
description: The timestamp of when the message was created.
metadata:
type: object
additionalProperties:
description: Any type
description: The metadata associated with the message.
name:
type: string
description: >-
Customizable name of the sender of the message (e.g., "john",
"sales_agent").
processed:
type: boolean
description: Whether the message has been processed.
role:
$ref: '#/components/schemas/apidata.RoleType'
description: The role of message sender (e.g., "user", "system").
uuid:
type: string
description: The unique identifier of the message.
required:
- content
- role
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.thread.message.update(
message_uuid="messageUUID",
metadata={"key": "value"},
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.thread.message.update("messageUUID", {
metadata: {
"key": "value"
}
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
thread "github.com/getzep/zep-go/v3/thread"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Thread.Message.Update(
context.TODO(),
"messageUUID",
&thread.ThreadMessageUpdate{
Metadata: map[string]interface{}{
"key": "value",
},
},
)
```
# Add User
POST https://api.getzep.com/api/v2/users
Content-Type: application/json
Adds a user.
Reference: https://help.getzep.com/sdk-reference/user/add
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Add User
version: endpoint_user.add
paths:
/users:
post:
operationId: add
summary: Add User
description: Adds a user.
tags:
- - subpackage_user
parameters: []
responses:
'201':
description: The user that was added.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.User'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: The user to add.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.CreateUserRequest'
components:
schemas:
apidata.FactRatingExamples:
type: object
properties:
high:
type: string
low:
type: string
medium:
type: string
apidata.FactRatingInstruction:
type: object
properties:
examples:
$ref: '#/components/schemas/apidata.FactRatingExamples'
description: >-
Examples is a list of examples that demonstrate how facts might be
rated based on your instruction. You should provide
an example of a highly rated example, a low rated example, and a
medium (or in between example). For example, if you are rating
based on relevance to a trip planning application, your examples
might be:
High: "Joe's dream vacation is Bali"
Medium: "Joe has a fear of flying",
Low: "Joe's favorite food is Japanese",
instruction:
type: string
description: >-
A string describing how to rate facts as they apply to your
application. A trip planning application may
use something like "relevancy to planning a trip, the user's
preferences when traveling,
or the user's travel history."
apidata.CreateUserRequest:
type: object
properties:
disable_default_ontology:
type: boolean
description: >-
When true, disables the use of default/fallback ontology for the
user's graph.
email:
type: string
description: The email address of the user.
fact_rating_instruction:
$ref: '#/components/schemas/apidata.FactRatingInstruction'
description: >-
Deprecated: this field will be removed in a future release. Optional
instruction to use for fact rating.
first_name:
type: string
description: The first name of the user.
last_name:
type: string
description: The last name of the user.
metadata:
type: object
additionalProperties:
description: Any type
description: The metadata associated with the user.
user_id:
type: string
description: The unique identifier of the user.
required:
- user_id
models.FactRatingExamples:
type: object
properties:
high:
type: string
low:
type: string
medium:
type: string
models.FactRatingInstruction:
type: object
properties:
examples:
$ref: '#/components/schemas/models.FactRatingExamples'
description: >-
Examples is a list of examples that demonstrate how facts might be
rated based on your instruction. You should provide
an example of a highly rated example, a low rated example, and a
medium (or in between example). For example, if you are rating
based on relevance to a trip planning application, your examples
might be:
High: "Joe's dream vacation is Bali"
Medium: "Joe has a fear of flying",
Low: "Joe's favorite food is Japanese",
instruction:
type: string
description: >-
A string describing how to rate facts as they apply to your
application. A trip planning application may
use something like "relevancy to planning a trip, the user's
preferences when traveling,
or the user's travel history."
apidata.User:
type: object
properties:
created_at:
type: string
deleted_at:
type: string
disable_default_ontology:
type: boolean
email:
type: string
fact_rating_instruction:
$ref: '#/components/schemas/models.FactRatingInstruction'
first_name:
type: string
id:
type: integer
last_name:
type: string
metadata:
type: object
additionalProperties:
description: Any type
description: Deprecated
project_uuid:
type: string
session_count:
type: integer
description: Deprecated
updated_at:
type: string
description: Deprecated
user_id:
type: string
uuid:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.add(
user_id="user_id",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.add({
userId: "user_id"
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.Add(
context.TODO(),
&v3.CreateUserRequest{
UserID: "user_id",
},
)
```
# Get Users
GET https://api.getzep.com/api/v2/users-ordered
Returns all users.
Reference: https://help.getzep.com/sdk-reference/user/list-ordered
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Users
version: endpoint_user.listOrdered
paths:
/users-ordered:
get:
operationId: list-ordered
summary: Get Users
description: Returns all users.
tags:
- - subpackage_user
parameters:
- name: pageNumber
in: query
description: Page number for pagination, starting from 1
required: false
schema:
type: integer
- name: pageSize
in: query
description: Number of users to retrieve per page
required: false
schema:
type: integer
responses:
'200':
description: Successfully retrieved list of users
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.UserListResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
models.FactRatingExamples:
type: object
properties:
high:
type: string
low:
type: string
medium:
type: string
models.FactRatingInstruction:
type: object
properties:
examples:
$ref: '#/components/schemas/models.FactRatingExamples'
description: >-
Examples is a list of examples that demonstrate how facts might be
rated based on your instruction. You should provide
an example of a highly rated example, a low rated example, and a
medium (or in between example). For example, if you are rating
based on relevance to a trip planning application, your examples
might be:
High: "Joe's dream vacation is Bali"
Medium: "Joe has a fear of flying",
Low: "Joe's favorite food is Japanese",
instruction:
type: string
description: >-
A string describing how to rate facts as they apply to your
application. A trip planning application may
use something like "relevancy to planning a trip, the user's
preferences when traveling,
or the user's travel history."
apidata.User:
type: object
properties:
created_at:
type: string
deleted_at:
type: string
disable_default_ontology:
type: boolean
email:
type: string
fact_rating_instruction:
$ref: '#/components/schemas/models.FactRatingInstruction'
first_name:
type: string
id:
type: integer
last_name:
type: string
metadata:
type: object
additionalProperties:
description: Any type
description: Deprecated
project_uuid:
type: string
session_count:
type: integer
description: Deprecated
updated_at:
type: string
description: Deprecated
user_id:
type: string
uuid:
type: string
apidata.UserListResponse:
type: object
properties:
row_count:
type: integer
total_count:
type: integer
users:
type: array
items:
$ref: '#/components/schemas/apidata.User'
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.list_ordered(
page_number=1,
page_size=1,
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.listOrdered({
pageNumber: 1,
pageSize: 1
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.ListOrdered(
context.TODO(),
&v3.UserListOrderedRequest{
PageNumber: v3.Int(
1,
),
PageSize: v3.Int(
1,
),
},
)
```
# Get User
GET https://api.getzep.com/api/v2/users/{userId}
Returns a user.
Reference: https://help.getzep.com/sdk-reference/user/get
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get User
version: endpoint_user.get
paths:
/users/{userId}:
get:
operationId: get
summary: Get User
description: Returns a user.
tags:
- - subpackage_user
parameters:
- name: userId
in: path
description: The user_id of the user to get.
required: true
schema:
type: string
responses:
'200':
description: The user that was retrieved.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.User'
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
models.FactRatingExamples:
type: object
properties:
high:
type: string
low:
type: string
medium:
type: string
models.FactRatingInstruction:
type: object
properties:
examples:
$ref: '#/components/schemas/models.FactRatingExamples'
description: >-
Examples is a list of examples that demonstrate how facts might be
rated based on your instruction. You should provide
an example of a highly rated example, a low rated example, and a
medium (or in between example). For example, if you are rating
based on relevance to a trip planning application, your examples
might be:
High: "Joe's dream vacation is Bali"
Medium: "Joe has a fear of flying",
Low: "Joe's favorite food is Japanese",
instruction:
type: string
description: >-
A string describing how to rate facts as they apply to your
application. A trip planning application may
use something like "relevancy to planning a trip, the user's
preferences when traveling,
or the user's travel history."
apidata.User:
type: object
properties:
created_at:
type: string
deleted_at:
type: string
disable_default_ontology:
type: boolean
email:
type: string
fact_rating_instruction:
$ref: '#/components/schemas/models.FactRatingInstruction'
first_name:
type: string
id:
type: integer
last_name:
type: string
metadata:
type: object
additionalProperties:
description: Any type
description: Deprecated
project_uuid:
type: string
session_count:
type: integer
description: Deprecated
updated_at:
type: string
description: Deprecated
user_id:
type: string
uuid:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.get(
user_id="userId",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.get("userId");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.Get(
context.TODO(),
"userId",
)
```
# Delete User
DELETE https://api.getzep.com/api/v2/users/{userId}
Deletes a user.
Reference: https://help.getzep.com/sdk-reference/user/delete
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Delete User
version: endpoint_user.delete
paths:
/users/{userId}:
delete:
operationId: delete
summary: Delete User
description: Deletes a user.
tags:
- - subpackage_user
parameters:
- name: userId
in: path
description: User ID
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.delete(
user_id="userId",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.delete("userId");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.Delete(
context.TODO(),
"userId",
)
```
# Update User
PATCH https://api.getzep.com/api/v2/users/{userId}
Content-Type: application/json
Updates a user.
Reference: https://help.getzep.com/sdk-reference/user/update
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Update User
version: endpoint_user.update
paths:
/users/{userId}:
patch:
operationId: update
summary: Update User
description: Updates a user.
tags:
- - subpackage_user
parameters:
- name: userId
in: path
description: User ID
required: true
schema:
type: string
responses:
'200':
description: The user that was updated.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.User'
'400':
description: Bad Request
content: {}
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Update User Request
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.UpdateUserRequest'
components:
schemas:
apidata.FactRatingExamples:
type: object
properties:
high:
type: string
low:
type: string
medium:
type: string
apidata.FactRatingInstruction:
type: object
properties:
examples:
$ref: '#/components/schemas/apidata.FactRatingExamples'
description: >-
Examples is a list of examples that demonstrate how facts might be
rated based on your instruction. You should provide
an example of a highly rated example, a low rated example, and a
medium (or in between example). For example, if you are rating
based on relevance to a trip planning application, your examples
might be:
High: "Joe's dream vacation is Bali"
Medium: "Joe has a fear of flying",
Low: "Joe's favorite food is Japanese",
instruction:
type: string
description: >-
A string describing how to rate facts as they apply to your
application. A trip planning application may
use something like "relevancy to planning a trip, the user's
preferences when traveling,
or the user's travel history."
apidata.UpdateUserRequest:
type: object
properties:
disable_default_ontology:
type: boolean
description: >-
When true, disables the use of default/fallback ontology for the
user's graph.
email:
type: string
description: The email address of the user.
fact_rating_instruction:
$ref: '#/components/schemas/apidata.FactRatingInstruction'
description: >-
Deprecated: this field will be removed in a future release. Optional
instruction to use for fact rating.
first_name:
type: string
description: The first name of the user.
last_name:
type: string
description: The last name of the user.
metadata:
type: object
additionalProperties:
description: Any type
description: The metadata to update
models.FactRatingExamples:
type: object
properties:
high:
type: string
low:
type: string
medium:
type: string
models.FactRatingInstruction:
type: object
properties:
examples:
$ref: '#/components/schemas/models.FactRatingExamples'
description: >-
Examples is a list of examples that demonstrate how facts might be
rated based on your instruction. You should provide
an example of a highly rated example, a low rated example, and a
medium (or in between example). For example, if you are rating
based on relevance to a trip planning application, your examples
might be:
High: "Joe's dream vacation is Bali"
Medium: "Joe has a fear of flying",
Low: "Joe's favorite food is Japanese",
instruction:
type: string
description: >-
A string describing how to rate facts as they apply to your
application. A trip planning application may
use something like "relevancy to planning a trip, the user's
preferences when traveling,
or the user's travel history."
apidata.User:
type: object
properties:
created_at:
type: string
deleted_at:
type: string
disable_default_ontology:
type: boolean
email:
type: string
fact_rating_instruction:
$ref: '#/components/schemas/models.FactRatingInstruction'
first_name:
type: string
id:
type: integer
last_name:
type: string
metadata:
type: object
additionalProperties:
description: Any type
description: Deprecated
project_uuid:
type: string
session_count:
type: integer
description: Deprecated
updated_at:
type: string
description: Deprecated
user_id:
type: string
uuid:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.update(
user_id="userId",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.update("userId");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.Update(
context.TODO(),
"userId",
&v3.UpdateUserRequest{},
)
```
# Get User Node
GET https://api.getzep.com/api/v2/users/{userId}/node
Returns a user's node.
Reference: https://help.getzep.com/sdk-reference/user/get-user-node
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get User Node
version: endpoint_user.getUserNode
paths:
/users/{userId}/node:
get:
operationId: get-user-node
summary: Get User Node
description: Returns a user's node.
tags:
- - subpackage_user
parameters:
- name: userId
in: path
description: The user_id of the user to get the node for.
required: true
schema:
type: string
responses:
'200':
description: Response object containing the User node.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.UserNodeResponse'
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
graphiti.EntityNode:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the node. Dependent on node labels
created_at:
type: string
description: Creation time of the node
labels:
type: array
items:
type: string
description: Labels associated with the node
name:
type: string
description: Name of the node
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
summary:
type: string
description: Regional summary of surrounding edges
uuid:
type: string
description: UUID of the node
required:
- created_at
- name
- summary
- uuid
apidata.UserNodeResponse:
type: object
properties:
node:
$ref: '#/components/schemas/graphiti.EntityNode'
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.get_node(
user_id="userId",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.getNode("userId");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.GetNode(
context.TODO(),
"userId",
)
```
# Get User Threads
GET https://api.getzep.com/api/v2/users/{userId}/threads
Returns all threads for a user.
Reference: https://help.getzep.com/sdk-reference/user/get-user-threads
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get User Threads
version: endpoint_user.getUserThreads
paths:
/users/{userId}/threads:
get:
operationId: get-user-threads
summary: Get User Threads
description: Returns all threads for a user.
tags:
- - subpackage_user
parameters:
- name: userId
in: path
description: User ID
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/apidata.Thread'
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.Thread:
type: object
properties:
created_at:
type: string
project_uuid:
type: string
thread_id:
type: string
user_id:
type: string
uuid:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.get_threads(
user_id="userId",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.getThreads("userId");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.GetThreads(
context.TODO(),
"userId",
)
```
# Warm User Cache
GET https://api.getzep.com/api/v2/users/{userId}/warm
Hints Zep to warm a user's graph for low-latency search
Reference: https://help.getzep.com/sdk-reference/user/warm-user-cache
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Warm User Cache
version: endpoint_user.warmUserCache
paths:
/users/{userId}/warm:
get:
operationId: warm-user-cache
summary: Warm User Cache
description: Hints Zep to warm a user's graph for low-latency search
tags:
- - subpackage_user
parameters:
- name: userId
in: path
description: User ID
required: true
schema:
type: string
responses:
'200':
description: Warm hint accepted
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
'404':
description: User or graph not found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.warm(
user_id="userId",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.warm("userId");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.Warm(
context.TODO(),
"userId",
)
```
# List User Instructions
GET https://api.getzep.com/api/v2/user-summary-instructions
Lists all user summary instructions for a project, user.
Reference: https://help.getzep.com/sdk-reference/user/list-user-instructions
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: List User Instructions
version: endpoint_user.listUserInstructions
paths:
/user-summary-instructions:
get:
operationId: list-user-instructions
summary: List User Instructions
description: Lists all user summary instructions for a project, user.
tags:
- - subpackage_user
parameters:
- name: user_id
in: query
description: User ID to get user-specific instructions
required: false
schema:
type: string
responses:
'200':
description: The list of instructions.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.ListUserInstructionsResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.UserInstruction:
type: object
properties:
name:
type: string
text:
type: string
required:
- name
- text
apidata.ListUserInstructionsResponse:
type: object
properties:
instructions:
type: array
items:
$ref: '#/components/schemas/apidata.UserInstruction'
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.list_user_summary_instructions(
user_id="user_id",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.listUserSummaryInstructions({
userId: "user_id"
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.ListUserSummaryInstructions(
context.TODO(),
&v3.UserListUserSummaryInstructionsRequest{
UserID: v3.String(
"user_id",
),
},
)
```
# Add User Instructions
POST https://api.getzep.com/api/v2/user-summary-instructions
Content-Type: application/json
Adds new summary instructions for users graphs without removing existing ones. If user_ids is empty, adds to project-wide default instructions.
Reference: https://help.getzep.com/sdk-reference/user/add-user-instructions
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Add User Instructions
version: endpoint_user.addUserInstructions
paths:
/user-summary-instructions:
post:
operationId: add-user-instructions
summary: Add User Instructions
description: >-
Adds new summary instructions for users graphs without removing existing
ones. If user_ids is empty, adds to project-wide default instructions.
tags:
- - subpackage_user
parameters: []
responses:
'200':
description: Instructions added successfully
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: The instructions to add
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.AddUserInstructionsRequest'
components:
schemas:
apidata.UserInstruction:
type: object
properties:
name:
type: string
text:
type: string
required:
- name
- text
apidata.AddUserInstructionsRequest:
type: object
properties:
instructions:
type: array
items:
$ref: '#/components/schemas/apidata.UserInstruction'
description: Instructions to add to the user summary generation.
user_ids:
type: array
items:
type: string
description: >-
User IDs to add the instructions to. If empty, the instructions are
added to the project-wide default.
required:
- instructions
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud import UserInstruction, Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.add_user_summary_instructions(
instructions=[
UserInstruction(
name="name",
text="text",
)
],
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.addUserSummaryInstructions({
instructions: [{
name: "name",
text: "text"
}]
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.AddUserSummaryInstructions(
context.TODO(),
&v3.AddUserInstructionsRequest{
Instructions: []*v3.UserInstruction{
&v3.UserInstruction{
Name: "name",
Text: "text",
},
},
},
)
```
# Delete User Instructions
DELETE https://api.getzep.com/api/v2/user-summary-instructions
Content-Type: application/json
Deletes user summary/instructions for users or project wide defaults.
Reference: https://help.getzep.com/sdk-reference/user/delete-user-instructions
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Delete User Instructions
version: endpoint_user.deleteUserInstructions
paths:
/user-summary-instructions:
delete:
operationId: delete-user-instructions
summary: Delete User Instructions
description: Deletes user summary/instructions for users or project wide defaults.
tags:
- - subpackage_user
parameters: []
responses:
'200':
description: Instructions deleted successfully
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: The instructions to delete
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.DeleteUserInstructionsRequest'
components:
schemas:
apidata.DeleteUserInstructionsRequest:
type: object
properties:
instruction_names:
type: array
items:
type: string
description: >-
Unique identifier for the instructions to be deleted. If empty
deletes all instructions.
user_ids:
type: array
items:
type: string
description: >-
Determines which users will have their custom instructions deleted.
If no users are provided, the project-wide custom instructions will
be effected.
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.user.delete_user_summary_instructions()
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.user.deleteUserSummaryInstructions();
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.User.DeleteUserSummaryInstructions(
context.TODO(),
&v3.DeleteUserInstructionsRequest{},
)
```
# Create Context Template
POST https://api.getzep.com/api/v2/context-templates
Content-Type: application/json
Creates a new context template.
Reference: https://help.getzep.com/sdk-reference/context/create-context-template
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Create Context Template
version: endpoint_context.createContextTemplate
paths:
/context-templates:
post:
operationId: create-context-template
summary: Create Context Template
description: Creates a new context template.
tags:
- - subpackage_context
parameters: []
responses:
'200':
description: The created context template.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.ContextTemplateResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: The context template to create
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.CreateContextTemplateRequest'
components:
schemas:
apidata.CreateContextTemplateRequest:
type: object
properties:
template:
type: string
description: The template content (max 1200 characters).
template_id:
type: string
description: Unique identifier for the template (max 100 characters).
required:
- template
- template_id
apidata.ContextTemplateResponse:
type: object
properties:
template:
type: string
description: The template content.
template_id:
type: string
description: Unique identifier for the template (max 100 characters).
uuid:
type: string
description: Unique identifier for the template.
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.context.create_context_template(
template="template",
template_id="template_id",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.context.createContextTemplate({
template: "template",
templateId: "template_id"
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Context.CreateContextTemplate(
context.TODO(),
&v3.CreateContextTemplateRequest{
Template: "template",
TemplateID: "template_id",
},
)
```
# Get Context Template
GET https://api.getzep.com/api/v2/context-templates/{template_id}
Retrieves a context template by template_id.
Reference: https://help.getzep.com/sdk-reference/context/get-context-template
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Context Template
version: endpoint_context.getContextTemplate
paths:
/context-templates/{template_id}:
get:
operationId: get-context-template
summary: Get Context Template
description: Retrieves a context template by template_id.
tags:
- - subpackage_context
parameters:
- name: template_id
in: path
description: Template ID
required: true
schema:
type: string
responses:
'200':
description: The context template.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.ContextTemplateResponse'
'400':
description: Bad Request
content: {}
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.ContextTemplateResponse:
type: object
properties:
template:
type: string
description: The template content.
template_id:
type: string
description: Unique identifier for the template (max 100 characters).
uuid:
type: string
description: Unique identifier for the template.
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.context.get_context_template(
template_id="template_id",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.context.getContextTemplate("template_id");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Context.GetContextTemplate(
context.TODO(),
"template_id",
)
```
# Update Context Template
PUT https://api.getzep.com/api/v2/context-templates/{template_id}
Content-Type: application/json
Updates an existing context template by template_id.
Reference: https://help.getzep.com/sdk-reference/context/update-context-template
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Update Context Template
version: endpoint_context.updateContextTemplate
paths:
/context-templates/{template_id}:
put:
operationId: update-context-template
summary: Update Context Template
description: Updates an existing context template by template_id.
tags:
- - subpackage_context
parameters:
- name: template_id
in: path
description: Template ID
required: true
schema:
type: string
responses:
'200':
description: The updated context template.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.ContextTemplateResponse'
'400':
description: Bad Request
content: {}
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: The updated template content
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.UpdateContextTemplateRequest'
components:
schemas:
apidata.UpdateContextTemplateRequest:
type: object
properties:
template:
type: string
description: The template content (max 1200 characters).
required:
- template
apidata.ContextTemplateResponse:
type: object
properties:
template:
type: string
description: The template content.
template_id:
type: string
description: Unique identifier for the template (max 100 characters).
uuid:
type: string
description: Unique identifier for the template.
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.context.update_context_template(
template_id="template_id",
template="template",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.context.updateContextTemplate("template_id", {
template: "template"
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Context.UpdateContextTemplate(
context.TODO(),
"template_id",
&v3.UpdateContextTemplateRequest{
Template: "template",
},
)
```
# List Context Templates
GET https://api.getzep.com/api/v2/context-templates
Lists all context templates.
Reference: https://help.getzep.com/sdk-reference/context/list-context-templates
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: List Context Templates
version: endpoint_context.listContextTemplates
paths:
/context-templates:
get:
operationId: list-context-templates
summary: List Context Templates
description: Lists all context templates.
tags:
- - subpackage_context
parameters: []
responses:
'200':
description: The list of context templates.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.ListContextTemplatesResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.ContextTemplateResponse:
type: object
properties:
template:
type: string
description: The template content.
template_id:
type: string
description: Unique identifier for the template (max 100 characters).
uuid:
type: string
description: Unique identifier for the template.
apidata.ListContextTemplatesResponse:
type: object
properties:
templates:
type: array
items:
$ref: '#/components/schemas/apidata.ContextTemplateResponse'
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.context.list_context_templates()
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.context.listContextTemplates();
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Context.ListContextTemplates(
context.TODO(),
)
```
# Delete Context Template
DELETE https://api.getzep.com/api/v2/context-templates/{template_id}
Deletes a context template by template_id.
Reference: https://help.getzep.com/sdk-reference/context/delete-context-template
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Delete Context Template
version: endpoint_context.deleteContextTemplate
paths:
/context-templates/{template_id}:
delete:
operationId: delete-context-template
summary: Delete Context Template
description: Deletes a context template by template_id.
tags:
- - subpackage_context
parameters:
- name: template_id
in: path
description: Template ID
required: true
schema:
type: string
responses:
'200':
description: Template deleted successfully
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
'400':
description: Bad Request
content: {}
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.context.delete_context_template(
template_id="template_id",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.context.deleteContextTemplate("template_id");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Context.DeleteContextTemplate(
context.TODO(),
"template_id",
)
```
# Set graph ontology
PUT https://api.getzep.com/api/v2/graph/set-ontology
Content-Type: application/json
Sets custom entity and edge types for your graph. This wrapper method
provides a clean interface for defining your graph schema with custom
entity and edge types.
See the [full documentation](/customizing-graph-structure#setting-entity-and-edge-types) for details.
Reference: https://help.getzep.com/sdk-reference/graph/set-ontology
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Set graph ontology
version: endpoint_graph.set_ontology
paths:
/graph/set-ontology:
put:
operationId: set-ontology
summary: Set graph ontology
description: >
Sets custom entity and edge types for your graph. This wrapper method
provides a clean interface for defining your graph schema with custom
entity and edge types.
See the [full
documentation](/customizing-graph-structure#setting-entity-and-edge-types)
for details.
tags:
- - subpackage_graph
parameters: []
responses:
'200':
description: Ontology set successfully
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
requestBody:
content:
application/json:
schema:
type: object
properties:
entities:
$ref: >-
#/components/schemas/GraphSetOntologyPutRequestBodyContentApplicationJsonSchemaEntities
description: Dictionary mapping entity type names to their definitions
edges:
$ref: >-
#/components/schemas/GraphSetOntologyPutRequestBodyContentApplicationJsonSchemaEdges
description: >-
Dictionary mapping edge type names to their definitions with
source/target constraints
user_ids:
type: array
items:
type: string
description: Optional list of user IDs to apply ontology to
graph_ids:
type: array
items:
type: string
description: Optional list of graph IDs to apply ontology to
components:
schemas:
GraphSetOntologyPutRequestBodyContentApplicationJsonSchemaEntities:
type: object
properties: {}
GraphSetOntologyPutRequestBodyContentApplicationJsonSchemaEdges:
type: object
properties: {}
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud.client import Zep
from zep_cloud.external_clients.ontology import EntityModel, EntityText, EdgeModel
from zep_cloud import EntityEdgeSourceTarget
from pydantic import Field
class Restaurant(EntityModel):
cuisine_type: EntityText = Field(description="The cuisine type", default=None)
class RestaurantVisit(EdgeModel):
restaurant_name: EntityText = Field(description="Restaurant name", default=None)
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.set_ontology(
entities={
"Restaurant": Restaurant,
},
edges={
"RESTAURANT_VISIT": (
RestaurantVisit,
[EntityEdgeSourceTarget(source="User", target="Restaurant")]
),
}
)
```
```typescript
import { ZepClient, entityFields, EntityType, EdgeType } from "@getzep/zep-cloud";
const RestaurantSchema: EntityType = {
description: "Represents a restaurant.",
fields: {
cuisine_type: entityFields.text("The cuisine type"),
},
};
const RestaurantVisit: EdgeType = {
description: "User visited a restaurant.",
fields: {
restaurant_name: entityFields.text("Restaurant name"),
},
sourceTargets: [
{ source: "User", target: "Restaurant" },
],
};
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.setOntology(
{
Restaurant: RestaurantSchema,
},
{
RESTAURANT_VISIT: RestaurantVisit,
}
);
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
type Restaurant struct {
v3.BaseEntity `name:"Restaurant" description:"Represents a restaurant."`
CuisineType string `description:"The cuisine type" json:"cuisine_type,omitempty"`
}
type RestaurantVisit struct {
v3.BaseEdge `name:"RESTAURANT_VISIT" description:"User visited a restaurant."`
RestaurantName string `description:"Restaurant name" json:"restaurant_name,omitempty"`
}
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
_, err := client.Graph.SetOntology(
context.TODO(),
[]v3.EntityDefinition{
Restaurant{},
},
[]v3.EdgeDefinitionWithSourceTargets{
{
EdgeModel: RestaurantVisit{},
SourceTargets: []v3.EntityEdgeSourceTarget{
{Source: v3.String("User"), Target: v3.String("Restaurant")},
},
},
},
)
if err != nil {
panic(err)
}
```
# List graph ontology
GET https://api.getzep.com/api/v2/graph/list-ontology
Retrieves the current entity and edge types configured for your graph.
See the [full documentation](/customizing-graph-structure) for details.
Reference: https://help.getzep.com/sdk-reference/graph/list-ontology
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: List graph ontology
version: endpoint_graph.list_ontology
paths:
/graph/list-ontology:
get:
operationId: list-ontology
summary: List graph ontology
description: |
Retrieves the current entity and edge types configured for your graph.
See the [full documentation](/customizing-graph-structure) for details.
tags:
- - subpackage_graph
parameters: []
responses:
'200':
description: Current ontology
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.EntityTypeResponse'
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
models.EntityPropertyType:
type: string
enum:
- value: Text
- value: Int
- value: Float
- value: Boolean
apidata.EntityProperty:
type: object
properties:
description:
type: string
name:
type: string
type:
$ref: '#/components/schemas/models.EntityPropertyType'
required:
- description
- name
- type
apidata.EntityEdgeSourceTarget:
type: object
properties:
source:
type: string
description: >-
Source represents the originating node identifier in the edge type
relationship. (optional)
target:
type: string
description: >-
Target represents the target node identifier in the edge type
relationship. (optional)
apidata.EdgeType:
type: object
properties:
description:
type: string
name:
type: string
properties:
type: array
items:
$ref: '#/components/schemas/apidata.EntityProperty'
source_targets:
type: array
items:
$ref: '#/components/schemas/apidata.EntityEdgeSourceTarget'
required:
- description
- name
apidata.EntityType:
type: object
properties:
description:
type: string
name:
type: string
properties:
type: array
items:
$ref: '#/components/schemas/apidata.EntityProperty'
required:
- description
- name
apidata.EntityTypeResponse:
type: object
properties:
edge_types:
type: array
items:
$ref: '#/components/schemas/apidata.EdgeType'
entity_types:
type: array
items:
$ref: '#/components/schemas/apidata.EntityType'
```
## SDK Code Examples
```python
from zep_cloud.client import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
ontology = client.graph.list_ontology()
print("Entity types:", ontology.entity_types)
print("Edge types:", ontology.edge_types)
```
```typescript
import { ZepClient } from "@getzep/zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
const ontology = await client.graph.listOntology();
console.log("Entity types:", ontology.entityTypes);
console.log("Edge types:", ontology.edgeTypes);
```
```go
import (
context "context"
fmt "fmt"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
ontology, err := client.Graph.ListOntology(context.TODO())
if err != nil {
panic(err)
}
fmt.Printf("Entity types: %+v\n", ontology.EntityTypes)
fmt.Printf("Edge types: %+v\n", ontology.EdgeTypes)
```
# Add Data
POST https://api.getzep.com/api/v2/graph
Content-Type: application/json
Add data to the graph.
Reference: https://help.getzep.com/sdk-reference/graph/add-data
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Add Data
version: endpoint_data.addData
paths:
/graph:
post:
operationId: add-data
summary: Add Data
description: Add data to the graph.
tags:
- - subpackage_data
parameters: []
responses:
'202':
description: Added episode
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphEpisode'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Add data request
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.AddDataRequest'
components:
schemas:
models.GraphDataType:
type: string
enum:
- value: text
- value: json
- value: message
apidata.AddDataRequest:
type: object
properties:
created_at:
type: string
data:
type: string
graph_id:
type: string
description: >-
graph_id is the ID of the graph to which the data will be added. If
adding to the user graph, please use user_id field instead.
source_description:
type: string
type:
$ref: '#/components/schemas/models.GraphDataType'
user_id:
type: string
description: >-
User ID is the ID of the user to which the data will be added. If
not adding to a user graph, please use graph_id field instead.
required:
- data
- type
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
apidata.GraphEpisode:
type: object
properties:
content:
type: string
created_at:
type: string
metadata:
type: object
additionalProperties:
description: Any type
processed:
type: boolean
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
role:
type: string
description: >-
Optional role, will only be present if the episode was created using
memory.add API
role_type:
$ref: '#/components/schemas/apidata.RoleType'
description: >-
Optional role_type, will only be present if the episode was created
using memory.add API
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source:
$ref: '#/components/schemas/models.GraphDataType'
source_description:
type: string
task_id:
type: string
description: >-
Optional task ID to poll episode processing status. Currently only
available for batch ingestion.
thread_id:
type: string
description: >-
Optional thread ID, will be present if the episode is part of a
thread
uuid:
type: string
required:
- content
- created_at
- uuid
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.add(
data="data",
type="text",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.add({
data: "data",
type: "text"
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Add(
context.TODO(),
&v3.AddDataRequest{
Data: "data",
Type: v3.GraphDataTypeText,
},
)
```
# Add Data in batch mode
POST https://api.getzep.com/api/v2/graph-batch
Content-Type: application/json
Add data to the graph in batch mode, processing episodes concurrently. Use only for data that is insensitive to processing order.
Reference: https://help.getzep.com/sdk-reference/graph/add-data-in-batch-mode
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Add Data in batch mode
version: endpoint_data.addDataInBatchMode
paths:
/graph-batch:
post:
operationId: add-data-in-batch-mode
summary: Add Data in batch mode
description: >-
Add data to the graph in batch mode, processing episodes concurrently.
Use only for data that is insensitive to processing order.
tags:
- - subpackage_data
parameters: []
responses:
'202':
description: Added episodes
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/apidata.GraphEpisode'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Add data request
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.AddDataBatchRequest'
components:
schemas:
models.GraphDataType:
type: string
enum:
- value: text
- value: json
- value: message
apidata.EpisodeData:
type: object
properties:
created_at:
type: string
data:
type: string
source_description:
type: string
type:
$ref: '#/components/schemas/models.GraphDataType'
required:
- data
- type
apidata.AddDataBatchRequest:
type: object
properties:
episodes:
type: array
items:
$ref: '#/components/schemas/apidata.EpisodeData'
graph_id:
type: string
description: >-
graph_id is the ID of the graph to which the data will be added. If
adding to the user graph, please use user_id field instead.
user_id:
type: string
description: >-
User ID is the ID of the user to which the data will be added. If
not adding to a user graph, please use graph_id field instead.
required:
- episodes
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
apidata.GraphEpisode:
type: object
properties:
content:
type: string
created_at:
type: string
metadata:
type: object
additionalProperties:
description: Any type
processed:
type: boolean
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
role:
type: string
description: >-
Optional role, will only be present if the episode was created using
memory.add API
role_type:
$ref: '#/components/schemas/apidata.RoleType'
description: >-
Optional role_type, will only be present if the episode was created
using memory.add API
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source:
$ref: '#/components/schemas/models.GraphDataType'
source_description:
type: string
task_id:
type: string
description: >-
Optional task ID to poll episode processing status. Currently only
available for batch ingestion.
thread_id:
type: string
description: >-
Optional thread ID, will be present if the episode is part of a
thread
uuid:
type: string
required:
- content
- created_at
- uuid
```
## SDK Code Examples
```python
from zep_cloud import EpisodeData, Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.add_batch(
episodes=[
EpisodeData(
data="data",
type="text",
)
],
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.addBatch({
episodes: [{
data: "data",
type: "text"
}]
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.AddBatch(
context.TODO(),
&v3.AddDataBatchRequest{
Episodes: []*v3.EpisodeData{
&v3.EpisodeData{
Data: "data",
Type: v3.GraphDataTypeText,
},
},
},
)
```
# Add Fact Triple
POST https://api.getzep.com/api/v2/graph/add-fact-triple
Content-Type: application/json
Add a fact triple for a user or group
Reference: https://help.getzep.com/sdk-reference/graph/add-fact-triple
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Add Fact Triple
version: endpoint_entity.addFactTriple
paths:
/graph/add-fact-triple:
post:
operationId: add-fact-triple
summary: Add Fact Triple
description: Add a fact triple for a user or group
tags:
- - subpackage_entity
parameters: []
responses:
'200':
description: Resulting triple
content:
application/json:
schema:
$ref: '#/components/schemas/graphiti.AddTripleResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Triple to add
content:
application/json:
schema:
$ref: '#/components/schemas/graphiti.AddTripleRequest'
components:
schemas:
graphiti.AddTripleRequest:
type: object
properties:
created_at:
type: string
description: The timestamp of the message
edge_attributes:
type: object
additionalProperties:
description: Any type
description: >-
Additional attributes of the edge. Values must be scalar types
(string, number, boolean, or null).
Nested objects and arrays are not allowed.
expired_at:
type: string
description: The time (if any) at which the edge expires
fact:
type: string
description: The fact relating the two nodes that this edge represents
fact_name:
type: string
description: >-
The name of the edge to add. Should be all caps using snake case (eg
RELATES_TO)
fact_uuid:
type: string
description: The uuid of the edge to add
graph_id:
type: string
invalid_at:
type: string
description: The time (if any) at which the fact stops being true
source_node_attributes:
type: object
additionalProperties:
description: Any type
description: >-
Additional attributes of the source node. Values must be scalar
types (string, number, boolean, or null).
Nested objects and arrays are not allowed.
source_node_name:
type: string
description: The name of the source node to add
source_node_summary:
type: string
description: The summary of the source node to add
source_node_uuid:
type: string
description: The source node uuid
target_node_attributes:
type: object
additionalProperties:
description: Any type
description: >-
Additional attributes of the target node. Values must be scalar
types (string, number, boolean, or null).
Nested objects and arrays are not allowed.
target_node_name:
type: string
description: The name of the target node to add
target_node_summary:
type: string
description: The summary of the target node to add
target_node_uuid:
type: string
description: The target node uuid
user_id:
type: string
valid_at:
type: string
description: The time at which the fact becomes true
required:
- fact
- fact_name
graphiti.EntityEdge:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the edge. Dependent on edge types
created_at:
type: string
description: Creation time of the edge
episodes:
type: array
items:
type: string
description: List of episode ids that reference these entity edges
expired_at:
type: string
description: Datetime of when the node was invalidated
fact:
type: string
description: Fact representing the edge and nodes that it connects
invalid_at:
type: string
description: Datetime of when the fact stopped being true
name:
type: string
description: Name of the edge, relation name
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source_node_uuid:
type: string
description: UUID of the source node
target_node_uuid:
type: string
description: UUID of the target node
uuid:
type: string
description: UUID of the edge
valid_at:
type: string
description: Datetime of when the fact became true
required:
- created_at
- fact
- name
- source_node_uuid
- target_node_uuid
- uuid
graphiti.EntityNode:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the node. Dependent on node labels
created_at:
type: string
description: Creation time of the node
labels:
type: array
items:
type: string
description: Labels associated with the node
name:
type: string
description: Name of the node
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
summary:
type: string
description: Regional summary of surrounding edges
uuid:
type: string
description: UUID of the node
required:
- created_at
- name
- summary
- uuid
graphiti.AddTripleResponse:
type: object
properties:
edge:
$ref: '#/components/schemas/graphiti.EntityEdge'
source_node:
$ref: '#/components/schemas/graphiti.EntityNode'
target_node:
$ref: '#/components/schemas/graphiti.EntityNode'
task_id:
type: string
description: Task ID of the add triple task
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.add_fact_triple(
fact="fact",
fact_name="fact_name",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.addFactTriple({
fact: "fact",
factName: "fact_name"
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.AddFactTriple(
context.TODO(),
&v3.AddTripleRequest{
Fact: "fact",
FactName: "fact_name",
},
)
```
# Clone graph
POST https://api.getzep.com/api/v2/graph/clone
Content-Type: application/json
Clone a user or group graph.
Reference: https://help.getzep.com/sdk-reference/graph/clone-graph
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Clone graph
version: endpoint_data.cloneGraph
paths:
/graph/clone:
post:
operationId: clone-graph
summary: Clone graph
description: Clone a user or group graph.
tags:
- - subpackage_data
parameters: []
responses:
'202':
description: >-
Response object containing graph_id or user_id pointing to the new
graph
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.CloneGraphResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Clone graph request
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.CloneGraphRequest'
components:
schemas:
apidata.CloneGraphRequest:
type: object
properties:
source_graph_id:
type: string
description: >-
source_graph_id is the ID of the graph to be cloned. Required if
source_user_id is not provided
source_user_id:
type: string
description: >-
user_id of the user whose graph is being cloned. Required if
source_graph_id is not provided
target_graph_id:
type: string
description: >-
target_graph_id is the ID to be set on the cloned graph. Must not
point to an existing graph. Required if target_user_id is not
provided.
target_user_id:
type: string
description: >-
user_id to be set on the cloned user. Must not point to an existing
user. Required if target_graph_id is not provided.
apidata.CloneGraphResponse:
type: object
properties:
graph_id:
type: string
description: graph_id is the ID of the cloned graph
task_id:
type: string
description: Task ID of the clone graph task
user_id:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.clone()
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.clone();
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Clone(
context.TODO(),
&v3.CloneGraphRequest{},
)
```
# Create Graph
POST https://api.getzep.com/api/v2/graph/create
Content-Type: application/json
Creates a new graph.
Reference: https://help.getzep.com/sdk-reference/graph/create-graph
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Create Graph
version: endpoint_graph.createGraph
paths:
/graph/create:
post:
operationId: create-graph
summary: Create Graph
description: Creates a new graph.
tags:
- - subpackage_graph
parameters: []
responses:
'201':
description: The added graph
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.Graph'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Graph
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.CreateGraphRequest'
components:
schemas:
apidata.FactRatingExamples:
type: object
properties:
high:
type: string
low:
type: string
medium:
type: string
apidata.FactRatingInstruction:
type: object
properties:
examples:
$ref: '#/components/schemas/apidata.FactRatingExamples'
description: >-
Examples is a list of examples that demonstrate how facts might be
rated based on your instruction. You should provide
an example of a highly rated example, a low rated example, and a
medium (or in between example). For example, if you are rating
based on relevance to a trip planning application, your examples
might be:
High: "Joe's dream vacation is Bali"
Medium: "Joe has a fear of flying",
Low: "Joe's favorite food is Japanese",
instruction:
type: string
description: >-
A string describing how to rate facts as they apply to your
application. A trip planning application may
use something like "relevancy to planning a trip, the user's
preferences when traveling,
or the user's travel history."
apidata.CreateGraphRequest:
type: object
properties:
description:
type: string
fact_rating_instruction:
$ref: '#/components/schemas/apidata.FactRatingInstruction'
graph_id:
type: string
name:
type: string
required:
- graph_id
apidata.Graph:
type: object
properties:
created_at:
type: string
description:
type: string
fact_rating_instruction:
$ref: '#/components/schemas/apidata.FactRatingInstruction'
graph_id:
type: string
id:
type: integer
name:
type: string
project_uuid:
type: string
uuid:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.create(
graph_id="graph_id",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.create({
graphId: "graph_id"
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Create(
context.TODO(),
&v3.CreateGraphRequest{
GraphID: "graph_id",
},
)
```
# List all graphs.
GET https://api.getzep.com/api/v2/graph/list-all
Returns all graphs. In order to list users, use user.list_ordered instead
Reference: https://help.getzep.com/sdk-reference/graph/list-all-graphs
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: List all graphs.
version: endpoint_graph.listAllGraphs
paths:
/graph/list-all:
get:
operationId: list-all-graphs
summary: List all graphs.
description: >-
Returns all graphs. In order to list users, use user.list_ordered
instead
tags:
- - subpackage_graph
parameters:
- name: pageNumber
in: query
description: Page number for pagination, starting from 1.
required: false
schema:
type: integer
- name: pageSize
in: query
description: Number of graphs to retrieve per page.
required: false
schema:
type: integer
responses:
'200':
description: Successfully retrieved list of graphs.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphListResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.FactRatingExamples:
type: object
properties:
high:
type: string
low:
type: string
medium:
type: string
apidata.FactRatingInstruction:
type: object
properties:
examples:
$ref: '#/components/schemas/apidata.FactRatingExamples'
description: >-
Examples is a list of examples that demonstrate how facts might be
rated based on your instruction. You should provide
an example of a highly rated example, a low rated example, and a
medium (or in between example). For example, if you are rating
based on relevance to a trip planning application, your examples
might be:
High: "Joe's dream vacation is Bali"
Medium: "Joe has a fear of flying",
Low: "Joe's favorite food is Japanese",
instruction:
type: string
description: >-
A string describing how to rate facts as they apply to your
application. A trip planning application may
use something like "relevancy to planning a trip, the user's
preferences when traveling,
or the user's travel history."
apidata.Graph:
type: object
properties:
created_at:
type: string
description:
type: string
fact_rating_instruction:
$ref: '#/components/schemas/apidata.FactRatingInstruction'
graph_id:
type: string
id:
type: integer
name:
type: string
project_uuid:
type: string
uuid:
type: string
apidata.GraphListResponse:
type: object
properties:
graphs:
type: array
items:
$ref: '#/components/schemas/apidata.Graph'
row_count:
type: integer
total_count:
type: integer
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.list_all(
page_number=1,
page_size=1,
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.listAll({
pageNumber: 1,
pageSize: 1
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.ListAll(
context.TODO(),
&v3.GraphListAllRequest{
PageNumber: v3.Int(
1,
),
PageSize: v3.Int(
1,
),
},
)
```
# Search Graph
POST https://api.getzep.com/api/v2/graph/search
Content-Type: application/json
Perform a graph search query.
Reference: https://help.getzep.com/sdk-reference/graph/search
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Search Graph
version: endpoint_search.graph
paths:
/graph/search:
post:
operationId: graph
summary: Search Graph
description: Perform a graph search query.
tags:
- - subpackage_search
parameters: []
responses:
'200':
description: Graph search results
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphSearchResults'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Graph search query
content:
application/json:
schema:
$ref: '#/components/schemas/graphiti.GraphSearchQuery'
components:
schemas:
graphiti.Reranker:
type: string
enum:
- value: rrf
- value: mmr
- value: node_distance
- value: episode_mentions
- value: cross_encoder
graphiti.GraphSearchScope:
type: string
enum:
- value: edges
- value: nodes
- value: episodes
graphiti.ComparisonOperator:
type: string
enum:
- value: '='
- value: <>
- value: '>'
- value: <
- value: '>='
- value: <=
- value: IS NULL
- value: IS NOT NULL
graphiti.DateFilter:
type: object
properties:
comparison_operator:
$ref: '#/components/schemas/graphiti.ComparisonOperator'
description: Comparison operator for date filter
date:
type: string
description: >-
Date to filter on. Required for non-null operators (=, <>, >, <, >=,
<=).
Should be omitted for IS NULL and IS NOT NULL operators.
required:
- comparison_operator
graphiti.PropertyFilter:
type: object
properties:
comparison_operator:
$ref: '#/components/schemas/graphiti.ComparisonOperator'
description: Comparison operator for property filter
property_name:
type: string
description: Property name to filter on
property_value:
description: >-
Property value to match on. Accepted types: string, int, float64,
bool, or nil.
Invalid types (e.g., arrays, objects) will be rejected by
validation.
Must be non-nil for non-null operators (=, <>, >, <, >=, <=).
required:
- comparison_operator
- property_name
graphiti.SearchFilters:
type: object
properties:
created_at:
type: array
items:
type: array
items:
$ref: '#/components/schemas/graphiti.DateFilter'
description: >-
2D array of date filters for the created_at field.
The outer array elements are combined with OR logic.
The inner array elements are combined with AND logic.
Example: [[{">", date1}, {"<", date2}], [{"=", date3}]]
This translates to: (created_at > date1 AND created_at < date2) OR
(created_at = date3)
edge_types:
type: array
items:
type: string
description: List of edge types to filter on
edge_uuids:
type: array
items:
type: string
description: List of edge UUIDs to filter on
exclude_edge_types:
type: array
items:
type: string
description: List of edge types to exclude from results
exclude_node_labels:
type: array
items:
type: string
description: List of node labels to exclude from results
expired_at:
type: array
items:
type: array
items:
$ref: '#/components/schemas/graphiti.DateFilter'
description: >-
2D array of date filters for the expired_at field.
The outer array elements are combined with OR logic.
The inner array elements are combined with AND logic.
Example: [[{">", date1}, {"<", date2}], [{"=", date3}]]
This translates to: (expired_at > date1 AND expired_at < date2) OR
(expired_at = date3)
invalid_at:
type: array
items:
type: array
items:
$ref: '#/components/schemas/graphiti.DateFilter'
description: >-
2D array of date filters for the invalid_at field.
The outer array elements are combined with OR logic.
The inner array elements are combined with AND logic.
Example: [[{">", date1}, {"<", date2}], [{"=", date3}]]
This translates to: (invalid_at > date1 AND invalid_at < date2) OR
(invalid_at = date3)
node_labels:
type: array
items:
type: string
description: List of node labels to filter on
property_filters:
type: array
items:
$ref: '#/components/schemas/graphiti.PropertyFilter'
description: List of property filters to apply to nodes and edges
valid_at:
type: array
items:
type: array
items:
$ref: '#/components/schemas/graphiti.DateFilter'
description: >-
2D array of date filters for the valid_at field.
The outer array elements are combined with OR logic.
The inner array elements are combined with AND logic.
Example: [[{">", date1}, {"<", date2}], [{"=", date3}]]
This translates to: (valid_at > date1 AND valid_at < date2) OR
(valid_at = date3)
graphiti.GraphSearchQuery:
type: object
properties:
bfs_origin_node_uuids:
type: array
items:
type: string
description: Nodes that are the origins of the BFS searches
center_node_uuid:
type: string
description: Node to rerank around for node distance reranking
graph_id:
type: string
description: >-
The graph_id to search in. When searching user graph, please use
user_id instead.
limit:
type: integer
description: >-
The maximum number of facts to retrieve. Defaults to 10. Limited to
50.
min_fact_rating:
type: number
format: double
description: The minimum rating by which to filter relevant facts
min_score:
type: number
format: double
description: Deprecated
mmr_lambda:
type: number
format: double
description: weighting for maximal marginal relevance
query:
type: string
description: The string to search for (required)
reranker:
$ref: '#/components/schemas/graphiti.Reranker'
description: Defaults to RRF
scope:
$ref: '#/components/schemas/graphiti.GraphSearchScope'
description: Defaults to Edges. Communities will be added in the future.
search_filters:
$ref: '#/components/schemas/graphiti.SearchFilters'
description: Search filters to apply to the search
user_id:
type: string
description: >-
The user_id when searching user graph. If not searching user graph,
please use graph_id instead.
required:
- query
graphiti.EntityEdge:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the edge. Dependent on edge types
created_at:
type: string
description: Creation time of the edge
episodes:
type: array
items:
type: string
description: List of episode ids that reference these entity edges
expired_at:
type: string
description: Datetime of when the node was invalidated
fact:
type: string
description: Fact representing the edge and nodes that it connects
invalid_at:
type: string
description: Datetime of when the fact stopped being true
name:
type: string
description: Name of the edge, relation name
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source_node_uuid:
type: string
description: UUID of the source node
target_node_uuid:
type: string
description: UUID of the target node
uuid:
type: string
description: UUID of the edge
valid_at:
type: string
description: Datetime of when the fact became true
required:
- created_at
- fact
- name
- source_node_uuid
- target_node_uuid
- uuid
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
models.GraphDataType:
type: string
enum:
- value: text
- value: json
- value: message
apidata.GraphEpisode:
type: object
properties:
content:
type: string
created_at:
type: string
metadata:
type: object
additionalProperties:
description: Any type
processed:
type: boolean
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
role:
type: string
description: >-
Optional role, will only be present if the episode was created using
memory.add API
role_type:
$ref: '#/components/schemas/apidata.RoleType'
description: >-
Optional role_type, will only be present if the episode was created
using memory.add API
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source:
$ref: '#/components/schemas/models.GraphDataType'
source_description:
type: string
task_id:
type: string
description: >-
Optional task ID to poll episode processing status. Currently only
available for batch ingestion.
thread_id:
type: string
description: >-
Optional thread ID, will be present if the episode is part of a
thread
uuid:
type: string
required:
- content
- created_at
- uuid
graphiti.EntityNode:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the node. Dependent on node labels
created_at:
type: string
description: Creation time of the node
labels:
type: array
items:
type: string
description: Labels associated with the node
name:
type: string
description: Name of the node
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
summary:
type: string
description: Regional summary of surrounding edges
uuid:
type: string
description: UUID of the node
required:
- created_at
- name
- summary
- uuid
apidata.GraphSearchResults:
type: object
properties:
edges:
type: array
items:
$ref: '#/components/schemas/graphiti.EntityEdge'
episodes:
type: array
items:
$ref: '#/components/schemas/apidata.GraphEpisode'
nodes:
type: array
items:
$ref: '#/components/schemas/graphiti.EntityNode'
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.search(
query="query",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.search({
query: "query"
});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Search(
context.TODO(),
&v3.GraphSearchQuery{
Query: "query",
},
)
```
# Get Graph
GET https://api.getzep.com/api/v2/graph/{graphId}
Returns a graph.
Reference: https://help.getzep.com/sdk-reference/graph/get-graph
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Graph
version: endpoint_graph.getGraph
paths:
/graph/{graphId}:
get:
operationId: get-graph
summary: Get Graph
description: Returns a graph.
tags:
- - subpackage_graph
parameters:
- name: graphId
in: path
description: The graph_id of the graph to get.
required: true
schema:
type: string
responses:
'200':
description: The graph that was retrieved.
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.Graph'
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.FactRatingExamples:
type: object
properties:
high:
type: string
low:
type: string
medium:
type: string
apidata.FactRatingInstruction:
type: object
properties:
examples:
$ref: '#/components/schemas/apidata.FactRatingExamples'
description: >-
Examples is a list of examples that demonstrate how facts might be
rated based on your instruction. You should provide
an example of a highly rated example, a low rated example, and a
medium (or in between example). For example, if you are rating
based on relevance to a trip planning application, your examples
might be:
High: "Joe's dream vacation is Bali"
Medium: "Joe has a fear of flying",
Low: "Joe's favorite food is Japanese",
instruction:
type: string
description: >-
A string describing how to rate facts as they apply to your
application. A trip planning application may
use something like "relevancy to planning a trip, the user's
preferences when traveling,
or the user's travel history."
apidata.Graph:
type: object
properties:
created_at:
type: string
description:
type: string
fact_rating_instruction:
$ref: '#/components/schemas/apidata.FactRatingInstruction'
graph_id:
type: string
id:
type: integer
name:
type: string
project_uuid:
type: string
uuid:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.get(
graph_id="graphId",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.get("graphId");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Get(
context.TODO(),
"graphId",
)
```
# Delete Graph
DELETE https://api.getzep.com/api/v2/graph/{graphId}
Deletes a graph. If you would like to delete a user graph, make sure to use user.delete instead.
Reference: https://help.getzep.com/sdk-reference/graph/delete-graph
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Delete Graph
version: endpoint_graph.deleteGraph
paths:
/graph/{graphId}:
delete:
operationId: delete-graph
summary: Delete Graph
description: >-
Deletes a graph. If you would like to delete a user graph, make sure to
use user.delete instead.
tags:
- - subpackage_graph
parameters:
- name: graphId
in: path
description: Graph ID
required: true
schema:
type: string
responses:
'200':
description: Deleted
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
'400':
description: Bad Request
content: {}
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.delete(
graph_id="graphId",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.delete("graphId");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Delete(
context.TODO(),
"graphId",
)
```
# Update Graph.
PATCH https://api.getzep.com/api/v2/graph/{graphId}
Content-Type: application/json
Updates information about a graph.
Reference: https://help.getzep.com/sdk-reference/graph/update-graph
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Update Graph.
version: endpoint_graph.updateGraph
paths:
/graph/{graphId}:
patch:
operationId: update-graph
summary: Update Graph.
description: Updates information about a graph.
tags:
- - subpackage_graph
parameters:
- name: graphId
in: path
description: Graph ID
required: true
schema:
type: string
responses:
'201':
description: The updated graph object
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.Graph'
'400':
description: Bad Request
content: {}
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Graph
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.UpdateGraphRequest'
components:
schemas:
apidata.FactRatingExamples:
type: object
properties:
high:
type: string
low:
type: string
medium:
type: string
apidata.FactRatingInstruction:
type: object
properties:
examples:
$ref: '#/components/schemas/apidata.FactRatingExamples'
description: >-
Examples is a list of examples that demonstrate how facts might be
rated based on your instruction. You should provide
an example of a highly rated example, a low rated example, and a
medium (or in between example). For example, if you are rating
based on relevance to a trip planning application, your examples
might be:
High: "Joe's dream vacation is Bali"
Medium: "Joe has a fear of flying",
Low: "Joe's favorite food is Japanese",
instruction:
type: string
description: >-
A string describing how to rate facts as they apply to your
application. A trip planning application may
use something like "relevancy to planning a trip, the user's
preferences when traveling,
or the user's travel history."
apidata.UpdateGraphRequest:
type: object
properties:
description:
type: string
fact_rating_instruction:
$ref: '#/components/schemas/apidata.FactRatingInstruction'
name:
type: string
apidata.Graph:
type: object
properties:
created_at:
type: string
description:
type: string
fact_rating_instruction:
$ref: '#/components/schemas/apidata.FactRatingInstruction'
graph_id:
type: string
id:
type: integer
name:
type: string
project_uuid:
type: string
uuid:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.update(
graph_id="graphId",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.update("graphId");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Update(
context.TODO(),
"graphId",
&v3.UpdateGraphRequest{},
)
```
# Get Graph Edges
POST https://api.getzep.com/api/v2/graph/edge/graph/{graph_id}
Content-Type: application/json
Returns all edges for a graph.
Reference: https://help.getzep.com/sdk-reference/graph/edge/get-graph-edges
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Graph Edges
version: endpoint_entity.getGraphEdges
paths:
/graph/edge/graph/{graph_id}:
post:
operationId: get-graph-edges
summary: Get Graph Edges
description: Returns all edges for a graph.
tags:
- - subpackage_entity
parameters:
- name: graph_id
in: path
description: Graph ID
required: true
schema:
type: string
responses:
'200':
description: Edges
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/graphiti.EntityEdge'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Pagination parameters
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphEdgesRequest'
components:
schemas:
apidata.GraphEdgesRequest:
type: object
properties:
limit:
type: integer
description: Maximum number of items to return
uuid_cursor:
type: string
description: >-
UUID based cursor, used for pagination. Should be the UUID of the
last item in the previous page
graphiti.EntityEdge:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the edge. Dependent on edge types
created_at:
type: string
description: Creation time of the edge
episodes:
type: array
items:
type: string
description: List of episode ids that reference these entity edges
expired_at:
type: string
description: Datetime of when the node was invalidated
fact:
type: string
description: Fact representing the edge and nodes that it connects
invalid_at:
type: string
description: Datetime of when the fact stopped being true
name:
type: string
description: Name of the edge, relation name
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source_node_uuid:
type: string
description: UUID of the source node
target_node_uuid:
type: string
description: UUID of the target node
uuid:
type: string
description: UUID of the edge
valid_at:
type: string
description: Datetime of when the fact became true
required:
- created_at
- fact
- name
- source_node_uuid
- target_node_uuid
- uuid
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.edge.get_by_graph_id(
graph_id="graph_id",
)
```
```typescript
import { ZepClient, Zep } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.edge.getByGraphId("graph_id", {});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Edge.GetByGraphID(
context.TODO(),
"graph_id",
&v3.GraphEdgesRequest{},
)
```
# Get User Edges
POST https://api.getzep.com/api/v2/graph/edge/user/{user_id}
Content-Type: application/json
Returns all edges for a user.
Reference: https://help.getzep.com/sdk-reference/graph/edge/get-user-edges
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get User Edges
version: endpoint_entity.getUserEdges
paths:
/graph/edge/user/{user_id}:
post:
operationId: get-user-edges
summary: Get User Edges
description: Returns all edges for a user.
tags:
- - subpackage_entity
parameters:
- name: user_id
in: path
description: User ID
required: true
schema:
type: string
responses:
'200':
description: Edges
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/graphiti.EntityEdge'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Pagination parameters
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphEdgesRequest'
components:
schemas:
apidata.GraphEdgesRequest:
type: object
properties:
limit:
type: integer
description: Maximum number of items to return
uuid_cursor:
type: string
description: >-
UUID based cursor, used for pagination. Should be the UUID of the
last item in the previous page
graphiti.EntityEdge:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the edge. Dependent on edge types
created_at:
type: string
description: Creation time of the edge
episodes:
type: array
items:
type: string
description: List of episode ids that reference these entity edges
expired_at:
type: string
description: Datetime of when the node was invalidated
fact:
type: string
description: Fact representing the edge and nodes that it connects
invalid_at:
type: string
description: Datetime of when the fact stopped being true
name:
type: string
description: Name of the edge, relation name
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source_node_uuid:
type: string
description: UUID of the source node
target_node_uuid:
type: string
description: UUID of the target node
uuid:
type: string
description: UUID of the edge
valid_at:
type: string
description: Datetime of when the fact became true
required:
- created_at
- fact
- name
- source_node_uuid
- target_node_uuid
- uuid
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.edge.get_by_user_id(
user_id="user_id",
)
```
```typescript
import { ZepClient, Zep } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.edge.getByUserId("user_id", {});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Edge.GetByUserID(
context.TODO(),
"user_id",
&v3.GraphEdgesRequest{},
)
```
# Get Edge
GET https://api.getzep.com/api/v2/graph/edge/{uuid}
Returns a specific edge by its UUID.
Reference: https://help.getzep.com/sdk-reference/graph/edge/get-edge
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Edge
version: endpoint_entity.getEdge
paths:
/graph/edge/{uuid}:
get:
operationId: get-edge
summary: Get Edge
description: Returns a specific edge by its UUID.
tags:
- - subpackage_entity
parameters:
- name: uuid
in: path
description: Edge UUID
required: true
schema:
type: string
responses:
'200':
description: Edge
content:
application/json:
schema:
$ref: '#/components/schemas/graphiti.EntityEdge'
'400':
description: Bad Request
content: {}
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
graphiti.EntityEdge:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the edge. Dependent on edge types
created_at:
type: string
description: Creation time of the edge
episodes:
type: array
items:
type: string
description: List of episode ids that reference these entity edges
expired_at:
type: string
description: Datetime of when the node was invalidated
fact:
type: string
description: Fact representing the edge and nodes that it connects
invalid_at:
type: string
description: Datetime of when the fact stopped being true
name:
type: string
description: Name of the edge, relation name
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source_node_uuid:
type: string
description: UUID of the source node
target_node_uuid:
type: string
description: UUID of the target node
uuid:
type: string
description: UUID of the edge
valid_at:
type: string
description: Datetime of when the fact became true
required:
- created_at
- fact
- name
- source_node_uuid
- target_node_uuid
- uuid
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.edge.get(
uuid_="uuid",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.edge.get("uuid");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Edge.Get(
context.TODO(),
"uuid",
)
```
# Delete Edge
DELETE https://api.getzep.com/api/v2/graph/edge/{uuid}
Deletes an edge by UUID.
Reference: https://help.getzep.com/sdk-reference/graph/edge/delete-edge
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Delete Edge
version: endpoint_entity.deleteEdge
paths:
/graph/edge/{uuid}:
delete:
operationId: delete-edge
summary: Delete Edge
description: Deletes an edge by UUID.
tags:
- - subpackage_entity
parameters:
- name: uuid
in: path
description: Edge UUID
required: true
schema:
type: string
responses:
'200':
description: Edge deleted
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.edge.delete(
uuid_="uuid",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.edge.delete("uuid");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Edge.Delete(
context.TODO(),
"uuid",
)
```
# Get Graph Episodes
GET https://api.getzep.com/api/v2/graph/episodes/graph/{graph_id}
Returns episodes by graph id.
Reference: https://help.getzep.com/sdk-reference/graph/episode/get-graph-episodes
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Graph Episodes
version: endpoint_episodes.getGraphEpisodes
paths:
/graph/episodes/graph/{graph_id}:
get:
operationId: get-graph-episodes
summary: Get Graph Episodes
description: Returns episodes by graph id.
tags:
- - subpackage_episodes
parameters:
- name: graph_id
in: path
description: Graph ID
required: true
schema:
type: string
- name: lastn
in: query
description: The number of most recent episodes to retrieve.
required: false
schema:
type: integer
responses:
'200':
description: Episodes
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphEpisodeResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
models.GraphDataType:
type: string
enum:
- value: text
- value: json
- value: message
apidata.GraphEpisode:
type: object
properties:
content:
type: string
created_at:
type: string
metadata:
type: object
additionalProperties:
description: Any type
processed:
type: boolean
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
role:
type: string
description: >-
Optional role, will only be present if the episode was created using
memory.add API
role_type:
$ref: '#/components/schemas/apidata.RoleType'
description: >-
Optional role_type, will only be present if the episode was created
using memory.add API
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source:
$ref: '#/components/schemas/models.GraphDataType'
source_description:
type: string
task_id:
type: string
description: >-
Optional task ID to poll episode processing status. Currently only
available for batch ingestion.
thread_id:
type: string
description: >-
Optional thread ID, will be present if the episode is part of a
thread
uuid:
type: string
required:
- content
- created_at
- uuid
apidata.GraphEpisodeResponse:
type: object
properties:
episodes:
type: array
items:
$ref: '#/components/schemas/apidata.GraphEpisode'
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.episode.get_by_graph_id(
graph_id="graph_id",
lastn=1,
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.episode.getByGraphId("graph_id", {
lastn: 1
});
```
```go
import (
context "context"
graph "github.com/getzep/zep-go/v3/graph"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Episode.GetByGraphID(
context.TODO(),
"graph_id",
&graph.EpisodeGetByGraphIDRequest{
Lastn: v3.Int(
1,
),
},
)
```
# Get User Episodes
GET https://api.getzep.com/api/v2/graph/episodes/user/{user_id}
Returns episodes by user id.
Reference: https://help.getzep.com/sdk-reference/graph/episode/get-user-episodes
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get User Episodes
version: endpoint_episodes.getUserEpisodes
paths:
/graph/episodes/user/{user_id}:
get:
operationId: get-user-episodes
summary: Get User Episodes
description: Returns episodes by user id.
tags:
- - subpackage_episodes
parameters:
- name: user_id
in: path
description: User ID
required: true
schema:
type: string
- name: lastn
in: query
description: The number of most recent episodes entries to retrieve.
required: false
schema:
type: integer
responses:
'200':
description: Episodes
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphEpisodeResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
models.GraphDataType:
type: string
enum:
- value: text
- value: json
- value: message
apidata.GraphEpisode:
type: object
properties:
content:
type: string
created_at:
type: string
metadata:
type: object
additionalProperties:
description: Any type
processed:
type: boolean
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
role:
type: string
description: >-
Optional role, will only be present if the episode was created using
memory.add API
role_type:
$ref: '#/components/schemas/apidata.RoleType'
description: >-
Optional role_type, will only be present if the episode was created
using memory.add API
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source:
$ref: '#/components/schemas/models.GraphDataType'
source_description:
type: string
task_id:
type: string
description: >-
Optional task ID to poll episode processing status. Currently only
available for batch ingestion.
thread_id:
type: string
description: >-
Optional thread ID, will be present if the episode is part of a
thread
uuid:
type: string
required:
- content
- created_at
- uuid
apidata.GraphEpisodeResponse:
type: object
properties:
episodes:
type: array
items:
$ref: '#/components/schemas/apidata.GraphEpisode'
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.episode.get_by_user_id(
user_id="user_id",
lastn=1,
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.episode.getByUserId("user_id", {
lastn: 1
});
```
```go
import (
context "context"
graph "github.com/getzep/zep-go/v3/graph"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Episode.GetByUserID(
context.TODO(),
"user_id",
&graph.EpisodeGetByUserIDRequest{
Lastn: v3.Int(
1,
),
},
)
```
# Get Episode
GET https://api.getzep.com/api/v2/graph/episodes/{uuid}
Returns episodes by UUID
Reference: https://help.getzep.com/sdk-reference/graph/episode/get-episode
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Episode
version: endpoint_episodes.getEpisode
paths:
/graph/episodes/{uuid}:
get:
operationId: get-episode
summary: Get Episode
description: Returns episodes by UUID
tags:
- - subpackage_episodes
parameters:
- name: uuid
in: path
description: Episode UUID
required: true
schema:
type: string
responses:
'200':
description: Episode
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphEpisode'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
models.GraphDataType:
type: string
enum:
- value: text
- value: json
- value: message
apidata.GraphEpisode:
type: object
properties:
content:
type: string
created_at:
type: string
metadata:
type: object
additionalProperties:
description: Any type
processed:
type: boolean
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
role:
type: string
description: >-
Optional role, will only be present if the episode was created using
memory.add API
role_type:
$ref: '#/components/schemas/apidata.RoleType'
description: >-
Optional role_type, will only be present if the episode was created
using memory.add API
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source:
$ref: '#/components/schemas/models.GraphDataType'
source_description:
type: string
task_id:
type: string
description: >-
Optional task ID to poll episode processing status. Currently only
available for batch ingestion.
thread_id:
type: string
description: >-
Optional thread ID, will be present if the episode is part of a
thread
uuid:
type: string
required:
- content
- created_at
- uuid
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.episode.get(
uuid_="uuid",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.episode.get("uuid");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Episode.Get(
context.TODO(),
"uuid",
)
```
# Delete Episode
DELETE https://api.getzep.com/api/v2/graph/episodes/{uuid}
Deletes an episode by its UUID.
Reference: https://help.getzep.com/sdk-reference/graph/episode/delete-episode
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Delete Episode
version: endpoint_entity.deleteEpisode
paths:
/graph/episodes/{uuid}:
delete:
operationId: delete-episode
summary: Delete Episode
description: Deletes an episode by its UUID.
tags:
- - subpackage_entity
parameters:
- name: uuid
in: path
description: Episode UUID
required: true
schema:
type: string
responses:
'200':
description: Episode deleted
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
'400':
description: Bad Request
content: {}
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.episode.delete(
uuid_="uuid",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.episode.delete("uuid");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Episode.Delete(
context.TODO(),
"uuid",
)
```
# Return any nodes and edges mentioned in an episode
GET https://api.getzep.com/api/v2/graph/episodes/{uuid}/mentions
Returns nodes and edges mentioned in an episode
Reference: https://help.getzep.com/sdk-reference/graph/episode/return-any-nodes-and-edges-mentioned-in-an-episode
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Return any nodes and edges mentioned in an episode
version: endpoint_episodes.returnAnyNodesAndEdgesMentionedInAnEpisode
paths:
/graph/episodes/{uuid}/mentions:
get:
operationId: return-any-nodes-and-edges-mentioned-in-an-episode
summary: Return any nodes and edges mentioned in an episode
description: Returns nodes and edges mentioned in an episode
tags:
- - subpackage_episodes
parameters:
- name: uuid
in: path
description: Episode uuid
required: true
schema:
type: string
responses:
'200':
description: Edges and nodes mentioned in an episode
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.EpisodeMentions'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
graphiti.EntityEdge:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the edge. Dependent on edge types
created_at:
type: string
description: Creation time of the edge
episodes:
type: array
items:
type: string
description: List of episode ids that reference these entity edges
expired_at:
type: string
description: Datetime of when the node was invalidated
fact:
type: string
description: Fact representing the edge and nodes that it connects
invalid_at:
type: string
description: Datetime of when the fact stopped being true
name:
type: string
description: Name of the edge, relation name
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source_node_uuid:
type: string
description: UUID of the source node
target_node_uuid:
type: string
description: UUID of the target node
uuid:
type: string
description: UUID of the edge
valid_at:
type: string
description: Datetime of when the fact became true
required:
- created_at
- fact
- name
- source_node_uuid
- target_node_uuid
- uuid
graphiti.EntityNode:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the node. Dependent on node labels
created_at:
type: string
description: Creation time of the node
labels:
type: array
items:
type: string
description: Labels associated with the node
name:
type: string
description: Name of the node
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
summary:
type: string
description: Regional summary of surrounding edges
uuid:
type: string
description: UUID of the node
required:
- created_at
- name
- summary
- uuid
apidata.EpisodeMentions:
type: object
properties:
edges:
type: array
items:
$ref: '#/components/schemas/graphiti.EntityEdge'
nodes:
type: array
items:
$ref: '#/components/schemas/graphiti.EntityNode'
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.episode.get_nodes_and_edges(
uuid_="uuid",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.episode.getNodesAndEdges("uuid");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Episode.GetNodesAndEdges(
context.TODO(),
"uuid",
)
```
# Get Graph Nodes
POST https://api.getzep.com/api/v2/graph/node/graph/{graph_id}
Content-Type: application/json
Returns all nodes for a graph.
Reference: https://help.getzep.com/sdk-reference/graph/node/get-graph-nodes
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Graph Nodes
version: endpoint_entity.getGraphNodes
paths:
/graph/node/graph/{graph_id}:
post:
operationId: get-graph-nodes
summary: Get Graph Nodes
description: Returns all nodes for a graph.
tags:
- - subpackage_entity
parameters:
- name: graph_id
in: path
description: Graph ID
required: true
schema:
type: string
responses:
'200':
description: Nodes
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/graphiti.EntityNode'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Pagination parameters
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphNodesRequest'
components:
schemas:
apidata.GraphNodesRequest:
type: object
properties:
limit:
type: integer
description: Maximum number of items to return
uuid_cursor:
type: string
description: >-
UUID based cursor, used for pagination. Should be the UUID of the
last item in the previous page
graphiti.EntityNode:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the node. Dependent on node labels
created_at:
type: string
description: Creation time of the node
labels:
type: array
items:
type: string
description: Labels associated with the node
name:
type: string
description: Name of the node
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
summary:
type: string
description: Regional summary of surrounding edges
uuid:
type: string
description: UUID of the node
required:
- created_at
- name
- summary
- uuid
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.node.get_by_graph_id(
graph_id="graph_id",
)
```
```typescript
import { ZepClient, Zep } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.node.getByGraphId("graph_id", {});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Node.GetByGraphID(
context.TODO(),
"graph_id",
&v3.GraphNodesRequest{},
)
```
# Get User Nodes
POST https://api.getzep.com/api/v2/graph/node/user/{user_id}
Content-Type: application/json
Returns all nodes for a user
Reference: https://help.getzep.com/sdk-reference/graph/node/get-user-nodes
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get User Nodes
version: endpoint_entity.getUserNodes
paths:
/graph/node/user/{user_id}:
post:
operationId: get-user-nodes
summary: Get User Nodes
description: Returns all nodes for a user
tags:
- - subpackage_entity
parameters:
- name: user_id
in: path
description: User ID
required: true
schema:
type: string
responses:
'200':
description: Nodes
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/graphiti.EntityNode'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
requestBody:
description: Pagination parameters
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphNodesRequest'
components:
schemas:
apidata.GraphNodesRequest:
type: object
properties:
limit:
type: integer
description: Maximum number of items to return
uuid_cursor:
type: string
description: >-
UUID based cursor, used for pagination. Should be the UUID of the
last item in the previous page
graphiti.EntityNode:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the node. Dependent on node labels
created_at:
type: string
description: Creation time of the node
labels:
type: array
items:
type: string
description: Labels associated with the node
name:
type: string
description: Name of the node
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
summary:
type: string
description: Regional summary of surrounding edges
uuid:
type: string
description: UUID of the node
required:
- created_at
- name
- summary
- uuid
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.node.get_by_user_id(
user_id="user_id",
)
```
```typescript
import { ZepClient, Zep } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.node.getByUserId("user_id", {});
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3 "github.com/getzep/zep-go/v3"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Node.GetByUserID(
context.TODO(),
"user_id",
&v3.GraphNodesRequest{},
)
```
# Get Entity Edges for a node
GET https://api.getzep.com/api/v2/graph/node/{node_uuid}/entity-edges
Returns all edges for a node
Reference: https://help.getzep.com/sdk-reference/graph/node/get-entity-edges-for-a-node
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Entity Edges for a node
version: endpoint_entity.getEntityEdgesForANode
paths:
/graph/node/{node_uuid}/entity-edges:
get:
operationId: get-entity-edges-for-a-node
summary: Get Entity Edges for a node
description: Returns all edges for a node
tags:
- - subpackage_entity
parameters:
- name: node_uuid
in: path
description: Node UUID
required: true
schema:
type: string
responses:
'200':
description: Edges
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/graphiti.EntityEdge'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
graphiti.EntityEdge:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the edge. Dependent on edge types
created_at:
type: string
description: Creation time of the edge
episodes:
type: array
items:
type: string
description: List of episode ids that reference these entity edges
expired_at:
type: string
description: Datetime of when the node was invalidated
fact:
type: string
description: Fact representing the edge and nodes that it connects
invalid_at:
type: string
description: Datetime of when the fact stopped being true
name:
type: string
description: Name of the edge, relation name
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source_node_uuid:
type: string
description: UUID of the source node
target_node_uuid:
type: string
description: UUID of the target node
uuid:
type: string
description: UUID of the edge
valid_at:
type: string
description: Datetime of when the fact became true
required:
- created_at
- fact
- name
- source_node_uuid
- target_node_uuid
- uuid
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.node.get_edges(
node_uuid="node_uuid",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.node.getEdges("node_uuid");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Node.GetEdges(
context.TODO(),
"node_uuid",
)
```
# Get Episodes for a node
GET https://api.getzep.com/api/v2/graph/node/{node_uuid}/episodes
Returns all episodes that mentioned a given node
Reference: https://help.getzep.com/sdk-reference/graph/node/get-episodes-for-a-node
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Episodes for a node
version: endpoint_entity.getEpisodesForANode
paths:
/graph/node/{node_uuid}/episodes:
get:
operationId: get-episodes-for-a-node
summary: Get Episodes for a node
description: Returns all episodes that mentioned a given node
tags:
- - subpackage_entity
parameters:
- name: node_uuid
in: path
description: Node UUID
required: true
schema:
type: string
responses:
'200':
description: Episodes
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GraphEpisodeResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.RoleType:
type: string
enum:
- value: norole
- value: system
- value: assistant
- value: user
- value: function
- value: tool
models.GraphDataType:
type: string
enum:
- value: text
- value: json
- value: message
apidata.GraphEpisode:
type: object
properties:
content:
type: string
created_at:
type: string
metadata:
type: object
additionalProperties:
description: Any type
processed:
type: boolean
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
role:
type: string
description: >-
Optional role, will only be present if the episode was created using
memory.add API
role_type:
$ref: '#/components/schemas/apidata.RoleType'
description: >-
Optional role_type, will only be present if the episode was created
using memory.add API
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
source:
$ref: '#/components/schemas/models.GraphDataType'
source_description:
type: string
task_id:
type: string
description: >-
Optional task ID to poll episode processing status. Currently only
available for batch ingestion.
thread_id:
type: string
description: >-
Optional thread ID, will be present if the episode is part of a
thread
uuid:
type: string
required:
- content
- created_at
- uuid
apidata.GraphEpisodeResponse:
type: object
properties:
episodes:
type: array
items:
$ref: '#/components/schemas/apidata.GraphEpisode'
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.node.get_episodes(
node_uuid="node_uuid",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.node.getEpisodes("node_uuid");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Node.GetEpisodes(
context.TODO(),
"node_uuid",
)
```
# Get Node
GET https://api.getzep.com/api/v2/graph/node/{uuid}
Returns a specific node by its UUID.
Reference: https://help.getzep.com/sdk-reference/graph/node/get-node
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Node
version: endpoint_entity.getNode
paths:
/graph/node/{uuid}:
get:
operationId: get-node
summary: Get Node
description: Returns a specific node by its UUID.
tags:
- - subpackage_entity
parameters:
- name: uuid
in: path
description: Node UUID
required: true
schema:
type: string
responses:
'200':
description: Node
content:
application/json:
schema:
$ref: '#/components/schemas/graphiti.EntityNode'
'400':
description: Bad Request
content: {}
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
graphiti.EntityNode:
type: object
properties:
attributes:
type: object
additionalProperties:
description: Any type
description: Additional attributes of the node. Dependent on node labels
created_at:
type: string
description: Creation time of the node
labels:
type: array
items:
type: string
description: Labels associated with the node
name:
type: string
description: Name of the node
relevance:
type: number
format: double
description: >-
Relevance is an experimental rank-aligned score in [0,1] derived
from Score via logit transformation.
Only populated when using cross_encoder reranker; omitted for other
reranker types (e.g., RRF).
score:
type: number
format: double
description: >-
Score is the reranker output: sigmoid-distributed logits [0,1] when
using cross_encoder reranker, or RRF ordinal rank when using rrf
reranker
summary:
type: string
description: Regional summary of surrounding edges
uuid:
type: string
description: UUID of the node
required:
- created_at
- name
- summary
- uuid
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.node.get(
uuid_="uuid",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.node.get("uuid");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Node.Get(
context.TODO(),
"uuid",
)
```
# Retrieves project information
GET https://api.getzep.com/api/v2/projects/info
Retrieve project info based on the provided api key.
Reference: https://help.getzep.com/sdk-reference/project/retrieves-project-information
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Retrieves project information
version: endpoint_project.retrievesProjectInformation
paths:
/projects/info:
get:
operationId: retrieves-project-information
summary: Retrieves project information
description: Retrieve project info based on the provided api key.
tags:
- - subpackage_project
parameters: []
responses:
'200':
description: Retrieved
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.ProjectInfoResponse'
'400':
description: Bad Request
content: {}
'404':
description: Not Found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.ProjectInfo:
type: object
properties:
created_at:
type: string
description:
type: string
name:
type: string
uuid:
type: string
apidata.ProjectInfoResponse:
type: object
properties:
project:
$ref: '#/components/schemas/apidata.ProjectInfo'
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.project.get()
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.project.get();
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Project.Get(
context.TODO(),
)
```
# Delete Node
DELETE https://api.getzep.com/api/v2/graph/node/{uuid}
Deletes a node by UUID.
Reference: https://help.getzep.com/sdk-reference/entity/delete-node
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Delete Node
version: endpoint_entity.deleteNode
paths:
/graph/node/{uuid}:
delete:
operationId: delete-node
summary: Delete Node
description: Deletes a node by UUID.
tags:
- - subpackage_entity
parameters:
- name: uuid
in: path
description: Node UUID
required: true
schema:
type: string
responses:
'200':
description: Node deleted
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.SuccessResponse'
'400':
description: Bad Request
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.SuccessResponse:
type: object
properties:
message:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.graph.node.delete(
uuid_="uuid",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.graph.node.delete("uuid");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Graph.Node.Delete(
context.TODO(),
"uuid",
)
```
# Get Task
GET https://api.getzep.com/api/v2/tasks/{task_id}
Gets a task by its ID
Reference: https://help.getzep.com/sdk-reference/task/get-task
## OpenAPI Specification
```yaml
openapi: 3.1.1
info:
title: Get Task
version: endpoint_task.getTask
paths:
/tasks/{task_id}:
get:
operationId: get-task
summary: Get Task
description: Gets a task by its ID
tags:
- - subpackage_task
parameters:
- name: task_id
in: path
description: Task ID
required: true
schema:
type: string
responses:
'200':
description: Task
content:
application/json:
schema:
$ref: '#/components/schemas/apidata.GetTaskResponse'
'404':
description: Task not found
content: {}
'500':
description: Internal Server Error
content: {}
components:
schemas:
apidata.TaskErrorResponse:
type: object
properties:
code:
type: string
details:
type: object
additionalProperties:
description: Any type
message:
type: string
apidata.TaskProgress:
type: object
properties:
message:
type: string
stage:
type: string
apidata.GetTaskResponse:
type: object
properties:
completed_at:
type: string
created_at:
type: string
error:
$ref: '#/components/schemas/apidata.TaskErrorResponse'
progress:
$ref: '#/components/schemas/apidata.TaskProgress'
started_at:
type: string
status:
type: string
task_id:
type: string
type:
type: string
updated_at:
type: string
```
## SDK Code Examples
```python
from zep_cloud import Zep
client = Zep(
api_key="YOUR_API_KEY",
)
client.task.get(
task_id="task_id",
)
```
```typescript
import { ZepClient } from "zep-cloud";
const client = new ZepClient({ apiKey: "YOUR_API_KEY" });
await client.task.get("task_id");
```
```go
import (
context "context"
option "github.com/getzep/zep-go/v3/option"
v3client "github.com/getzep/zep-go/v3/client"
)
client := v3client.NewClient(
option.WithAPIKey(
"",
),
)
response, err := client.Task.Get(
context.TODO(),
"task_id",
)
```
# Welcome to Graphiti!
Want to use Graphiti with AI assistants like Claude Desktop or Cursor? Check out the [Knowledge Graph MCP Server](/graphiti/getting-started/mcp-server).
Graphiti is a Python framework for building temporally-aware knowledge graphs designed for AI agents. It enables real-time incremental updates to knowledge graphs without batch recomputation, making it suitable for dynamic environments where relationships and information evolve over time.
Learn about Graphiti's core concepts and how temporal knowledge graphs work.
Get up and running with Graphiti in minutes including installation, episodes, search, and basic operations.
Use Graphiti with AI assistants like Claude Desktop or Cursor.
Learn how to add text and JSON episodes to build your knowledge graph.
Discover hybrid search capabilities combining semantic, keyword, and graph-based retrieval.
Define domain-specific entity types for more precise knowledge representation.
# Overview
> Temporal Knowledge Graphs for Agentic Applications
Graphiti helps you create and query Knowledge Graphs that evolve over time. A
knowledge graph is a network of interconnected facts, such as *“Kendra loves
Adidas shoes.”* Each fact is a *“triplet”* represented by two entities, or
nodes (*”Kendra”, “Adidas shoes”*), and their relationship, or edge
(*”loves”*).
Knowledge Graphs have been explored extensively for information retrieval.
What makes Graphiti unique is its ability to autonomously build a knowledge
graph while handling changing relationships and maintaining historical
context.

Graphiti builds dynamic, temporally-aware knowledge graphs that represent complex, evolving relationships between entities over time. It ingests both unstructured and structured data, and the resulting graph may be queried using a fusion of time, full-text, semantic, and graph algorithm approaches.
With Graphiti, you can build LLM applications such as:
* Assistants that learn from user interactions, fusing personal knowledge with dynamic data from business systems like CRMs and billing platforms.
* Agents that autonomously execute complex tasks, reasoning with state changes from multiple dynamic sources.
Graphiti supports a wide range of applications in sales, customer service, health, finance, and more, enabling long-term recall and state-based reasoning for both assistants and agents.
## Graphiti and Zep
Graphiti powers the core of [Zep's context layer](https://www.getzep.com) for LLM-powered Assistants and Agents.
We're excited to open-source Graphiti, believing its potential reaches far beyond context applications.
## Why Graphiti?
We were intrigued by Microsoft’s GraphRAG, which expanded on RAG text chunking by using a graph to better model a document corpus and making this representation available via semantic and graph search techniques. However, GraphRAG did not address our core problem: It's primarily designed for static documents and doesn't inherently handle temporal aspects of data.
Graphiti is designed from the ground up to handle constantly changing information, hybrid semantic and graph search, and scale:
* **Temporal Awareness:** Tracks changes in facts and relationships over time, enabling point-in-time queries. Graph edges include temporal metadata to record relationship lifecycles.
* **Episodic Processing:** Ingests data as discrete episodes, maintaining data provenance and allowing incremental entity and relationship extraction.
* **Custom Entity Types:** Supports defining domain-specific entity types, enabling more precise knowledge representation for specialized applications.
* **Hybrid Search:** Combines semantic and BM25 full-text search, with the ability to rerank results by distance from a central node e.g. "Kendra".
* **Scalable:** Designed for processing large datasets, with parallelization of LLM calls for bulk processing while preserving the chronology of events.
* **Supports Varied Sources:** Can ingest both unstructured text and structured JSON data.
| Aspect | GraphRAG | Graphiti |
| -------------------------- | ------------------------------------- | ------------------------------------------------ |
| **Primary Use** | Static document summarization | Dynamic data management |
| **Data Handling** | Batch-oriented processing | Continuous, incremental updates |
| **Knowledge Structure** | Entity clusters & community summaries | Episodic data, semantic entities, communities |
| **Retrieval Method** | Sequential LLM summarization | Hybrid semantic, keyword, and graph-based search |
| **Adaptability** | Low | High |
| **Temporal Handling** | Basic timestamp tracking | Explicit bi-temporal tracking |
| **Contradiction Handling** | LLM-driven summarization judgments | Temporal edge invalidation |
| **Query Latency** | Seconds to tens of seconds | Typically sub-second latency |
| **Custom Entity Types** | No | Yes, customizable |
| **Scalability** | Moderate | High, optimized for large datasets |
Graphiti is specifically designed to address the challenges of dynamic and frequently updated datasets, making it particularly suitable for applications requiring real-time interaction and precise historical queries.

# Quick Start
> Getting started with Graphiti
For complete working examples, check out the [Graphiti Quickstart Examples](https://github.com/getzep/graphiti/tree/main/examples/quickstart) on GitHub.
## Installation
Requirements:
* Python 3.10 or higher
* Neo4j 5.26 or higher or FalkorDB 1.1.2 or higher (see [Graph Database Configuration](/graphiti/configuration/graph-db-configuration) for setup options)
* OpenAI API key (Graphiti defaults to OpenAI for LLM inference and embedding)
The simplest way to install Neo4j is via [Neo4j Desktop](https://neo4j.com/download/). It provides a user-friendly interface to manage Neo4j instances and databases.
```bash
pip install graphiti-core
```
or
```bash
uv add graphiti-core
```
### Alternative LLM Providers
While Graphiti defaults to OpenAI, it supports multiple LLM providers including Azure OpenAI, Google Gemini, Anthropic, Groq, and local models via Ollama. For detailed configuration instructions, see our [LLM Configuration](/graphiti/configuration/llm-configuration) guide.
### Default to Slower, Low Concurrency; LLM Provider 429 Rate Limit Errors
Graphiti's ingestion pipelines are designed for high concurrency. By default, concurrency is set low to avoid LLM Provider 429 Rate Limit Errors. If you find Graphiti slow, please increase concurrency as described below.
Concurrency controlled by the `SEMAPHORE_LIMIT` environment variable. By default, `SEMAPHORE_LIMIT` is set to `10` concurrent operations to help prevent `429` rate limit errors from your LLM provider. If you encounter such errors, try lowering this value.
If your LLM provider allows higher throughput, you can increase `SEMAPHORE_LIMIT` to boost episode ingestion performance.
### Environment Variables
Set your OpenAI API key:
```bash
export OPENAI_API_KEY=your_openai_api_key_here
```
#### Optional Variables
* `GRAPHITI_TELEMETRY_ENABLED`: Set to `false` to disable anonymous telemetry collection
## Getting Started with Graphiti
For a comprehensive overview of Graphiti and its capabilities, check out the [Overview](/graphiti/getting-started/overview) page.
### Required Imports
First, import the necessary libraries for working with Graphiti:
```python
import asyncio
import json
import logging
import os
from datetime import datetime, timezone
from logging import INFO
from dotenv import load_dotenv
from graphiti_core import Graphiti
from graphiti_core.nodes import EpisodeType
from graphiti_core.search.search_config_recipes import NODE_HYBRID_SEARCH_RRF
```
### Configuration
Graphiti uses OpenAI by default for LLM inference and embedding. Ensure that an `OPENAI_API_KEY` is set in your environment. Support for multiple LLM providers is available - see our [LLM Configuration](/graphiti/configuration/llm-configuration) guide.
Graphiti also requires Neo4j connection parameters. Set the following environment variables:
* `NEO4J_URI`: The URI of your Neo4j database (default: bolt://localhost:7687)
* `NEO4J_USER`: Your Neo4j username (default: neo4j)
* `NEO4J_PASSWORD`: Your Neo4j password
For detailed database setup instructions, see our [Graph Database Configuration](/graphiti/configuration/graph-db-configuration) guide.
Set up logging and environment variables for connecting to the Neo4j database:
```python
# Configure logging
logging.basicConfig(
level=INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
)
logger = logging.getLogger(__name__)
load_dotenv()
# Neo4j connection parameters
# Make sure Neo4j Desktop is running with a local DBMS started
neo4j_uri = os.environ.get('NEO4J_URI', 'bolt://localhost:7687')
neo4j_user = os.environ.get('NEO4J_USER', 'neo4j')
neo4j_password = os.environ.get('NEO4J_PASSWORD', 'password')
if not neo4j_uri or not neo4j_user or not neo4j_password:
raise ValueError('NEO4J_URI, NEO4J_USER, and NEO4J_PASSWORD must be set')
```
### Main Function
Create an async main function to run all Graphiti operations:
```python
async def main():
# Main function implementation will go here
pass
if __name__ == '__main__':
asyncio.run(main())
```
### Initialization
Connect to Neo4j and set up Graphiti indices. This is required before using other Graphiti functionality:
```python
# Initialize Graphiti with Neo4j connection
graphiti = Graphiti(neo4j_uri, neo4j_user, neo4j_password)
try:
# Initialize the graph database with graphiti's indices. This only needs to be done once.
await graphiti.build_indices_and_constraints()
# Additional code will go here
finally:
# Close the connection
await graphiti.close()
print('\nConnection closed')
```
### Adding Episodes
Episodes are the primary units of information in Graphiti. They can be text or structured JSON and are automatically processed to extract entities and relationships. For more detailed information on episodes and bulk loading, see the [Adding Episodes](/graphiti/core-concepts/adding-episodes) page:
```python
# Episodes list containing both text and JSON episodes
episodes = [
{
'content': 'Kamala Harris is the Attorney General of California. She was previously '
'the district attorney for San Francisco.',
'type': EpisodeType.text,
'description': 'podcast transcript',
},
{
'content': 'As AG, Harris was in office from January 3, 2011 – January 3, 2017',
'type': EpisodeType.text,
'description': 'podcast transcript',
},
{
'content': {
'name': 'Gavin Newsom',
'position': 'Governor',
'state': 'California',
'previous_role': 'Lieutenant Governor',
'previous_location': 'San Francisco',
},
'type': EpisodeType.json,
'description': 'podcast metadata',
},
{
'content': {
'name': 'Gavin Newsom',
'position': 'Governor',
'term_start': 'January 7, 2019',
'term_end': 'Present',
},
'type': EpisodeType.json,
'description': 'podcast metadata',
},
]
# Add episodes to the graph
for i, episode in enumerate(episodes):
await graphiti.add_episode(
name=f'Freakonomics Radio {i}',
episode_body=episode['content']
if isinstance(episode['content'], str)
else json.dumps(episode['content']),
source=episode['type'],
source_description=episode['description'],
reference_time=datetime.now(timezone.utc),
)
print(f'Added episode: Freakonomics Radio {i} ({episode["type"].value})')
```
### Basic Search
The simplest way to retrieve relationships (edges) from Graphiti is using the search method, which performs a hybrid search combining semantic similarity and BM25 text retrieval. For more details on search capabilities, see the [Searching the Graph](/graphiti/working-with-data/searching) page:
```python
# Perform a hybrid search combining semantic similarity and BM25 retrieval
print("\nSearching for: 'Who was the California Attorney General?'")
results = await graphiti.search('Who was the California Attorney General?')
# Print search results
print('\nSearch Results:')
for result in results:
print(f'UUID: {result.uuid}')
print(f'Fact: {result.fact}')
if hasattr(result, 'valid_at') and result.valid_at:
print(f'Valid from: {result.valid_at}')
if hasattr(result, 'invalid_at') and result.invalid_at:
print(f'Valid until: {result.invalid_at}')
print('---')
```
### Center Node Search
For more contextually relevant results, you can use a center node to rerank search results based on their graph distance to a specific node. This is particularly useful for entity-specific queries as described in the [Searching the Graph](/graphiti/working-with-data/searching) page:
```python
# Use the top search result's UUID as the center node for reranking
if results and len(results) > 0:
# Get the source node UUID from the top result
center_node_uuid = results[0].source_node_uuid
print('\nReranking search results based on graph distance:')
print(f'Using center node UUID: {center_node_uuid}')
reranked_results = await graphiti.search(
'Who was the California Attorney General?', center_node_uuid=center_node_uuid
)
# Print reranked search results
print('\nReranked Search Results:')
for result in reranked_results:
print(f'UUID: {result.uuid}')
print(f'Fact: {result.fact}')
if hasattr(result, 'valid_at') and result.valid_at:
print(f'Valid from: {result.valid_at}')
if hasattr(result, 'invalid_at') and result.invalid_at:
print(f'Valid until: {result.invalid_at}')
print('---')
else:
print('No results found in the initial search to use as center node.')
```
### Node Search Using Search Recipes
Graphiti provides predefined search recipes optimized for different search scenarios. Here we use NODE\_HYBRID\_SEARCH\_RRF for retrieving nodes directly instead of edges. For a complete list of available search recipes and reranking approaches, see the [Configurable Search Strategies](/graphiti/working-with-data/searching#configurable-search-strategies) section in the Searching documentation:
```python
# Example: Perform a node search using _search method with standard recipes
print(
'\nPerforming node search using _search method with standard recipe NODE_HYBRID_SEARCH_RRF:'
)
# Use a predefined search configuration recipe and modify its limit
node_search_config = NODE_HYBRID_SEARCH_RRF.model_copy(deep=True)
node_search_config.limit = 5 # Limit to 5 results
# Execute the node search
node_search_results = await graphiti._search(
query='California Governor',
config=node_search_config,
)
# Print node search results
print('\nNode Search Results:')
for node in node_search_results.nodes:
print(f'Node UUID: {node.uuid}')
print(f'Node Name: {node.name}')
node_summary = node.summary[:100] + '...' if len(node.summary) > 100 else node.summary
print(f'Content Summary: {node_summary}')
print(f"Node Labels: {', '.join(node.labels)}")
print(f'Created At: {node.created_at}')
if hasattr(node, 'attributes') and node.attributes:
print('Attributes:')
for key, value in node.attributes.items():
print(f' {key}: {value}')
print('---')
```
### Complete Example
For a complete working example that puts all these concepts together, check out the [Graphiti Quickstart Examples](https://github.com/getzep/graphiti/tree/main/examples/quickstart) on GitHub.
## Next Steps
Now that you've learned the basics of Graphiti, you can explore more advanced features:
* [Custom Entity and Edge Types](/graphiti/core-concepts/custom-entity-and-edge-types): Learn how to define and use custom entity and edge types to better model your domain-specific knowledge
* [Communities](/graphiti/core-concepts/communities): Discover how to work with communities, which are groups of related nodes that share common attributes or relationships
* [Advanced Search Techniques](/graphiti/working-with-data/searching): Explore more sophisticated search strategies, including different reranking approaches and configurable search recipes
* [Adding Fact Triples](/graphiti/working-with-data/adding-fact-triples): Learn how to directly add fact triples to your graph for more precise knowledge representation
* [Agent Integration](/graphiti/integrations/lang-graph-agent): Discover how to integrate Graphiti with LLM agents for more powerful AI applications
Make sure to run await statements within an [async function](https://docs.python.org/3/library/asyncio-task.html).
# Knowledge Graph MCP Server
> A Knowledge Graph MCP Server for AI Assistants
The Graphiti MCP Server is an experimental implementation that exposes Graphiti's key functionality through the Model Context Protocol (MCP). This enables AI assistants like Claude Desktop, Cursor, and VS Code with Copilot to interact with Graphiti's knowledge graph capabilities, providing persistent context and contextual awareness.
The Graphiti MCP Server bridges AI assistants with Graphiti's temporally-aware knowledge graphs, allowing assistants to maintain persistent context across conversations and sessions. Unlike traditional RAG methods, it continuously integrates user interactions, structured and unstructured data, and external information into a coherent, queryable graph.
## Key Features
The MCP server exposes Graphiti's core capabilities:
* **Episode Management**: Add, retrieve, and delete episodes (text, messages, or JSON data)
* **Entity Management**: Search and manage entity nodes and relationships
* **Search Capabilities**: Semantic and hybrid search for facts and node summaries
* **Group Management**: Organize data with group\_id filtering for multi-user scenarios
* **Graph Maintenance**: Clear graphs and rebuild indices as needed
* **Pre-configured Entity Types**: Structured entity extraction for domain-specific use cases
* **Multiple Database Support**: FalkorDB (Redis-based, default) and Neo4j
* **Flexible LLM Providers**: OpenAI, Anthropic, Gemini, Groq, and Azure OpenAI
* **Multiple Embedding Options**: OpenAI, Voyage, Sentence Transformers, and Gemini
## Quick Start
This quick start uses OpenAI and FalkorDB (default). The server supports multiple LLM providers (OpenAI, Anthropic, Gemini, Grogu, Azure OpenAI) and databases (FalkorDB, Neo4j). For detailed configuration options, see the [MCP Server README](https://github.com/getzep/graphiti/blob/main/mcp_server/README.md).
### Prerequisites
Before getting started, ensure you have:
1. **Python 3.10+** installed on your system
2. **Database** - Either FalkorDB (default, Redis-based) or Neo4j (5.26+) running locally or accessible remotely
3. **LLM API key** - For OpenAI, Anthropic, Gemini, Groq, or Azure OpenAI
### Installation
1. Clone the Graphiti repository:
```bash
git clone https://github.com/getzep/graphiti.git
cd graphiti
```
2. Navigate to the MCP server directory and install dependencies:
```bash
cd mcp_server
uv sync
```
### Configuration
Configuration follows a precedence hierarchy: command-line arguments override environment variables, which override `config.yaml` settings.
Set up your environment variables in a `.env` file:
```bash
# Required LLM Configuration
OPENAI_API_KEY=your_openai_api_key_here
MODEL_NAME=gpt-4o-mini
# Database Configuration (FalkorDB is default, or use Neo4j)
# For FalkorDB (Redis-based):
# REDIS_HOST=localhost
# REDIS_PORT=6379
# REDIS_PASSWORD=your_redis_password
# For Neo4j:
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your_neo4j_password
# Optional: Disable telemetry
# GRAPHITI_TELEMETRY_ENABLED=false
```
### Running the Server
Start the MCP server:
```bash
uv run graphiti_mcp_server.py
```
For development with custom options:
```bash
uv run graphiti_mcp_server.py --model gpt-4o-mini --transport sse --group-id my-project
```
## MCP Client Integration
The MCP server supports integration with multiple AI assistants through different transport protocols.
### Claude Desktop
Configure Claude Desktop to connect via the stdio transport:
```json
{
"mcpServers": {
"graphiti-context": {
"transport": "stdio",
"command": "/path/to/uv",
"args": [
"run",
"--directory",
"/path/to/graphiti/mcp_server",
"graphiti_mcp_server.py",
"--transport",
"stdio"
],
"env": {
"OPENAI_API_KEY": "your_api_key",
"MODEL_NAME": "gpt-4o-mini",
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USER": "neo4j",
"NEO4J_PASSWORD": "your_password"
}
}
}
}
```
### Cursor IDE
For Cursor, use the SSE transport configuration:
```json
{
"mcpServers": {
"graphiti-context": {
"url": "http://localhost:8000/sse"
}
}
}
```
### VS Code with Copilot
VS Code with Copilot can connect to the MCP server using HTTP endpoints. Configure your VS Code settings to point to the running MCP server.
## Available Tools
Once connected, AI assistants have access to these Graphiti tools:
* `add_episode` - Store episodes and interactions in the knowledge graph
* `search_facts` - Find relevant facts and relationships
* `search_nodes` - Search for entity summaries and information
* `get_episodes` - Retrieve recent episodes for context
* `delete_episode` - Remove episodes from the graph
* `clear_graph` - Reset the knowledge graph entirely
## Docker Deployment
For containerized deployment, use the provided Docker Compose setup:
```bash
docker compose up
```
This starts both the database (FalkorDB or Neo4j) and the MCP server with SSE transport enabled. Docker Compose can launch services in unified or separate containers with sensible defaults for immediate use.
## Performance and Privacy
### Performance Tuning
Episode processing uses asynchronous queuing with concurrency controlled by `SEMAPHORE_LIMIT`. The MCP server README provides tier-specific guidelines for major LLM providers to prevent rate-limiting while maximizing throughput.
### Telemetry
The framework includes optional anonymous telemetry collection that captures only system information. Telemetry never exposes API keys or graph content. Disable telemetry by setting:
```bash
GRAPHITI_TELEMETRY_ENABLED=false
```
## Next Steps
For comprehensive configuration options, advanced features, and troubleshooting:
* **Full Documentation**: See the complete [MCP Server README](https://github.com/getzep/graphiti/blob/main/mcp_server/README.md)
* **Integration Examples**: Explore client-specific setup guides for Claude Desktop, Cursor, and VS Code
* **Custom Entity Types**: Configure pre-configured entity types for domain-specific extraction
* **Multi-tenant Setup**: Use group IDs for organizing data across different contexts
* **Alternative LLM Providers**: Configure Anthropic, Gemini, Groq, or Azure OpenAI
* **Database Options**: Switch between FalkorDB and Neo4j based on your needs
The MCP server is experimental and under active development. Features and APIs may change between releases.
# LLM Configuration
> Configure Graphiti with different LLM providers
Graphiti works best with LLM services that support Structured Output (such as OpenAI and Gemini). Using other services may result in incorrect output schemas and ingestion failures, particularly when using smaller models.
Graphiti defaults to using OpenAI for LLM inference and embeddings, but supports multiple LLM providers including Azure OpenAI, Google Gemini, Anthropic, Groq, and local models via Ollama. This guide covers configuring Graphiti with alternative LLM providers.
## Azure OpenAI
**Azure OpenAI v1 API Opt-in Required for Structured Outputs**
Graphiti uses structured outputs via the `client.beta.chat.completions.parse()` method, which requires Azure OpenAI deployments to opt into the v1 API. Without this opt-in, you'll encounter 404 Resource not found errors during episode ingestion.
To enable v1 API support in your Azure OpenAI deployment, follow Microsoft's guide: [Azure OpenAI API version lifecycle](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/api-version-lifecycle?tabs=key#api-evolution).
Azure OpenAI deployments often require different endpoints for LLM and embedding services, and separate deployments for default and small models.
### Installation
```bash
pip install graphiti-core
```
### Configuration
```python
from openai import AsyncAzureOpenAI
from graphiti_core import Graphiti
from graphiti_core.llm_client import LLMConfig, OpenAIClient
from graphiti_core.embedder.openai import OpenAIEmbedder, OpenAIEmbedderConfig
from graphiti_core.cross_encoder.openai_reranker_client import OpenAIRerankerClient
# Azure OpenAI configuration - use separate endpoints for different services
api_key = ""
api_version = ""
llm_endpoint = "" # e.g., "https://your-llm-resource.openai.azure.com/"
embedding_endpoint = "" # e.g., "https://your-embedding-resource.openai.azure.com/"
# Create separate Azure OpenAI clients for different services
llm_client_azure = AsyncAzureOpenAI(
api_key=api_key,
api_version=api_version,
azure_endpoint=llm_endpoint
)
embedding_client_azure = AsyncAzureOpenAI(
api_key=api_key,
api_version=api_version,
azure_endpoint=embedding_endpoint
)
# Create LLM Config with your Azure deployment names
azure_llm_config = LLMConfig(
small_model="gpt-4.1-nano",
model="gpt-4.1-mini",
)
# Initialize Graphiti with Azure OpenAI clients
graphiti = Graphiti(
"bolt://localhost:7687",
"neo4j",
"password",
llm_client=OpenAIClient(
config=azure_llm_config,
client=llm_client_azure
),
embedder=OpenAIEmbedder(
config=OpenAIEmbedderConfig(
embedding_model="text-embedding-3-small-deployment" # Your Azure embedding deployment name
),
client=embedding_client_azure
),
cross_encoder=OpenAIRerankerClient(
config=LLMConfig(
model=azure_llm_config.small_model # Use small model for reranking
),
client=llm_client_azure
)
)
```
Make sure to replace the placeholder values with your actual Azure OpenAI credentials and deployment names.
### Environment Variables
Azure OpenAI can also be configured using environment variables:
* `AZURE_OPENAI_ENDPOINT` - Azure OpenAI LLM endpoint URL
* `AZURE_OPENAI_DEPLOYMENT_NAME` - Azure OpenAI LLM deployment name
* `AZURE_OPENAI_API_VERSION` - Azure OpenAI API version
* `AZURE_OPENAI_EMBEDDING_API_KEY` - Azure OpenAI Embedding deployment key (if different from `OPENAI_API_KEY`)
* `AZURE_OPENAI_EMBEDDING_ENDPOINT` - Azure OpenAI Embedding endpoint URL
* `AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME` - Azure OpenAI embedding deployment name
* `AZURE_OPENAI_EMBEDDING_API_VERSION` - Azure OpenAI embedding API version
* `AZURE_OPENAI_USE_MANAGED_IDENTITY` - Use Azure Managed Identities for authentication
## Google Gemini
Google's Gemini models provide excellent structured output support and can be used for LLM inference, embeddings, and cross-encoding/reranking.
### Installation
```bash
pip install "graphiti-core[google-genai]"
```
### Configuration
```python
from graphiti_core import Graphiti
from graphiti_core.llm_client.gemini_client import GeminiClient, LLMConfig
from graphiti_core.embedder.gemini import GeminiEmbedder, GeminiEmbedderConfig
from graphiti_core.cross_encoder.gemini_reranker_client import GeminiRerankerClient
# Google API key configuration
api_key = ""
# Initialize Graphiti with Gemini clients
graphiti = Graphiti(
"bolt://localhost:7687",
"neo4j",
"password",
llm_client=GeminiClient(
config=LLMConfig(
api_key=api_key,
model="gemini-2.0-flash"
)
),
embedder=GeminiEmbedder(
config=GeminiEmbedderConfig(
api_key=api_key,
embedding_model="embedding-001"
)
),
cross_encoder=GeminiRerankerClient(
config=LLMConfig(
api_key=api_key,
model="gemini-2.0-flash-exp"
)
)
)
```
The Gemini reranker uses the `gemini-2.0-flash-exp` model by default, which is optimized for cost-effective and low-latency classification tasks.
### Environment Variables
Google Gemini can be configured using:
* `GOOGLE_API_KEY` - Your Google API key
## Anthropic
Anthropic's Claude models can be used for LLM inference with OpenAI embeddings and reranking.
When using Anthropic for LLM inference, you still need an OpenAI API key for embeddings and reranking functionality. Make sure to set both `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` environment variables.
### Installation
```bash
pip install "graphiti-core[anthropic]"
```
### Configuration
```python
from graphiti_core import Graphiti
from graphiti_core.llm_client.anthropic_client import AnthropicClient, LLMConfig
from graphiti_core.embedder.openai import OpenAIEmbedder, OpenAIEmbedderConfig
from graphiti_core.cross_encoder.openai_reranker_client import OpenAIRerankerClient
# Configure Anthropic LLM with OpenAI embeddings and reranking
graphiti = Graphiti(
"bolt://localhost:7687",
"neo4j",
"password",
llm_client=AnthropicClient(
config=LLMConfig(
api_key="",
model="claude-sonnet-4-20250514",
small_model="claude-3-5-haiku-20241022"
)
),
embedder=OpenAIEmbedder(
config=OpenAIEmbedderConfig(
api_key="",
embedding_model="text-embedding-3-small"
)
),
cross_encoder=OpenAIRerankerClient(
config=LLMConfig(
api_key="",
model="gpt-4.1-nano" # Use a smaller model for reranking
)
)
)
```
### Environment Variables
Anthropic can be configured using:
* `ANTHROPIC_API_KEY` - Your Anthropic API key
* `OPENAI_API_KEY` - Required for embeddings and reranking
## Groq
Groq provides fast inference with various open-source models, using OpenAI for embeddings and reranking.
When using Groq, avoid smaller models as they may not accurately extract data or output the correct JSON structures required by Graphiti. Use larger, more capable models like Llama 3.1 70B for best results.
### Installation
```bash
pip install "graphiti-core[groq]"
```
### Configuration
```python
from graphiti_core import Graphiti
from graphiti_core.llm_client.groq_client import GroqClient, LLMConfig
from graphiti_core.embedder.openai import OpenAIEmbedder, OpenAIEmbedderConfig
from graphiti_core.cross_encoder.openai_reranker_client import OpenAIRerankerClient
# Configure Groq LLM with OpenAI embeddings and reranking
graphiti = Graphiti(
"bolt://localhost:7687",
"neo4j",
"password",
llm_client=GroqClient(
config=LLMConfig(
api_key="",
model="llama-3.1-70b-versatile",
small_model="llama-3.1-8b-instant"
)
),
embedder=OpenAIEmbedder(
config=OpenAIEmbedderConfig(
api_key="",
embedding_model="text-embedding-3-small"
)
),
cross_encoder=OpenAIRerankerClient(
config=LLMConfig(
api_key="",
model="gpt-4.1-nano" # Use a smaller model for reranking
)
)
)
```
### Environment Variables
Groq can be configured using:
* `GROQ_API_KEY` - Your Groq API key
* `OPENAI_API_KEY` - Required for embeddings
## Ollama (Local LLMs)
Ollama enables running local LLMs and embedding models via its OpenAI-compatible API, ideal for privacy-focused applications or avoiding API costs.
When using Ollama, avoid smaller local models as they may not accurately extract data or output the correct JSON structures required by Graphiti. Use larger, more capable models and ensure they support structured output for reliable knowledge graph construction.
Ollama provides an OpenAI-compatible API, but does not support the `/v1/responses` endpoint that `OpenAIClient` uses. Use `OpenAIGenericClient` instead, which uses the `/v1/chat/completions` endpoint with `response_format` for structured outputs—both of which Ollama supports.
### Installation
First, install and configure Ollama:
```bash
# Install Ollama (visit https://ollama.ai for installation instructions)
# Then pull the models you want to use:
ollama pull deepseek-r1:7b # LLM
ollama pull nomic-embed-text # embeddings
```
### Configuration
```python
from graphiti_core import Graphiti
from graphiti_core.llm_client.config import LLMConfig
from graphiti_core.llm_client.openai_generic_client import OpenAIGenericClient
from graphiti_core.embedder.openai import OpenAIEmbedder, OpenAIEmbedderConfig
from graphiti_core.cross_encoder.openai_reranker_client import OpenAIRerankerClient
# Configure Ollama LLM client using OpenAIGenericClient for compatibility
llm_config = LLMConfig(
api_key="ollama", # Ollama doesn't require a real API key
model="deepseek-r1:7b",
small_model="deepseek-r1:7b",
base_url="http://localhost:11434/v1",
)
llm_client = OpenAIGenericClient(config=llm_config)
# Initialize Graphiti with Ollama clients
graphiti = Graphiti(
"bolt://localhost:7687",
"neo4j",
"password",
llm_client=llm_client,
embedder=OpenAIEmbedder(
config=OpenAIEmbedderConfig(
api_key="ollama",
embedding_model="nomic-embed-text",
embedding_dim=768,
base_url="http://localhost:11434/v1",
)
),
cross_encoder=OpenAIRerankerClient(client=llm_client, config=llm_config),
)
```
Ensure Ollama is running (`ollama serve`) and that you have pulled the models you want to use.
## OpenAI Compatible Services
Many LLM providers offer OpenAI-compatible APIs. Use the `OpenAIGenericClient` for these services, which ensures proper schema injection for JSON output since most providers don't support OpenAI's structured output format.
When using OpenAI-compatible services, avoid smaller models as they may not accurately extract data or output the correct JSON structures required by Graphiti. Choose larger, more capable models that can handle complex reasoning and structured output.
### Installation
```bash
pip install graphiti-core
```
### Configuration
```python
from graphiti_core import Graphiti
from graphiti_core.llm_client.openai_generic_client import OpenAIGenericClient
from graphiti_core.llm_client.config import LLMConfig
from graphiti_core.embedder.openai import OpenAIEmbedder, OpenAIEmbedderConfig
from graphiti_core.cross_encoder.openai_reranker_client import OpenAIRerankerClient
# Configure OpenAI-compatible service
llm_config = LLMConfig(
api_key="",
model="", # e.g., "mistral-large-latest"
small_model="", # e.g., "mistral-small-latest"
base_url="", # e.g., "https://api.mistral.ai/v1"
)
# Initialize Graphiti with OpenAI-compatible service
graphiti = Graphiti(
"bolt://localhost:7687",
"neo4j",
"password",
llm_client=OpenAIGenericClient(config=llm_config),
embedder=OpenAIEmbedder(
config=OpenAIEmbedderConfig(
api_key="",
embedding_model="", # e.g., "mistral-embed"
base_url="",
)
),
cross_encoder=OpenAIRerankerClient(
config=LLMConfig(
api_key="",
model="", # Use smaller model for reranking
base_url="",
)
)
)
```
Replace the placeholder values with your actual service credentials and model names.
# Neo4j Configuration
> Configure Neo4j as the graph provider for Graphiti
Neo4j is the primary graph database backend for Graphiti. Version 5.26 or higher is required for full functionality.
## Neo4j Community Edition
Neo4j Community Edition is free and suitable for development, testing, and smaller production workloads.
### Installation via Neo4j Desktop
The simplest way to install Neo4j is via [Neo4j Desktop](https://neo4j.com/download/), which provides a user-friendly interface to manage Neo4j instances and databases.
1. Download and install Neo4j Desktop
2. Create a new project
3. Add a new database (Local DBMS)
4. Set a password for the `neo4j` user
5. Start the database
### Docker Installation
For containerized deployments:
```bash
docker run \
--name neo4j-community \
-p 7474:7474 -p 7687:7687 \
-e NEO4J_AUTH=neo4j/your_password \
-e NEO4J_PLUGINS='["apoc"]' \
neo4j:5.26-community
```
### Configuration
Set the following environment variables:
```bash
export NEO4J_URI=bolt://localhost:7687
export NEO4J_USER=neo4j
export NEO4J_PASSWORD=your_password
```
### Connection in Python
```python
from graphiti_core import Graphiti
graphiti = Graphiti(
neo4j_uri="bolt://localhost:7687",
neo4j_user="neo4j",
neo4j_password="your_password"
)
```
## Neo4j AuraDB (Cloud)
Neo4j AuraDB is a fully managed cloud service that handles infrastructure, backups, and updates automatically.
### Setup
1. Sign up for [Neo4j Aura](https://neo4j.com/cloud/platform/aura-graph-database/)
2. Create a new AuraDB instance
3. Note down the connection URI and credentials
4. Download the connection details or copy the connection string
### Configuration
AuraDB connections use the `neo4j+s://` protocol for secure connections:
```bash
export NEO4J_URI=neo4j+s://your-instance.databases.neo4j.io
export NEO4J_USER=neo4j
export NEO4J_PASSWORD=your_generated_password
```
### Connection in Python
```python
from graphiti_core import Graphiti
graphiti = Graphiti(
neo4j_uri="neo4j+s://your-instance.databases.neo4j.io",
neo4j_user="neo4j",
neo4j_password="your_generated_password"
)
```
AuraDB instances automatically include APOC procedures. No additional configuration is required for most Graphiti operations.
## Neo4j Enterprise Edition
Neo4j Enterprise Edition provides advanced features including clustering, hot backups, and performance optimizations.
### Installation
Enterprise Edition requires a commercial license. Installation options include:
* **Neo4j Desktop**: Add Enterprise Edition license key
* **Docker**: Use `neo4j:5.26-enterprise` image with license
* **Server Installation**: Download from Neo4j website with valid license
### Docker with Enterprise Features
```bash
docker run \
--name neo4j-enterprise \
-p 7474:7474 -p 7687:7687 \
-e NEO4J_AUTH=neo4j/your_password \
-e NEO4J_PLUGINS='["apoc"]' \
-e NEO4J_ACCEPT_LICENSE_AGREEMENT=yes \
neo4j:5.26-enterprise
```
### Parallel Runtime Configuration
Enterprise Edition supports parallel runtime for improved query performance:
```bash
export USE_PARALLEL_RUNTIME=true
```
The `USE_PARALLEL_RUNTIME` feature is only available in Neo4j Enterprise Edition and larger AuraDB instances. It is not supported in Community Edition or smaller AuraDB instances.
### Connection in Python
```python
import os
from graphiti_core import Graphiti
# Enable parallel runtime for Enterprise Edition
os.environ['USE_PARALLEL_RUNTIME'] = 'true'
graphiti = Graphiti(
neo4j_uri="bolt://localhost:7687",
neo4j_user="neo4j",
neo4j_password="your_password"
)
```
# FalkorDB Configuration
> Configure FalkorDB as the graph provider for Graphiti
FalkorDB configuration requires version 1.1.2 or higher.
## Installation
Install Graphiti with FalkorDB support:
```bash
pip install graphiti-core[falkordb]
```
or
```bash
uv add graphiti-core[falkordb]
```
## Docker Installation
The simplest way to run FalkorDB is via Docker:
```bash
docker run -p 6379:6379 -p 3000:3000 -it --rm falkordb/falkordb:latest
```
This command:
* Exposes FalkorDB on port 6379 (Redis protocol)
* Provides a web interface on port 3000
* Runs in foreground mode for easy testing
## Configuration
Set the following environment variables for FalkorDB (optional):
```bash
export FALKORDB_HOST=localhost # Default: localhost
export FALKORDB_PORT=6379 # Default: 6379
export FALKORDB_USERNAME= # Optional: usually not required
export FALKORDB_PASSWORD= # Optional: usually not required
```
## Connection in Python
```python
from graphiti_core import Graphiti
from graphiti_core.driver.falkordb_driver import FalkorDriver
# FalkorDB connection using FalkorDriver
falkor_driver = FalkorDriver(
host='localhost', # or os.environ.get('FALKORDB_HOST', 'localhost')
port='6379', # or os.environ.get('FALKORDB_PORT', '6379')
username=None, # or os.environ.get('FALKORDB_USERNAME', None)
password=None # or os.environ.get('FALKORDB_PASSWORD', None)
)
graphiti = Graphiti(graph_driver=falkor_driver)
```
FalkorDB uses a dedicated `FalkorDriver` and connects via Redis protocol on port 6379. Unlike Neo4j, authentication is typically not required for local FalkorDB instances.
# AWS Neptune Configuration
> Configure Amazon Neptune as the graph provider for Graphiti
Neptune DB is Amazon's fully managed graph database service that supports both property graph and RDF data models. Graphiti integrates with Neptune to provide scalable, cloud-native graph storage with automatic backups, encryption, and high availability.
## Prerequisites
Neptune DB integration requires both Neptune and Amazon OpenSearch Serverless (AOSS) services:
* **Neptune Service**: For graph data storage and Cypher query processing
* **OpenSearch Serverless**: For text search and hybrid retrieval functionality
* **AWS Credentials**: Configured via AWS CLI, environment variables, or IAM roles
For detailed setup instructions, see:
* [AWS Neptune Developer Resources](https://aws.amazon.com/neptune/developer-resources/)
* [Neptune Database Documentation](https://docs.aws.amazon.com/neptune/latest/userguide/)
* [Neptune Analytics Documentation](https://docs.aws.amazon.com/neptune-analytics/latest/userguide/)
* [OpenSearch Serverless Documentation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless.html)
## Setup
1. Create a Neptune Database cluster in the AWS Console or via CloudFormation
2. Create an OpenSearch Serverless collection for text search
3. Configure VPC networking and security groups to allow communication between services
4. Note your Neptune cluster endpoint and OpenSearch collection endpoint
## Configuration
Set the following environment variables:
```bash
export NEPTUNE_HOST=your-neptune-cluster.cluster-xyz.us-west-2.neptune.amazonaws.com
export NEPTUNE_PORT=8182 # Optional, defaults to 8182
export AOSS_HOST=your-collection.us-west-2.aoss.amazonaws.com
```
## Installation
Install the required dependencies:
```bash
pip install graphiti-core[neptune]
```
or
```bash
uv add graphiti-core[neptune]
```
## Connection in Python
```python
import os
from graphiti_core import Graphiti
from graphiti_core.driver.neptune_driver import NeptuneDriver
# Get connection parameters from environment
neptune_uri = os.getenv('NEPTUNE_HOST')
neptune_port = int(os.getenv('NEPTUNE_PORT', 8182))
aoss_host = os.getenv('AOSS_HOST')
# Validate required parameters
if not neptune_uri or not aoss_host:
raise ValueError("NEPTUNE_HOST and AOSS_HOST environment variables must be set")
# Create Neptune driver
driver = NeptuneDriver(
host=neptune_uri, # Required: Neptune cluster endpoint
aoss_host=aoss_host, # Required: OpenSearch Serverless collection endpoint
port=neptune_port # Optional: Neptune port (defaults to 8182)
)
# Pass the driver to Graphiti
graphiti = Graphiti(graph_driver=driver)
```
# Kuzu DB Configuration
> Configure Kuzu as the graph provider for Graphiti
Kuzu is an embedded graph engine that does not require any additional setup. You can enable the Kuzu driver by installing graphiti with the Kuzu extra:
```bash
pip install graphiti-core[kuzu]
```
## Configuration
Set the following environment variables for Kuzu (optional):
```bash
export KUZU_DB=/path/to/graphiti.kuzu # Default: :memory:
```
## Connection in Python
```python
from graphiti_core import Graphiti
from graphiti_core.driver.kuzu_driver import KuzuDriver
# Kuzu connection using KuzuDriver
kuzu_driver = KuzuDriver(
db='/path/to/graphiti.kuzu' # or os.environ.get('KUZU_DB', ':memory:')
)
graphiti = Graphiti(graph_driver=kuzu_driver)
```
# Adding Episodes
> How to add data to your Graphiti graph
Refer to the [Custom Entity Types](/graphiti/core-concepts/custom-entity-and-edge-types) page for detailed instructions on adding user-defined ontology to your graph.
### Adding Episodes
Episodes represent a single data ingestion event. An `episode` is itself a node, and any nodes identified while ingesting the
episode are related to the episode via `MENTIONS` edges.
Episodes enable querying for information at a point in time and understanding the provenance of nodes and their edge relationships.
Supported episode types:
* `text`: Unstructured text data
* `message`: Conversational messages of the format `speaker: message...`
* `json`: Structured data, processed distinctly from the other types
The graph below was generated using the code in the [Quick Start](/graphiti/getting-started/quick-start). Each **podcast** is an individual episode.

#### Adding a `text` or `message` Episode
Using the `EpisodeType.text` type:
```python
await graphiti.add_episode(
name="tech_innovation_article",
episode_body=(
"MIT researchers have unveiled 'ClimateNet', an AI system capable of predicting "
"climate patterns with unprecedented accuracy. Early tests show it can forecast "
"major weather events up to three weeks in advance, potentially revolutionizing "
"disaster preparedness and agricultural planning."
),
source=EpisodeType.text,
# A description of the source (e.g., "podcast", "news article")
source_description="Technology magazine article",
# The timestamp for when this episode occurred or was created
reference_time=datetime(2023, 11, 15, 9, 30),
)
```
Using the `EpisodeType.message` type supports passing in multi-turn conversations in the `episode_body`.
The text should be structured in `{role/name}: {message}` pairs.
```python
await graphiti.add_episode(
name="Customer_Support_Interaction_1",
episode_body=(
"Customer: Hi, I'm having trouble with my Allbirds shoes. "
"The sole is coming off after only 2 months of use.\n"
"Support: I'm sorry to hear that. Can you please provide your order number?"
),
source=EpisodeType.message,
source_description="Customer support chat",
reference_time=datetime(2024, 3, 15, 14, 45),
)
```
#### Adding an Episode using structured data in JSON format
JSON documents can be arbitrarily nested. However, it's advisable to keep documents compact, as they must fit within your LLM's context window.
For large data imports, consider using the `add_episode_bulk` API to
efficiently add multiple episodes at once.
```python
product_data = {
"id": "PROD001",
"name": "Men's SuperLight Wool Runners",
"color": "Dark Grey",
"sole_color": "Medium Grey",
"material": "Wool",
"technology": "SuperLight Foam",
"price": 125.00,
"in_stock": True,
"last_updated": "2024-03-15T10:30:00Z"
}
# Add the episode to the graph
await graphiti.add_episode(
name="Product Update - PROD001",
episode_body=product_data, # Pass the Python dictionary directly
source=EpisodeType.json,
source_description="Allbirds product catalog update",
reference_time=datetime.now(),
)
```
#### Loading Episodes in Bulk
Graphiti offers `add_episode_bulk` for efficient batch ingestion of episodes, significantly outperforming `add_episode` for large datasets. This method is highly recommended for bulk loading.
Use `add_episode_bulk` only for populating empty graphs or when edge invalidation is not required. The bulk ingestion pipeline does not perform edge invalidation operations.
```python
product_data = [
{
"id": "PROD001",
"name": "Men's SuperLight Wool Runners",
"color": "Dark Grey",
"sole_color": "Medium Grey",
"material": "Wool",
"technology": "SuperLight Foam",
"price": 125.00,
"in_stock": true,
"last_updated": "2024-03-15T10:30:00Z"
},
...
{
"id": "PROD0100",
"name": "Kids Wool Runner-up Mizzles",
"color": "Natural Grey",
"sole_color": "Orange",
"material": "Wool",
"technology": "Water-repellent",
"price": 80.00,
"in_stock": true,
"last_updated": "2024-03-17T14:45:00Z"
}
]
# Prepare the episodes for bulk loading
bulk_episodes = [
RawEpisode(
name=f"Product Update - {product['id']}",
content=json.dumps(product),
source=EpisodeType.json,
source_description="Allbirds product catalog update",
reference_time=datetime.now()
)
for product in product_data
]
await graphiti.add_episode_bulk(bulk_episodes)
```
```
```
# Custom Entity and Edge Types
> Enhancing Graphiti with Custom Ontologies
Graphiti allows you to define custom entity types and edge types to better represent your domain-specific knowledge. This enables more structured data extraction and richer semantic relationships in your knowledge graph.
## Defining Custom Entity and Edge Types
Custom entity types and edge types are defined using Pydantic models. Each model represents a specific type with custom attributes.
```python
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional
# Custom Entity Types
class Person(BaseModel):
"""A person entity with biographical information."""
age: Optional[int] = Field(None, description="Age of the person")
occupation: Optional[str] = Field(None, description="Current occupation")
location: Optional[str] = Field(None, description="Current location")
birth_date: Optional[datetime] = Field(None, description="Date of birth")
class Company(BaseModel):
"""A business organization."""
industry: Optional[str] = Field(None, description="Primary industry")
founded_year: Optional[int] = Field(None, description="Year company was founded")
headquarters: Optional[str] = Field(None, description="Location of headquarters")
employee_count: Optional[int] = Field(None, description="Number of employees")
class Product(BaseModel):
"""A product or service."""
category: Optional[str] = Field(None, description="Product category")
price: Optional[float] = Field(None, description="Price in USD")
release_date: Optional[datetime] = Field(None, description="Product release date")
# Custom Edge Types
class Employment(BaseModel):
"""Employment relationship between a person and company."""
position: Optional[str] = Field(None, description="Job title or position")
start_date: Optional[datetime] = Field(None, description="Employment start date")
end_date: Optional[datetime] = Field(None, description="Employment end date")
salary: Optional[float] = Field(None, description="Annual salary in USD")
is_current: Optional[bool] = Field(None, description="Whether employment is current")
class Investment(BaseModel):
"""Investment relationship between entities."""
amount: Optional[float] = Field(None, description="Investment amount in USD")
investment_type: Optional[str] = Field(None, description="Type of investment (equity, debt, etc.)")
stake_percentage: Optional[float] = Field(None, description="Percentage ownership")
investment_date: Optional[datetime] = Field(None, description="Date of investment")
class Partnership(BaseModel):
"""Partnership relationship between companies."""
partnership_type: Optional[str] = Field(None, description="Type of partnership")
duration: Optional[str] = Field(None, description="Expected duration")
deal_value: Optional[float] = Field(None, description="Financial value of partnership")
```
## Using Custom Entity and Edge Types
Pass your custom entity types and edge types to the add\_episode method:
```python
entity_types = {
"Person": Person,
"Company": Company,
"Product": Product
}
edge_types = {
"Employment": Employment,
"Investment": Investment,
"Partnership": Partnership
}
edge_type_map = {
("Person", "Company"): ["Employment"],
("Company", "Company"): ["Partnership", "Investment"],
("Person", "Person"): ["Partnership"],
("Entity", "Entity"): ["Investment"], # Apply to any entity type
}
await graphiti.add_episode(
name="Business Update",
episode_body="Sarah joined TechCorp as CTO in January 2023 with a $200K salary. TechCorp partnered with DataCorp in a $5M deal.",
source_description="Business news",
reference_time=datetime.now(),
entity_types=entity_types,
edge_types=edge_types,
edge_type_map=edge_type_map
)
```
## Searching with Custom Types
You can filter search results to specific entity types or edge types using SearchFilters:
```python
from graphiti_core.search.search_filters import SearchFilters
# Search for only specific entity types
search_filter = SearchFilters(
node_labels=["Person", "Company"] # Only return Person and Company entities
)
results = await graphiti.search_(
query="Who works at tech companies?",
search_filter=search_filter
)
# Search for only specific edge types
search_filter = SearchFilters(
edge_types=["Employment", "Partnership"] # Only return Employment and Partnership edges
)
results = await graphiti.search_(
query="Tell me about business relationships",
search_filter=search_filter
)
```
## How Custom Types Work
### Entity Extraction Process
1. **Extraction**: Graphiti extracts entities from text and classifies them using your custom types
2. **Validation**: Each entity is validated against the appropriate Pydantic model
3. **Attribute Population**: Custom attributes are extracted from the text and populated
4. **Storage**: Entities are stored with their custom attributes
### Edge Extraction Process
1. **Relationship Detection**: Graphiti identifies relationships between extracted entities
2. **Type Classification**: Based on the entity types involved and your edge\_type\_map, relationships are classified
3. **Attribute Extraction**: For custom edge types, additional attributes are extracted from the context
4. **Validation**: Edge attributes are validated against the Pydantic model
5. **Storage**: Edges are stored with their custom attributes and relationship metadata
## Edge Type Mapping
The edge\_type\_map parameter defines which edge types can exist between specific entity type pairs:
```python
edge_type_map = {
("Person", "Company"): ["Employment"],
("Company", "Company"): ["Partnership", "Investment"],
("Person", "Person"): ["Partnership"],
("Entity", "Entity"): ["Investment"], # Apply to any entity type
}
```
If an entity pair doesn't have a defined edge type mapping, Graphiti will use default relationship types and the relationship will still be captured with a generic RELATES\_TO type.
## Schema Evolution
Your knowledge graph's schema can evolve over time as your needs change. You can update entity types by adding new attributes to existing types without breaking existing nodes. When you add new attributes, existing nodes will preserve their original attributes while supporting the new ones for future updates. This flexible approach allows your knowledge graph to grow and adapt while maintaining backward compatibility with historical data.
For example, if you initially defined a "Customer" type with basic attributes like name and email, you could later add attributes like "loyalty\_tier" or "acquisition\_channel" without needing to modify or migrate existing customer nodes in your graph.
## Best Practices
### Model Design
* **Clear Descriptions**: Always include detailed descriptions in docstrings and Field descriptions
* **Optional Fields**: Make custom attributes optional to handle cases where information isn't available
* **Appropriate Types**: Use specific types (datetime, int, float) rather than strings when possible
* **Validation**: Consider adding Pydantic validators for complex validation rules
* **Atomic Attributes**: Attributes should be broken down into their smallest meaningful units rather than storing compound information
```python
from pydantic import validator
class Person(BaseModel):
"""A person entity."""
age: Optional[int] = Field(None, description="Age in years")
@validator('age')
def validate_age(cls, v):
if v is not None and (v < 0 or v > 150):
raise ValueError('Age must be between 0 and 150')
return v
```
**Instead of compound information:**
```python
class Customer(BaseModel):
contact_info: Optional[str] = Field(None, description="Name and email") # Don't do this
```
**Use atomic attributes:**
```python
class Customer(BaseModel):
name: Optional[str] = Field(None, description="Customer name")
email: Optional[str] = Field(None, description="Customer email address")
```
### Naming Conventions
* **Entity Types**: Use PascalCase (e.g., Person, TechCompany)
* **Edge Types**: Use PascalCase for custom types (e.g., Employment, Partnership)
* **Attributes**: Use snake\_case (e.g., start\_date, employee\_count)
* **Descriptions**: Be specific and actionable for the LLM
* **Consistency**: Maintain consistent naming conventions across related entity types
### Edge Type Mapping Strategy
* **Specific Mappings**: Define specific entity type pairs for targeted relationships
* **Fallback to Entity**: Use ("Entity", "Entity") as a fallback for general relationships
* **Balanced Scope**: Don't make edge types too specific or too general
* **Domain Coverage**: Ensure your edge types cover the main relationships in your domain
```python
# Good: Specific and meaningful
edge_type_map = {
("Person", "Company"): ["Employment", "Investment"],
("Company", "Company"): ["Partnership", "Acquisition"],
("Person", "Product"): ["Usage", "Review"],
("Entity", "Entity"): ["RELATES_TO"] # Fallback for unexpected relationships
}
# Avoid: Too granular
edge_type_map = {
("CEO", "TechCompany"): ["CEOEmployment"],
("Engineer", "TechCompany"): ["EngineerEmployment"],
# This creates too many specific types
}
```
## Entity Type Exclusion
You can exclude specific entity types from extraction using the excluded\_entity\_types parameter:
```python
await graphiti.add_episode(
name="Business Update",
episode_body="The meeting discussed various topics including weather and sports.",
source_description="Meeting notes",
reference_time=datetime.now(),
entity_types=entity_types,
excluded_entity_types=["Person"] # Won't extract Person entities
)
```
## Migration Guide
If you're upgrading from a previous version of Graphiti:
* You can add entity types to new episodes, even if existing episodes in the graph did not have entity types. Existing nodes will continue to work without being classified.
* To add types to previously ingested data, you need to re-ingest it with entity types set into a new graph.
## Important Constraints
### Protected Attribute Names
Custom entity type attributes cannot use protected names that are already used by Graphiti's core EntityNode class:
* `uuid`, `name`, `group_id`, `labels`, `created_at`, `summary`, `attributes`, `name_embedding`
Custom entity types and edge types provide powerful ways to structure your knowledge graph according to your domain needs. They enable more precise extraction, better organization, and richer semantic relationships in your data.
# Communities
> How to create and update communities
In Graphiti, communities (represented as `CommunityNode` objects) represent groups of related entity nodes.
Communities can be generated using the `build_communities` method on the graphiti class.
```python
await graphiti.build_communities()
```
Communities are determined using the Leiden algorithm, which groups strongly connected nodes together.
Communities contain a summary field that collates the summaries held on each of its member entities.
This allows Graphiti to provide high-level synthesized information about what the graph contains in addition to the more granular facts stored on edges.
Once communities are built, they can also be updated with new episodes by passing in `update_communities=True` to the `add_episode` method.
If a new node is added to the graph, we will determine which community it should be added to based on the most represented community of the new node's surrounding nodes.
This updating methodology is inspired by the label propagation algorithm for determining communities.
However, we still recommend periodically rebuilding communities to ensure the most optimal grouping.
Whenever the `build_communities` method is called it will remove any existing communities before creating new ones.
# Graph Namespacing
> Using group_ids to create isolated graph namespaces
## Overview
Graphiti supports the concept of graph namespacing through the use of `group_id` parameters. This feature allows you to create isolated graph environments within the same Graphiti instance, enabling multiple distinct knowledge graphs to coexist without interference.
Graph namespacing is particularly useful for:
* **Multi-tenant applications**: Isolate data between different customers or organizations
* **Testing environments**: Maintain separate development, testing, and production graphs
* **Domain-specific knowledge**: Create specialized graphs for different domains or use cases
* **Team collaboration**: Allow different teams to work with their own graph spaces
## How Namespacing Works
In Graphiti, every node and edge can be associated with a `group_id`. When you specify a `group_id`, you're effectively creating a namespace for that data. Nodes and edges with the same `group_id` form a cohesive, isolated graph that can be queried and manipulated independently from other namespaces.
### Key Benefits
* **Data isolation**: Prevent data leakage between different namespaces
* **Simplified management**: Organize and manage related data together
* **Performance optimization**: Improve query performance by limiting the search space
* **Flexible architecture**: Support multiple use cases within a single Graphiti instance
## Using group\_ids in Graphiti
### Adding Episodes with group\_id
When adding episodes to your graph, you can specify a `group_id` to namespace the episode and all its extracted entities:
```python
await graphiti.add_episode(
name="customer_interaction",
episode_body="Customer Jane mentioned she loves our new SuperLight Wool Runners in Dark Grey.",
source=EpisodeType.text,
source_description="Customer feedback",
reference_time=datetime.now(),
group_id="customer_team" # This namespaces the episode and its entities
)
```
### Adding Fact Triples with group\_id
When manually adding fact triples, ensure both nodes and the edge share the same `group_id`:
```python
from graphiti_core.nodes import EntityNode
from graphiti_core.edges import EntityEdge
import uuid
from datetime import datetime
# Define a namespace for this data
namespace = "product_catalog"
# Create source and target nodes with the namespace
source_node = EntityNode(
uuid=str(uuid.uuid4()),
name="SuperLight Wool Runners",
group_id=namespace # Apply namespace to source node
)
target_node = EntityNode(
uuid=str(uuid.uuid4()),
name="Sustainable Footwear",
group_id=namespace # Apply namespace to target node
)
# Create an edge with the same namespace
edge = EntityEdge(
group_id=namespace, # Apply namespace to edge
source_node_uuid=source_node.uuid,
target_node_uuid=target_node.uuid,
created_at=datetime.now(),
name="is_category_of",
fact="SuperLight Wool Runners is a product in the Sustainable Footwear category"
)
# Add the triplet to the graph
await graphiti.add_triplet(source_node, edge, target_node)
```
### Querying Within a Namespace
When querying the graph, specify the `group_id` to limit results to a particular namespace:
```python
# Search within a specific namespace
search_results = await graphiti.search(
query="Wool Runners",
group_id="product_catalog" # Only search within this namespace
)
# For more advanced node-specific searches, use the _search method with a recipe
from graphiti_core.search.search_config_recipes import NODE_HYBRID_SEARCH_RRF
# Create a search config for nodes only
node_search_config = NODE_HYBRID_SEARCH_RRF.model_copy(deep=True)
node_search_config.limit = 5 # Limit to 5 results
# Execute the node search within a specific namespace
node_search_results = await graphiti._search(
query="SuperLight Wool Runners",
group_id="product_catalog", # Only search within this namespace
config=node_search_config
)
```
## Best Practices for Graph Namespacing
1. **Consistent naming**: Use a consistent naming convention for your `group_id` values
2. **Documentation**: Maintain documentation of your namespace structure and purpose
3. **Granularity**: Choose an appropriate level of granularity for your namespaces
* Too many namespaces can lead to fragmented data
* Too few namespaces may not provide sufficient isolation
4. **Cross-namespace queries**: When necessary, perform multiple queries across namespaces and combine results in your application logic
## Example: Multi-tenant Application
Here's an example of using namespacing in a multi-tenant application:
```python
async def add_customer_data(tenant_id, customer_data):
"""Add customer data to a tenant-specific namespace"""
# Use the tenant_id as the namespace
namespace = f"tenant_{tenant_id}"
# Create an episode for this customer data
await graphiti.add_episode(
name=f"customer_data_{customer_data['id']}",
episode_body=customer_data,
source=EpisodeType.json,
source_description="Customer profile update",
reference_time=datetime.now(),
group_id=namespace # Namespace by tenant
)
async def search_tenant_data(tenant_id, query):
"""Search within a tenant's namespace"""
namespace = f"tenant_{tenant_id}"
# Only search within this tenant's namespace
return await graphiti.search(
query=query,
group_id=namespace
)
```
# Searching the Graph
> How to retrieve information from your Graphiti graph
The examples below demonstrate two search approaches in the Graphiti library:
1. **Hybrid Search:**
```python
await graphiti.search(query)
```
Combines semantic similarity and BM25 retrieval, reranked using Reciprocal Rank Fusion.
Example: Does a broad retrieval of facts related to Allbirds Wool Runners and Jane's purchase.
2. **Node Distance Reranking:**
```python
await graphiti.search(query, focal_node_uuid)
```
Extends Hybrid Search above by prioritizing results based on proximity to a specified node in the graph.
Example: Focuses on Jane-specific information, highlighting her wool allergy.
Node Distance Reranking is particularly useful for entity-specific queries, providing more contextually relevant results. It weights facts by their closeness to the focal node, emphasizing information directly related to the entity of interest.
This dual approach allows for both broad exploration and targeted, entity-specific information retrieval from the knowledge graph.
```python
query = "Can Jane wear Allbirds Wool Runners?"
jane_node_uuid = "123e4567-e89b-12d3-a456-426614174000"
def print_facts(edges):
print("\n".join([edge.fact for edge in edges]))
# Hybrid Search
results = await graphiti.search(query)
print_facts(results)
> The Allbirds Wool Runners are sold by Allbirds.
> Men's SuperLight Wool Runners - Dark Grey (Medium Grey Sole) has a runner silhouette.
> Jane purchased SuperLight Wool Runners.
# Hybrid Search with Node Distance Reranking
await client.search(query, jane_node_uuid)
print_facts(results)
> Jane purchased SuperLight Wool Runners.
> Jane is allergic to wool.
> The Allbirds Wool Runners are sold by Allbirds.
```
## Configurable Search Strategies
Graphiti also provides a low-level search method that is more configurable than the out-of-the-box search.
This search method can be called using `graphiti._search()` and passing in an additional config parameter of type `SearchConfig`.
`SearchConfig` contains 4 fields: one for the limit, and three more configs for each of edges, nodes, and communities.
The `graphiti._search()` method returns a `SearchResults` object containing a list of nodes, edges, and communities.
The `graphiti._search()` method is quite configurable and can be complicated to work with at first.
As such, we also have a `search_config_recipes.py` file that contains a few prebuilt `SearchConfig` recipes for common use cases.
The 15 recipes are the following:
| Search Type | Description |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| COMBINED\_HYBRID\_SEARCH\_RRF | Performs a hybrid search with RRF reranking over edges, nodes, and communities. |
| COMBINED\_HYBRID\_SEARCH\_MMR | Performs a hybrid search with MMR reranking over edges, nodes, and communities. |
| COMBINED\_HYBRID\_SEARCH\_CROSS\_ENCODER | Performs a full-text search, similarity search, and BFS with cross\_encoder reranking over edges, nodes, and communities. |
| EDGE\_HYBRID\_SEARCH\_RRF | Performs a hybrid search over edges with RRF reranking. |
| EDGE\_HYBRID\_SEARCH\_MMR | Performs a hybrid search over edges with MMR reranking. |
| EDGE\_HYBRID\_SEARCH\_NODE\_DISTANCE | Performs a hybrid search over edges with node distance reranking. |
| EDGE\_HYBRID\_SEARCH\_EPISODE\_MENTIONS | Performs a hybrid search over edges with episode mention reranking. |
| EDGE\_HYBRID\_SEARCH\_CROSS\_ENCODER | Performs a hybrid search over edges with cross encoder reranking. |
| NODE\_HYBRID\_SEARCH\_RRF | Performs a hybrid search over nodes with RRF reranking. |
| NODE\_HYBRID\_SEARCH\_MMR | Performs a hybrid search over nodes with MMR reranking. |
| NODE\_HYBRID\_SEARCH\_NODE\_DISTANCE | Performs a hybrid search over nodes with node distance reranking. |
| NODE\_HYBRID\_SEARCH\_EPISODE\_MENTIONS | Performs a hybrid search over nodes with episode mentions reranking. |
| NODE\_HYBRID\_SEARCH\_CROSS\_ENCODER | Performs a hybrid search over nodes with cross encoder reranking. |
| COMMUNITY\_HYBRID\_SEARCH\_RRF | Performs a hybrid search over communities with RRF reranking. |
| COMMUNITY\_HYBRID\_SEARCH\_MMR | Performs a hybrid search over communities with MMR reranking. |
| COMMUNITY\_HYBRID\_SEARCH\_CROSS\_ENCODER | Performs a hybrid search over communities with cross encoder reranking. |
## Supported Reranking Approaches
**Reciprocal Rank Fusion (RRF)** enhances search by combining results from different algorithms, like BM25 and semantic search. Each algorithm's results are ranked, converted to reciprocal scores (1/rank), and summed. This aggregated score determines the final ranking, leveraging the strengths of each method for more accurate retrieval.
**Maximal Marginal Relevance (MMR)** is a search strategy that balances relevance and diversity in results. It selects results that are both relevant to the query and diverse from already chosen ones, reducing redundancy and covering different query aspects. MMR ensures comprehensive and varied search results by iteratively choosing results that maximize relevance while minimizing similarity to previously selected results.
A **Cross-Encoder** is a model that jointly encodes a query and a result, scoring their relevance by considering their combined context. This approach often yields more accurate results compared to methods that encode query and a text separately.
Graphiti supports three cross encoders:
* `OpenAIRerankerClient` (the default) - Uses an OpenAI model to classify relevance and the resulting `logprobs` are used to rerank results.
* `GeminiRerankerClient` - Uses Google's Gemini models to classify relevance for cost-effective and low-latency reranking.
* `BGERerankerClient` - Uses the `BAAI/bge-reranker-v2-m3` model and requires `sentence_transformers` be installed.
# CRUD Operations
> How to access and modify Nodes and Edges
The Graphiti library uses 8 core classes to add data to your graph:
* `Node`
* `EpisodicNode`
* `EntityNode`
* `Edge`
* `EpisodicEdge`
* `EntityEdge`
* `CommunityNode`
* `CommunityEdge`
The generic `Node` and `Edge` classes are abstract base classes, and the other 4 classes inherit from them.
Each of `EpisodicNode`, `EntityNode`, `EpisodicEdge`, and `EntityEdge` have fully supported CRUD operations.
The save method performs a find or create based on the uuid of the object, and will add or update any other data from the class to the graph.
A driver must be provided to the save method. The Entity Node save method is shown below as a sample.
```python
async def save(self, driver: AsyncDriver):
result = await driver.execute_query(
"""
MERGE (n:Entity {uuid: $uuid})
SET n = {uuid: $uuid, name: $name, name_embedding: $name_embedding, summary: $summary, created_at: $created_at}
RETURN n.uuid AS uuid""",
uuid=self.uuid,
name=self.name,
summary=self.summary,
name_embedding=self.name_embedding,
created_at=self.created_at,
)
logger.info(f'Saved Node to neo4j: {self.uuid}')
return result
```
Graphiti also supports hard deleting nodes and edges using the delete method, which also requires a driver.
```python
async def delete(self, driver: AsyncDriver):
result = await driver.execute_query(
"""
MATCH (n:Entity {uuid: $uuid})
DETACH DELETE n
""",
uuid=self.uuid,
)
logger.info(f'Deleted Node: {self.uuid}')
return result
```
Finally, Graphiti also provides class methods to get nodes and edges by uuid.
Note that because these are class methods they are called using the class rather than an instance of the class.
```python
async def get_by_uuid(cls, driver: AsyncDriver, uuid: str):
records, _, _ = await driver.execute_query(
"""
MATCH (n:Entity {uuid: $uuid})
RETURN
n.uuid As uuid,
n.name AS name,
n.created_at AS created_at,
n.summary AS summary
""",
uuid=uuid,
)
nodes: list[EntityNode] = []
for record in records:
nodes.append(
EntityNode(
uuid=record['uuid'],
name=record['name'],
labels=['Entity'],
created_at=record['created_at'].to_native(),
summary=record['summary'],
)
)
logger.info(f'Found Node: {uuid}')
return nodes[0]
```
# Adding Fact Triples
> How to add fact triples to your Graphiti graph
A "fact triple" consists of two nodes and an edge between them, where the edge typically contains some fact. You can manually add a fact triple of your choosing to the graph like this:
```python
from graphiti_core.nodes import EpisodeType, EntityNode
from graphiti_core.edges import EntityEdge
import uuid
from datetime import datetime
source_name = "Bob"
target_name = "bananas"
source_uuid = "some existing UUID" # This is an existing node, so we use the existing UUID obtained from Neo4j Desktop
target_uuid = str(uuid.uuid4()) # This is a new node, so we create a new UUID
edge_name = "LIKES"
edge_fact = "Bob likes bananas"
source_node = EntityNode(
uuid=source_uuid,
name=source_name,
group_id=""
)
target_node = EntityNode(
uuid=target_uuid,
name=target_name,
group_id=""
)
edge = EntityEdge(
group_id="",
source_node_uuid=source_uuid,
target_node_uuid=target_uuid,
created_at=datetime.now(),
name=edge_name,
fact=edge_fact
)
await graphiti.add_triplet(source_node, edge, target_node)
```
When you add a fact triple, Graphiti will attempt to deduplicate your passed in nodes and edge with the already existing nodes and edges in the graph. If there are no duplicates, it will add them as new nodes and edges.
Also, you can avoid constructing `EntityEdge` or `EntityNode` objects manually by using the results of a Graphiti search (see [Searching the Graph](/graphiti/graphiti/searching)).
# Using LangGraph and Graphiti
> Building an agent with LangChain's LangGraph and Graphiti
A Jupyter notebook version of this example is available [on GitHub](https://github.com/getzep/graphiti/blob/main/examples/langgraph-agent/agent.ipynb).
Looking for a managed Graphiti service? Check out [Zep Cloud](https://www.getzep.com).
* Designed as a self-improving context layer for Agents.
* No need to run Neo4j or other dependencies.
* Additional features for startups and enterprises alike.
* Fast and scalable.
The following example demonstrates building an agent using LangGraph. Graphiti is used to personalize agent responses based on information learned from prior conversations. Additionally, a database of products is loaded into the Graphiti graph, enabling the agent to speak to these products.
The agent implements:
* persistance of new chat turns to Graphiti and recall of relevant Facts using the most recent message.
* a tool for querying Graphiti for shoe information
* an in-memory `MemorySaver` to maintain agent state.
## Install dependencies
```shell
pip install graphiti-core langchain-openai langgraph ipywidgets
```
Ensure that you've followed the [Graphiti installation instructions](/graphiti/getting-started/quick-start). In particular, installation of `neo4j`.
```python
import asyncio
import json
import logging
import os
import sys
import uuid
from contextlib import suppress
from datetime import datetime
from pathlib import Path
from typing import Annotated
import ipywidgets as widgets
from dotenv import load_dotenv
from IPython.display import Image, display
from typing_extensions import TypedDict
load_dotenv()
```
```python
def setup_logging():
logger = logging.getLogger()
logger.setLevel(logging.ERROR)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
return logger
logger = setup_logging()
```
## Configure Graphiti
Ensure that you have `neo4j` running and a database created. You'll need the following environment variables configured:
```bash
NEO4J_URI=
NEO4J_USER=
NEO4J_PASSWORD=
```
```python
# Configure Graphiti
from graphiti_core import Graphiti
from graphiti_core.edges import EntityEdge
from graphiti_core.nodes import EpisodeType
from graphiti_core.utils.bulk_utils import RawEpisode
from graphiti_core.utils.maintenance.graph_data_operations import clear_data
neo4j_uri = os.environ.get('NEO4J_URI', 'bolt://localhost:7687')
neo4j_user = os.environ.get('NEO4J_USER', 'neo4j')
neo4j_password = os.environ.get('NEO4J_PASSWORD', 'password')
client = Graphiti(
neo4j_uri,
neo4j_user,
neo4j_password,
)
```
## Generating a database schema
The following is only required for the first run of this notebook or when you'd like to start your database over.
`clear_data` is destructive and will wipe your entire database.
```python
# Note: This will clear the database
await clear_data(client.driver)
await client.build_indices_and_constraints()
```
## Load Shoe Data into the Graph
Load several shoe and related products into the Graphiti. This may take a while.
This only needs to be done once. If you run `clear_data` you'll need to rerun this step.
```python
async def ingest_products_data(client: Graphiti):
script_dir = Path.cwd().parent
json_file_path = script_dir / 'data' / 'manybirds_products.json'
with open(json_file_path) as file:
products = json.load(file)['products']
episodes: list[RawEpisode] = [
RawEpisode(
name=product.get('title', f'Product {i}'),
content=str({k: v for k, v in product.items() if k != 'images'}),
source_description='ManyBirds products',
source=EpisodeType.json,
reference_time=datetime.now(),
)
for i, product in enumerate(products)
]
await client.add_episode_bulk(episodes)
await ingest_products_data(client)
```
## Create a user node in the Graphiti graph
In your own app, this step could be done later once the user has identified themselves and made their sales intent known. We do this here so we can configure the agent with the user's `node_uuid`.
```python
user_name = 'jess'
await client.add_episode(
name='User Creation',
episode_body=(f'{user_name} is interested in buying a pair of shoes'),
source=EpisodeType.text,
reference_time=datetime.now(),
source_description='SalesBot',
)
# let's get Jess's node uuid
nl = await client.get_nodes_by_query(user_name)
user_node_uuid = nl[0].uuid
# and the ManyBirds node uuid
nl = await client.get_nodes_by_query('ManyBirds')
manybirds_node_uuid = nl[0].uuid
```
## Helper Functions and LangChain Imports
```python
def edges_to_facts_string(entities: list[EntityEdge]):
return '-' + '\n- '.join([edge.fact for edge in entities])
```
```python
from langchain_core.messages import AIMessage, SystemMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, START, StateGraph, add_messages
from langgraph.prebuilt import ToolNode
```
## `get_shoe_data` Tool
The agent will use this to search the Graphiti graph for information about shoes. We center the search on the `manybirds_node_uuid` to ensure we rank shoe-related data over user data.
```python
@tool
async def get_shoe_data(query: str) -> str:
"""Search the graphiti graph for information about shoes"""
edge_results = await client.search(
query,
center_node_uuid=manybirds_node_uuid,
num_results=10,
)
return edges_to_facts_string(edge_results)
tools = [get_shoe_data]
tool_node = ToolNode(tools)
```
## Initialize the LLM and bind tools
```python
llm = ChatOpenAI(model='gpt-4o-mini', temperature=0).bind_tools(tools)
```
### Test the tool node
```python
await tool_node.ainvoke({'messages': [await llm.ainvoke('wool shoes')]})
```
```json
{
"messages": [
{
"content": "-The product 'Men's SuperLight Wool Runners - Dark Grey (Medium Grey Sole)' is made of Wool.\n- Women's Tree Breezers Knit - Rugged Beige (Hazy Beige Sole) has sizing options related to women's move shoes half sizes.\n- TinyBirds Wool Runners - Little Kids - Natural Black (Blizzard Sole) is a type of Shoes.\n- The product 'Men's SuperLight Wool Runners - Dark Grey (Medium Grey Sole)' belongs to the category Shoes.\n- The product 'Men's SuperLight Wool Runners - Dark Grey (Medium Grey Sole)' uses SuperLight Foam technology.\n- TinyBirds Wool Runners - Little Kids - Natural Black (Blizzard Sole) is sold by Manybirds.\n- Jess is interested in buying a pair of shoes.\n- TinyBirds Wool Runners - Little Kids - Natural Black (Blizzard Sole) has the handle TinyBirds-wool-runners-little-kids.\n- ManyBirds Men's Couriers are a type of Shoes.\n- Women's Tree Breezers Knit - Rugged Beige (Hazy Beige Sole) belongs to the Shoes category.",
"name": "get_shoe_data",
"tool_call_id": "call_EPpOpD75rdq9jKRBUsfRnfxx"
}
]
}
```
## Chatbot Function Explanation
The chatbot uses Graphiti to provide context-aware responses in a shoe sales scenario. Here's how it works:
1. **Context Retrieval**: It searches the Graphiti graph for relevant information based on the latest message, using the user's node as the center point. This ensures that user-related facts are ranked higher than other information in the graph.
2. **System Message**: It constructs a system message incorporating facts from Graphiti, setting the context for the AI's response.
3. **Knowledge Persistence**: After generating a response, it asynchronously adds the interaction to the Graphiti graph, allowing future queries to reference this conversation.
This approach enables the chatbot to maintain context across interactions and provide personalized responses based on the user's history and preferences stored in the Graphiti graph.
```python
class State(TypedDict):
messages: Annotated[list, add_messages]
user_name: str
user_node_uuid: str
async def chatbot(state: State):
facts_string = None
if len(state['messages']) > 0:
last_message = state['messages'][-1]
graphiti_query = f'{"SalesBot" if isinstance(last_message, AIMessage) else state["user_name"]}: {last_message.content}'
# search graphiti using Jess's node uuid as the center node
# graph edges (facts) further from the Jess node will be ranked lower
edge_results = await client.search(
graphiti_query, center_node_uuid=state['user_node_uuid'], num_results=5
)
facts_string = edges_to_facts_string(edge_results)
system_message = SystemMessage(
content=f"""You are a skillfull shoe salesperson working for ManyBirds. Review information about the user and their prior conversation below and respond accordingly.
Keep responses short and concise. And remember, always be selling (and helpful!)
Things you'll need to know about the user in order to close a sale:
- the user's shoe size
- any other shoe needs? maybe for wide feet?
- the user's preferred colors and styles
- their budget
Ensure that you ask the user for the above if you don't already know.
Facts about the user and their conversation:
{facts_string or 'No facts about the user and their conversation'}"""
)
messages = [system_message] + state['messages']
response = await llm.ainvoke(messages)
# add the response to the graphiti graph.
# this will allow us to use the graphiti search later in the conversation
# we're doing async here to avoid blocking the graph execution
asyncio.create_task(
client.add_episode(
name='Chatbot Response',
episode_body=f"{state['user_name']}: {state['messages'][-1]}\nSalesBot: {response.content}",
source=EpisodeType.message,
reference_time=datetime.now(),
source_description='Chatbot',
)
)
return {'messages': [response]}
```
## Setting up the Agent
This section sets up the Agent's LangGraph graph:
1. **Graph Structure**: It defines a graph with nodes for the agent (chatbot) and tools, connected in a loop.
2. **Conditional Logic**: The `should_continue` function determines whether to end the graph execution or continue to the tools node based on the presence of tool calls.
3. **Memory Management**: It uses a MemorySaver to maintain conversation state across turns. This is in addition to using Graphiti for facts.
```python
graph_builder = StateGraph(State)
memory = MemorySaver()
# Define the function that determines whether to continue or not
async def should_continue(state, config):
messages = state['messages']
last_message = messages[-1]
# If there is no function call, then we finish
if not last_message.tool_calls:
return 'end'
# Otherwise if there is, we continue
else:
return 'continue'
graph_builder.add_node('agent', chatbot)
graph_builder.add_node('tools', tool_node)
graph_builder.add_edge(START, 'agent')
graph_builder.add_conditional_edges('agent', should_continue, {'continue': 'tools', 'end': END})
graph_builder.add_edge('tools', 'agent')
graph = graph_builder.compile(checkpointer=memory)
```
Our LangGraph agent graph is illustrated below.
```python
with suppress(Exception):
display(Image(graph.get_graph().draw_mermaid_png()))
```

## Running the Agent
Let's test the agent with a single call
```python
await graph.ainvoke(
{
'messages': [
{
'role': 'user',
'content': 'What sizes do the TinyBirds Wool Runners in Natural Black come in?',
}
],
'user_name': user_name,
'user_node_uuid': user_node_uuid,
},
config={'configurable': {'thread_id': uuid.uuid4().hex}},
)
```
```json
{
"messages": [
{
"content": "What sizes do the TinyBirds Wool Runners in Natural Black come in?",
"id": "6a940637-70a0-4c95-a4d7-4c4846909747",
"type": "HumanMessage"
},
{
"content": "The TinyBirds Wool Runners in Natural Black are available in the following sizes for little kids: 5T, 6T, 8T, 9T, and 10T. \n\nDo you have a specific size in mind, or are you looking for something else? Let me know your needs, and I can help you find the perfect pair!",
"additional_kwargs": {
"refusal": null
},
"response_metadata": {
"token_usage": {
"completion_tokens": 76,
"prompt_tokens": 314,
"total_tokens": 390
},
"model_name": "gpt-4o-mini-2024-07-18",
"system_fingerprint": "fp_f33667828e",
"finish_reason": "stop",
"logprobs": null
},
"id": "run-d2f79c7f-4d41-4896-88dc-476a8e38bea8-0",
"usage_metadata": {
"input_tokens": 314,
"output_tokens": 76,
"total_tokens": 390
},
"type": "AIMessage"
}
],
"user_name": "jess",
"user_node_uuid": "186a845eee4849619d1e625b178d1845"
}
```
## Viewing the Graph
At this stage, the graph would look something like this. The `jess` node is `INTERESTED_IN` the `TinyBirds Wool Runner` node. The image below was generated using Neo4j Desktop.

## Running the Agent interactively
The following code will run the agent in a Jupyter notebook event loop. You can modify the code to suite your own needs.
Just enter a message into the box and click submit.
```python
conversation_output = widgets.Output()
config = {'configurable': {'thread_id': uuid.uuid4().hex}}
user_state = {'user_name': user_name, 'user_node_uuid': user_node_uuid}
async def process_input(user_state: State, user_input: str):
conversation_output.append_stdout(f'\nUser: {user_input}\n')
conversation_output.append_stdout('\nAssistant: ')
graph_state = {
'messages': [{'role': 'user', 'content': user_input}],
'user_name': user_state['user_name'],
'user_node_uuid': user_state['user_node_uuid'],
}
try:
async for event in graph.astream(
graph_state,
config=config,
):
for value in event.values():
if 'messages' in value:
last_message = value['messages'][-1]
if isinstance(last_message, AIMessage) and isinstance(
last_message.content, str
):
conversation_output.append_stdout(last_message.content)
except Exception as e:
conversation_output.append_stdout(f'Error: {e}')
def on_submit(b):
user_input = input_box.value
input_box.value = ''
asyncio.create_task(process_input(user_state, user_input))
input_box = widgets.Text(placeholder='Type your message here...')
submit_button = widgets.Button(description='Send')
submit_button.on_click(on_submit)
conversation_output.append_stdout('Asssistant: Hello, how can I help you find shoes today?')
display(widgets.VBox([input_box, submit_button, conversation_output]))
```
# Telemetry
# Telemetry
Graphiti collects anonymous usage statistics to help us understand how the framework is being used and improve it for everyone. We believe transparency is important, so here's exactly what we collect and why.
## What We Collect
When you initialize a Graphiti instance, we collect:
* **Anonymous identifier**: A randomly generated UUID stored locally in `~/.cache/graphiti/telemetry_anon_id`
* **System information**: Operating system, Python version, and system architecture
* **Graphiti version**: The version you're using
* **Configuration choices**:
* LLM provider type (OpenAI, Azure, Anthropic, etc.)
* Database backend (Neo4j, FalkorDB)
* Embedder provider (OpenAI, Azure, Voyage, etc.)
## What We Don't Collect
We are committed to protecting your privacy. We **never** collect:
* Personal information or identifiers
* API keys or credentials
* Your actual data, queries, or graph content
* IP addresses or hostnames
* File paths or system-specific information
* Any content from your episodes, nodes, or edges
## Why We Collect This Data
This information helps us:
* Understand which configurations are most popular to prioritize support and testing
* Identify which LLM and database providers to focus development efforts on
* Track adoption patterns to guide our roadmap
* Ensure compatibility across different Python versions and operating systems
By sharing this anonymous information, you help us make Graphiti better for everyone in the community.
## View the Telemetry Code
The Telemetry code [may be found here](https://github.com/getzep/graphiti/blob/main/graphiti_core/telemetry/telemetry.py).
## How to Disable Telemetry
Telemetry is **opt-out** and can be disabled at any time. To disable telemetry collection:
### Option 1: Environment Variable
```bash
export GRAPHITI_TELEMETRY_ENABLED=false
```
### Option 2: Set in your shell profile
```bash
# For bash users (~/.bashrc or ~/.bash_profile)
echo 'export GRAPHITI_TELEMETRY_ENABLED=false' >> ~/.bashrc
# For zsh users (~/.zshrc)
echo 'export GRAPHITI_TELEMETRY_ENABLED=false' >> ~/.zshrc
```
### Option 3: Set for a specific Python session
```python
import os
os.environ['GRAPHITI_TELEMETRY_ENABLED'] = 'false'
# Then initialize Graphiti as usual
from graphiti_core import Graphiti
graphiti = Graphiti(...)
```
Telemetry is automatically disabled during test runs (when `pytest` is detected).
## Technical Details
* Telemetry uses PostHog for anonymous analytics collection
* All telemetry operations are designed to fail silently - they will never interrupt your application or affect Graphiti functionality
* The anonymous ID is stored locally and is not tied to any personal information