GraphRAG for Microservices: Call Graphs Beat Flat Chunk Retrieval

GraphRAG for Microservices: Call Graphs Beat Flat Chunk Retrieval

Flat vector RAG treats every function like an island. In a microservice estate that is how you get answers that cite payments-api when the bug lives three hops away in ledger-writer. GraphRAG stitches call graphs, ownership, and deploy boundaries into retrieval so the model walks the hop chain humans already keep in their heads.

⚡ TL;DR: Index symbols as nodes and RPC/event edges as first-class retrieval signals. Expand top-k chunks along the call graph (depth 1–2) with ownership filters before generation. Prefer graph expansion over dumping more random neighbors. Pair with Hybrid Search for Code and Bedrock Retrieval Filters so lexical + vector + graph stay tenant-safe.

Model services as a typed graph

Store nodes for services, packages, and symbols; edges for calls, publishes, consumes, and owned_by.

// graph/types.ts
export type NodeKind = "service" | "package" | "symbol";
export type EdgeKind = "calls" | "publishes" | "consumes" | "owned_by";

export type GraphNode = {
  id: string;           // payments-api::InvoiceService.create
  kind: NodeKind;
  service: string;
  path: string;
  owner: string;        // CODEOWNERS team
  chunkId: string;      // vector store id
};

export type GraphEdge = {
  from: string;
  to: string;
  kind: EdgeKind;
  weight: number;       // call frequency / static confidence
};

Build edges from OpenAPI clients, EventBridge schemas, and static imports — not from README prose. Compare store choices in OpenSearch vs Aurora pgvector.

Retrieve, then expand along edges

# graphrag_retrieve.py — illustrative
from dataclasses import dataclass

@dataclass
class Hit:
    chunk_id: str
    score: float
    node_id: str

def retrieve_and_expand(query: str, k: int = 6, depth: int = 2) -> list[Hit]:
    seeds = vector_search(query, k=k)  # BM25+kNN hybrid
    frontier = {h.node_id for h in seeds}
    expanded = list(seeds)
    for _ in range(depth):
        nxt = set()
        for nid in frontier:
            for edge in outbound(nid, kinds=("calls", "publishes", "consumes")):
                if edge.weight < 0.3:
                    continue
                chunk = chunk_for(edge.to)
                expanded.append(Hit(chunk.id, edge.weight * 0.5, edge.to))
                nxt.add(edge.to)
        frontier = nxt
    # ✅ Dedupe by chunk_id, keep highest score
    return dedupe_keep_best(expanded)[:12]

❌ Expanding every import in node_modules or every transitive type-only edge. Cap depth and filter by service boundary.

Ownership metadata keeps answers actionable

When the graph returns ledger-writer, attach the CODEOWNERS team and on-call so the assistant can say who to ping — not just what file. Same discipline as RAG over ADRs.

# chunk metadata (ingest)
service: ledger-writer
symbol: PostingJournal.append
owner: team-ledger
oncall: pagerduty:ledger
path: src/journal/PostingJournal.ts

Fail closed on cross-domain hops

Product policy: agents may traverse calls inside a bounded context, but crossing payments → growth-experiments requires an explicit allow edge. Encode that as a graph ACL checked before expansion — mirrors tenant isolation filters.

Signal Use Anti-pattern
Static call edge Always expand depth 1 Blind full-repo BFS
Event publish/consume Expand with schema id Matching topic names only
Ownership Cite team in answer Orphan chunks with no owner
Deploy unit Prefer same service Mixing unrelated cell graphs

Closing checklist

✅ Dos
– ✅ Build edges from contracts (OpenAPI, events), not prose
– ✅ Hybrid seed retrieval, then depth-limited graph expand
– ✅ Stamp owner + service on every node
– ✅ ACL cross-domain hops
– ✅ Evaluate with multi-hop questions (“who writes the ledger after checkout?”)

❌ Don’ts
– ❌ Don’t treat graph edges as optional metadata the model might ignore
– ❌ Don’t expand into vendored dependencies
– ❌ Don’t return chunks without a path + service citation
– ❌ Don’t skip reindex when CODEOWNERS or OpenAPI change

Related reading

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply