| title | AVR Function Calls |
|---|---|
| description | |
| published | true |
| date | 2025-12-16 12:40:25 UTC |
| tags | avr, transfer, hangup, function calls, tools, avr_tools |
| editor | markdown |
| dateCreated | 2025-09-04 14:35:17 UTC |
AVR (Agent Voice Response) supports function calling capabilities, allowing developers to create custom tools that can be invoked during real-time speech-to-speech conversations. This guide explains how to create, implement, and use custom function calls in the AVR system.
The function calling system in AVR consists of two main directories:
avr_tools/- Project-provided tools (core functionality)tools/- User custom tools (extensible functionality)
Tools are automatically loaded at runtime and made available to the LLM for function calling.
AVR comes with two essential built-in function calls that are automatically available in every conversation:
Purpose: Transfers the current call to a specific extension, commonly used to connect users with human operators.
Usage: The AI agent can automatically transfer calls when:
- A user requests to speak with a human operator
- The conversation reaches a point where human intervention is needed
- The AI determines it cannot handle a specific request
Parameters:
transfer_extension(required): The extension number to transfer totransfer_context(optional): The context/department for the transfertransfer_priority(optional): Priority level for the transfer
Example AI Usage: "I understand you'd like to speak with a human operator. Let me transfer you to our customer service team."
Purpose: Forces the virtual agent to end the conversation and hang up the call.
Usage: The AI agent can automatically hang up when:
- A task or conversation is completed
- No further assistance is needed
- The user's request has been fulfilled
- Maintenance or booking has been completed
Parameters: None required
Example AI Usage: "Your appointment has been successfully scheduled. Thank you for calling, and have a great day!"
These default tools ensure that every AVR deployment has the essential call management capabilities without requiring additional development.
Each tool must follow a specific structure defined by the following properties:
module.exports = {
name: "tool_name", // Unique identifier for the tool
description: "Tool description", // Human-readable description
input_schema: {}, // JSON Schema for input validation
handler: async (uuid, args) => {} // Function to execute
};- Must be unique across all tools
- Used by LLM to identify which tool to call
- Should be descriptive and follow snake_case convention
- Example:
"get_weather","avr_transfer","avr_hangup"
- Clear explanation of what the tool does
- Used by LLM to understand when to call this tool
- Should be concise but informative
- Example:
"Retrieves weather information for a specific location"
- JSON Schema that defines the expected input parameters
- LLM uses this to validate and structure the input data
- Must follow JSON Schema specification
- Async function that executes the tool's logic
- Receives two parameters:
uuidandargs - Must return a string response that will be sent back to the user
Create a new JavaScript file in the tools/ directory:
touch path/to/tools/my_custom_tool.js// tools/my_custom_tool.js
module.exports = {
name: "my_custom_tool",
description: "Performs a custom operation based on user input",
input_schema: {
type: "object",
properties: {
parameter1: {
type: "string",
description: "First parameter description"
},
parameter2: {
type: "number",
description: "Second parameter description"
}
},
required: ["parameter1"] // parameter2 is optional
},
handler: async (uuid, { parameter1, parameter2 }) => {
try {
// Your custom logic here
const result = await performCustomOperation(parameter1, parameter2);
return `Operation completed successfully: ${result}`;
} catch (error) {
return `Error occurred: ${error.message}`;
}
}
};After adding a new tool, restart the AVR services for changes to take effect.
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The name of the location"
}
},
required: ["location"]
}input_schema: {
type: "object",
properties: {
name: {
type: "string",
description: "User's full name"
},
age: {
type: "number",
description: "User's age in years"
},
isActive: {
type: "boolean",
description: "Whether the user account is active"
}
},
required: ["name", "age"]
}input_schema: {
type: "object",
properties: {
items: {
type: "array",
items: {
type: "string"
},
description: "List of items to process"
}
},
required: ["items"]
}handler: async (uuid, args) => {
// uuid: Session identifier for tracking
// args: Object containing the validated input parameters
}-
uuid(string): Unique session identifier for the current conversation- Use this for logging, tracking, or making session-specific API calls
- Example:
"550e8400-e29b-41d4-a716-446655440000"
-
args(object): Destructured parameters from the input schema- Contains the validated parameters as defined in
input_schema - Example:
{ location: "New York" }
- Contains the validated parameters as defined in
The handler must return a string that will be:
- Sent back to OpenAI as instructions
- Converted to speech and sent to the user
- Used to continue the conversation
Always implement proper error handling in your handlers:
handler: async (uuid, args) => {
try {
// Your logic here
const result = await someAsyncOperation(args);
return `Success: ${result}`;
} catch (error) {
console.error(`Error in ${this.name}:`, error);
return `I encountered an error: ${error.message}`;
}
}// tools/get_weather.js
const axios = require('axios');
module.exports = {
name: "get_weather",
description: "Retrieves current weather information for a specified location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city or location name"
},
units: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Temperature units (default: celsius)"
}
},
required: ["location"]
},
handler: async (uuid, { location, units = "celsius" }) => {
try {
// Simulate weather API call
const weather = await getWeatherData(location);
return `The weather in ${location} is currently ${weather.condition} with a temperature of ${weather.temperature}°${units === "fahrenheit" ? "F" : "C"}.`;
} catch (error) {
return `I'm sorry, I couldn't retrieve the weather for ${location}. Please try again.`;
}
}
};// tools/avr_transfer.js
require("dotenv").config();
const axios = require("axios");
module.exports = {
name: "avr_transfer",
description: "Transfers the call to a designated internal extension when the user requests to speak with an internal operator or be redirected to another extension. Optional context and priority information may be included to support proper call handling and routing.",
input_schema: {
type: "object",
properties: {
transfer_extension: {
type: "string",
description: "The transfer extension to transfer the call to.",
},
transfer_context: {
type: "string",
description: "The context to transfer the call to.",
},
transfer_priority: {
type: "string",
description: "The priority of the transfer.",
},
},
required: ["transfer_extension"],
},
handler: async (
uuid,
{ transfer_extension, transfer_context, transfer_priority }
) => {
console.log("Transfering call to:", transfer_extension);
console.log("UUID:", uuid);
try {
const url = process.env.AMI_URL || "http://127.0.0.1:6006";
const res = await axios.post(`${url}/transfer`, {
uuid,
exten: transfer_extension,
context: transfer_context || "demo",
priority: transfer_priority || 1,
});
console.log("Transfer response:", res.data);
return res.data.message;
} catch (error) {
console.error("Error during transfer:", error.message);
return `Error during transfer: ${error.message}`;
}
},
};// tools/avr_hangup.js
require("dotenv").config();
const axios = require("axios");
module.exports = {
name: "avr_hangup",
description:
"Ends the call when the customer has no further information to request, after all relevant actions have been completed, or when the customer explicitly says goodbye, ensuring a clean and graceful termination of the interaction.",
input_schema: {
type: "object",
properties: {},
required: [],
},
handler: async (uuid, {}) => {
console.log("Hangup call");
const url = process.env.AMI_URL || "http://127.0.0.1:6006";
try {
const res = await axios.post(`${url}/hangup`, { uuid });
console.log("Hangup response:", res.data);
return res.data.message;
} catch (error) {
console.error("Error during hangup:", error.message);
return `Error during hangup: ${error.message}`;
}
},
};
- Use descriptive, lowercase names with underscores
- Prefix AVR-specific tools with
avr_ - Keep names short but meaningful
- Always wrap your logic in try-catch blocks
- Return user-friendly error messages
- Log errors for debugging purposes
- Use comprehensive JSON schemas
- Make required fields explicit
- Provide clear descriptions for all parameters
- Keep responses conversational and natural
- Include relevant information from the operation
- Use appropriate language for the context
- Keep handlers lightweight and fast
- Use async/await for external API calls
- Avoid blocking operations
-
Tool not loading
- Check file syntax and exports
- Verify file is in the correct directory
- Restart the service after changes
-
Handler not executing
- Check logs for function call attempts
- Verify the tool name matches exactly
- Ensure the handler function is properly exported
-
Parameter validation errors
- Review your JSON schema syntax
- Check required field definitions
- Verify parameter types match expected values
- Add console.log statements in your handlers
- Check the AVR service logs for errors
- Verify tool loading in the startup logs
- Test with simple tools first before complex implementations
Your tools can access environment variables through process.env:
handler: async (uuid, args) => {
const apiKey = process.env.MY_API_KEY;
const baseUrl = process.env.MY_SERVICE_URL;
// Use these in your tool logic
}- Never expose sensitive information in tool responses
- Validate all input parameters thoroughly
- Use environment variables for API keys and secrets
- Implement rate limiting for external API calls
- Sanitize user input to prevent injection attacks
Function calls in AVR provide a powerful way to extend the system's capabilities. By following this guide, you can create robust, reliable tools that enhance the user experience and provide valuable functionality during voice conversations.
The built-in avr_transfer and avr_hangup tools ensure that every deployment has essential call management capabilities, while the extensible tools/ directory allows you to add custom functionality specific to your use case.
For additional support and examples, refer to the existing tools in the avr_tools/ directory and the main AVR documentation.