diff --git a/.github/workflows/build-custom-mcp.yml b/.github/workflows/build-custom-mcp.yml index 3953c5c8..ab38f36a 100644 --- a/.github/workflows/build-custom-mcp.yml +++ b/.github/workflows/build-custom-mcp.yml @@ -92,8 +92,8 @@ jobs: - name: Build and push Docker image uses: docker/build-push-action@v5 with: - context: . - file: ./mcp_server/docker/Dockerfile.custom + context: ./mcp_server + file: ./mcp_server/docker/Dockerfile.standalone platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.docker_tags.outputs.tags }} diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 00000000..14d86ad6 --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1 @@ +/cache diff --git a/.serena/memories/code_structure.md b/.serena/memories/code_structure.md new file mode 100644 index 00000000..af79b307 --- /dev/null +++ b/.serena/memories/code_structure.md @@ -0,0 +1,76 @@ +# Graphiti Codebase Structure + +## Root Directory Layout +``` +graphiti/ +├── graphiti_core/ # Core library (main Python package) +├── server/ # FastAPI REST API service +├── mcp_server/ # Model Context Protocol server for AI assistants +├── tests/ # Test suite (unit and integration tests) +├── examples/ # Example implementations and use cases +├── images/ # Documentation images and assets +├── signatures/ # CLA signatures +├── .github/ # GitHub Actions workflows +├── pyproject.toml # Project configuration and dependencies +├── Makefile # Development commands +├── README.md # Main documentation +├── CLAUDE.md # Claude Code assistant instructions +├── CONTRIBUTING.md # Contribution guidelines +└── docker-compose.yml # Docker configuration +``` + +## Core Library (`graphiti_core/`) + +### Main Components +- **`graphiti.py`**: Main entry point containing the `Graphiti` class that orchestrates all functionality +- **`nodes.py`**: Core node/entity data structures +- **`edges.py`**: Core edge/relationship data structures +- **`graphiti_types.py`**: Type definitions +- **`errors.py`**: Custom exception classes +- **`helpers.py`**: Utility helper functions +- **`graph_queries.py`**: Graph query definitions +- **`decorators.py`**: Function decorators +- **`tracer.py`**: OpenTelemetry tracing support + +### Subdirectories +- **`driver/`**: Database drivers for Neo4j, FalkorDB, Kuzu, Neptune +- **`llm_client/`**: LLM clients for OpenAI, Anthropic, Gemini, Groq +- **`embedder/`**: Embedding clients for various providers (OpenAI, Voyage, local models) +- **`cross_encoder/`**: Cross-encoder models for reranking +- **`search/`**: Hybrid search implementation with configurable strategies +- **`prompts/`**: LLM prompts for entity extraction, deduplication, summarization +- **`utils/`**: Maintenance operations, bulk processing, datetime handling +- **`models/`**: Pydantic models for data structures +- **`migrations/`**: Database migration scripts +- **`telemetry/`**: Analytics and telemetry code + +## Server (`server/`) +- **`graph_service/main.py`**: FastAPI application entry point +- **`routers/`**: API endpoint definitions (ingestion, retrieval) +- **`dto/`**: Data Transfer Objects for API contracts +- Has its own `Makefile` for server-specific commands + +## MCP Server (`mcp_server/`) +- **`graphiti_mcp_server.py`**: Model Context Protocol server implementation +- **`docker-compose.yml`**: Containerized deployment with Neo4j +- Has its own `pyproject.toml` and dependencies + +## Tests (`tests/`) +- **Unit tests**: Standard pytest tests +- **Integration tests**: Files with `_int` suffix (require database connections) +- **`evals/`**: End-to-end evaluation scripts +- **`conftest.py`**: Pytest configuration and fixtures (at root level) + +## Key Classes +From `graphiti_core/graphiti.py`: +- `Graphiti`: Main orchestrator class +- `AddEpisodeResults`: Results from adding episodes +- `AddBulkEpisodeResults`: Results from bulk episode operations +- `AddTripletResults`: Results from adding triplets + +## Configuration Files +- **`pyproject.toml`**: Main project configuration (dependencies, build system, tool configs) +- **`pytest.ini`**: Pytest configuration +- **`.env.example`**: Example environment variables +- **`docker-compose.yml`**: Docker setup for development +- **`docker-compose.test.yml`**: Docker setup for testing diff --git a/.serena/memories/code_style_and_conventions.md b/.serena/memories/code_style_and_conventions.md new file mode 100644 index 00000000..49b66628 --- /dev/null +++ b/.serena/memories/code_style_and_conventions.md @@ -0,0 +1,85 @@ +# Code Style and Conventions + +## Formatting and Linting + +### Ruff Configuration +- **Tool**: Ruff (handles both linting and formatting) +- **Line length**: 100 characters +- **Quote style**: Single quotes (`'`) +- **Indentation**: Spaces (not tabs) +- **Docstring code format**: Enabled (formats code in docstrings) + +### Linting Rules (Ruff) +Enabled rule sets: +- `E` - pycodestyle errors +- `F` - Pyflakes +- `UP` - pyupgrade (Python version upgrades) +- `B` - flake8-bugbear (common bugs) +- `SIM` - flake8-simplify (simplification suggestions) +- `I` - isort (import sorting) + +Ignored rules: +- `E501` - Line too long (handled by line-length setting) + +### Type Checking +- **Tool**: Pyright +- **Python version**: 3.10+ +- **Type checking mode**: `basic` for main project, `standard` for server +- **Scope**: Main type checking focuses on `graphiti_core/` directory +- **Type hints**: Required and enforced + +## Code Conventions + +### General Guidelines +- Use type hints for all function parameters and return values +- Follow PEP 8 style guide (enforced by Ruff) +- Use Pydantic models for data validation and structure +- Prefer async/await for I/O operations +- Use descriptive variable and function names + +### Python Version +- Minimum supported: Python 3.10 +- Maximum supported: Python 3.x (< 4.0) + +### Import Organization +Imports are automatically organized by Ruff using isort rules: +1. Standard library imports +2. Third-party imports +3. Local application imports + +### Documentation +- Use docstrings for classes and public methods +- Keep README.md and CLAUDE.md up to date +- Add examples to `examples/` folder for new features +- Document breaking changes and migrations + +### Testing Conventions +- Use pytest for all tests +- Async tests use `pytest-asyncio` +- Integration tests must have `_int` suffix in filename or test name +- Unit tests should not require external services +- Use fixtures from `conftest.py` +- Parallel execution supported via `pytest-xdist` + +### Naming Conventions +- Classes: PascalCase (e.g., `Graphiti`, `AddEpisodeResults`) +- Functions/methods: snake_case (e.g., `add_episode`, `build_indices`) +- Constants: UPPER_SNAKE_CASE +- Private methods/attributes: prefix with underscore (e.g., `_internal_method`) + +### Error Handling +- Use custom exceptions from `graphiti_core/errors.py` +- Provide meaningful error messages +- Use `tenacity` for retry logic on external service calls + +### LLM Provider Support +- The codebase supports multiple LLM providers +- Best compatibility with services supporting structured output (OpenAI, Gemini) +- Smaller models may cause schema validation issues +- Always validate LLM outputs against expected schemas + +## Configuration and Dependencies +- Use `pyproject.toml` for all project configuration +- Pin minimum versions in dependencies +- Optional features go in `[project.optional-dependencies]` +- Development dependencies go in `dev` extra diff --git a/.serena/memories/development_commands.md b/.serena/memories/development_commands.md new file mode 100644 index 00000000..66de511f --- /dev/null +++ b/.serena/memories/development_commands.md @@ -0,0 +1,169 @@ +# Development Commands + +## Package Manager +This project uses **uv** (https://docs.astral.sh/uv/) instead of pip or poetry. + +## Main Project Commands (from project root) + +### Installation +```bash +# Install all dependencies including dev tools +make install +# OR +uv sync --extra dev +``` + +### Code Formatting +```bash +# Format code (runs ruff import sorting + code formatting) +make format +# Equivalent to: +# uv run ruff check --select I --fix +# uv run ruff format +``` + +### Linting +```bash +# Lint code (runs ruff checks + pyright type checking) +make lint +# Equivalent to: +# uv run ruff check +# uv run pyright ./graphiti_core +``` + +### Testing +```bash +# Run unit tests only (excludes integration tests) +make test +# Equivalent to: +# DISABLE_FALKORDB=1 DISABLE_KUZU=1 DISABLE_NEPTUNE=1 uv run pytest -m "not integration" + +# Run all tests including integration tests +uv run pytest + +# Run only integration tests +uv run pytest -k "_int" + +# Run specific test file +uv run pytest tests/test_specific_file.py + +# Run specific test method +uv run pytest tests/test_file.py::test_method_name + +# Run tests in parallel (faster) +uv run pytest -n auto +``` + +### Combined Checks +```bash +# Run format, lint, and test in sequence +make check +# OR +make all +``` + +## Server Commands (from server/ directory) + +```bash +cd server/ + +# Install server dependencies +uv sync --extra dev + +# Run server in development mode with auto-reload +uvicorn graph_service.main:app --reload + +# Format server code +make format + +# Lint server code +make lint + +# Test server code +make test +``` + +## MCP Server Commands (from mcp_server/ directory) + +```bash +cd mcp_server/ + +# Install MCP server dependencies +uv sync + +# Run with Docker Compose +docker-compose up + +# Stop Docker Compose +docker-compose down +``` + +## Environment Variables for Testing + +### Required for Integration Tests +```bash +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=... +``` + +### Optional Runtime Variables +```bash +export OPENAI_API_KEY=... # For LLM inference +export USE_PARALLEL_RUNTIME=true # Neo4j parallel runtime (enterprise only) +export ANTHROPIC_API_KEY=... # For Claude models +export GOOGLE_API_KEY=... # For Gemini models +export GROQ_API_KEY=... # For Groq models +export VOYAGE_API_KEY=... # For VoyageAI embeddings +``` + +## Git Workflow + +```bash +# Create a new branch +git checkout -b feature/your-feature-name + +# After making changes, run checks +make check + +# Commit changes (ensure all checks pass first) +git add . +git commit -m "Your commit message" + +# Push to your fork +git push origin feature/your-feature-name +``` + +## Common Development Tasks + +### Before Submitting PR +1. `make check` - Ensures code is formatted, linted, and tested +2. Verify all tests pass including integration tests if applicable +3. Update documentation if needed + +### Adding New Dependencies +Edit `pyproject.toml`: +- Core dependencies → `[project.dependencies]` +- Optional features → `[project.optional-dependencies]` +- Dev dependencies → `[project.optional-dependencies.dev]` + +Then run: +```bash +uv sync --extra dev +``` + +### Database Setup +- **Neo4j**: Version 5.26+ required, use Neo4j Desktop +- **FalkorDB**: Version 1.1.2+ as alternative backend + +## Tool Versions +- Python: 3.10+ +- UV: Latest stable +- Pytest: 8.3.3+ +- Ruff: 0.7.1+ +- Pyright: 1.1.404+ diff --git a/.serena/memories/git_workflow.md b/.serena/memories/git_workflow.md new file mode 100644 index 00000000..1ad565e4 --- /dev/null +++ b/.serena/memories/git_workflow.md @@ -0,0 +1,104 @@ +# Git Workflow for Graphiti Fork + +## Repository Setup + +This repository is a fork of the official Graphiti project with custom MCP server enhancements. + +### Remote Configuration + +```bash +origin https://github.com/Varming73/graphiti.git (your fork) +upstream https://github.com/getzep/graphiti.git (official Graphiti) +``` + +**Best Practice Convention:** +- `origin` = Your fork (where you push your changes) +- `upstream` = Official project (where you pull updates from) + +## Common Workflows + +### Push Your Changes +```bash +git add +git commit -m "Your message" +git push origin main +``` + +### Pull Upstream Updates +```bash +# Fetch latest from upstream +git fetch upstream + +# Merge upstream changes into your main branch +git merge upstream/main + +# Or rebase if you prefer +git rebase upstream/main + +# Push to your fork +git push origin main +``` + +### Check for Upstream Updates +```bash +git fetch upstream +git log HEAD..upstream/main --oneline # See what's new +``` + +### Sync with Upstream (Full Update) +```bash +# Fetch upstream +git fetch upstream + +# Switch to main +git checkout main + +# Merge or rebase +git merge upstream/main # or git rebase upstream/main + +# Push to your fork +git push origin main +``` + +## Current Status + +### Last Repository Replacement +- **Date**: 2025-11-08 +- **Action**: Force-pushed clean code to replace "messed" project +- **Commit**: Added get_entities_by_type and compare_facts_over_time MCP tools +- **Result**: Successfully replaced entire fork history with clean implementation + +### Upstream Tracking +- Upstream connection verified and working +- Can freely pull updates from official Graphiti project +- Your customizations remain in your fork + +## MCP Server Customizations + +Your fork contains these custom MCP tools (not in upstream): +1. `get_entities_by_type` - Retrieve entities by type classification +2. `compare_facts_over_time` - Compare facts between time periods +3. Enhanced `add_memory` UUID documentation + +**Important**: When pulling upstream updates, these customizations are ONLY in `mcp_server/src/graphiti_mcp_server.py`. You may need to manually merge if upstream changes that file. + +## Safety Notes + +- **Never push to upstream** - You don't have permission and shouldn't try +- **Always test locally** before pushing to origin +- **Pull upstream regularly** to stay current with bug fixes and features +- **Document custom changes** in commit messages for future reference + +## If You Need to Reset to Upstream + +```bash +# Backup your current work first! +git checkout -b backup-branch + +# Reset main to match upstream exactly +git checkout main +git reset --hard upstream/main +git push origin main --force + +# Then cherry-pick your custom commits from backup-branch +``` diff --git a/.serena/memories/mcp_server_tools.md b/.serena/memories/mcp_server_tools.md new file mode 100644 index 00000000..0d7a39f9 --- /dev/null +++ b/.serena/memories/mcp_server_tools.md @@ -0,0 +1,193 @@ +# MCP Server Tools Documentation + +## Overview +The Graphiti MCP Server exposes Graphiti functionality through the Model Context Protocol (MCP) for AI assistants (like those in LibreChat). Each tool is decorated with `@mcp.tool()` and provides a specific capability. + +## Tool Naming Convention +All tools follow MCP best practices: +- **snake_case naming**: All lowercase with underscores +- **Action-oriented**: Start with verbs (add, search, get, compare, delete) +- **Concise descriptions**: First line describes core action +- **Clear parameters**: Descriptions specify format and provide examples + +Reference: https://modelcontextprotocol.io/specification/2025-06-18/server/tools + +## Recent Changes + +### 2025-11-08 - UUID Parameter Documentation Enhanced +**Problem**: LLMs were attempting to generate and provide UUIDs when adding NEW memories, which should never happen - UUIDs must be auto-generated for new episodes. + +**Solution**: Enhanced the `uuid` parameter documentation in `add_memory` to be very explicit: "NEVER provide a UUID for new episodes - UUIDs are auto-generated. This parameter can ONLY be used for updating an existing episode by providing its existing UUID." + +**Impact**: Clear guidance for LLMs to prevent them from trying to generate UUIDs for new memories while preserving the ability to update existing episodes. + +## Tool List + +### Core Memory Management +1. **add_memory** - Add episodes to the knowledge graph ✨ IMPROVED DOCS +2. **clear_graph** - Clear all data for specified group IDs +3. **get_status** - Get server and database connection status + +### Search and Retrieval Tools +4. **search_nodes** - Search for nodes/entities using semantic search +5. **search_memory_facts** - Search for facts/relationships using semantic search +6. **get_entities_by_type** ⭐ NEW - Retrieve entities by their type classification +7. **compare_facts_over_time** ⭐ NEW - Compare facts between two time periods + +### Entity and Episode Management +8. **get_entity_edge** - Retrieve a specific entity edge by UUID +9. **delete_entity_edge** - Delete an entity edge from the graph +10. **get_episodes** - Retrieve episodes from the graph +11. **delete_episode** - Delete an episode from the graph + +## Tool Details + +### add_memory (Updated Documentation) +**Purpose**: Add episodes to the knowledge graph + +**MCP-Compliant Description**: "Add an episode to memory. This is the primary way to add information to the graph." + +**Parameters**: +- `name`: str - Name of the episode +- `episode_body`: str - Content to persist (JSON string for source='json') +- `group_id`: Optional[str] - Group ID for this graph (uses default if not provided) +- `source`: str = 'text' - Source type ('text', 'json', or 'message') +- `source_description`: str = '' - Optional description of the source +- `uuid`: Optional[str] = None - **NEVER provide for NEW episodes**. Can ONLY be used to update an existing episode by providing its UUID. + +**UUID Parameter Behavior**: +- **For NEW episodes**: Do NOT provide - auto-generated +- **For UPDATING episodes**: Provide the existing episode's UUID to replace/update it +- **Other uses**: Idempotent operations or external system integration (advanced) + +**Implementation Notes**: +- Returns immediately, processes in background +- Episodes for same group_id processed sequentially +- Providing a UUID updates the episode with that UUID if it exists + +### get_entities_by_type +**Added**: 2025-11-08 +**Purpose**: Essential for PKM (Personal Knowledge Management) - enables browsing entities by their type classification + +**MCP-Compliant Description**: "Retrieve entities by their type classification." + +**Parameters**: +- `entity_types`: List[str] - Entity types to retrieve (e.g., ["Pattern", "Insight", "Preference"]) +- `group_ids`: Optional[List[str]] - Filter by group IDs +- `max_entities`: int = 20 - Maximum entities to return +- `query`: Optional[str] - Optional search query to filter entities + +**Implementation Notes**: +- Uses `SearchFilters(node_labels=entity_types)` from graphiti_core +- Uses `NODE_HYBRID_SEARCH_RRF` search config +- When query is provided: semantic search with type filter +- When query is empty: uses space (' ') as generic query to retrieve all of the type +- Returns `NodeSearchResponse` (same format as search_nodes) + +**Use Cases**: +- "Show me all my Preferences" +- "List Patterns I've identified" +- "Get Insights about productivity" +- "Find all documented Procedures" + +**Example**: +```python +# Get all preferences +get_entities_by_type(entity_types=["Preference"]) + +# Get patterns and insights about productivity +get_entities_by_type( + entity_types=["Pattern", "Insight"], + query="productivity" +) +``` + +### compare_facts_over_time +**Added**: 2025-11-08 +**Purpose**: Track how knowledge/understanding evolved over time - critical for seeing how Patterns, Insights, and understanding changed + +**MCP-Compliant Description**: "Compare facts between two time periods." + +**Parameters**: +- `query`: str - Search query for facts to compare +- `start_time`: str - ISO 8601 timestamp (e.g., "2024-01-01" or "2024-01-01T10:30:00Z") +- `end_time`: str - ISO 8601 timestamp +- `group_ids`: Optional[List[str]] - Filter by group IDs +- `max_facts_per_period`: int = 10 - Max facts per time category + +**Returns**: Dictionary with: +- `facts_from_start`: Facts valid at start_time +- `facts_at_end`: Facts valid at end_time +- `facts_invalidated`: Facts that were invalidated between start and end +- `facts_added`: Facts that became valid between start and end +- `summary`: Count statistics + +**Implementation Notes**: +- Uses `DateFilter` and `ComparisonOperator` from graphiti_core.search.search_filters +- Uses `EDGE_HYBRID_SEARCH_RRF` search config +- Makes 4 separate searches with temporal filters: + 1. Facts valid at start (valid_at <= start AND (invalid_at > start OR invalid_at IS NULL)) + 2. Facts valid at end (valid_at <= end AND (invalid_at > end OR invalid_at IS NULL)) + 3. Facts invalidated (invalid_at > start AND invalid_at <= end) + 4. Facts added (created_at > start AND created_at <= end) +- Uses `format_fact_result()` helper for consistent formatting + +**Use Cases**: +- "How did my understanding of sleep patterns change this month?" +- "What productivity insights were replaced?" +- "Show me how my procedures evolved" +- "Track changes in my preferences over time" + +**Example**: +```python +compare_facts_over_time( + query="productivity patterns", + start_time="2024-01-01", + end_time="2024-03-01" +) +``` + +## Implementation Constraints + +### Safe Design Principles +All tools follow strict constraints to maintain upstream compatibility: +1. **Only use public Graphiti APIs** - No custom Cypher queries, no internal methods +2. **MCP server only changes** - No modifications to graphiti_core/ +3. **Existing patterns** - Follow same structure as existing tools +4. **Standard imports** - Only use imports already in the file or from stable public APIs +5. **MCP compliance** - Follow MCP specification for tool naming and descriptions +6. **LLM-friendly documentation** - Clear guidance to prevent LLM confusion (e.g., UUID usage) + +### Dependencies +All required imports are either: +- Already present in the file (SearchFilters, format_fact_result) +- From stable public APIs (DateFilter, ComparisonOperator, search configs) + +No new dependencies added to pyproject.toml. + +## Testing Notes + +### Validation Tests Passed +- ✅ Python syntax check (py_compile) +- ✅ Ruff formatting (auto-formatted) +- ✅ Ruff linting (all checks passed) +- ✅ No custom Cypher or internal APIs used +- ✅ Follows project code style conventions +- ✅ MCP specification compliance verified +- ✅ UUID documentation enhanced to prevent LLM misuse + +### Manual Testing Required +Before production use, test: +1. add_memory without LLM trying to provide UUIDs for NEW episodes +2. add_memory with UUID for UPDATING existing episodes +3. get_entities_by_type with various entity type combinations +4. get_entities_by_type with and without query parameter +5. compare_facts_over_time with various date ranges +6. Error handling for invalid inputs (empty types, bad dates, etc.) + +## File Location +`mcp_server/src/graphiti_mcp_server.py` + +- `add_memory`: Updated documentation (lines 320-403) +- `get_entities_by_type`: Inserted after `search_nodes` function (lines 486-583) +- `compare_facts_over_time`: Inserted after `search_memory_facts` function (lines 585-766) diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md new file mode 100644 index 00000000..36286516 --- /dev/null +++ b/.serena/memories/project_overview.md @@ -0,0 +1,52 @@ +# Graphiti Project Overview + +## Purpose +Graphiti is a Python framework for building and querying temporally-aware knowledge graphs, specifically designed for AI agents operating in dynamic environments. It continuously integrates user interactions, structured/unstructured data, and external information into a coherent, queryable graph with incremental updates and efficient retrieval. + +## Key Features +- **Bi-temporal data model**: Explicit tracking of event occurrence times +- **Hybrid retrieval**: Combining semantic embeddings, keyword search (BM25), and graph traversal +- **Custom entity definitions**: Support via Pydantic models +- **Real-time incremental updates**: No batch recomputation required +- **Multiple graph backends**: Neo4j and FalkorDB support +- **Optional OpenTelemetry tracing**: For distributed systems + +## Use Cases +- 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 + +## Relationship to Zep +Graphiti powers the core of Zep, a turn-key context engineering platform for AI Agents. This is the open-source version that provides flexibility for custom implementations. + +## Tech Stack +- **Language**: Python 3.10+ +- **Package Manager**: uv (modern, fast Python package installer) +- **Core Dependencies**: + - Pydantic 2.11.5+ (data validation and models) + - Neo4j 5.26.0+ (primary graph database) + - OpenAI 1.91.0+ (LLM inference and embeddings) + - Tenacity 9.0.0+ (retry logic) + - DiskCache 5.6.3+ (caching) + +- **Optional Integrations**: + - Anthropic (Claude models) + - Google Gemini + - Groq + - FalkorDB (alternative graph database) + - Kuzu (graph database) + - Neptune (AWS graph database) + - VoyageAI (embeddings) + - Sentence Transformers (local embeddings) + - OpenTelemetry (tracing) + +- **Development Tools**: + - Ruff (linting and formatting) + - Pyright (type checking) + - Pytest (testing framework with pytest-asyncio and pytest-xdist) + +## Project Version +Current version: 0.22.1pre2 (pre-release) + +## Repository +https://github.com/getzep/graphiti diff --git a/.serena/memories/system_commands.md b/.serena/memories/system_commands.md new file mode 100644 index 00000000..01e69f9e --- /dev/null +++ b/.serena/memories/system_commands.md @@ -0,0 +1,183 @@ +# System Commands (Darwin/macOS) + +This project is being developed on **Darwin** (macOS). Here are the relevant system commands: + +## File System Navigation + +### Basic Commands +```bash +ls # List directory contents +ls -la # List all files including hidden, with details +cd # Change directory +pwd # Print working directory +mkdir # Create directory +rm # Remove file +rm -rf # Remove directory recursively +``` + +### macOS-Specific Notes +- Case-insensitive filesystem by default (though case-preserving) +- Hidden files start with `.` (like `.env`, `.gitignore`) +- Use `open .` to open current directory in Finder +- Use `open ` to open file with default application + +## File Operations + +### Reading Files +```bash +cat # Display entire file +head -n 20 # First 20 lines +tail -n 20 # Last 20 lines +less # Page through file +``` + +### Searching Files +```bash +find . -name "*.py" # Find Python files +find . -type f -name "test_*.py" # Find test files +grep -r "pattern" . # Search for pattern recursively +grep -r "pattern" --include="*.py" . # Search only in Python files +``` + +## Git Commands + +### Basic Git Operations +```bash +git status # Check status +git branch # List branches +git checkout -b # Create and switch to new branch +git add # Stage file +git add . # Stage all changes +git commit -m "message" # Commit changes +git push origin # Push to remote +git pull # Pull latest changes +git diff # Show unstaged changes +git diff --staged # Show staged changes +git log # View commit history +git log --oneline # Compact commit history +``` + +### Current Repository Info +- Current branch: `main` +- Main branch for PRs: `main` + +## Process Management + +```bash +ps aux # List all running processes +ps aux | grep python # Find Python processes +kill # Terminate process +kill -9 # Force kill process +``` + +## Environment Variables + +### View Environment +```bash +env # List all environment variables +echo $PATH # Show PATH variable +echo $OPENAI_API_KEY # Show specific variable +``` + +### Set Environment Variables +```bash +export VAR_NAME=value # Set for current session +export OPENAI_API_KEY="sk-..." # Example +``` + +### Permanent Environment Variables +For permanent variables, add to `~/.zshrc` or `~/.bash_profile`: +```bash +echo 'export VAR_NAME=value' >> ~/.zshrc +source ~/.zshrc +``` + +## Docker Commands (if applicable) + +```bash +docker ps # List running containers +docker ps -a # List all containers +docker-compose up # Start services +docker-compose up -d # Start in background +docker-compose down # Stop services +docker-compose logs -f # Follow logs +docker-compose ps # List compose services +``` + +## Network Commands + +```bash +curl # Make HTTP request +curl -I # Get headers only +ping # Check connectivity +netstat -an | grep LISTEN # Show listening ports +lsof -i : # See what's using a port +``` + +## Permissions + +```bash +chmod +x # Make file executable +chmod 644 # Set file permissions (rw-r--r--) +chmod 755 # Set file permissions (rwxr-xr-x) +chown # Change file owner +``` + +## Useful Utilities + +### Text Processing +```bash +wc -l # Count lines +wc -w # Count words +sort # Sort lines +uniq # Remove duplicates +awk '{print $1}' # Print first column +sed 's/old/new/g' # Replace text +``` + +### Archives +```bash +tar -czf archive.tar.gz # Create tar.gz archive +tar -xzf archive.tar.gz # Extract tar.gz archive +zip -r archive.zip # Create zip archive +unzip archive.zip # Extract zip archive +``` + +## macOS-Specific Commands + +```bash +pbcopy < # Copy file contents to clipboard +pbpaste > # Paste clipboard to file +caffeinate # Prevent system sleep +say "text" # Text-to-speech +``` + +## Python/UV Specific + +```bash +which python3 # Find Python executable location +python3 --version # Check Python version +uv --version # Check UV version +uv run python # Run Python with UV +uv pip list # List installed packages +``` + +## Development Workflow Integration + +For this project, you'll commonly use: +```bash +# Navigate to project +cd /Users/lvarming/it-setup/projects/graphiti + +# Check git status +git status + +# Run development checks +make check + +# Search for code patterns +grep -r "def add_episode" graphiti_core/ + +# Find specific files +find . -name "graphiti.py" +``` diff --git a/.serena/memories/task_completion_checklist.md b/.serena/memories/task_completion_checklist.md new file mode 100644 index 00000000..0cf9c24c --- /dev/null +++ b/.serena/memories/task_completion_checklist.md @@ -0,0 +1,128 @@ +# Task Completion Checklist + +When you complete a coding task, follow these steps to ensure quality: + +## 1. Format Code +```bash +make format +``` +This will: +- Sort imports using ruff (isort rules) +- Format code to 100-character line length +- Apply single-quote style +- Format code in docstrings + +## 2. Lint Code +```bash +make lint +``` +This will: +- Run ruff checks for code quality issues +- Run pyright type checking on `graphiti_core/` +- Identify any style violations or type errors + +Fix any issues reported by the linter. + +## 3. Run Tests +```bash +# Run unit tests (default) +make test + +# OR run all tests including integration tests +uv run pytest +``` + +Ensure all tests pass. If you: +- Modified existing functionality: Verify related tests still pass +- Added new functionality: Consider adding new tests +- Fixed a bug: Consider adding a regression test + +## 4. Integration Testing (if applicable) +If your changes affect: +- Database interactions +- LLM integrations +- External service calls + +Run integration tests: +```bash +# Ensure environment variables are set +export TEST_OPENAI_API_KEY=... +export TEST_URI=neo4j://... +export TEST_USER=... +export TEST_PASSWORD=... + +# Run integration tests +uv run pytest -k "_int" +``` + +## 5. Type Checking +Pyright should have passed during `make lint`, but if you added new code, verify: +- All function parameters have type hints +- Return types are specified +- No `Any` types unless necessary +- Pydantic models are properly defined + +## 6. Run Complete Check +Run the comprehensive check command: +```bash +make check +``` +This runs format, lint, and test in sequence. All should pass. + +## 7. Documentation Updates (if needed) +Consider if your changes require: +- README.md updates (for user-facing features) +- CLAUDE.md updates (for development patterns) +- Docstring additions/updates +- Example code in `examples/` folder +- Comments for complex logic + +## 8. Git Commit +Only commit if all checks pass: +```bash +git add +git commit -m "Descriptive commit message" +``` + +## 9. PR Preparation (if submitting changes) +Before creating a PR: +- Ensure `make check` passes completely +- Review your changes for any debug code or comments +- Check for any TODO items you added +- Verify no sensitive data (API keys, passwords) in code +- Consider if changes need an issue/RFC (>500 LOC changes require discussion) + +## Quick Reference +Most common workflow: +```bash +# After making changes +make check + +# If all passes, commit +git add . +git commit -m "Your message" +``` + +## Special Cases + +### Server Changes +If you modified `server/` code: +```bash +cd server/ +make format +make lint +make test +``` + +### MCP Server Changes +If you modified `mcp_server/` code: +```bash +cd mcp_server/ +# Test with Docker +docker-compose up +``` + +### Large Architectural Changes +- Create GitHub issue (RFC) first +- Discuss technical design and justification +- Get feedback before implementing >500 LOC changes diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 00000000..e052ead8 --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,84 @@ +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp csharp_omnisharp +# dart elixir elm erlang fortran go +# haskell java julia kotlin lua markdown +# nix perl php python python_jedi r +# rego ruby ruby_solargraph rust scala swift +# terraform typescript typescript_vts zig +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# Special requirements: +# - csharp: Requires the presence of a .sln file in the project folder. +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- python + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# whether to use the project's gitignore file to ignore files +# Added on 2025-04-07 +ignore_all_files_in_gitignore: true + +# list of additional paths to ignore +# same syntax as gitignore, so you can use * and ** +# Was previously called `ignored_dirs`, please update your config if you are using that. +# Added (renamed) on 2025-04-07 +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +project_name: "graphiti" +included_optional_tools: [] diff --git a/DOCS/GitHub-DockerHub-Setup.md b/DOCS/GitHub-DockerHub-Setup.md new file mode 100644 index 00000000..f250738b --- /dev/null +++ b/DOCS/GitHub-DockerHub-Setup.md @@ -0,0 +1,379 @@ +# GitHub Actions → Docker Hub Automated Build Setup + +This guide explains how to automatically build your custom Graphiti MCP Docker image with your local changes and push it to Docker Hub using GitHub Actions. + +## Why This Approach? + +✅ **Automatic builds** - Every push to main triggers a new build +✅ **Reproducible** - Anyone can see exactly what was built +✅ **Multi-platform** - Builds for both AMD64 and ARM64 +✅ **No local building** - GitHub does all the work +✅ **Version tracking** - Tied to git commits +✅ **Clean workflow** - Professional CI/CD pipeline + +## Prerequisites + +1. **GitHub account** with a fork of the graphiti repository +2. **Docker Hub account** (username: `lvarming`) +3. **Docker Hub Access Token** (for GitHub Actions to push images) + +--- + +## Step 1: Create Docker Hub Access Token + +1. Go to [Docker Hub](https://hub.docker.com/) +2. Click your username → **Account Settings** +3. Click **Security** → **New Access Token** +4. Give it a description: "GitHub Actions - Graphiti MCP" +5. **Copy the token** (you'll only see it once!) + +--- + +## Step 2: Add Token to GitHub Repository Secrets + +1. Go to your forked repository on GitHub +2. Click **Settings** → **Secrets and variables** → **Actions** +3. Click **New repository secret** +4. Name: `DOCKERHUB_TOKEN` +5. Value: Paste the access token from Step 1 +6. Click **Add secret** + +--- + +## Step 3: Verify Workflow Files + +The repository already includes the necessary workflow file at: +``` +.github/workflows/build-custom-mcp.yml +``` + +And the custom Dockerfile at: +``` +mcp_server/docker/Dockerfile.custom +``` + +These files are configured to: +- Build using YOUR local `graphiti-core` changes (not PyPI) +- Push to `lvarming/graphiti-mcp` on Docker Hub +- Tag with version numbers and `latest` + +--- + +## Step 4: Trigger a Build + +### Option A: Automatic Build (On Push) + +The workflow automatically triggers when you: +- Push to the `main` branch +- Modify files in `graphiti_core/` or `mcp_server/` + +Simply commit and push your changes: +```bash +git add . +git commit -m "Update graphiti-core with custom changes" +git push origin main +``` + +### Option B: Manual Build + +1. Go to your repository on GitHub +2. Click **Actions** tab +3. Select **Build Custom MCP Server** workflow +4. Click **Run workflow** dropdown +5. (Optional) Specify a custom tag, or leave as `latest` +6. Click **Run workflow** + +--- + +## Step 5: Monitor the Build + +1. Click on the running workflow to see progress +2. The build takes about 5-10 minutes +3. You'll see: + - Version extraction + - Docker image build (for AMD64 and ARM64) + - Push to Docker Hub + - Build summary with tags + +--- + +## Step 6: Verify Image on Docker Hub + +1. Go to [Docker Hub](https://hub.docker.com/) +2. Navigate to your repository: `lvarming/graphiti-mcp` +3. Check the **Tags** tab +4. You should see tags like: + - `latest` + - `mcp-1.0.0` + - `mcp-1.0.0-core-0.23.0` + - `sha-abc1234` + +--- + +## Step 7: Use Your Custom Image + +### In Unraid + +Update your Docker container to use: +``` +Repository: lvarming/graphiti-mcp:latest +``` + +### In Docker Compose + +```yaml +services: + graphiti-mcp: + image: lvarming/graphiti-mcp:latest + container_name: graphiti-mcp + restart: unless-stopped + # ... rest of your config +``` + +### Pull Manually + +```bash +docker pull lvarming/graphiti-mcp:latest +``` + +--- + +## Understanding the Build Process + +### What Gets Built + +The custom Dockerfile (`Dockerfile.custom`) does the following: + +1. **Copies entire project** - Both `graphiti_core/` and `mcp_server/` +2. **Builds graphiti-core from local source** - Not from PyPI +3. **Installs MCP server** - Using the local graphiti-core +4. **Creates multi-platform image** - AMD64 and ARM64 + +### Version Tagging + +Each build creates multiple tags: + +| Tag | Description | Example | +|-----|-------------|---------| +| `latest` | Always points to most recent build | `lvarming/graphiti-mcp:latest` | +| `mcp-X.Y.Z` | MCP server version | `lvarming/graphiti-mcp:mcp-1.0.0` | +| `mcp-X.Y.Z-core-A.B.C` | Full version info | `lvarming/graphiti-mcp:mcp-1.0.0-core-0.23.0` | +| `sha-xxxxxxx` | Git commit SHA | `lvarming/graphiti-mcp:sha-abc1234` | + +### Build Arguments + +The workflow passes these build arguments: + +```dockerfile +GRAPHITI_CORE_VERSION=0.23.0 # From pyproject.toml +MCP_SERVER_VERSION=1.0.0 # From mcp_server/pyproject.toml +BUILD_DATE=2025-11-08T12:00:00Z # UTC timestamp +VCS_REF=abc1234 # Git commit hash +``` + +--- + +## Workflow Customization + +### Change Docker Hub Username + +If you want to use a different Docker Hub account, edit `.github/workflows/build-custom-mcp.yml`: + +```yaml +env: + DOCKERHUB_USERNAME: your-username # Change this + IMAGE_NAME: graphiti-mcp +``` + +### Change Trigger Conditions + +To only build on tags instead of every push: + +```yaml +on: + push: + tags: + - 'v*.*.*' +``` + +### Add Slack/Discord Notifications + +Add a notification step at the end of the workflow: + +```yaml +- name: Notify on Success + uses: slackapi/slack-github-action@v1 + with: + webhook-url: ${{ secrets.SLACK_WEBHOOK }} + payload: | + { + "text": "✅ New Graphiti MCP image built: lvarming/graphiti-mcp:latest" + } +``` + +--- + +## Troubleshooting + +### Build Fails - "Error: buildx failed" + +**Cause**: Docker Buildx issue +**Solution**: Re-run the workflow (transient issue) + +### Build Fails - "unauthorized: incorrect username or password" + +**Cause**: Invalid Docker Hub credentials +**Solution**: +1. Verify `DOCKERHUB_TOKEN` secret is correct +2. Regenerate access token on Docker Hub +3. Update the secret in GitHub + +### Build Fails - "No space left on device" + +**Cause**: GitHub runner out of disk space +**Solution**: Add cleanup step before build: + +```yaml +- name: Free up disk space + run: | + docker system prune -af + df -h +``` + +### Image Not Found on Docker Hub + +**Cause**: Image is private +**Solution**: +1. Go to Docker Hub → lvarming/graphiti-mcp +2. Click **Settings** +3. Make repository **Public** + +### Workflow Doesn't Trigger + +**Cause**: Branch protection or incorrect path filters +**Solution**: +1. Check you're pushing to `main` branch +2. Verify changes are in `graphiti_core/` or `mcp_server/` +3. Manually trigger from Actions tab + +--- + +## Advanced: Multi-Repository Setup + +If you want separate images for development and production: + +### Development Image + +Create `.github/workflows/build-dev-mcp.yml`: + +```yaml +name: Build Dev MCP Server + +on: + push: + branches: + - dev + - feature/* + +env: + DOCKERHUB_USERNAME: lvarming + IMAGE_NAME: graphiti-mcp-dev # Different image name +``` + +### Production Image + +Keep the main workflow for production builds on `main` branch. + +--- + +## Comparing with Official Builds + +| Feature | Official (zepai) | Your Custom Build | +|---------|-----------------|-------------------| +| Source | PyPI graphiti-core | Local graphiti-core | +| Trigger | Manual tags only | Auto on push + manual | +| Docker Hub | zepai/knowledge-graph-mcp | lvarming/graphiti-mcp | +| Build Platform | Depot (paid) | GitHub Actions (free) | +| Customization | Limited | Full control | + +--- + +## Best Practices + +### 1. **Pin Versions for Production** + +Instead of `latest`, use specific versions: +```yaml +image: lvarming/graphiti-mcp:mcp-1.0.0-core-0.23.0 +``` + +### 2. **Test Before Deploying** + +Add a test step in the workflow: +```yaml +- name: Test image + run: | + docker run --rm lvarming/graphiti-mcp:latest --version +``` + +### 3. **Keep Workflows Updated** + +GitHub Actions updates frequently. Use Dependabot: + +Create `.github/dependabot.yml`: +```yaml +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" +``` + +### 4. **Monitor Build Times** + +If builds are slow, enable caching: +```yaml +cache-from: type=gha +cache-to: type=gha,mode=max +``` +(Already enabled in the workflow!) + +### 5. **Security Scanning** + +Add Trivy security scanner: +```yaml +- name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: lvarming/graphiti-mcp:latest + format: 'table' + exit-code: '1' + severity: 'CRITICAL,HIGH' +``` + +--- + +## Next Steps + +1. ✅ Set up Docker Hub access token +2. ✅ Add secret to GitHub repository +3. ✅ Push changes to trigger first build +4. ✅ Verify image appears on Docker Hub +5. ✅ Update your Unraid/LibreChat config to use new image +6. 📝 Document any custom changes in DOCS/ + +--- + +## Questions? + +- **GitHub Actions Issues**: Check the Actions tab for detailed logs +- **Docker Hub Issues**: Verify your account and access token +- **Build Failures**: Review the workflow logs for specific errors + +## Related Documentation + +- [LibreChat Setup Guide](./Librechat.setup.md) +- [OpenAI Compatible Endpoints](./OpenAI-Compatible-Endpoints.md) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [Docker Hub Documentation](https://docs.docker.com/docker-hub/) diff --git a/DOCS/Librechat.setup.md b/DOCS/Librechat.setup.md new file mode 100644 index 00000000..01894ddd --- /dev/null +++ b/DOCS/Librechat.setup.md @@ -0,0 +1,371 @@ + Complete Setup Guide: Graphiti MCP + LibreChat + Neo4j on Unraid + + Prerequisites + + - LibreChat running in Docker on Unraid + - Neo4j Docker container running on Unraid + - OpenAI API key (or other LLM provider) + - Access to your Unraid Docker network + + --- + Step 1: Prepare Graphiti MCP Configuration + + 1.1 Create a directory on Unraid for Graphiti MCP + + mkdir -p /mnt/user/appdata/graphiti-mcp/config + + 1.2 Create .env file + + Create /mnt/user/appdata/graphiti-mcp/.env with your settings: + + # Neo4j Connection - IMPORTANT: Use your existing Neo4j container details + # If your Neo4j container is named "neo4j", use: bolt://neo4j:7687 + # Replace with your actual container name if different + NEO4J_URI=bolt://YOUR_NEO4J_CONTAINER_NAME:7687 + NEO4J_USER=neo4j + NEO4J_PASSWORD=YOUR_NEO4J_PASSWORD + NEO4J_DATABASE=neo4j + + # OpenAI Configuration (Required) + OPENAI_API_KEY=sk-your-openai-api-key-here + + # LLM Model (default: gpt-5-mini) + MODEL_NAME=gpt-5-mini + + # Concurrency Control - adjust based on your OpenAI tier + # Tier 1 (free): 1-2, Tier 2: 5-8, Tier 3: 10-15 + SEMAPHORE_LIMIT=10 + + # Group ID for namespacing (optional) + GRAPHITI_GROUP_ID=main + + # Disable telemetry (optional) + GRAPHITI_TELEMETRY_ENABLED=false + + 1.3 Create config file + + Create /mnt/user/appdata/graphiti-mcp/config/config.yaml: + + server: + transport: "http" + host: "0.0.0.0" + port: 8000 + + llm: + provider: "openai" + model: "gpt-5-mini" + max_tokens: 4096 + + providers: + openai: + api_key: ${OPENAI_API_KEY} + api_url: ${OPENAI_API_URL:https://api.openai.com/v1} + + embedder: + provider: "openai" + model: "text-embedding-3-small" + dimensions: 1536 + + providers: + openai: + api_key: ${OPENAI_API_KEY} + + database: + provider: "neo4j" + + providers: + neo4j: + uri: ${NEO4J_URI} + username: ${NEO4J_USER} + password: ${NEO4J_PASSWORD} + database: ${NEO4J_DATABASE:neo4j} + use_parallel_runtime: false + + graphiti: + group_id: ${GRAPHITI_GROUP_ID:main} + user_id: ${USER_ID:mcp_user} + entity_types: + - name: "Preference" + description: "User preferences, choices, opinions, or selections" + - name: "Requirement" + description: "Specific needs, features, or functionality that must be fulfilled" + - name: "Procedure" + description: "Standard operating procedures and sequential instructions" + - name: "Location" + description: "Physical or virtual places where activities occur" + - name: "Event" + description: "Time-bound activities, occurrences, or experiences" + - name: "Organization" + description: "Companies, institutions, groups, or formal entities" + - name: "Document" + description: "Information content in various forms" + - name: "Topic" + description: "Subject of conversation, interest, or knowledge domain" + - name: "Object" + description: "Physical items, tools, devices, or possessions" + + --- + Step 2: Deploy Graphiti MCP on Unraid + + You have two options for deploying on Unraid: + + Option A: Using Unraid Docker Template (Recommended) + + 1. Go to Docker tab in Unraid + 2. Click Add Container + 3. Fill in the following settings: + + Basic Settings: + - Name: graphiti-mcp + - Repository: lvarming/graphiti-mcp:latest # Custom build with your changes + - Network Type: bridge (or custom: br0 if you have a custom network) + + Port Mappings: + - Container Port: 8000 → Host Port: 8000 + + Path Mappings: + - Config Path: + - Container Path: /app/mcp/config/config.yaml + - Host Path: /mnt/user/appdata/graphiti-mcp/config/config.yaml + - Access Mode: Read Only + + Environment Variables: + Add each variable from your .env file: + NEO4J_URI=bolt://YOUR_NEO4J_CONTAINER_NAME:7687 + NEO4J_USER=neo4j + NEO4J_PASSWORD=your_password + NEO4J_DATABASE=neo4j + OPENAI_API_KEY=sk-your-key-here + GRAPHITI_GROUP_ID=main + SEMAPHORE_LIMIT=10 + CONFIG_PATH=/app/mcp/config/config.yaml + PATH=/root/.local/bin:${PATH} + + Extra Parameters: + --env-file=/mnt/user/appdata/graphiti-mcp/.env + + Network: Ensure this container is on the same Docker network as your Neo4j and LibreChat containers. + + Option B: Using Docker Compose + + Create /mnt/user/appdata/graphiti-mcp/docker-compose.yml: + + version: '3.8' + + services: + graphiti-mcp: + image: lvarming/graphiti-mcp:latest # Custom build with your changes + container_name: graphiti-mcp + restart: unless-stopped + env_file: + - .env + environment: + - NEO4J_URI=${NEO4J_URI} + - NEO4J_USER=${NEO4J_USER} + - NEO4J_PASSWORD=${NEO4J_PASSWORD} + - NEO4J_DATABASE=${NEO4J_DATABASE:-neo4j} + - 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.yaml:/app/mcp/config/config.yaml:ro + ports: + - "8000:8000" + networks: + - unraid_network # Replace with your network name + + networks: + unraid_network: + external: true # Use existing Unraid network + + Then run: + cd /mnt/user/appdata/graphiti-mcp + docker-compose up -d + + --- + Step 3: Configure Docker Networking + + Find Your Neo4j Container Name + + docker ps | grep neo4j + + The container name will be something like neo4j or neo4j-community. Use this exact name in your NEO4J_URI. + + Ensure Same Network + + All three containers (Neo4j, Graphiti MCP, LibreChat) should be on the same Docker network. + + Check which network your Neo4j is on: + docker inspect YOUR_NEO4J_CONTAINER_NAME | grep NetworkMode + + Connect Graphiti MCP to the same network: + docker network connect NETWORK_NAME graphiti-mcp + + --- + Step 4: Configure LibreChat + + 4.1 Add Graphiti MCP to LibreChat's librechat.yaml + + Edit your LibreChat configuration file (usually /mnt/user/appdata/librechat/librechat.yaml): + + # ... existing LibreChat config ... + + # Add MCP server configuration + mcpServers: + graphiti-memory: + url: "http://graphiti-mcp:8000/mcp/" + # For multi-user support with user-specific graphs + server_instructions: | + You have access to a knowledge graph memory system through Graphiti. + + IMPORTANT USAGE GUIDELINES: + 1. Always search existing knowledge before adding new information + 2. Use entity type filters: Preference, Procedure, Requirement + 3. Store new information immediately using add_memory + 4. Follow discovered procedures and respect preferences + + Available tools: + - add_episode: Store new conversations/information + - search_nodes: Find entities and summaries + - search_facts: Find relationships between entities + - get_episodes: Retrieve recent conversations + + # Optional: Hide from chat menu (agent-only access) + # chatMenu: false + + # Optional: User-specific group IDs for isolation + # This requires configuring Graphiti to accept dynamic group_id + # user_headers: + # X-User-ID: "{{LIBRECHAT_USER_ID}}" + # X-User-Email: "{{LIBRECHAT_USER_EMAIL}}" + + 4.2 For Production: Use Streamable HTTP + + According to LibreChat docs, for multi-user deployments, ensure the transport is HTTP (which is already default for Graphiti MCP). + + 4.3 Restart LibreChat + + docker restart YOUR_LIBRECHAT_CONTAINER_NAME + + --- + Step 5: Test the Setup + + 5.1 Verify Graphiti MCP is Running + + curl http://YOUR_UNRAID_IP:8000/health + + You should see a health status response. + + 5.2 Test Neo4j Connection + + Check Graphiti MCP logs: + docker logs graphiti-mcp + + Look for successful Neo4j connection messages. + + 5.3 Test in LibreChat + + 1. Open LibreChat in your browser + 2. Start a new chat + 3. In an agent configuration, you should see graphiti-memory available + 4. Try asking the agent to remember something: + Please remember that I prefer dark mode for all interfaces + 5. Then later ask: + What do you know about my preferences? + + --- + Step 6: Advanced Configuration (Optional) + + Per-User Graph Isolation + + To give each LibreChat user their own knowledge graph, you need to: + + 1. Modify Graphiti MCP to accept dynamic group_id from headers + 2. Update LibreChat config to send user info: + + mcpServers: + graphiti-memory: + url: "http://graphiti-mcp:8000/mcp/" + user_headers: + X-User-ID: "{{LIBRECHAT_USER_ID}}" + X-User-Email: "{{LIBRECHAT_USER_EMAIL}}" + + This requires custom modification of Graphiti MCP to read the X-User-ID header and use it as the group_id. + + Using Different LLM Providers + + If you want to use Claude or other providers instead of OpenAI, update config.yaml: + + llm: + provider: "anthropic" + model: "claude-sonnet-4-5-latest" + + providers: + anthropic: + api_key: ${ANTHROPIC_API_KEY} + + And add ANTHROPIC_API_KEY to your .env file. + + --- + Troubleshooting + + Graphiti MCP Can't Connect to Neo4j + + - Issue: Connection refused or timeout + - Solution: + - Verify Neo4j container name: docker ps | grep neo4j + - Use exact container name in NEO4J_URI: bolt://container_name:7687 + - Ensure both containers are on same Docker network + - Check Neo4j is listening on port 7687: docker exec NEO4J_CONTAINER netstat -tlnp | grep 7687 + + LibreChat Can't See Graphiti Tools + + - Issue: Tools not appearing in agent builder + - Solution: + - Check Graphiti MCP is running: curl http://localhost:8000/health + - Verify librechat.yaml syntax is correct + - Restart LibreChat: docker restart librechat + - Check LibreChat logs: docker logs librechat + + Rate Limit Errors + + - Issue: 429 errors from OpenAI + - Solution: Lower SEMAPHORE_LIMIT in .env (try 5 or lower) + + Memory/Performance Issues + + - Issue: Slow responses or high memory usage + - Solution: + - Adjust Neo4j memory in your Neo4j container settings + - Reduce SEMAPHORE_LIMIT to lower concurrent processing + + --- + +⏺ Quick Start Summary + + Building Your Custom Docker Image: + + This setup uses a custom Docker image built from YOUR fork with YOUR changes. + The image is automatically built by GitHub Actions and pushed to Docker Hub. + + Setup Steps: + 1. Fork the graphiti repository to your GitHub account + 2. Add Docker Hub credentials to your repository secrets: + - Go to Settings → Secrets and variables → Actions + - Add secret: DOCKERHUB_TOKEN (your Docker Hub access token) + 3. Push changes to trigger automatic build, or manually trigger from Actions tab + 4. Image will be available at: lvarming/graphiti-mcp:latest + + Key Points: + + 1. Docker Image: Use lvarming/graphiti-mcp:latest (your custom build) + 2. Port: Expose 8000 for HTTP transport + 3. Neo4j Connection: Use bolt://YOUR_NEO4J_CONTAINER_NAME:7687 (container name, not localhost) + 4. Network: All 3 containers (Neo4j, Graphiti MCP, LibreChat) must be on same Docker network + 5. LibreChat Config: Add to librechat.yaml under mcpServers with URL: http://graphiti-mcp:8000/mcp/ + 6. Required Env Vars: OPENAI_API_KEY, NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD + + The setup gives LibreChat powerful knowledge graph memory capabilities, allowing it to remember user preferences, procedures, and facts across conversations! + + Let me know if you need help with any specific step or run into issues during setup. \ No newline at end of file diff --git a/DOCS/OpenAI-Compatible-Endpoints.md b/DOCS/OpenAI-Compatible-Endpoints.md new file mode 100644 index 00000000..9e2a7034 --- /dev/null +++ b/DOCS/OpenAI-Compatible-Endpoints.md @@ -0,0 +1,569 @@ +# OpenAI-Compatible Custom Endpoint Support in Graphiti + +## Overview + +This document analyzes how Graphiti handles OpenAI-compatible custom endpoints (like OpenRouter, NagaAI, Together.ai, etc.) and provides recommendations for improving support. + +## Current Architecture + +Graphiti has **three main OpenAI-compatible client implementations**: + +### 1. OpenAIClient (Default) + +**File**: `graphiti_core/llm_client/openai_client.py` + +- Extends `BaseOpenAIClient` +- Uses the **new OpenAI Responses API** (`/v1/responses` endpoint) +- Uses `client.responses.parse()` for structured outputs (OpenAI SDK v1.91+) +- This is the **default client** exported in the public API + +```python +response = await self.client.responses.parse( + model=model, + input=messages, + temperature=temperature, + max_output_tokens=max_tokens, + text_format=response_model, + reasoning={'effort': reasoning}, + text={'verbosity': verbosity}, +) +``` + +### 2. OpenAIGenericClient (Legacy) + +**File**: `graphiti_core/llm_client/openai_generic_client.py` + +- Uses the **standard Chat Completions API** (`/v1/chat/completions`) +- Uses `client.chat.completions.create()` +- **Only supports unstructured JSON responses** (not Pydantic schemas) +- Currently **not exported** in `__init__.py` (hidden from public API) + +```python +response = await self.client.chat.completions.create( + model=model, + messages=messages, + temperature=temperature, + max_tokens=max_tokens, + response_format={'type': 'json_object'}, +) +``` + +### 3. AzureOpenAILLMClient + +**File**: `graphiti_core/llm_client/azure_openai_client.py` + +- Azure-specific implementation +- Also uses `responses.parse()` like `OpenAIClient` +- Handles Azure-specific authentication and endpoints + +## The Root Problem + +### Issue Description + +When users configure Graphiti with custom OpenAI-compatible endpoints, they encounter errors because: + +1. **`OpenAIClient` uses the new `/v1/responses` endpoint** via `client.responses.parse()` + - This is a **new OpenAI API** (introduced in OpenAI SDK v1.91.0) for structured outputs + - This endpoint is **proprietary to OpenAI** and **not part of the standard OpenAI-compatible API specification** + +2. **Most OpenAI-compatible services** (OpenRouter, NagaAI, Ollama, Together.ai, etc.) **only implement** the standard `/v1/chat/completions` endpoint + - They do **NOT** implement `/v1/responses` + +3. When you configure a `base_url` pointing to these services, Graphiti tries to call: + ``` + https://your-custom-endpoint.com/v1/responses + ``` + Instead of the expected: + ``` + https://your-custom-endpoint.com/v1/chat/completions + ``` + +### Example Error Scenario + +```python +from graphiti_core import Graphiti +from graphiti_core.llm_client import OpenAIClient, LLMConfig + +config = LLMConfig( + api_key="sk-or-v1-...", + model="meta-llama/llama-3-8b-instruct", + base_url="https://openrouter.ai/api/v1" +) + +llm_client = OpenAIClient(config=config) +graphiti = Graphiti(uri, user, password, llm_client=llm_client) + +# This will fail because OpenRouter doesn't have /v1/responses endpoint +# Error: 404 Not Found - https://openrouter.ai/api/v1/responses +``` + +## Current Workaround (Documented) + +The README documents using `OpenAIGenericClient` with Ollama: + +```python +from graphiti_core.llm_client.openai_generic_client import OpenAIGenericClient +from graphiti_core.llm_client.config import LLMConfig + +llm_config = LLMConfig( + api_key="ollama", + model="deepseek-r1:7b", + base_url="http://localhost:11434/v1" +) + +llm_client = OpenAIGenericClient(config=llm_config) +``` + +### Limitations of Current Workaround + +- `OpenAIGenericClient` **doesn't support structured outputs with Pydantic models** +- It only returns raw JSON and manually validates schemas +- It's not the recommended/default client +- It's **not exported** in the public API (`graphiti_core.llm_client`) +- Users must know to import from the internal module path + +## Recommended Solutions + +### Priority 1: Quick Wins (High Priority) + +#### 1.1 Export `OpenAIGenericClient` in Public API + +**File**: `graphiti_core/llm_client/__init__.py` + +**Current**: +```python +from .client import LLMClient +from .config import LLMConfig +from .errors import RateLimitError +from .openai_client import OpenAIClient + +__all__ = ['LLMClient', 'OpenAIClient', 'LLMConfig', 'RateLimitError'] +``` + +**Proposed**: +```python +from .client import LLMClient +from .config import LLMConfig +from .errors import RateLimitError +from .openai_client import OpenAIClient +from .openai_generic_client import OpenAIGenericClient + +__all__ = ['LLMClient', 'OpenAIClient', 'OpenAIGenericClient', 'LLMConfig', 'RateLimitError'] +``` + +#### 1.2 Add Clear Documentation + +**File**: `README.md` + +Add a dedicated section: + +```markdown +### Using OpenAI-Compatible Endpoints (OpenRouter, NagaAI, Together.ai, etc.) + +Most OpenAI-compatible services only support the standard Chat Completions API, +not OpenAI's newer Responses API. Use `OpenAIGenericClient` for these services: + +**OpenRouter Example**: +```python +from graphiti_core import Graphiti +from graphiti_core.llm_client import OpenAIGenericClient, LLMConfig + +config = LLMConfig( + api_key="sk-or-v1-...", + model="meta-llama/llama-3-8b-instruct", + base_url="https://openrouter.ai/api/v1" +) + +llm_client = OpenAIGenericClient(config=config) +graphiti = Graphiti(uri, user, password, llm_client=llm_client) +``` + +**Together.ai Example**: +```python +config = LLMConfig( + api_key="your-together-api-key", + model="meta-llama/Llama-3-70b-chat-hf", + base_url="https://api.together.xyz/v1" +) +llm_client = OpenAIGenericClient(config=config) +``` + +**Note**: `OpenAIGenericClient` has limited structured output support compared to +the default `OpenAIClient`. It uses JSON mode instead of Pydantic schema validation. +``` + +#### 1.3 Add Better Error Messages + +**File**: `graphiti_core/llm_client/openai_client.py` + +Add error handling that detects the issue: + +```python +async def _create_structured_completion(self, ...): + try: + response = await self.client.responses.parse(...) + return response + except openai.NotFoundError as e: + if self.config.base_url and "api.openai.com" not in self.config.base_url: + raise Exception( + f"The OpenAI Responses API (/v1/responses) is not available at {self.config.base_url}. " + f"Most OpenAI-compatible services only support /v1/chat/completions. " + f"Please use OpenAIGenericClient instead of OpenAIClient for custom endpoints. " + f"See: https://help.getzep.com/graphiti/guides/custom-endpoints" + ) from e + raise +``` + +### Priority 2: Better UX (Medium Priority) + +#### 2.1 Add Auto-Detection Logic + +**File**: `graphiti_core/llm_client/config.py` + +```python +class LLMConfig: + 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, + use_responses_api: bool | None = None, # NEW: Auto-detect if None + ): + 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 + + # Auto-detect API style based on base_url + if use_responses_api is None: + self.use_responses_api = self._should_use_responses_api() + else: + self.use_responses_api = use_responses_api + + def _should_use_responses_api(self) -> bool: + """Determine if we should use the Responses API based on base_url.""" + if self.base_url is None: + return True # Default OpenAI + + # Known services that support Responses API + supported_services = ["api.openai.com", "azure.com"] + return any(service in self.base_url for service in supported_services) +``` + +#### 2.2 Create a Unified Smart Client + +**Option A**: Modify `OpenAIClient` to Fall Back + +```python +class OpenAIClient(BaseOpenAIClient): + def __init__(self, config: LLMConfig | None = None, ...): + super().__init__(config, ...) + if config is None: + config = LLMConfig() + + self.use_responses_api = config.use_responses_api + self.client = AsyncOpenAI(api_key=config.api_key, base_url=config.base_url) + + async def _create_structured_completion(self, ...): + if self.use_responses_api: + # Use responses.parse() for OpenAI native + return await self.client.responses.parse(...) + else: + # Fall back to chat.completions with JSON schema for compatibility + return await self.client.chat.completions.create( + model=model, + messages=messages, + temperature=temperature, + max_tokens=max_tokens, + response_format={ + "type": "json_schema", + "json_schema": { + "name": response_model.__name__, + "schema": response_model.model_json_schema(), + "strict": False + } + } + ) +``` + +**Option B**: Create a Factory Function + +```python +# graphiti_core/llm_client/__init__.py + +def create_openai_client( + config: LLMConfig | None = None, + cache: bool = False, + **kwargs +) -> LLMClient: + """ + Factory to create the appropriate OpenAI-compatible client. + + Automatically selects between OpenAIClient (for native OpenAI) + and OpenAIGenericClient (for OpenAI-compatible services). + + Args: + config: LLM configuration including base_url + cache: Whether to enable caching + **kwargs: Additional arguments passed to the client + + Returns: + LLMClient: Either OpenAIClient or OpenAIGenericClient + + Example: + >>> # Automatically uses OpenAIGenericClient for OpenRouter + >>> config = LLMConfig( + ... api_key="sk-or-v1-...", + ... model="meta-llama/llama-3-8b-instruct", + ... base_url="https://openrouter.ai/api/v1" + ... ) + >>> client = create_openai_client(config) + """ + if config is None: + config = LLMConfig() + + # Auto-detect based on base_url + if config.base_url is None or "api.openai.com" in config.base_url: + return OpenAIClient(config, cache, **kwargs) + else: + return OpenAIGenericClient(config, cache, **kwargs) +``` + +#### 2.3 Enhance `OpenAIGenericClient` with Better Structured Output Support + +**File**: `graphiti_core/llm_client/openai_generic_client.py` + +```python +async def _generate_response( + self, + messages: list[Message], + response_model: type[BaseModel] | None = None, + max_tokens: int = DEFAULT_MAX_TOKENS, + model_size: ModelSize = ModelSize.medium, +) -> dict[str, typing.Any]: + openai_messages: list[ChatCompletionMessageParam] = [] + for m in messages: + m.content = self._clean_input(m.content) + if m.role == 'user': + openai_messages.append({'role': 'user', 'content': m.content}) + elif m.role == 'system': + openai_messages.append({'role': 'system', 'content': m.content}) + + try: + # Try to use json_schema format (supported by more providers) + if response_model: + response = await self.client.chat.completions.create( + model=self.model or DEFAULT_MODEL, + messages=openai_messages, + temperature=self.temperature, + max_tokens=max_tokens or self.max_tokens, + response_format={ + "type": "json_schema", + "json_schema": { + "name": response_model.__name__, + "schema": response_model.model_json_schema(), + "strict": False # Most providers don't support strict mode + } + } + ) + else: + response = await self.client.chat.completions.create( + model=self.model or DEFAULT_MODEL, + messages=openai_messages, + temperature=self.temperature, + max_tokens=max_tokens or self.max_tokens, + response_format={'type': 'json_object'}, + ) + + result = response.choices[0].message.content or '{}' + return json.loads(result) + except Exception as e: + logger.error(f'Error in generating LLM response: {e}') + raise +``` + +### Priority 3: Nice to Have (Low Priority) + +#### 3.1 Provider-Specific Clients + +Create convenience clients for popular providers: + +```python +# graphiti_core/llm_client/openrouter_client.py +class OpenRouterClient(OpenAIGenericClient): + """Pre-configured client for OpenRouter. + + Example: + >>> client = OpenRouterClient( + ... api_key="sk-or-v1-...", + ... model="meta-llama/llama-3-8b-instruct" + ... ) + """ + def __init__( + self, + api_key: str, + model: str, + temperature: float = DEFAULT_TEMPERATURE, + max_tokens: int = DEFAULT_MAX_TOKENS, + **kwargs + ): + config = LLMConfig( + api_key=api_key, + model=model, + base_url="https://openrouter.ai/api/v1", + temperature=temperature, + max_tokens=max_tokens + ) + super().__init__(config=config, **kwargs) +``` + +```python +# graphiti_core/llm_client/together_client.py +class TogetherClient(OpenAIGenericClient): + """Pre-configured client for Together.ai. + + Example: + >>> client = TogetherClient( + ... api_key="your-together-key", + ... model="meta-llama/Llama-3-70b-chat-hf" + ... ) + """ + def __init__( + self, + api_key: str, + model: str, + temperature: float = DEFAULT_TEMPERATURE, + max_tokens: int = DEFAULT_MAX_TOKENS, + **kwargs + ): + config = LLMConfig( + api_key=api_key, + model=model, + base_url="https://api.together.xyz/v1", + temperature=temperature, + max_tokens=max_tokens + ) + super().__init__(config=config, **kwargs) +``` + +#### 3.2 Provider Compatibility Matrix + +Add to documentation: + +| Provider | Standard Client | Generic Client | Structured Outputs | Notes | +|----------|----------------|----------------|-------------------|-------| +| OpenAI | ✅ `OpenAIClient` | ✅ | ✅ Full (Responses API) | Recommended: Use `OpenAIClient` | +| Azure OpenAI | ✅ `AzureOpenAILLMClient` | ✅ | ✅ Full (Responses API) | Requires API version 2024-08-01-preview+ | +| OpenRouter | ❌ | ✅ `OpenAIGenericClient` | ⚠️ Limited (JSON Schema) | Use `OpenAIGenericClient` | +| Together.ai | ❌ | ✅ `OpenAIGenericClient` | ⚠️ Limited (JSON Schema) | Use `OpenAIGenericClient` | +| Ollama | ❌ | ✅ `OpenAIGenericClient` | ⚠️ Limited (JSON mode) | Local deployment | +| Groq | ❌ | ✅ `OpenAIGenericClient` | ⚠️ Limited (JSON Schema) | Very fast inference | +| Perplexity | ❌ | ✅ `OpenAIGenericClient` | ⚠️ Limited (JSON mode) | Primarily for search | + +## Testing Recommendations + +### Unit Tests + +1. **Endpoint detection logic** + ```python + def test_should_use_responses_api(): + # OpenAI native should use Responses API + config = LLMConfig(base_url="https://api.openai.com/v1") + assert config.use_responses_api is True + + # Custom endpoints should not + config = LLMConfig(base_url="https://openrouter.ai/api/v1") + assert config.use_responses_api is False + ``` + +2. **Client selection** + ```python + def test_create_openai_client_auto_selection(): + # Should return OpenAIClient for OpenAI + config = LLMConfig(api_key="test") + client = create_openai_client(config) + assert isinstance(client, OpenAIClient) + + # Should return OpenAIGenericClient for others + config = LLMConfig(api_key="test", base_url="https://openrouter.ai/api/v1") + client = create_openai_client(config) + assert isinstance(client, OpenAIGenericClient) + ``` + +### Integration Tests + +1. **Mock server tests** with responses for both endpoints +2. **Real provider tests** (optional, may require API keys): + - OpenRouter + - Together.ai + - Ollama (local) + +### Manual Testing Checklist + +- [ ] OpenRouter with Llama models +- [ ] Together.ai with various models +- [ ] Ollama with local models +- [ ] Groq with fast models +- [ ] Verify error messages are helpful +- [ ] Test both structured and unstructured outputs + +## Summary of Issues + +| Issue | Current State | Impact | Priority | +|-------|---------------|--------|----------| +| `/v1/responses` endpoint usage | Used by default `OpenAIClient` | **BREAKS** all non-OpenAI providers | High | +| `OpenAIGenericClient` not exported | Hidden from public API | Users can't easily use it | High | +| Poor error messages | Generic 404 errors | Confusing for users | High | +| No auto-detection | Must manually choose client | Poor DX | Medium | +| Limited docs | Only Ollama example | Users don't know how to configure other providers | High | +| No structured output in Generic client | Only supports loose JSON | Reduced type safety for custom endpoints | Medium | +| No provider-specific helpers | Generic configuration only | More setup required | Low | + +## Implementation Roadmap + +### Phase 1: Quick Fixes (1-2 days) +1. Export `OpenAIGenericClient` in public API +2. Add documentation section for custom endpoints +3. Improve error messages in `OpenAIClient` +4. Add examples for OpenRouter, Together.ai + +### Phase 2: Enhanced Support (3-5 days) +1. Add auto-detection logic to `LLMConfig` +2. Create factory function for client selection +3. Enhance `OpenAIGenericClient` with better JSON schema support +4. Add comprehensive tests + +### Phase 3: Polish (2-3 days) +1. Create provider-specific client classes +2. Build compatibility matrix documentation +3. Add integration tests with real providers +4. Update all examples and guides + +## References + +- OpenAI SDK v1.91.0+ Responses API: https://platform.openai.com/docs/api-reference/responses +- OpenAI Chat Completions API: https://platform.openai.com/docs/api-reference/chat +- OpenRouter API: https://openrouter.ai/docs +- Together.ai API: https://docs.together.ai/docs/openai-api-compatibility +- Ollama OpenAI compatibility: https://github.com/ollama/ollama/blob/main/docs/openai.md + +## Contributing + +If you're implementing these changes, please ensure: + +1. All changes follow the repository guidelines in `AGENTS.md` +2. Run `make format` before committing +3. Run `make lint` and `make test` to verify changes +4. Update documentation for any new public APIs +5. Add examples demonstrating the new functionality + +## Questions or Issues? + +- Open an issue: https://github.com/getzep/graphiti/issues +- Discussion: https://github.com/getzep/graphiti/discussions +- Documentation: https://help.getzep.com/graphiti diff --git a/DOCS/README.md b/DOCS/README.md new file mode 100644 index 00000000..dbab750b --- /dev/null +++ b/DOCS/README.md @@ -0,0 +1,178 @@ +# Graphiti Custom Build Documentation + +This directory contains documentation for building and deploying a custom Graphiti MCP server with your local changes. + +## Quick Navigation + +### 🐳 Docker Build Setup +**[GitHub-DockerHub-Setup.md](./GitHub-DockerHub-Setup.md)** +- Complete guide for automated Docker builds via GitHub Actions +- Builds with YOUR local graphiti-core changes (not PyPI) +- Pushes to Docker Hub (`lvarming/graphiti-mcp`) +- **Start here** if you want to build custom Docker images + +### 🖥️ LibreChat Integration +**[Librechat.setup.md](./Librechat.setup.md)** +- Complete setup guide for Graphiti MCP + LibreChat + Neo4j on Unraid +- Uses your custom Docker image from Docker Hub +- Step-by-step deployment instructions + +### 🔌 OpenAI API Compatibility +**[OpenAI-Compatible-Endpoints.md](./OpenAI-Compatible-Endpoints.md)** +- Analysis of OpenAI-compatible endpoint support +- Explains `/v1/responses` vs `/v1/chat/completions` issue +- Recommendations for supporting OpenRouter, Together.ai, Ollama, etc. + +--- + +## Quick Start for Custom Builds + +### 1. Setup GitHub → Docker Hub Pipeline + +Follow **[GitHub-DockerHub-Setup.md](./GitHub-DockerHub-Setup.md)** to: +1. Create Docker Hub access token +2. Add token to GitHub repository secrets +3. Push changes to trigger automatic build + +### 2. Deploy on Unraid + +Follow **[Librechat.setup.md](./Librechat.setup.md)** to: +1. Configure Neo4j connection +2. Deploy Graphiti MCP container using `lvarming/graphiti-mcp:latest` +3. Integrate with LibreChat + +--- + +## What's Different in This Setup? + +### Standard Graphiti Deployment +```yaml +# Uses official image from PyPI +image: zepai/knowledge-graph-mcp:standalone +``` + +### Your Custom Deployment +```yaml +# Uses YOUR image with YOUR changes +image: lvarming/graphiti-mcp:latest +``` + +The custom image includes: +- ✅ Your local `graphiti-core` changes +- ✅ Your MCP server modifications +- ✅ Both Neo4j and FalkorDB drivers +- ✅ Built automatically on every push + +--- + +## Files in This Repository + +### Workflow Files +- `.github/workflows/build-custom-mcp.yml` - GitHub Actions workflow for automated builds + +### Docker Files +- `mcp_server/docker/Dockerfile.custom` - Custom Dockerfile that uses local graphiti-core + +### Documentation +- `DOCS/GitHub-DockerHub-Setup.md` - Docker build setup guide +- `DOCS/Librechat.setup.md` - LibreChat integration guide +- `DOCS/OpenAI-Compatible-Endpoints.md` - API compatibility analysis +- `DOCS/README.md` - This file + +--- + +## Workflow Overview + +```mermaid +graph LR + A[Make Changes] --> B[Git Push] + B --> C[GitHub Actions] + C --> D[Build Docker Image] + D --> E[Push to Docker Hub] + E --> F[Deploy on Unraid] + F --> G[Use in LibreChat] +``` + +1. **Make Changes** - Modify `graphiti_core/` or `mcp_server/` +2. **Git Push** - Push to `main` branch on GitHub +3. **GitHub Actions** - Automatically triggered +4. **Build Image** - Using `Dockerfile.custom` with local code +5. **Push to Docker Hub** - Tagged as `lvarming/graphiti-mcp:latest` +6. **Deploy on Unraid** - Pull latest image +7. **Use in LibreChat** - Configure MCP server URL + +--- + +## Version Information + +Your builds include comprehensive version tracking: + +```bash +docker inspect lvarming/graphiti-mcp:latest | jq '.[0].Config.Labels' +``` + +Returns: +```json +{ + "org.opencontainers.image.title": "Graphiti MCP Server (Custom Build)", + "org.opencontainers.image.version": "1.0.0", + "graphiti.core.version": "0.23.0", + "graphiti.core.source": "local", + "org.opencontainers.image.revision": "abc1234", + "org.opencontainers.image.created": "2025-11-08T12:00:00Z" +} +``` + +--- + +## Key Benefits + +### 🚀 Automated +- No manual Docker builds +- No need to push images yourself +- Triggered automatically on code changes + +### 🔄 Reproducible +- Every build is traced to a git commit +- Anyone can see exactly what was built +- Version labels include all metadata + +### 🏗️ Multi-Platform +- Builds for AMD64 and ARM64 +- Works on Intel, AMD, and Apple Silicon +- Single command works everywhere + +### 🎯 Clean Workflow +- Professional CI/CD pipeline +- Follows industry best practices +- Easy to maintain and extend + +--- + +## Support + +### Issues with Docker Builds? +See [GitHub-DockerHub-Setup.md - Troubleshooting](./GitHub-DockerHub-Setup.md#troubleshooting) + +### Issues with Deployment? +See [Librechat.setup.md - Troubleshooting](./Librechat.setup.md#troubleshooting) + +### Issues with API Compatibility? +See [OpenAI-Compatible-Endpoints.md](./OpenAI-Compatible-Endpoints.md) + +--- + +## Contributing + +If you make improvements to these docs or workflows: + +1. Update the relevant documentation file +2. Test the changes +3. Commit and push +4. (Optional) Share with the community via PR to upstream + +--- + +## License + +This documentation follows the same license as the Graphiti project. diff --git a/mcp_server/docker/Dockerfile.custom b/mcp_server/docker/Dockerfile.custom deleted file mode 100644 index 92b25faf..00000000 --- a/mcp_server/docker/Dockerfile.custom +++ /dev/null @@ -1,83 +0,0 @@ -# syntax=docker/dockerfile:1 -# Custom Graphiti MCP Server Image with Local graphiti-core Changes -# This Dockerfile builds the MCP server using the LOCAL graphiti-core code -# instead of pulling from PyPI - -FROM python:3.11-slim-bookworm - -# Install system dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl \ - ca-certificates \ - git \ - && rm -rf /var/lib/apt/lists/* - -# Install uv for Python package management -ADD https://astral.sh/uv/install.sh /uv-installer.sh -RUN sh /uv-installer.sh && rm /uv-installer.sh - -# Add uv to PATH -ENV PATH="/root/.local/bin:${PATH}" - -# Configure uv for optimal Docker usage -ENV UV_COMPILE_BYTECODE=1 \ - UV_LINK_MODE=copy \ - UV_PYTHON_DOWNLOADS=never \ - MCP_SERVER_HOST="0.0.0.0" \ - PYTHONUNBUFFERED=1 - -# Set up application directory -WORKDIR /app - -# Copy the ENTIRE graphiti project (both core and mcp_server) -# This allows us to use the local graphiti-core -COPY . /app - -# Build and install graphiti-core from local source first -WORKDIR /app -RUN uv pip install --system "./[neo4j,falkordb]" - -# Now set up MCP server -WORKDIR /app/mcp_server - -# Remove the local path override for graphiti-core in Docker builds -# and remove graphiti-core from dependencies since we already installed it -RUN sed -i '/\[tool\.uv\.sources\]/,/graphiti-core/d' pyproject.toml && \ - sed -i '/graphiti-core\[falkordb\]/d' pyproject.toml - -# Install remaining MCP server dependencies (graphiti-core already installed from local) -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --no-dev - -# Accept version build arguments -ARG GRAPHITI_CORE_VERSION=local -ARG MCP_SERVER_VERSION=1.0.0 -ARG BUILD_DATE -ARG VCS_REF - -# Store version info -RUN echo "${GRAPHITI_CORE_VERSION}" > /app/mcp_server/.graphiti-core-version - -# Create log directory -RUN mkdir -p /var/log/graphiti - -# Add Docker labels with version information -LABEL org.opencontainers.image.title="Graphiti MCP Server (Custom Build)" \ - org.opencontainers.image.description="Custom Graphiti MCP server with local graphiti-core changes" \ - org.opencontainers.image.version="${MCP_SERVER_VERSION}" \ - org.opencontainers.image.created="${BUILD_DATE}" \ - org.opencontainers.image.revision="${VCS_REF}" \ - org.opencontainers.image.vendor="Custom Build" \ - org.opencontainers.image.source="https://github.com/lvarming/graphiti" \ - graphiti.core.version="${GRAPHITI_CORE_VERSION}" \ - graphiti.core.source="local" - -# Expose MCP server port -EXPOSE 8000 - -# Health check - verify MCP server is responding -HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=3 \ - CMD curl -f http://localhost:8000/health || exit 1 - -# Run the MCP server -CMD ["uv", "run", "main.py"]