feat(tool_search): optional embedding reranker for progressive tool disclosure - #35457
feat(tool_search): optional embedding reranker for progressive tool disclosure#35457davidgut1982 wants to merge 1 commit into
Conversation
|
Per-Scope Cache Improvement Added Cherry-picked commit 09d86e6 (fix(tool_search): per-scope reranker cache) onto this PR. This adds critical multi-agent support:
New test coverage:
This is essential for orchestrator patterns where multiple concurrent agents with different MCP toolsets need to avoid thrashing the embedding endpoint. |
23c8dcd to
1a33591
Compare
6814273 to
fbcbad6
Compare
31b272c to
5ebf969
Compare
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the optional reranker work. The semantic-retrieval premise remains valid: current main's tools/tool_search.py:378-418 is BM25 plus a substring fallback only.
Problems
tools/tool_search.py:863-903is a one-entry global reranker cache. A second toolset scope replaces the first, so it does not provide the per-scope retention described in the PR discussion.RerankerConfig.top_kis parsed attools/tool_search.py:131and documented atwebsite/docs/user-guide/features/tool-search.md:221, but reranking uses only the calllimitattools/tool_search.py:804.- The added example places
api_keyinconfig.yaml(website/docs/user-guide/features/tool-search.md:224), while repository documentation requires credentials in.env.
Suggested changes
- Implement and test a bounded scope-keyed cache, including A → B → A reuse.
- Remove
top_kor implement and test its precedence. - Route endpoint credentials through the established secret path.
- Preserve current-main deferred-call validation from
37df7ff01671685dae4e2d7204180beda7747a02during salvage.
Automated hermes-sweeper review.
| # Lazily constructed on first use; None means reranker is disabled or not | ||
| # yet built. The reranker embeds tool texts; the cache lives here across | ||
| # search calls within the same process, invalidated when the catalog changes. | ||
| _reranker: Optional[EmbeddingReranker] = None |
There was a problem hiding this comment.
This is a single global cache slot, so a request for scope B replaces scope A's reranker and cache. That contradicts the PR discussion's claimed bounded per-scope cache; use a scope-keyed bounded map and add an A → B → A reuse test.
| # search_default_limit is already applied by dispatch_tool_search before | ||
| # calling search_catalog; returning more than limit here violates the | ||
| # search_catalog(limit=N) contract and over-returns to the model. | ||
| top_k = limit |
There was a problem hiding this comment.
RerankerConfig.top_k is parsed and documented but is not consulted here; this makes the documented setting a no-op. Please either remove it or define and test its precedence relative to the tool-call limit.
| top_k: 5 # results to return (should match search_default_limit) | ||
| query_prefix: "search_query: " # nomic task prefix for queries | ||
| doc_prefix: "search_document: " # nomic task prefix for tool docs | ||
| api_key: "" # optional bearer token |
There was a problem hiding this comment.
Please do not document bearer credentials in config.yaml. Hermes' documented convention is .env for API keys and tokens; route this through the established secret-resolution/setup path instead.
e8136eb to
ea64142
Compare
|
Reworked and rebased onto current main. Addressing all three points:
Also removed a couple of stray /tmp local-path references from the docs/comments. 64 tests pass, ruff clean. |
2a02f9b to
9ad68ad
Compare
…isclosure Adds an optional embedding-based reranker over the existing BM25 tool search, gated behind config (disabled by default). Reworked per maintainer review: - Scope-keyed bounded reranker cache. Replaces the previous single global reranker slot (which let a second toolset scope evict the first) with a bounded OrderedDict keyed on endpoint+model+mode+rrf_k+prefixes+tool-names (FIFO eviction, max 8). A -> B -> A reuses scope A's instance without rebuilding. Cache key uses a NUL separator so tool names containing commas cannot collide, and includes the behavior-affecting config fields so a config hot-reload cannot serve a stale-mode reranker. Fast-path read uses an atomic dict.get() (no in/getitem TOCTOU against concurrent eviction). - Removed the dead RerankerConfig.top_k field (reranking honors the caller's limit; top_k was parsed and documented but never consulted). - Endpoint credentials go through .env (HERMES_EMBED_API_KEY) per the repo convention rather than config.yaml; an explicit config value still overrides. api_key is excluded from the dataclass repr to avoid token leakage in logs. Tests: scope-keyed cache reuse/eviction (A->B->A, concurrent scopes, FIFO), config parsing, rerank invocation + fallback, RRF math, prefix validation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9ad68ad to
ef559b9
Compare
What
Adds optional embedding-based reranker for semantic tool discovery on top of BM25 lexical search. When enabled, all tool descriptions are embedded once per process using nomic-embed-text-v2-moe (MD5-cached), then per-query tool candidates are reranked by cosine similarity. Implements progressive tool disclosure: when a profile exceeds the activation threshold, the full catalog (~54k tokens) is deferred behind
tool_searchstubs (~2.3k tokens) and tools are fetched on demand. Two reranking modes: pure cosine or Reciprocal Rank Fusion (RRF k=10).Files modified:
tools/tool_search.py(reranker + progressive disclosure),tests/tools/test_tool_search.py(new tests),website/docs/user-guide/features/tool-search.md(updated).Why
BM25 lexical matching fails on semantic queries ("remind me tonight" vs "create_calendar_event"). Embedding reranker recovers those cases. Large tool catalogs consume 34-67% of a 131k context window. Progressive disclosure defers the catalog and reduces visible tools from 226 → 4, freeing 95.8% of tool-definition tokens.
Tests
53 tests pass: BM25 fallback, RRF exact-score, limit contract, dimension-mismatch, prefix payload, cache invalidation. Offline eval suite shows R@5 improvement from 0.634 (BM25) → 0.810 (with reranker).
Platforms tested
Linux (CT/LXC environment, Python 3.13)