-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
Bug Summary
When using graphiti.add_triplet()
with FalkorDB, edges ARE created successfully but the source_node_uuid
and target_node_uuid
properties on the relationship are stored as None
instead of the actual node UUIDs.
Impact
- Severity: High
- Affected Provider: FalkorDB only (Neo4j works correctly)
- Version: graphiti-core 0.21.0
Consequences:
- Edge deduplication may fail (can't match edges by source/target UUIDs)
- Edge filtering by UUID doesn't work correctly
- Data integrity issues when validating edge relationships
- Inconsistent behavior compared to Neo4j provider
Root Cause
File: graphiti_core/models/edges/edge_db_queries.py
Function: get_entity_edge_save_bulk_query()
Lines: 129-133
Issue 1: MATCH vs MERGE (Fixed with this proposal for PR)
The original query used MATCH
to find source and target nodes, which would fail if nodes didn't exist. This needs to be changed to MERGE
(lines 129-130) to ensure nodes are created if they don't exist.
Change: MATCH
→ MERGE
for both source and target node lookups
Issue 2: Missing UUID Fields in SET Statement (Fixed with this proposal for PR)
The FalkorDB Cypher query explicitly lists properties in the SET statement but omits source_node_uuid
and target_node_uuid
:
case GraphProvider.FALKORDB:
return """
UNWIND $entity_edges AS edge
MERGE (source:Entity {uuid: edge.source_node_uuid})
MERGE (target:Entity {uuid: edge.target_node_uuid})
MERGE (source)-[r:RELATES_TO {uuid: edge.uuid}]->(target)
SET r = {
uuid: edge.uuid,
name: edge.name,
group_id: edge.group_id,
fact: edge.fact,
episodes: edge.episodes,
created_at: edge.created_at,
expired_at: edge.expired_at,
valid_at: edge.valid_at,
invalid_at: edge.invalid_at,
fact_embedding: vecf32(edge.fact_embedding)
}
WITH r, edge
RETURN edge.uuid AS uuid
"""
Why This is FalkorDB-Specific
Neo4j uses SET e = edge
which copies all properties from the edge dictionary:
case _: # Neo4j
return """
UNWIND $entity_edges AS edge
MATCH (source:Entity {uuid: edge.source_node_uuid})
MATCH (target:Entity {uuid: edge.target_node_uuid})
MERGE (source)-[e:RELATES_TO {uuid: edge.uuid}]->(target)
SET e = edge # ← Copies ALL properties
RETURN edge.uuid AS uuid
"""
FalkorDB requires explicit property listing, and the source_node_uuid
and target_node_uuid
fields were accidentally omitted.
Proposed Fix
Two changes needed in the FalkorDB case:
- Change MATCH to MERGE (lines 129-130) - Ensures nodes exist before creating relationships
- Add missing UUID fields (line 132) - Stores source and target UUIDs in edge properties
Complete fixed query:
case GraphProvider.FALKORDB:
return """
UNWIND $entity_edges AS edge
MERGE (source:Entity {uuid: edge.source_node_uuid})
MERGE (target:Entity {uuid: edge.target_node_uuid})
MERGE (source)-[r:RELATES_TO {uuid: edge.uuid}]->(target)
SET r = {
uuid: edge.uuid,
source_node_uuid: edge.source_node_uuid, # ADD THIS
target_node_uuid: edge.target_node_uuid, # ADD THIS
name: edge.name,
group_id: edge.group_id,
fact: edge.fact,
episodes: edge.episodes,
created_at: edge.created_at,
expired_at: edge.expired_at,
valid_at: edge.valid_at,
invalid_at: edge.invalid_at,
fact_embedding: vecf32(edge.fact_embedding)
}
WITH r, edge
RETURN edge.uuid AS uuid
"""
Test Case
Reproduction
from graphiti_core import Graphiti
from graphiti_core.driver.falkordb_driver import FalkorDriver
from graphiti_core.nodes import EntityNode
from graphiti_core.edges import EntityEdge
from datetime import datetime, timezone
driver = FalkorDriver(host='localhost', port=6379, database='test_db')
graphiti = Graphiti(graph_driver=driver)
# Add triplet
result = await graphiti.add_triplet(
source_node=EntityNode(
name="Person A",
group_id="test",
labels=["Entity", "Person"],
created_at=datetime.now(timezone.utc)
),
edge=EntityEdge(
name="knows",
source_node_uuid="",
target_node_uuid="",
group_id="test",
fact="Person A knows Person B",
fact_embedding=None,
episodes=[],
valid_at=datetime.now(timezone.utc),
invalid_at=None,
created_at=datetime.now(timezone.utc),
expired_at=None
),
target_node=EntityNode(
name="Person B",
group_id="test",
labels=["Entity", "Person"],
created_at=datetime.now(timezone.utc)
)
)
# Verify edge properties
query = """
MATCH (source:Entity)-[r:RELATES_TO]->(target:Entity)
WHERE r.group_id = 'test'
RETURN
source.uuid AS source_uuid,
target.uuid AS target_uuid,
r.source_node_uuid AS stored_source_uuid,
r.target_node_uuid AS stored_target_uuid
"""
result = await driver.execute_query(query)
# Result: stored_source_uuid and stored_target_uuid are None (BEFORE FIX)
# Result: stored_source_uuid and stored_target_uuid match source_uuid and target_uuid (AFTER FIX)
Expected Behavior (After Fix)
source_uuid: bf29de8d-a3e8-41d8-b078-d3e191c35457
target_uuid: 3794242a-1749-4567-b2e7-6cefb06d38b3
stored_source_uuid: bf29de8d-a3e8-41d8-b078-d3e191c35457
stored_target_uuid: 3794242a-1749-4567-b2e7-6cefb06d38b3
Actual Behavior (Before Fix)
source_uuid: bf29de8d-a3e8-41d8-b078-d3e191c35457
target_uuid: 3794242a-1749-4567-b2e7-6cefb06d38b3
stored_source_uuid: None
stored_target_uuid: None
Investigation Details
The UUIDs are correctly populated in the edge_data
dictionary before being passed to the query (verified in bulk_utils.py:207-208
):
edge_data: dict[str, Any] = {
'uuid': edge.uuid,
'source_node_uuid': edge.source_node_uuid, # Populated correctly
'target_node_uuid': edge.target_node_uuid, # Populated correctly
'name': edge.name,
'fact': edge.fact,
# ... other fields
}
The issue is purely in the Cypher query's SET statement, which doesn't include these fields in the FalkorDB case.
Additional Notes
Changes Made in This Fix
Before (Current Code):
- Lines 129-130: Uses
MATCH
which fails if nodes don't exist - Lines 132-133: SET statement missing
source_node_uuid
andtarget_node_uuid
After (Fixed Code):
- Lines 129-130: Changed to
MERGE
- creates nodes if they don't exist - Line 132: Added
source_node_uuid
andtarget_node_uuid
to SET statement
Why Both Changes Matter
-
MATCH → MERGE: Prevents query failure when nodes don't exist yet
- Original behavior: Query fails silently if nodes aren't found
- Fixed behavior: Nodes are created automatically, edge is always created
-
Missing UUID fields: Critical for edge metadata integrity
- Affects edge deduplication logic
- Breaks filtering edges by source/target UUID
- Causes data integrity validation issues
Testing
Both fixes have been tested locally with real-world data (fundraising knowledge graph) and verified working correctly. Edges now have proper UUID metadata and are created reliably.
Environment
- graphiti-core: 0.21.0
- FalkorDB: Latest (Docker container)
- Python: 3.11
- OS: macOS
Related Files
graphiti_core/models/edges/edge_db_queries.py
(lines 124-136)graphiti_core/utils/bulk_utils.py
(lines 206-209)
Priority: High - Affects data integrity for FalkorDB users
Complexity: Low - Simple 2-line addition to existing query
Testing: Verified fix works correctly with real-world test cases