Tools
Tools allow your flows to execute external actions like API calls, database operations, or integrations.
Defining Tools
typescript
import { Tools, FlowEngine } from "@andresaya/flowkit";
const tools = new Tools();
// Register a tool
tools.register("send_email", async (payload) => {
await emailService.send({
to: payload.to,
subject: payload.subject,
body: payload.body
});
return { success: true };
});
// Use in engine
const engine = new FlowEngine(flow, { llm: adapter, storage, tools });Using Tools in Flows
Use .do() to execute tools:
typescript
flow("signup", agent)
.ask("email", "What's your email?", email(), "user_email")
.then("send_welcome")
.say("send_welcome", "Sending you a welcome email...")
.do("send_email", {
to: "{{user_email}}",
subject: "Welcome!",
body: "Thanks for signing up!"
})
.then("done")
.say("done", "Check your inbox!")
.done()
.build();Tool Payload
Payloads can include:
- Static values
- Slot interpolation with
typescript
.do("create_ticket", {
customer: "{{customer_name}}",
email: "{{customer_email}}",
issue: "{{issue_description}}",
priority: "normal", // Static value
source: "chatbot"
})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,
phone: payload.phone,
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 };
});CRM Integration
typescript
tools.register("create_contact", async (payload) => {
const hubspot = new Hubspot({ apiKey: process.env.HUBSPOT_KEY });
const contact = await hubspot.contacts.create({
email: payload.email,
firstname: payload.firstName,
lastname: payload.lastName,
phone: payload.phone
});
return { contactId: contact.vid };
});Multiple Tools
Register as many tools as needed:
typescript
const tools = new Tools();
tools.register("log_event", async (p) => { /* ... */ });
tools.register("send_email", async (p) => { /* ... */ });
tools.register("create_ticket", async (p) => { /* ... */ });
tools.register("notify_team", async (p) => { /* ... */ });
tools.register("update_crm", async (p) => { /* ... */ });Chain multiple tools in a flow:
typescript
.say("processing", "Processing your request...")
.do("log_event", { event: "request_received", user: "{{user_email}}" })
.do("create_ticket", { title: "{{issue}}", email: "{{user_email}}" })
.do("send_email", { to: "{{user_email}}", subject: "Ticket Created" })
.do("notify_team", { channel: "support", message: "New ticket from {{user_email}}" })
.then("confirm")Tool Events
Listen for tool execution events using onEvent:
typescript
const engine = new FlowEngine(flow, {
llm: adapter,
storage,
tools,
onEvent: (event) => {
if (event.type === "tool:call") {
console.log(`Calling ${event.tool} with`, event.payload);
}
if (event.type === "tool:result") {
console.log(`${event.tool} returned`, event.result);
}
}
});Error Handling
Handle tool errors gracefully:
typescript
tools.register("risky_operation", async (payload) => {
try {
const result = await externalApi.call(payload);
return { success: true, data: result };
} catch (error) {
// Log the error
console.error("Tool error:", error);
// Return error info (don't throw)
return { success: false, error: error.message };
}
});Listen for errors via onEvent:
typescript
const engine = new FlowEngine(flow, {
llm: adapter,
storage,
tools,
onEvent: (event) => {
if (event.type === "error") {
// Handle tool-specific errors
await alertTeam(event);
}
}
});Tool Results
Tool results can influence the flow (advanced):
typescript
// The tool result is available for inspection via events
// You can use this to take action based on tool results
const engine = new FlowEngine(flow, {
llm: adapter,
storage,
tools,
onEvent: async (event) => {
if (event.type === "tool:result" && event.tool === "verify_identity") {
if (!event.result.verified) {
// Could update a slot or take action
await engine.setSlot(event.conversationId, "verified", "no");
}
}
}
});Best Practices
Keep tools focused - One responsibility per tool
Handle errors - Return error info instead of throwing
Use environment variables - Don't hardcode credentials
Log appropriately - Track tool calls for debugging
Timeout handling - Set timeouts for external calls
Idempotency - Design tools to be safely retryable
typescript
tools.register("safe_create_order", async (payload) => {
// Check if already exists (idempotent)
const existing = await db.orders.findOne({
idempotencyKey: payload.idempotencyKey
});
if (existing) {
return { orderId: existing.id, duplicate: true };
}
// Create new order
const order = await db.orders.insert(payload);
return { orderId: order.id, duplicate: false };
});