Dynamic Tools¶
The registerDynamicTool() function provides a way to register tools at runtime without using class decorators. This is intended for customer-specific integrations, tools defined in configuration files, or tools backed by external API calls.
Import¶
import { registerDynamicTool } from "@modernpath/agent-framework";
import type {
DynamicToolDefinition,
DynamicToolContext,
DynamicToolExecute,
} from "@modernpath/agent-framework";
Function Signature¶
| Parameter | Type | Description |
|---|---|---|
registry | ToolRegistry | The registry to register the tool into. |
definition | DynamicToolDefinition | The tool definition including name, parameters, and execute function. |
DynamicToolDefinition¶
interface DynamicToolDefinition {
name: string;
description: string;
parameters?: Record<string, ToolParameter>;
category?: string;
tags?: string[];
requiresAuth?: boolean;
rateLimit?: number;
version?: string;
execute: DynamicToolExecute;
}
Properties¶
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | -- | Unique tool identifier. |
description | string | Yes | -- | Human-readable description. Used by LLM planners and tool catalogs. |
parameters | Record<string, ToolParameter> | No | undefined | Parameter schema for validation and UI introspection. Uses the same ToolParameter type as @Tool. |
category | string | No | undefined | Category for grouping (e.g. "external", "client"). |
tags | string[] | No | undefined | Freeform tags for filtering. |
requiresAuth | boolean | No | undefined | Indicates the tool requires authentication. |
rateLimit | number | No | undefined | Maximum calls per minute (informational). |
version | string | No | undefined | Tool version string. |
execute | DynamicToolExecute | Yes | -- | The function that performs the tool's work. Receives cleaned parameters and a DynamicToolContext. |
DynamicToolContext¶
The context object passed as the second argument to the execute function.
| Name | Type | Description |
|---|---|---|
userId | number | The authenticated user ID, injected by the agent or orchestrator at execution time. |
auditingId | number | The audit trail ID, injected by the agent or orchestrator at execution time. |
metadata | Record<string, any> | Optional metadata passed by the orchestrator (e.g. step number, plan type). |
userId and auditingId are injected automatically
When a dynamic tool is executed through an agent (via useTool() or the orchestrator), the framework injects userId and auditingId from the AgentContext. You do not need to pass these manually.
DynamicToolExecute¶
The function signature for the tool's execution logic.
The params object has userId, auditingId, and __metadata keys removed before being passed to your function. These values are extracted and provided via the DynamicToolContext instead.
When to Use Dynamic Tools vs @Tool¶
| Criterion | @Tool Decorator | registerDynamicTool() |
|---|---|---|
| Tool logic is known at build time | Best choice | Works, but more verbose |
| Tool definitions come from config/DB | Impractical | Best choice |
| Tool wraps an external REST API | Either works | Simpler -- no class needed |
| Need TypeScript class features (DI, state) | Best choice | Possible with closures |
| Orchestrator tool steps | Either works | Preferred for external integrations |
Use dynamic tools for customer-specific integrations
If your deployment needs to register tools based on per-tenant configuration or feature flags, registerDynamicTool() is the right choice. You can read tool definitions from a database or config file and register them at startup.
Code Examples¶
Basic Dynamic Tool¶
import { ToolRegistry, registerDynamicTool } from "@modernpath/agent-framework";
const registry = new ToolRegistry();
registerDynamicTool(registry, {
name: "fetch-stock-price",
description: "Fetches the current stock price for a given ticker symbol",
category: "external",
tags: ["finance", "stocks", "external"],
parameters: {
ticker: {
type: "string",
description: "Stock ticker symbol (e.g. AAPL, GOOGL)",
pattern: "^[A-Z]{1,5}$",
},
currency: {
type: "string",
description: "Currency for the price",
required: false,
default: "USD",
enum: ["USD", "EUR", "GBP"],
},
},
execute: async (params, ctx) => {
console.log(`User ${ctx.userId} requesting price for ${params.ticker}`);
const response = await fetch(
`https://api.stockdata.example/v1/price?ticker=${params.ticker}¤cy=${params.currency || "USD"}`,
);
const data = await response.json();
return {
ticker: params.ticker,
price: data.price,
currency: params.currency || "USD",
timestamp: new Date().toISOString(),
};
},
});
// The tool is now available in the registry
console.log(registry.has("fetch-stock-price")); // true
Registering Multiple Dynamic Tools from Configuration¶
import { ToolRegistry, registerDynamicTool } from "@modernpath/agent-framework";
import type { DynamicToolDefinition } from "@modernpath/agent-framework";
interface ToolConfig {
name: string;
description: string;
endpoint: string;
method: "GET" | "POST";
parameters: Record<string, any>;
}
// Tool definitions loaded from a config file or database
const toolConfigs: ToolConfig[] = [
{
name: "crm-lookup",
description: "Look up a customer in the CRM system",
endpoint: "https://crm.example.com/api/customers",
method: "GET",
parameters: {
customerId: { type: "string", description: "Customer ID" },
},
},
{
name: "ticket-create",
description: "Create a support ticket",
endpoint: "https://support.example.com/api/tickets",
method: "POST",
parameters: {
title: { type: "string", description: "Ticket title" },
body: { type: "string", description: "Ticket description" },
priority: {
type: "string",
description: "Priority level",
enum: ["low", "medium", "high", "critical"],
},
},
},
];
const registry = new ToolRegistry();
for (const config of toolConfigs) {
registerDynamicTool(registry, {
name: config.name,
description: config.description,
category: "external",
tags: ["client", "external"],
parameters: config.parameters,
execute: async (params, ctx) => {
const url = new URL(config.endpoint);
const options: RequestInit = {
method: config.method,
headers: {
"Content-Type": "application/json",
"X-User-Id": String(ctx.userId),
},
};
if (config.method === "GET") {
Object.entries(params).forEach(([k, v]) =>
url.searchParams.set(k, String(v)),
);
} else {
options.body = JSON.stringify(params);
}
const response = await fetch(url, options);
return response.json();
},
});
}
console.log(registry.list());
// => ["crm-lookup", "ticket-create"]
Using Dynamic Tools with the Orchestrator¶
Dynamic tools registered with the "external" category or "external" / "client" tags are automatically exposed to the OrchestratorAgent planner when exposeToolsToPlanner is enabled:
import { ToolRegistry, registerDynamicTool } from "@modernpath/agent-framework";
import { OrchestratorAgent } from "@modernpath/agent-framework";
const registry = new ToolRegistry();
// Register a dynamic tool tagged as external
registerDynamicTool(registry, {
name: "send-notification",
description: "Send a notification to a user via email or SMS",
category: "external",
tags: ["notification", "external"],
parameters: {
channel: { type: "string", description: "Channel", enum: ["email", "sms"] },
recipient: { type: "string", description: "Email or phone number" },
message: { type: "string", description: "Notification body" },
},
execute: async (params, ctx) => {
// Send the notification...
return { sent: true, channel: params.channel };
},
});
// The orchestrator can now include "send-notification" in its plans
const orchestrator = new OrchestratorAgent(
registry,
geminiClient,
prompts,
{ documentAnalysis, qaChat },
{
exposeToolsToPlanner: true, // (1)!
toolCatalogMode: "externalOnly",
},
);
- With
exposeToolsToPlanner: true, the orchestrator includes tool names and schemas in the planning prompt, allowing the LLM to generate plan steps that invoke tools directly.
Related Pages¶
- @Tool Decorator -- class-based tool definition
- ToolRegistry -- the registry that holds all tools
- OrchestratorAgent -- how the orchestrator invokes tool steps