Skip to content

Commit 7909573

Browse files
cursoragentsahil
andcommitted
Add documentation for client-side tool calls
Co-authored-by: sahil <[email protected]>
1 parent 6f3d78c commit 7909573

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
---
2+
title: Client tool calls
3+
subtitle: Handle tools on the client using the Web SDK
4+
slug: assistants/client-tool-calls
5+
---
6+
7+
## Overview
8+
9+
Run tools directly in your web app. When a tool in your assistant has no `server.url`, Vapi sends a `tool-calls` message to your client. Your app executes the action locally (e.g., update UI, access browser APIs) and can optionally send results back to the assistant.
10+
11+
**In this guide, you'll learn to:**
12+
- Configure a tool to run on the client
13+
- Enable client messages for tool calling
14+
- Handle `tool-calls` in the Web SDK
15+
- Decide when to return results vs fire-and-forget
16+
17+
## Prerequisites
18+
- A Vapi assistant with at least one function tool
19+
- Web SDK installed and initialized
20+
- Familiarity with [Web Quickstart](mdc:fern/quickstart/web)
21+
22+
## Configuration
23+
24+
<Steps>
25+
<Step title="Define a client-side tool (no server.url)">
26+
Omit the `server` property so calls are sent to your client:
27+
28+
```json title="Tool (client-executed)"
29+
{
30+
"type": "function",
31+
"async": false,
32+
"function": {
33+
"name": "updateUI",
34+
"description": "Updates UI with new data",
35+
"parameters": {
36+
"type": "object",
37+
"properties": {
38+
"action": { "type": "string" },
39+
"data": { "type": "object" }
40+
},
41+
"required": ["action"]
42+
}
43+
},
44+
"messages": [
45+
{ "type": "request-start", "content": "Let me update that for you..." },
46+
{ "type": "request-complete", "content": "Done!" }
47+
]
48+
}
49+
```
50+
</Step>
51+
52+
<Step title="Enable client messages on the assistant">
53+
Make sure your assistant sends tool events to the client. You can include the defaults or explicitly list the ones you need:
54+
55+
```json title="Assistant (excerpt)"
56+
{
57+
"monitorPlan": {
58+
"clientMessages": [
59+
"conversation-update",
60+
"function-call",
61+
"model-output",
62+
"speech-update",
63+
"status-update",
64+
"tool-calls",
65+
"tool-calls-result",
66+
"transfer-update",
67+
"transcript",
68+
"user-interrupted"
69+
]
70+
},
71+
"model": {
72+
"tools": [
73+
{
74+
"type": "function",
75+
"function": { "name": "updateUI", "parameters": { "type": "object" } }
76+
}
77+
]
78+
}
79+
}
80+
```
81+
82+
<Note>
83+
`tool-calls` notifies your client when the model wants to run a tool. `tool-calls-result` forwards results back to clients (useful when tools execute elsewhere).
84+
</Note>
85+
</Step>
86+
</Steps>
87+
88+
## Handle tool calls in the Web SDK
89+
90+
<CodeBlocks>
91+
```tsx title="React / TypeScript"
92+
import React, { useEffect, useState } from 'react';
93+
import Vapi from '@vapi-ai/web';
94+
95+
function App() {
96+
const [vapi, setVapi] = useState<Vapi | null>(null);
97+
98+
useEffect(() => {
99+
const client = new Vapi('YOUR_PUBLIC_API_KEY');
100+
setVapi(client);
101+
102+
client.on('message', async (message: any) => {
103+
if (message.type === 'tool-calls') {
104+
// message.toolCallList: minimal list of calls
105+
// message.toolWithToolCallList: tools with full definitions + calls
106+
for (const call of message.toolCallList) {
107+
const fn = call.function?.name;
108+
const args = safeParse(call.function?.arguments);
109+
110+
if (fn === 'updateUI') {
111+
await handleUIUpdate(args?.action, args?.data);
112+
// For sync tools (async: false), optionally add a function result to context
113+
client.send({
114+
type: 'add-message',
115+
message: {
116+
role: 'function',
117+
name: 'updateUI',
118+
content: JSON.stringify({ ok: true })
119+
}
120+
});
121+
}
122+
}
123+
} else if (message.type === 'tool-calls-result') {
124+
// Optional: consume results produced by non-client tools
125+
console.log('Tool result:', message.toolCallResult);
126+
}
127+
});
128+
129+
return () => client.stop();
130+
}, []);
131+
132+
return (
133+
<button onClick={() => vapi?.start('YOUR_ASSISTANT_ID')}>Start</button>
134+
);
135+
}
136+
137+
function safeParse(v: unknown) {
138+
if (typeof v === 'string') {
139+
try { return JSON.parse(v); } catch { return undefined; }
140+
}
141+
return v ?? undefined;
142+
}
143+
144+
async function handleUIUpdate(action?: string, data?: Record<string, unknown>) {
145+
if (!action) return;
146+
switch (action) {
147+
case 'show_notification':
148+
// implement your UI behavior
149+
break;
150+
case 'navigate':
151+
// router.push(data?.href as string)
152+
break;
153+
default:
154+
break;
155+
}
156+
}
157+
158+
export default App;
159+
```
160+
161+
```ts title="Vanilla TypeScript"
162+
import Vapi from '@vapi-ai/web';
163+
164+
const vapi = new Vapi('YOUR_PUBLIC_API_KEY');
165+
166+
evapi.on('message', async (message: any) => {
167+
if (message.type === 'tool-calls') {
168+
for (const call of message.toolCallList) {
169+
const fn = call.function?.name;
170+
const args = typeof call.function?.arguments === 'string'
171+
? JSON.parse(call.function.arguments)
172+
: call.function?.arguments;
173+
174+
if (fn === 'showNotification') {
175+
showToast(args?.title, args?.message, args?.variant);
176+
// fire-and-forget example: if tool is async, you can skip returning a result
177+
}
178+
}
179+
}
180+
});
181+
182+
function showToast(title?: string, message?: string, variant: 'info'|'success'|'warning'|'error' = 'info') {
183+
console.log(`[${variant}] ${title ?? ''} ${message ?? ''}`);
184+
}
185+
```
186+
</CodeBlocks>
187+
188+
<Info>
189+
Results can be reflected back to the model by adding a function message via `vapi.send({ type: "add-message", message: { role: "function", name, content } })`. Additionally, when other systems execute tools, your client can receive their outcomes via the `tool-calls-result` client message.
190+
</Info>
191+
192+
## When to respond vs not respond
193+
194+
Use these rules to decide whether to send a result back after handling a tool on the client:
195+
196+
- **Respond (send a result) when:**
197+
- **Tool is sync (`async: false`)** and the model should incorporate the output into its next turn
198+
- You want the transcript/history to include a structured function result
199+
- The assistant prompted with `request-complete`/`request-failed` messaging that depends on outcome data
200+
201+
- **Do not respond (fire-and-forget) when:**
202+
- **Tool is async (`async: true`)** and UI-side effects are enough (e.g., open modal, play sound)
203+
- The result is purely visual and doesn’t change the conversation
204+
- Returning data would be redundant or overly large
205+
206+
<Warning>
207+
If a tool is configured as sync but your client never returns a result (e.g., via a function message), the assistant may wait or proceed with limited context. Prefer `async: true` for one-way UI effects.
208+
</Warning>
209+
210+
## Example: client-side notification tool
211+
212+
```json
213+
{
214+
"type": "function",
215+
"async": true,
216+
"function": {
217+
"name": "showNotification",
218+
"description": "Shows a notification to the user in the browser",
219+
"parameters": {
220+
"type": "object",
221+
"properties": {
222+
"title": { "type": "string" },
223+
"message": { "type": "string" },
224+
"variant": { "type": "string", "enum": ["info", "success", "warning", "error"] }
225+
},
226+
"required": ["message"]
227+
}
228+
},
229+
"messages": [
230+
{ "type": "request-start", "content": "I'll show you that notification now." }
231+
]
232+
}
233+
```
234+
235+
## Troubleshooting
236+
- **Not receiving `tool-calls` on the client?** Ensure `monitorPlan.clientMessages` includes `tool-calls` and you started the call with the Web SDK.
237+
- **Parse errors on arguments?** Some providers stream arguments as strings—safely `JSON.parse` when needed.
238+
- **Assistant waiting on results?** Either return a function message result or set the tool to `async: true`.
239+
240+
## Next steps
241+
- **Web Quickstart:** Build and test calls with the [Web SDK](mdc:fern/quickstart/web)
242+
- **Custom tools:** Learn server-executed tools and formats in [Custom Tools](mdc:fern/tools/custom-tools)
243+
- **Server events:** Compare with server-side `tool-calls` in [Server events](mdc:fern/server-url/events)

0 commit comments

Comments
 (0)