Tools API
Tools allow executing custom functions during conversation flows.
Basic Usage
typescript
import { Tools, FlowEngine } from "@andresaya/flowkit";
const tools = new Tools();
// Register a tool
tools.register("send_email", async (payload) => {
await emailService.send(payload);
return { success: true };
});
// Use in engine
const engine = new FlowEngine(flow, { llm, storage, tools });Tools Class
register(name, handler)
Registers a tool function.
typescript
tools.register("tool_name", async (payload) => {
// Your logic here
return { result: "value" };
});Parameters:
name- Tool identifierhandler- Async function(payload: any) => Promise<any>
get(name)
Gets a registered tool.
typescript
const tool = tools.get("send_email");has(name)
Checks if a tool exists.
typescript
if (tools.has("send_email")) {
// ...
}Using Tools in Flows
Use .do() to call a tool:
typescript
const myFlow = flow("signup", agent)
.ask("email", "What's your email?", email(), "user_email")
.then("send")
.say("send", "Sending confirmation...")
.do("send_email", { to: "{{user_email}}", subject: "Welcome!" })
.then("done")
.say("done", "Check your inbox!")
.done()
.build();Payload Interpolation
Use to insert slot values:
typescript
.do("create_ticket", {
customer: "{{customer_name}}",
email: "{{customer_email}}",
issue: "{{issue_description}}",
priority: "normal" // Static value
})Tool Examples
HTTP API Call
typescript
tools.register("create_order", async (payload) => {
const response = await fetch("https://api.example.com/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify(payload)
});
return await response.json();
});Database Operation
typescript
tools.register("save_lead", async (payload) => {
const result = await db.leads.insert({
name: payload.name,
email: payload.email,
createdAt: new Date()
});
return { id: result.insertedId };
});Email Service
typescript
tools.register("send_email", async (payload) => {
await sendgrid.send({
to: payload.to,
from: "noreply@example.com",
subject: payload.subject,
text: payload.body
});
return { sent: true };
});SMS Notification
typescript
tools.register("send_sms", async (payload) => {
await twilio.messages.create({
to: payload.phone,
from: process.env.TWILIO_NUMBER,
body: payload.message
});
return { sent: true };
});Slack Notification
typescript
tools.register("notify_slack", async (payload) => {
await fetch(process.env.SLACK_WEBHOOK, {
method: "POST",
body: JSON.stringify({
text: payload.message,
channel: payload.channel || "#general"
})
});
return { notified: true };
});Tool Events
Track tool execution via onEvent:
typescript
const engine = new FlowEngine(flow, {
llm, storage, tools,
onEvent: (event) => {
if (event.type === "tool:call") {
console.log(`Calling: ${event.tool}`, event.payload);
}
if (event.type === "tool:result") {
console.log(`Result:`, event.result);
}
}
});Error Handling
Return errors gracefully:
typescript
tools.register("risky_operation", async (payload) => {
try {
const result = await externalApi.call(payload);
return { success: true, data: result };
} catch (error) {
console.error("Tool error:", error);
return { success: false, error: error.message };
}
});Inline Actions
For simple operations, use inline functions:
typescript
.do(async (ctx) => {
// Access slots
const name = ctx.slots.user_name;
// Modify slots
ctx.slots.processed = true;
ctx.slots.timestamp = Date.now();
// Call external services
await logEvent("user_processed", { name });
})ToolsRegistry Interface
typescript
interface ToolsRegistry {
register(name: string, handler: ToolHandler): void;
get(name: string): ToolHandler | undefined;
has(name: string): boolean;
}
type ToolHandler = (payload: JsonValue) => Promise<JsonValue>;Best Practices
Keep Tools Focused
One responsibility per tool:
typescript
// Good
tools.register("send_email", ...);
tools.register("create_ticket", ...);
// Avoid
tools.register("send_email_and_create_ticket", ...);Handle Errors
Never throw errors from tools. Return error info instead:
typescript
return { success: false, error: "..." };Use Environment Variables
Never hardcode credentials:
typescript
const apiKey = process.env.API_KEY;Design for Idempotency
Tools should be safely retryable:
typescript
tools.register("create_order", async (payload) => {
// Check if already exists
const existing = await db.orders.findOne({
idempotencyKey: payload.key
});
if (existing) {
return { orderId: existing.id, duplicate: true };
}
// Create new
const order = await db.orders.insert(payload);
return { orderId: order.id, duplicate: false };
});