Tools
Tools are reusable functions that agents can call to perform specific actions like processing payments, sending emails, or updating databases.
Tool Structure
Every tool must implement the Tool
interface:
interface Tool<Input extends z.ZodSchema, Memory> {
name: string; // Unique tool identifier
description: string; // What the tool does (used by LLM)
input: Input; // Zod schema for input validation
isGlobal?: boolean; // Optional: available across all LLM calls
execute: ({ input, state, agent }) => Promise<{ state?; result? }>;
}
Execute Function Signature
The execute
function is the core of every tool. It receives validated input from the LLM, current state (including memory), and the agent instance, then returns updated state and/or result.
Parameters
execute: ({ input, state, agent }) => Promise<{ state?; result? }>;
input
: Validated data matching your Zod schema - contains the parameters the LLM extracted from the conversationstate
: Current conversation state including memory, sessionId, and other context dataagent
: Agent instance providing access to PII gateway, logging, and other platform features
Return Value
The execute function returns a Promise with an object containing:
state?
(optional): Updated state object that will be merged with existing state. This is a partial object - you only need to return the parts that changedresult?
(optional): Result that gets sent back to the LLM as the tool's response
State Object Structure
The state
object contains:
memory
: Your user-defined memory schema - the main working memory for your agentsessionId
: Unique identifier for the current sessionmessages
: Array of conversation messages (AI, Human, System, etc.)history
: Array of HistoryStep objects tracking the flow execution (node visits, trigger events, tool calls)Other platform context: Additional fields managed by the platform
Basic Tool Example
Here's a simple refund processing tool:
import { z } from 'zod';
import { Tool } from 'mindedjs/src/types/Tools.types';
import { logger } from 'mindedjs';
import memorySchema from '../schema';
type Memory = z.infer<typeof memorySchema>;
const schema = z.object({
orderId: z.string(),
customerName: z.string(),
});
const refundOrderTool: Tool<typeof schema, Memory> = {
name: 'refundOrder',
description: 'Process a customer refund for their order',
input: schema,
execute: async ({ input, state, agent }) => {
// Access current memory
const currentMemory = state.memory;
// Use the provided logger
logger.info('Processing refund', {
sessionId: state.sessionId,
orderId: input.orderId,
});
// Your business logic here
const refundAmount = await processRefundInSystem(input.orderId);
// Return partial state update - only the parts that changed
return {
state: {
memory: {
orderId: input.orderId,
customerName: input.customerName,
issue: `Refund processed: $${refundAmount}`,
},
},
result: `Refund processed successfully for order ${input.orderId}`,
};
},
};
export default refundOrderTool;
Input Schema Configuration
Input schemas use Zod for validation and provide important metadata to help the LLM understand how to better use each input parameter.
Parameter Descriptions
Add descriptions to parameters using Zod's .describe()
method. These descriptions help the LLM understand what each parameter represents:
const schema = z.object({
orderId: z.string().describe('The unique identifier of the customer order to process'),
customerName: z.string().describe('Full name of the customer requesting the refund'),
refundAmount: z.number().describe('Amount to refund in dollars (optional if full refund)'),
reason: z.string().describe('Reason for the refund request'),
});
Optional Parameters
Make parameters optional using Zod's .optional()
method. Optional parameters don't need to be provided by the LLM:
const schema = z.object({
orderId: z.string().describe('The unique identifier of the customer order'),
customerName: z.string().describe('Full name of the customer'),
refundAmount: z.number().optional().describe('Specific refund amount, defaults to full order amount'),
expedited: z.boolean().optional().describe('Whether to expedite the refund process'),
});
Combining Descriptions and Optional Parameters
You can chain Zod methods to create descriptive optional parameters:
const updateOrderSchema = z.object({
orderId: z.string().describe('The order ID that needs to be updated'),
newAddress: z.string().optional().describe('Updated shipping address if customer wants to change it'),
specialInstructions: z.string().optional().describe('Any special delivery instructions from the customer'),
urgentDelivery: z.boolean().optional().describe('Whether customer needs urgent delivery (additional charges may apply)'),
});
Tool Registration
Global Tools
Mark tools as global if you want them to be available in all LLM calls.
const auditLogTool: Tool<typeof auditSchema, Memory> = {
name: 'auditLog',
description: 'Log user action for compliance',
isGlobal: true, // Available in all flows
input: z.object({
action: z.string(),
details: z.string(),
}),
execute: async ({ input, state, agent }) => {
logger.info('Audit log entry', {
sessionId: state.sessionId,
action: input.action,
});
await auditService.log({
userId: state.memory.userId,
action: input.action,
details: input.details,
timestamp: new Date(),
});
},
};
State Management
Tools can read and update state including memory:
const updateProfileTool: Tool<typeof profileSchema, Memory> = {
name: 'updateProfile',
description: 'Update customer profile information',
input: z.object({
field: z.string(),
value: z.string(),
}),
execute: async ({ input, state, agent }) => {
// Read current memory from state
console.log(`Current customer: ${state.memory.customerName}`);
// Update external system
await profileService.update(state.memory.customerId, {
[input.field]: input.value,
});
// Return partial state update
return {
state: {
memory: {
[`${input.field}Updated`]: true,
},
},
};
},
};
Error Handling
Handle errors gracefully in tools:
const paymentTool: Tool<typeof paymentSchema, Memory> = {
name: 'processPayment',
description: 'Process customer payment',
input: z.object({
amount: z.number(),
cardToken: z.string(),
}),
execute: async ({ input, state, agent }) => {
try {
const result = await paymentService.charge({
amount: input.amount,
cardToken: input.cardToken,
customerId: state.memory.customerId,
});
return {
result: `Payment successful: ${result.transactionId}`,
state: {
memory: { lastPaymentId: result.transactionId },
},
};
} catch (err) {
logger.error({
message: 'Payment failed',
sessionId: state.sessionId,
err,
});
return {
result: 'Payment failed. Please try again or contact support.',
state: {
memory: { paymentError: err.message },
},
};
}
},
};
## Flow Control with goto
Tools can control the flow by returning a `goto` property in their state update. This allows tools to programmatically jump to a specific node by setting `state.goto` to the target node ID. This is useful for dynamic flow control based on runtime conditions.
### Basic goto Usage
```ts
const moveToAnotherRepresentativeTool: Tool<typeof schema, Memory> = {
name: 'moveToAnotherRepresentative',
description: 'Transfer the conversation to another representative or department',
input: schema,
execute: async ({ input, state }) => {
logger.info({
message: 'Moving conversation to another representative',
sessionId: state.sessionId,
department: input.department,
reason: input.reason,
priority: input.priority,
});
return {
state: {
goto: 'share-new-representative',
memory: {
orderId: 'share-new-representative',
},
},
};
},
};
```
### Dynamic Routing Example
```typescript
const routingTool: Tool<typeof routingSchema, Memory> = {
name: 'routingTool',
description: 'Route conversation based on urgency',
input: routingSchema,
execute: async ({ input, state }) => {
// Perform logic to determine routing
if (input.condition === 'urgent') {
// Jump to urgent handler node
return {
result: 'Routing to urgent handler',
state: {
...state,
goto: 'urgent-handler-node',
},
};
}
// Normal flow continues
return { result: 'Continue normal flow' };
},
};
```
> **Important:** The jump occurs only when the current invocation completes and the next one begins. The agent will finish executing the current node/tool before jumping to the specified node.
## Best Practices
1. **Clear Descriptions**: Write descriptions that help the LLM understand when to use the tool
2. **Input Validation**: Use Zod schemas to validate all inputs
3. **State Updates**: Return partial state updates - only include the parts that changed
4. **Error Handling**: Always handle errors gracefully and provide meaningful messages
5. **Async Operations**: Use async/await for external API calls and database operations
6. **Logging**: Use the provided logger for consistent, structured logging with session context
## Tool Nodes
Tool nodes force execution of specific tools at defined points in your flow, bypassing LLM decision-making.
### Configuration
```yaml
nodes:
- name: 'Force Refund Processing'
type: 'tool'
toolName: 'refundOrder'
Tool Node vs Automatic Tool Calls
Execution
Guaranteed when node is reached
Only when LLM decides to call
Control
Explicit flow control
LLM-driven decision
Use Case
Required business logic steps
Flexible, context-dependent actions
Example
nodes:
- name: 'Customer Support Start'
type: 'prompt'
prompt: 'How can I help you today?'
- name: 'Log Interaction'
type: 'tool'
toolName: 'auditLog'
edges:
- source: 'Customer Support Start'
target: 'Log Interaction'
type: 'always'
Best Practices
Strategic Placement: Use for critical business logic that must always execute
Clear Context: Ensure conversation provides context for parameter extraction
Error Handling: Handle errors gracefully since they bypass LLM error recovery
Tool parameters are automatically extracted from conversation context and memory state using an internal LLM agent.
Last updated