Example: Function calling

View as Markdown

For the complete documentation index, see llms.txt.

This example answers an inbound call, enables Gemini tool calling, and sends tool responses back to the model.

⬇️ Jump to the Full VoxEngine scenario.

Gemini 3.1 Flash Live Preview

This page reflects the current gemini-3.1-flash-live-preview flow from Google’s Live API docs: https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-live-preview

Prerequisites

Tool definitions

The full example also uses thinkingConfig: { thinkingLevel: "minimal" } to keep tool-calling turns responsive.

The example registers tools in connectConfig.tools using functionDeclarations and JSON Schema parameters:

1const TOOLS = [
2 {
3 functionDeclarations: [
4 {
5 name: "get_weather",
6 description: "Get current weather for a location (demo stub)",
7 parametersJsonSchema: {
8 type: "object",
9 properties: {
10 location: { type: "string" },
11 },
12 required: ["location"],
13 },
14 },
15 ],
16 },
17];

Handling tool calls

Gemini sends tool calls via Gemini.LiveAPIEvents.ToolCall. Respond with sendToolResponse:

1geminiLiveAPIClient.addEventListener(Gemini.LiveAPIEvents.ToolCall, (event) => {
2 const functionCalls = event?.data?.payload?.functionCalls || [];
3 const responses = functionCalls.map((fn) => ({
4 id: fn.id,
5 name: fn.name,
6 response: { output: { ok: true } },
7 }));
8 geminiLiveAPIClient.sendToolResponse({ functionResponses: responses });
9});

Notes

  • The example uses the Gemini Developer API (Gemini.Backend.GEMINI_API), not Vertex AI.
  • The current sample uses gemini-3.1-flash-live-preview.
  • Tool implementations in the example are stubs. Replace with real integrations as needed.
Gemini 2.5 compatibility

If you are updating a 2.5 Gemini Live function-calling sample, switch the startup prompt from sendClientContent(...) to sendRealtimeInput(...) for 3.1. Also replace the older thinkingBudget field with thinkingLevel.

See the VoxEngine API Reference for more details.

Full VoxEngine scenario

voxeengine-gemini-function-calling.js
1/**
2 * Voximplant + Gemini Live API connector demo
3 * Scenario: answer an incoming call and handle Gemini tool calls.
4 */
5
6require(Modules.Gemini);
7require(Modules.ApplicationStorage);
8
9const SYSTEM_INSTRUCTION = `
10You are Voxi, a helpful voice assistant for phone callers.
11Keep responses short and telephony-friendly (usually 1-2 sentences).
12If the caller asks about the weather, call the get_weather tool.
13If the caller wants to end the call, call the hangup_call tool.
14`;
15
16const CONNECT_CONFIG = {
17 responseModalities: ["AUDIO"],
18 thinkingConfig: {thinkingLevel: "minimal"},
19 systemInstruction: {
20 parts: [{text: SYSTEM_INSTRUCTION}],
21 },
22 tools: [
23 {
24 functionDeclarations: [
25 {
26 name: "get_weather",
27 description: "Get current weather for a location (demo stub)",
28 parametersJsonSchema: {
29 type: "object",
30 properties: {
31 location: {
32 type: "string",
33 description: "City name, for example: San Francisco",
34 },
35 },
36 required: ["location"],
37 },
38 },
39 {
40 name: "hangup_call",
41 description: "Hang up the current call",
42 parametersJsonSchema: {
43 type: "object",
44 properties: {},
45 required: [],
46 },
47 },
48 ],
49 },
50 ],
51 inputAudioTranscription: {},
52 outputAudioTranscription: {},
53};
54
55VoxEngine.addEventListener(AppEvents.CallAlerting, async ({call}) => {
56 let voiceAIClient;
57
58 // Termination functions - add cleanup and logging as needed
59 call.addEventListener(CallEvents.Disconnected, VoxEngine.terminate);
60 call.addEventListener(CallEvents.Failed, VoxEngine.terminate);
61
62 try {
63 call.answer();
64
65 voiceAIClient = await Gemini.createLiveAPIClient({
66 apiKey: (await ApplicationStorage.get("GEMINI_API_KEY")).value,
67 model: "gemini-3.1-flash-live-preview",
68 backend: Gemini.Backend.GEMINI_API,
69 connectConfig: CONNECT_CONFIG,
70 onWebSocketClose: (event) => {
71 Logger.write("===Gemini.WebSocket.Close===");
72 if (event) Logger.write(JSON.stringify(event));
73 VoxEngine.terminate();
74 },
75 });
76
77 voiceAIClient.addEventListener(Gemini.LiveAPIEvents.SetupComplete, () => {
78 VoxEngine.sendMediaBetween(call, voiceAIClient);
79 voiceAIClient.sendRealtimeInput({
80 text: "Say hello and ask how you can help.",
81 });
82 });
83
84 voiceAIClient.addEventListener(Gemini.LiveAPIEvents.ToolCall, (event) => {
85 const functionCalls = event?.data?.payload?.functionCalls || [];
86 if (!functionCalls.length) return;
87
88 const responses = functionCalls.map((fn) => {
89 const {id, name, args} = fn || {};
90 if (!id || !name) return null;
91
92 if (name === "get_weather") {
93 const location = args?.location || "Unknown";
94 return {
95 id,
96 name,
97 response: {
98 output: {
99 location,
100 temperature_f: 72,
101 condition: "sunny",
102 },
103 },
104 };
105 }
106
107 if (name === "hangup_call") {
108 // hang up after 5 seconds to allow message playback
109 setTimeout(()=>{
110 call.hangup();
111 VoxEngine.terminate();
112 }, 5_000);
113 return {
114 id,
115 name,
116 response: {
117 output: {
118 result: "Hanging up now. Goodbye!",
119 },
120 },
121 };
122 }
123
124 return {
125 id,
126 name,
127 response: {
128 error: `Unhandled tool: ${name}`,
129 },
130 };
131 }).filter(Boolean);
132
133 if (responses.length) {
134 voiceAIClient.sendToolResponse({
135 functionResponses: responses,
136 });
137 }
138 });
139
140 // handle barge-in
141 voiceAIClient.addEventListener(Gemini.LiveAPIEvents.ServerContent, (event) => {
142 const payload = event?.data?.payload || {};
143 if (payload.interrupted) {
144 Logger.write("===BARGE-IN=== Gemini.LiveAPIEvents.ServerContent");
145 voiceAIClient.clearMediaBuffer();
146 }
147 });
148
149 [
150 Gemini.LiveAPIEvents.SetupComplete,
151 Gemini.LiveAPIEvents.ServerContent,
152 Gemini.LiveAPIEvents.ToolCall,
153 Gemini.LiveAPIEvents.ToolCallCancellation,
154 Gemini.LiveAPIEvents.ConnectorInformation,
155 Gemini.LiveAPIEvents.Unknown,
156 Gemini.Events.WebSocketMediaStarted,
157 Gemini.Events.WebSocketMediaEnded,
158 ].forEach((eventName) => {
159 voiceAIClient.addEventListener(eventName, (event) => {
160 Logger.write(`===${event.name}===`);
161 if (event?.data) Logger.write(JSON.stringify(event.data));
162 });
163 });
164 } catch (error) {
165 Logger.write("===SOMETHING_WENT_WRONG===");
166 Logger.write(error);
167 voiceAIClient?.close();
168 VoxEngine.terminate();
169 }
170});