Introduction
A knowledge graph is a database of things and how they connect: entities as nodes, relationships as edges. "Ada owns Payments-API", "Payments-API depends on Ledger-DB", "Ledger-DB runs in eu-west" — each fact is a triple:
(subject) —[predicate]—> (object)Embeddings answer "what is similar?" A knowledge graph answers "what is true, and how is it connected?" Production AI needs both.
The Mental Model
Think of it in three layers:
Documents (raw text)
↓ extraction
Triples (entity — relation — entity)
↓ storage + constraints
Queryable graph (paths, patterns, provenance)Unlike a vector index, the graph enforces structure: a service has exactly one owning team, a dependency path is traversable step by step, and every answer can cite the exact edges it used.
A Minimal Example
You don't need a graph database on day one. The model matters more than the store:
from dataclasses import dataclass
@dataclass(frozen=True)
class Edge:
subject: str
predicate: str
object: str
source: str # provenance: which doc/ticket this came from
updated: str # freshness: when it was last confirmed
graph = [
Edge("payments-api", "owned_by", "team-ledger", "service-catalog.yaml", "2026-09-01"),
Edge("payments-api", "depends_on", "ledger-db", "terraform/main.tf", "2026-08-28"),
Edge("ledger-db", "runs_in", "eu-west", "terraform/main.tf", "2026-08-28"),
]
def owners_of(service: str) -> list[str]:
return [e.object for e in graph
if e.subject == service and e.predicate == "owned_by"]Queries Vectors Can't Answer
| Question | Why vectors fail | Graph approach |
|---|---|---|
| "Which services transitively depend on ledger-db?" | Similarity ≠ dependency | Path traversal, depth N |
| "Who owns the on-call for this endpoint?" | Ownership is a fact, not a vibe | One-hop edge lookup |
| "Show me only docs my team may see" | Embeddings have no ACLs | Filter by permission edges |
Building One From Your Own Data
- Pick one domain — ownership or dependencies, never "everything".
- Extract with an LLM, verify with code — constrain output to a schema, reject triples that reference unknown entities.
- Attach provenance to every edge — source document, extraction date, confidence.
- Serve it beside vectors — retrieve from both, fuse, generate with citations.
def grounded_answer(question: str) -> str:
entities = extract_entities(question)
graph_facts = knowledge_graph.neighborhood(entities, depth=2)
vec_hits = vector_db.search(question, k=6)
return llm.generate(question, graph_facts + vec_hits,
require_citations=True)Key Takeaways
- Knowledge graphs store facts and connections; vectors store similarity. They complement each other.
- Model first, database second — a validated edge list beats an uncurated graph DB.
- Every edge needs provenance and a freshness date, or it isn't production data.
- Serve graph + vector retrieval together and require citations.