This is page 1 of 9. Use http://codebase.md/getzep/graphiti?page={x} to view the full context.
# Directory Structure
```
├── .env.example
├── .github
│ ├── dependabot.yml
│ ├── ISSUE_TEMPLATE
│ │ └── bug_report.md
│ ├── pull_request_template.md
│ ├── secret_scanning.yml
│ └── workflows
│ ├── ai-moderator.yml
│ ├── cla.yml
│ ├── claude-code-review-manual.yml
│ ├── claude-code-review.yml
│ ├── claude.yml
│ ├── codeql.yml
│ ├── daily_issue_maintenance.yml
│ ├── issue-triage.yml
│ ├── lint.yml
│ ├── release-graphiti-core.yml
│ ├── release-mcp-server.yml
│ ├── release-server-container.yml
│ ├── typecheck.yml
│ └── unit_tests.yml
├── .gitignore
├── AGENTS.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── conftest.py
├── CONTRIBUTING.md
├── depot.json
├── docker-compose.test.yml
├── docker-compose.yml
├── Dockerfile
├── ellipsis.yaml
├── examples
│ ├── azure-openai
│ │ ├── .env.example
│ │ ├── azure_openai_neo4j.py
│ │ └── README.md
│ ├── data
│ │ └── manybirds_products.json
│ ├── ecommerce
│ │ ├── runner.ipynb
│ │ └── runner.py
│ ├── langgraph-agent
│ │ ├── agent.ipynb
│ │ └── tinybirds-jess.png
│ ├── opentelemetry
│ │ ├── .env.example
│ │ ├── otel_stdout_example.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── podcast
│ │ ├── podcast_runner.py
│ │ ├── podcast_transcript.txt
│ │ └── transcript_parser.py
│ ├── quickstart
│ │ ├── quickstart_falkordb.py
│ │ ├── quickstart_neo4j.py
│ │ ├── quickstart_neptune.py
│ │ ├── README.md
│ │ └── requirements.txt
│ └── wizard_of_oz
│ ├── parser.py
│ ├── runner.py
│ └── woo.txt
├── graphiti_core
│ ├── __init__.py
│ ├── cross_encoder
│ │ ├── __init__.py
│ │ ├── bge_reranker_client.py
│ │ ├── client.py
│ │ ├── gemini_reranker_client.py
│ │ └── openai_reranker_client.py
│ ├── decorators.py
│ ├── driver
│ │ ├── __init__.py
│ │ ├── driver.py
│ │ ├── falkordb_driver.py
│ │ ├── graph_operations
│ │ │ └── graph_operations.py
│ │ ├── kuzu_driver.py
│ │ ├── neo4j_driver.py
│ │ ├── neptune_driver.py
│ │ └── search_interface
│ │ └── search_interface.py
│ ├── edges.py
│ ├── embedder
│ │ ├── __init__.py
│ │ ├── azure_openai.py
│ │ ├── client.py
│ │ ├── gemini.py
│ │ ├── openai.py
│ │ └── voyage.py
│ ├── errors.py
│ ├── graph_queries.py
│ ├── graphiti_types.py
│ ├── graphiti.py
│ ├── helpers.py
│ ├── llm_client
│ │ ├── __init__.py
│ │ ├── anthropic_client.py
│ │ ├── azure_openai_client.py
│ │ ├── client.py
│ │ ├── config.py
│ │ ├── errors.py
│ │ ├── gemini_client.py
│ │ ├── groq_client.py
│ │ ├── openai_base_client.py
│ │ ├── openai_client.py
│ │ ├── openai_generic_client.py
│ │ └── utils.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── edges
│ │ │ ├── __init__.py
│ │ │ └── edge_db_queries.py
│ │ └── nodes
│ │ ├── __init__.py
│ │ └── node_db_queries.py
│ ├── nodes.py
│ ├── prompts
│ │ ├── __init__.py
│ │ ├── dedupe_edges.py
│ │ ├── dedupe_nodes.py
│ │ ├── eval.py
│ │ ├── extract_edge_dates.py
│ │ ├── extract_edges.py
│ │ ├── extract_nodes.py
│ │ ├── invalidate_edges.py
│ │ ├── lib.py
│ │ ├── models.py
│ │ ├── prompt_helpers.py
│ │ ├── snippets.py
│ │ └── summarize_nodes.py
│ ├── py.typed
│ ├── search
│ │ ├── __init__.py
│ │ ├── search_config_recipes.py
│ │ ├── search_config.py
│ │ ├── search_filters.py
│ │ ├── search_helpers.py
│ │ ├── search_utils.py
│ │ └── search.py
│ ├── telemetry
│ │ ├── __init__.py
│ │ └── telemetry.py
│ ├── tracer.py
│ └── utils
│ ├── __init__.py
│ ├── bulk_utils.py
│ ├── datetime_utils.py
│ ├── maintenance
│ │ ├── __init__.py
│ │ ├── community_operations.py
│ │ ├── dedup_helpers.py
│ │ ├── edge_operations.py
│ │ ├── graph_data_operations.py
│ │ ├── node_operations.py
│ │ └── temporal_operations.py
│ ├── ontology_utils
│ │ └── entity_types_utils.py
│ └── text_utils.py
├── images
│ ├── arxiv-screenshot.png
│ ├── graphiti-graph-intro.gif
│ ├── graphiti-intro-slides-stock-2.gif
│ └── simple_graph.svg
├── LICENSE
├── Makefile
├── mcp_server
│ ├── .env.example
│ ├── .python-version
│ ├── config
│ │ ├── config-docker-falkordb-combined.yaml
│ │ ├── config-docker-falkordb.yaml
│ │ ├── config-docker-neo4j.yaml
│ │ ├── config.yaml
│ │ └── mcp_config_stdio_example.json
│ ├── docker
│ │ ├── build-standalone.sh
│ │ ├── build-with-version.sh
│ │ ├── docker-compose-falkordb.yml
│ │ ├── docker-compose-neo4j.yml
│ │ ├── docker-compose.yml
│ │ ├── Dockerfile
│ │ ├── Dockerfile.standalone
│ │ ├── github-actions-example.yml
│ │ ├── README-falkordb-combined.md
│ │ └── README.md
│ ├── docs
│ │ └── cursor_rules.md
│ ├── main.py
│ ├── pyproject.toml
│ ├── pytest.ini
│ ├── README.md
│ ├── src
│ │ ├── __init__.py
│ │ ├── config
│ │ │ ├── __init__.py
│ │ │ └── schema.py
│ │ ├── graphiti_mcp_server.py
│ │ ├── models
│ │ │ ├── __init__.py
│ │ │ ├── entity_types.py
│ │ │ └── response_types.py
│ │ ├── services
│ │ │ ├── __init__.py
│ │ │ ├── factories.py
│ │ │ └── queue_service.py
│ │ └── utils
│ │ ├── __init__.py
│ │ ├── formatting.py
│ │ └── utils.py
│ ├── tests
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── pytest.ini
│ │ ├── README.md
│ │ ├── run_tests.py
│ │ ├── test_async_operations.py
│ │ ├── test_comprehensive_integration.py
│ │ ├── test_configuration.py
│ │ ├── test_falkordb_integration.py
│ │ ├── test_fixtures.py
│ │ ├── test_http_integration.py
│ │ ├── test_integration.py
│ │ ├── test_mcp_integration.py
│ │ ├── test_mcp_transports.py
│ │ ├── test_stdio_simple.py
│ │ └── test_stress_load.py
│ └── uv.lock
├── OTEL_TRACING.md
├── py.typed
├── pyproject.toml
├── pytest.ini
├── README.md
├── SECURITY.md
├── server
│ ├── .env.example
│ ├── graph_service
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── dto
│ │ │ ├── __init__.py
│ │ │ ├── common.py
│ │ │ ├── ingest.py
│ │ │ └── retrieve.py
│ │ ├── main.py
│ │ ├── routers
│ │ │ ├── __init__.py
│ │ │ ├── ingest.py
│ │ │ └── retrieve.py
│ │ └── zep_graphiti.py
│ ├── Makefile
│ ├── pyproject.toml
│ ├── README.md
│ └── uv.lock
├── signatures
│ └── version1
│ └── cla.json
├── tests
│ ├── cross_encoder
│ │ ├── test_bge_reranker_client_int.py
│ │ └── test_gemini_reranker_client.py
│ ├── driver
│ │ ├── __init__.py
│ │ └── test_falkordb_driver.py
│ ├── embedder
│ │ ├── embedder_fixtures.py
│ │ ├── test_gemini.py
│ │ ├── test_openai.py
│ │ └── test_voyage.py
│ ├── evals
│ │ ├── data
│ │ │ └── longmemeval_data
│ │ │ ├── longmemeval_oracle.json
│ │ │ └── README.md
│ │ ├── eval_cli.py
│ │ ├── eval_e2e_graph_building.py
│ │ ├── pytest.ini
│ │ └── utils.py
│ ├── helpers_test.py
│ ├── llm_client
│ │ ├── test_anthropic_client_int.py
│ │ ├── test_anthropic_client.py
│ │ ├── test_azure_openai_client.py
│ │ ├── test_client.py
│ │ ├── test_errors.py
│ │ └── test_gemini_client.py
│ ├── test_edge_int.py
│ ├── test_entity_exclusion_int.py
│ ├── test_graphiti_int.py
│ ├── test_graphiti_mock.py
│ ├── test_node_int.py
│ ├── test_text_utils.py
│ └── utils
│ ├── maintenance
│ │ ├── test_bulk_utils.py
│ │ ├── test_edge_operations.py
│ │ ├── test_node_operations.py
│ │ └── test_temporal_operations_int.py
│ └── search
│ └── search_utils_test.py
├── uv.lock
└── Zep-CLA.md
```
# Files
--------------------------------------------------------------------------------
/mcp_server/.python-version:
--------------------------------------------------------------------------------
```
3.10
```
--------------------------------------------------------------------------------
/examples/opentelemetry/.env.example:
--------------------------------------------------------------------------------
```
# OpenAI API key (required for LLM inference and embeddings)
OPENAI_API_KEY=your_api_key_here
```
--------------------------------------------------------------------------------
/server/.env.example:
--------------------------------------------------------------------------------
```
OPENAI_API_KEY=
NEO4J_PORT=7687
# Only used if not running a neo4j container in docker
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=password
```
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
OPENAI_API_KEY=
# Neo4j database connection
NEO4J_URI=
NEO4J_PORT=
NEO4J_USER=
NEO4J_PASSWORD=
# FalkorDB database connection
FALKORDB_URI=
FALKORDB_PORT=
FALKORDB_USER=
FALKORDB_PASSWORD=
USE_PARALLEL_RUNTIME=
SEMAPHORE_LIMIT=
GITHUB_SHA=
MAX_REFLEXION_ITERATIONS=
ANTHROPIC_API_KEY=
```
--------------------------------------------------------------------------------
/examples/azure-openai/.env.example:
--------------------------------------------------------------------------------
```
# Neo4j connection settings
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=password
# Azure OpenAI settings
AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com
AZURE_OPENAI_API_KEY=your-api-key-here
AZURE_OPENAI_DEPLOYMENT=gpt-5-mini
AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small
```
--------------------------------------------------------------------------------
/mcp_server/.env.example:
--------------------------------------------------------------------------------
```
# Graphiti MCP Server Environment Configuration
# Neo4j Database Configuration
# These settings are used to connect to your Neo4j database
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=demodemo
# OpenAI API Configuration
# Required for LLM operations
OPENAI_API_KEY=your_openai_api_key_here
MODEL_NAME=gpt-4.1-mini
# Optional: Only needed for non-standard OpenAI endpoints
# OPENAI_BASE_URL=https://api.openai.com/v1
# Optional: Group ID for namespacing graph data
# GROUP_ID=my_project
# Concurrency Control
# Controls how many episodes can be processed simultaneously
# Default: 10 (suitable for OpenAI Tier 3, mid-tier Anthropic)
# Adjust based on your LLM provider's rate limits:
# - OpenAI Tier 1 (free): 1-2
# - OpenAI Tier 2: 5-8
# - OpenAI Tier 3: 10-15
# - OpenAI Tier 4: 20-50
# - Anthropic default: 5-8
# - Anthropic high tier: 15-30
# - Ollama (local): 1-5
# See README.md "Concurrency and LLM Provider 429 Rate Limit Errors" for details
SEMAPHORE_LIMIT=10
# Optional: Path configuration for Docker
# PATH=/root/.local/bin:${PATH}
# Optional: Memory settings for Neo4j (used in Docker Compose)
# NEO4J_server_memory_heap_initial__size=512m
# NEO4J_server_memory_heap_max__size=1G
# NEO4J_server_memory_pagecache_size=512m
# Azure OpenAI configuration
# Optional: Only needed for Azure OpenAI endpoints
# AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint_here
# AZURE_OPENAI_API_VERSION=2025-01-01-preview
# AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o-gpt-4o-mini-deployment
# AZURE_OPENAI_EMBEDDING_API_VERSION=2023-05-15
# AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=text-embedding-3-large-deployment
# AZURE_OPENAI_USE_MANAGED_IDENTITY=false
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# uv
# It is generally recommended to include uv.lock in version control.
# This ensures reproducibility across different environments.
# https://docs.astral.sh/uv/concepts/projects/#lockfile
# uv.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
.vscode/
## Other
# Cache files
cache.db*
# All DS_Store files
.DS_Store
```
--------------------------------------------------------------------------------
/tests/evals/data/longmemeval_data/README.md:
--------------------------------------------------------------------------------
```markdown
The `longmemeval_oracle` dataset is an open-source dataset that we are using.
We did not create this dataset and it can be found
here: https://huggingface.co/datasets/xiaowu0162/longmemeval/blob/main/longmemeval_oracle.
```
--------------------------------------------------------------------------------
/examples/opentelemetry/README.md:
--------------------------------------------------------------------------------
```markdown
# OpenTelemetry Stdout Tracing Example
Configure Graphiti with OpenTelemetry to output trace spans to stdout.
## Setup
```bash
uv sync
export OPENAI_API_KEY=your_api_key_here
uv run otel_stdout_example.py
```
## Configure OpenTelemetry with Graphiti
```python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
# Set up OpenTelemetry with stdout exporter
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
# Get tracer and pass to Graphiti
tracer = trace.get_tracer(__name__)
graphiti = Graphiti(
graph_driver=kuzu_driver,
tracer=tracer,
trace_span_prefix='graphiti.example'
)
```
```
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
```markdown
# graph-service
Graph service is a fast api server implementing the [graphiti](https://github.com/getzep/graphiti) package.
## Container Releases
The FastAPI server container is automatically built and published to Docker Hub when a new `graphiti-core` version is released to PyPI.
**Image:** `zepai/graphiti`
**Available tags:**
- `latest` - Latest stable release
- `0.22.1` - Specific version (matches graphiti-core version)
**Platforms:** linux/amd64, linux/arm64
The automated release workflow:
1. Triggers when `graphiti-core` PyPI release completes
2. Waits for PyPI package availability
3. Builds multi-platform Docker image
4. Tags with version number and `latest`
5. Pushes to Docker Hub
Only stable releases are built automatically (pre-release versions are skipped).
## Running Instructions
1. Ensure you have Docker and Docker Compose installed on your system.
2. Add `zepai/graphiti:latest` to your service setup
3. Make sure to pass the following environment variables to the service
```
OPENAI_API_KEY=your_openai_api_key
NEO4J_USER=your_neo4j_user
NEO4J_PASSWORD=your_neo4j_password
NEO4J_PORT=your_neo4j_port
```
4. This service depends on having access to a neo4j instance, you may wish to add a neo4j image to your service setup as well. Or you may wish to use neo4j cloud or a desktop version if running this locally.
An example of docker compose setup may look like this:
```yml
version: '3.8'
services:
graph:
image: zepai/graphiti:latest
ports:
- "8000:8000"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- NEO4J_URI=bolt://neo4j:${NEO4J_PORT}
- NEO4J_USER=${NEO4J_USER}
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
neo4j:
image: neo4j:5.22.0
ports:
- "7474:7474" # HTTP
- "${NEO4J_PORT}:${NEO4J_PORT}" # Bolt
volumes:
- neo4j_data:/data
environment:
- NEO4J_AUTH=${NEO4J_USER}/${NEO4J_PASSWORD}
volumes:
neo4j_data:
```
5. Once you start the service, it will be available at `http://localhost:8000` (or the port you have specified in the docker compose file).
6. You may access the swagger docs at `http://localhost:8000/docs`. You may also access redocs at `http://localhost:8000/redoc`.
7. You may also access the neo4j browser at `http://localhost:7474` (the port depends on the neo4j instance you are using).
```
--------------------------------------------------------------------------------
/examples/azure-openai/README.md:
--------------------------------------------------------------------------------
```markdown
# Azure OpenAI with Neo4j Example
This example demonstrates how to use Graphiti with Azure OpenAI and Neo4j to build a knowledge graph.
## Prerequisites
- Python 3.10+
- Neo4j database (running locally or remotely)
- Azure OpenAI subscription with deployed models
## Setup
### 1. Install Dependencies
```bash
uv sync
```
### 2. Configure Environment Variables
Copy the `.env.example` file to `.env` and fill in your credentials:
```bash
cd examples/azure-openai
cp .env.example .env
```
Edit `.env` with your actual values:
```env
# Neo4j connection settings
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your-password
# Azure OpenAI settings
AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com
AZURE_OPENAI_API_KEY=your-api-key-here
AZURE_OPENAI_DEPLOYMENT=gpt-5-mini
AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small
```
### 3. Azure OpenAI Model Deployments
This example requires two Azure OpenAI model deployments:
1. **Chat Completion Model**: Used for entity extraction and relationship analysis
- Set the deployment name in `AZURE_OPENAI_DEPLOYMENT`
2. **Embedding Model**: Used for semantic search
- Set the deployment name in `AZURE_OPENAI_EMBEDDING_DEPLOYMENT`
### 4. Neo4j Setup
Make sure Neo4j is running and accessible at the URI specified in your `.env` file.
For local development:
- Download and install [Neo4j Desktop](https://neo4j.com/download/)
- Create a new database
- Start the database
- Use the credentials in your `.env` file
## Running the Example
```bash
cd examples/azure-openai
uv run azure_openai_neo4j.py
```
## What This Example Does
1. **Initialization**: Sets up connections to Neo4j and Azure OpenAI
2. **Adding Episodes**: Ingests text and JSON data about California politics
3. **Basic Search**: Performs hybrid search combining semantic similarity and BM25 retrieval
4. **Center Node Search**: Reranks results based on graph distance to a specific node
5. **Cleanup**: Properly closes database connections
## Key Concepts
### Azure OpenAI Integration
The example shows how to configure Graphiti to use Azure OpenAI with the OpenAI v1 API:
```python
# Initialize Azure OpenAI client using the standard OpenAI client
# with Azure's v1 API endpoint
azure_client = AsyncOpenAI(
base_url=f"{azure_endpoint}/openai/v1/",
api_key=azure_api_key,
)
# Create LLM and Embedder clients
llm_client = AzureOpenAILLMClient(
azure_client=azure_client,
config=LLMConfig(model=azure_deployment, small_model=azure_deployment)
)
embedder_client = AzureOpenAIEmbedderClient(
azure_client=azure_client,
model=azure_embedding_deployment
)
# Initialize Graphiti with custom clients
graphiti = Graphiti(
neo4j_uri,
neo4j_user,
neo4j_password,
llm_client=llm_client,
embedder=embedder_client,
)
```
**Note**: This example uses Azure OpenAI's v1 API compatibility layer, which allows using the standard `AsyncOpenAI` client. The endpoint format is `https://your-resource-name.openai.azure.com/openai/v1/`.
### Episodes
Episodes are the primary units of information in Graphiti. They can be:
- **Text**: Raw text content (e.g., transcripts, documents)
- **JSON**: Structured data with key-value pairs
### Hybrid Search
Graphiti combines multiple search strategies:
- **Semantic Search**: Uses embeddings to find semantically similar content
- **BM25**: Keyword-based text retrieval
- **Graph Traversal**: Leverages relationships between entities
## Troubleshooting
### Azure OpenAI API Errors
- Verify your endpoint URL is correct (should end in `.openai.azure.com`)
- Check that your API key is valid
- Ensure your deployment names match actual deployments in Azure
- Verify API version is supported by your deployment
### Neo4j Connection Issues
- Ensure Neo4j is running
- Check firewall settings
- Verify credentials are correct
- Check URI format (should be `bolt://` or `neo4j://`)
## Next Steps
- Explore other search recipes in `graphiti_core/search/search_config_recipes.py`
- Try different episode types and content
- Experiment with custom entity definitions
- Add more episodes to build a larger knowledge graph
## Related Examples
- `examples/quickstart/` - Basic Graphiti usage with OpenAI
- `examples/podcast/` - Processing longer content
- `examples/ecommerce/` - Domain-specific knowledge graphs
```
--------------------------------------------------------------------------------
/examples/quickstart/README.md:
--------------------------------------------------------------------------------
```markdown
# Graphiti Quickstart Example
This example demonstrates the basic functionality of Graphiti, including:
1. Connecting to a Neo4j or FalkorDB database
2. Initializing Graphiti indices and constraints
3. Adding episodes to the graph
4. Searching the graph with semantic and keyword matching
5. Exploring graph-based search with reranking using the top search result's source node UUID
6. Performing node search using predefined search recipes
## Prerequisites
- Python 3.9+
- OpenAI API key (set as `OPENAI_API_KEY` environment variable)
- **For Neo4j**:
- Neo4j Desktop installed and running
- A local DBMS created and started in Neo4j Desktop
- **For FalkorDB**:
- FalkorDB server running (see [FalkorDB documentation](https://docs.falkordb.com) for setup)
- **For Amazon Neptune**:
- Amazon server running (see [Amazon Neptune documentation](https://aws.amazon.com/neptune/developer-resources/) for setup)
## Setup Instructions
1. Install the required dependencies:
```bash
pip install graphiti-core
```
2. Set up environment variables:
```bash
# Required for LLM and embedding
export OPENAI_API_KEY=your_openai_api_key
# Optional Neo4j connection parameters (defaults shown)
export NEO4J_URI=bolt://localhost:7687
export NEO4J_USER=neo4j
export NEO4J_PASSWORD=password
# Optional FalkorDB connection parameters (defaults shown)
export FALKORDB_URI=falkor://localhost:6379
# Optional Amazon Neptune connection parameters
NEPTUNE_HOST=your_neptune_host
NEPTUNE_PORT=your_port_or_8182
AOSS_HOST=your_aoss_host
AOSS_PORT=your_port_or_443
# To use a different database, modify the driver constructor in the script
```
TIP: For Amazon Neptune host string please use the following formats
* For Neptune Database: `neptune-db://<cluster endpoint>`
* For Neptune Analytics: `neptune-graph://<graph identifier>`
3. Run the example:
```bash
python quickstart_neo4j.py
# For FalkorDB
python quickstart_falkordb.py
# For Amazon Neptune
python quickstart_neptune.py
```
## What This Example Demonstrates
- **Graph Initialization**: Setting up the Graphiti indices and constraints in Neo4j, Amazon Neptune, or FalkorDB
- **Adding Episodes**: Adding text content that will be analyzed and converted into knowledge graph nodes and edges
- **Edge Search Functionality**: Performing hybrid searches that combine semantic similarity and BM25 retrieval to find relationships (edges)
- **Graph-Aware Search**: Using the source node UUID from the top search result to rerank additional search results based on graph distance
- **Node Search Using Recipes**: Using predefined search configurations like NODE_HYBRID_SEARCH_RRF to directly search for nodes rather than edges
- **Result Processing**: Understanding the structure of search results including facts, nodes, and temporal metadata
## Next Steps
After running this example, you can:
1. Modify the episode content to add your own information
2. Try different search queries to explore the knowledge extraction
3. Experiment with different center nodes for graph-distance-based reranking
4. Try other predefined search recipes from `graphiti_core.search.search_config_recipes`
5. Explore the more advanced examples in the other directories
## Troubleshooting
### "Graph not found: default_db" Error
If you encounter the error `Neo.ClientError.Database.DatabaseNotFound: Graph not found: default_db`, this occurs when the driver is trying to connect to a database that doesn't exist.
**Solution:**
The Neo4j driver defaults to using `neo4j` as the database name. If you need to use a different database, modify the driver constructor in the script:
```python
# In quickstart_neo4j.py, change:
driver = Neo4jDriver(uri=neo4j_uri, user=neo4j_user, password=neo4j_password)
# To specify a different database:
driver = Neo4jDriver(uri=neo4j_uri, user=neo4j_user, password=neo4j_password, database="your_db_name")
```
## Understanding the Output
### Edge Search Results
The edge search results include EntityEdge objects with:
- UUID: Unique identifier for the edge
- Fact: The extracted fact from the episode
- Valid at/invalid at: Time period during which the fact was true (if available)
- Source/target node UUIDs: Connections between entities in the knowledge graph
### Node Search Results
The node search results include EntityNode objects with:
- UUID: Unique identifier for the node
- Name: The name of the entity
- Content Summary: A summary of the node's content
- Node Labels: The types of the node (e.g., Person, Organization)
- Created At: When the node was created
- Attributes: Additional properties associated with the node
```
--------------------------------------------------------------------------------
/mcp_server/docker/README.md:
--------------------------------------------------------------------------------
```markdown
# Docker Deployment for Graphiti MCP Server
This directory contains Docker Compose configurations for running the Graphiti MCP server with graph database backends: FalkorDB (combined image) and Neo4j.
## Quick Start
```bash
# Default configuration (FalkorDB combined image)
docker-compose up
# Neo4j (separate containers)
docker-compose -f docker-compose-neo4j.yml up
```
## Environment Variables
Create a `.env` file in this directory with your API keys:
```bash
# Required
OPENAI_API_KEY=your-api-key-here
# Optional
GRAPHITI_GROUP_ID=main
SEMAPHORE_LIMIT=10
# Database-specific variables (see database sections below)
```
## Database Configurations
### FalkorDB (Combined Image)
**File:** `docker-compose.yml` (default)
The default configuration uses a combined Docker image that bundles both FalkorDB and the MCP server together for simplified deployment.
#### Configuration
```bash
# Environment variables
FALKORDB_URI=redis://localhost:6379 # Connection URI (services run in same container)
FALKORDB_PASSWORD= # Password (default: empty)
FALKORDB_DATABASE=default_db # Database name (default: default_db)
```
#### Accessing Services
- **FalkorDB (Redis):** redis://localhost:6379
- **FalkorDB Web UI:** http://localhost:3000
- **MCP Server:** http://localhost:8000
#### Data Management
**Backup:**
```bash
docker run --rm -v mcp_server_falkordb_data:/var/lib/falkordb/data -v $(pwd):/backup alpine \
tar czf /backup/falkordb-backup.tar.gz -C /var/lib/falkordb/data .
```
**Restore:**
```bash
docker run --rm -v mcp_server_falkordb_data:/var/lib/falkordb/data -v $(pwd):/backup alpine \
tar xzf /backup/falkordb-backup.tar.gz -C /var/lib/falkordb/data
```
**Clear Data:**
```bash
docker-compose down
docker volume rm mcp_server_falkordb_data
docker-compose up
```
#### Gotchas
- Both FalkorDB and MCP server run in the same container
- FalkorDB uses Redis persistence mechanisms (AOF/RDB)
- Default configuration has no password - add one for production
- Health check only monitors FalkorDB; MCP server startup visible in logs
See [README-falkordb-combined.md](README-falkordb-combined.md) for detailed information about the combined image.
### Neo4j
**File:** `docker-compose-neo4j.yml`
Neo4j runs as a separate container service with its own web interface.
#### Configuration
```bash
# Environment variables
NEO4J_URI=bolt://neo4j:7687 # Connection URI (default: bolt://neo4j:7687)
NEO4J_USER=neo4j # Username (default: neo4j)
NEO4J_PASSWORD=demodemo # Password (default: demodemo)
NEO4J_DATABASE=neo4j # Database name (default: neo4j)
USE_PARALLEL_RUNTIME=false # Enterprise feature (default: false)
```
#### Accessing Neo4j
- **Web Interface:** http://localhost:7474
- **Bolt Protocol:** bolt://localhost:7687
- **MCP Server:** http://localhost:8000
Default credentials: `neo4j` / `demodemo`
#### Data Management
**Backup:**
```bash
# Backup both data and logs volumes
docker run --rm -v docker_neo4j_data:/data -v $(pwd):/backup alpine \
tar czf /backup/neo4j-data-backup.tar.gz -C /data .
docker run --rm -v docker_neo4j_logs:/logs -v $(pwd):/backup alpine \
tar czf /backup/neo4j-logs-backup.tar.gz -C /logs .
```
**Restore:**
```bash
# Restore both volumes
docker run --rm -v docker_neo4j_data:/data -v $(pwd):/backup alpine \
tar xzf /backup/neo4j-data-backup.tar.gz -C /data
docker run --rm -v docker_neo4j_logs:/logs -v $(pwd):/backup alpine \
tar xzf /backup/neo4j-logs-backup.tar.gz -C /logs
```
**Clear Data:**
```bash
docker-compose -f docker-compose-neo4j.yml down
docker volume rm docker_neo4j_data docker_neo4j_logs
docker-compose -f docker-compose-neo4j.yml up
```
#### Gotchas
- Neo4j takes 30+ seconds to start up - wait for the health check
- The web interface requires authentication even for local access
- Memory heap is configured for 512MB initial, 1GB max
- Page cache is set to 512MB
- Enterprise features like parallel runtime require a license
## Switching Between Databases
To switch from FalkorDB to Neo4j (or vice versa):
1. **Stop current setup:**
```bash
docker-compose down # Stop FalkorDB combined image
# or
docker-compose -f docker-compose-neo4j.yml down # Stop Neo4j
```
2. **Start new database:**
```bash
docker-compose up # Start FalkorDB combined image
# or
docker-compose -f docker-compose-neo4j.yml up # Start Neo4j
```
Note: Data is not automatically migrated between different database types. You'll need to export from one and import to another using the MCP API.
## Troubleshooting
### Port Conflicts
If port 8000 is already in use:
```bash
# Find what's using the port
lsof -i :8000
# Change the port in docker-compose.yml
# Under ports section: "8001:8000"
```
### Container Won't Start
1. Check logs:
```bash
docker-compose logs graphiti-mcp
```
2. Verify `.env` file exists and contains valid API keys:
```bash
cat .env | grep API_KEY
```
3. Ensure Docker has enough resources allocated
### Database Connection Issues
**FalkorDB:**
- Test Redis connectivity: `docker compose exec graphiti-falkordb redis-cli ping`
- Check FalkorDB logs: `docker compose logs graphiti-falkordb`
- Verify both services started: Look for "FalkorDB is ready!" and "Starting MCP server..." in logs
**Neo4j:**
- Wait for health check to pass (can take 30+ seconds)
- Check Neo4j logs: `docker-compose -f docker-compose-neo4j.yml logs neo4j`
- Verify credentials match environment variables
**FalkorDB:**
- Test Redis connectivity: `redis-cli -h localhost ping`
### Data Not Persisting
1. Verify volumes are created:
```bash
docker volume ls | grep docker_
```
2. Check volume mounts in container:
```bash
docker inspect graphiti-mcp | grep -A 5 Mounts
```
3. Ensure proper shutdown:
```bash
docker-compose down # Not docker-compose down -v (which removes volumes)
```
### Performance Issues
**FalkorDB:**
- Adjust `SEMAPHORE_LIMIT` environment variable
- Monitor with: `docker stats graphiti-falkordb`
- Check Redis memory: `docker compose exec graphiti-falkordb redis-cli info memory`
**Neo4j:**
- Increase heap memory in docker-compose-neo4j.yml
- Adjust page cache size based on data size
- Check query performance in Neo4j browser
## Docker Resources
### Volumes
Each database configuration uses named volumes for data persistence:
- FalkorDB (combined): `falkordb_data`
- Neo4j: `neo4j_data`, `neo4j_logs`
### Networks
All configurations use the default bridge network. Services communicate using container names as hostnames.
### Resource Limits
No resource limits are set by default. To add limits, modify the docker-compose file:
```yaml
services:
graphiti-mcp:
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
```
## Configuration Files
Each database has a dedicated configuration file in `../config/`:
- `config-docker-falkordb-combined.yaml` - FalkorDB combined image configuration
- `config-docker-neo4j.yaml` - Neo4j configuration
These files are mounted read-only into the container at `/app/mcp/config/config.yaml` (for combined image) or `/app/config/config.yaml` (for Neo4j).
```
--------------------------------------------------------------------------------
/mcp_server/tests/README.md:
--------------------------------------------------------------------------------
```markdown
# Graphiti MCP Server Integration Tests
This directory contains a comprehensive integration test suite for the Graphiti MCP Server using the official Python MCP SDK.
## Overview
The test suite is designed to thoroughly test all aspects of the Graphiti MCP server with special consideration for LLM inference latency and system performance.
## Test Organization
### Core Test Modules
- **`test_comprehensive_integration.py`** - Main integration test suite covering all MCP tools
- **`test_async_operations.py`** - Tests for concurrent operations and async patterns
- **`test_stress_load.py`** - Stress testing and load testing scenarios
- **`test_fixtures.py`** - Shared fixtures and test utilities
- **`test_mcp_integration.py`** - Original MCP integration tests
- **`test_configuration.py`** - Configuration loading and validation tests
### Test Categories
Tests are organized with pytest markers:
- `unit` - Fast unit tests without external dependencies
- `integration` - Tests requiring database and services
- `slow` - Long-running tests (stress/load tests)
- `requires_neo4j` - Tests requiring Neo4j
- `requires_falkordb` - Tests requiring FalkorDB
- `requires_openai` - Tests requiring OpenAI API key
## Installation
```bash
# Install test dependencies
uv add --dev pytest pytest-asyncio pytest-timeout pytest-xdist faker psutil
# Install MCP SDK
uv add mcp
```
## Running Tests
### Quick Start
```bash
# Run smoke tests (quick validation)
python tests/run_tests.py smoke
# Run integration tests with mock LLM
python tests/run_tests.py integration --mock-llm
# Run all tests
python tests/run_tests.py all
```
### Test Runner Options
```bash
python tests/run_tests.py [suite] [options]
Suites:
unit - Unit tests only
integration - Integration tests
comprehensive - Comprehensive integration suite
async - Async operation tests
stress - Stress and load tests
smoke - Quick smoke tests
all - All tests
Options:
--database - Database backend (neo4j, falkordb)
--mock-llm - Use mock LLM for faster testing
--parallel N - Run tests in parallel with N workers
--coverage - Generate coverage report
--skip-slow - Skip slow tests
--timeout N - Test timeout in seconds
--check-only - Only check prerequisites
```
### Examples
```bash
# Quick smoke test with FalkorDB (default)
python tests/run_tests.py smoke
# Full integration test with Neo4j
python tests/run_tests.py integration --database neo4j
# Stress testing with parallel execution
python tests/run_tests.py stress --parallel 4
# Run with coverage
python tests/run_tests.py all --coverage
# Check prerequisites only
python tests/run_tests.py all --check-only
```
## Test Coverage
### Core Operations
- Server initialization and tool discovery
- Adding memories (text, JSON, message)
- Episode queue management
- Search operations (semantic, hybrid)
- Episode retrieval and deletion
- Entity and edge operations
### Async Operations
- Concurrent operations
- Queue management
- Sequential processing within groups
- Parallel processing across groups
### Performance Testing
- Latency measurement
- Throughput testing
- Batch processing
- Resource usage monitoring
### Stress Testing
- Sustained load scenarios
- Spike load handling
- Memory leak detection
- Connection pool exhaustion
- Rate limit handling
## Configuration
### Environment Variables
```bash
# Database configuration
export DATABASE_PROVIDER=falkordb # or neo4j
export NEO4J_URI=bolt://localhost:7687
export NEO4J_USER=neo4j
export NEO4J_PASSWORD=graphiti
export FALKORDB_URI=redis://localhost:6379
# LLM configuration
export OPENAI_API_KEY=your_key_here # or use --mock-llm
# Test configuration
export TEST_MODE=true
export LOG_LEVEL=INFO
```
### pytest.ini Configuration
The `pytest.ini` file configures:
- Test discovery patterns
- Async mode settings
- Test markers
- Timeout settings
- Output formatting
## Test Fixtures
### Data Generation
The test suite includes comprehensive data generators:
```python
from test_fixtures import TestDataGenerator
# Generate test data
company = TestDataGenerator.generate_company_profile()
conversation = TestDataGenerator.generate_conversation()
document = TestDataGenerator.generate_technical_document()
```
### Test Client
Simplified client creation:
```python
from test_fixtures import graphiti_test_client
async with graphiti_test_client(database="falkordb") as (session, group_id):
# Use session for testing
result = await session.call_tool('add_memory', {...})
```
## Performance Considerations
### LLM Latency Management
The tests account for LLM inference latency through:
1. **Configurable timeouts** - Different timeouts for different operations
2. **Mock LLM option** - Fast testing without API calls
3. **Intelligent polling** - Adaptive waiting for episode processing
4. **Batch operations** - Testing efficiency of batched requests
### Resource Management
- Memory leak detection
- Connection pool monitoring
- Resource usage tracking
- Graceful degradation testing
## CI/CD Integration
### GitHub Actions
```yaml
name: MCP Integration Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
neo4j:
image: neo4j:5.26
env:
NEO4J_AUTH: neo4j/graphiti
ports:
- 7687:7687
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
pip install uv
uv sync --extra dev
- name: Run smoke tests
run: python tests/run_tests.py smoke --mock-llm
- name: Run integration tests
run: python tests/run_tests.py integration --database neo4j
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
```
## Troubleshooting
### Common Issues
1. **Database connection failures**
```bash
# Check Neo4j
curl http://localhost:7474
# Check FalkorDB
redis-cli ping
```
2. **API key issues**
```bash
# Use mock LLM for testing without API key
python tests/run_tests.py all --mock-llm
```
3. **Timeout errors**
```bash
# Increase timeout for slow systems
python tests/run_tests.py integration --timeout 600
```
4. **Memory issues**
```bash
# Skip stress tests on low-memory systems
python tests/run_tests.py all --skip-slow
```
## Test Reports
### Performance Report
After running performance tests:
```python
from test_fixtures import PerformanceBenchmark
benchmark = PerformanceBenchmark()
# ... run tests ...
print(benchmark.report())
```
### Load Test Report
Stress tests generate detailed reports:
```
LOAD TEST REPORT
================
Test Run 1:
Total Operations: 100
Success Rate: 95.0%
Throughput: 12.5 ops/s
Latency (avg/p50/p95/p99/max): 0.8/0.7/1.5/2.1/3.2s
```
## Contributing
When adding new tests:
1. Use appropriate pytest markers
2. Include docstrings explaining test purpose
3. Use fixtures for common operations
4. Consider LLM latency in test design
5. Add timeout handling for long operations
6. Include performance metrics where relevant
## License
See main project LICENSE file.
```
--------------------------------------------------------------------------------
/mcp_server/README.md:
--------------------------------------------------------------------------------
```markdown
# Graphiti MCP Server
Graphiti is a framework for building and querying temporally-aware knowledge graphs, specifically tailored for AI agents
operating in dynamic environments. Unlike traditional retrieval-augmented generation (RAG) methods, Graphiti
continuously integrates user interactions, structured and unstructured enterprise data, and external information into a
coherent, queryable graph. The framework supports incremental data updates, efficient retrieval, and precise historical
queries without requiring complete graph recomputation, making it suitable for developing interactive, context-aware AI
applications.
This is an experimental Model Context Protocol (MCP) server implementation for Graphiti. The MCP server exposes
Graphiti's key functionality through the MCP protocol, allowing AI assistants to interact with Graphiti's knowledge
graph capabilities.
## Features
The Graphiti MCP server provides comprehensive knowledge graph capabilities:
- **Episode Management**: Add, retrieve, and delete episodes (text, messages, or JSON data)
- **Entity Management**: Search and manage entity nodes and relationships in the knowledge graph
- **Search Capabilities**: Search for facts (edges) and node summaries using semantic and hybrid search
- **Group Management**: Organize and manage groups of related data with group_id filtering
- **Graph Maintenance**: Clear the graph and rebuild indices
- **Graph Database Support**: Multiple backend options including FalkorDB (default) and Neo4j
- **Multiple LLM Providers**: Support for OpenAI, Anthropic, Gemini, Groq, and Azure OpenAI
- **Multiple Embedding Providers**: Support for OpenAI, Voyage, Sentence Transformers, and Gemini embeddings
- **Rich Entity Types**: Built-in entity types including Preferences, Requirements, Procedures, Locations, Events, Organizations, Documents, and more for structured knowledge extraction
- **HTTP Transport**: Default HTTP transport with MCP endpoint at `/mcp/` for broad client compatibility
- **Queue-based Processing**: Asynchronous episode processing with configurable concurrency limits
## Quick Start
### Clone the Graphiti GitHub repo
```bash
git clone https://github.com/getzep/graphiti.git
```
or
```bash
gh repo clone getzep/graphiti
```
### For Claude Desktop and other `stdio` only clients
1. Note the full path to this directory.
```
cd graphiti && pwd
```
2. Install the [Graphiti prerequisites](#prerequisites).
3. Configure Claude, Cursor, or other MCP client to use [Graphiti with a `stdio` transport](#integrating-with-mcp-clients). See the client documentation on where to find their MCP configuration files.
### For Cursor and other HTTP-enabled clients
1. Change directory to the `mcp_server` directory
`cd graphiti/mcp_server`
2. Start the combined FalkorDB + MCP server using Docker Compose (recommended)
```bash
docker compose up
```
This starts both FalkorDB and the MCP server in a single container.
**Alternative**: Run with separate containers using Neo4j:
```bash
docker compose -f docker/docker-compose-neo4j.yml up
```
4. Point your MCP client to `http://localhost:8000/mcp/`
## Installation
### Prerequisites
1. Docker and Docker Compose (for the default FalkorDB setup)
2. OpenAI API key for LLM operations (or API keys for other supported LLM providers)
3. (Optional) Python 3.10+ if running the MCP server standalone with an external FalkorDB instance
### Setup
1. Clone the repository and navigate to the mcp_server directory
2. Use `uv` to create a virtual environment and install dependencies:
```bash
# Install uv if you don't have it already
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create a virtual environment and install dependencies in one step
uv sync
# Optional: Install additional LLM providers (anthropic, gemini, groq, voyage, sentence-transformers)
uv sync --extra providers
```
## Configuration
The server can be configured using a `config.yaml` file, environment variables, or command-line arguments (in order of precedence).
### Default Configuration
The MCP server comes with sensible defaults:
- **Transport**: HTTP (accessible at `http://localhost:8000/mcp/`)
- **Database**: FalkorDB (combined in single container with MCP server)
- **LLM**: OpenAI with model gpt-5-mini
- **Embedder**: OpenAI text-embedding-3-small
### Database Configuration
#### FalkorDB (Default)
FalkorDB is a Redis-based graph database that comes bundled with the MCP server in a single Docker container. This is the default and recommended setup.
```yaml
database:
provider: "falkordb" # Default
providers:
falkordb:
uri: "redis://localhost:6379"
password: "" # Optional
database: "default_db" # Optional
```
#### Neo4j
For production use or when you need a full-featured graph database, Neo4j is recommended:
```yaml
database:
provider: "neo4j"
providers:
neo4j:
uri: "bolt://localhost:7687"
username: "neo4j"
password: "your_password"
database: "neo4j" # Optional, defaults to "neo4j"
```
#### FalkorDB
FalkorDB is another graph database option based on Redis:
```yaml
database:
provider: "falkordb"
providers:
falkordb:
uri: "redis://localhost:6379"
password: "" # Optional
database: "default_db" # Optional
```
### Configuration File (config.yaml)
The server supports multiple LLM providers (OpenAI, Anthropic, Gemini, Groq) and embedders. Edit `config.yaml` to configure:
```yaml
server:
transport: "http" # Default. Options: stdio, http
llm:
provider: "openai" # or "anthropic", "gemini", "groq", "azure_openai"
model: "gpt-4.1" # Default model
database:
provider: "falkordb" # Default. Options: "falkordb", "neo4j"
```
### Using Ollama for Local LLM
To use Ollama with the MCP server, configure it as an OpenAI-compatible endpoint:
```yaml
llm:
provider: "openai"
model: "gpt-oss:120b" # or your preferred Ollama model
api_base: "http://localhost:11434/v1"
api_key: "ollama" # dummy key required
embedder:
provider: "sentence_transformers" # recommended for local setup
model: "all-MiniLM-L6-v2"
```
Make sure Ollama is running locally with: `ollama serve`
### Entity Types
Graphiti MCP Server includes built-in entity types for structured knowledge extraction. These entity types are always enabled and configured via the `entity_types` section in your `config.yaml`:
**Available Entity Types:**
- **Preference**: User preferences, choices, opinions, or selections (prioritized for user-specific information)
- **Requirement**: Specific needs, features, or functionality that must be fulfilled
- **Procedure**: Standard operating procedures and sequential instructions
- **Location**: Physical or virtual places where activities occur
- **Event**: Time-bound activities, occurrences, or experiences
- **Organization**: Companies, institutions, groups, or formal entities
- **Document**: Information content in various forms (books, articles, reports, videos, etc.)
- **Topic**: Subject of conversation, interest, or knowledge domain (used as a fallback)
- **Object**: Physical items, tools, devices, or possessions (used as a fallback)
These entity types are defined in `config.yaml` and can be customized by modifying the descriptions:
```yaml
graphiti:
entity_types:
- name: "Preference"
description: "User preferences, choices, opinions, or selections"
- name: "Requirement"
description: "Specific needs, features, or functionality"
# ... additional entity types
```
The MCP server automatically uses these entity types during episode ingestion to extract and structure information from conversations and documents.
### Environment Variables
The `config.yaml` file supports environment variable expansion using `${VAR_NAME}` or `${VAR_NAME:default}` syntax. Key variables:
- `NEO4J_URI`: URI for the Neo4j database (default: `bolt://localhost:7687`)
- `NEO4J_USER`: Neo4j username (default: `neo4j`)
- `NEO4J_PASSWORD`: Neo4j password (default: `demodemo`)
- `OPENAI_API_KEY`: OpenAI API key (required for OpenAI LLM/embedder)
- `ANTHROPIC_API_KEY`: Anthropic API key (for Claude models)
- `GOOGLE_API_KEY`: Google API key (for Gemini models)
- `GROQ_API_KEY`: Groq API key (for Groq models)
- `AZURE_OPENAI_API_KEY`: Azure OpenAI API key
- `AZURE_OPENAI_ENDPOINT`: Azure OpenAI endpoint URL
- `AZURE_OPENAI_DEPLOYMENT`: Azure OpenAI deployment name
- `AZURE_OPENAI_EMBEDDINGS_ENDPOINT`: Optional Azure OpenAI embeddings endpoint URL
- `AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT`: Optional Azure OpenAI embeddings deployment name
- `AZURE_OPENAI_API_VERSION`: Optional Azure OpenAI API version
- `USE_AZURE_AD`: Optional use Azure Managed Identities for authentication
- `SEMAPHORE_LIMIT`: Episode processing concurrency. See [Concurrency and LLM Provider 429 Rate Limit Errors](#concurrency-and-llm-provider-429-rate-limit-errors)
You can set these variables in a `.env` file in the project directory.
## Running the Server
### Default Setup (FalkorDB Combined Container)
To run the Graphiti MCP server with the default FalkorDB setup:
```bash
docker compose up
```
This starts a single container with:
- HTTP transport on `http://localhost:8000/mcp/`
- FalkorDB graph database on `localhost:6379`
- FalkorDB web UI on `http://localhost:3000`
- OpenAI LLM with gpt-5-mini model
### Running with Neo4j
#### Option 1: Using Docker Compose
The easiest way to run with Neo4j is using the provided Docker Compose configuration:
```bash
# This starts both Neo4j and the MCP server
docker compose -f docker/docker-compose.neo4j.yaml up
```
#### Option 2: Direct Execution with Existing Neo4j
If you have Neo4j already running:
```bash
# Set environment variables
export NEO4J_URI="bolt://localhost:7687"
export NEO4J_USER="neo4j"
export NEO4J_PASSWORD="your_password"
# Run with Neo4j
uv run graphiti_mcp_server.py --database-provider neo4j
```
Or use the Neo4j configuration file:
```bash
uv run graphiti_mcp_server.py --config config/config-docker-neo4j.yaml
```
### Running with FalkorDB
#### Option 1: Using Docker Compose
```bash
# This starts both FalkorDB (Redis-based) and the MCP server
docker compose -f docker/docker-compose.falkordb.yaml up
```
#### Option 2: Direct Execution with Existing FalkorDB
```bash
# Set environment variables
export FALKORDB_URI="redis://localhost:6379"
export FALKORDB_PASSWORD="" # If password protected
# Run with FalkorDB
uv run graphiti_mcp_server.py --database-provider falkordb
```
Or use the FalkorDB configuration file:
```bash
uv run graphiti_mcp_server.py --config config/config-docker-falkordb.yaml
```
### Available Command-Line Arguments
- `--config`: Path to YAML configuration file (default: config.yaml)
- `--llm-provider`: LLM provider to use (openai, anthropic, gemini, groq, azure_openai)
- `--embedder-provider`: Embedder provider to use (openai, azure_openai, gemini, voyage)
- `--database-provider`: Database provider to use (falkordb, neo4j) - default: falkordb
- `--model`: Model name to use with the LLM client
- `--temperature`: Temperature setting for the LLM (0.0-2.0)
- `--transport`: Choose the transport method (http or stdio, default: http)
- `--group-id`: Set a namespace for the graph (optional). If not provided, defaults to "main"
- `--destroy-graph`: If set, destroys all Graphiti graphs on startup
### Concurrency and LLM Provider 429 Rate Limit Errors
Graphiti's ingestion pipelines are designed for high concurrency, controlled by the `SEMAPHORE_LIMIT` environment variable. This setting determines how many episodes can be processed simultaneously. Since each episode involves multiple LLM calls (entity extraction, deduplication, summarization), the actual number of concurrent LLM requests will be several times higher.
**Default:** `SEMAPHORE_LIMIT=10` (suitable for OpenAI Tier 3, mid-tier Anthropic)
#### Tuning Guidelines by LLM Provider
**OpenAI:**
- Tier 1 (free): 3 RPM → `SEMAPHORE_LIMIT=1-2`
- Tier 2: 60 RPM → `SEMAPHORE_LIMIT=5-8`
- Tier 3: 500 RPM → `SEMAPHORE_LIMIT=10-15`
- Tier 4: 5,000 RPM → `SEMAPHORE_LIMIT=20-50`
**Anthropic:**
- Default tier: 50 RPM → `SEMAPHORE_LIMIT=5-8`
- High tier: 1,000 RPM → `SEMAPHORE_LIMIT=15-30`
**Azure OpenAI:**
- Consult your quota in Azure Portal and adjust accordingly
- Start conservative and increase gradually
**Ollama (local):**
- Hardware dependent → `SEMAPHORE_LIMIT=1-5`
- Monitor CPU/GPU usage and adjust
#### Symptoms
- **Too high**: 429 rate limit errors, increased API costs from parallel processing
- **Too low**: Slow episode throughput, underutilized API quota
#### Monitoring
- Watch logs for `429` rate limit errors
- Monitor episode processing times in server logs
- Check your LLM provider's dashboard for actual request rates
- Track token usage and costs
Set this in your `.env` file:
```bash
SEMAPHORE_LIMIT=10 # Adjust based on your LLM provider tier
```
### Docker Deployment
The Graphiti MCP server can be deployed using Docker with your choice of database backend. The Dockerfile uses `uv` for package management, ensuring consistent dependency installation.
A pre-built Graphiti MCP container is available at: `zepai/knowledge-graph-mcp`
#### Environment Configuration
Before running Docker Compose, configure your API keys using a `.env` file (recommended):
1. **Create a .env file in the mcp_server directory**:
```bash
cd graphiti/mcp_server
cp .env.example .env
```
2. **Edit the .env file** to set your API keys:
```bash
# Required - at least one LLM provider API key
OPENAI_API_KEY=your_openai_api_key_here
# Optional - other LLM providers
ANTHROPIC_API_KEY=your_anthropic_key
GOOGLE_API_KEY=your_google_key
GROQ_API_KEY=your_groq_key
# Optional - embedder providers
VOYAGE_API_KEY=your_voyage_key
```
**Important**: The `.env` file must be in the `mcp_server/` directory (the parent of the `docker/` subdirectory).
#### Running with Docker Compose
**All commands must be run from the `mcp_server` directory** to ensure the `.env` file is loaded correctly:
```bash
cd graphiti/mcp_server
```
##### Option 1: FalkorDB Combined Container (Default)
Single container with both FalkorDB and MCP server - simplest option:
```bash
docker compose up
```
##### Option 2: Neo4j Database
Separate containers with Neo4j and MCP server:
```bash
docker compose -f docker/docker-compose-neo4j.yml up
```
Default Neo4j credentials:
- Username: `neo4j`
- Password: `demodemo`
- Bolt URI: `bolt://neo4j:7687`
- Browser UI: `http://localhost:7474`
##### Option 3: FalkorDB with Separate Containers
Alternative setup with separate FalkorDB and MCP server containers:
```bash
docker compose -f docker/docker-compose-falkordb.yml up
```
FalkorDB configuration:
- Redis port: `6379`
- Web UI: `http://localhost:3000`
- Connection: `redis://falkordb:6379`
#### Accessing the MCP Server
Once running, the MCP server is available at:
- **HTTP endpoint**: `http://localhost:8000/mcp/`
- **Health check**: `http://localhost:8000/health`
#### Running Docker Compose from a Different Directory
If you run Docker Compose from the `docker/` subdirectory instead of `mcp_server/`, you'll need to modify the `.env` file path in the compose file:
```yaml
# Change this line in the docker-compose file:
env_file:
- path: ../.env # When running from mcp_server/
# To this:
env_file:
- path: .env # When running from mcp_server/docker/
```
However, **running from the `mcp_server/` directory is recommended** to avoid confusion.
## Integrating with MCP Clients
### VS Code / GitHub Copilot
VS Code with GitHub Copilot Chat extension supports MCP servers. Add to your VS Code settings (`.vscode/mcp.json` or global settings):
```json
{
"mcpServers": {
"graphiti": {
"uri": "http://localhost:8000/mcp/",
"transport": {
"type": "http"
}
}
}
}
```
### Other MCP Clients
To use the Graphiti MCP server with other MCP-compatible clients, configure it to connect to the server:
> [!IMPORTANT]
> You will need the Python package manager, `uv` installed. Please refer to the [`uv` install instructions](https://docs.astral.sh/uv/getting-started/installation/).
>
> Ensure that you set the full path to the `uv` binary and your Graphiti project folder.
```json
{
"mcpServers": {
"graphiti-memory": {
"transport": "stdio",
"command": "/Users/<user>/.local/bin/uv",
"args": [
"run",
"--isolated",
"--directory",
"/Users/<user>>/dev/zep/graphiti/mcp_server",
"--project",
".",
"graphiti_mcp_server.py",
"--transport",
"stdio"
],
"env": {
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USER": "neo4j",
"NEO4J_PASSWORD": "password",
"OPENAI_API_KEY": "sk-XXXXXXXX",
"MODEL_NAME": "gpt-4.1-mini"
}
}
}
}
```
For HTTP transport (default), you can use this configuration:
```json
{
"mcpServers": {
"graphiti-memory": {
"transport": "http",
"url": "http://localhost:8000/mcp/"
}
}
}
```
## Available Tools
The Graphiti MCP server exposes the following tools:
- `add_episode`: Add an episode to the knowledge graph (supports text, JSON, and message formats)
- `search_nodes`: Search the knowledge graph for relevant node summaries
- `search_facts`: Search the knowledge graph for relevant facts (edges between entities)
- `delete_entity_edge`: Delete an entity edge from the knowledge graph
- `delete_episode`: Delete an episode from the knowledge graph
- `get_entity_edge`: Get an entity edge by its UUID
- `get_episodes`: Get the most recent episodes for a specific group
- `clear_graph`: Clear all data from the knowledge graph and rebuild indices
- `get_status`: Get the status of the Graphiti MCP server and Neo4j connection
## Working with JSON Data
The Graphiti MCP server can process structured JSON data through the `add_episode` tool with `source="json"`. This
allows you to automatically extract entities and relationships from structured data:
```
add_episode(
name="Customer Profile",
episode_body="{\"company\": {\"name\": \"Acme Technologies\"}, \"products\": [{\"id\": \"P001\", \"name\": \"CloudSync\"}, {\"id\": \"P002\", \"name\": \"DataMiner\"}]}",
source="json",
source_description="CRM data"
)
```
## Integrating with the Cursor IDE
To integrate the Graphiti MCP Server with the Cursor IDE, follow these steps:
1. Run the Graphiti MCP server using the default HTTP transport:
```bash
uv run graphiti_mcp_server.py --group-id <your_group_id>
```
Hint: specify a `group_id` to namespace graph data. If you do not specify a `group_id`, the server will use "main" as the group_id.
or
```bash
docker compose up
```
2. Configure Cursor to connect to the Graphiti MCP server.
```json
{
"mcpServers": {
"graphiti-memory": {
"url": "http://localhost:8000/mcp/"
}
}
}
```
3. Add the Graphiti rules to Cursor's User Rules. See [cursor_rules.md](cursor_rules.md) for details.
4. Kick off an agent session in Cursor.
The integration enables AI assistants in Cursor to maintain persistent memory through Graphiti's knowledge graph
capabilities.
## Integrating with Claude Desktop (Docker MCP Server)
The Graphiti MCP Server uses HTTP transport (at endpoint `/mcp/`). Claude Desktop does not natively support HTTP transport, so you'll need to use a gateway like `mcp-remote`.
1. **Run the Graphiti MCP server**:
```bash
docker compose up
# Or run directly with uv:
uv run graphiti_mcp_server.py
```
2. **(Optional) Install `mcp-remote` globally**:
If you prefer to have `mcp-remote` installed globally, or if you encounter issues with `npx` fetching the package, you can install it globally. Otherwise, `npx` (used in the next step) will handle it for you.
```bash
npm install -g mcp-remote
```
3. **Configure Claude Desktop**:
Open your Claude Desktop configuration file (usually `claude_desktop_config.json`) and add or modify the `mcpServers` section as follows:
```json
{
"mcpServers": {
"graphiti-memory": {
// You can choose a different name if you prefer
"command": "npx", // Or the full path to mcp-remote if npx is not in your PATH
"args": [
"mcp-remote",
"http://localhost:8000/mcp/" // The Graphiti server's HTTP endpoint
]
}
}
}
```
If you already have an `mcpServers` entry, add `graphiti-memory` (or your chosen name) as a new key within it.
4. **Restart Claude Desktop** for the changes to take effect.
## Requirements
- Python 3.10 or higher
- OpenAI API key (for LLM operations and embeddings) or other LLM provider API keys
- MCP-compatible client
- Docker and Docker Compose (for the default FalkorDB combined container)
- (Optional) Neo4j database (version 5.26 or later) if not using the default FalkorDB setup
## Telemetry
The Graphiti MCP server uses the Graphiti core library, which includes anonymous telemetry collection. When you initialize the Graphiti MCP server, anonymous usage statistics are collected to help improve the framework.
### What's Collected
- Anonymous identifier and system information (OS, Python version)
- Graphiti version and configuration choices (LLM provider, database backend, embedder type)
- **No personal data, API keys, or actual graph content is ever collected**
### How to Disable
To disable telemetry in the MCP server, set the environment variable:
```bash
export GRAPHITI_TELEMETRY_ENABLED=false
```
Or add it to your `.env` file:
```
GRAPHITI_TELEMETRY_ENABLED=false
```
For complete details about what's collected and why, see the [Telemetry section in the main Graphiti README](../README.md#telemetry).
## License
This project is licensed under the same license as the parent Graphiti project.
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
<p align="center">
<a href="https://www.getzep.com/">
<img src="https://github.com/user-attachments/assets/119c5682-9654-4257-8922-56b7cb8ffd73" width="150" alt="Zep Logo">
</a>
</p>
<h1 align="center">
Graphiti
</h1>
<h2 align="center"> Build Real-Time Knowledge Graphs for AI Agents</h2>
<div align="center">
[](https://github.com/getzep/Graphiti/actions/workflows/lint.yml)
[](https://github.com/getzep/Graphiti/actions/workflows/unit_tests.yml)
[](https://github.com/getzep/Graphiti/actions/workflows/typecheck.yml)

[](https://discord.com/invite/W8Kw6bsgXQ)
[](https://arxiv.org/abs/2501.13956)
[](https://github.com/getzep/graphiti/releases)
</div>
<div align="center">
<a href="https://trendshift.io/repositories/12986" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12986" alt="getzep%2Fgraphiti | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>
:star: _Help us reach more developers and grow the Graphiti community. Star this repo!_
<br />
> [!TIP]
> Check out the new [MCP server for Graphiti](mcp_server/README.md)! Give Claude, Cursor, and other MCP clients powerful
> Knowledge Graph-based memory.
Graphiti is a framework for building and querying temporally-aware knowledge graphs, specifically tailored for AI agents
operating in dynamic environments. Unlike traditional retrieval-augmented generation (RAG) methods, Graphiti
continuously integrates user interactions, structured and unstructured enterprise data, and external information into a
coherent, queryable graph. The framework supports incremental data updates, efficient retrieval, and precise historical
queries without requiring complete graph recomputation, making it suitable for developing interactive, context-aware AI
applications.
Use Graphiti to:
- Integrate and maintain dynamic user interactions and business data.
- Facilitate state-based reasoning and task automation for agents.
- Query complex, evolving data with semantic, keyword, and graph-based search methods.
<br />
<p align="center">
<img src="images/graphiti-graph-intro.gif" alt="Graphiti temporal walkthrough" width="700px">
</p>
<br />
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 and Zep's Context Engineering Platform.
Graphiti powers the core of [Zep](https://www.getzep.com), a turn-key context engineering platform for AI Agents. Zep
offers agent memory, Graph RAG for dynamic data, and context retrieval and assembly.
Using Graphiti, we've demonstrated Zep is
the [State of the Art in Agent Memory](https://blog.getzep.com/state-of-the-art-agent-memory/).
Read our paper: [Zep: A Temporal Knowledge Graph Architecture for Agent Memory](https://arxiv.org/abs/2501.13956).
We're excited to open-source Graphiti, believing its potential reaches far beyond AI memory applications.
<p align="center">
<a href="https://arxiv.org/abs/2501.13956"><img src="images/arxiv-screenshot.png" alt="Zep: A Temporal Knowledge Graph Architecture for Agent Memory" width="700px"></a>
</p>
## Zep vs Graphiti
| Aspect | Zep | Graphiti |
|--------|-----|----------|
| **What they are** | Fully managed platform for context engineering and AI memory | 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.
## Why Graphiti?
Traditional RAG approaches often rely on batch processing and static data summarization, making them inefficient for
frequently changing data. Graphiti addresses these challenges by providing:
- **Real-Time Incremental Updates:** Immediate integration of new data episodes without batch recomputation.
- **Bi-Temporal Data Model:** Explicit tracking of event occurrence and ingestion times, allowing accurate point-in-time
queries.
- **Efficient Hybrid Retrieval:** Combines semantic embeddings, keyword (BM25), and graph traversal to achieve
low-latency queries without reliance on LLM summarization.
- **Custom Entity Definitions:** Flexible ontology creation and support for developer-defined entities through
straightforward Pydantic models.
- **Scalability:** Efficiently manages large datasets with parallel processing, suitable for enterprise environments.
<p align="center">
<img src="/images/graphiti-intro-slides-stock-2.gif" alt="Graphiti structured + unstructured demo" width="700px">
</p>
## Graphiti vs. GraphRAG
| 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.
## Installation
Requirements:
- Python 3.10 or higher
- Neo4j 5.26 / FalkorDB 1.1.2 / Kuzu 0.11.2 / Amazon Neptune Database Cluster or Neptune Analytics Graph + Amazon
OpenSearch Serverless collection (serves as the full text search backend)
- OpenAI API key (Graphiti defaults to OpenAI for LLM inference and embedding)
> [!IMPORTANT]
> 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. This is particularly
> problematic when using smaller models.
Optional:
- Google Gemini, Anthropic, or Groq API key (for alternative LLM providers)
> [!TIP]
> 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.
> Alternatively, you can use FalkorDB on-premises via Docker and instantly start with the quickstart example:
```bash
docker run -p 6379:6379 -p 3000:3000 -it --rm falkordb/falkordb:latest
```
```bash
pip install graphiti-core
```
or
```bash
uv add graphiti-core
```
### Installing with FalkorDB Support
If you plan to use FalkorDB as your graph database backend, install with the FalkorDB extra:
```bash
pip install graphiti-core[falkordb]
# or with uv
uv add graphiti-core[falkordb]
```
### Installing with Kuzu Support
If you plan to use Kuzu as your graph database backend, install with the Kuzu extra:
```bash
pip install graphiti-core[kuzu]
# or with uv
uv add graphiti-core[kuzu]
```
### Installing with Amazon Neptune Support
If you plan to use Amazon Neptune as your graph database backend, install with the Amazon Neptune extra:
```bash
pip install graphiti-core[neptune]
# or with uv
uv add graphiti-core[neptune]
```
### You can also install optional LLM providers as extras:
```bash
# Install with Anthropic support
pip install graphiti-core[anthropic]
# Install with Groq support
pip install graphiti-core[groq]
# Install with Google Gemini support
pip install graphiti-core[google-genai]
# Install with multiple providers
pip install graphiti-core[anthropic,groq,google-genai]
# Install with FalkorDB and LLM providers
pip install graphiti-core[falkordb,anthropic,google-genai]
# Install with Amazon Neptune
pip install graphiti-core[neptune]
```
## Default to 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.
## Quick Start
> [!IMPORTANT]
> Graphiti defaults to using OpenAI for LLM inference and embedding. Ensure that an `OPENAI_API_KEY` is set in your
> environment.
> Support for Anthropic and Groq LLM inferences is available, too. Other LLM providers may be supported via OpenAI
> compatible APIs.
For a complete working example, see the [Quickstart Example](./examples/quickstart/README.md) in the examples directory.
The quickstart demonstrates:
1. Connecting to a Neo4j, Amazon Neptune, FalkorDB, or Kuzu database
2. Initializing Graphiti indices and constraints
3. Adding episodes to the graph (both text and structured JSON)
4. Searching for relationships (edges) using hybrid search
5. Reranking search results using graph distance
6. Searching for nodes using predefined search recipes
The example is fully documented with clear explanations of each functionality and includes a comprehensive README with
setup instructions and next steps.
### Running with Docker Compose
You can use Docker Compose to quickly start the required services:
- **Neo4j Docker:**
```sh
docker compose up
```
This will start the Neo4j Docker service and related components.
- **FalkorDB Docker:**
```sh
docker compose --profile falkordb up
```
This will start the FalkorDB Docker service and related components.
## MCP Server
The `mcp_server` directory contains a Model Context Protocol (MCP) server implementation for Graphiti. This server
allows AI assistants to interact with Graphiti's knowledge graph capabilities through the MCP protocol.
Key features of the MCP server include:
- Episode management (add, retrieve, delete)
- Entity management and relationship handling
- Semantic and hybrid search capabilities
- Group management for organizing related data
- Graph maintenance operations
The MCP server can be deployed using Docker with Neo4j, making it easy to integrate Graphiti into your AI assistant
workflows.
For detailed setup instructions and usage examples, see the [MCP server README](./mcp_server/README.md).
## REST Service
The `server` directory contains an API service for interacting with the Graphiti API. It is built using FastAPI.
Please see the [server README](./server/README.md) for more information.
## Optional Environment Variables
In addition to the Neo4j and OpenAi-compatible credentials, Graphiti also has a few optional environment variables.
If you are using one of our supported models, such as Anthropic or Voyage models, the necessary environment variables
must be set.
### Database Configuration
Database names are configured directly in the driver constructors:
- **Neo4j**: Database name defaults to `neo4j` (hardcoded in Neo4jDriver)
- **FalkorDB**: Database name defaults to `default_db` (hardcoded in FalkorDriver)
As of v0.17.0, if you need to customize your database configuration, you can instantiate a database driver and pass it
to the Graphiti constructor using the `graph_driver` parameter.
#### Neo4j with Custom Database Name
```python
from graphiti_core import Graphiti
from graphiti_core.driver.neo4j_driver import Neo4jDriver
# Create a Neo4j driver with custom database name
driver = Neo4jDriver(
uri="bolt://localhost:7687",
user="neo4j",
password="password",
database="my_custom_database" # Custom database name
)
# Pass the driver to Graphiti
graphiti = Graphiti(graph_driver=driver)
```
#### FalkorDB with Custom Database Name
```python
from graphiti_core import Graphiti
from graphiti_core.driver.falkordb_driver import FalkorDriver
# Create a FalkorDB driver with custom database name
driver = FalkorDriver(
host="localhost",
port=6379,
username="falkor_user", # Optional
password="falkor_password", # Optional
database="my_custom_graph" # Custom database name
)
# Pass the driver to Graphiti
graphiti = Graphiti(graph_driver=driver)
```
#### Kuzu
```python
from graphiti_core import Graphiti
from graphiti_core.driver.kuzu_driver import KuzuDriver
# Create a Kuzu driver
driver = KuzuDriver(db="/tmp/graphiti.kuzu")
# Pass the driver to Graphiti
graphiti = Graphiti(graph_driver=driver)
```
#### Amazon Neptune
```python
from graphiti_core import Graphiti
from graphiti_core.driver.neptune_driver import NeptuneDriver
# Create a FalkorDB driver with custom database name
driver = NeptuneDriver(
host= < NEPTUNE
ENDPOINT >,
aoss_host = < Amazon
OpenSearch
Serverless
Host >,
port = < PORT > # Optional, defaults to 8182,
aoss_port = < PORT > # Optional, defaults to 443
)
driver = NeptuneDriver(host=neptune_uri, aoss_host=aoss_host, port=neptune_port)
# Pass the driver to Graphiti
graphiti = Graphiti(graph_driver=driver)
```
## Using Graphiti with Azure OpenAI
Graphiti supports Azure OpenAI for both LLM inference and embeddings using Azure's OpenAI v1 API compatibility layer.
### Quick Start
```python
from openai import AsyncOpenAI
from graphiti_core import Graphiti
from graphiti_core.llm_client.azure_openai_client import AzureOpenAILLMClient
from graphiti_core.llm_client.config import LLMConfig
from graphiti_core.embedder.azure_openai import AzureOpenAIEmbedderClient
# Initialize Azure OpenAI client using the standard OpenAI client
# with Azure's v1 API endpoint
azure_client = AsyncOpenAI(
base_url="https://your-resource-name.openai.azure.com/openai/v1/",
api_key="your-api-key",
)
# Create LLM and Embedder clients
llm_client = AzureOpenAILLMClient(
azure_client=azure_client,
config=LLMConfig(model="gpt-5-mini", small_model="gpt-5-mini") # Your Azure deployment name
)
embedder_client = AzureOpenAIEmbedderClient(
azure_client=azure_client,
model="text-embedding-3-small" # Your Azure embedding deployment name
)
# Initialize Graphiti with Azure OpenAI clients
graphiti = Graphiti(
"bolt://localhost:7687",
"neo4j",
"password",
llm_client=llm_client,
embedder=embedder_client,
)
# Now you can use Graphiti with Azure OpenAI
```
**Key Points:**
- Use the standard `AsyncOpenAI` client with Azure's v1 API endpoint format: `https://your-resource-name.openai.azure.com/openai/v1/`
- The deployment names (e.g., `gpt-5-mini`, `text-embedding-3-small`) should match your Azure OpenAI deployment names
- See `examples/azure-openai/` for a complete working example
Make sure to replace the placeholder values with your actual Azure OpenAI credentials and deployment names.
## Using Graphiti with Google Gemini
Graphiti supports Google's Gemini models for LLM inference, embeddings, and cross-encoding/reranking. To use Gemini,
you'll need to configure the LLM client, embedder, and the cross-encoder with your Google API key.
Install Graphiti:
```bash
uv add "graphiti-core[google-genai]"
# or
pip install "graphiti-core[google-genai]"
```
```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 = "<your-google-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.5-flash-lite"
)
)
)
# Now you can use Graphiti with Google Gemini for all components
```
The Gemini reranker uses the `gemini-2.5-flash-lite` model by default, which is optimized for
cost-effective and low-latency classification tasks. It uses the same boolean classification approach as the OpenAI
reranker, leveraging Gemini's log probabilities feature to rank passage relevance.
## Using Graphiti with Ollama (Local LLM)
Graphiti supports Ollama for running local LLMs and embedding models via Ollama's OpenAI-compatible API. This is ideal
for privacy-focused applications or when you want to avoid API costs.
**Note:** Use `OpenAIGenericClient` (not `OpenAIClient`) for Ollama and other OpenAI-compatible providers like LM Studio. The `OpenAIGenericClient` is optimized for local models with a higher default max token limit (16K vs 8K) and full support for structured outputs.
Install the models:
```bash
ollama pull deepseek-r1:7b # LLM
ollama pull nomic-embed-text # embeddings
```
```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
llm_config = LLMConfig(
api_key="ollama", # Ollama doesn't require a real API key, but some placeholder is needed
model="deepseek-r1:7b",
small_model="deepseek-r1:7b",
base_url="http://localhost:11434/v1", # Ollama's OpenAI-compatible endpoint
)
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", # Placeholder API key
embedding_model="nomic-embed-text",
embedding_dim=768,
base_url="http://localhost:11434/v1",
)
),
cross_encoder=OpenAIRerankerClient(client=llm_client, config=llm_config),
)
# Now you can use Graphiti with local Ollama models
```
Ensure Ollama is running (`ollama serve`) and that you have pulled the models you want to use.
## Documentation
- [Guides and API documentation](https://help.getzep.com/graphiti).
- [Quick Start](https://help.getzep.com/graphiti/graphiti/quick-start)
- [Building an agent with LangChain's LangGraph and Graphiti](https://help.getzep.com/graphiti/integrations/lang-graph-agent)
## 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, Kuzu, Amazon Neptune Database or Neptune Analytics)
- 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](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
## Status and Roadmap
Graphiti is under active development. We aim to maintain API stability while working on:
- [x] Supporting custom graph schemas:
- Allow developers to provide their own defined node and edge classes when ingesting episodes
- Enable more flexible knowledge representation tailored to specific use cases
- [x] Enhancing retrieval capabilities with more robust and configurable options
- [x] Graphiti MCP Server
- [ ] Expanding test coverage to ensure reliability and catch edge cases
## Contributing
We encourage and appreciate all forms of contributions, whether it's code, documentation, addressing GitHub Issues, or
answering questions in the Graphiti Discord channel. For detailed guidelines on code contributions, please refer
to [CONTRIBUTING](CONTRIBUTING.md).
## Support
Join the [Zep Discord server](https://discord.com/invite/W8Kw6bsgXQ) and make your way to the **#Graphiti** channel!
```
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
```markdown
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
|---------|--------------------|
| 0.x | :white_check_mark: |
## Reporting a Vulnerability
Please use GitHub's Private Vulnerability Reporting mechanism found in the Security section of this repo.
```
--------------------------------------------------------------------------------
/AGENTS.md:
--------------------------------------------------------------------------------
```markdown
# Repository Guidelines
## Project Structure & Module Organization
Graphiti's core library lives under `graphiti_core/`, split into domain modules such as `nodes.py`, `edges.py`, `models/`, and `search/` for retrieval pipelines. Service adapters and API glue reside in `server/graph_service/`, while the MCP integration lives in `mcp_server/`. Shared assets and collateral sit in `images/` and `examples/`. Tests cover the package via `tests/`, with configuration in `conftest.py`, `pytest.ini`, and Docker compose files for optional services. Tooling manifests live at the repo root, including `pyproject.toml`, `Makefile`, and deployment compose files.
## Build, Test, and Development Commands
- `uv sync --extra dev`: install the dev environment declared in `pyproject.toml`.
- `make format`: run `ruff` to sort imports and apply the canonical formatter.
- `make lint`: execute `ruff` plus `pyright` type checks against `graphiti_core`.
- `make test`: run the full `pytest` suite (`uv run pytest`).
- `uv run pytest tests/path/test_file.py`: target a specific module or test selection.
- `docker-compose -f docker-compose.test.yml up`: provision local graph/search dependencies for integration flows.
## Coding Style & Naming Conventions
Python code uses 4-space indentation, 100-character lines, and prefers single quotes as configured in `pyproject.toml`. Modules, files, and functions stay snake_case; Pydantic models in `graphiti_core/models` use PascalCase with explicit type hints. Keep side-effectful code inside drivers or adapters (`graphiti_core/driver`, `graphiti_core/utils`) and rely on pure helpers elsewhere. Run `make format` before committing to normalize imports and docstring formatting.
## Testing Guidelines
Author tests alongside features under `tests/`, naming files `test_<feature>.py` and functions `test_<behavior>`. Use `@pytest.mark.integration` for database-reliant scenarios so CI can gate them. Reproduce regressions with a failing test first and validate fixes via `uv run pytest -k "pattern"`. Start required backing services through `docker-compose.test.yml` when running integration suites locally.
## Commit & Pull Request Guidelines
Commits use an imperative, present-tense summary (for example, `add async cache invalidation`) optionally suffixed with the PR number as seen in history (`(#927)`). Squash fixups and keep unrelated changes isolated. Pull requests should include: a concise description, linked tracking issue, notes about schema or API impacts, and screenshots or logs when behavior changes. Confirm `make lint` and `make test` pass locally, and update docs or examples when public interfaces shift.
```
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
```markdown
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[email protected].
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
```
--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------
```markdown
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
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.
Key features:
- Bi-temporal data model with explicit tracking of event occurrence times
- Hybrid retrieval combining semantic embeddings, keyword search (BM25), and graph traversal
- Support for custom entity definitions via Pydantic models
- Integration with Neo4j and FalkorDB as graph storage backends
- Optional OpenTelemetry distributed tracing support
## Development Commands
### Main Development Commands (run from project root)
```bash
# Install dependencies
uv sync --extra dev
# Format code (ruff import sorting + formatting)
make format
# Lint code (ruff + pyright type checking)
make lint
# Run tests
make test
# Run all checks (format, lint, test)
make check
```
### Server Development (run from server/ directory)
```bash
cd server/
# Install server dependencies
uv sync --extra dev
# Run server in development mode
uvicorn graph_service.main:app --reload
# Format, lint, test server code
make format
make lint
make test
```
### MCP Server Development (run from mcp_server/ directory)
```bash
cd mcp_server/
# Install MCP server dependencies
uv sync
# Run with Docker Compose
docker-compose up
```
## Code Architecture
### Core Library (`graphiti_core/`)
- **Main Entry Point**: `graphiti.py` - Contains the main `Graphiti` class that orchestrates all functionality
- **Graph Storage**: `driver/` - Database drivers for Neo4j and FalkorDB
- **LLM Integration**: `llm_client/` - Clients for OpenAI, Anthropic, Gemini, Groq
- **Embeddings**: `embedder/` - Embedding clients for various providers
- **Graph Elements**: `nodes.py`, `edges.py` - Core graph data structures
- **Search**: `search/` - Hybrid search implementation with configurable strategies
- **Prompts**: `prompts/` - LLM prompts for entity extraction, deduplication, summarization
- **Utilities**: `utils/` - Maintenance operations, bulk processing, datetime handling
### Server (`server/`)
- **FastAPI Service**: `graph_service/main.py` - REST API server
- **Routers**: `routers/` - API endpoints for ingestion and retrieval
- **DTOs**: `dto/` - Data transfer objects for API contracts
### MCP Server (`mcp_server/`)
- **MCP Implementation**: `graphiti_mcp_server.py` - Model Context Protocol server for AI assistants
- **Docker Support**: Containerized deployment with Neo4j
## Testing
- **Unit Tests**: `tests/` - Comprehensive test suite using pytest
- **Integration Tests**: Tests marked with `_int` suffix require database connections
- **Evaluation**: `tests/evals/` - End-to-end evaluation scripts
## Configuration
### Environment Variables
- `OPENAI_API_KEY` - Required for LLM inference and embeddings
- `USE_PARALLEL_RUNTIME` - Optional boolean for Neo4j parallel runtime (enterprise only)
- Provider-specific keys: `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, `GROQ_API_KEY`, `VOYAGE_API_KEY`
### Database Setup
- **Neo4j**: Version 5.26+ required, available via Neo4j Desktop
- Database name defaults to `neo4j` (hardcoded in Neo4jDriver)
- Override by passing `database` parameter to driver constructor
- **FalkorDB**: Version 1.1.2+ as alternative backend
- Database name defaults to `default_db` (hardcoded in FalkorDriver)
- Override by passing `database` parameter to driver constructor
## Development Guidelines
### Code Style
- Use Ruff for formatting and linting (configured in pyproject.toml)
- Line length: 100 characters
- Quote style: single quotes
- Type checking with Pyright is enforced
- Main project uses `typeCheckingMode = "basic"`, server uses `typeCheckingMode = "standard"`
### Testing Requirements
- Run tests with `make test` or `pytest`
- Integration tests require database connections and are marked with `_int` suffix
- Use `pytest-xdist` for parallel test execution
- Run specific test files: `pytest tests/test_specific_file.py`
- Run specific test methods: `pytest tests/test_file.py::test_method_name`
- Run only integration tests: `pytest tests/ -k "_int"`
- Run only unit tests: `pytest tests/ -k "not _int"`
### LLM Provider Support
The codebase supports multiple LLM providers but works best with services supporting structured output (OpenAI, Gemini). Other providers may cause schema validation issues, especially with smaller models.
#### Current LLM Models (as of November 2025)
**OpenAI Models:**
- **GPT-5 Family** (Reasoning models, require temperature=0):
- `gpt-5-mini` - Fast reasoning model
- `gpt-5-nano` - Smallest reasoning model
- **GPT-4.1 Family** (Standard models):
- `gpt-4.1` - Full capability model
- `gpt-4.1-mini` - Efficient model for most tasks
- `gpt-4.1-nano` - Lightweight model
- **Legacy Models** (Still supported):
- `gpt-4o` - Previous generation flagship
- `gpt-4o-mini` - Previous generation efficient
**Anthropic Models:**
- **Claude 4.5 Family** (Latest):
- `claude-sonnet-4-5-latest` - Flagship model, auto-updates
- `claude-sonnet-4-5-20250929` - Pinned Sonnet version from September 2025
- `claude-haiku-4-5-latest` - Fast model, auto-updates
- **Claude 3.7 Family**:
- `claude-3-7-sonnet-latest` - Auto-updates
- `claude-3-7-sonnet-20250219` - Pinned version from February 2025
- **Claude 3.5 Family**:
- `claude-3-5-sonnet-latest` - Auto-updates
- `claude-3-5-sonnet-20241022` - Pinned version from October 2024
- `claude-3-5-haiku-latest` - Fast model
**Google Gemini Models:**
- **Gemini 2.5 Family** (Latest):
- `gemini-2.5-pro` - Flagship reasoning and multimodal
- `gemini-2.5-flash` - Fast, efficient
- **Gemini 2.0 Family**:
- `gemini-2.0-flash` - Experimental fast model
- **Gemini 1.5 Family** (Stable):
- `gemini-1.5-pro` - Production-stable flagship
- `gemini-1.5-flash` - Production-stable efficient
**Note**: Model names like `gpt-5-mini`, `gpt-4.1`, and `gpt-4.1-mini` used in this codebase are valid OpenAI model identifiers. The GPT-5 family are reasoning models that require `temperature=0` (automatically handled in the code).
### MCP Server Usage Guidelines
When working with the MCP server, follow the patterns established in `mcp_server/cursor_rules.md`:
- Always search for existing knowledge before adding new information
- Use specific entity type filters (`Preference`, `Procedure`, `Requirement`)
- Store new information immediately using `add_memory`
- Follow discovered procedures and respect established preferences
```
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
```markdown
# Contributing to Graphiti
We're thrilled you're interested in contributing to Graphiti! As firm believers in the power of open source collaboration, we're committed to building not just a tool, but a vibrant community where developers of all experience levels can make meaningful contributions.
When I first joined this project, I was overwhelmed trying to figure out where to start. Someone eventually pointed me to a random "good first issue," but I later discovered there were multiple ways I could have contributed that would have better matched my skills and interests.
We've restructured our contribution paths to solve this problem:
# Four Ways to Get Involved
### Pick Up Existing Issues
Our developers regularly tag issues with "help wanted" and "good first issue." These are pre-vetted tasks with clear scope and someone ready to help you if you get stuck.
### Create Your Own Tickets
See something that needs fixing? Have an idea for an improvement? You don't need permission to identify problems. The people closest to the pain are often best positioned to describe the solution.
For **feature requests**, tell us the story of what you're trying to accomplish. What are you working on? What's getting in your way? What would make your life easier? Submit these through our [GitHub issue tracker](https://github.com/getzep/graphiti/issues) with a "Feature Request" label.
For **bug reports**, we need enough context to reproduce the problem. Use the [GitHub issue tracker](https://github.com/getzep/graphiti/issues) and include:
- A clear title that summarizes the specific problem
- What you were trying to do when you encountered the bug
- What you expected to happen
- What actually happened
- A code sample or test case that demonstrates the issue
### Share Your Use Cases
Sometimes the most valuable contribution isn't code. If you're using our project in an interesting way, add it to the [examples](https://github.com/getzep/graphiti/tree/main/examples) folder. This helps others discover new possibilities and counts as a meaningful contribution. We regularly feature compelling examples in our blog posts and videos - your work might be showcased to the broader community!
### Help Others in Discord
Join our [Discord server](https://discord.com/invite/W8Kw6bsgXQ) community and pitch in at the helpdesk. Answering questions and helping troubleshoot issues is an incredibly valuable contribution that benefits everyone. The knowledge you share today saves someone hours of frustration tomorrow.
## What happens next?
### Notes for Large Changes
> Please keep the changes as concise as possible. For major architectural changes (>500 LOC), we would expect a GitHub issue (RFC) discussing the technical design and justification. Otherwise, we will tag it with rfc-required and might not go through the PR.
Once you've found an issue tagged with "good first issue" or "help wanted," or prepared an example to share, here's how to turn that into a contribution:
1. Share your approach in the issue discussion or [Discord](https://discord.com/invite/W8Kw6bsgXQ) before diving deep into code. This helps ensure your solution adheres to the architecture of Graphiti from the start and saves you from potential rework.
2. Fork the repo, make your changes in a branch, and submit a PR. We've included more detailed technical instructions below; be open to feedback during review.
## Setup
1. Fork the repository on GitHub.
2. Clone your fork locally:
```
git clone https://github.com/getzep/graphiti
cd graphiti
```
3. Set up your development environment:
- Ensure you have Python 3.10+ installed.
- Install uv: https://docs.astral.sh/uv/getting-started/installation/
- Install project dependencies:
```
make install
```
- To run integration tests, set the appropriate environment variables
```
export TEST_OPENAI_API_KEY=...
export TEST_OPENAI_MODEL=...
export TEST_ANTHROPIC_API_KEY=...
# For Neo4j
export TEST_URI=neo4j://...
export TEST_USER=...
export TEST_PASSWORD=...
```
## Making Changes
1. Create a new branch for your changes:
```
git checkout -b your-branch-name
```
2. Make your changes in the codebase.
3. Write or update tests as necessary.
4. Run the tests to ensure they pass:
```
make test
```
5. Format your code:
```
make format
```
6. Run linting checks:
```
make lint
```
## Submitting Changes
1. Commit your changes:
```
git commit -m "Your detailed commit message"
```
2. Push to your fork:
```
git push origin your-branch-name
```
3. Submit a pull request through the GitHub website to https://github.com/getzep/graphiti.
## Pull Request Guidelines
- Provide a clear title and description of your changes.
- Include any relevant issue numbers in the PR description.
- Ensure all tests pass and there are no linting errors.
- Update documentation if you're changing functionality.
## Code Style and Quality
We use several tools to maintain code quality:
- Ruff for linting and formatting
- Pyright for static type checking
- Pytest for testing
Before submitting a pull request, please run:
```
make check
```
This command will format your code, run linting checks, and execute tests.
## Third-Party Integrations
When contributing integrations for third-party services (LLM providers, embedding services, databases, etc.), please follow these patterns:
### Optional Dependencies
All third-party integrations must be optional dependencies to keep the core library lightweight. Follow this pattern:
1. **Add to `pyproject.toml`**: Define your dependency as an optional extra AND include it in the dev extra:
```toml
[project.optional-dependencies]
your-service = ["your-package>=1.0.0"]
dev = [
# ... existing dev dependencies
"your-package>=1.0.0", # Include all optional extras here
# ... other dependencies
]
```
2. **Use TYPE_CHECKING pattern**: In your integration module, import dependencies conditionally:
```python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import your_package
from your_package import SomeType
else:
try:
import your_package
from your_package import SomeType
except ImportError:
raise ImportError(
'your-package is required for YourServiceClient. '
'Install it with: pip install graphiti-core[your-service]'
) from None
```
3. **Benefits of this pattern**:
- Fast startup times (no import overhead during type checking)
- Clear error messages with installation instructions
- Proper type hints for development
- Consistent user experience
4. **Do NOT**:
- Add optional imports to `__init__.py` files
- Use direct imports without error handling
- Include optional dependencies in the main `dependencies` list
### Integration Structure
- Place LLM clients in `graphiti_core/llm_client/`
- Place embedding clients in `graphiti_core/embedder/`
- Place database drivers in `graphiti_core/driver/`
- Follow existing naming conventions (e.g., `your_service_client.py`)
### Testing
- Add comprehensive tests in the appropriate `tests/` subdirectory
- Mark integration tests with `_int` suffix if they require external services
- Include both unit tests and integration tests where applicable
# Questions?
Stuck on a contribution or have a half-formed idea? Come say hello in our [Discord server](https://discord.com/invite/W8Kw6bsgXQ). Whether you're ready to contribute or just want to learn more, we're happy to have you! It's faster than GitHub issues and you'll find both maintainers and fellow contributors ready to help.
Thank you for contributing to Graphiti!
```
--------------------------------------------------------------------------------
/graphiti_core/migrations/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/graphiti_core/models/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/graphiti_core/models/edges/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/graphiti_core/models/nodes/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/graphiti_core/search/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/graphiti_core/utils/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/mcp_server/src/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/mcp_server/src/config/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/mcp_server/src/models/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/mcp_server/src/services/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/mcp_server/src/utils/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/mcp_server/tests/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/server/graph_service/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/server/graph_service/routers/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/depot.json:
--------------------------------------------------------------------------------
```json
{"id":"v9jv1mlpwc"}
```
--------------------------------------------------------------------------------
/examples/quickstart/requirements.txt:
--------------------------------------------------------------------------------
```
graphiti-core
python-dotenv>=1.0.0
```
--------------------------------------------------------------------------------
/tests/driver/__init__.py:
--------------------------------------------------------------------------------
```python
"""Tests for database drivers."""
```
--------------------------------------------------------------------------------
/graphiti_core/__init__.py:
--------------------------------------------------------------------------------
```python
from .graphiti import Graphiti
__all__ = ['Graphiti']
```
--------------------------------------------------------------------------------
/graphiti_core/prompts/__init__.py:
--------------------------------------------------------------------------------
```python
from .lib import prompt_library
from .models import Message
__all__ = ['prompt_library', 'Message']
```
--------------------------------------------------------------------------------
/tests/evals/pytest.ini:
--------------------------------------------------------------------------------
```
[pytest]
asyncio_default_fixture_loop_scope = function
markers =
integration: marks tests as integration tests
```
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
```
[pytest]
markers =
integration: marks tests as integration tests
asyncio_default_fixture_loop_scope = function
asyncio_mode = auto
```
--------------------------------------------------------------------------------
/graphiti_core/embedder/__init__.py:
--------------------------------------------------------------------------------
```python
from .client import EmbedderClient
from .openai import OpenAIEmbedder, OpenAIEmbedderConfig
__all__ = [
'EmbedderClient',
'OpenAIEmbedder',
'OpenAIEmbedderConfig',
]
```
--------------------------------------------------------------------------------
/graphiti_core/telemetry/__init__.py:
--------------------------------------------------------------------------------
```python
"""
Telemetry module for Graphiti.
This module provides anonymous usage analytics to help improve Graphiti.
"""
from .telemetry import capture_event, is_telemetry_enabled
__all__ = ['capture_event', 'is_telemetry_enabled']
```
--------------------------------------------------------------------------------
/examples/opentelemetry/pyproject.toml:
--------------------------------------------------------------------------------
```toml
[project]
name = "graphiti-otel-stdout-example"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"graphiti-core",
"kuzu>=0.11.2",
"opentelemetry-api>=1.20.0",
"opentelemetry-sdk>=1.20.0",
]
[tool.uv.sources]
graphiti-core = { path = "../..", editable = true }
```
--------------------------------------------------------------------------------
/graphiti_core/utils/maintenance/__init__.py:
--------------------------------------------------------------------------------
```python
from .edge_operations import build_episodic_edges, extract_edges
from .graph_data_operations import clear_data, retrieve_episodes
from .node_operations import extract_nodes
__all__ = [
'extract_edges',
'build_episodic_edges',
'extract_nodes',
'clear_data',
'retrieve_episodes',
]
```
--------------------------------------------------------------------------------
/mcp_server/pytest.ini:
--------------------------------------------------------------------------------
```
[pytest]
# MCP Server specific pytest configuration
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short
# Configure asyncio
asyncio_mode = auto
asyncio_default_fixture_loop_scope = function
# Ignore warnings from dependencies
filterwarnings =
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
```
--------------------------------------------------------------------------------
/server/graph_service/dto/__init__.py:
--------------------------------------------------------------------------------
```python
from .common import Message, Result
from .ingest import AddEntityNodeRequest, AddMessagesRequest
from .retrieve import FactResult, GetMemoryRequest, GetMemoryResponse, SearchQuery, SearchResults
__all__ = [
'SearchQuery',
'Message',
'AddMessagesRequest',
'AddEntityNodeRequest',
'SearchResults',
'FactResult',
'Result',
'GetMemoryRequest',
'GetMemoryResponse',
]
```
--------------------------------------------------------------------------------
/.github/secret_scanning.yml:
--------------------------------------------------------------------------------
```yaml
# Secret scanning configuration
# This file excludes specific files/directories from secret scanning alerts
paths-ignore:
# PostHog public API key for anonymous telemetry
# This is a public key intended for client-side use and safe to commit
# Key: phc_UG6EcfDbuXz92neb3rMlQFDY0csxgMqRcIPWESqnSmo
- "graphiti_core/telemetry/telemetry.py"
# Example/test directories that may contain dummy credentials
- "tests/**/fixtures/**"
```
--------------------------------------------------------------------------------
/mcp_server/config/mcp_config_stdio_example.json:
--------------------------------------------------------------------------------
```json
{
"mcpServers": {
"graphiti": {
"transport": "stdio",
"command": "uv",
"args": [
"run",
"/ABSOLUTE/PATH/TO/graphiti_mcp_server.py",
"--transport",
"stdio"
],
"env": {
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USER": "neo4j",
"NEO4J_PASSWORD": "demodemo",
"OPENAI_API_KEY": "${OPENAI_API_KEY}",
"MODEL_NAME": "gpt-4.1-mini"
}
}
}
}
```
--------------------------------------------------------------------------------
/conftest.py:
--------------------------------------------------------------------------------
```python
import os
import sys
# This code adds the project root directory to the Python path, allowing imports to work correctly when running tests.
# Without this file, you might encounter ModuleNotFoundError when trying to import modules from your project, especially when running tests.
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__))))
from tests.helpers_test import graph_driver, mock_embedder
__all__ = ['graph_driver', 'mock_embedder']
```
--------------------------------------------------------------------------------
/mcp_server/tests/conftest.py:
--------------------------------------------------------------------------------
```python
"""
Pytest configuration for MCP server tests.
This file prevents pytest from loading the parent project's conftest.py
"""
import sys
from pathlib import Path
import pytest
# Add src directory to Python path for imports
src_path = Path(__file__).parent.parent / 'src'
sys.path.insert(0, str(src_path))
from config.schema import GraphitiConfig # noqa: E402
@pytest.fixture
def config():
"""Provide a default GraphitiConfig for tests."""
return GraphitiConfig()
```
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
```yaml
name: Lint with Ruff
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
ruff:
environment: development
runs-on: depot-ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install "ruff>0.1.7"
- name: Run Ruff linting
run: ruff check --output-format=github
```
--------------------------------------------------------------------------------
/server/graph_service/dto/ingest.py:
--------------------------------------------------------------------------------
```python
from pydantic import BaseModel, Field
from graph_service.dto.common import Message
class AddMessagesRequest(BaseModel):
group_id: str = Field(..., description='The group id of the messages to add')
messages: list[Message] = Field(..., description='The messages to add')
class AddEntityNodeRequest(BaseModel):
uuid: str = Field(..., description='The uuid of the node to add')
group_id: str = Field(..., description='The group id of the node to add')
name: str = Field(..., description='The name of the node to add')
summary: str = Field(default='', description='The summary of the node to add')
```
--------------------------------------------------------------------------------
/graphiti_core/driver/__init__.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from neo4j import Neo4jDriver
__all__ = ['Neo4jDriver']
```
--------------------------------------------------------------------------------
/server/graph_service/config.py:
--------------------------------------------------------------------------------
```python
from functools import lru_cache
from typing import Annotated
from fastapi import Depends
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict # type: ignore
class Settings(BaseSettings):
openai_api_key: str
openai_base_url: str | None = Field(None)
model_name: str | None = Field(None)
embedding_model_name: str | None = Field(None)
neo4j_uri: str
neo4j_user: str
neo4j_password: str
model_config = SettingsConfigDict(env_file='.env', extra='ignore')
@lru_cache
def get_settings():
return Settings() # type: ignore[call-arg]
ZepEnvDep = Annotated[Settings, Depends(get_settings)]
```
--------------------------------------------------------------------------------
/mcp_server/main.py:
--------------------------------------------------------------------------------
```python
#!/usr/bin/env python3
"""
Main entry point for Graphiti MCP Server
This is a backwards-compatible wrapper around the original graphiti_mcp_server.py
to maintain compatibility with existing deployment scripts and documentation.
Usage:
python main.py [args...]
All arguments are passed through to the original server implementation.
"""
import sys
from pathlib import Path
# Add src directory to Python path for imports
src_path = Path(__file__).parent / 'src'
sys.path.insert(0, str(src_path))
# Import and run the original server
if __name__ == '__main__':
from graphiti_mcp_server import main
# Pass all command line arguments to the original main function
main()
```
--------------------------------------------------------------------------------
/server/graph_service/main.py:
--------------------------------------------------------------------------------
```python
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from graph_service.config import get_settings
from graph_service.routers import ingest, retrieve
from graph_service.zep_graphiti import initialize_graphiti
@asynccontextmanager
async def lifespan(_: FastAPI):
settings = get_settings()
await initialize_graphiti(settings)
yield
# Shutdown
# No need to close Graphiti here, as it's handled per-request
app = FastAPI(lifespan=lifespan)
app.include_router(retrieve.router)
app.include_router(ingest.router)
@app.get('/healthcheck')
async def healthcheck():
return JSONResponse(content={'status': 'healthy'}, status_code=200)
```
--------------------------------------------------------------------------------
/graphiti_core/cross_encoder/__init__.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2025, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from .client import CrossEncoderClient
from .openai_reranker_client import OpenAIRerankerClient
__all__ = ['CrossEncoderClient', 'OpenAIRerankerClient']
```
--------------------------------------------------------------------------------
/mcp_server/src/models/response_types.py:
--------------------------------------------------------------------------------
```python
"""Response type definitions for Graphiti MCP Server."""
from typing import Any
from typing_extensions import TypedDict
class ErrorResponse(TypedDict):
error: str
class SuccessResponse(TypedDict):
message: str
class NodeResult(TypedDict):
uuid: str
name: str
labels: list[str]
created_at: str | None
summary: str | None
group_id: str
attributes: dict[str, Any]
class NodeSearchResponse(TypedDict):
message: str
nodes: list[NodeResult]
class FactSearchResponse(TypedDict):
message: str
facts: list[dict[str, Any]]
class EpisodeSearchResponse(TypedDict):
message: str
episodes: list[dict[str, Any]]
class StatusResponse(TypedDict):
status: str
message: str
```
--------------------------------------------------------------------------------
/graphiti_core/llm_client/__init__.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from .client import LLMClient
from .config import LLMConfig
from .errors import RateLimitError
from .openai_client import OpenAIClient
__all__ = ['LLMClient', 'OpenAIClient', 'LLMConfig', 'RateLimitError']
```
--------------------------------------------------------------------------------
/tests/embedder/embedder_fixtures.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
def create_embedding_values(multiplier: float = 0.1, dimension: int = 1536) -> list[float]:
"""Create embedding values with the specified multiplier and dimension."""
return [multiplier] * dimension
```
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
```yaml
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "pip"
directory: "/server" # Location of server package manifests
schedule:
interval: "weekly"
- package-ecosystem: "pip"
directory: "/mcp_server" # Location of server package manifests
schedule:
interval: "weekly"
```
--------------------------------------------------------------------------------
/.github/workflows/ai-moderator.yml:
--------------------------------------------------------------------------------
```yaml
name: AI Moderator
on:
issues:
types: [opened]
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
spam-detection:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
models: read
contents: read
steps:
- uses: actions/checkout@v4
- uses: github/ai-moderator@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
spam-label: 'spam'
ai-label: 'ai-generated'
minimize-detected-comments: true
# Built-in prompt configuration (all enabled by default)
enable-spam-detection: true
enable-link-spam-detection: true
enable-ai-detection: true
# custom-prompt-path: '.github/prompts/my-custom.prompt.yml' # Optional
```
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
```markdown
## Summary
Brief description of the changes in this PR.
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Performance improvement
- [ ] Documentation/Tests
## Objective
**For new features and performance improvements:** Clearly describe the objective and rationale for this change.
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] All existing tests pass
## Breaking Changes
- [ ] This PR contains breaking changes
If this is a breaking change, describe:
- What functionality is affected
- Migration path for existing users
## Checklist
- [ ] Code follows project style guidelines (`make lint` passes)
- [ ] Self-review completed
- [ ] Documentation updated where necessary
- [ ] No secrets or sensitive information committed
## Related Issues
Closes #[issue number]
```
--------------------------------------------------------------------------------
/ellipsis.yaml:
--------------------------------------------------------------------------------
```yaml
# See https://docs.ellipsis.dev for all available configurations.
version: 1.3
pr_address_comments:
delivery: "new_commit"
pr_review:
auto_review_enabled: true # enable auto-review of PRs
auto_summarize_pr: true # enable auto-summary of PRs
confidence_threshold: 0.8 # Threshold for how confident Ellipsis needs to be in order to leave a comment, in range [0.0-1.0]
rules: # customize behavior
- "Ensure the copyright notice is present as the header of all Python files"
- "Ensure code is idiomatic"
- "Code should be DRY (Don't Repeat Yourself)"
- "Extremely Complicated Code Needs Comments"
- "Use Descriptive Variable and Constant Names"
- "Follow the Single Responsibility Principle"
- "Function and Method Naming Should Follow Consistent Patterns"
- "There should no secrets or credentials in the code"
- "Don't log sensitive data"
```
--------------------------------------------------------------------------------
/graphiti_core/prompts/models.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from collections.abc import Callable
from typing import Any, Protocol
from pydantic import BaseModel
class Message(BaseModel):
role: str
content: str
class PromptVersion(Protocol):
def __call__(self, context: dict[str, Any]) -> list[Message]: ...
PromptFunction = Callable[[dict[str, Any]], list[Message]]
```
--------------------------------------------------------------------------------
/mcp_server/src/utils/utils.py:
--------------------------------------------------------------------------------
```python
"""Utility functions for Graphiti MCP Server."""
from collections.abc import Callable
def create_azure_credential_token_provider() -> Callable[[], str]:
"""
Create Azure credential token provider for managed identity authentication.
Requires azure-identity package. Install with: pip install mcp-server[azure]
Raises:
ImportError: If azure-identity package is not installed
"""
try:
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
except ImportError:
raise ImportError(
'azure-identity is required for Azure AD authentication. '
'Install it with: pip install mcp-server[azure]'
) from None
credential = DefaultAzureCredential()
token_provider = get_bearer_token_provider(
credential, 'https://cognitiveservices.azure.com/.default'
)
return token_provider
```
--------------------------------------------------------------------------------
/mcp_server/tests/pytest.ini:
--------------------------------------------------------------------------------
```
[pytest]
# Pytest configuration for Graphiti MCP integration tests
# Test discovery patterns
python_files = test_*.py
python_classes = Test*
python_functions = test_*
# Asyncio configuration
asyncio_mode = auto
# Markers for test categorization
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: marks tests as integration tests requiring external services
unit: marks tests as unit tests
stress: marks tests as stress/load tests
requires_neo4j: test requires Neo4j database
requires_falkordb: test requires FalkorDB
requires_openai: test requires OpenAI API key
# Test output options
addopts =
-v
--tb=short
--strict-markers
--color=yes
-p no:warnings
# Timeout for tests (seconds)
timeout = 300
# Coverage options
testpaths = tests
# Environment variables for testing
env =
TEST_MODE=true
LOG_LEVEL=INFO
```
--------------------------------------------------------------------------------
/docker-compose.test.yml:
--------------------------------------------------------------------------------
```yaml
services:
graph:
image: graphiti-service:${GITHUB_SHA}
ports:
- "8000:8000"
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://localhost:8000/healthcheck')",
]
interval: 10s
timeout: 5s
retries: 3
depends_on:
neo4j:
condition: service_healthy
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- NEO4J_URI=bolt://neo4j:${NEO4J_PORT}
- NEO4J_USER=${NEO4J_USER}
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- PORT=8000
neo4j:
image: neo4j:5.26.2
ports:
- "7474:7474"
- "${NEO4J_PORT}:${NEO4J_PORT}"
healthcheck:
test: wget "http://localhost:${NEO4J_PORT}" || exit 1
interval: 1s
timeout: 10s
retries: 20
start_period: 3s
environment:
- NEO4J_AUTH=${NEO4J_USER}/${NEO4J_PASSWORD}
```
--------------------------------------------------------------------------------
/.github/workflows/typecheck.yml:
--------------------------------------------------------------------------------
```yaml
name: Pyright Type Check
permissions:
contents: read
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
pyright:
runs-on: depot-ubuntu-22.04
environment: development
steps:
- uses: actions/checkout@v4
- name: Set up Python
id: setup-python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"
- name: Install dependencies
run: uv sync --all-extras
- name: Run Pyright for graphiti-core
shell: bash
run: |
uv run pyright ./graphiti_core
- name: Install graph-service dependencies
shell: bash
run: |
cd server
uv sync --all-extras
- name: Run Pyright for graph-service
shell: bash
run: |
cd server
uv run pyright .
```
--------------------------------------------------------------------------------
/graphiti_core/llm_client/utils.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import logging
from time import time
from graphiti_core.embedder.client import EmbedderClient
logger = logging.getLogger(__name__)
async def generate_embedding(embedder: EmbedderClient, text: str):
start = time()
text = text.replace('\n', ' ')
embedding = await embedder.create(input_data=[text])
end = time()
logger.debug(f'embedded text of length {len(text)} in {end - start} ms')
return embedding
```
--------------------------------------------------------------------------------
/server/graph_service/dto/common.py:
--------------------------------------------------------------------------------
```python
from datetime import datetime
from typing import Literal
from graphiti_core.utils.datetime_utils import utc_now
from pydantic import BaseModel, Field
class Result(BaseModel):
message: str
success: bool
class Message(BaseModel):
content: str = Field(..., description='The content of the message')
uuid: str | None = Field(default=None, description='The uuid of the message (optional)')
name: str = Field(
default='', description='The name of the episodic node for the message (optional)'
)
role_type: Literal['user', 'assistant', 'system'] = Field(
..., description='The role type of the message (user, assistant or system)'
)
role: str | None = Field(
description='The custom role of the message to be used alongside role_type (user name, bot name, etc.)',
)
timestamp: datetime = Field(default_factory=utc_now, description='The timestamp of the message')
source_description: str = Field(
default='', description='The description of the source of the message'
)
```
--------------------------------------------------------------------------------
/graphiti_core/graphiti_types.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from pydantic import BaseModel, ConfigDict
from graphiti_core.cross_encoder import CrossEncoderClient
from graphiti_core.driver.driver import GraphDriver
from graphiti_core.embedder import EmbedderClient
from graphiti_core.llm_client import LLMClient
from graphiti_core.tracer import Tracer
class GraphitiClients(BaseModel):
driver: GraphDriver
llm_client: LLMClient
embedder: EmbedderClient
cross_encoder: CrossEncoderClient
tracer: Tracer
model_config = ConfigDict(arbitrary_types_allowed=True)
```
--------------------------------------------------------------------------------
/.github/workflows/release-graphiti-core.yml:
--------------------------------------------------------------------------------
```yaml
name: Release to PyPI
on:
push:
tags: ["v*.*.*"]
jobs:
release:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
environment:
name: release
url: https://pypi.org/p/zep-cloud
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"
- name: Compare pyproject version with tag
run: |
TAG_VERSION=${GITHUB_REF#refs/tags/}
PROJECT_VERSION=$(uv run python -c "import tomllib; print('v' + tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
if [ "$TAG_VERSION" != "$PROJECT_VERSION" ]; then
echo "Tag version $TAG_VERSION does not match the project version $PROJECT_VERSION"
exit 1
fi
- name: Build project for distribution
run: uv build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
```
--------------------------------------------------------------------------------
/examples/wizard_of_oz/parser.py:
--------------------------------------------------------------------------------
```python
import os
import re
def parse_wizard_of_oz(file_path):
with open(file_path, encoding='utf-8') as file:
content = file.read()
# Split the content into chapters
chapters = re.split(r'\n\n+Chapter [IVX]+\n', content)[
1:
] # Skip the first split which is before Chapter I
episodes = []
for i, chapter in enumerate(chapters, start=1):
# Extract chapter title
title_match = re.match(r'(.*?)\n\n', chapter)
title = title_match.group(1) if title_match else f'Chapter {i}'
# Remove the title from the chapter content
chapter_content = chapter[len(title) :].strip() if title_match else chapter.strip()
# Create episode dictionary
episode = {'episode_number': i, 'title': title, 'content': chapter_content}
episodes.append(episode)
return episodes
def get_wizard_of_oz_messages():
file_path = 'woo.txt'
script_dir = os.path.dirname(__file__)
relative_path = os.path.join(script_dir, file_path)
# Use the function
parsed_episodes = parse_wizard_of_oz(relative_path)
return parsed_episodes
```
--------------------------------------------------------------------------------
/graphiti_core/embedder/client.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
from abc import ABC, abstractmethod
from collections.abc import Iterable
from pydantic import BaseModel, Field
EMBEDDING_DIM = int(os.getenv('EMBEDDING_DIM', 1024))
class EmbedderConfig(BaseModel):
embedding_dim: int = Field(default=EMBEDDING_DIM, frozen=True)
class EmbedderClient(ABC):
@abstractmethod
async def create(
self, input_data: str | list[str] | Iterable[int] | Iterable[Iterable[int]]
) -> list[float]:
pass
async def create_batch(self, input_data_list: list[str]) -> list[list[float]]:
raise NotImplementedError()
```
--------------------------------------------------------------------------------
/OTEL_TRACING.md:
--------------------------------------------------------------------------------
```markdown
# OpenTelemetry Tracing in Graphiti
Graphiti supports OpenTelemetry distributed tracing. Tracing is optional - without a tracer, operations use no-op implementations with zero overhead.
## Installation
```bash
uv add opentelemetry-sdk
```
## Basic Usage
```python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
from graphiti_core import Graphiti
# Set up OpenTelemetry
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
# Get tracer and pass to Graphiti
tracer = trace.get_tracer(__name__)
graphiti = Graphiti(
uri="bolt://localhost:7687",
user="neo4j",
password="password",
tracer=tracer,
trace_span_prefix="myapp.graphiti" # Optional, defaults to "graphiti"
)
```
## With Kuzu (In-Memory)
```python
from graphiti_core.driver.kuzu_driver import KuzuDriver
kuzu_driver = KuzuDriver()
graphiti = Graphiti(graph_driver=kuzu_driver, tracer=tracer)
```
## Example
See `examples/opentelemetry/` for a complete working example with stdout tracing
```
--------------------------------------------------------------------------------
/tests/evals/utils.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import logging
import sys
def setup_logging():
# Create a logger
logger = logging.getLogger()
logger.setLevel(logging.INFO) # Set the logging level to INFO
# Create console handler and set level to INFO
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
# Create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# Add formatter to console handler
console_handler.setFormatter(formatter)
# Add console handler to logger
logger.addHandler(console_handler)
return logger
```
--------------------------------------------------------------------------------
/tests/evals/eval_cli.py:
--------------------------------------------------------------------------------
```python
import argparse
import asyncio
from tests.evals.eval_e2e_graph_building import build_baseline_graph, eval_graph
async def main():
parser = argparse.ArgumentParser(
description='Run eval_graph and optionally build_baseline_graph from the command line.'
)
parser.add_argument(
'--multi-session-count',
type=int,
required=True,
help='Integer representing multi-session count',
)
parser.add_argument('--session-length', type=int, required=True, help='Length of each session')
parser.add_argument(
'--build-baseline', action='store_true', help='If set, also runs build_baseline_graph'
)
args = parser.parse_args()
# Optionally run the async function
if args.build_baseline:
print('Running build_baseline_graph...')
await build_baseline_graph(
multi_session_count=args.multi_session_count, session_length=args.session_length
)
# Always call eval_graph
result = await eval_graph(
multi_session_count=args.multi_session_count, session_length=args.session_length
)
print('Result of eval_graph:', result)
if __name__ == '__main__':
asyncio.run(main())
```
--------------------------------------------------------------------------------
/server/pyproject.toml:
--------------------------------------------------------------------------------
```toml
[project]
name = "graph-service"
version = "0.1.0"
description = "Zep Graph service implementing Graphiti package"
authors = [
{ "name" = "Paul Paliychuk", "email" = "[email protected]" },
]
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"fastapi>=0.115.0",
"graphiti-core",
"pydantic-settings>=2.4.0",
"uvicorn>=0.30.6",
"httpx>=0.28.1",
]
[project.optional-dependencies]
dev = [
"pydantic>=2.8.2",
"pyright>=1.1.380",
"pytest>=8.3.2",
"python-dotenv>=1.0.1",
"pytest-asyncio>=0.24.0",
"pytest-xdist>=3.6.1",
"ruff>=0.6.2",
"fastapi-cli>=0.0.5",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["graph_service"]
[tool.pytest.ini_options]
pythonpath = ["."]
[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = [
# pycodestyle
"E",
# Pyflakes
"F",
# pyupgrade
"UP",
# flake8-bugbear
"B",
# flake8-simplify
"SIM",
# isort
"I",
]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "single"
indent-style = "space"
docstring-code-format = true
[tool.pyright]
include = ["."]
pythonVersion = "3.10"
typeCheckingMode = "standard"
```
--------------------------------------------------------------------------------
/graphiti_core/llm_client/errors.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
class RateLimitError(Exception):
"""Exception raised when the rate limit is exceeded."""
def __init__(self, message='Rate limit exceeded. Please try again later.'):
self.message = message
super().__init__(self.message)
class RefusalError(Exception):
"""Exception raised when the LLM refuses to generate a response."""
def __init__(self, message: str):
self.message = message
super().__init__(self.message)
class EmptyResponseError(Exception):
"""Exception raised when the LLM returns an empty response."""
def __init__(self, message: str):
self.message = message
super().__init__(self.message)
```
--------------------------------------------------------------------------------
/graphiti_core/utils/ontology_utils/entity_types_utils.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from pydantic import BaseModel
from graphiti_core.errors import EntityTypeValidationError
from graphiti_core.nodes import EntityNode
def validate_entity_types(
entity_types: dict[str, type[BaseModel]] | None,
) -> bool:
if entity_types is None:
return True
entity_node_field_names = EntityNode.model_fields.keys()
for entity_type_name, entity_type_model in entity_types.items():
entity_type_field_names = entity_type_model.model_fields.keys()
for entity_type_field_name in entity_type_field_names:
if entity_type_field_name in entity_node_field_names:
raise EntityTypeValidationError(entity_type_name, entity_type_field_name)
return True
```
--------------------------------------------------------------------------------
/server/graph_service/dto/retrieve.py:
--------------------------------------------------------------------------------
```python
from datetime import datetime, timezone
from pydantic import BaseModel, Field
from graph_service.dto.common import Message
class SearchQuery(BaseModel):
group_ids: list[str] | None = Field(
None, description='The group ids for the memories to search'
)
query: str
max_facts: int = Field(default=10, description='The maximum number of facts to retrieve')
class FactResult(BaseModel):
uuid: str
name: str
fact: str
valid_at: datetime | None
invalid_at: datetime | None
created_at: datetime
expired_at: datetime | None
class Config:
json_encoders = {datetime: lambda v: v.astimezone(timezone.utc).isoformat()}
class SearchResults(BaseModel):
facts: list[FactResult]
class GetMemoryRequest(BaseModel):
group_id: str = Field(..., description='The group id of the memory to get')
max_facts: int = Field(default=10, description='The maximum number of facts to retrieve')
center_node_uuid: str | None = Field(
..., description='The uuid of the node to center the retrieval on'
)
messages: list[Message] = Field(
..., description='The messages to build the retrieval query from '
)
class GetMemoryResponse(BaseModel):
facts: list[FactResult] = Field(..., description='The facts that were retrieved from the graph')
```
--------------------------------------------------------------------------------
/mcp_server/docker/docker-compose.yml:
--------------------------------------------------------------------------------
```yaml
services:
graphiti-falkordb:
image: zepai/knowledge-graph-mcp:latest
build:
context: ..
dockerfile: docker/Dockerfile
args:
GRAPHITI_CORE_VERSION: ${GRAPHITI_CORE_VERSION:-0.23.0}
MCP_SERVER_VERSION: ${MCP_SERVER_VERSION:-1.0.0}
BUILD_DATE: ${BUILD_DATE:-}
VCS_REF: ${VCS_REF:-}
env_file:
- path: ../.env
required: false
environment:
# FalkorDB configuration
- FALKORDB_PASSWORD=${FALKORDB_PASSWORD:-}
- BROWSER=${BROWSER:-1} # Enable FalkorDB Browser UI (set to 0 to disable)
# MCP Server configuration
- FALKORDB_URI=redis://localhost:6379
- FALKORDB_DATABASE=${FALKORDB_DATABASE:-default_db}
- GRAPHITI_GROUP_ID=${GRAPHITI_GROUP_ID:-main}
- SEMAPHORE_LIMIT=${SEMAPHORE_LIMIT:-10}
- CONFIG_PATH=/app/mcp/config/config.yaml
- PATH=/root/.local/bin:${PATH}
volumes:
- falkordb_data:/var/lib/falkordb/data
- mcp_logs:/var/log/graphiti
ports:
- "6379:6379" # FalkorDB/Redis
- "3000:3000" # FalkorDB web UI
- "8000:8000" # MCP server HTTP
healthcheck:
test: ["CMD", "redis-cli", "-p", "6379", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
volumes:
falkordb_data:
driver: local
mcp_logs:
driver: local
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
```markdown
---
name: Bug Report
about: Create a report to help us improve Graphiti
title: '[BUG] '
labels: bug
assignees: ''
---
## Bug Description
A clear and concise description of what the bug is.
## Steps to Reproduce
Provide a minimal code example that reproduces the issue:
```python
# Your code here
```
## Expected Behavior
A clear and concise description of what you expected to happen.
## Actual Behavior
A clear and concise description of what actually happened.
## Environment
- **Graphiti Version**: [e.g. 0.15.1]
- **Python Version**: [e.g. 3.11.5]
- **Operating System**: [e.g. macOS 14.0, Ubuntu 22.04]
- **Database Backend**: [e.g. Neo4j 5.26, FalkorDB 1.1.2]
- **LLM Provider & Model**: [e.g. OpenAI gpt-4.1, Anthropic claude-4-sonnet, Google gemini-2.5-flash]
## Installation Method
- [ ] pip install
- [ ] uv add
- [ ] Development installation (git clone)
## Error Messages/Traceback
```
Paste the full error message and traceback here
```
## Configuration
```python
# Relevant configuration or initialization code
```
## Additional Context
- Does this happen consistently or intermittently?
- Which component are you using? (core library, REST server, MCP server)
- Any recent changes to your environment?
- Related issues or similar problems you've encountered?
## Possible Solution
If you have ideas about what might be causing the issue or how to fix it, please share them here.
```
--------------------------------------------------------------------------------
/graphiti_core/prompts/prompt_helpers.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import json
from typing import Any
DO_NOT_ESCAPE_UNICODE = '\nDo not escape unicode characters.\n'
def to_prompt_json(data: Any, ensure_ascii: bool = False, indent: int | None = None) -> str:
"""
Serialize data to JSON for use in prompts.
Args:
data: The data to serialize
ensure_ascii: If True, escape non-ASCII characters. If False (default), preserve them.
indent: Number of spaces for indentation. Defaults to None (minified).
Returns:
JSON string representation of the data
Notes:
By default (ensure_ascii=False), non-ASCII characters (e.g., Korean, Japanese, Chinese)
are preserved in their original form in the prompt, making them readable
in LLM logs and improving model understanding.
"""
return json.dumps(data, ensure_ascii=ensure_ascii, indent=indent)
```
--------------------------------------------------------------------------------
/mcp_server/pyproject.toml:
--------------------------------------------------------------------------------
```toml
[project]
name = "mcp-server"
version = "1.0.1"
description = "Graphiti MCP Server"
readme = "README.md"
requires-python = ">=3.10,<4"
dependencies = [
"mcp>=1.9.4",
"openai>=1.91.0",
"graphiti-core[falkordb]>=0.23.1",
"pydantic-settings>=2.0.0",
"pyyaml>=6.0",
"typing-extensions>=4.0.0",
]
[project.optional-dependencies]
azure = [
"azure-identity>=1.21.0",
]
providers = [
"google-genai>=1.8.0",
"anthropic>=0.49.0",
"groq>=0.2.0",
"voyageai>=0.2.3",
"sentence-transformers>=2.0.0",
]
[tool.pyright]
include = ["src", "tests"]
pythonVersion = "3.10"
typeCheckingMode = "basic"
[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = [
# pycodestyle
"E",
# Pyflakes
"F",
# pyupgrade
"UP",
# flake8-bugbear
"B",
# flake8-simplify
"SIM",
# isort
"I",
]
ignore = ["E501"]
[tool.ruff.lint.flake8-tidy-imports.banned-api]
# Required by Pydantic on Python < 3.12
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
[tool.ruff.format]
quote-style = "single"
indent-style = "space"
docstring-code-format = true
[tool.uv.sources]
graphiti-core = { path = "../", editable = true }
[dependency-groups]
dev = [
"faker>=37.12.0",
"httpx>=0.28.1",
"psutil>=7.1.2",
"pyright>=1.1.404",
"pytest>=8.0.0",
"pytest-asyncio>=0.21.0",
"pytest-timeout>=2.4.0",
"pytest-xdist>=3.8.0",
"ruff>=0.7.1",
]
```
--------------------------------------------------------------------------------
/graphiti_core/cross_encoder/client.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from abc import ABC, abstractmethod
class CrossEncoderClient(ABC):
"""
CrossEncoderClient is an abstract base class that defines the interface
for cross-encoder models used for ranking passages based on their relevance to a query.
It allows for different implementations of cross-encoder models to be used interchangeably.
"""
@abstractmethod
async def rank(self, query: str, passages: list[str]) -> list[tuple[str, float]]:
"""
Rank the given passages based on their relevance to the query.
Args:
query (str): The query string.
passages (list[str]): A list of passages to rank.
Returns:
list[tuple[str, float]]: A list of tuples containing the passage and its score,
sorted in descending order of relevance.
"""
pass
```
--------------------------------------------------------------------------------
/mcp_server/src/utils/formatting.py:
--------------------------------------------------------------------------------
```python
"""Formatting utilities for Graphiti MCP Server."""
from typing import Any
from graphiti_core.edges import EntityEdge
from graphiti_core.nodes import EntityNode
def format_node_result(node: EntityNode) -> dict[str, Any]:
"""Format an entity node into a readable result.
Since EntityNode is a Pydantic BaseModel, we can use its built-in serialization capabilities.
Excludes embedding vectors to reduce payload size and avoid exposing internal representations.
Args:
node: The EntityNode to format
Returns:
A dictionary representation of the node with serialized dates and excluded embeddings
"""
result = node.model_dump(
mode='json',
exclude={
'name_embedding',
},
)
# Remove any embedding that might be in attributes
result.get('attributes', {}).pop('name_embedding', None)
return result
def format_fact_result(edge: EntityEdge) -> dict[str, Any]:
"""Format an entity edge into a readable result.
Since EntityEdge is a Pydantic BaseModel, we can use its built-in serialization capabilities.
Args:
edge: The EntityEdge to format
Returns:
A dictionary representation of the edge with serialized dates and excluded embeddings
"""
result = edge.model_dump(
mode='json',
exclude={
'fact_embedding',
},
)
result.get('attributes', {}).pop('fact_embedding', None)
return result
```
--------------------------------------------------------------------------------
/mcp_server/docker/docker-compose-falkordb.yml:
--------------------------------------------------------------------------------
```yaml
services:
falkordb:
image: falkordb/falkordb:latest
ports:
- "6379:6379" # Redis/FalkorDB port
- "3000:3000" # FalkorDB web UI
environment:
- FALKORDB_PASSWORD=${FALKORDB_PASSWORD:-}
- BROWSER=${BROWSER:-1} # Enable FalkorDB Browser UI (set to 0 to disable)
volumes:
- falkordb_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-p", "6379", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
graphiti-mcp:
image: zepai/knowledge-graph-mcp:standalone
# For specific versions, replace 'standalone' with a version tag:
# image: zepai/knowledge-graph-mcp:1.0.0-standalone
# When building locally, the build section below will be used.
build:
context: ..
dockerfile: docker/Dockerfile.standalone
env_file:
- path: ../.env
required: false
depends_on:
falkordb:
condition: service_healthy
environment:
# Database configuration
- FALKORDB_URI=${FALKORDB_URI:-redis://falkordb:6379}
- FALKORDB_PASSWORD=${FALKORDB_PASSWORD:-}
- FALKORDB_DATABASE=${FALKORDB_DATABASE:-default_db}
# Application configuration
- GRAPHITI_GROUP_ID=${GRAPHITI_GROUP_ID:-main}
- SEMAPHORE_LIMIT=${SEMAPHORE_LIMIT:-10}
- CONFIG_PATH=/app/mcp/config/config.yaml
- PATH=/root/.local/bin:${PATH}
volumes:
- ../config/config-docker-falkordb.yaml:/app/mcp/config/config.yaml:ro
ports:
- "8000:8000" # Expose the MCP server via HTTP transport
command: ["uv", "run", "main.py"]
volumes:
falkordb_data:
driver: local
```
--------------------------------------------------------------------------------
/graphiti_core/prompts/snippets.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
summary_instructions = """Guidelines:
1. Output only factual content. Never explain what you're doing, why, or mention limitations/constraints.
2. Only use the provided messages, entity, and entity context to set attribute values.
3. Keep the summary concise and to the point. STATE FACTS DIRECTLY IN UNDER 250 CHARACTERS.
Example summaries:
BAD: "This is the only activity in the context. The user listened to this song. No other details were provided to include in this summary."
GOOD: "User played 'Blue Monday' by New Order (electronic genre) on 2024-12-03 at 14:22 UTC."
BAD: "Based on the messages provided, the user attended a meeting. This summary focuses on that event as it was the main topic discussed."
GOOD: "User attended Q3 planning meeting with sales team on March 15."
BAD: "The context shows John ordered pizza. Due to length constraints, other details are omitted from this summary."
GOOD: "John ordered pepperoni pizza from Mario's at 7:30 PM, delivered to office."
"""
```
--------------------------------------------------------------------------------
/mcp_server/docker/build-with-version.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
# Script to build Docker image with proper version tagging
# This script queries PyPI for the latest graphiti-core version and includes it in the image tag
set -e
# Get MCP server version from pyproject.toml
MCP_VERSION=$(grep '^version = ' ../pyproject.toml | sed 's/version = "\(.*\)"/\1/')
# Get latest graphiti-core version from PyPI
echo "Querying PyPI for latest graphiti-core version..."
GRAPHITI_CORE_VERSION=$(curl -s https://pypi.org/pypi/graphiti-core/json | python3 -c "import sys, json; print(json.load(sys.stdin)['info']['version'])")
echo "Latest graphiti-core version: ${GRAPHITI_CORE_VERSION}"
# Get build metadata
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Build the image with explicit graphiti-core version
echo "Building Docker image..."
docker build \
--build-arg MCP_SERVER_VERSION="${MCP_VERSION}" \
--build-arg GRAPHITI_CORE_VERSION="${GRAPHITI_CORE_VERSION}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg VCS_REF="${MCP_VERSION}" \
-f Dockerfile \
-t "zepai/graphiti-mcp:${MCP_VERSION}" \
-t "zepai/graphiti-mcp:${MCP_VERSION}-graphiti-${GRAPHITI_CORE_VERSION}" \
-t "zepai/graphiti-mcp:latest" \
..
echo ""
echo "Build complete!"
echo " MCP Server Version: ${MCP_VERSION}"
echo " Graphiti Core Version: ${GRAPHITI_CORE_VERSION}"
echo " Build Date: ${BUILD_DATE}"
echo ""
echo "Image tags:"
echo " - zepai/graphiti-mcp:${MCP_VERSION}"
echo " - zepai/graphiti-mcp:${MCP_VERSION}-graphiti-${GRAPHITI_CORE_VERSION}"
echo " - zepai/graphiti-mcp:latest"
echo ""
echo "To inspect image metadata:"
echo " docker inspect zepai/graphiti-mcp:${MCP_VERSION} | jq '.[0].Config.Labels'"
```
--------------------------------------------------------------------------------
/graphiti_core/utils/text_utils.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import re
# Maximum length for entity/node summaries
MAX_SUMMARY_CHARS = 500
def truncate_at_sentence(text: str, max_chars: int) -> str:
"""
Truncate text at or about max_chars while respecting sentence boundaries.
Attempts to truncate at the last complete sentence before max_chars.
If no sentence boundary is found before max_chars, truncates at max_chars.
Args:
text: The text to truncate
max_chars: Maximum number of characters
Returns:
Truncated text
"""
if not text or len(text) <= max_chars:
return text
# Find all sentence boundaries (., !, ?) up to max_chars
truncated = text[:max_chars]
# Look for sentence boundaries: period, exclamation, or question mark followed by space or end
sentence_pattern = r'[.!?](?:\s|$)'
matches = list(re.finditer(sentence_pattern, truncated))
if matches:
# Truncate at the last sentence boundary found
last_match = matches[-1]
return text[: last_match.end()].rstrip()
# No sentence boundary found, truncate at max_chars
return truncated.rstrip()
```
--------------------------------------------------------------------------------
/mcp_server/docker/docker-compose-neo4j.yml:
--------------------------------------------------------------------------------
```yaml
services:
neo4j:
image: neo4j:5.26.0
ports:
- "7474:7474" # HTTP
- "7687:7687" # Bolt
environment:
- NEO4J_AUTH=${NEO4J_USER:-neo4j}/${NEO4J_PASSWORD:-demodemo}
- NEO4J_server_memory_heap_initial__size=512m
- NEO4J_server_memory_heap_max__size=1G
- NEO4J_server_memory_pagecache_size=512m
volumes:
- neo4j_data:/data
- neo4j_logs:/logs
healthcheck:
test: ["CMD", "wget", "-O", "/dev/null", "http://localhost:7474"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
graphiti-mcp:
image: zepai/knowledge-graph-mcp:standalone
# For specific versions, replace 'standalone' with a version tag:
# image: zepai/knowledge-graph-mcp:1.0.0-standalone
# When building locally, the build section below will be used.
build:
context: ..
dockerfile: docker/Dockerfile.standalone
env_file:
- path: ../.env
required: false
depends_on:
neo4j:
condition: service_healthy
environment:
# Database configuration
- NEO4J_URI=${NEO4J_URI:-bolt://neo4j:7687}
- NEO4J_USER=${NEO4J_USER:-neo4j}
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-demodemo}
- NEO4J_DATABASE=${NEO4J_DATABASE:-neo4j}
# Application configuration
- GRAPHITI_GROUP_ID=${GRAPHITI_GROUP_ID:-main}
- SEMAPHORE_LIMIT=${SEMAPHORE_LIMIT:-10}
- CONFIG_PATH=/app/mcp/config/config.yaml
- PATH=/root/.local/bin:${PATH}
volumes:
- ../config/config-docker-neo4j.yaml:/app/mcp/config/config.yaml:ro
ports:
- "8000:8000" # Expose the MCP server via HTTP transport
command: ["uv", "run", "main.py"]
volumes:
neo4j_data:
neo4j_logs:
```
--------------------------------------------------------------------------------
/graphiti_core/cross_encoder/bge_reranker_client.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import asyncio
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from sentence_transformers import CrossEncoder
else:
try:
from sentence_transformers import CrossEncoder
except ImportError:
raise ImportError(
'sentence-transformers is required for BGERerankerClient. '
'Install it with: pip install graphiti-core[sentence-transformers]'
) from None
from graphiti_core.cross_encoder.client import CrossEncoderClient
class BGERerankerClient(CrossEncoderClient):
def __init__(self):
self.model = CrossEncoder('BAAI/bge-reranker-v2-m3')
async def rank(self, query: str, passages: list[str]) -> list[tuple[str, float]]:
if not passages:
return []
input_pairs = [[query, passage] for passage in passages]
# Run the synchronous predict method in an executor
loop = asyncio.get_running_loop()
scores = await loop.run_in_executor(None, self.model.predict, input_pairs)
ranked_passages = sorted(
[(passage, float(score)) for passage, score in zip(passages, scores, strict=False)],
key=lambda x: x[1],
reverse=True,
)
return ranked_passages
```
--------------------------------------------------------------------------------
/graphiti_core/utils/datetime_utils.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from datetime import datetime, timezone
def utc_now() -> datetime:
"""Returns the current UTC datetime with timezone information."""
return datetime.now(timezone.utc)
def ensure_utc(dt: datetime | None) -> datetime | None:
"""
Ensures a datetime is timezone-aware and in UTC.
If the datetime is naive (no timezone), assumes it's in UTC.
If the datetime has a different timezone, converts it to UTC.
Returns None if input is None.
"""
if dt is None:
return None
if dt.tzinfo is None:
# If datetime is naive, assume it's UTC
return dt.replace(tzinfo=timezone.utc)
elif dt.tzinfo != timezone.utc:
# If datetime has a different timezone, convert to UTC
return dt.astimezone(timezone.utc)
return dt
def convert_datetimes_to_strings(obj):
if isinstance(obj, dict):
return {k: convert_datetimes_to_strings(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [convert_datetimes_to_strings(item) for item in obj]
elif isinstance(obj, tuple):
return tuple(convert_datetimes_to_strings(item) for item in obj)
elif isinstance(obj, datetime):
return obj.isoformat()
else:
return obj
```
--------------------------------------------------------------------------------
/server/graph_service/routers/retrieve.py:
--------------------------------------------------------------------------------
```python
from datetime import datetime, timezone
from fastapi import APIRouter, status
from graph_service.dto import (
GetMemoryRequest,
GetMemoryResponse,
Message,
SearchQuery,
SearchResults,
)
from graph_service.zep_graphiti import ZepGraphitiDep, get_fact_result_from_edge
router = APIRouter()
@router.post('/search', status_code=status.HTTP_200_OK)
async def search(query: SearchQuery, graphiti: ZepGraphitiDep):
relevant_edges = await graphiti.search(
group_ids=query.group_ids,
query=query.query,
num_results=query.max_facts,
)
facts = [get_fact_result_from_edge(edge) for edge in relevant_edges]
return SearchResults(
facts=facts,
)
@router.get('/entity-edge/{uuid}', status_code=status.HTTP_200_OK)
async def get_entity_edge(uuid: str, graphiti: ZepGraphitiDep):
entity_edge = await graphiti.get_entity_edge(uuid)
return get_fact_result_from_edge(entity_edge)
@router.get('/episodes/{group_id}', status_code=status.HTTP_200_OK)
async def get_episodes(group_id: str, last_n: int, graphiti: ZepGraphitiDep):
episodes = await graphiti.retrieve_episodes(
group_ids=[group_id], last_n=last_n, reference_time=datetime.now(timezone.utc)
)
return episodes
@router.post('/get-memory', status_code=status.HTTP_200_OK)
async def get_memory(
request: GetMemoryRequest,
graphiti: ZepGraphitiDep,
):
combined_query = compose_query_from_messages(request.messages)
result = await graphiti.search(
group_ids=[request.group_id],
query=combined_query,
num_results=request.max_facts,
)
facts = [get_fact_result_from_edge(edge) for edge in result]
return GetMemoryResponse(facts=facts)
def compose_query_from_messages(messages: list[Message]):
combined_query = ''
for message in messages:
combined_query += f'{message.role_type or ""}({message.role or ""}): {message.content}\n'
return combined_query
```
--------------------------------------------------------------------------------
/mcp_server/docker/build-standalone.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
# Script to build and push standalone Docker image with both Neo4j and FalkorDB drivers
# This script queries PyPI for the latest graphiti-core version and includes it in the image tag
set -e
# Get MCP server version from pyproject.toml
MCP_VERSION=$(grep '^version = ' ../pyproject.toml | sed 's/version = "\(.*\)"/\1/')
# Get latest graphiti-core version from PyPI
echo "Querying PyPI for latest graphiti-core version..."
GRAPHITI_CORE_VERSION=$(curl -s https://pypi.org/pypi/graphiti-core/json | python3 -c "import sys, json; print(json.load(sys.stdin)['info']['version'])")
echo "Latest graphiti-core version: ${GRAPHITI_CORE_VERSION}"
# Get build metadata
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
VCS_REF=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
# Build the standalone image with explicit graphiti-core version
echo "Building standalone Docker image..."
docker build \
--build-arg MCP_SERVER_VERSION="${MCP_VERSION}" \
--build-arg GRAPHITI_CORE_VERSION="${GRAPHITI_CORE_VERSION}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg VCS_REF="${VCS_REF}" \
-f Dockerfile.standalone \
-t "zepai/knowledge-graph-mcp:standalone" \
-t "zepai/knowledge-graph-mcp:${MCP_VERSION}-standalone" \
-t "zepai/knowledge-graph-mcp:${MCP_VERSION}-graphiti-${GRAPHITI_CORE_VERSION}-standalone" \
..
echo ""
echo "Build complete!"
echo " MCP Server Version: ${MCP_VERSION}"
echo " Graphiti Core Version: ${GRAPHITI_CORE_VERSION}"
echo " Build Date: ${BUILD_DATE}"
echo " VCS Ref: ${VCS_REF}"
echo ""
echo "Image tags:"
echo " - zepai/knowledge-graph-mcp:standalone"
echo " - zepai/knowledge-graph-mcp:${MCP_VERSION}-standalone"
echo " - zepai/knowledge-graph-mcp:${MCP_VERSION}-graphiti-${GRAPHITI_CORE_VERSION}-standalone"
echo ""
echo "To push to DockerHub:"
echo " docker push zepai/knowledge-graph-mcp:standalone"
echo " docker push zepai/knowledge-graph-mcp:${MCP_VERSION}-standalone"
echo " docker push zepai/knowledge-graph-mcp:${MCP_VERSION}-graphiti-${GRAPHITI_CORE_VERSION}-standalone"
echo ""
echo "Or push all tags:"
echo " docker push --all-tags zepai/knowledge-graph-mcp"
```
--------------------------------------------------------------------------------
/graphiti_core/embedder/openai.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from collections.abc import Iterable
from openai import AsyncAzureOpenAI, AsyncOpenAI
from openai.types import EmbeddingModel
from .client import EmbedderClient, EmbedderConfig
DEFAULT_EMBEDDING_MODEL = 'text-embedding-3-small'
class OpenAIEmbedderConfig(EmbedderConfig):
embedding_model: EmbeddingModel | str = DEFAULT_EMBEDDING_MODEL
api_key: str | None = None
base_url: str | None = None
class OpenAIEmbedder(EmbedderClient):
"""
OpenAI Embedder Client
This client supports both AsyncOpenAI and AsyncAzureOpenAI clients.
"""
def __init__(
self,
config: OpenAIEmbedderConfig | None = None,
client: AsyncOpenAI | AsyncAzureOpenAI | None = None,
):
if config is None:
config = OpenAIEmbedderConfig()
self.config = config
if client is not None:
self.client = client
else:
self.client = AsyncOpenAI(api_key=config.api_key, base_url=config.base_url)
async def create(
self, input_data: str | list[str] | Iterable[int] | Iterable[Iterable[int]]
) -> list[float]:
result = await self.client.embeddings.create(
input=input_data, model=self.config.embedding_model
)
return result.data[0].embedding[: self.config.embedding_dim]
async def create_batch(self, input_data_list: list[str]) -> list[list[float]]:
result = await self.client.embeddings.create(
input=input_data_list, model=self.config.embedding_model
)
return [embedding.embedding[: self.config.embedding_dim] for embedding in result.data]
```
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
```yaml
services:
graph:
profiles: [""]
build:
context: .
ports:
- "8000:8000"
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://localhost:8000/healthcheck')",
]
interval: 10s
timeout: 5s
retries: 3
depends_on:
neo4j:
condition: service_healthy
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- NEO4J_URI=bolt://neo4j:${NEO4J_PORT:-7687}
- NEO4J_USER=${NEO4J_USER:-neo4j}
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
- PORT=8000
- db_backend=neo4j
neo4j:
image: neo4j:5.26.2
profiles: [""]
healthcheck:
test:
[
"CMD-SHELL",
"wget -qO- http://localhost:${NEO4J_PORT:-7474} || exit 1",
]
interval: 1s
timeout: 10s
retries: 10
start_period: 3s
ports:
- "7474:7474" # HTTP
- "${NEO4J_PORT:-7687}:${NEO4J_PORT:-7687}" # Bolt
volumes:
- neo4j_data:/data
environment:
- NEO4J_AUTH=${NEO4J_USER:-neo4j}/${NEO4J_PASSWORD:-password}
falkordb:
image: falkordb/falkordb:latest
profiles: ["falkordb"]
ports:
- "6379:6379"
volumes:
- falkordb_data:/data
environment:
- FALKORDB_ARGS=--port 6379 --cluster-enabled no
healthcheck:
test: ["CMD", "redis-cli", "-p", "6379", "ping"]
interval: 1s
timeout: 10s
retries: 10
start_period: 3s
graph-falkordb:
build:
args:
INSTALL_FALKORDB: "true"
context: .
profiles: ["falkordb"]
ports:
- "8001:8001"
depends_on:
falkordb:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8001/healthcheck')"]
interval: 10s
timeout: 5s
retries: 3
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- FALKORDB_HOST=falkordb
- FALKORDB_PORT=6379
- FALKORDB_DATABASE=default_db
- GRAPHITI_BACKEND=falkordb
- PORT=8001
- db_backend=falkordb
volumes:
neo4j_data:
falkordb_data:
```
--------------------------------------------------------------------------------
/tests/llm_client/test_client.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from graphiti_core.llm_client.client import LLMClient
from graphiti_core.llm_client.config import LLMConfig
class MockLLMClient(LLMClient):
"""Concrete implementation of LLMClient for testing"""
async def _generate_response(self, messages, response_model=None):
return {'content': 'test'}
def test_clean_input():
client = MockLLMClient(LLMConfig())
test_cases = [
# Basic text should remain unchanged
('Hello World', 'Hello World'),
# Control characters should be removed
('Hello\x00World', 'HelloWorld'),
# Newlines, tabs, returns should be preserved
('Hello\nWorld\tTest\r', 'Hello\nWorld\tTest\r'),
# Invalid Unicode should be removed
('Hello\udcdeWorld', 'HelloWorld'),
# Zero-width characters should be removed
('Hello\u200bWorld', 'HelloWorld'),
('Test\ufeffWord', 'TestWord'),
# Multiple issues combined
('Hello\x00\u200b\nWorld\udcde', 'Hello\nWorld'),
# Empty string should remain empty
('', ''),
# Form feed and other control characters from the error case
('{"edges":[{"relation_typ...\f\x04Hn\\?"}]}', '{"edges":[{"relation_typ...Hn\\?"}]}'),
# More specific control character tests
('Hello\x0cWorld', 'HelloWorld'), # form feed \f
('Hello\x04World', 'HelloWorld'), # end of transmission
# Combined JSON-like string with control characters
('{"test": "value\f\x00\x04"}', '{"test": "value"}'),
]
for input_str, expected in test_cases:
assert client._clean_input(input_str) == expected, f'Failed for input: {repr(input_str)}'
```
--------------------------------------------------------------------------------
/.github/workflows/claude.yml:
--------------------------------------------------------------------------------
```yaml
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
# model: "claude-opus-4-20250514"
# Optional: Customize the trigger phrase (default: @claude)
# trigger_phrase: "/claude"
# Optional: Trigger when specific user is assigned to an issue
# assignee_trigger: "claude-bot"
# Optional: Allow Claude to run specific commands
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
# Optional: Add custom instructions for Claude to customize its behavior for your project
# custom_instructions: |
# Follow our coding standards
# Ensure all new code has tests
# Use TypeScript for new files
# Optional: Custom environment variables for Claude
# claude_env: |
# NODE_ENV: test
```
--------------------------------------------------------------------------------
/tests/cross_encoder/test_bge_reranker_client_int.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import pytest
from graphiti_core.cross_encoder.bge_reranker_client import BGERerankerClient
@pytest.fixture
def client():
return BGERerankerClient()
@pytest.mark.asyncio
async def test_rank_basic_functionality(client):
query = 'What is the capital of France?'
passages = [
'Paris is the capital and most populous city of France.',
'London is the capital city of England and the United Kingdom.',
'Berlin is the capital and largest city of Germany.',
]
ranked_passages = await client.rank(query, passages)
# Check if the output is a list of tuples
assert isinstance(ranked_passages, list)
assert all(isinstance(item, tuple) for item in ranked_passages)
# Check if the output has the correct length
assert len(ranked_passages) == len(passages)
# Check if the scores are floats and passages are strings
for passage, score in ranked_passages:
assert isinstance(passage, str)
assert isinstance(score, float)
# Check if the results are sorted in descending order
scores = [score for _, score in ranked_passages]
assert scores == sorted(scores, reverse=True)
@pytest.mark.asyncio
async def test_rank_empty_input(client):
query = 'Empty test'
passages = []
ranked_passages = await client.rank(query, passages)
# Check if the output is an empty list
assert ranked_passages == []
@pytest.mark.asyncio
async def test_rank_single_passage(client):
query = 'Test query'
passages = ['Single test passage']
ranked_passages = await client.rank(query, passages)
# Check if the output has one item
assert len(ranked_passages) == 1
# Check if the passage is correct and the score is a float
assert ranked_passages[0][0] == passages[0]
assert isinstance(ranked_passages[0][1], float)
```
--------------------------------------------------------------------------------
/graphiti_core/driver/search_interface/search_interface.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from typing import Any
from pydantic import BaseModel
class SearchInterface(BaseModel):
"""
This is an interface for implementing custom search logic
"""
async def edge_fulltext_search(
self,
driver: Any,
query: str,
search_filter: Any,
group_ids: list[str] | None = None,
limit: int = 100,
) -> list[Any]:
raise NotImplementedError
async def edge_similarity_search(
self,
driver: Any,
search_vector: list[float],
source_node_uuid: str | None,
target_node_uuid: str | None,
search_filter: Any,
group_ids: list[str] | None = None,
limit: int = 100,
min_score: float = 0.7,
) -> list[Any]:
raise NotImplementedError
async def node_fulltext_search(
self,
driver: Any,
query: str,
search_filter: Any,
group_ids: list[str] | None = None,
limit: int = 100,
) -> list[Any]:
raise NotImplementedError
async def node_similarity_search(
self,
driver: Any,
search_vector: list[float],
search_filter: Any,
group_ids: list[str] | None = None,
limit: int = 100,
min_score: float = 0.7,
) -> list[Any]:
raise NotImplementedError
async def episode_fulltext_search(
self,
driver: Any,
query: str,
search_filter: Any, # kept for parity even if unused in your impl
group_ids: list[str] | None = None,
limit: int = 100,
) -> list[Any]:
raise NotImplementedError
# ---------- SEARCH FILTERS (sync) ----------
def build_node_search_filters(self, search_filters: Any) -> Any:
raise NotImplementedError
def build_edge_search_filters(self, search_filters: Any) -> Any:
raise NotImplementedError
class Config:
arbitrary_types_allowed = True
```
--------------------------------------------------------------------------------
/mcp_server/docs/cursor_rules.md:
--------------------------------------------------------------------------------
```markdown
## Instructions for Using Graphiti's MCP Tools for Agent Memory
### Before Starting Any Task
- **Always search first:** Use the `search_nodes` tool to look for relevant preferences and procedures before beginning work.
- **Search for facts too:** Use the `search_facts` tool to discover relationships and factual information that may be relevant to your task.
- **Filter by entity type:** Specify `Preference`, `Procedure`, or `Requirement` in your node search to get targeted results.
- **Review all matches:** Carefully examine any preferences, procedures, or facts that match your current task.
### Always Save New or Updated Information
- **Capture requirements and preferences immediately:** When a user expresses a requirement or preference, use `add_memory` to store it right away.
- _Best practice:_ Split very long requirements into shorter, logical chunks.
- **Be explicit if something is an update to existing knowledge.** Only add what's changed or new to the graph.
- **Document procedures clearly:** When you discover how a user wants things done, record it as a procedure.
- **Record factual relationships:** When you learn about connections between entities, store these as facts.
- **Be specific with categories:** Label preferences and procedures with clear categories for better retrieval later.
### During Your Work
- **Respect discovered preferences:** Align your work with any preferences you've found.
- **Follow procedures exactly:** If you find a procedure for your current task, follow it step by step.
- **Apply relevant facts:** Use factual information to inform your decisions and recommendations.
- **Stay consistent:** Maintain consistency with previously identified preferences, procedures, and facts.
### Best Practices
- **Search before suggesting:** Always check if there's established knowledge before making recommendations.
- **Combine node and fact searches:** For complex tasks, search both nodes and facts to build a complete picture.
- **Use `center_node_uuid`:** When exploring related information, center your search around a specific node.
- **Prioritize specific matches:** More specific information takes precedence over general information.
- **Be proactive:** If you notice patterns in user behavior, consider storing them as preferences or procedures.
**Remember:** The knowledge graph is your memory. Use it consistently to provide personalized assistance that respects the user's established preferences, procedures, and factual context.
```
--------------------------------------------------------------------------------
/tests/test_graphiti_int.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import logging
import sys
import pytest
from graphiti_core.graphiti import Graphiti
from graphiti_core.search.search_filters import ComparisonOperator, DateFilter, SearchFilters
from graphiti_core.search.search_helpers import search_results_to_context_string
from graphiti_core.utils.datetime_utils import utc_now
from tests.helpers_test import GraphProvider
pytestmark = pytest.mark.integration
pytest_plugins = ('pytest_asyncio',)
def setup_logging():
# Create a logger
logger = logging.getLogger()
logger.setLevel(logging.INFO) # Set the logging level to INFO
# Create console handler and set level to INFO
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
# Create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# Add formatter to console handler
console_handler.setFormatter(formatter)
# Add console handler to logger
logger.addHandler(console_handler)
return logger
@pytest.mark.asyncio
async def test_graphiti_init(graph_driver):
if graph_driver.provider == GraphProvider.FALKORDB:
pytest.skip('Skipping as tests fail on Falkordb')
logger = setup_logging()
graphiti = Graphiti(graph_driver=graph_driver)
await graphiti.build_indices_and_constraints()
search_filter = SearchFilters(
node_labels=['Person', 'City'],
created_at=[
[DateFilter(date=None, comparison_operator=ComparisonOperator.is_null)],
[DateFilter(date=utc_now(), comparison_operator=ComparisonOperator.less_than)],
[DateFilter(date=None, comparison_operator=ComparisonOperator.is_not_null)],
],
)
results = await graphiti.search_(
query='Who is Tania',
search_filter=search_filter,
)
pretty_results = search_results_to_context_string(results)
logger.info(pretty_results)
await graphiti.close()
```
--------------------------------------------------------------------------------
/graphiti_core/embedder/azure_openai.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import logging
from typing import Any
from openai import AsyncAzureOpenAI, AsyncOpenAI
from .client import EmbedderClient
logger = logging.getLogger(__name__)
class AzureOpenAIEmbedderClient(EmbedderClient):
"""Wrapper class for Azure OpenAI that implements the EmbedderClient interface.
Supports both AsyncAzureOpenAI and AsyncOpenAI (with Azure v1 API endpoint).
"""
def __init__(
self,
azure_client: AsyncAzureOpenAI | AsyncOpenAI,
model: str = 'text-embedding-3-small',
):
self.azure_client = azure_client
self.model = model
async def create(self, input_data: str | list[str] | Any) -> list[float]:
"""Create embeddings using Azure OpenAI client."""
try:
# Handle different input types
if isinstance(input_data, str):
text_input = [input_data]
elif isinstance(input_data, list) and all(isinstance(item, str) for item in input_data):
text_input = input_data
else:
# Convert to string list for other types
text_input = [str(input_data)]
response = await self.azure_client.embeddings.create(model=self.model, input=text_input)
# Return the first embedding as a list of floats
return response.data[0].embedding
except Exception as e:
logger.error(f'Error in Azure OpenAI embedding: {e}')
raise
async def create_batch(self, input_data_list: list[str]) -> list[list[float]]:
"""Create batch embeddings using Azure OpenAI client."""
try:
response = await self.azure_client.embeddings.create(
model=self.model, input=input_data_list
)
return [embedding.embedding for embedding in response.data]
except Exception as e:
logger.error(f'Error in Azure OpenAI batch embedding: {e}')
raise
```
--------------------------------------------------------------------------------
/graphiti_core/embedder/voyage.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from collections.abc import Iterable
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import voyageai
else:
try:
import voyageai
except ImportError:
raise ImportError(
'voyageai is required for VoyageAIEmbedderClient. '
'Install it with: pip install graphiti-core[voyageai]'
) from None
from pydantic import Field
from .client import EmbedderClient, EmbedderConfig
DEFAULT_EMBEDDING_MODEL = 'voyage-3'
class VoyageAIEmbedderConfig(EmbedderConfig):
embedding_model: str = Field(default=DEFAULT_EMBEDDING_MODEL)
api_key: str | None = None
class VoyageAIEmbedder(EmbedderClient):
"""
VoyageAI Embedder Client
"""
def __init__(self, config: VoyageAIEmbedderConfig | None = None):
if config is None:
config = VoyageAIEmbedderConfig()
self.config = config
self.client = voyageai.AsyncClient(api_key=config.api_key) # type: ignore[reportUnknownMemberType]
async def create(
self, input_data: str | list[str] | Iterable[int] | Iterable[Iterable[int]]
) -> list[float]:
if isinstance(input_data, str):
input_list = [input_data]
elif isinstance(input_data, list):
input_list = [str(i) for i in input_data if i]
else:
input_list = [str(i) for i in input_data if i is not None]
input_list = [i for i in input_list if i]
if len(input_list) == 0:
return []
result = await self.client.embed(input_list, model=self.config.embedding_model)
return [float(x) for x in result.embeddings[0][: self.config.embedding_dim]]
async def create_batch(self, input_data_list: list[str]) -> list[list[float]]:
result = await self.client.embed(input_data_list, model=self.config.embedding_model)
return [
[float(x) for x in embedding[: self.config.embedding_dim]]
for embedding in result.embeddings
]
```
--------------------------------------------------------------------------------
/graphiti_core/llm_client/config.py:
--------------------------------------------------------------------------------
```python
"""
Copyright 2024, Zep Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from enum import Enum
DEFAULT_MAX_TOKENS = 8192
DEFAULT_TEMPERATURE = 1
class ModelSize(Enum):
small = 'small'
medium = 'medium'
class LLMConfig:
"""
Configuration class for the Language Learning Model (LLM).
This class encapsulates the necessary parameters to interact with an LLM API,
such as OpenAI's GPT models. It stores the API key, model name, and base URL
for making requests to the LLM service.
"""
def __init__(
self,
api_key: str | None = None,
model: str | None = None,
base_url: str | None = None,
temperature: float = DEFAULT_TEMPERATURE,
max_tokens: int = DEFAULT_MAX_TOKENS,
small_model: str | None = None,
):
"""
Initialize the LLMConfig with the provided parameters.
Args:
api_key (str): The authentication key for accessing the LLM API.
This is required for making authorized requests.
model (str, optional): The specific LLM model to use for generating responses.
Defaults to "gpt-4.1-mini".
base_url (str, optional): The base URL of the LLM API service.
Defaults to "https://api.openai.com", which is OpenAI's standard API endpoint.
This can be changed if using a different provider or a custom endpoint.
small_model (str, optional): The specific LLM model to use for generating responses of simpler prompts.
Defaults to "gpt-4.1-nano".
"""
self.base_url = base_url
self.api_key = api_key
self.model = model
self.small_model = small_model
self.temperature = temperature
self.max_tokens = max_tokens
```