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

QuestionWhy vectors failGraph approach
"Which services transitively depend on ledger-db?"Similarity ≠ dependencyPath traversal, depth N
"Who owns the on-call for this endpoint?"Ownership is a fact, not a vibeOne-hop edge lookup
"Show me only docs my team may see"Embeddings have no ACLsFilter by permission edges

Building One From Your Own Data

  1. Pick one domain — ownership or dependencies, never "everything".
  2. Extract with an LLM, verify with code — constrain output to a schema, reject triples that reference unknown entities.
  3. Attach provenance to every edge — source document, extraction date, confidence.
  4. 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.