Tools in Ultravox (also known as function calling) are a powerful way to extend your agents' capabilities by connecting them to external services and systems. At their core, tools are simply functions that agents can invoke to perform specific actions or retrieve information.

Here are some of the things you can do with tools:

Communicate with the Outside world

Lookup the weather, get movie times, create calendar events, or send emails.

Order Lookup

Lookup orders, backordered items, or provide shipment updates.

Knowledge Base

Consult product and support documentation for contextual support.

Create Support Case

Open tailored support cases for human follow-up.

Transfer Call

Hand-off or escalate calls to human support agents.

End Call

End calls due to user inactivity or after successful resolution.

Any functionality you can encapsulate in a function can be exposed to your agents as a tool. Addtionally, Ultravox automatically calls the underlying function so you don't have to sweat gluing things together.
Tools in Ultravox are Different

Unlike using tools with single-generation LLM APIs, Ultravox actually calls your tool. This means you need to do a bit more work upfront in defining tools with the proper authentication and parameters.

Use ultravox-70B for Tools

While tools are supported across multiple variants of the Ultravox model, using tools with smaller models (i.e. 8B) typically don’t work well. YMMV. See available models for more info.

Creating Your First Tool

Let’s look at creating a tool that sends an email with a summary of the conversation.

There are three steps:

1

Define the Tool

We need to define the tool and provide it to our agent. Everything we provide here will be seen by the agent so we need to be thoughtful of names, descriptions, parameters, etc.

{
  "systemPrompt": "You are a helpful assistant...",
  "model": "fixie-ai/ultravox",
  "selectedTools": [
    {
      "temporaryTool": {
        "modelToolName": "sendConversationSummary",
        "description": "Use this tool at the end of a conversation to send the caller a summary of the conversation.",
        "dynamicParameters": [
          {
            "name": "conversationSummary",
            "location": "PARAMATER_LOCATION_BODY",
            "schema": {
              "description": "A 2-3 sentence summary of the conversation.",
              "type": "string"
            },
            "required": true
          }
        ],
        "http": {
          "baseUrlPattern": "https://foo.bar/sendSummary",
          "httpMethod": "POST"
        }
      }
    }
  ]
}

What’s happening here:

  • We are adding selectedTools to the request body of the Create Call.
  • There’s a single tool named sendConversationSummary.
  • This tool requires a single dynamic parameter called conversationSummary that is passed in the request body.
  • The tool’s functionality is available via POST at the url https://foo.bar/sendSummary.
2

Implement the Function

Now that we’ve defined the tool, let’s implement the functionality. This is a simplified example using Express.js and imagines a generic email API provider.

const express = require('express');
const router = express.Router();

router.post('/sendSummary', async (req, res) => {
  try {
    const { conversationSummary } = req.body;

    // Send the email using our email provider
    sendEmail(conversationSummary);

    return res.status(200).json({
      message: 'Conversation summary sent successfully. Continue the conversation with the user.'
    });
  } catch (error) {
    return res.status(500).json({
      message: 'Internal server error',
      error: error.message
    });
  }
});

module.exports = router;

This function does the following:

  • Accepts the conversationSummary via a POST.
  • Passes the data along to another function (sendEmail) that will send it via email.
3

Instruct the Agent on Tool Use

The last thing we need to do is provide additional instructions to the agent on how to use the tool. This is done by adding to the systemPrompt. Let’s update what we used in the first step.


const updatedPrompt = `
  You're a friendly and fun guy. You like to chat casually while learning more about the person you're chatting with (name, hobbies, likes/dislikes). 
  
  Be casual. Be fun to chat with. Don't talk too much. Keep your sentences pretty short and fun. Let the user guide the conversation.
  
  As you chat, try and learn more about the person you are talking to such as their name, hobbies, and their likes/dislikes. 
  
  Once you have all the information, call the 'sendSummary' tool to send a summary of the conversation.
`;
{
  "systemPrompt": updatedPrompt,
  "model": "fixie-ai/ultravox",
  "selectedTools": [
      // Same as before
  ]
}

We’ve updated the system prompt that is used when the Ultravox call is created to instruct the agent when and how to use the tool.

Server vs. Client Tools

You can implement your tools as either server (the tool’s functionality is exposed via a URL) or client (the tool’s functionality is implemented in your client application) tools. From the perspective of Ultravox Realtime, there is no difference between server or client tools. When the model chooses to call a tool, Ultravox will simply call the tool however it’s defined.

There are a couple things to note:

Tool Authentication

Ultravox has rich support for tools auth. When creating a tool, you must specify what is required for successful auth to the backend service.

Three methods for passing API keys are supported and are used when creating the tool:

1

Query Parameter

The API key will be passed via the query string. The name of the parameter must be provided when the tool is created.
2

Header

The API key will be passed via a custom header. The name of the header must be provided when the tool is created.
3

HTTP Authentication

The API key will be passed via the HTTP Authentication header. The name of the scheme (e.g. Bearer) must be provided when the tool is created.

You then pass in the key(s) in the authTokens property of selectedTools when creating a call.

// Create a tool that uses a query parameter called 'apiKey'
{
  "name": "stock_price"
  "definition": {
    "description": "Get the current stock price for a given symbol",
    "requirements": {
      "httpSecurityOptions": {
        "options": [
          "requirements": {
            "mySeviceApiKey": {
              "queryApiKey": {
                "name": "apiKey"
              }
            }
          }
        ]
      }
    }
  }
}

// Pass the API key during call creation
{
  "model": "fixie-ai/ultravox-70B"
  "systemPrompt": ...
  "selectedTools": [
    {
      "toolName": "stock_price"
      "authTokens": {
        "myServiceApiKey": "your_token_here"
      }
    }
  ]
}

Tool Parameters

Tool parameters define what gets passed in to your backend service when the tool is called. When creating a tool, parameters are defined as one of three types:

1

Dynamic

The model will choose which values to pass. These are the parameters you’d use for a single-generation LLM API. Can be overridden (see below).
2

Static

Value is known when the tool is defined and is unconditionally set on invocations. Not exposed to or set by the model.
3

Automatic

Like “Static”, except that the value may not be known when the tool is defined but will instead be populated by the system when the tool is invoked.

Dynamic Parameters

Dynamic parameters will have their values set by the model. Creating a dynamic parameter on a tool looks like this:

// Adding a dynamic parameter to a stock price tool
{
  "name": "stock_price",
  "description": "Get the current stock price for a given symbol",
  "dynamicParameters": [
    {
      "name": "symbol",
      "location": "PARAMETER_LOCATION_QUERY",
      "schema": {
        "type": "string",
        "description": "Stock symbol (e.g., AAPL for Apple Inc.)"
      },
      "required": true
    }
  ]
}

Parameter Overrides

You can choose to set static values for dynamic parameters when you start a call. The model won’t see any parameters that you override. When creating a call simply pass in the overrides with each tool:

// Overriding dynamic parameter when starting a new call
// Always set the stock symbol to 'NVDA'
{
  "model": "fixie-ai/ultravox-70B",
  "systemPrompt": ...
  "selectedTools": [
    "toolName": "stock_price",
    "parameterOverrides": {
      "symbol": "NVDA"
    }
  ]
}

Parameter overrides don’t make sense for temporary tools. Instead of overriding a dynamic parameter, use a static parameter instead.

Static Parameters

If you have parameters that are known at the time you create the tool, static parameters can be used.

// Adding a static parameter that always sends utm=ultravox
{
  "name": "stock_price",
  "description": "Get the current stock price for a given symbol",
  "staticParameters": [
    {
      "name": "utm",
      "location": "PARAMETER_LOCATION_QUERY",
      "value": "ultravox"
    }
  ]
}

Automatic Parameters

Automatic parameters are used when you don’t want the model to specify the value and you don’t know the value when the tool is created. The primary use case for automatic parameters today is for using the call_id that is generated for the current call and then passing it as a unique identifier to your tool. Can also be used to get the current conversation history.

// Adding an automatic parameter to a profile creation tool
{
  "name": "create_profile",
  "description": "Creates a profile for the current caller",
  "automaticParameters": [
    {
      "name": "call_id",
      "location": "PARAMETER_LOCATION_QUERY",
      "knownValue": "KNOWN_PARAM_CALL_ID"
    }
  ]
}

Temporary vs. Durable Tools

Ultravox supports two types of tools: temporary and durable. There is much more information below but there are a few things to consider right upfront:

1

Creation

Temporary tools are created in the request body when a new call is created. Durable tools are created using the Ultravox REST API.
2

No Functional Difference

There is no functional difference within the context of an Ultravox call between the two tool types.
3

Iteration Speed

Temporary tools are great when you are building out a new application and need to iterate.
4

Reuse & Collaboration

Durable tools are best when you have things dialed in and want to reuse tools across applications and/or work with a team and want to divide ownership of tools from the rest of your app.

Temporary Tools

Temporary tools are created each time you create a new Call and exist exclusively within the context of that call. (Temporary tools aren’t visible in the List Tools response for example.)

Iteration is faster when using temporary tools because you don’t have to create/update/delete tools as you build out your application. You can simply adjust the JSON in the request body and start a new call.

Creating & Using Temporary Tools

Temporary tools are defined and passed in the request body of the Create Call endpoint. They are available during the current call.

{
  "model": "fixie-ai/ultravox-70B",
  "systemPrompt": ...
  "selectedTools": [
    "temporaryTool": {
      "modelToolName": "stock_price",
      "description": "Get the current stock price for a given symbol",
      "dynamicParameters": [
        {
          "name": "symbol",
          "location": "PARAMETER_LOCATION_QUERY",
          "schema": {
            "type": "string",
            "description": "Stock symbol (e.g., AAPL for Apple Inc.)"
          },
          "required": true
        }
      ],
      "http": {
        "baseUrlPattern": "https://api.stockmarket.com/v1/price",
        "httpMethod": "GET"
      }
    }
  ]
}

Durable Tools

In addition to temporary tools, Ultravox supports the creation of durable tools. There is no functional difference between durable and temporary tools within the context of a call.

Durable tools are persisted and can be reused across calls or applications. They shine once you have things dialed in, when you want to share tools across multiple applications, or if you have split responsibilities on the team.

Creating Durable Tools

The /tools endpoint in the Ultravox API is for working with durable tools. You create durable tools either by uploading an OpenAPI spec or via the request body in the Create Tool endpoint. Your OpenAPI spec must be either json or yaml format.

Using Durable Tools

To use a durable tool in a call, set the toolName or toolId field instead of using a temporaryTool.

For example:

// Request body for creating an Ultravox call with a durable tool
{
  "model": "fixie-ai/ultravox-70B",
  "systemPrompt": ...
  "selectedTools": [
    "toolName": "stock_price",
  ]
}

Additional Information

Changing Call State

For most tools, the response will include data you want the model to use (e.g. the results of a lookup). However, Ultravox has support for special tool actions that can end the call or change the call stage.

These tool actions require setting a special response type.

Response TypeTool Action
hang-upTerminates the call. In addition to having Ultravox end the call after periods of user inactivity, your custom tool can end the call.
new-stageCreates a new call stage. See here for more.

How you set the response type depends on if you are using a server/HTTP or a client tool:

Server/HTTP Tools

Server tools must respond with the X-Ultravox-Response-Type header set to either hang-up or new-stage

Client Tools

For client tools, set responseType="hang-up" or responseType="new-stage" on your ClientToolResult object.

Debugging

The Ultravox SDK enables viewing debug messages for any active call. These messages include tool calls.

Tool Definition Schema

The definition object in the tool creation and update requests follows the BaseToolDefinition schema.

Here’s a breakdown of its main components:

  • description (string): A clear, concise description of what the tool does.
  • dynamicParameters (array, optional): List of parameters that can be set by the AI model when using the tool. Each parameter is an object containining:
    • name (string): The name of the parameter.
    • location (string): Where the parameter is used (“PARAMETER_LOCATION_QUERY”, “PARAMETER_LOCATION_PATH”, “PARAMETER_LOCATION_HEADER”, “PARAMETER_LOCATION_BODY”).
    • schema (object): JSON Schema definition of the parameter. This typically includes things like type, description, enum values, format, other restrictions, etc.
    • required (boolean): Whether the parameter is required.
  • staticParameters (array, optional): List of parameters that are always set to a known, fixed value when the tool is used. These are unconditionally added when the tool is invoked. These parameters are not exposed to or set by the model. Example: you use an API for various things but want to track which requests come from your Ultravox app so you always append utm=ultravox to the query parameters.
  • automaticParameters (array, optional): Additional parameters automatically set by the system. Used when the value is not known when the tool is created but that will be known when the tool is called. Example: you want to use the unique call_id from ultravox as a key in your backend and you have the tool include call_id in the request body when your tool’s API is called.
  • requirements (object, optional): Any specific requirements for using the tool. Currently this is used for security (e.g. API keys or HTTP Auth).
  • http (object): Details for invoking the tool via HTTP. For server tools.
    • baseUrlPattern (string): The base URL pattern for the tool, possibly with placeholders for path parameters.
    • httpMethod (string): The HTTP method for the tool (e.g., “GET”, “POST”).
  • client (object): Declares the tool as a client tool. Exactly one of http or client must be set for a tool.

Best Practices for Creating Tools

  1. Clear Naming: Choose a descriptive and unique name for your tool that clearly indicates its function.

  2. Detailed Description: Provide a comprehensive description of what the tool does, including any important details about its usage or limitations. This and the name will help the model decide when and how to use your tool.

  3. Precise Parameters: Define your dynamic parameters carefully, ensuring that the AI model has all the information it needs to use the tool effectively.

  4. Error Handling: Consider how your tool will handle errors or unexpected inputs, and document this behavior in the tool’s description.

  5. Iterate Faster: Use temporary tools when you are building your application. Persist durable tools in the system when things have stabilized.

  6. Version Control: When updating tools, consider creating a new version (e.g., “stock_price_v2”) rather than modifying the existing tool. This allows testing of the new tool before impacting new calls made with the prior version of the tool.

  7. Security: Be mindful of security when creating tools, especially when they interact with external APIs. Use appropriate authentication methods and avoid exposing sensitive information.

  8. Testing: Thoroughly test your tools before deploying them in production conversations to ensure they function as expected.