Knowledge Graph
The Knowledge Graph service provides semantic search and graph query capabilities. Use this for ingesting nodes, querying relationships, building product catalogs, or implementing semantic search.
Overview
The Knowledge Graph stores structured data with relationships and vector embeddings, enabling:
- Semantic Search: Find similar items using vector similarity
- Graph Queries: Traverse relationships between nodes
- Product Catalogs: Build searchable catalogs with rich relationships
Core Concepts
KG Node
A node in the knowledge graph:
interface KGNode {
id: string;
graphId: string;
label: string; // Node type (product, category, user)
name: string; // Display name
description?: string;
properties: Record<string, any>;
embedding?: number[]; // Vector embedding for semantic search
createdAt: string;
updatedAt: string;
}
KG Edge
A relationship between nodes:
interface KGEdge {
id: string;
graphId: string;
sourceId: string;
targetId: string;
label: string; // Relationship type (belongs_to, related_to)
properties?: Record<string, any>;
}
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/kg/ingest/nodes | Ingest nodes |
| POST | /api/v1/kg/ingest/edges | Ingest edges |
| POST | /api/v1/kg/query/search | Semantic search |
| POST | /api/v1/kg/query/traverse | Graph traversal |
| GET | /api/v1/kg/graphs | List graphs |
| GET | /api/v1/kg/graphs/{id}/labels | Get labels in graph |
Semantic Search
Search Request
interface SearchRequest {
graphId: string;
query: string; // Natural language query
labels?: string[]; // Filter by node labels
limit?: number; // Max results (default: 10)
threshold?: number; // Similarity threshold (0-1)
}
Frontend Usage
import { useKnowledgeGraph } from "@/hooks/use-knowledge-graph";
function ProductSearch() {
const { search, results, loading } = useKnowledgeGraph();
const handleSearch = async (query: string) => {
await search({
graphId: "products-graph",
query,
labels: ["product"],
limit: 20,
});
};
return (
<div>
<input
type="text"
onChange={(e) => handleSearch(e.target.value)}
placeholder="Search products..."
/>
{loading && <Spinner />}
{results.map((node) => (
<ProductCard key={node.id} product={node} />
))}
</div>
);
}
Ingesting Data
Ingest Nodes
const nodes: KGNodeInput[] = [
{
label: "product",
name: "iPhone 15",
description: "Latest Apple smartphone",
properties: {
price: 999,
category: "electronics",
brand: "Apple",
},
},
{
label: "category",
name: "Electronics",
properties: {
displayOrder: 1,
},
},
];
await ingestNodes({
graphId: "products-graph",
nodes,
});
Ingest Edges
const edges: KGEdgeInput[] = [
{
sourceId: "product-123",
targetId: "category-456",
label: "belongs_to",
},
{
sourceId: "product-123",
targetId: "product-789",
label: "related_to",
properties: {
similarity: 0.85,
},
},
];
await ingestEdges({
graphId: "products-graph",
edges,
});
Graph Traversal
Traverse Request
interface TraverseRequest {
graphId: string;
startNodeId: string;
direction: "outbound" | "inbound" | "any";
edgeLabels?: string[];
depth?: number;
limit?: number;
}
Example: Find Related Products
const related = await traverse({
graphId: "products-graph",
startNodeId: "product-123",
direction: "outbound",
edgeLabels: ["related_to"],
depth: 2,
limit: 10,
});
Backend Implementation
Search Handler
func (h *Handler) Search(w http.ResponseWriter, r *http.Request) {
orgID := r.Header.Get("X-Org-ID")
var req SearchRequest
json.NewDecoder(r.Body).Decode(&req)
// Generate embedding for query
embedding, err := h.embedder.Embed(r.Context(), req.Query)
if err != nil {
writeError(w, http.StatusInternalServerError, "EMBED_FAILED", err.Error())
return
}
// Search by vector similarity
results, err := h.service.VectorSearch(
r.Context(),
orgID,
req.GraphID,
embedding,
req.Labels,
req.Limit,
req.Threshold,
)
if err != nil {
writeError(w, http.StatusInternalServerError, "SEARCH_FAILED", err.Error())
return
}
writeJSON(w, http.StatusOK, SearchResponse{Nodes: results})
}
ClickHouse Storage
Knowledge graph data is stored in ClickHouse with per-org isolation:
-- Nodes table
CREATE TABLE org_{orgId}.kg_nodes (
id String,
graph_id String,
label String,
name String,
description String,
properties String, -- JSON
embedding Array(Float32),
created_at DateTime64,
updated_at DateTime64
) ENGINE = MergeTree()
ORDER BY (graph_id, label, id);
-- Vector search index
ALTER TABLE org_{orgId}.kg_nodes
ADD INDEX embedding_idx embedding TYPE annoy('L2Distance', 100);
Best Practices
- Use meaningful labels - Clear node/edge types improve queries
- Generate embeddings on ingest - Pre-compute for search performance
- Batch ingest operations - Ingest multiple nodes/edges at once
- Set appropriate thresholds - Balance precision vs recall
- Index frequently queried properties - Configure in ClickHouse
Next Steps
- Entities Management - Working with structured data
- AI Agents - Using KG with agents
- Workflow Engine - Automating graph operations