Summary
The tool-call parser validates a compact single-key tool call like {"get_weather":"Denver"} (a shape small models emit routinely, #438) by checking whether the key names a known tool:
// crates/genie-core/src/tools/parser.rs
let known_tool = tools.tool_defs().iter().any(|tool| tool.name == *tool_name);
But ToolDispatcher::tool_defs() (dispatch.rs) is the system-prompt builder: it allocates a ToolDef per built-in tool — each carrying a serde_json::json! parameter schema (nested maps, enum arrays, required vectors) — and serde_json::from_str-parses every loaded skill's parameters_json. The parser throws all of that away after reading a single .name, on every compact tool-emitting model response.
Where
crates/genie-core/src/tools/parser.rs — normalize_single_key_tool_call builds the full table to test one name.
crates/genie-core/src/tools/dispatch.rs — tool_defs() builds ~10–15 JSON schemas + parses skill schemas per call.
Proposed fix
Add ToolDispatcher::is_known_tool(name) — an allocation-free membership check over the same name set, with the same has_home_automation() / has_web_search() gating and the same loaded skills — and use it from the parser. Keep it in lockstep with tool_defs() via a test that asserts membership parity across home/web/skill configurations.
Impact
Local microbench (laptop, release): the per-name check drops from ~11.2 µs/call to ~15 ns/call (~700×), eliminating ~10–15 json! schema builds (and the matching allocator churn) per compact tool call on the parser hot path. Behavior is identical — same set of accepted names.
Contributes under #402.
Summary
The tool-call parser validates a compact single-key tool call like
{"get_weather":"Denver"}(a shape small models emit routinely, #438) by checking whether the key names a known tool:But
ToolDispatcher::tool_defs()(dispatch.rs) is the system-prompt builder: it allocates aToolDefper built-in tool — each carrying aserde_json::json!parameter schema (nested maps, enum arrays,requiredvectors) — andserde_json::from_str-parses every loaded skill'sparameters_json. The parser throws all of that away after reading a single.name, on every compact tool-emitting model response.Where
crates/genie-core/src/tools/parser.rs—normalize_single_key_tool_callbuilds the full table to test one name.crates/genie-core/src/tools/dispatch.rs—tool_defs()builds ~10–15 JSON schemas + parses skill schemas per call.Proposed fix
Add
ToolDispatcher::is_known_tool(name)— an allocation-free membership check over the same name set, with the samehas_home_automation()/has_web_search()gating and the same loaded skills — and use it from the parser. Keep it in lockstep withtool_defs()via a test that asserts membership parity across home/web/skill configurations.Impact
Local microbench (laptop, release): the per-name check drops from ~11.2 µs/call to ~15 ns/call (~700×), eliminating ~10–15
json!schema builds (and the matching allocator churn) per compact tool call on the parser hot path. Behavior is identical — same set of accepted names.Contributes under #402.