{"slug": "show-hn-hubmesh-multi-hop-rag-retrieval-with-zero-llm-calls-in-the-query-path", "title": "Show HN: Hubmesh – Multi-hop RAG retrieval with zero LLM calls in the query path", "summary": "Hubmesh, a new Python library, improves multi-hop RAG retrieval by adding a centrality-aware planner over existing vector databases, eliminating LLM calls in the query path. The library uses multi-component seed selection and budget-aware context packing to enhance retrieval quality, as demonstrated in its Show HN launch.", "body_md": "**Centrality-aware GraphRAG retrieval planner. Drop-in layer over any vector DB.**\n\n`hubmesh`\n\nis a Python library that improves multi-hop RAG quality on top of an existing\nvector database. You don't replace your infrastructure — you add a smart planner between\nyour vector DB and your LLM.\n\nNaive vector retrieval (\"embed query, get top-k by cosine similarity\") fails on multi-hop\nquestions like *\"Where was the founder of the company that acquired Slack born?\"* The\ncorrect answer requires retrieving entities along a reasoning path, not the single most\nsimilar item.\n\nGraphRAG and HippoRAG showed that running a small Personalized PageRank over a knowledge\ngraph at query time can substantially improve multi-hop retrieval. `hubmesh`\n\nextends\nthat line with two contributions:\n\n**Multi-component seed selection.** Instead of picking PPR seeds by raw query similarity (which picks wrong-community seeds at high feature overlap), seeds are chosen by a multi-component score combining query relevance, structural fit, and coverage diversity.**Budget-aware context packing.** Once relevant entities are scored, pack them into the LLM's context window with explicit coverage and redundancy control rather than just truncating top-k.\n\nThe multi-component scoring pattern is adapted from the NNSI framework (Naidu Dsk, ICOMP'25 — to appear) for SDN topology optimization, repurposed here for retrieval planning.\n\n``` python\nfrom hubmesh import Planner\nfrom hubmesh.adapters import InMemoryStore\n\nembed = ...   # callable: text -> np.ndarray\ndocs = [...]  # list of Document or strings or dicts\n\nstore = InMemoryStore.from_documents(docs, embed=embed)\nplanner = Planner(store=store, embed=embed)\nresult = planner.retrieve(query=\"...\", top_k=10, budget_tokens=4000)\npython\nfrom hubmesh import Planner\nfrom hubmesh.adapters import QdrantStore\n\nstore = QdrantStore.from_documents(docs)                          # in-memory\nstore = QdrantStore.from_documents(docs, path=\"./qdrant_data\")    # on-disk\nstore = QdrantStore.from_documents(docs, url=\"http://localhost:6333\")  # remote\n\nplanner = Planner(store=store, embed=embed)\nresult = planner.retrieve(query=\"...\", top_k=10)\npython\nfrom hubmesh.adapters import ChromaStore\n\nstore = ChromaStore.from_documents(docs)                          # ephemeral\nstore = ChromaStore.from_documents(docs, persist_directory=\"./chroma_data\")\nstore = ChromaStore.from_documents(docs, host=\"localhost\", port=8000)\npython\nfrom hubmesh.kg import build_entity_kg\nimport spacy\n\nnlp = spacy.load(\"en_core_web_sm\")\nkg = build_entity_kg(docs, nlp=nlp)\n\nplanner = Planner(store=store, kg=kg, nlp=nlp)\nresult = planner.retrieve(query=\"Where was the founder of the company that bought Slack born?\",\n                          top_k=10, budget_tokens=4000)\n\n# RetrievalResult includes reasoning paths showing why each doc was returned\nfor path in result.reasoning:\n    print(f\"  score={path.score:.3f}  {' → '.join(path.node_ids)}\")\npython\nfrom hubmesh.kg_llm import build_entity_kg_llm\nfrom hubmesh.entity_linker import EmbeddingLinker, make_st_embedder\n\ndef llm(prompt):  # provider-agnostic — bring your own\n    return your_llm_call(prompt)\n\nkg = build_entity_kg_llm(docs, llm=llm, cache_path=\"kg_cache.json\")\n\n# optional: cross-document entity dedup — same Linker protocol as the spaCy path\nkg = build_entity_kg_llm(docs, llm=llm, cache_path=\"kg_cache.json\",\n                         linker=EmbeddingLinker(embed=make_st_embedder()))\n\nplanner = Planner(store=store, kg=kg)\npython\nfrom hubmesh.kg import build_entity_kg\nfrom hubmesh.entity_linker import EmbeddingLinker, make_st_embedder\n\n# Cluster surface variations: \"United States\" / \"U.S.\" / \"USA\" → one entity\nlinker = EmbeddingLinker(embed=make_st_embedder(), threshold=0.82)\nkg = build_entity_kg(docs, linker=linker)\nr1 = planner.retrieve(query=question, top_k=5)\n\n# your agent reads r1, spots the bridge entity, then aims hop 2 at it:\nr2 = planner.retrieve(\n    query=question, top_k=5,\n    seed_entities=[\"Nimbus Analytics\"],           # merged with the query's own seeds\n    exclude_docs=[s.doc.id for s in r1.sources],  # don't re-retrieve consumed docs\n)\n```\n\nSeed mentions resolve through the alias index, so free-text entity names work. The query path stays deterministic and LLM-free — the planning intelligence lives in the caller.\n\n```\npip install \"hubmesh[mcp]\"\npython -m spacy download en_core_web_sm\n{\"mcpServers\": {\"hubmesh\": {\"command\": \"hubmesh-mcp\"}}}\n```\n\nExposes the planner as deterministic operator tools over stdio —\n`index_corpus`\n\n, `retrieve`\n\n(seed-steerable, as above), `resolve_entities`\n\n,\n`entity_neighbors`\n\n, `path_between`\n\n, `get_document`\n\n, `graph_stats`\n\n,\n`list_corpora`\n\n. Your agent is the solver: it decomposes the question,\nreads each hop, and aims the next one; the server answers in\nmilliseconds with zero LLM calls. Corpora persist as plain JSON/NPZ\nunder `~/.hubmesh/corpora`\n\n.\n\nThe server warms up models and persisted corpora in the background at launch (~5-10s on first run), so tool calls stay fast from the start — relevant for strict-timeout connector clients (Perplexity, etc.).\n\nFor web-based connector clients, serve SSE natively — no gateway process needed:\n\n```\nhubmesh-mcp --transport sse --port 8000 --allow-tunnel\nngrok http 8000     # paste https://<your-url>/sse into the connector\n```\n\nTunnel field notes (from a live Perplexity integration): **ngrok works**\n(free tier included); **cloudflared quick tunnels buffer SSE bodies**\nand hang tool calls; **supergateway is unnecessary** here and crashes\non reconnect. `--allow-tunnel`\n\naccepts the tunnel's forwarded Host\nheader — without it, proxied requests get 421 Misdirected Request.\n\nFull field report — setup, error decoder, a 9/9 test battery run\nthrough Perplexity, and two findings about reasoning-model behaviour —\nin [docs/perplexity.md](/DemigodDSK/hubmesh/blob/main/docs/perplexity.md).\n\n``` python\nfrom hubmesh import chunk_by_sentences, chunk_documents\n\nchunks = chunk_documents(\n    [{\"id\": \"doc1\", \"text\": long_text}, ...],\n    strategy=\"sentences\", target_tokens=200,\n)\n# Then embed chunks and index normally\npip install hubmesh                   # core\npip install \"hubmesh[qdrant]\"         # Qdrant adapter\npip install \"hubmesh[chroma]\"         # Chroma adapter\npip install \"hubmesh[kg]\"             # entity-linked KG (spaCy)\npip install \"hubmesh[linker]\"         # embedding-based entity linker\npip install \"hubmesh[all]\"            # everything\npython -m spacy download en_core_web_sm   # required for KG mode\nquery → first-pass ANN  → induced subgraph → multi-component scoring\n                              ↓                        ↓\n                       community anchoring → Personalized PageRank\n                              ↓                        ↓\n                              └─────→ ranking → budget-aware packing → context\n```\n\nEach layer is independently testable and replaceable. Adapters wrap your existing vector DB so you don't have to migrate.\n\n**Headline:** on multi-hop QA, hubmesh's KG mode beats both naive cosine\nretrieval and a HippoRAG-style PPR-only ablation that uses the same KG,\nat every hop depth.\n\n| Benchmark | Setting | recall@10 vs naive |\n|---|---|---|\nHotpotQA dev, N=7405 (full) |\nKG mode | +5.90 pts |\n| HotpotQA dev, N=500 | KG mode | +5.0 pts |\n| MuSiQue dev, N=300, 2-hop | KG mode | +6.0 pts |\n| MuSiQue dev, N=300, 3-hop | KG mode | +3.2 pts |\n| MuSiQue dev, N=300, 4-hop | KG mode | +5.0 pts |\n\nAll rows measured with v0.4.0 defaults (alias-indexed seeds + NNSI-KG\nconvergence; ablation JSONs committed in `benchmarks/`\n\n). Disclosed:\nconvergence trades top-rank precision for depth recall — recall@2 is\n**−0.75 pts vs naive on full dev** (dips ≤0.5 at smaller n); if you\nretrieve with `top_k=2`\n\n, set `use_convergence=False`\n\n. Multi-seed\nqueries cost ~1.5–1.8× (still zero LLM tokens, deterministic).\n\nvs PPR-only ablation on the same KG: **+29.8 pts** on HotpotQA at N=500\n(measured on v0.2.0) — the multi-component scoring is doing the work,\nnot just \"having a graph.\"\n\nOn the full N=7405 HotpotQA dev: hubmesh hits **75.2% supporting-fact\nrecall@10** vs naive cosine's **69.3%** (+4.21 pts at recall@5;\nrecall@2 −0.75, disclosed above).\n\nLatency: **~22 ms** mean / 26 ms p95 per query on a 7K-node KG (after PPR\nmatrix caching); ~3 s/query at the 66K-paragraph full-dev scale with\nv0.4 convergence on.\n\nSee [BENCHMARKS.md](/DemigodDSK/hubmesh/blob/main/BENCHMARKS.md) for the full methodology, ablations,\nper-hop breakdown, and notes on what this proves and doesn't.\n\nReproduce:\n\n```\npython benchmarks/run_hotpotqa.py --n 500 --kg\npython benchmarks/run_musique.py  --n 300 --kg\npython benchmarks/profile_query.py        # latency profile\n```\n\nPre-alpha (v0.4.0). Core algorithms implemented and validated; adapters for\nin-memory, Qdrant, and Chroma; entity-linked KG with both spaCy NER and\nLLM-based extraction (both linker-aware); alias-indexed entity resolution;\nNNSI-KG scoring (multi-source convergence default-on, hub-discounted PPR\nopt-in); agent-driven iterative multi-hop via `seed_entities`\n\n/\n`exclude_docs`\n\n; MCP operator server (`hubmesh-mcp`\n\n, native SSE) with\nJSON/NPZ corpus persistence; document chunking; reasoning-path\nexplanation; PPR-cache latency optimisation. Pinecone / pgvector / Weaviate adapters\nand additional multi-hop benchmarks are tracked as\n[good first issues](https://github.com/DemigodDSK/hubmesh/issues).\n\nThe multi-component scoring pattern is adapted from the **Network Node Significance\nIndex (NNSI)** framework introduced in\nNaidu Dsk, \"A Framework for Improving Network Topology Based on Graph\nTheory in Software-Defined Networking\", 26th International Conference on\nInternet Computing & IoT (ICOMP'25), Las Vegas, July 2025 — proceedings\nto appear. Repurposed here from SDN topology optimization to retrieval\nplanning.\n\nMIT", "url": "https://wpnews.pro/news/show-hn-hubmesh-multi-hop-rag-retrieval-with-zero-llm-calls-in-the-query-path", "canonical_source": "https://github.com/DemigodDSK/hubmesh", "published_at": "2026-08-05 01:27:25+00:00", "updated_at": "2026-08-05 01:52:29.964977+00:00", "lang": "en", "topics": ["artificial-intelligence", "machine-learning", "large-language-models", "ai-tools", "ai-infrastructure"], "entities": ["Hubmesh", "GraphRAG", "HippoRAG", "NNSI", "Naidu Dsk", "Qdrant", "Chroma", "spaCy"], "alternates": {"html": "https://wpnews.pro/news/show-hn-hubmesh-multi-hop-rag-retrieval-with-zero-llm-calls-in-the-query-path", "markdown": "https://wpnews.pro/news/show-hn-hubmesh-multi-hop-rag-retrieval-with-zero-llm-calls-in-the-query-path.md", "text": "https://wpnews.pro/news/show-hn-hubmesh-multi-hop-rag-retrieval-with-zero-llm-calls-in-the-query-path.txt", "jsonld": "https://wpnews.pro/news/show-hn-hubmesh-multi-hop-rag-retrieval-with-zero-llm-calls-in-the-query-path.jsonld"}}