graphiti/migrate_group_id.py
Lars Varming 341efd8c3d Fix: Critical database parameter bug + index creation error handling
CRITICAL FIX - Database Parameter (graphiti_core):
- Fixed graphiti_core/driver/neo4j_driver.py execute_query method
- database_ parameter was incorrectly added to params dict instead of kwargs
- Now correctly passed as keyword argument to Neo4j driver
- Impact: All queries now execute in configured database (not default 'neo4j')
- Root cause: Violated Neo4j Python driver API contract

Technical Details:
Previous code (BROKEN):
  params.setdefault('database_', self._database)  # Wrong - in params dict
  result = await self.client.execute_query(cypher_query_, parameters_=params, **kwargs)

Fixed code (CORRECT):
  kwargs.setdefault('database_', self._database)  # Correct - in kwargs
  result = await self.client.execute_query(cypher_query_, parameters_=params, **kwargs)

FIX - Index Creation Error Handling (MCP server):
- Added graceful handling for Neo4j IF NOT EXISTS bug
- Prevents MCP server crash when indices already exist
- Logs warning instead of failing initialization
- Handles EquivalentSchemaRuleAlreadyExists error gracefully

Files Modified:
- graphiti_core/driver/neo4j_driver.py (3 lines changed)
- mcp_server/src/graphiti_mcp_server.py (12 lines added error handling)
- mcp_server/pyproject.toml (version bump to 1.0.5)

Testing:
- Python syntax validation: PASSED
- Ruff formatting: PASSED
- Ruff linting: PASSED

Closes issues with:
- Data being stored in wrong Neo4j database
- MCP server crashing on startup with EquivalentSchemaRuleAlreadyExists
- NEO4J_DATABASE environment variable being ignored

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 11:37:16 +01:00

189 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""
Migrate Graphiti data between databases and group_ids.
Usage:
python migrate_group_id.py
This script migrates data from:
Source: neo4j database, group_id='lvarming73'
Target: graphiti database, group_id='6910959f2128b5c4faa22283'
"""
from neo4j import GraphDatabase
import os
# Configuration
NEO4J_URI = "bolt://192.168.1.25:7687"
NEO4J_USER = "neo4j"
NEO4J_PASSWORD = os.environ.get("NEO4J_PASSWORD", '!"MiTa1205')
SOURCE_DATABASE = "neo4j"
SOURCE_GROUP_ID = "lvarming73"
TARGET_DATABASE = "graphiti"
TARGET_GROUP_ID = "6910959f2128b5c4faa22283"
def migrate_data():
"""Migrate all nodes and relationships from source to target."""
driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASSWORD))
try:
# Step 1: Export data from source database
print(f"\n📤 Exporting data from {SOURCE_DATABASE} database (group_id: {SOURCE_GROUP_ID})...")
with driver.session(database=SOURCE_DATABASE) as session:
# Get all nodes with the source group_id
nodes_result = session.run("""
MATCH (n {group_id: $group_id})
RETURN
id(n) as old_id,
labels(n) as labels,
properties(n) as props
ORDER BY old_id
""", group_id=SOURCE_GROUP_ID)
nodes = list(nodes_result)
print(f" Found {len(nodes)} nodes to migrate")
if len(nodes) == 0:
print(" ⚠️ No nodes found. Nothing to migrate.")
return
# Get all relationships between nodes with the source group_id
rels_result = session.run("""
MATCH (n {group_id: $group_id})-[r]->(m {group_id: $group_id})
RETURN
id(startNode(r)) as from_id,
id(endNode(r)) as to_id,
type(r) as rel_type,
properties(r) as props
""", group_id=SOURCE_GROUP_ID)
relationships = list(rels_result)
print(f" Found {len(relationships)} relationships to migrate")
# Step 2: Create ID mapping (old Neo4j internal ID -> new node UUID)
print(f"\n📥 Importing data to {TARGET_DATABASE} database (group_id: {TARGET_GROUP_ID})...")
id_mapping = {}
with driver.session(database=TARGET_DATABASE) as session:
# Create nodes
for node in nodes:
old_id = node['old_id']
labels = node['labels']
props = dict(node['props'])
# Update group_id
props['group_id'] = TARGET_GROUP_ID
# Get the uuid if it exists (for tracking)
node_uuid = props.get('uuid', old_id)
# Build labels string
labels_str = ':'.join(labels)
# Create node
result = session.run(f"""
CREATE (n:{labels_str})
SET n = $props
RETURN id(n) as new_id, n.uuid as uuid
""", props=props)
record = result.single()
id_mapping[old_id] = record['new_id']
print(f" ✅ Created {len(nodes)} nodes")
# Create relationships
rel_count = 0
for rel in relationships:
from_old_id = rel['from_id']
to_old_id = rel['to_id']
rel_type = rel['rel_type']
props = dict(rel['props']) if rel['props'] else {}
# Update group_id in relationship properties if it exists
if 'group_id' in props:
props['group_id'] = TARGET_GROUP_ID
# Get new node IDs
from_new_id = id_mapping.get(from_old_id)
to_new_id = id_mapping.get(to_old_id)
if from_new_id is None or to_new_id is None:
print(f" ⚠️ Skipping relationship: node mapping not found")
continue
# Create relationship
session.run(f"""
MATCH (a), (b)
WHERE id(a) = $from_id AND id(b) = $to_id
CREATE (a)-[r:{rel_type}]->(b)
SET r = $props
""", from_id=from_new_id, to_id=to_new_id, props=props)
rel_count += 1
print(f" ✅ Created {rel_count} relationships")
# Step 3: Verify migration
print(f"\n✅ Migration complete!")
print(f"\n📊 Verification:")
with driver.session(database=TARGET_DATABASE) as session:
# Count nodes in target
result = session.run("""
MATCH (n {group_id: $group_id})
RETURN count(n) as node_count
""", group_id=TARGET_GROUP_ID)
target_count = result.single()['node_count']
print(f" Target database now has {target_count} nodes with group_id={TARGET_GROUP_ID}")
# Show node types
result = session.run("""
MATCH (n {group_id: $group_id})
RETURN labels(n) as labels, count(*) as count
ORDER BY count DESC
""", group_id=TARGET_GROUP_ID)
print(f"\n Node types:")
for record in result:
labels = ':'.join(record['labels'])
count = record['count']
print(f" {labels}: {count}")
print(f"\n🎉 Done! Your data has been migrated successfully.")
print(f"\nNext steps:")
print(f"1. Verify the data in Neo4j Browser:")
print(f" :use graphiti")
print(f" MATCH (n {{group_id: '{TARGET_GROUP_ID}'}}) RETURN n LIMIT 25")
print(f"2. Test in LibreChat to ensure everything works")
print(f"3. Once verified, you can delete the old data:")
print(f" :use neo4j")
print(f" MATCH (n {{group_id: '{SOURCE_GROUP_ID}'}}) DETACH DELETE n")
finally:
driver.close()
if __name__ == "__main__":
print("=" * 70)
print("Graphiti Data Migration Script")
print("=" * 70)
print(f"\nSource: {SOURCE_DATABASE} database, group_id='{SOURCE_GROUP_ID}'")
print(f"Target: {TARGET_DATABASE} database, group_id='{TARGET_GROUP_ID}'")
print(f"\nNeo4j URI: {NEO4J_URI}")
print("=" * 70)
response = input("\n⚠️ Ready to migrate? This will copy all data. Type 'yes' to continue: ")
if response.lower() == 'yes':
migrate_data()
else:
print("\n❌ Migration cancelled.")