Skip to content
Conduit

mcp server

hyset-poc

Clean-room PoC of HySet (arXiv:2607.25718): set-level tool retrieval for LLM agents via query-conditioned hyperedge prediction. PyTorch model + training, CLI, HTTP & MCP server, LangChain execution fe

cicorias/hyset-poccommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "hyset-poc": {
      "command": "uvx",
      "args": [
        "hyset"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 26, 2026

hyset — set-level tool retrieval for LLM agents

License: MIT Python 3.12 paper: arXiv:2607.25718

A runnable, clean-room proof of concept of HySet, from "Tools Are Not Islands: Set-Level Tool Retrieval for LLM Agents via Query-Conditioned Hyperedge Prediction" (Hong, Dong, Yu, Jiang, arXiv:2607.25718).

Independent reimplementation. It is not affiliated with the paper's authors, and their code was not used (see Clean room).

An agent with thousands of tools can't put them all in its prompt, so a retriever picks a few first. Most retrievers score each tool on its own and take the top-k. A travel request then comes back with three flight APIs and no currency converter. HySet makes the tool set the unit of retrieval: it scores whole candidate sets (hyperedges of a tool co-invocation hypergraph) and hands the single best set to an unmodified agent.

What's in the box:

  • Method: the HySet model, its training loop (InfoNCE over pools of negative sets, plus reward-weighted self-training), and exact two-stage inference (shortlist, then score all 21,699 subsets).
  • Data: a synthetic tool world with planted set structure (decoy tools, implicit sub-tasks, cardinality-dependent co-invocation), plus a ToolBench converter.
  • Execution feedback: via LangChain, with Claude (default) or a local Ollama model as agent and judge over mock tools. An offline sim reward covers demos and CI.
  • Interfaces: a hyset CLI, an HTTP service (FastAPI), and an MCP server, so agents can call it as a pre-selection step.

How it works

Each tool t has a learned unit vector z_t, initialized from a frozen text encoder's embedding of its description. A candidate set E with m tools is scored as

F(x, E) =  Σ_{a<b ∈ E} z_aᵀ M_m z_b       +   Σ_{k ∈ E} softmax_k(ℓ) · ℓ_k,     ℓ_k = r(x)ᵀ P z_k
           └ co-invocation prior, one      ┘   └ attention-pooled query–tool alignment ┘
             symmetric matrix per set size

The interaction matrix depends on the set's size m. That lets "weather + currency" be a strong pair inside a 4-tool trip plan and a weak pair on its own.

flowchart LR
    Q[query x] --> R["frozen encoder r(x)"]
    R --> S1["stage 1: score every tool<br/>top-K1 + complementary tools<br/>(K_pool = 20)"]
    S1 --> S2["stage 2: score all subsets<br/>of size 1..M (21,699 sets)"]
    S2 --> E["Ê = argmax F(x, E)"]
    E --> A["LLM agent sees only Ê"]

Training contrasts the annotated set with a pool of 63 negative sets. The pool mixes random, in-batch, near-miss and model-mined sets. Optionally, rewards from running an agent with the chosen set reinforce sets that actually work. The paper-to-code map, every deviation from the paper, and the training findings are in docs/DESIGN.md.

Quickstart

Requires mise. Everything else (Python 3.12, uv, the .venv) is managed from mise.toml.

mise install
mise run setup        # uv sync --all-extras -> .venv
mise run test         # ~30 s, offline
mise run demo         # synth data, train (bge-small, CPU), evaluate, retrieve

mise run demo downloads BAAI/bge-small-en-v1.5 (~130 MB) once and takes roughly 10-20 minutes on a 16-core CPU. For a quick no-download smoke run: uv run hyset demo --encoder hashing --epochs 2 --out runs/quick.

Results on the synthetic benchmark

Synthetic tool world (203 tools: 42 capabilities, 11 look-alike decoys, 150 fillers; 4,000 train / 400 val / 600 test queries, sets of 1-5 tools), frozen bge-small-en-v1.5, CPU, seed 0, early stopping on validation Recall@5. Percentages. set_* columns score the variable-size set Ê handed to the agent; exec_sim is the mean offline simulated-execution reward of the set handed over (rankers hand over their top-5).

methodRecall@5NDCG@5COMP@3COMP@5set exactset ⊇ GTset sizeexec_sim
BM25 (top-k)43.639.78.315.0–––17.8
frozen bge-small (top-k)68.365.331.342.8–––42.8
same HySet params, per-tool top-k (stage 1)91.191.856.774.2–––72.9
HySet (mined negatives)92.492.661.582.048.552.52.3564.7
HySet + sim execution feedback92.792.861.882.347.552.52.4264.7
HySet, paper's 50/30/20 negative mix81.183.345.256.221.546.03.5455.9
ablation: shared M (no cardinality)93.594.363.386.722.224.71.6941.4
ablation: M_m = I92.192.460.083.24.362.75.0059.5
ablation: w/o F_set (per-tool scorer)97.197.169.791.58.78.71.0023.1

On the paper's own motivating query ("…find round-trip flights, book a hotel near Shinjuku, check the weather…, convert my 2,000 USD budget into yen"), the frozen retriever's top-4 is CheapFlights Deals, SkyRoute Flight Search, VisaCheck, FXRates — a decoy flight API and no hotel or weather — while HySet returns exactly {SkyRoute Flight Search, StayFinder Hotels, WeatherNow Forecast, FXRates Currency Converter}.

What this does and does not show:

  • Reproduced: training the set scorer beats untrained retrieval by a wide margin, and the cardinality-specific interaction matrices are what make variable-size set prediction work: exact-set match 48.5 vs 22.2 (shared M), 4.3 (M = I) and 8.7 (no F_set); the alternatives pick sets that are systematically too small or too large.
  • Not reproduced: the paper's claim that F_set also improves ranking. Here a fine-tuned per-tool scorer (the w/o-F_set ablation) ranks best, though its predicted set is always a single tool. On the 57 test queries whose tool combination never appears in training, HySet's COMP@5 (17.5) trails its own per-tool ranking (38.6): the learned co-invocation prior over-trusts familiar combinations (e.g. it drops weather from "convert 100 USD to euros and check the weather in Paris", a pair that is rare as a 2-tool set). See docs/DESIGN.md §4 for why.
  • Small exact sets vs. a generous top-5 is unresolved. Handing the agent the per-tool top-5 scores higher on simulated execution (72.9) than HySet's predicted set (64.7): the top-5 covers more sub-tasks, and the simulator only charges 0.05 per extra tool. Whether HySet's smaller sets pay off depends on how much distractor tools really hurt an agent — the paper measures that with a live agent (pass rate); this PoC has not.
  • Execution feedback with the offline sim reward changes little here: it is nearly the same signal as the annotations. The paper reports its gains on agent pass rate.
  • The paper's own negative mix trains noticeably worse on this data than adding model-mined negatives (the default here).

Reproduce (about 10-20 min per run on a 16-core CPU; runs can go in parallel with OMP_NUM_THREADS=4):

uv run hyset synth data/synthetic
uv run hyset train data/synthetic runs/annot                       # HySet
uv run hyset train data/synthetic runs/demo --reward sim           # + execution feedback
uv run hyset train data/synthetic runs/paper-mix --neg-mix paper
uv run hyset train data/synthetic runs/shared-m --interaction shared   # also: identity, none
uv run hyset eval runs/annot

Using it

uv run hyset synth data/synthetic                    # or: hyset toolbench <ToolBench/data> data/toolbench
uv run hyset train data/synthetic runs/demo          # --neg-mix paper|mined, --interaction cardinality|shared|identity|none
uv run hyset eval runs/demo                          # HySet vs stage-1 vs frozen dense vs BM25
uv run hyset retrieve -r runs/demo "Find flights to Tokyo, book a hotel near Shinjuku and convert 2,000 USD to yen"

Python:

from hyset.run import Retriever

retriever = Retriever.load("runs/demo")
result = retriever.retrieve("convert my budget to yen and tell me what to pack for Tokyo")[0]
print([t.name for t in result.tools])      # the jointly scored tool set
print([t.id for t in result.ranking])      # set-aware ranking, for a fixed budget

HTTP

uv run hyset serve runs/demo --port 8000
curl -s localhost:8000/retrieve -H 'content-type: application/json' \
     -d '{"query": "find a Thai restaurant near the harbor and get directions"}'

Endpoints: POST /retrieve, POST /retrieve/batch, GET /tools, GET /tools/{id}, GET /health.

MCP

hyset mcp runs/demo serves select_tools(task, rank_k) and describe_tool(tool_id) over stdio (or --transport streamable-http). To register it with Claude Code:

claude mcp add hyset -- uv run --directory "$PWD" hyset mcp runs/demo

Execution feedback (the paper's L_self)

# offline, free: simulated execution (capability coverage + noise)
uv run hyset train data/synthetic runs/sim --reward sim

# Claude as agent + judge over mock tools (needs ANTHROPIC_API_KEY; costs tokens)
uv run hyset reward-probe data/synthetic --n 2                       # sanity check first
uv run hyset train data/synthetic runs/claude --reward llm --reward-mode judge \
    --max-reward-calls 500 --agent-model anthropic:claude-opus-5-5

# local model via Ollama (ollama serve; agent mode needs a tool-calling model)
uv run hyset train data/synthetic runs/ollama --reward llm \
    --agent-model ollama:qwen2.5:7b --max-reward-calls 500

--reward-mode judge makes one judge call per (query, set); agent runs a tool-calling rollout and then a judge call. --max-reward-calls, --n-sub, and --refresh-steps bound spend (the paper's rollout cap, N_sub, and T_ref).

Status and limitations

  • PoC on CPU: synthetic data by default; the ToolBench converter is tested only on a fixture in ToolBench's format.
  • The agent in the reward loop uses mock tools (no StableToolBench mirror), so the LLM reward measures "used the right capabilities", not real API success.
  • The Claude / Ollama reward path is covered by tests with LangChain fake models only; it has not yet been run against a live API (hyset reward-probe is the first thing to try).
  • Results are single-seed on synthetic data; the paper's ToolBench numbers are not reproduced here.
  • Not yet: UltraTool, zero/few-shot splits, ToolEval pass rate, significance tests, cardinality calibration. See docs/DESIGN.md §3.

Development

mise run lint      # ruff check + format check
mise run fmt       # auto-fix
mise run test      # fast suite, no downloads or network
mise run test:all  # also downloads the sentence-transformers model

Rules for contributors and coding agents are in AGENTS.md: always mise

  • uv + .venv, the code layout, testing conventions, and gotchas. CLAUDE.md imports it for Claude Code.
src/hyset/   model.py · inference.py · negatives.py · train.py · evaluate.py · metrics.py
             encoders.py · data.py · run.py · cli.py · baselines.py
             datasets/ (synthetic, toolbench) · reward/ (sim, llm, feedback) · serve/ (http, mcp)
tests/       pytest; brute-force checks of inference, Theorem 1, rewards, CLI/HTTP/MCP
docs/        DESIGN.md (put the paper PDF here locally; it is git-ignored)
examples/    preselect_then_act.py: HySet in front of a LangChain agent

Clean room

The authors released MIT-licensed code at stormwther18/HYSET. For this project, a separate agent read that repository and wrote a functional spec: interfaces, data formats, and default hyperparameters. The implementation here was written from the paper and that spec only. No code was copied or adapted.

Citation

If you use the method, cite the paper:

@article{hong2026hyset,
  title         = {Tools Are Not Islands: Set-Level Tool Retrieval for {LLM} Agents via
                   Query-Conditioned Hyperedge Prediction},
  author        = {Hong, Xinyi and Dong, Pinjun and Yu, Xinyang and Jiang, Binyan},
  journal       = {arXiv preprint arXiv:2607.25718},
  year          = {2026},
  eprint        = {2607.25718},
  archivePrefix = {arXiv},
  primaryClass  = {cs.LG}
}

License

MIT for the code in this repository. The paper belongs to its authors and is not redistributed here.

↑↓ navigate openesc close