Skip to main content

Overview

Client tools allow your AI persona to trigger events in your client application. When the LLM invokes a client tool, an event is sent via WebSocket to your application, enabling the persona to:
  • Navigate to specific pages or sections
  • Open modals, dialogs, or overlays
  • Update UI state based on conversation
  • Trigger animations or visual effects
  • Control media playback
  • Submit forms or initiate actions
This creates a seamless voice-driven or chat-driven user experience where the AI can guide users through your application.
Beta Feature: Tool calling is currently in beta. You may encounter some issues as we continue to improve the feature. Please report any feedback or issues to help us make it better.

How Client Tools Work

1

User makes a request

User: “Show me the pricing page”
2

LLM decides to call tool

The LLM recognizes this requires a client-side action and generates a tool call:
{
  "name": "navigate_to_page",
  "arguments": {
    "page": "pricing"
  }
}
3

Event sent to client

The Anam engine sends a tool_call event via WebSocket to your client application.
4

Your app handles the event

Your event handler receives the tool call and executes the action:
if (toolName === "navigate_to_page") {
  window.location.href = `/${arguments.page}`;
}
5

LLM continues conversation

The persona confirms: “I’ve opened the pricing page for you!”
The user experiences a seamless interaction between voice/chat and UI.

Creating Client Tools

Basic Client Tool Structure

A client tool requires four components:
type
string
required
Must be "client" for client-side tools
name
string
required
Unique identifier for the tool (snake_case, 1-64 characters)
description
string
required
Describes when the LLM should use this tool (helps with decision-making)
parameters
object
JSON Schema defining the parameters the tool accepts

Example: Page Navigation

{
  "type": "client",
  "name": "navigate_to_page",
  "description": "Navigate to a specific page when user asks to see pricing, features, documentation, or other sections",
  "parameters": {
    "type": "object",
    "properties": {
      "page": {
        "type": "string",
        "description": "The page to navigate to (pricing, features, docs, contact)",
        "enum": ["pricing", "features", "docs", "contact", "dashboard"]
      },
      "section": {
        "type": "string",
        "description": "Optional section anchor to scroll to"
      }
    },
    "required": ["page"]
  }
}

Example: Open Modal

{
  "type": "client",
  "name": "open_modal",
  "description": "Open a modal dialog when user wants to perform an action like checkout, signup, or view details",
  "parameters": {
    "type": "object",
    "properties": {
      "modalType": {
        "type": "string",
        "description": "The type of modal to open",
        "enum": ["checkout", "signup", "login", "contact", "product_details"]
      },
      "data": {
        "type": "object",
        "description": "Additional data to pass to the modal",
        "properties": {
          "productId": { "type": "string" },
          "userId": { "type": "string" }
        }
      }
    },
    "required": ["modalType"]
  }
}

Example: Update UI State

{
  "type": "client",
  "name": "update_filters",
  "description": "Update product filters when user describes what they're looking for",
  "parameters": {
    "type": "object",
    "properties": {
      "category": {
        "type": "string",
        "description": "Product category"
      },
      "priceRange": {
        "type": "object",
        "properties": {
          "min": { "type": "number" },
          "max": { "type": "number" }
        }
      },
      "inStock": {
        "type": "boolean",
        "description": "Only show in-stock items"
      }
    }
  }
}

Handling Tool Events in Your Application

Setting Up Event Listeners

import { AnamClient } from '@anam-ai/js-sdk';

const client = new AnamClient('YOUR_SESSION_TOKEN');

// Listen for tool calls
anamClient.addListener(AnamEvent.CLIENT_TOOL_EVENT_RECEIVED, (event) => {
  const { eventName, eventData } = event;
  switch(eventName) {
    case 'navigate_to_page':
      handleNavigation(eventData.page);
      break;
    case 'open_modal':
      openModal(eventData.modalType, eventData.data);
      break;
  }
});

// Start the session
await client.streamToVideoElement('video-element-id');
See the complete SDK Reference for all available events and methods.

Real-World Examples

E-commerce Shopping Assistant

Create a shopping assistant that can guide users through your product catalog:
const tools = [
  {
    type: "client",
    name: "show_product",
    description: "Display a product when user asks about specific items",
    parameters: {
      type: "object",
      properties: {
        productId: { type: "string" },
        productName: { type: "string" },
      },
      required: ["productId"],
    },
  },
  {
    type: "client",
    name: "add_to_cart",
    description: "Add a product to cart when user wants to purchase",
    parameters: {
      type: "object",
      properties: {
        productId: { type: "string" },
        quantity: { type: "number", default: 1 },
      },
      required: ["productId"],
    },
  },
  {
    type: "client",
    name: "apply_filter",
    description: "Filter products when user describes preferences",
    parameters: {
      type: "object",
      properties: {
        category: { type: "string" },
        maxPrice: { type: "number" },
        inStock: { type: "boolean" },
      },
    },
  },
  {
    type: "client",
    name: "open_checkout",
    description: "Open checkout when user is ready to purchase",
    parameters: {
      type: "object",
      properties: {},
    },
  },
];

// Event handler
function handleToolCall(toolName, args) {
  switch (toolName) {
    case "show_product":
      router.push(`/products/${args.productId}`);
      break;

    case "add_to_cart":
      cart.addItem(args.productId, args.quantity);
      showNotification(`Added ${args.quantity}x to cart`);
      break;

    case "apply_filter":
      productList.filter(args);
      break;

    case "open_checkout":
      router.push("/checkout");
      break;
  }
}
Example conversation:
  • User: “Show me wireless headphones under $100”
  • AI: Calls apply_filter with category: “headphones”, maxPrice: 100
  • User: “I like the Sony ones”
  • AI: Calls show_product with productId: “sony-wh-1000xm4”
  • User: “Add them to my cart”
  • AI: Calls add_to_cart “Added to your cart! Ready to checkout?”

Customer Support Dashboard

Create a support agent that can navigate your dashboard:
const tools = [
  {
    type: "client",
    name: "show_ticket",
    description: "Display a support ticket when user mentions a ticket number",
    parameters: {
      type: "object",
      properties: {
        ticketId: { type: "string" },
      },
      required: ["ticketId"],
    },
  },
  {
    type: "client",
    name: "open_chat",
    description:
      "Open live chat with a human agent when issue needs escalation",
    parameters: {
      type: "object",
      properties: {
        reason: { type: "string", description: "Why escalating to human" },
      },
    },
  },
  {
    type: "client",
    name: "show_analytics",
    description:
      "Show analytics dashboard when user asks for metrics or reports",
    parameters: {
      type: "object",
      properties: {
        dateRange: { type: "string", enum: ["today", "week", "month"] },
      },
    },
  },
];

SaaS Application Navigator

const tools = [
  {
    type: "client",
    name: "navigate_to_feature",
    description: "Navigate to a specific feature or section of the application",
    parameters: {
      type: "object",
      properties: {
        feature: {
          type: "string",
          enum: [
            "dashboard",
            "analytics",
            "settings",
            "billing",
            "team",
            "integrations",
          ],
        },
      },
      required: ["feature"],
    },
  },
  {
    type: "client",
    name: "create_new",
    description:
      "Open creation modal for new items (project, user, campaign, etc.)",
    parameters: {
      type: "object",
      properties: {
        itemType: {
          type: "string",
          enum: ["project", "campaign", "user", "report"],
        },
        prefill: {
          type: "object",
          description: "Data to prefill in the creation form",
        },
      },
      required: ["itemType"],
    },
  },
  {
    type: "client",
    name: "run_export",
    description: "Export data when user requests a download",
    parameters: {
      type: "object",
      properties: {
        exportType: { type: "string", enum: ["csv", "pdf", "json"] },
        dataType: { type: "string" },
      },
    },
  },
];

Best Practices

Provide Clear Descriptions

The description helps the LLM decide when to use the tool:
// ✅ Good - Specific about when to use
{
  name: 'open_checkout',
  description: 'Open the checkout page when user explicitly says they want to purchase, buy, checkout, or complete their order'
}

// ❌ Bad - Too vague
{
  name: 'open_checkout',
  description: 'Opens checkout'
}

Use Enums for Constrained Values

When parameters have a limited set of valid values, use enums:
{
  parameters: {
    type: 'object',
    properties: {
      page: {
        type: 'string',
        enum: ['home', 'pricing', 'features', 'contact'],
        description: 'The page to navigate to'
      }
    }
  }
}
This prevents the LLM from generating invalid values.

Handle Errors Gracefully

Always validate tool arguments in your event handler:
function handleToolCall(toolName, args) {
  try {
    switch (toolName) {
      case "show_product":
        if (!args.productId) {
          console.error("Missing productId");
          return;
        }

        // Validate product exists
        if (!productExists(args.productId)) {
          showNotification("Product not found");
          return;
        }

        router.push(`/products/${args.productId}`);
        break;

      default:
        console.warn("Unknown tool:", toolName);
    }
  } catch (error) {
    console.error("Tool execution error:", error);
    showNotification("Something went wrong");
  }
}

Provide User Feedback

Give immediate feedback when tools execute:
case 'add_to_cart':
  cart.addItem(args.productId, args.quantity);

  // Visual feedback
  showNotification(`Added ${args.quantity}x to cart`, 'success');

  // Update cart icon with animation
  cartIcon.classList.add('bounce');
  setTimeout(() => cartIcon.classList.remove('bounce'), 300);
  break;

Test Tool Execution

Use browser console to debug tool calls:
ws.onmessage = (event) => {
  const message = JSON.parse(event.data);

  if (message.type === "tool_call") {
    console.group("🔧 Tool Call");
    console.log("Tool:", message.toolName);
    console.log("Arguments:", message.arguments);
    console.log("Timestamp:", new Date().toISOString());
    console.groupEnd();

    handleToolCall(message.toolName, message.arguments);
  }
};

Security Considerations

Validate All Parameters

Never trust client tool arguments without validation:
case 'navigate_to_page':
  // ✅ Validate against whitelist
  const validPages = ['home', 'pricing', 'features', 'contact'];
  if (!validPages.includes(args.page)) {
    console.error('Invalid page:', args.page);
    return;
  }

  window.location.href = `/${args.page}`;
  break;

Avoid Exposing Sensitive Data

Don’t include sensitive information in tool parameters:
// ❌ Bad - Exposes sensitive data
{
  name: 'show_user_profile',
  parameters: {
    userId: { type: 'string' },
    email: { type: 'string' },
    creditCard: { type: 'string' }  // Never expose this!
  }
}

// ✅ Good - Only IDs, fetch sensitive data server-side
{
  name: 'show_user_profile',
  parameters: {
    userId: { type: 'string' }
  }
}

Rate Limit Tool Calls

Prevent abuse by tracking and limiting tool execution:
const toolCallCounts = {};
const MAX_CALLS_PER_MINUTE = 20;

function handleToolCall(toolName, args) {
  // Track calls
  const now = Date.now();
  toolCallCounts[toolName] = toolCallCounts[toolName] || [];
  toolCallCounts[toolName].push(now);

  // Remove old entries
  toolCallCounts[toolName] = toolCallCounts[toolName].filter(
    (time) => now - time < 60000
  );

  // Check limit
  if (toolCallCounts[toolName].length > MAX_CALLS_PER_MINUTE) {
    console.warn("Too many tool calls");
    return;
  }

  // Execute tool...
}

Next Steps