Skip to content

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

  1. Keep tools focused - One responsibility per tool

  2. Handle errors - Return error info instead of throwing

  3. Use environment variables - Don't hardcode credentials

  4. Log appropriately - Track tool calls for debugging

  5. Timeout handling - Set timeouts for external calls

  6. 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 };
});

Released under the MIT License.