Libby Louis

Rambling thoughts from a curious engineer

RAG: Relationship graph and traversal

I have recently been working on an AI-native search software based on a RAG framework.

The system treats each indexed JSON document as an entity with relationships that point at other entity items via unique IDs. Those links are normalized into a tenant-scoped graph: stored edges, reverse indexes for “who points at whom,” Redis caches for hot paths, and an admin-facing view that shows direct links as target → sources (every entity that references that target under a given relationship type).

With “out of the box” RAG search products, you type words, the system finds chunks that sound like those words, and you hope the right thing floats to the top. That works—until users stop asking “what is X?” and start asking “who offers X?”, “where can I get Y?”, or “how is A connected to B?”

Those questions are not really about vocabulary overlap. They are about structure: this person is tied to these places, this service is offered here but not there, this thing references that thing. If the index only knows text, you are guessing. If the index also knows relationships, you can answer in a way that matches how people actually think about the domain.


The problem

In domains like healthcare directories, professional services, or any catalog where entities (providers, locations, services, products) link to each other, two failures show up again and again:

  1. The right entity never appears because the user’s words match a related document more strongly than the one they care about. Semantic search is great at similarity; it is weaker when the “correct” answer is one hop away in meaning but one hop away in the graph.
  2. The system sounds confident but is wrong about which things go together—because nothing in the pipeline encodes “these IDs are explicitly linked in the source data.”

We did not want a one-off hack per industry. We wanted something that respects whatever shape of links the customer puts in their data—services, locations, “part of,” “offered at,” anything—without hard-coding a fixed ontology. The graph is the customer’s model of their world, reflected in the index.


The core idea: search should respect explicit relationships

When someone uploads structured content, they often already know: this provider operates at these locationsthis service is available through these channels. That is not implied by embeddings; it is stated. Throwing that away and relying only on whether two paragraphs feel similar wastes trustworthy signal.

So the theory is simple:

  • Treat each item as an entity with an identity.
  • Let them declare outbound relationships to other entities (by id and type).
  • Build a tenant-scoped view of who points to whom, under each relationship name the data uses.

That gives you two different wins:

For humans (and debugging) you can ask: “If I look up this location, who claims to be there?” That is the same question your users are implicitly asking when they say “near me” or “at that clinic.” Seeing it in an admin view is less about pretty diagrams and more about sanity: does the data actually say what we think it says?

For ranking you can use those links after you have done normal search: start from the things the query already surfaced, then walk one step along declared relationships to pull in or boost neighbors that the raw similarity list might have buried. You are not replacing semantic search; you are grounding it in explicit structure so “related” means “linked in the source,” not only “nearby in vector space.”


Why “target → who points here” matters

There is a subtle design choice that matches how people reason about directories.

A provider might list five locations. Five edges, same source, different targets. For search, you often care: “given this location (or this service), what hangs off it?” So organizing the mental model around the thing being pointed at—and listing everyone who points to it—matches questions like “who offers this?” or “what is tied to this hub?” It is the same information as arrows from provider to location, just pivoted to the question people usually ask about the target.


Why we do not draw the whole transitive graph

Real graphs get messy fast: service → provider → location → region → … If you always expanded every path, you would flood results with everything vaguely in the same network. The useful compromise is:

  • Store and show direct relationships so truth stays inspectable.
  • At query time, expand carefully—often a single hop, with rules that depend on what kind of question the system thinks the user asked—so you get the benefit of connectivity without turning every search into “everything connected to everything.”

The theory: transitive inference is for the moment of answering, not for one giant picture that tries to precompute all implications. Context and intent matter.


Why this is not “smarter embeddings”

Embeddings capture statistical similarity in language. Relationships capture commitments in data: “we assert this relationship.” Those are complementary. Relying only on embeddings for structured domains means you are constantly asking the model to rediscover structure that the customer already encoded. The graph is a way of saying: trust the customer’s graph where they gave you one; use vectors where language is fuzzy.


Take away

We built the relationship graph not to look clever on an architecture diagram, but because a lot of real search is relational in the human sense—who, where, what goes with what—and plain full-text or pure semantic retrieval underuses the strongest signal many tenants already have.

If you are building search over catalogs, marketplaces, or any graph-shaped domain, the question worth asking is not only “how good is our retriever?” but “are we leveraging explicit relationships?”

Leave a Reply