Bygg en röstagent på 20 minuter med ElevenLabs och Twilio
- Publicerad
- Senast uppdaterad
LyssnaLyssna på den här artikeln
En röstagent kan svara på inkommande telefonsamtal, transkribera uppringare i realtid med
För utvecklare består hela stacken av ElevenLabs för talsyntes (Flash v2.5) och transkribering (Scribe v2 Realtime), Twilio för telefoni och antingen OpenAI eller Anthropic som LLM. Alla dessa delar går att byta ut, så du kan välja de komponenter du är mest bekväm med.
Den här artikeln visar hur du bygger en röstagent på 20 minuter med Node.js och Typescript. Vill du ha ett färdigt alternativ som hanterar turordning, avbrott och telefoni utan att du själv behöver hålla ihop allt, kolla in ElevenAgents.
Så fungerar arkitekturen för en röstagent
Innan du skriver någon kod är det bra att förstå hur de tre tjänsterna i din tech-stack hänger ihop.
- Twilio: Hanterar telefonsamtalet och ljudöverföringen.
- ElevenLabs: Hanterar STT via Scribe v2 Realtime och TTS via Flash v2.5.
- LLM: Hanterar verktygsanrop och skriver svaret.
Varje steg är en tunn adapter, vilket gör att du kan byta ut en tjänst utan att röra resten. Till exempel kan du byta ut OpenAI:s LLM mot Anthropic utan att behöva skriva om övriga delar.
Ett telefonsamtal når din server via Twilio. Twilio svarar på PSTN-samtalet, öppnar en WebSocket till din server och skickar inringarens ljud som en ström av base64-kodade mu-law-ramar. Din server kör kedjan och strömmar syntetiserat ljud tillbaka över samma WebSocket, och Twilio spelar upp det för inringaren.

Så här ser flödet ut när du bygger en röstagent:
En inringare ringer ditt Twilio-nummer. Twilio hämtar ett TwiML-dokument från din webhook. TwiML:en säger åt Twilio att öppna en Media Stream till din WebSocket-endpoint. Twilio strömmar inkommande ljud som JSON-händelser med base64 mu-law (ulaw_8000)-data.
Din server skickar vidare ljudbitar till Scribe v2 Realtime för strömmande transkribering. När en inringares tur är klar skickar du transkriptionen till LLM, och syntetiserar sedan svaret med Flash v2.5 i ulaw_8000. Du skickar de syntetiserade mu-law-ramarna tillbaka till Twilio över WebSocket, base64-kodat, och Twilio spelar upp dem för inringaren.
Scribe v2 Realtime ger deltranskriptioner med cirka 150 ms fördröjning, och Flash v2.5 körs på ungefär 75 ms modell-inferens, exklusive nätverks- och applikationsfördröjning. LLM är den största och mest oförutsägbara delen för time-to-first-audio, och det är där största delen av fördröjningen hamnar. För att hålla gapet kort strömmar vi ut LLM-svaret token för token och börjar syntetisera innan modellen är klar med meningen.
För modellvalen bakom dessa beslut, se modellöversikten och förklaringen om förstå fördröjning.
Det här behöver du innan du börjar bygga en röstagent
Guiden utgår från att du har fyra saker på plats. Alla är snabba att sätta upp, men saknas någon av dem kan inte servern köras.
Här är förutsättningarna att kolla:
- Ett Twilio-telefonnummer med Voice-stöd: Notera numret samt ditt Account SID och Auth Token från Twilio-konsolen.
- ElevenLabs API-nyckel: Skapas i din ElevenLabs-panel. Nyckeln skickas i xi-api-key-headern och är hemlig, så håll den bara på serversidan. Se
- LLM API-nyckel: Den här guiden behandlar Anthropic Claude och OpenAI som utbytbara backend, så välj en.
- Ngrok (eller annan tunnel) för lokal utveckling: Twilio måste nå din server via en publik HTTPS- och WSS-URL, och ngrok löser det utan att du behöver deploya något.
Sätt dina hemligheter som miljövariabler och checka aldrig in dem.
Starta sedan en tunnel mot porten din server använder:
Förstå Twilio Media Streams-protokollet
Twilio ger dig inte en rå ljudsocket. Istället paketeras allt i ett strukturerat JSON-protokoll över WebSocket. Om du förstår de fyra händelsetyperna och sändformatet blir WebSocket-hanteraren i steg 2 enkel att följa när du skriver den.
När Twilio ansluter till din WebSocket skickas en sekvens av JSON-textmeddelanden, som kan ha fyra olika händelsetyper.
connected-händelsen kommer först och bekräftar att WebSocket är uppe. start-händelsen skickas en gång när mediastreamen börjar; den innehåller ett streamSid som du måste spara, eftersom du behöver det för att skicka ljud tillbaka, och även samtalsmetadata under start.customParameters och start.callSid.
media-händelsen återkommer: media.payload är en base64-kodad bit av 8kHz mu-law-ljud, 20 ms per ram, och media.track är inbound för inringarens ljud. Slutligen skickas stop när strömmen avslutas, oftast för att samtalet lagts på.
För att spela upp ljud skickar du ett meddelande av typen media med samma streamSid och en base64 mu-law-payload. För att avbryta ljud som redan är köat skickar du ett clear-meddelande med streamSid, vilket tömmer Twilios utgående buffert.
In- och utgående kodning är identisk (ulaw_8000). Vi begär ulaw_8000 från ElevenLabs Text to Speech och skickar byten direkt till Twilio utan att omkoda något däremellan.
Steg 1: Servera TwiML-webhooken
När ett samtal kommer in gör Twilio en HTTP-förfrågan till din webhook, och du svarar med TwiML som kopplar samtalet till din Media Stream. <Connect><Stream>-verbet öppnar en tvåvägs-WebSocket. Använd <Connect> istället för <Start> här: det håller samtalet vid liv under hela strömmen och låter dig skicka ljud tillbaka, vilket är syftet med den här lösningen.
TwiML:en som webhooken returnerar är:
I Express är det en enkel POST-handler som fyller i host och returnerar dokumentet:
I Twilio-konsolen, ställ in numrets "A call comes in"-webhook till https://your-subdomain.ngrok.app/incoming-call med HTTP POST.
Steg 2: Ta emot Media Stream-WebSocketen
WebSocket-hanteraren läser Twilio-händelser, driver kedjan och skriver tillbaka ljud.
Vi håller lite tillstånd per samtal: streamSid, en STT-anslutning och en flagga för om agenten pratar just nu. Hanteraren avkodar varje inkommande media-frame från base64 och skickar de råa mu-law-bytena till STT:
Steg 3: Transkribera med Scribe v2 Realtime
Scribe v2 Realtime tar emot strömmande ljudbitar och returnerar del- och sluttranskriptioner, och den stöder mu-law-kodning direkt, så vi skickar Twilios ramar oförändrade.
Den erbjuder också Voice Activity Detection för segmentering baserat på tystnad och manuell commit-kontroll för att avsluta ett segment. För en
Stegen är: Öppna en STT-ström när samtalet startar. Skicka varje inkommande mu-law-bit. Reagera på färdiga transkriptioner genom att anropa LLM.
Realtime-STT-klienten utvecklas fortfarande, så exemplet nedan ligger bakom en liten TypeScript-adapter (openRealtimeStt) som du implementerar mot det faktiska API:et istället för ett fast fältnamnsschema. Se onFinal som kroken som lämnar över en färdig inringartur till nästa steg.
Fördröjningen för realtidsigenkänning är cirka 150 ms för deltranskriptioner, vilket gör att gapet mellan att inringaren slutar prata och agenten börjar känns litet. För batch-varianten och hela funktionsuppsättningen, se Speech to Text-dokumentationen och produktsidan för realtime Speech to Text.
Steg 4: Generera svar med en LLM
Det här steget tar fram svaret. LLM tar samtalshistoriken och returnerar assistentens text. Strömma svaret så du kan börja syntetisera redan på första meningen.
Här används OpenAI:
Modell-ID:t ovan, gpt-4.1-mini, är ett exempel på ett lågfördröjningsval; claude-haiku-4-5 är ett likvärdigt alternativ hos Anthropic. Båda levererar till samma llmReply-kontrakt; byt bara ut funktionskroppen, resten av agenten är oförändrad.
Systemprompten begränsar svarslängden, vilket är viktigt i telefon: långa svar känns långsamma och är svåra att avbryta naturligt.
Steg 5: Syntetisera med Flash TTS i ulaw_8000
Texten ska nu bli ljud som Twilio kan spela upp. Begär Flash v2.5 med outputFormat: "ulaw_8000" så att byten matchar Twilios förväntade kodning, strömma sedan ljudet och skicka varje bit tillbaka över WebSocket som en media-händelse.
Samla LLM-tokens till meningsstora fragment och syntetisera varje fragment direkt när det är klart, istället för att vänta på hela svaret. Det minskar time-to-first-audio eftersom inringaren hör första meningen medan modellen fortfarande producerar nästa. För mer kontroll över inkrementell syntes visar guiden för realtime TTS WebSocket hur du matar in text i en öppen syntes-socket; HTTP-strömningen nedan är enklare och räcker för korta samtalsturer.
Gör din AI-röstagent redo för produktion
Efter de fem stegen ovan har du en fungerande agent. Det är inte samma sak som en produktionssatt lösning.
Det finns flera saker du behöver tänka på innan du kopplar agenten till ett riktigt telefonnummer.
Validera Twilio-webhook-signaturer
Alla som får tag på din webhook-URL kan POST:a till den, så första steget är att bekräfta att förfrågan faktiskt kommer från Twilio. Twilio signerar varje förfrågan med din Auth Token i X-Twilio-Signature-headern, och du ska neka allt som inte klarar valideringen. Signaturen beräknas på hela URL:en och POST-parametrarna, så du måste göra det på samma sätt som Twilio.
Twilios hjälpfunktion gör det åt dig:
Hantera hemligheter på rätt sätt
Håll ELEVENLABS_API_KEY, LLM-nyckeln och TWILIO_AUTH_TOKEN i en secrets manager, inte i källkoden och inte i okrypterade env-filer i repo. Begränsa ElevenLabs-nyckeln till bara de endpoints tjänsten behöver och sätt en kreditgräns så att en eventuell läcka får begränsad effekt.
Enterprise-planer kan dessutom begränsa en nyckel till specifika IP-intervall med IP-whitelisting. Den här servern använder API-nyckeln direkt eftersom den aldrig lämnar din backend; om någon ljudlogik flyttas till webbläsare eller mobilklient bör du byta till engångstokens så nyckeln aldrig exponeras mot klienten.
Förstå samtidighetsgränsen
Varje plan har en samtidighetsgräns som skiljer sig mellan modellfamiljer, och gränsen räknar hur många förfrågningar som samtidigt genererar ljud.
För en telefonagent är det till din fördel. Ljudgenerering är snabbare än uppspelning, så varje samtal använder bara TTS-samtidighet under de korta stunder då ett svar syntetiseras, inte under hela samtalet. Som tumregel kan en samtidighetsgräns på runt fem hantera cirka 100 samtidiga samtal, eftersom genereringen är klar långt innan uppspelningen.
Men övervaka alltid marginalen istället för att gissa. ElevenLabs-svar visar current-concurrent-requests och maximum-concurrent-requests i headers; logga dem och larma när du närmar dig max. Om du överskrider gränsen köas förfrågningar efter prioritet, vilket oftast lägger till cirka 50 ms, och vid långvarig överbelastning returneras HTTP 429.
Hantera HTTP 429-svar med kort backoff. Om de fortsätter, höj gränsen via prissidan eller, för Enterprise-kunder, via din account manager.
Hantera barge-in och avbrott
En inringare som börjar prata medan agenten pratar förväntar sig att agenten tystnar. Det kallas barge-in, och att hantera det rätt är avgörande för att agenten ska kännas naturlig.
Upptäck inringarens tal under agentens uppspelning med STT VAD-signalen. När du märker det, gör två saker. För det första, sluta skicka TTS-bitar, vilket agentSpeaking-flaggan redan hanterar genom att bryta loopen. För det andra, skicka ett clear-meddelande till Twilio för att tömma ljudet du redan har köat.
Om du hoppar över clear fortsätter Twilio spela buffrat ljud efter att du slutat skicka, så agenten pratar över inringaren.
Logga, övervaka och hantera fel snyggt
Instrumentera varje steg så du kan se var fördröjningen uppstår om ett samtal känns långsamt. Mät gapet från sista transkription till första LLM-token, från första LLM-token till första TTS-byte, och från första TTS-byte till ramen som skickas till Twilio. Du kommer märka att mest varierande fördröjning finns i LLM-steget; STT och TTS är mer stabila.
Planera också för delvisa fel. LLM kan få timeout, STT-strömmen kan brytas och nätverksrundan till ElevenLabs varierar mellan cirka 20 och 200 ms över publika internet beroende på geografi. Placera din server nära inringarna, inte bara nära ElevenLabs, eftersom ElevenLabs redan routar till närmaste kluster i Nordamerika, Europa eller Sydostasien.
Om ett steg misslyckas, lämna inte inringaren i tystnad: syntetisera en kort reservfras ("Förlåt, kan du säga det igen?") och håll samtalet vid liv. Lägg in timeout och try/catch runt varje steg så att ett misslyckat steg inte river hela WebSocketen.
Några fler standardinställningar är bra att sätta innan du går live:
- Begränsa svarslängden i systemprompten, så turerna hålls korta och möjliga att avbryta.
- Begränsa samtalshistoriken så att långa samtal inte gör LLM-kontexten oändligt stor.
- Sätt en maxlängd på samtal som skydd mot fastnade sessioner som tyst förbrukar samtidighet.
För att fortsätta trimma de delar du kan styra, läs latensdokumentet som förklarar var time-to-first-audio uppstår, modellöversikten som går igenom hastighet och kvalitet, och guiden för realtime TTS WebSocket som visar hur du kan pressa ner syntesfördröjningen med inkrementell text.
Bygg produktionsklara röstagenter med ElevenAPI
Nu när 20 minuter har gått har du alla delar av en produktionsklar
Vill du slippa underhålla kedjan själv erbjuder ElevenAgents turordning, avbrottshantering och telefoni som en hanterad tjänst byggd på samma modeller du just kopplat ihop.
Vill du fortsätta trimma en stack du själv styr, kolla in produktsidan för ElevenAPI för planer, samtidighetsgränser och röstbiblioteket. Eller registrera dig och kom igång idag för att ringa ditt första samtal.
.webp&w=3840&q=80)
.webp&w=3840&q=80)

.webp&w=3840&q=80)
