Skip to main content

Tool Execution

BlackBox agents can call tools/functions during conversations to interact with external systems, query databases, perform actions, and retrieve real-time information. This guide covers handling tool execution requests via WebSocket.

Overview

Tools enable agents to:
  • Query external APIs and databases
  • Perform actions (create records, send emails, etc.)
  • Retrieve real-time information
  • Integrate with your business systems
There are two ways agents can call tools:
  1. Webhook Tools: Agent calls webhook endpoint (traditional method)
  2. WebSocket Tools: Agent sends tool request via WebSocket (real-time)
This guide focuses on WebSocket tool execution for low-latency, real-time integrations.
When to Use WebSocket Tools: WebSocket tool execution should only be used when the tool needs to interact directly with the user or user’s application in the browser. Examples include:
  • UI Interactions: Switching browser tabs, scrolling pages, clicking buttons
  • Form Manipulation: Writing text in input fields, selecting dropdown options, checking checkboxes
  • Browser Actions: Opening new windows, navigating to URLs, manipulating DOM elements
  • Client-Side State: Accessing localStorage, sessionStorage, or browser APIs
For server-side operations (API calls, database queries, sending emails, etc.), use webhook tools instead. Webhook tools execute on your server and don’t require direct browser access.

Tool Execution Flow

Step 1: Configure Tools for WebSocket

Tools must be configured to use WebSocket execution:

Via Web Integration Settings

In your web integration configuration, select tools that should use WebSocket:
  1. Navigate to agent → Web Integrations
  2. Select your integration
  3. Go to Features tab
  4. Select tools from the dropdown Allowed Tools
  5. Selected tools will be called via WebSocket

Via API

When creating/updating web integration, specify tools in toolsViaWebsocket:
{
  "toolsViaWebsocket": [
    "get_customer_info",
    "create_support_ticket",
    "check_inventory"
  ]
}

Step 2: Handle Tool Requests

Handle websocketToolRequest messages via WebSocket:
const ws = new WebSocket(
  'wss://blackbox.dasha.ai/api/v1/ws/webCall?token=YOUR_WEB_INTEGRATION_TOKEN'
);

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'initialize',
    timestamp: new Date().toISOString(),
    request: {
      callType: 'chat',
      additionalData: {}
    }
  }));
};

ws.onmessage = async (event) => {
  const message = JSON.parse(event.data);
  
  if (message.type === 'websocketToolRequest') {
    const { id, toolName, args } = message.content;
    
    try {
      // Execute tool
      const result = await executeTool(toolName, args);
      
      // Send response
      ws.send(JSON.stringify({
        type: 'websocketToolResponse',
        timestamp: new Date().toISOString(),
        content: {
          id: id,
          result: result
        }
      }));
    } catch (error) {
      // Send error response
      ws.send(JSON.stringify({
        type: 'websocketToolResponse',
        timestamp: new Date().toISOString(),
        content: {
          id: id,
          result: {
            status: 'error',
            message: error instanceof Error ? error.message : 'Unknown error'
          }
        }
      }));
    }
  }
};

async function executeTool(name: string, args: Record<string, unknown>): Promise<unknown> {
  switch (name) {
    case 'get_customer_info':
      return await getCustomerInfo(args.customerId as string);
      
    case 'create_support_ticket':
      return await createSupportTicket(args);
      
    case 'check_inventory':
      return await checkInventory(args.productId as string);
      
    default:
      return {
        status: 'error',
        message: `Unknown tool: ${name}`
      };
  }
}

Manual WebSocket Implementation

Handle websocketToolRequest messages directly:
ws.onmessage = async (event) => {
  const message = JSON.parse(event.data);
  
  if (message.type === 'websocketToolRequest') {
    const { id, toolName, args } = message.content;
    
    try {
      // Execute tool
      const result = await executeTool(toolName, args);
      
      // Send response
      ws.send(JSON.stringify({
        type: 'websocketToolResponse',
        timestamp: new Date().toISOString(),
        content: {
          id: id,
          result: result
        }
      }));
    } catch (error) {
      // Send error response
      ws.send(JSON.stringify({
        type: 'websocketToolResponse',
        timestamp: new Date().toISOString(),
        content: {
          id: id,
          result: {
            status: 'error',
            message: error.message
          }
        }
      }));
    }
  }
};

Step 3: Implement Tool Functions

Example: Customer Info Tool

async function getCustomerInfo(customerId: string): Promise<unknown> {
  try {
    // Query your database or API
    const customer = await fetch(`/api/customers/${customerId}`)
      .then(res => res.json());
    
    return {
      status: 'success',
      data: {
        customerId: customer.id,
        name: customer.name,
        email: customer.email,
        phone: customer.phone,
        accountStatus: customer.status
      }
    };
  } catch (error) {
    return {
      status: 'error',
      message: `Failed to fetch customer: ${error.message}`
    };
  }
}

Example: Support Ticket Tool

async function createSupportTicket(args: Record<string, unknown>): Promise<unknown> {
  try {
    const ticket = await fetch('/api/support/tickets', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        subject: args.subject as string,
        description: args.description as string,
        priority: args.priority as string || 'normal',
        customerId: args.customerId as string
      })
    }).then(res => res.json());
    
    return {
      status: 'success',
      ticketId: ticket.id,
      message: `Support ticket #${ticket.id} created successfully`
    };
  } catch (error) {
    return {
      status: 'error',
      message: `Failed to create ticket: ${error.message}`
    };
  }
}

Example: Inventory Check Tool

async function checkInventory(productId: string): Promise<unknown> {
  try {
    const product = await fetch(`/api/products/${productId}/inventory`)
      .then(res => res.json());
    
    return {
      status: 'success',
      productId: product.id,
      inStock: product.quantity > 0,
      quantity: product.quantity,
      message: product.quantity > 0
        ? `${product.quantity} units available`
        : 'Out of stock'
    };
  } catch (error) {
    return {
      status: 'error',
      message: `Failed to check inventory: ${error.message}`
    };
  }
}

Step 4: Complete Example

Here’s a complete React component handling tool execution:
import React, { useState, useEffect, useRef } from 'react';

export function ChatWithTools() {
  const [messages, setMessages] = useState<string[]>([]);
  const [tools, setTools] = useState<Array<{ name: string; status: string }>>([]);
  const wsRef = useRef<WebSocket | null>(null);

  useEffect(() => {
    const ws = new WebSocket(
      'wss://blackbox.dasha.ai/api/v1/ws/webCall?token=YOUR_WEB_INTEGRATION_TOKEN'
    );
    wsRef.current = ws;

    ws.onopen = () => {
      ws.send(JSON.stringify({
        type: 'initialize',
        timestamp: new Date().toISOString(),
        request: {
          callType: 'chat',
          additionalData: {}
        }
      }));
    };

    ws.onmessage = async (event) => {
      const message = JSON.parse(event.data);
      
      if (message.type === 'text' && message.content.source === 'assistant') {
        setMessages(prev => [...prev, `Agent: ${message.content.text}`]);
      } else if (message.type === 'toolCall') {
        setMessages(prev => [...prev, `🔧 Tool called: ${message.name}`]);
      } else if (message.type === 'toolCallResult') {
        setMessages(prev => [...prev, `✅ Tool completed: ${message.name}`]);
      } else if (message.type === 'websocketToolRequest') {
        const { id, toolName, args } = message.content;
        setTools(prev => [...prev, { name: toolName, status: 'executing' }]);
        
        try {
          const result = await executeTool(toolName, args);
          
          setTools(prev => prev.map(t => 
            t.name === toolName ? { ...t, status: 'completed' } : t
          ));
          
          // Send tool response
          ws.send(JSON.stringify({
            type: 'websocketToolResponse',
            timestamp: new Date().toISOString(),
            content: {
              id: id,
              result: result
            }
          }));
        } catch (error) {
          setTools(prev => prev.map(t => 
            t.name === toolName ? { ...t, status: 'error' } : t
          ));
          
          ws.send(JSON.stringify({
            type: 'websocketToolResponse',
            timestamp: new Date().toISOString(),
            content: {
              id: id,
              result: {
                status: 'error',
                message: error instanceof Error ? error.message : 'Unknown error'
              }
            }
          }));
        }
      }
    };

    return () => {
      if (wsRef.current) {
        wsRef.current.close();
      }
    };
  }, []);

  const executeTool = async (name: string, args: Record<string, unknown>): Promise<unknown> => {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    switch (name) {
      case 'get_customer_info':
        return {
          status: 'success',
          data: {
            customerId: args.customerId,
            name: 'John Doe',
            email: 'john@example.com'
          }
        };
        
      case 'create_support_ticket':
        return {
          status: 'success',
          ticketId: 'TKT-12345',
          message: 'Ticket created successfully'
        };
        
      default:
        return {
          status: 'error',
          message: `Unknown tool: ${name}`
        };
    }
  };

  const sendMessage = (text: string) => {
    if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
      wsRef.current.send(JSON.stringify({
        type: 'incomingChatMessage',
        content: text,
        timestamp: new Date().toISOString()
      }));
      setMessages(prev => [...prev, `You: ${text}`]);
    }
  };

  return (
    <div>
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i}>{msg}</div>
        ))}
      </div>
      
      <div className="tools">
        <h3>Tool Execution Status</h3>
        {tools.map((tool, i) => (
          <div key={i}>
            {tool.name}: {tool.status}
          </div>
        ))}
      </div>
      
      <input
        onKeyPress={(e) => {
          if (e.key === 'Enter') {
            sendMessage(e.currentTarget.value);
            e.currentTarget.value = '';
          }
        }}
        placeholder="Type a message..."
      />
    </div>
  );
}

Step 5: Error Handling

Handle tool execution errors gracefully:
onToolCall: async (name: string, args: Record<string, unknown>) => {
  try {
    // Validate arguments
    if (!validateArgs(name, args)) {
      return {
        status: 'error',
        message: 'Invalid arguments',
        details: getValidationErrors(name, args)
      };
    }
    
    // Execute tool with timeout
    const result = await Promise.race([
      executeTool(name, args),
      new Promise((_, reject) => 
        setTimeout(() => reject(new Error('Tool timeout')), 30000)
      )
    ]);
    
    return result;
  } catch (error) {
    console.error(`Tool execution error (${name}):`, error);
    
    return {
      status: 'error',
      message: error.message || 'Tool execution failed',
      errorCode: error.code || 'UNKNOWN_ERROR'
    };
  }
}

Step 6: Tool Result Format

Tool results should follow a consistent format:

Success Response

{
  status: 'success',
  data?: unknown,        // Tool-specific data
  message?: string,      // Optional success message
  // ... tool-specific fields
}

Error Response

{
  status: 'error',
  message: string,       // Error message
  errorCode?: string,    // Optional error code
  details?: unknown      // Optional error details
}

Example Responses

// Customer info tool
{
  status: 'success',
  data: {
    customerId: '123',
    name: 'John Doe',
    email: 'john@example.com'
  }
}

// Support ticket tool
{
  status: 'success',
  ticketId: 'TKT-12345',
  message: 'Ticket created successfully'
}

// Error response
{
  status: 'error',
  message: 'Customer not found',
  errorCode: 'CUSTOMER_NOT_FOUND'
}

Best Practices

1. Validate Arguments

Always validate tool arguments before execution:
function validateArgs(name: string, args: Record<string, unknown>): boolean {
  switch (name) {
    case 'get_customer_info':
      return typeof args.customerId === 'string' && args.customerId.length > 0;
    case 'create_support_ticket':
      return typeof args.subject === 'string' && args.subject.length > 0;
    default:
      return true;
  }
}

2. Handle Timeouts

Set reasonable timeouts for tool execution:
async function executeToolWithTimeout(
  name: string,
  args: Record<string, unknown>,
  timeoutMs: number = 30000
): Promise<unknown> {
  return Promise.race([
    executeTool(name, args),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Tool execution timeout')), timeoutMs)
    )
  ]);
}

3. Log Tool Execution

Log tool calls for debugging and monitoring:
onToolCall: async (name, args) => {
  const startTime = Date.now();
  console.log(`[Tool] ${name} called`, args);
  
  try {
    const result = await executeTool(name, args);
    const duration = Date.now() - startTime;
    console.log(`[Tool] ${name} completed in ${duration}ms`);
    return result;
  } catch (error) {
    const duration = Date.now() - startTime;
    console.error(`[Tool] ${name} failed after ${duration}ms:`, error);
    throw error;
  }
}

4. Cache Results

Cache frequently accessed data:
const cache = new Map<string, { data: unknown; expires: number }>();

async function getCachedCustomerInfo(customerId: string): Promise<unknown> {
  const cacheKey = `customer:${customerId}`;
  const cached = cache.get(cacheKey);
  
  if (cached && cached.expires > Date.now()) {
    return cached.data;
  }
  
  const data = await fetchCustomerInfo(customerId);
  cache.set(cacheKey, {
    data,
    expires: Date.now() + 60000 // 1 minute cache
  });
  
  return data;
}

Troubleshooting

Tool Not Called

Symptoms: Agent doesn’t call expected tool Solutions:
  1. Verify tool is configured in agent settings
  2. Check tool is selected in web integration
  3. Ensure tool name matches exactly
  4. Verify tool description helps agent understand when to use it

Tool Execution Fails

Symptoms: Tool called but returns error Solutions:
  1. Check argument validation
  2. Verify API endpoints are accessible
  3. Check authentication/authorization
  4. Review error logs for details
  5. Test tool function independently

Slow Tool Execution

Symptoms: Long delays before agent responds Solutions:
  1. Optimize tool implementation
  2. Add caching for frequent queries
  3. Set appropriate timeouts
  4. Consider async processing for long operations
  5. Use webhooks for slow tools instead

Next Steps