DEV Community

Datta sai krishna Naidu
Datta sai krishna Naidu

Posted on

Multi-hop questions break vector search. Here is a graph layer for Qdrant that fixes them.

Qdrant answers "which vectors are near this query?" in milliseconds at
billion scale. But there's a class of questions where nearness is the
wrong criterion entirely:

"Where did the founder of the company that acquired Slack study?"

The passage that answers this talks about Marc Benioff and USC. It
never mentions Slack. Cosine similarity — any similarity — ranks it
low, because the answer doesn't look like the question. It's
connected to the question, three entity-hops away: Slack → acquired
by Salesforce → founded by Benioff → studied at USC. That's a topology
problem, and no amount of ANN speed solves a topology problem.

hubmesh is a small MIT
library that adds the topology layer on top of your existing Qdrant
collection. Qdrant keeps doing what it's best at (first-pass ANN);
hubmesh builds an entity–document graph at index time and, at query
time, diffuses Personalized PageRank from the question's entities over
that graph, fusing graph reachability with your cosine scores. No LLM
is involved at query time — retrieval is one sparse matrix iteration,
deterministic, roughly 100ms on a 30K-document corpus.

Setup

pip install "hubmesh[qdrant,kg]"
python -m spacy download en_core_web_sm
Enter fullscreen mode Exit fullscreen mode

Index: your Qdrant collection + an entity graph

from hubmesh import Planner
from hubmesh.adapters import QdrantStore
from hubmesh.kg import build_entity_kg
import spacy

embed = ...  # your embedding callable: text -> np.ndarray

# any of: in-memory, on-disk, or your running Qdrant server
store = QdrantStore.from_documents(docs, url="http://localhost:6333")

# entity-document graph via spaCy NER — zero LLM tokens to build
nlp = spacy.load("en_core_web_sm")
kg = build_entity_kg(store.get_many(store.all_ids()), nlp=nlp)

planner = Planner(store=store, kg=kg, nlp=nlp, embed=embed)
Enter fullscreen mode Exit fullscreen mode

Query: diffusion + similarity, fused

result = planner.retrieve(
    "Where did the founder of the company that acquired Slack study?",
    top_k=10,
)
for path in result.reasoning:
    print(f"{path.score:.3f}  " + " -> ".join(path.node_ids))
# 0.031  ent:slack -> doc:acquisition -> ent:salesforce -> doc:benioff_bio
Enter fullscreen mode Exit fullscreen mode

That reasoning field is not a post-hoc explanation — it's the actual
graph route that surfaced each document, which means your RAG pipeline
can show why a passage was retrieved.

What the scoring actually is

Each candidate document gets a composite of three signals, normalized
and combined (the formula descends from a network-topology paper —
NNSI, ICOMP'25 — where the same lesson appeared: no single centrality
metric identifies important nodes, but a weighted composite does):

  • Relevance — cosine against your Qdrant vectors (geometry)
  • Structure — Personalized PageRank mass diffused from the question's entities (topology)
  • Convergence — the geometric mean of per-entity diffusion, so a document must be reachable from every anchor in the question, not just flooded with score from one

Numbers (harness and raw JSONs in the repo)

Benchmark recall@10 vs naive cosine, same embeddings
HotpotQA full dev (7,405 q) 75.2% vs 69.3% (+5.9 pts)
MuSiQue 2/3/4-hop +6.0 / +3.2 / +5.0 pts
vs HippoRAG-style PPR-only, same graph +29.8 pts

Disclosed trade-off: the convergence term buys depth recall with
top-rank precision — recall@2 is 0.75 pts below naive on full dev. If
you retrieve with top_k=2, disable it (use_convergence=False).
Everything above reproduces with the scripts in benchmarks/.

Agents can steer it

retrieve accepts seed_entities and exclude_docs, so an agent can
iterate: retrieve, read, then aim hop two at the entity it just
discovered. There's an MCP server included (hubmesh-mcp, listed in
the official MCP Registry) — the repo contains a field report of
Perplexity driving a 3-hop chain through it, tool call by tool call.

When NOT to use this

Single-hop corpora where similarity already wins; corpus-wide summary
questions ("what are the main themes?") — that's community-summary
territory (Microsoft GraphRAG's use case), a different query class.
hubmesh is for multi-hop factual retrieval, and it deliberately keeps
Qdrant as the geometry engine underneath.

Repo: github.com/DemigodDSK/hubmesh · PyPI: pip install hubmesh · MIT

Top comments (0)