By Sagar Shankaran, Founder of CallSphere
Use the Vercel AI SDK's transcription/speech functions plus Twilio Media Streams to ship a voice agent on Vercel Edge Runtime. Real Next.js Route Handler, working code, deploy in 5 min.
Key takeaways
TL;DR — Vercel AI SDK 5 ships
transcribe()andspeak()as first-class functions across providers, plus thegenerateText/streamTextagent loop. Combined with Twilio Media Streams over WebSockets in a Next.js Route Handler running on Node runtime (not Edge for WS), you get a voice agent deployed to Vercel in five minutes.
A Next.js 15 app with three routes:
POST /api/twilio/voice returns TwiML pointing at a WS endpointGET /api/twilio/media (WebSocket upgrade) bridges Twilio audio to a sandwich agenttranscribe(whisper-1) → streamText(gpt-5) → speak(elevenlabs)Deployed to Vercel with one push. Twilio webhook hits the production URL.
ai v5, @ai-sdk/openai, @ai-sdk/elevenlabs.OPENAI_API_KEY, ELEVENLABS_API_KEY in Vercel env.flowchart LR
C[Caller] --> T[Twilio]
T -->|HTTP TwiML| API[/api/twilio/voice]
API -->|<Connect><Stream>| T
T -->|wss media| WS[/api/twilio/media]
WS -->|transcribe| W[Whisper]
W -->|text| LLM[streamText gpt-5]
LLM -->|text| TTS[speak ElevenLabs]
TTS --> WS
WS --> T --> C
```ts
// app/api/twilio/voice/route.ts
export async function POST(req: Request) {
const host = req.headers.get("host");
const xml = `
```ts // app/api/twilio/media/route.ts export const runtime = "nodejs"; import { WebSocketServer } from "ws"; import { transcribe, generateText, experimental_generateSpeech as speak } from "ai"; import { openai } from "@ai-sdk/openai"; import { elevenlabs } from "@ai-sdk/elevenlabs";
let wss: WebSocketServer | null = null; function init(server: any) { if (wss) return; wss = new WebSocketServer({ server, path: "/api/twilio/media" }); wss.on("connection", handleConn); }
async function handleConn(ws: any) { const buffer: Buffer[] = []; let streamSid = ""; ws.on("message", async (raw: any) => { const ev = JSON.parse(raw.toString()); if (ev.event === "start") streamSid = ev.streamSid; if (ev.event === "media") { buffer.push(Buffer.from(ev.media.payload, "base64")); if (buffer.length > 50) await respond(ws, streamSid, buffer.splice(0)); } }); }
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
async function respond(ws: any, sid: string, frames: Buffer[]) { const wav = mulawToWav(Buffer.concat(frames)); const { text } = await transcribe({ model: openai.transcription("whisper-1"), audio: wav }); if (!text) return; const { text: reply } = await generateText({ model: openai("gpt-5"), system: "You are a friendly receptionist. Reply in one sentence.", prompt: text, }); const audio = await speak({ model: elevenlabs.speech("eleven_turbo_v2_5"), text: reply }); for (const chunk of chunked(audio.audio, 320)) { ws.send(JSON.stringify({ event: "media", streamSid: sid, media: { payload: pcmToMulaw(chunk).toString("base64") } })); } } ```
Twilio sends mu-law 8kHz; Whisper wants WAV/PCM. ElevenLabs returns PCM 16kHz; convert to mu-law 8kHz before sending back.
```ts import { Readable } from "stream"; function mulawToWav(mulaw: Buffer): Buffer { // 44-byte WAV header + linear PCM 8kHz mono // (use pcm-util or write manually) return makeWavHeader(8000, decodeMulaw(mulaw)); } ```
For production, use @scramjet/audio-utils or a small WASM transcoder.
streamText + sentence-by-sentence TTSReplace generateText with streamText and chunk on sentence boundaries to start TTS earlier:
```ts const { textStream } = await streamText({ model: openai("gpt-5"), prompt: text }); let buf = ""; for await (const delta of textStream) { buf += delta; const m = buf.match(/^([^.!?]+[.!?])\s*/); if (m) { speakAndSend(m[1]); buf = buf.slice(m[0].length); } } ```
Cuts perceived latency by ~40%.
In the Twilio console → your number → A Call Comes In → Webhook → https://your-app.vercel.app/api/twilio/voice. Done.
```bash vercel --prod ```
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
WebSocket support requires the Hobby+ plan with Functions running on Node runtime, not Edge. Set runtime: "nodejs" in the route file.
```ts import { tool } from "ai"; import { z } from "zod";
const tools = { lookup_appointment: tool({ description: "Get next appointment for a patient", parameters: z.object({ patient_id: z.string() }), execute: async ({ patient_id }) => fetchAppt(patient_id), }) }; const { text: reply } = await generateText({ model: openai("gpt-5"), tools, prompt: text }); ```
fluid: true for warm function pools, or use Vercel's new "Functions / Sandbox" preview that holds connections.experimental_generateSpeech: API name may move out of experimental; pin SDK version.CallSphere's Healthcare voice path uses OpenAI Realtime directly over Twilio (not the sandwich pattern) because Realtime cuts ~400ms vs STT+LLM+TTS chains. We use Vercel only for the marketing site. Our voice agent lives on FastAPI :8084. 37 agents, 90+ tools, 115+ DB tables, 6 verticals, $149/$499/$1499, 14-day trial, 22% affiliate.
Q: Why not use Realtime API directly?
You can — replace the sandwich with the Vercel AI SDK's experimental_realtime (in beta May 2026) for native bidirectional. The sandwich pattern is more debuggable, Realtime is faster.
Q: Does this work on Vercel Edge? No, Edge runtime doesn't support WebSocket server upgrades. Use Node runtime.
Q: Latency target? Sandwich pattern: ~1.2s voice-to-voice. Realtime: ~700ms.
Q: ElevenLabs vs OpenAI TTS? ElevenLabs Turbo v2.5 is ~150ms first-byte vs OpenAI TTS-1 at ~300ms. ElevenLabs voices sound better. Cost: about the same.
Q: How do I add a vector store / RAG?
Use @ai-sdk/openai embeddings + Vercel KV for cheap dev; for prod, point at Pinecone or pg-vector.
Written by
Sagar Shankaran· Founder, CallSphere
Sagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A VoIP telephone number is a phone number that routes calls over the internet instead of copper lines. Learn what a VoIP number is, how to get one, what it costs, and how to pair it with an AI voice agent in 2026.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
Haystack 2.7's Agent component plus an Ollama-served Llama 3.2 gives you tool-calling RAG with citations. Here's a complete pipeline against your own document store.
Run STT, LLM, and TTS entirely on Cloudflare's edge — no OpenAI, no ElevenLabs. Real working code with Whisper, Llama 3.3 70B, and Deepgram Aura.
Version your prompts in git, run a 50-case eval suite on every PR, block merges below threshold, and ship a new agent prompt with confidence — full GitHub Actions tutorial.
AI SDK 5 ships fully typed chat for React, Svelte, Vue, and Angular plus first-class agent loop primitives. Here are the patterns that matter for shipping in 2026.
© 2026 CallSphere LLC. All rights reserved.
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI