Skip to content

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 identifier
  • handler - 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 };
});

Released under the MIT License.