Docs menu
Semantic tool search (toolSearch)
A processor that picks the topK most relevant tools out of dozens or hundreds by embedding similarity to the last user message; the selection is journaled via ctx.step → resume/replay never re-calls embed.
What it's for / when to use it#
When an agent has dozens or hundreds of tools, showing all of them to the model causes two problems: context bloats (token cost) and selection quality drops. toolSearch is a processTools processor: it takes the last user message as a query signal, embeds tool names + descriptions, and picks the most relevant topK tools by cosine similarity, then runs the model with only those — semantic tool search over the tools you already registered ToolSearchProcessor/Skills.
Selection is non-deterministic (it involves an embed call) — that's why the selected tool names ctx.step are journaled. On resume/replay, embed is not called again; the model sees the exact same tool subset. Tool names in the journaled selection that no longer exist are silently skipped (replay doesn't break even if the tool set changes).
Usage#
Pass the processor to createGnl's shared processors:array, or to a single agent. Safe-side behavior: if there's no query signal, or the tool count is already at or below topK, it does not narrow anything down (everything stays visible).
import { toolSearch } from '@gnldev/processors';
const gnl = createGnl({
storage,
processors: [
toolSearch({
embed, // text → number[] (wires into the AI SDK embed)
topK: 8, // how many of the most relevant tools to show the model (default 8)
always: ['final'],// always included (never scored, never counted toward topK)
minScore: 0.2, // anything below this similarity is dropped even if it would fit in topK
}),
],
agents: { assistant: { model, tools: bigToolset /* 100+ tool */ } },
});Determinism and the safe side#
Selection runs inside ctx.step('tool-search', ...) → only the final tool names are written to the journal. Ties are broken with a stable sort by tool name (reproducibility). TheprocessTools surface was extended to async for this and ctx.input was added — backward compatible.
always lets you keep core tools that must always be visible (e.g. a final/complete tool) out of the scoring; they don't spend from the topK budget.API reference#
toolSearch(ToolSearchOptions) → Processor. Inside processTools, picks the topK tools closest to the last user message; the selection is journaled.
ToolSearchOptions{ embed, topK?=8, always?, minScore? } — embed is required; always is not scored, minScore filters out anything below the threshold.
ProcessorThe @gnldev/durable processor contract; toolSearch implements the processTools hook.