Example: Function calling
Example: Function calling
For the complete documentation index, see llms.txt.
This example answers an inbound call, connects it to Ultravox, and handles client tool invocations via Ultravox data messages.
⬇️ Jump to the Full VoxEngine scenario.
Prerequisites
- Set up an inbound entrypoint for the caller:
- Create a routing rule that points the destination (phone number / WhatsApp / SIP username / app user alias) to this scenario.
- Store your Ultravox API key in Voximplant Secrets under
ULTRAVOX_API_KEY. - Configure tools in Ultravox (agent or call config) so tool invocations are emitted during the call.
Tool invocation flow
Ultravox emits a ClientToolInvocation data message when the agent calls a client tool. The event payload includes:
toolNameinvocationIdparameters(tool input)
The example listens for these events and returns a client_tool_result response using voiceAIClient.clientToolResult(...).
Tool response (from the example)
1 voiceAIClient.clientToolResult({ 2 type: "client_tool_result", 3 invocationId, 4 responseType: "tool-response", 5 agentReaction: "speaks", 6 result: JSON.stringify({ location, temperature_f: 72, condition: "sunny" }), 7 });
Full VoxEngine scenario
voxeengine-ultravox-function-calling.js
1 /** 2 * Voximplant + Ultravox WebSocket API connector demo 3 * Scenario: answer an incoming call and handle client tool invocations. 4 */ 5 6 require(Modules.Ultravox); 7 const SYSTEM_PROMPT = `You are a helpful phone assistant for Voximplant callers. 8 If you need external data, call the get_weather tool. 9 If the caller wants to end the call, call the hangup_call tool.`; 10 11 // -------------------- Ultravox WebSocket API settings -------------------- 12 const AGENT_CONFIG = { 13 systemPrompt: SYSTEM_PROMPT, 14 model: "ultravox-v0.7", 15 voice: "Mark", 16 }; 17 const WEATHER_TOOL_NAME = "get_weather"; // Needs to be defined in the Ultravox portal 18 const HANGUP_TOOL_NAME = "hangUp"; // Built-in Ultravox tool 19 20 VoxEngine.addEventListener(AppEvents.CallAlerting, async ({call}) => { 21 let voiceAIClient; 22 let hangupAfterResponse = false; 23 let hangupTimer; 24 25 // Termination functions - add cleanup and logging as needed 26 call.addEventListener(CallEvents.Disconnected, ()=>VoxEngine.terminate()); 27 call.addEventListener(CallEvents.Failed, ()=>VoxEngine.terminate()); 28 29 try { 30 call.answer(); 31 // call.record({ hd_audio: true, stereo: true }); // Optional: record the call 32 33 // Create client and wire media 34 voiceAIClient = await Ultravox.createWebSocketAPIClient( 35 { 36 endpoint: Ultravox.HTTPEndpoint.CREATE_CALL, 37 authorizations: {"X-API-Key": VoxEngine.getSecretValue('ULTRAVOX_API_KEY')}, 38 body: AGENT_CONFIG, 39 onWebSocketClose: (event) => { 40 Logger.write("===ULTRAVOX_WEBSOCKET_CLOSED==="); 41 if (event) Logger.write(JSON.stringify(event)); 42 VoxEngine.terminate(); 43 }, 44 }, 45 ); 46 47 VoxEngine.sendMediaBetween(call, voiceAIClient); 48 49 // ---------------------- Event handlers ----------------------- 50 // Function calling: handle tool requests and send back responses 51 voiceAIClient.addEventListener( 52 Ultravox.WebSocketAPIEvents.ClientToolInvocation, 53 (event) => { 54 const payload = event?.data?.payload || event?.data || {}; 55 const {toolName, invocationId, parameters} = payload; 56 57 if (!toolName || !invocationId) return; 58 59 if (toolName !== WEATHER_TOOL_NAME && toolName !== HANGUP_TOOL_NAME) { 60 voiceAIClient.clientToolResult({ 61 type: "client_tool_result", 62 invocationId, 63 errorType: "tool_not_found", 64 errorMessage: `Unhandled tool: ${toolName}`, 65 }); 66 return; 67 } 68 69 if (toolName === WEATHER_TOOL_NAME) { 70 const location = parameters?.location || "Unknown"; 71 const result = JSON.stringify({ 72 location, 73 temperature_f: 72, 74 condition: "sunny", 75 }); 76 77 voiceAIClient.clientToolResult({ 78 type: "client_tool_result", 79 invocationId, 80 responseType: "tool-response", 81 agentReaction: "speaks", 82 result, 83 }); 84 return; 85 } 86 87 if (toolName === HANGUP_TOOL_NAME) { 88 hangupAfterResponse = true; 89 voiceAIClient.clientToolResult({ 90 type: "client_tool_result", 91 invocationId, 92 responseType: "tool-response", 93 agentReaction: "speaks", 94 result: JSON.stringify({ result: "Hanging up now. Goodbye!" }), 95 }); 96 } 97 }, 98 ); 99 100 // Barge-in: keep conversation responsive and capture transcript 101 voiceAIClient.addEventListener( 102 Ultravox.WebSocketAPIEvents.Transcript, 103 (event) => { 104 const payload = event?.data?.payload || event?.data || {}; 105 const role = payload.role; 106 const text = payload.text || payload.delta; 107 108 if (role && text) Logger.write(`===TRANSCRIPT=== ${role}: ${text}`); 109 if (role === "user") voiceAIClient.clearMediaBuffer(); 110 if (hangupAfterResponse && role === "assistant" && payload.text) { 111 if (hangupTimer) clearTimeout(hangupTimer); 112 hangupTimer = setTimeout(() => call.hangup(), 1200); 113 } 114 }, 115 ); 116 117 voiceAIClient.addEventListener( 118 Ultravox.WebSocketAPIEvents.PlaybackClearBuffer, 119 () => voiceAIClient.clearMediaBuffer(), 120 ); 121 122 // If the media stream ends after a goodbye response, hang up the call. 123 voiceAIClient.addEventListener(Ultravox.Events.WebSocketMediaEnded, () => { 124 if (hangupAfterResponse) call.hangup(); 125 }); 126 127 // Consolidated "log-only" handlers - key Ultravox/VoxEngine debugging events 128 [ 129 Ultravox.WebSocketAPIEvents.ConnectorInformation, 130 Ultravox.WebSocketAPIEvents.HTTPResponse, 131 Ultravox.WebSocketAPIEvents.State, 132 Ultravox.WebSocketAPIEvents.Debug, 133 Ultravox.WebSocketAPIEvents.WebSocketError, 134 Ultravox.WebSocketAPIEvents.Unknown, 135 Ultravox.Events.WebSocketMediaStarted, 136 Ultravox.Events.WebSocketMediaEnded, 137 ].forEach((eventName) => { 138 voiceAIClient.addEventListener(eventName, (event) => { 139 Logger.write(`===${event.name}===`); 140 Logger.write(JSON.stringify(event)); 141 }); 142 }); 143 } catch (error) { 144 Logger.write("===SOMETHING_WENT_WRONG==="); 145 Logger.write(error); 146 VoxEngine.terminate(); 147 } 148 });