Building a Realtime Voice Support Agent with Vercel AI SDK v7
Voice agents used to mean marrying one provider's WebSocket event format. I used the new experimental realtime API in Vercel AI SDK v7 to build an order-status voice agent I can move between providers.
Every voice agent I have shipped before this year had the same architecture problem: the moment you open a WebSocket to a realtime model, you are writing against that one provider's event format. Session events, audio chunks, tool call frames, turn detection, all of it proprietary. Switching providers meant rewriting the client.
Vercel AI SDK v7 takes a swing at this with provider-agnostic realtime support. The session runs in the browser, connects directly to the provider over WebSockets, and your React code talks to one hook: experimental_useRealtime. I rebuilt a use case I know well, a customer support agent that answers "where is my order?" over voice, to see how much of the old pain is actually gone.
The token dance
The browser never sees your API key. Your server mints a short-lived token, and the client uses that to open the socket. The server side is a few lines:
// app/api/realtime/setup/route.ts
import { openai } from '@ai-sdk/openai';
export async function POST() {
const token = await openai.experimental_realtime.getToken({
model: 'gpt-realtime',
});
return Response.json(token);
}
This endpoint is effectively a key-vending machine, so put your normal auth and rate limiting in front of it. Anyone who can hit it can open billable voice sessions.
The client is one hook
On the client, experimental_useRealtime manages the WebSocket, microphone capture, and audio playback. You configure the session where you create it:
'use client';
import { experimental_useRealtime } from '@ai-sdk/react';
import { openai } from '@ai-sdk/openai';
const realtime = experimental_useRealtime({
model: openai.experimental_realtime('gpt-realtime'),
api: { token: '/api/realtime/setup' },
sessionConfig: {
instructions:
'You are a support agent for an online store. Look up orders before answering. Be brief.',
voice: 'alloy',
inputAudioTranscription: {},
turnDetection: { type: 'server-vad' },
},
onToolCall: async ({ toolCall }) => {
if (toolCall.toolName === 'getOrderStatus') {
const res = await fetch('/api/orders/status', {
method: 'POST',
body: JSON.stringify(toolCall.args),
});
return res.json();
}
},
});
Calling realtime.connect() starts the session; realtime.messages gives you a normal UIMessage[] array, so the transcript renders with the exact same components I already had for text chat. That detail alone saved me an afternoon.
Tool calls run in the client
This was the architectural surprise: realtime tool execution is client-driven. The provider sends the tool call over the socket, your onToolCall handler runs it, and the result flows back into the conversation. There is no server-side agent loop.
For my order lookup that is the right shape. The browser calls my own authenticated API route, the route talks to the database, and the model gets back a small JSON payload while the user hears "let me check that for you." For anything sensitive, the pattern is the same as regular client code: the tool handler hits your API, and your API enforces who can see what. If you need to feed a result back manually, realtime.addToolOutput(toolCallId, result) does it.
The transcription config matters more than it looks, too. With inputAudioTranscription on, you get text of what the caller actually said, which is the difference between debuggable sessions and listening to recordings.
The provider-agnostic part is real
The reason this is a v7 story and not just an OpenAI story: swap the model line and the rest of the code stands still. Google's Gemini Live and xAI's Grok voice models plug into the same hook, and the AI Gateway normalizes all of them behind one interface:
// server: mint the token through the gateway instead
const token = await gateway.experimental_realtime.getToken({
model: 'openai/gpt-realtime-2',
});
Change the model string, redeploy, and you are on a different provider. My session config and tool handlers did not change. That is the thing I could not do last year without a rewrite.
Why this matters
The demo took an evening: press a button, ask where order 4271 is, hear the agent check the database and answer. Voice quality and latency are the provider's problem now, and choosing a provider is a config change instead of an architecture decision.
It is experimental and the API will move. But if you have been putting off a voice interface because the last attempt welded you to one vendor's event soup, v7 is the first version where I would start that project again.
Related reading
Our monthly client reports followed a 40-line prompt nobody dared touch. I moved the whole playbook into an uploaded skill using the new uploadSkill API in Vercel AI SDK v7.
One-shot code execution can't build software. An autonomous coding agent needs a filesystem that survives across turns — to install dependencies, edit files, run the test suite, read the failures, and open a pull request. This builds that with a persistent E2B sandbox.
The moment you let an LLM run the code it writes, you've handed a stranger a shell on your server. Here's the threat model, and a step-by-step build of an isolated Docker sandbox your agent can safely execute code in.
Let's build something great.
Have a project in mind? We are an elite software and AI development studio ready to bring your ideas to production. Let's talk about your roadmap.