classifiers.refresh_tool_embeddings module
Refresh embeddings for tools whose descriptions have changed.
Compares each registered tool’s live description against the stored
metadata in Redis. Tools with mismatched descriptions are “stale”.
Tools whose tool_index_data.json entry is missing or does not have
the expected number of non-empty string synthetic_queries are “invalid”.
Both sets are refreshed (union) unless --invalid-only is passed.
Redis: This script does not delete or flush the embedding hash. It only HSETs keys for tools that successfully produced an embedding. If query generation or embedding fails for every target, Redis is left unchanged.
Index file: If tool_index_data.json exists but fails to parse, this
module used to load {} and could overwrite the file with a partial index.
That is now blocked (see load guard below).
Run python -m classifiers.refresh_tool_embeddings (see --help) with
optional arguments:
--force— Re-embed every tool regardless of whether its description changed (useful after switching embedding models).--tools— Comma-separated list of specific tool names to refresh.--tools-dir— Tool scripts directory (default:tools).--embed-only— Skip Gemini query generation; recompute embeddings from existingtool_index_data.jsononly (use afterbuild_tool_index).--invalid-only— Only tools with invalid/missing index queries (skip stale-description check). Ignored with--forceor--embed-only.
Synthetic query generation uses exponential backoff, rotates Gemini models
(gemini-2.5-flash, gemini-3-flash-preview after the default), then
OpenRouter google/<primary> if all Gemini calls fail (key:
OPENROUTER_QUERY_GEN_API_KEY or OPENROUTER_API_KEY; 429 backoff).
See classifiers/build_tool_index.generate_synthetic_queries and
GEMINI_QUERY_GEN_* / GEMINI_QUERY_GEN_429_* env vars.
- async classifiers.refresh_tool_embeddings.find_stale_tools(registered, redis_client)[source]
Return names of tools whose live descriptions differ from Redis.
Detects “stale” tools: those whose currently registered
descriptionno longer matches the description captured the last time the tool was embedded. This is the cheap change-detection step that lets a refresh re-embed only the tools that actually changed instead of every tool.Reads the stored metadata once via
HGETALLon theTOOL_METADATA_HASH_KEYRedis hash, JSON-decodes each entry, and compares itsdescriptionfield against the livedescriptionattribute on the registered tool definition. Tools absent from the hash are skipped (nothing to compare against); tools whose stored metadata is unparseable JSON are treated as stale so they get rebuilt. This function only reads from Redis and does not mutate it.Called by
refresh_tool_embeddings()(twice: the--embed-onlyand the default stale/invalid paths) and by the standaloneclassifiers.update_changed_tool_embeddingsscript.- Parameters:
- Return type:
- Returns:
The list of tool names whose live description differs from (or has no valid stored counterpart for) the value in Redis.
- async classifiers.refresh_tool_embeddings.refresh_tool_embeddings(*, force=False, embed_only=False, invalid_only=False, tool_names=None, tools_dir='tools')[source]
Re-embed the tools that changed and write their vectors back to Redis.
The end-to-end refresh routine for the vector tool classifier. It decides which tools to refresh, (re)generates their synthetic queries via the LLM unless
embed_onlyis set, recomputes centroid embeddings for them, and stores those vectors plus metadata in Redis so the classifier can route to the changed tools. The default mode is incremental (only stale or invalid tools); flags widen or narrow that target set.This orchestrates several collaborators:
discover_toolsto enumerate registered tools,find_stale_tools()anddiscover_invalid_query_index_toolsto pick targets,load_index_file/save_index_filefortool_index_data.json(backed up to a.bakcopy before write),generate_synthetic_queriesto call GeminigenerateContent(with OpenRouter fallback) for query text, theOpenRouterEmbeddingsclient pluscompute_tool_centroids_bulk()to turn those queries into one vector per tool, andstore_tool_embedding_hashto index each vector. It touches three Redis structures: it readsTOOL_METADATA_HASH_KEY(viafind_stale_tools()) andTOOL_EMBEDDINGS_HASH_KEY(for the force path), then HSETs both hashes plus the per-tool RediSearch index entries. Redis is left untouched when no embedding is produced. It also readsConfig/ env vars for the API key and Redis URL, opens its ownhttpx.AsyncClientfor query generation, bounds generation concurrency with a semaphore of 3, and always closes the Redis client in afinallyblock.Called by the module’s
main()(CLI entry point) and invoked programmatically by thewrite_python_toolandimport_mcp_tooltools after a new tool is created, so the classifier learns the new tool.- Parameters:
force (
bool) – Re-embed every tool that already has an embedding (or, withembed_only, every tool with synthetic queries in the index), ignoring the stale-description check.embed_only (
bool) – Recompute embeddings from the existingtool_index_data.jsononly, skipping LLM synthetic-query generation. Used afterbuild_tool_index.invalid_only (
bool) – Refresh only tools whose index entry is missing or has a badsynthetic_querieslist, skipping the stale check. Ignored withforceorembed_only.tool_names (
list[str] |None) – Explicit list of tool names to refresh; unknown names are warned about and skipped. Overrides the automatic target selection.tools_dir (
str) – Directory of tool scripts to discover (defaulttools).
- Return type:
- Returns:
Trueon success (including the no-op case where nothing needed refreshing),Falseon a fatal precondition failure such as a missing API key, an unreadable index file, or no valid targets.- Raises:
RuntimeError – If a target tool has fewer than the expected number of synthetic queries in the index when embeddings are computed.
- async classifiers.refresh_tool_embeddings.main()[source]
Parse CLI arguments and drive a single
refresh_tool_embeddings()run.The command-line entry point for
python -m classifiers.refresh_tool_embeddings. It builds theargparseparser for the--force,--tools,--tools-dir,--embed-only, and--invalid-onlyflags, normalises the comma-separated--toolsvalue into a list, and forwards everything torefresh_tool_embeddings().The whole run’s exit status is derived here: it calls
sys.exit(0)on a truthy result,sys.exit(1)on a falsy result, and alsosys.exit(1)after logging the traceback if the refresh raises. Output goes to the module logger configured at import time.Called only by the
if __name__ == "__main__"guard viaasyncio.run(main()); it is the process entry point and has no in-repo callers.- Return type: