pypi hubmeshstdioMITupdated 28d ago
Centrality-aware GraphRAG retrieval planner. Drop-in layer over any vector DB.
What can you do with hubmesh?
hubmesh
Centrality-aware GraphRAG retrieval planner. Drop-in layer over any vector DB.
hubmesh is a Python library that improves multi-hop RAG quality on top of an existing
vector database. You don't replace your infrastructure ā you add a smart planner between
your vector DB and your LLM.
What problem this solves
Naive vector retrieval ("embed query, get top-k by cosine similarity") fails on multi-hop questions like "Where was the founder of the company that acquired Slack born?" The correct answer requires retrieving entities along a reasoning path, not the single most similar item.
GraphRAG and HippoRAG showed that running a small Personalized PageRank over a knowledge
graph at query time can substantially improve multi-hop retrieval. hubmesh extends
that line with two contributions:
- 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.
The 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.
Quickstart
In-memory (testing, small corpora)
from hubmesh import Planner
from hubmesh.adapters import InMemoryStore
embed = ... # callable: text -> np.ndarray
docs = [...] # list of Document or strings or dicts
store = InMemoryStore.from_documents(docs, embed=embed)
planner = Planner(store=store, embed=embed)
result = planner.retrieve(query="...", top_k=10, budget_tokens=4000)
Qdrant adapter (production)
from hubmesh import Planner
from hubmesh.adapters import QdrantStore
store = QdrantStore.from_documents(docs) # in-memory
store = QdrantStore.from_documents(docs, path="./qdrant_data") # on-disk
store = QdrantStore.from_documents(docs, url="http://localhost:6333") # remote
planner = Planner(store=store, embed=embed)
result = planner.retrieve(query="...", top_k=10)
Chroma adapter
from hubmesh.adapters import ChromaStore
store = ChromaStore.from_documents(docs) # ephemeral
store = ChromaStore.from_documents(docs, persist_directory="./chroma_data")
store = ChromaStore.from_documents(docs, host="localhost", port=8000)
Multi-hop / KG mode
from hubmesh.kg import build_entity_kg
import spacy
nlp = spacy.load("en_core_web_sm")
kg = build_entity_kg(docs, nlp=nlp)
planner = Planner(store=store, kg=kg, nlp=nlp)
result = planner.retrieve(query="Where was the founder of the company that bought Slack born?",
top_k=10, budget_tokens=4000)
# RetrievalResult includes reasoning paths showing why each doc was returned
for path in result.reasoning:
print(f" score={path.score:.3f} {' ā '.join(path.node_ids)}")
LLM-extracted KG (richer than spaCy)
from hubmesh.kg_llm import build_entity_kg_llm
from hubmesh.entity_linker import EmbeddingLinker, make_st_embedder
def llm(prompt): # provider-agnostic ā bring your own
return your_llm_call(prompt)
kg = build_entity_kg_llm(docs, llm=llm, cache_path="kg_cache.json")
# optional: cross-document entity dedup ā same Linker protocol as the spaCy path
kg = build_entity_kg_llm(docs, llm=llm, cache_path="kg_cache.json",
linker=EmbeddingLinker(embed=make_st_embedder()))
planner = Planner(store=store, kg=kg)
Better entity linking
from hubmesh.kg import build_entity_kg
from hubmesh.entity_linker import EmbeddingLinker, make_st_embedder
# Cluster surface variations: "United States" / "U.S." / "USA" ā one entity
linker = EmbeddingLinker(embed=make_st_embedder(), threshold=0.82)
kg = build_entity_kg(docs, linker=linker)
Iterative multi-hop: let your agent drive
r1 = planner.retrieve(query=question, top_k=5)
# your agent reads r1, spots the bridge entity, then aims hop 2 at it:
r2 = planner.retrieve(
query=question, top_k=5,
seed_entities=["Nimbus Analytics"], # merged with the query's own seeds
exclude_docs=[s.doc.id for s in r1.sources], # don't re-retrieve consumed docs
)
Seed 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.
MCP server: plug hubmesh into any agent
pip install "hubmesh[mcp]"
python -m spacy download en_core_web_sm
{"mcpServers": {"hubmesh": {"command": "hubmesh-mcp"}}}
Exposes the planner as deterministic operator tools over stdio ā
index_corpus, retrieve (seed-steerable, as above), resolve_entities,
entity_neighbors, path_between, get_document, graph_stats,
list_corpora. Your agent is the solver: it decomposes the question,
reads each hop, and aims the next one; the server answers in
milliseconds with zero LLM calls. Corpora persist as plain JSON/NPZ
under ~/.hubmesh/corpora.
The 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.).
For web-based connector clients, serve SSE natively ā no gateway process needed:
hubmesh-mcp --transport sse --port 8000 --allow-tunnel
ngrok http 8000 # paste https://<your-url>/sse into the connector
Tunnel field notes (from a live Perplexity integration): ngrok works
(free tier included); cloudflared quick tunnels buffer SSE bodies
and hang tool calls; supergateway is unnecessary here and crashes
on reconnect. --allow-tunnel accepts the tunnel's forwarded Host
header ā without it, proxied requests get 421 Misdirected Request.
Full field report ā setup, error decoder, a 9/9 test battery run through Perplexity, and two findings about reasoning-model behaviour ā in docs/perplexity.md.
Chunking long documents
from hubmesh import chunk_by_sentences, chunk_documents
chunks = chunk_documents(
[{"id": "doc1", "text": long_text}, ...],
strategy="sentences", target_tokens=200,
)
# Then embed chunks and index normally
Installation
pip install hubmesh # core
pip install "hubmesh[qdrant]" # Qdrant adapter
pip install "hubmesh[chroma]" # Chroma adapter
pip install "hubmesh[kg]" # entity-linked KG (spaCy)
pip install "hubmesh[linker]" # embedding-based entity linker
pip install "hubmesh[all]" # everything
python -m spacy download en_core_web_sm # required for KG mode
Design
query ā first-pass ANN ā induced subgraph ā multi-component scoring
ā ā
community anchoring ā Personalized PageRank
ā ā
āāāāāāā ranking ā budget-aware packing ā context
Each layer is independently testable and replaceable. Adapters wrap your existing vector DB so you don't have to migrate.
Benchmarks
Headline: on multi-hop QA, hubmesh's KG mode beats both naive cosine retrieval and a HippoRAG-style PPR-only ablation that uses the same KG, at every hop depth.
| Benchmark | Setting | recall@10 vs naive |
|---|---|---|
| HotpotQA dev, N=7405 (full) | KG mode | +5.90 pts |
| HotpotQA dev, N=500 | KG mode | +5.0 pts |
| MuSiQue dev, N=300, 2-hop | KG mode | +6.0 pts |
| MuSiQue dev, N=300, 3-hop | KG mode | +3.2 pts |
| MuSiQue dev, N=300, 4-hop | KG mode | +5.0 pts |
All rows measured with v0.4.0 defaults (alias-indexed seeds + NNSI-KG
convergence; ablation JSONs committed in benchmarks/). Disclosed:
convergence trades top-rank precision for depth recall ā recall@2 is
ā0.75 pts vs naive on full dev (dips ā¤0.5 at smaller n); if you
retrieve with top_k=2, set use_convergence=False. Multi-seed
queries cost ~1.5ā1.8Ć (still zero LLM tokens, deterministic).
vs PPR-only ablation on the same KG: +29.8 pts on HotpotQA at N=500 (measured on v0.2.0) ā the multi-component scoring is doing the work, not just "having a graph."
On the full N=7405 HotpotQA dev: hubmesh hits 75.2% supporting-fact recall@10 vs naive cosine's 69.3% (+4.21 pts at recall@5; recall@2 ā0.75, disclosed above).
Latency: ~22 ms mean / 26 ms p95 per query on a 7K-node KG (after PPR matrix caching); ~3 s/query at the 66K-paragraph full-dev scale with v0.4 convergence on.
See BENCHMARKS.md for the full methodology, ablations, per-hop breakdown, and notes on what this proves and doesn't.
Reproduce:
python benchmarks/run_hotpotqa.py --n 500 --kg
python benchmarks/run_musique.py --n 300 --kg
python benchmarks/profile_query.py # latency profile
Status
Pre-alpha (v0.4.0). Core algorithms implemented and validated; adapters for
in-memory, Qdrant, and Chroma; entity-linked KG with both spaCy NER and
LLM-based extraction (both linker-aware); alias-indexed entity resolution;
NNSI-KG scoring (multi-source convergence default-on, hub-discounted PPR
opt-in); agent-driven iterative multi-hop via seed_entities /
exclude_docs; MCP operator server (hubmesh-mcp, native SSE) with
JSON/NPZ corpus persistence; document chunking; reasoning-path
explanation; PPR-cache latency optimisation. Pinecone / pgvector / Weaviate adapters
and additional multi-hop benchmarks are tracked as
good first issues.
Acknowledgements
The multi-component scoring pattern is adapted from the Network Node Significance Index (NNSI) framework introduced in Naidu Dsk, "A Framework for Improving Network Topology Based on Graph Theory in Software-Defined Networking", 26th International Conference on Internet Computing & IoT (ICOMP'25), Las Vegas, July 2025 ā proceedings to appear. Repurposed here from SDN topology optimization to retrieval planning.
License
MIT
Install
Add hubmesh to your client. Pick the one you use.
claude mcp add hubmesh -- uvx hubmeshcodex mcp add hubmesh -- uvx hubmeshamp mcp add hubmesh -- uvx hubmesh{
"mcpServers": {
"hubmesh": {
"command": "uvx",
"args": [
"hubmesh"
]
}
}
}Add to `claude_desktop_config.json`, then restart Claude Desktop.
{
"mcpServers": {
"hubmesh": {
"command": "uvx",
"args": [
"hubmesh"
]
}
}
}Add to `~/.cursor/mcp.json`, or `.cursor/mcp.json` for a single project.
code --add-mcp '{"name":"hubmesh","command":"uvx","args":["hubmesh"]}'Or add the block manually to `.vscode/mcp.json` under `servers`.
{
"mcpServers": {
"hubmesh": {
"command": "uvx",
"args": [
"hubmesh"
]
}
}
}Add to `~/.codeium/windsurf/mcp_config.json`.
{
"mcpServers": {
"hubmesh": {
"command": "uvx",
"args": [
"hubmesh"
]
}
}
}Add to `cline_mcp_settings.json` via the MCP Servers panel.
{
"mcpServers": {
"hubmesh": {
"command": "uvx",
"args": [
"hubmesh"
]
}
}
}Add to `~/.gemini/settings.json`.
{
"mcpServers": {
"hubmesh": {
"type": "local",
"command": "uvx",
"args": [
"hubmesh"
],
"tools": [
"*"
]
}
}
}Add to `~/.copilot/mcp-config.json`, or run `/mcp add` inside the CLI.
{
"context_servers": {
"hubmesh": {
"command": {
"path": "uvx",
"args": [
"hubmesh"
]
}
}
}
}Add to your Zed `settings.json`.
uvx hubmeshRun `goose configure`, choose **Add Extension ā Command-line Extension**, and paste this command.
Score
39 / 100
Incomplete
- Documentation25/25
- Maintenance19/25
- Trust13/20
- Capability0/15
- Install experience12/15
- Documents what it does and how to connect
- Has a resolvable package or endpoint
- Exposes at least one tool, prompt or resource
- README has substantive content
- Includes a code example
- Documents its configuration
- Mentions credentials or security posture
- Last commit 20 days ago
- Has a release history
- Repository is not archived
- Licensed MIT
- Namespace verified in the official MCP registry
- Claimed by its owner
- Published under an organisation
- 0 tool(s) documented
- Provides prompt templates
- Provides resources
- 12 documented install method(s)
- Published to a package registry
- Offers a hosted endpoint ā no local install
Version history
| Versions | Published |
|---|---|
| 0.4.1Latest | Aug 5, 2026 |