UNPKG

@mastra/core

Version:
248 lines (172 loc) 10.2 kB
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt # ToolSearchProcessor The `ToolSearchProcessor` is an **input processor** that enables dynamic tool discovery and loading. Instead of providing all tools to the agent upfront, it gives the agent two meta-tools (`search_tools` and `load_tool`) that let it find and load tools on demand. This reduces context token usage when working with large tool libraries. ## Usage example ```typescript import { ToolSearchProcessor } from '@mastra/core/processors' const toolSearch = new ToolSearchProcessor({ tools: { createIssue: githubTools.createIssue, sendEmail: emailTools.send, getWeather: weatherTools.forecast, // ... many more tools }, search: { topK: 5, minScore: 0.1, }, }) ``` ## Constructor parameters **options** (`ToolSearchProcessorOptions`): Configuration options for the tool search processor **options.tools** (`Record<string, Tool>`): All tools that can be searched and loaded dynamically. These tools are not immediately available to the agent — they must be discovered via search and loaded on demand. **options.search** (`{ topK?: number; minScore?: number; autoLoad?: boolean }`): Configuration for the search behavior. **options.search.topK** (`number`): Maximum number of tools to return in search results. **options.search.minScore** (`number`): Minimum relevance score (0-1) for including a tool in search results. **options.search.autoLoad** (`boolean`): When true, tools returned by search\_tools are activated immediately as part of the search. The load\_tool meta-tool is not exposed, collapsing the two-step search then load flow into a single search step. Discovered tools become available on the next turn. Keep topK conservative since every match is activated. **options.storage** (`'in-memory' | 'context'`): Where loaded-tool state lives. 'in-memory' (default) tracks loaded tools in an in-memory map per thread with TTL cleanup (see ttl); state is lost on restart and anonymous requests share a 'default' entry. 'context' derives loaded state from the conversation messages — a tool is loaded while a search\_tools/load\_tool result naming it remains in the messages; it is restart-safe, requires no memory, and de-loads automatically once that result is no longer present in the messages. The 'context' store is opt-in. **options.ttl** (`number`): Time-to-live for in-memory thread state, in milliseconds. Only applies to the default storage: 'in-memory' store; after this duration of inactivity thread state is cleaned up. Set to 0 to disable cleanup. Ignored by the 'context' store. **options.filter** (`(args: ToolSearchFilterArgs) => boolean | Promise<boolean>`): Optional request-aware hook for hiding tools from search results, blocking tool loading, or hiding already-loaded tools for the current request. ## Returns **id** (`string`): Processor identifier set to 'tool-search' **name** (`string`): Processor display name set to 'Tool Search Processor' **processInputStep** (`(args: ProcessInputStepArgs) => Promise<ProcessInputStepResult>`): Processes each step to inject search/load meta-tools and any previously loaded tools into the agent's tool set. ## Methods ### State inspection (legacy `'in-memory'` store) These methods operate on the default `'in-memory'` store only. They're no-ops for the `'context'` store, whose state lives in the conversation messages rather than an in-process map. #### `clearState(threadId)` Clears the loaded-tool state for a single thread. ```typescript processor.clearState('thread-123') ``` #### `clearAllState()` Clears loaded-tool state for every thread. ```typescript processor.clearAllState() ``` #### `getStateStats()` Returns the number of tracked threads and the oldest access time, for debugging in-memory growth. ```typescript const { threadCount, oldestAccessTime } = processor.getStateStats() ``` Returns: `{ threadCount: number; oldestAccessTime: number | null }` #### `cleanupNow()` Immediately runs TTL cleanup instead of waiting for the scheduled sweep. ```typescript const cleaned = processor.cleanupNow() ``` Returns: `number` — the count of threads cleaned up. ## Request-aware filtering Use `filter` to apply request-specific policy to dynamic tools. The hook receives the resolved tool ID as `toolName`, the tool, request context, and phase. `toolName` is the ID returned by `search_tools`, which may differ from the key used in the `tools` object. ```typescript import { ToolSearchProcessor } from '@mastra/core/processors' const toolSearch = new ToolSearchProcessor({ tools: allTools, filter: ({ toolName, requestContext, phase }) => { const plan = requestContext?.get('plan') if (phase === 'search') { return true } return plan === 'pro' || !toolName.startsWith('premium_') }, }) ``` The `phase` value describes where the filter is being applied: - `search`: Filters results returned by `search_tools`. - `load`: Blocks `load_tool` from loading disallowed tools. - `active`: Hides already-loaded tools from the current request if they're no longer allowed. If the hook throws or rejects, `ToolSearchProcessor` treats the tool as disallowed for that request. The hook may run for every matching search candidate, so keep async policy checks cheap or cached. The `search_tools` meta-tool is always available; `load_tool` is available unless `search.autoLoad` is enabled. Tools passed directly through the agent or `processInputStep` remain available unless you filter them outside `ToolSearchProcessor`. ## Extended usage example ```typescript import { Agent } from '@mastra/core/agent' import { ToolSearchProcessor } from '@mastra/core/processors' // Tools from various integrations import { githubTools } from './tools/github' import { slackTools } from './tools/slack' import { dbTools } from './tools/database' const toolSearch = new ToolSearchProcessor({ tools: { ...githubTools, // createIssue, listPRs, mergePR, ... ...slackTools, // sendMessage, createChannel, ... ...dbTools, // query, insert, update, ... }, search: { topK: 5, minScore: 0.1, }, }) const agent = new Agent({ id: 'dynamic-tools-agent', name: 'dynamic-tools-agent', instructions: 'You are a helpful assistant with access to many tools. Use search_tools to find relevant tools, then load_tool to make them available.', model: 'openai/gpt-5.5', inputProcessors: [toolSearch], }) ``` The agent workflow is: 1. Agent receives a user message 2. Agent calls `search_tools` with keywords (e.g., "github issue") 3. Agent reviews results and calls `load_tool` with the tool name 4. The loaded tool becomes available on the next turn 5. Agent uses the loaded tool normally ## Single-step discovery with `autoLoad` Set `search.autoLoad` to `true` to skip the separate load step. The tools returned by `search_tools` are activated immediately, and the `load_tool` meta-tool isn't exposed. This removes one model turn per discovery, which lowers token usage and latency, and works the same across providers. ```typescript const toolSearch = new ToolSearchProcessor({ tools: allTools, search: { topK: 3, autoLoad: true, }, }) ``` With `autoLoad` the workflow becomes: 1. Agent receives a user message 2. Agent calls `search_tools` with keywords 3. The matching tools are activated automatically and become available on the next turn 4. Agent uses the tool normally Every match is activated, so keep `topK` small (for example, `3`) to avoid adding tools the agent didn't need. Activated tools are appended after existing tools, which keeps the cached prompt prefix stable for providers that support prompt caching. ## Loaded-tool storage The `storage` option controls where the set of loaded tools is tracked. The default is `'in-memory'`; the `'context'` store is opt-in. ### `'in-memory'` (default) Loaded tools are tracked in an in-memory map per thread, with TTL-based cleanup controlled by the `ttl` option (default one hour). This is the original behavior: - Requires no memory configuration. - State is lost on process restart. - Requests with no thread ID share a single `'default'` entry. Use `clearState`, `clearAllState`, `getStateStats`, and `cleanupNow` to inspect or reset this store. ### `'context'` Loaded state is derived from the conversation messages: a tool is loaded while a `search_tools` or `load_tool` result naming it remains in the messages. This mode: - Requires no memory configuration. - Is restart-safe — the durable record is the persisted message history. - De-loads a tool automatically once that result is no longer present in the messages. ```typescript import { ToolSearchProcessor } from '@mastra/core/processors' const toolSearch = new ToolSearchProcessor({ tools: allTools, storage: 'context', }) ``` Loading tools is cache-friendly in both modes: loads are append-only, so the cached prompt prefix stays stable for providers that support prompt caching. Unloading a tool changes the tool definitions sent to the model, which shifts the cached prefix and causes the next turn to pay a cache write instead of a cache hit. In `'in-memory'` mode this happens when a thread's state is evicted by `ttl`. In `'context'` mode it happens when a tool's discovery result is no longer present in the messages (for example, when older messages are trimmed) — the tool de-loads and the model must search for it again before reuse. This is expected: removing an unused tool trades one cache write for a smaller prefix on later turns. ## Combining with other processors ```typescript import { Agent } from '@mastra/core/agent' import { ToolSearchProcessor, TokenLimiter } from '@mastra/core/processors' const agent = new Agent({ id: 'my-agent', name: 'my-agent', model: 'openai/gpt-5.5', inputProcessors: [ new ToolSearchProcessor({ tools: allTools, search: { topK: 5 }, }), // Place TokenLimiter last to ensure context fits new TokenLimiter(127000), ], }) ``` ## Related - [Processors](https://mastra.ai/docs/agents/processors) - [Using Tools](https://mastra.ai/docs/agents/using-tools)