Last Updated: June 18, 2026 Status: ✅ Production Ready Version: GraphRAG V2.5 (Enhanced with Phase 2 improvements)
🚀 Quick Start: This document provides a comprehensive overview of GraphRAG V2 features and usage
The Atom Knowledge Graph provides a semantic layer over your existing database records, enabling high-order reasoning, visual relationship management, and intelligent entity extraction. Unlike flat vector search, GraphRAG can traverse relationships to find indirect connections (e.g., "Find all formulas used in tasks assigned to the support team").
Based on 2026 GraphRAG research, the system now includes:
- Multi-Hop Expansion - Cue-driven activation for entity relationship traversal
- Dynamic Graph Construction - Incremental updates without full rebuilds
- Enhanced Community Detection - Improved clustering with Leiden algorithm
- Stateless Recursive CTEs: High-performance graph traversal using PostgreSQL without the need for a dedicated graph database
- Bidirectional Traversal: Efficient graph queries that traverse both incoming and outgoing relationships
- Local Search: Explores the immediate neighborhood of an entity (configurable depth)
- Global Search: Summarizes high-level themes across the entire graph using pre-computed communities
- Community Detection: Background worker using NetworkX + Leiden Algorithm for clustering
- 🆕 Multi-Hop Expansion: Advanced query expansion with configurable hop depth limits
- 🆕 Dynamic Graph Updates: Incremental graph construction with temporal evolution tracking
The system includes 6 built-in canonical entity types that map directly to database models:
| Type | Database Model | Search Field | Updatable Fields | Description |
|---|---|---|---|---|
user |
User |
email |
first_name, last_name, specialty |
User accounts with specialties |
workspace |
Workspace |
name |
description |
Workspace/organization |
team |
Team |
name |
description |
Teams within workspace |
task |
UserTask |
title |
description, status |
Tasks and assignments |
ticket |
SupportTicket |
subject |
status, priority |
Support tickets |
formula |
Formula |
name |
expression, description |
Business logic formulas |
Users can define their own entity types with:
- JSON Schema Validation: Ensure data integrity with custom schemas
- Dynamic Model Factory: Runtime model creation for custom types
- Tenant Isolation: Custom types are scoped per workspace/tenant
- Skill Integration: Associate custom entities with specific skills
- Schema Evolution: Versioning support for entity type definitions
The Entity Registry is a centralized configuration that defines:
- Database Mapping: Which SQL model to use for canonical entities
- Search Fields: Which fields to search when resolving entities
- Updatable Fields: Which properties can be synced back to the database
- Custom Types: Runtime-loaded entity type definitions
Canonical Registry (defined in graphrag_engine.py):
canonical_registry = {
"user": {
"model": User,
"search_field": "email",
"updatable_fields": ["first_name", "last_name", "specialty"]
},
"workspace": {
"model": Workspace,
"search_field": "name",
"updatable_fields": ["description"]
},
# ... (see table above)
}Updating a property on an anchored node automatically syncs changes back to the underlying database record:
- Graph → Database: Changes in the graph UI update database records
- Database → Graph: Database changes are reflected in the graph
- Field Whitelisting: Only explicitly allowed fields can be synced (security)
- Real-time Updates: Changes propagate immediately via automation triggers
The system uses LLMService to extract entities and relationships from unstructured text:
Standard Entity Types:
- Person, Organization, Location, Date/Time, Email, URL
- Document, Project, Task, Ticket, Formula
Custom Entity Types:
- User-defined types are loaded dynamically and included in extraction prompts
- LLM is prompted with custom type descriptions to improve recognition
- Supports any domain-specific entity type (e.g., "Invoice", "Contract", "Product")
Purpose: PostgreSQL-backed graph traversal and entity extraction
Key Methods:
local_search(workspace_id, query, depth=2)- Recursive CTE-based BFS traversalglobal_search(workspace_id, query)- Community-based summarizationingest_document(workspace_id, doc_id, text, source)- Extract entities from textadd_entity(entity, workspace_id)- Insert/update graph nodeadd_relationship(rel, workspace_id)- Insert graph edgecanonical_search(workspace_id, entity_type, query)- Search canonical recordsget_context_for_ai(workspace_id, query)- Format context for AI prompts
Performance:
- Recursive CTE queries execute in <100ms for depth-2 traversal
- Bidirectional edge traversal minimizes database round-trips
- Stateless design allows horizontal scaling
Purpose: CRUD operations for dynamic entity type definitions
Key Methods:
create_entity_type(tenant_id, slug, display_name, json_schema, ...)- Create new typeget_entity_type(tenant_id, entity_type_id, slug)- Retrieve type definitionupdate_entity_type(entity_type_id, json_schema, ...)- Update schemalist_entity_types(tenant_id, is_active)- List all typesdelete_entity_type(entity_type_id)- Soft delete
Schema Validation:
- Uses
SchemaValidatorto ensure valid JSON Schema - Prevents invalid schemas from being created
- Provides clear error messages for validation failures
Purpose: Dynamic SQLAlchemy model creation for custom entity types
Key Methods:
create_model(entity_type_def)- Generate SQLAlchemy model from schemaget_model(tenant_id, slug)- Retrieve cached modelinvalidate_cache(tenant_id, slug)- Force model reload
Capabilities:
- Runtime model generation from JSON Schema
- Automatic table creation with proper constraints
- Index generation for search fields
- Model caching for performance
Purpose: LLM-based entity and relationship extraction from text
Key Methods:
extract_entities(text, custom_types)- Extract entities using LLMextract_relationships(text, entities)- Extract relationshipspattern_extraction_fallback(text)- Regex-based fallback
Features:
- Supports both LLM-based and pattern-based extraction
- Automatically detects custom entity types
- Handles unstructured text (documents, emails, messages)
CREATE TABLE graph_nodes (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(100) NOT NULL,
description TEXT,
properties JSONB,
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_workspace_type (workspace_id, type),
INDEX idx_name_trgm (name USING gin)
);CREATE TABLE graph_edges (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL,
source_node_id UUID NOT NULL,
target_node_id UUID NOT NULL,
relationship_type VARCHAR(100) NOT NULL,
properties JSONB,
created_at TIMESTAMP DEFAULT NOW(),
FOREIGN KEY (source_node_id) REFERENCES graph_nodes(id) ON DELETE CASCADE,
FOREIGN KEY (target_node_id) REFERENCES graph_nodes(id) ON DELETE CASCADE,
INDEX idx_source_target (source_node_id, target_node_id),
INDEX idx_rel_type (relationship_type)
);CREATE TABLE graph_communities (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL,
level INTEGER NOT NULL,
summary TEXT,
keywords VARCHAR(255)[],
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_workspace_level (workspace_id, level)
);CREATE TABLE community_membership (
id UUID PRIMARY KEY,
community_id UUID NOT NULL,
node_id UUID NOT NULL,
FOREIGN KEY (community_id) REFERENCES graph_communities(id) ON DELETE CASCADE,
FOREIGN KEY (node_id) REFERENCES graph_nodes(id) ON DELETE CASCADE,
UNIQUE (community_id, node_id)
);CREATE TABLE entity_type_definitions (
id UUID PRIMARY KEY,
tenant_id VARCHAR(255) NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL,
display_name VARCHAR(255) NOT NULL,
description TEXT,
json_schema JSONB NOT NULL,
available_skills VARCHAR(255)[],
is_system BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
version INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
INDEX idx_tenant_slug (tenant_id, slug)
);from core.graphrag_engine import graphrag_engine
# Find entities related to "John Doe" up to 2 hops away
result = graphrag_engine.local_search(
workspace_id="default",
query="John Doe",
depth=2
)
# Returns:
# {
# "mode": "local",
# "start_entities": ["John Doe"],
# "entities": [
# {"id": "...", "name": "John Doe", "type": "user", "description": "..."},
# {"id": "...", "name": "Project Alpha", "type": "project", "description": "..."},
# {"id": "...", "name": "Task #123", "type": "task", "description": "..."}
# ],
# "relationships": [
# {"from": "...", "to": "...", "type": "assigned_to"},
# {"from": "...", "to": "...", "type": "related_to"}
# ],
# "count": 15
# }# Get high-level themes across the entire graph
result = graphrag_engine.global_search(
workspace_id="default",
query="overview of projects"
)
# Returns:
# {
# "mode": "global",
# "summaries": [
# "Community 1: 5 projects focused on Q1 deliverables...",
# "Community 2: 3 support tickets related to billing..."
# ],
# "answer": "Community 1: ... | Community 2: ..."
# }from core.entity_type_service import EntityTypeService
service = EntityTypeService()
# Define a custom "Invoice" entity type
invoice_type = service.create_entity_type(
tenant_id="acme-corp",
slug="invoice",
display_name="Invoice",
description="Customer invoices for billing",
json_schema={
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"amount": {"type": "number"},
"due_date": {"type": "string", "format": "date"},
"customer_name": {"type": "string"},
"status": {"type": "string", "enum": ["pending", "paid", "overdue"]}
},
"required": ["invoice_number", "amount", "customer_name"]
},
available_skills=["invoice_processing", "payment_reminder"]
)
# The LLM will now recognize "Invoice" entities in documentsimport asyncio
# Extract entities and relationships from a document
text = """
John Doe assigned Task #456 to Jane Smith.
The task is related to Project Alpha and requires Formula:CalculateRevenue.
"""
result = await graphrag_engine.ingest_document(
workspace_id="default",
doc_id="doc-123",
text=text,
source="email"
)
# Returns: {"entities": 4, "relationships": 3}
# Entities: John Doe (Person), Jane Smith (Person), Task #456 (Task),
# Project Alpha (Project), Formula:CalculateRevenue (Formula)
# Relationships: John Doe -> assigned_to -> Task #456, etc.# Anchor a graph node to a User record
graphrag_engine.add_entity(
Entity(
id="node-123",
name="john@example.com",
entity_type="user",
description="Software Engineer",
properties={
"canonical_type": "user", # Anchors to User model
"specialty": "Backend Development" # Syncs to User.specialty
}
),
workspace_id="default"
)
# The graph node is now linked to the User record
# Changes to properties["specialty"] will sync to User.specialtyAs-built (July 2026): All three enhancements are wired into the production
GraphRAGEngine, not standalone modules. Multi-hop expansion runs automatically insidelocal_search; community detection is invoked viabuild_communities.
Multi-hop scored expansion runs automatically inside GraphRAGEngine.local_search
after the seed-node BFS, producing scored, prioritized multi-hop paths via
SQLMultiHopExpander. The response now includes a multi_hop_paths field with
relevance-scored traversal paths — no separate instantiation needed.
# Multi-hop expansion is automatic — just call local_search:
from core.graphrag_engine import graphrag_engine
result = graphrag_engine.local_search(
workspace_id="ws_123",
query="Find all formulas used in tasks assigned to support team",
depth=3, # max hop depth
)
# result["multi_hop_paths"] now contains scored paths with:
# - relationship-type prioritization
# - per-hop relevance scoring with decay
# - confidence propagation along pathsFeatures (wired into the live /api/graphrag/query route):
- Automatic — no separate API call; multi-hop results are part of every
local_searchresponse - Cue-driven activation — relationship/entity-type priorities guide expansion
- Per-hop relevance scoring with decay and confidence propagation
- Non-disruptive — base
entities/relationshipsfrom BFS are preserved; multi-hop augments via themulti_hop_pathsfield
DynamicGraphManager (core/graphrag/dynamic_graph.py) provides incremental
graph updates, versioning, and temporal tracking. It is available as a library
module but is not yet wired into the engine's write path (the engine writes
synchronously to Postgres; the dynamic manager would batch writes for
performance). This is a future optimization, not a user-facing gap.
Community detection is invoked via GraphRAGEngine.build_communities(workspace_id),
which delegates to CommunityDetectionService.detect_communities(store_results=True),
populating the graph_communities and community_membership tables. This makes
the global_search mode return community-synthesized answers.
# Community detection is invoked via the engine (also reachable via the API):
from core.graphrag_engine import graphrag_engine
result = graphrag_engine.build_communities(workspace_id="ws_123")
# {"success": True, "communities": 12, "workspace_id": "ws_123"}Uses the Leiden algorithm when leidenalg is installed (added to
requirements.txt), with a NetworkX Louvain fallback for environments
without it.
Performance Targets (not yet measured — see VALIDATION_METRICS.md):
- Multi-hop query accuracy: >85% (target)
- Graph construction time: <30s for 1000 entities (target)
- Community detection quality (NMI): >0.7 (target)
- Incremental update latency: <5s per 100 nodes (target)
POST /api/v1/entity-types
Content-Type: application/json
{
"slug": "contract",
"display_name": "Contract",
"description": "Legal contracts",
"json_schema": {
"type": "object",
"properties": {
"contract_number": {"type": "string"},
"party_a": {"type": "string"},
"party_b": {"type": "string"}
}
},
"available_skills": ["contract_analysis"]
}GET /api/v1/entity-types?is_active=trueGET /api/v1/entity-types/{entity_type_id}PUT /api/v1/entity-types/{entity_type_id}
Content-Type: application/json
{
"json_schema": {...},
"description": "Updated description"
}DELETE /api/v1/entity-types/{entity_type_id}POST /api/v1/graph/search/local
Content-Type: application/json
{
"query": "John Doe",
"depth": 2
}POST /api/v1/graph/search/global
Content-Type: application/json
{
"query": "project overview"
}POST /api/v1/graph/ingest
Content-Type: application/json
{
"doc_id": "doc-123",
"text": "Document text here...",
"source": "email"
}POST /api/v1/graph/entities
Content-Type: application/json
{
"name": "John Doe",
"type": "user",
"description": "Software Engineer",
"properties": {
"canonical_type": "user",
"email": "john@example.com"
}
}POST /api/v1/graph/context
Content-Type: application/json
{
"query": "What tasks are assigned to John?"
}| Metric | Target | Current |
|---|---|---|
| Local search (depth-2) | <100ms | ~50-80ms |
| Global search | <200ms | ~100-150ms |
| Entity extraction (LLM) | <5s | ~2-3s |
| Entity extraction (pattern) | <500ms | ~200-300ms |
| Document ingestion | <10s | ~3-5s |
| Graph visualization load | <2s | ~1-1.5s |
Only explicitly allowed fields can be synced from graph to database:
"updatable_fields": ["first_name", "last_name", "specialty"] # User model- Prevents unauthorized modifications to sensitive fields
- Ensures data integrity by restricting write access
- Auditable via property change tracking
- All graph queries scoped to
workspace_id - Custom entity types isolated per tenant
- No cross-tenant data leakage
- JSON Schema validation prevents invalid entity types
- Type checking ensures data consistency
- Clear error messages for debugging
- D3.js Force-Directed Layout: Interactive graph exploration
- Node Anchoring UI: Search and link to database records
- Real-time Updates: WebSocket-based live updates
- Filtering & Search: Filter by type, search by name
- Context Menu: Inspect entities, view relationships
- Type Editor: Create/edit custom entity types
- Schema Builder: Visual JSON Schema editor
- Preview: Test extraction with sample text
- Import/Export: Share entity types across workspaces
- Trigger: Manual enqueue or scheduled
- Purpose: Recalculate communities and update embeddings
- Algorithm: NetworkX Leiden for community detection
- Queue: Redis-based job queue (UPSTASH_REDIS_URL)
# Triggered on entity upsert
orchestrator.trigger_event("graph_entity_upsert", {
"entity_type": "user",
"entity_id": "...",
"name": "John Doe",
"is_new": True,
"tenant_id": "default"
})# GraphRAG LLM Configuration
GRAPHRAG_LLM_ENABLED=true
GRAPHRAG_LLM_PROVIDER=openai
GRAPHRAG_LLM_MODEL=gpt-4o-mini
# Redis for Job Queue
UPSTASH_REDIS_URL=redis://...
REDIS_URL=redis://...
# Workspace (Multi-tenant)
DEFAULT_WORKSPACE_ID=default- In-Memory → PostgreSQL: No more in-memory graph storage
- NetworkX → Recursive CTEs: Stateless SQL-based traversal
- Static → Dynamic Types: Custom entity types now supported
- Manual → Auto-Sync: Bidirectional sync with database
- Run migration script to create graph tables
- Export existing graph data (if any)
- Import into PostgreSQL graph tables
- Update API calls to use new endpoints
- Verify custom entity type definitions
Solution:
- Verify entity type is
is_active=True - Check
json_schemais valid - Ensure LLM is configured (
GRAPHRAG_LLM_ENABLED=true)
Solution:
- Check
updatable_fieldsincludes the field - Verify
canonical_typematches registry key - Ensure database model has the field
Solution:
- Verify workspace_id matches
- Check query string is not empty
- Ensure graph has entities (check
graph_nodestable)
Solution:
- Run community detection worker
- Verify
graph_communitiestable has data - Check workspace_id has communities
- Graph Visual Query Builder: Drag-and-drop query construction
- Relationship Types Schema: Validate relationship types
- Entity Versioning: Track entity changes over time
- Graph Analytics: Centrality, pathfinding, clustering coefficients
- Multi-Modal Entities: Combine text, image, and audio entities
- Real-time Collaboration: Multi-user graph editing
- GraphRAG Engine:
backend/core/graphrag_engine.py - Entity Type Service:
backend/core/entity_type_service.py - Model Factory:
backend/core/model_factory.py - API Routes:
backend/api/entity_type_routes.py,backend/api/graphrag_routes.py - Frontend:
frontend-nextjs/src/app/graph/page.tsx - Visualization:
frontend-nextjs/src/components/Graph/GraphVisualization.tsx
Last Updated: March 24, 2026