Grok Voice Think Fast 2.0 API-handledning: Bygg en röstagent i realtid i Python
Lär dig hur du använder Grok Voice Think Fast 2.0 för att bygga en röstagent i realtid som hanterar talade samtal, anropar verktyg, hanterar avbrott och återupptar frånkopplade sessioner.
SpaceXAIs Grok Voice Think Fast 2.0 är en speech-to-speech-modell. Du skickar ljud över en WebSocket och får tillbaka ljud, och däremellan kan den resonera och fortsätta prata medan ett funktionsanrop som den har bestämt sig för redan körs. Ingen separat speech-to-text, inget separat text-to-speech.
SpaceXAI tillkännagav Think Fast 2.0 den 29 juli 2026: snabbare första ljudet, stabilare full-duplexbeteende (lyssnar medan den pratar i stället för strikt turtagning) och verktygsanrop som startar tidigt i turen. Jag håller benchmark-snacket kort, för det som spelar roll i en handledning är vad som ändras i din kod.
Vi ska bygga en kundsupport-röstagent för en webbutik. En uppringare kan fråga om en order, ändra en leveransinstruktion, avbryta, avbryta agenten mitt i en mening och återuppta samtalet efter ett avbrott i anslutningen. Det här är API-vägen, inte no-code-verktyget Voice Agent Builder som vår handledning för Grok Voice Agent Builder går igenom. Börja där för konsol-först-versionen.
Vad är Grok Voice Think Fast 2.0?
Grok Voice Think Fast 2.0 är SpaceXAIs senaste modell för Speech to Speech-API:et, produktnamnet bakom det de flesta bara kallar Grok Voice. Om du fortfarande tänker på företaget som xAI är det samma gäng: det integrerades i SpaceX och bytte namn till SpaceXAI den 6 juli 2026. API:t följde inte med i omprofileringen, så varje identifierare nedan säger fortfarandexai, från variabeln XAI_API_KEY till värden api.x.ai.
En traditionell röststack kedjar tre tjänster: speech-to-text, en språkmodell och sedan text-to-speech, och varje hopp adderar latens och en plats där kontext kan gå förlorad. Think Fast 2.0 viker ihop det till en modell som tar emot ljud eller text och producerar ljud eller text över samma anslutning.
Speech-to-speech-WebSocket jämfört med arkitektur med tre tjänster. Bild av författaren.
För en agent som agerar snarare än bara pratar spelar det roll att resonemang och tal kör i parallell. SpaceXAI säger att verktygsanrop "vanligtvis" börjar köras innan agenten avslutar sin första mening, och det ordet gör verkligt arbete.
På benchmark-siffror som SpaceXAI hänvisar till från Artificial Analysis får Think Fast 2.0 82,9 % på Speech to Speech Index jämfört med 75,7 % för 1.0, och kapar tiden till första ljudet från 1,25 sekunder till 0,70 sekunder. Leverantörssiffror på ett generellt benchmark är en hypotes om ditt samtalsflöde, inte en testplan.
Det finns tre modellsträngar du kommer att se: grok-voice-latest, grok-voice-think-fast-2.0 och grok-voice-think-fast-1.0. Aliaset är bekvämt under prototypandet och inte tillräckligt stabilt för något annat.
När jag testade den 4 augusti 2026 löste grok-voice-latest fortfarande upp till grok-voice-think-fast-1.0, med SpaceXAIs releasenotiser som schemalade flytten till Think Fast 2.0 till nästa dag. Det bytet är lika mycket en prisändring som en modelländring, 0,08 $ per minut ljud jämfört med 0,05 $ för 1.0, så ett opinnat alias blir dyrare utan att en rad i din kod ändras. Fäst den versionsmärkta strängen i allt du driftsätter.
Vad vi ska bygga
Agenten täcker det som en supportlinje får frågor om: slå upp en order, hitta en via e-post när uppringaren saknar nummer, ändra en leveransinstruktion, avbryta, öppna eller kontrollera ett ärende, och koppla samtalet till en person. Avbrott och en tappad anslutning dyker upp längs vägen.
Det är en handfull små filer i stället för ett skript, eftersom varje del har ett annat jobb och du kommer vilja testa dem separat. Här är strukturen:
config.py laddar API-nyckeln och håller modellsträngen, samplingsfrekvensen och endpoint-URL:er
voice_client.pywrappar WebSocketen, spårar debitering och exponerar send/receive-hjälpare
tools.py definierar orderfunktionerna och ett litet minnesbaserat orderlager som ersättning för en riktig databas
assistant.pyhåller systemprompten, sessionskonfigurationen och händelseloopen som knyter ihop allt
token_server.py är en liten FastAPI-endpoint som slår mynt av kortlivade token
app_streamlit.py lägger samma klient bakom ett live-samtal i webbläsaren, som jag återkommer till efter testavsnittet
Lärvägen går via terminalen. Demon lägger till mikrofonen.
Förutsättningar
Du behöver ett SpaceXAI-konto med en API-nyckel, en finansierad faktureringslösning (det finns ingen permanent gratisnivå och nya kontons kampanjkrediter räcker inte), och tillräcklig vana vid asyncio och WebSockets för att följa med utan en rad-för-rad-förklaring av await.
SpaceXAIs snabbstarter använder paketet websockets rakt av i stället för ett dedikerat SDK, och det gör vi också. Dokumenten anger aldrig någon nödvändig Python-version. Jag testade på 3.11.
Behåll API-nyckeln på servern. Om en webbläsare eller mobilapp pratar direkt med Voice-API:t får den en kortlivad token i stället för din riktiga nyckel, täckt i säkerhetsavsnittet nedan.
Ställa in projektet
Varje fil nedan finns i projekt-repot, så du kan klona det i stället för att kopiera kodsnuttar:
git clone https://github.com/KhalidAbdelaty/grok-voice-think-fast-2.0.git
cd grok-voice-think-fast-2.0
pip install-r requirements.txtDrivs av
websockets bär realtidsanslutningen ochpython-dotenv läser din nyckel. Resten täcker token-endpointen och webbläsardemon. Lägg din nyckel i .env:
Det är det mesta av setupen. Anslutningen är den intressanta delen.
Förstå Grok Voice Realtime API
Grok Voice är produktnamnet. Det du faktiskt skriver kod mot är en WebSocket-endpoint på wss://api.x.ai/v1/realtime, och hela samtalet sker som en ström av JSON-händelser över just den socketen.
Händelselivscykeln
En anslutning följer en fast form: servern skickarsession.createdoch conversation.created så snart du ansluter, du skickar session.updateför att konfigurera röst och verktyg, servern bekräftar med session.updated, och därefter skapar du samtalsobjekt och begär svar. Jag körde detta mot en live-nyckel och ordningen matchade dokumenten exakt.
session.update(klient) konfigurerar röst, instruktioner, verktyg och ljudformat
conversation.item.create(klient) lägger till ett användarmeddelande, ett assistentmeddelande eller ett verktygsresultat
response.create(klient) ber modellen tala; serverns VAD skickar detta åt dig automatiskt
response.output_audio.delta och response.output_audio_transcript.delta(server) strömmar svaret medan det genereras
response.done(server) stänger turen
Två saker snubblar folk på. Sidan för Speech to Speech-dokumentationen jag länkat ovan nämner en conversation.item.created-händelse vid sessionsåterupptagning, men den kanoniska händelsereferensen listar bara conversation.item.added, och det var vad som kom fram i alla tester jag körde, så koda mot den. Du kommer också se en odokumenterad ping-händelse några sekunder in i de flesta anslutningar, nämnd bara så att du inte läser den som ett fel.
Ljudformat och transport
Codec och transport är separata val. Codecen, satt under audio.input.formatochaudio.output.format, är audio/pcm (Linear16, standard 24000 Hz), audio/pcmu elleraudio/pcma(G.711 på 8 kHz, för telefoni), elleraudio/opus (24 kHz). Transport är hur dessa byte färdas på linan:
json (standard) skickar ljud som base64-text i input_audio_buffer.appendoch response.output_audio.delta, lätt att logga och felsöka
binaryskickar råa codec-byte som WebSocket-binärramar och hoppar över base64-overheaden till priset av en mottagningsslinga som måste grena på meddelandetyp
Börja med JSON. Varje exempel i dokumenten använder det, det är trivialt att inspektera, och base64-overhead är inte det som flaskhalsar en supportagent-byggnad. Gå över till binärt om du mäter en anledning.
Kompatibilitet med OpenAI Realtime API
Hoppa vidare om du aldrig har rört OpenAIs Realtime API. För alla andra följer Speech to Speech-API:t OpenAI Realtime API tillräckligt nära för att de flesta klientkoder porteras över genom att byta bas-URL och nyckel, men det är inte en perfekt drop-in.
Transkript kommer som conversation.item.input_audio_transcription.updated här i stället för OpenAIsdelta, några OpenAI-händelser stöds inte, och SpaceXAI lägger till sina egna tillägg: force_message för en skriptad informationsrad,resumption för återanslutningar, och replaceför att rätta feluttalade varumärken innan text-to-speech.
Bygga röstagenten i realtid
Nog med protokoll. Här är klienten som pratar med det.
Ansluta och konfigurera sessionen
Anslutningen öppnas med en bearer-token och en modellfrågeparameter, och det första meddelandet du skickar konfigurerar allt om hur agenten beter sig:
import asyncio
import json
import os
import websockets
MODEL ="grok-voice-think-fast-2.0"# pin the version, not grok-voice-latestasyncdefconnect():
url =f"wss://api.x.ai/v1/realtime?model={MODEL}"
ws =await websockets.connect(
url, additional_headers={"Authorization":f"Bearer {os.environ['XAI_API_KEY']}"})await ws.send(json.dumps({"type":"session.update","session":{"voice":"eve","instructions": SYSTEM_PROMPT,"turn_detection":{"type":"server_vad"},"tools": ORDER_TOOLS,"resumption":{"enabled":True},}}))return wsDrivs av
instructions är systemprompten, och den här modellen vill ha korta sådana. SpaceXAIs migrationsnotiser säger att man ska förenkla promptar skrivna för äldre röstmodeller från GPT-eran i stället för att porta dem ordagrant. Min säger åt agenten att hålla svaren korta, ställa en fråga åt gången och läsa upp varje skrivning innan den agerar. En muntlig bekräftelse är en UX-finess, inte en säkerhetskontroll. Din applikation upprätthåller fortfarande behörighet på själva skrivningen.
En sak som överraskade mig: en okänd modellsträng utlöser inte ett fel vid anslutning, den faller tyst tillbaka tillgrok-voice-think-fast-1.0. Att nedgradera en betald begäran på grund av ett stavfel, utan att säga det, är ett märkligt standardbeteende. Logga session.createds fält session.model en gång vid uppstart och kontrollera att du fick det du bad om.
Terminalutskrift som visar session.created efter anslutning. Bild av författaren.
Strömma användarljud
Medturn_detection.type satt till server_vad behöver du bara fortsätta lägga till ljud. Servern avgör när uppringaren har slutat prata och triggar svaret åt dig. Sätt det till null i stället så äger du det beslutet själv och committar bufferten uttryckligen när du tycker att turen är över.
asyncdefsend_audio_chunk(ws, pcm_bytes:bytes):await ws.send(json.dumps({"type":"input_audio_buffer.append","audio": base64.b64encode(pcm_bytes).decode(),}))Drivs av
Server-VAD har tre rattar, och att få dem fel är det vanligaste sättet jag har sett en röstagent kännas trasig trots att inget i loggarna gav fel. Ingen av dem dyker upp i session.updateds eko som standard, så kontrollera dem mot dokumenten i stället för att anta.
threshold (0,1 till 0,9, standard 0,85), hur högt ljud måste vara för att räknas som tal; höj i bullriga rum, sänk om tysta talare missas
silence_duration_ms, hur länge uppringaren är tyst innan servern avslutar deras tur; för kort kapar folk mitt i tanken, för lång känns seg
prefix_padding_ms (standard 333), en skiva ljud bevarad precis före talsdetektion, så att första stavelsen inte klipps
Trimma silence_duration_msförst om uppringare ständigt kapas när de pausar för att tänka. Det är den jag tar till innan jag rör de andra två.
Ta emot och spela upp svaret
Ljud anländer i små bitar somresponse.output_audio.delta, och poängen med strömning är att du spelar varje bit i samma ögonblick som den landar i stället för att vänta på response.done.
asyncdefplay_response(ws):asyncfor message in ws:
event = json.loads(message)if event["type"]=="response.output_audio.delta":
chunk = base64.b64decode(event["delta"])
speaker.write(chunk)# your playback call goes hereelif event["type"]=="response.output_audio_transcript.delta":print(event["delta"], end="", flush=True)Drivs av
Behåll transkriptet även i produktion. Det är det billigaste felsökningsverktyget du har när en uppringare säger att agenten "sa något konstigt".
Lägga till verktyg i röstagenten
En röstagent som bara pratar är en chatbot med mikrofon.
Skapa orderverktygen
Varje verktyg är ett JSON-schema plus en enkel Python-funktion på vår sida. Modellen rör aldrig databasen, den ser bara vad vår funktion returnerar.
ORDER_TOOLS =[{"type":"function","name":"check_order_status","description":"Look up the status, ETA, and delivery instructions for an order.","parameters":{"type":"object","properties":{"order_number":{"type":"string","description":"e.g. ORD-1042"},},"required":["order_number"],},},# find_orders, update_delivery_instructions, cancel_order,# create_support_ticket, check_ticket_status and transfer_to_human# all follow the same shape]Drivs av
Läsoperationer som check_order_status är säkra att göra om vid timeout. Skrivoperationer är det inte: att försöka igen med update_delivery_instructions efter en tvetydig timeout kan tillämpa samma ändring två gånger. En bekräftelserad i prompten stoppar inte det, så ge skrivningar en idempotensnyckel eller en duplicatkontroll i stället.
Lägg in vägran i funktionen också. cancel_order returnerar en anledning och ett alternativ i stället för att avbryta en skickad order, för en prompt som säger "avbryt aldrig skickade order" är ett förslag och en funktion som vägrar är det inte.
Hantera loopen för verktygsanrop
Fyra steg, och ordningen spelar större roll än det ser ut som. Modellen skickar response.function_call_arguments.done, din kod kör funktionen, du skickar tillbaka resultatet som ett function_call_output-objekt, och först därefter ber du modellen fortsätta.
asyncdefhandle_tool_call(ws, event):
args = json.loads(event["arguments"])
result = execute(event["name"], args)# never raises; errors come back as {"error": ...}await ws.send(json.dumps({"type":"conversation.item.create","item":{"type":"function_call_output","call_id": event["call_id"],"output": json.dumps(result),},}))Drivs av
Om modellen behöver mer än ett verktyg för en begäran, avfyrar den flera function_call_arguments.done-händelser innan något ljud spelas. Lös alla och skicka alla resultat innan en enda response.create. Skickar du den för tidigt svarar modellen utan kontexten från anrop som fortfarande pågår.
Det finns en fallgrop här som SpaceXAI dokumenterar och som jag ändå stötte på första gången: att skicka response.create i samma ögonblick som ditt verktygsresultat går ut kan överlappa med den inledande meningen agenten fortfarande spelar upp. I en körning öppnade den med "Jag ska kolla status för order ORD-1042 direkt" och kallade på verktyget mitt i meningen, så ett omedelbart svar hade talat över sin egen öppning.
Vänta tills det aktuella turens ljud är klart, och visa ett kort "tänker"-läge däremellan.
Flöde för verktygsanrop innan svaret fortsätter. Bild av författaren.
Hantera avbrott och samtalstillstånd
Två separata problem här. En uppringare pratar över agenten mitt i ett svar, och en WebSocket bryts och behöver plockas upp igen.
Stöd för naturliga avbrott
Med server_vad på är avbrott (barge-in) automatiskt på serversidan: i samma ögonblick som den upptäcker att uppringaren pratar igen signalerar den input_audio_buffer.speech_started och slutar generera det gamla svaret. Din uppgift är klienthalvan av det handslaget, att rensa vad som redan ligger i kö så att agenten blir tyst i stället för att avsluta en mening ingen bad om att få höra.
if event["type"]=="input_audio_buffer.speech_started":
playback_queue.clear()Drivs av
För manuella, icke-VAD-sessioner gör response.cancel samma jobb på begäran. Det finns också conversation.item.truncateför att trimma ett assistentobjekt ner till vad som faktiskt hördes. Dokumenten bekräftar att det finns men inte när man ska avfyra det under ett live-avbrott, så testa timingen själv.
Jag testade detta med en ändring av leveransinstruktion mitt i ett svar: starta begäran, avbryt med en annan adressdel halvvägs genom agentens bekräftelse. Det som spelar roll är om agenten tillämpar den korrigerade instruktionen i stället för att tyst slutföra den gamla, inte om ljudet stoppade. Verifiera mot orderposten, inte tystnaden. Webbläsardemon i slutet låter dig höra detta.
Återuppta en frånkopplad session
Sessionsåterupptagning är opt-in och det är inte minne. Sättresumption.enabled: true i session.update, ta ID:t från händelsen conversation.created, och om socketen bryts, återanslut med ?conversation_id=<id> i URL:en och välj in igen på den nya anslutningen.
asyncdefreconnect(conversation_id):
url =f"wss://api.x.ai/v1/realtime?model={MODEL}&conversation_id={conversation_id}"
ws =await websockets.connect(url, additional_headers=auth_header)await ws.send(json.dumps({"type":"session.update","session":{"resumption":{"enabled":True}}}))return wsDrivs av
De cachade turerna, transkripten, verktygsanropen och verktygsresultaten spelas upp innan din nästa fråga, och cachen försvinner efter 30 minuters inaktivitet. Jag testade genom att fråga om en order, tappa anslutningen och återansluta för en uppföljning utan att upprepa mig; agenten plockade upp ETA:n korrekt.
En odokumenterad hake: uppspelningen landar inte omedelbart, så en fråga som skjuts i samma ögonblick som socketen öppnas kan slå den och komma tillbaka utan minne av den tidigare turen. Ge det en sekund innan du skuldbelägger återupptagningen.
Terminal-logg för en återupptagen session. Bild av författaren.
Använd inte detta i stället för att spara ordertillstånd i din egen databas. Om cachen löper ut eller uppringaren ringer tillbaka i morgon börjar du från noll kontext, och det är avsiktligt.
Säkra och övervaka agenten
Lägg aldrig en permanent API-nyckel i webbläsar- eller mobilkod. Om en klient ansluter direkt i stället för att gå via din server, mynta en kortlivad token:
from fastapi import FastAPI
import httpx, os
app = FastAPI()@app.post("/session")asyncdefcreate_session():asyncwith httpx.AsyncClient()as client:
response =await client.post("https://api.x.ai/v1/realtime/client_secrets",
headers={"Authorization":f"Bearer {os.environ['XAI_API_KEY']}"},
json={"expires_after":{"seconds":300}},)return response.json()# {"value": "xai-realtime-client-secret-...", "expires_at": ...}Drivs av
En webbläsare kan inte sätta en anpassad Authorization-header på ett WebSocket-handslag, så den skickar token via headern sec-websocket-protocol i stället, med prefixet xai-client-secret..
Servern myntar token, webbläsaren ansluter till samtalet. Bild av författaren.
Debitering körs på två mätare. Ljud, skickat eller mottaget, debiteras med de 0,08 $ per minut jag nämnde tidigare, vilket är 4,80 $ per timme, och varje conversation.item.create som inte är ljud och inte ett function_call_output kostar en fast avgift på 0,004 $. response.create debiteras inte alls. Varje response.done bär ett usage-objekt, som i mitt test rapporterade output_audio_seconds tillsammans med separata billable_audio_seconds. Fakturera från dem, inte från uppskattningar.
Dokumenterade gränser på Speech to Speech-API:t är 10 samtidiga sessioner per team och ett 120-minuters sessionslock, båda i us-east-1. Planera inte kapacitet utifrån Voice Agent API:ts siffror, som är annorlunda.
Var precis när det gäller integritet. SpaceXAIs säkerhets-FAQ säger att API-förfrågningar och svar behålls krypterade i 30 dagar för missbruksövervakning och inte används för träning utan tillstånd, och att team kan aktivera Zero Data Retention, även om ZDR tar bort ihållande röstagent-samtalshistorik och därför inte fungerar med återupptagning.
Om du informerar om att ett samtal spelas in eller hanteras av AI, är det det force_message-tillägget jag listade tidigare gör. Raden spelas upp exakt som den skrivs i stället för vad modellen parafraserar den till.
Testa röstagenten
En 200-status på WebSocket-handslaget säger ingenting om huruvida agenten gjorde rätt sak. Testa utfallet, inte bara anslutningen.
En ren orderuppslagning, där det talade svaret kontrolleras mot posten, inte bara att ett svar kom
Ett avbrutet svar, som bekräftar att uppspelningen stannar och att agenten adresserar den nya begäran
En leveransuppdatering som kräver bekräftelse, kontrollerad mot orderposten
En vägran, som att avbryta en skickad order, där agenten måste förklara regeln i stället för att be om ursäkt
Ett okänt ordernummer, och säkerställ att agenten säger det i stället för att hitta på en status
Ett verktyg som returnerar ett fel, och kontrollera att agenten säger det i stället för att fastna
Återanslutning och återupptagning, inklusive det uppspelningsfönster jag stötte på tidigare
Stökigt ljud, snabbt tal och en uppringare som bokstaverar siffror och adresser
Jag körde de flesta av dessa mot en live-nyckel medan jag skrev detta. De intressanta felen var beteendemässiga, inte fel: timingen för återupptagning ovan, och en VAD-tröskel utanför intervallet som accepterades i stället för att avvisas, den typen av sak som skeppas tyst trasig om du bara testar happy path. Lägg till ett flerspråkigt test också, och se FAQ för en knorr i hur du namnger språket.
Två av dessa kan du inte testa genom att skriva.app_streamlit.py är en Streamlit-sida som lägger ett live-samtal i webbläsaren: mikrofonen strömmar in i samma WebSocket över WebRTC, agentens röst strömmar tillbaka, och socketen förblir öppen hela tiden.
Avbryta agenten mitt i en mening. Video av författaren.
Prata över agenten så stannar den, eftersom speech_started anländer och sidan spolar den köade ljudet. Det är handslaget från avbrottsavsnittet, i verklig drift.
Titta på orderposten snarare än transkriptet: agenten läser upp en leveransändring och säger att den är gjord, och posten antingen ändrades eller inte. Använd hörlurar. På öppna högtalare hör agenten sig själv, tolkar det som ett avbrott och kapar sin egen mening, vilket är en försmak av vad en högtalartelefon-uppringare gör mot dig.
Begränsningar i Grok Voice Think Fast 2.0 och överväganden vid driftsättning
Planera för detta: verktygsanrop som misslyckas mitt i en tur, en modell som uttalar en bekräftelse mer självsäkert än åtgärden lyckades, VAD trimmad för ett tyst kontor som faller isär på en telefonlinje, och en uppringare som ändrar sig mitt i en mening.
För betalningar, kontotillgång eller en uppringare som låter förvirrad eller upprörd, koppla till en människa. Ge modellen ett verktyg transfer_to_human för det: utan ett sådant improviserar den en ursäkt i stället för att eskalera.
En modulär stack med speech-to-text, språkmodell och text-to-speech har fortfarande sin plats: separat kontroll över varje komponent och ett deterministiskt transkript innan något resonemang sker, till priset av mer integrationsarbete. Och om din arbetslast inte behöver levande fram-och-tillbaka alls är en textchatbot eller ett batchtranskriptionsjobb enklare och billigare än en realtidspipeline som ingen pratar med.
Slutsats
Över testerna i den här artikeln gjordegrok-voice-think-fast-2.0mestadels vad dokumentationen säger. Händelselivscykeln höll, en tappad anslutning kom tillbaka med sina tidigare turer intakta, och modellen kallade ett verktyg medan den fortfarande talade sin öppningsrad.
Bortom namngivningsmissmatchen på conversation.item.added är det som är värt att flagga hur mycket av det återstående arbetet som ligger på din sida av socketen: uppspelningsköer, när man ska vara tyst, när man inte ska ställa nästa fråga ännu.
Om jag startade ett projekt idag skulle mina standardval vara den versionsmärkta modellsträngen i stället för aliaset, server_vad med silence_duration_ms trimmad före de andra två rattarna, JSON-transport tills något mätbart kräver binärt, resumption.enabled på första session.update, och session.model loggad vid uppstart.
Vanor jag skulle ta med till varje röstagent: kontrollera skrivningar mot posten i stället för den talade bekräftelsen, lägg vägran i verktyget i stället för i prompten, låt uppspelningen dränera innan nästa response.create, och testa mot riktiga accenter, riktigt brus och verktyg som fallerar på det sätt de faktiskt fallerar.
De uppenbara utökningarna är telefoni (SpaceXAI dokumenterar SIP-stöd direkt), en webbläsarklient på kortlivade token, en MCP-koppling in i ett riktigt CRM och en ordentligt flerspråkig version. Och om Voice Agent API:t som jag kontrasterade de där sessionsgränserna mot ligger närmare det du behöver, täcker vår handledning för Grok Voice Agent API den vägen.
FAQs
Är grok-voice-latest säker att använda i produktion?⌄
Inte riktigt, som jag nämnde i versionsavsnittet ovan. Den byter på ett datum SpaceXAI väljer, inte du, och tar din faktura med på resan. Fäst grok-voice-think-fast-2.0 och spara aliaset för lokala experiment där ett överraskningsbyte inte landar på ett kundsamtal live.
Stöder Grok Voice Think Fast 2.0 andra språk än engelska?⌄
Ja, över tjugo är dokumenterade med autodetektering, och du kan baisa transkription mot ett specifikt med language_hint. Observera att spanska och portugisiska behöver en regionskod som es-MX eller pt-BR. Ett naket es eller pt accepteras inte, och okända koder ignoreras tyst och faller tillbaka till autodetektering, så ett stavfel här kostar dig inget men gör inte heller något.
Kan jag byta röst, och hur många finns det?⌄
eve är den i dokumenten och den jag använde, med ara, rex, sal och leo också tillgängliga, plus anpassade röst-ID:n. GET /v1/tts/voices returnerar den aktuella uppställningen. Om tempot stör dig tar audio.output.speed 0,7 till 1,5.
Kan jag få agenten att svara snabbare än den gör?⌄
Testa reasoning.effort, som jag hoppade över i genomgången eftersom standarden oftast är rätt. Den skeppas som "high" och tar också "none", vilket minskar hur mycket planering modellen gör per tur. Okej för enkla uppslagsflöden. Jag skulle inte röra den för något som måste välja mellan verktyg.
Behöver jag SpaceXAIs officiella SDK för att bygga detta?⌄
Nej, precis som avsnittet om förutsättningar sa. Det rena paketet websockets eller en OpenAI-kompatibel klient pekad mot bas-URL:en api.x.ai funkar båda. En sak att veta: det officiella xai-sdk är en separat gRPC-klient som inte pratar med denna WebSocket, så leta inte efter realtidsmetoder där. För en annan startpunkt än min har xai-cookbook exempel för iOS, webb, WebRTC och telefoni.
SpaceXAIs Grok Voice Think Fast 2.0 är en speech-to-speech-modell. Du skickar ljud över en WebSocket och får tillbaka ljud, och däremellan kan den resonera och fortsätta prata medan ett funktionsanrop som den har bestämt sig för redan körs. Ingen separat speech-to-text, inget separat text-to-speech.
SpaceXAI tillkännagav Think Fast 2.0 den 29 juli 2026: snabbare första ljudet, stabilare full-duplexbeteende (lyssnar medan den pratar i stället för strikt turtagning) och verktygsanrop som startar tidigt i turen. Jag håller benchmark-snacket kort, för det som spelar roll i en handledning är vad som ändras i din kod.
Vi ska bygga en kundsupport-röstagent för en webbutik. En uppringare kan fråga om en order, ändra en leveransinstruktion, avbryta, avbryta agenten mitt i en mening och återuppta samtalet efter ett avbrott i anslutningen. Det här är API-vägen, inte no-code-verktyget Voice Agent Builder som vår handledning för Grok Voice Agent Builder går igenom. Börja där för konsol-först-versionen.
Vad är Grok Voice Think Fast 2.0?
Grok Voice Think Fast 2.0 är SpaceXAIs senaste modell för Speech to Speech-API:et, produktnamnet bakom det de flesta bara kallar Grok Voice. Om du fortfarande tänker på företaget som xAI är det samma gäng: det integrerades i SpaceX och bytte namn till SpaceXAI den 6 juli 2026. API:t följde inte med i omprofileringen, så varje identifierare nedan säger fortfarande
xai, från variabelnXAI_API_KEYtill värdenapi.x.ai.En traditionell röststack kedjar tre tjänster: speech-to-text, en språkmodell och sedan text-to-speech, och varje hopp adderar latens och en plats där kontext kan gå förlorad. Think Fast 2.0 viker ihop det till en modell som tar emot ljud eller text och producerar ljud eller text över samma anslutning.
Speech-to-speech-WebSocket jämfört med arkitektur med tre tjänster. Bild av författaren.
För en agent som agerar snarare än bara pratar spelar det roll att resonemang och tal kör i parallell. SpaceXAI säger att verktygsanrop "vanligtvis" börjar köras innan agenten avslutar sin första mening, och det ordet gör verkligt arbete.
På benchmark-siffror som SpaceXAI hänvisar till från Artificial Analysis får Think Fast 2.0 82,9 % på Speech to Speech Index jämfört med 75,7 % för 1.0, och kapar tiden till första ljudet från 1,25 sekunder till 0,70 sekunder. Leverantörssiffror på ett generellt benchmark är en hypotes om ditt samtalsflöde, inte en testplan.
Det finns tre modellsträngar du kommer att se:
grok-voice-latest,grok-voice-think-fast-2.0ochgrok-voice-think-fast-1.0. Aliaset är bekvämt under prototypandet och inte tillräckligt stabilt för något annat.När jag testade den 4 augusti 2026 löste
grok-voice-latestfortfarande upp tillgrok-voice-think-fast-1.0, med SpaceXAIs releasenotiser som schemalade flytten till Think Fast 2.0 till nästa dag. Det bytet är lika mycket en prisändring som en modelländring, 0,08 $ per minut ljud jämfört med 0,05 $ för 1.0, så ett opinnat alias blir dyrare utan att en rad i din kod ändras. Fäst den versionsmärkta strängen i allt du driftsätter.Vad vi ska bygga
Agenten täcker det som en supportlinje får frågor om: slå upp en order, hitta en via e-post när uppringaren saknar nummer, ändra en leveransinstruktion, avbryta, öppna eller kontrollera ett ärende, och koppla samtalet till en person. Avbrott och en tappad anslutning dyker upp längs vägen.
Det är en handfull små filer i stället för ett skript, eftersom varje del har ett annat jobb och du kommer vilja testa dem separat. Här är strukturen:
config.pyladdar API-nyckeln och håller modellsträngen, samplingsfrekvensen och endpoint-URL:ervoice_client.pywrappar WebSocketen, spårar debitering och exponerar send/receive-hjälparetools.pydefinierar orderfunktionerna och ett litet minnesbaserat orderlager som ersättning för en riktig databasassistant.pyhåller systemprompten, sessionskonfigurationen och händelseloopen som knyter ihop allttoken_server.pyär en liten FastAPI-endpoint som slår mynt av kortlivade tokenapp_streamlit.pylägger samma klient bakom ett live-samtal i webbläsaren, som jag återkommer till efter testavsnittetLärvägen går via terminalen. Demon lägger till mikrofonen.
Förutsättningar
Du behöver ett SpaceXAI-konto med en API-nyckel, en finansierad faktureringslösning (det finns ingen permanent gratisnivå och nya kontons kampanjkrediter räcker inte), och tillräcklig vana vid
asynciooch WebSockets för att följa med utan en rad-för-rad-förklaring avawait.SpaceXAIs snabbstarter använder paketet
websocketsrakt av i stället för ett dedikerat SDK, och det gör vi också. Dokumenten anger aldrig någon nödvändig Python-version. Jag testade på 3.11.Behåll API-nyckeln på servern. Om en webbläsare eller mobilapp pratar direkt med Voice-API:t får den en kortlivad token i stället för din riktiga nyckel, täckt i säkerhetsavsnittet nedan.
Ställa in projektet
Varje fil nedan finns i projekt-repot, så du kan klona det i stället för att kopiera kodsnuttar:
websocketsbär realtidsanslutningen ochpython-dotenvläser din nyckel. Resten täcker token-endpointen och webbläsardemon. Lägg din nyckel i.env:Det är det mesta av setupen. Anslutningen är den intressanta delen.
Förstå Grok Voice Realtime API
Grok Voice är produktnamnet. Det du faktiskt skriver kod mot är en WebSocket-endpoint på
wss://api.x.ai/v1/realtime, och hela samtalet sker som en ström av JSON-händelser över just den socketen.Händelselivscykeln
En anslutning följer en fast form: servern skickar
session.createdochconversation.createdså snart du ansluter, du skickarsession.updateför att konfigurera röst och verktyg, servern bekräftar medsession.updated, och därefter skapar du samtalsobjekt och begär svar. Jag körde detta mot en live-nyckel och ordningen matchade dokumenten exakt.session.update(klient) konfigurerar röst, instruktioner, verktyg och ljudformatconversation.item.create(klient) lägger till ett användarmeddelande, ett assistentmeddelande eller ett verktygsresultatresponse.create(klient) ber modellen tala; serverns VAD skickar detta åt dig automatisktresponse.output_audio.deltaochresponse.output_audio_transcript.delta(server) strömmar svaret medan det genererasresponse.done(server) stänger turenTvå saker snubblar folk på. Sidan för Speech to Speech-dokumentationen jag länkat ovan nämner en
conversation.item.created-händelse vid sessionsåterupptagning, men den kanoniska händelsereferensen listar baraconversation.item.added, och det var vad som kom fram i alla tester jag körde, så koda mot den. Du kommer också se en odokumenteradping-händelse några sekunder in i de flesta anslutningar, nämnd bara så att du inte läser den som ett fel.Ljudformat och transport
Codec och transport är separata val. Codecen, satt under
audio.input.formatochaudio.output.format, äraudio/pcm(Linear16, standard 24000 Hz),audio/pcmuelleraudio/pcma(G.711 på 8 kHz, för telefoni), elleraudio/opus(24 kHz). Transport är hur dessa byte färdas på linan:json(standard) skickar ljud som base64-text iinput_audio_buffer.appendochresponse.output_audio.delta, lätt att logga och felsökabinaryskickar råa codec-byte som WebSocket-binärramar och hoppar över base64-overheaden till priset av en mottagningsslinga som måste grena på meddelandetypBörja med JSON. Varje exempel i dokumenten använder det, det är trivialt att inspektera, och base64-overhead är inte det som flaskhalsar en supportagent-byggnad. Gå över till binärt om du mäter en anledning.
Kompatibilitet med OpenAI Realtime API
Hoppa vidare om du aldrig har rört OpenAIs Realtime API. För alla andra följer Speech to Speech-API:t OpenAI Realtime API tillräckligt nära för att de flesta klientkoder porteras över genom att byta bas-URL och nyckel, men det är inte en perfekt drop-in.
Transkript kommer som
conversation.item.input_audio_transcription.updatedhär i stället för OpenAIsdelta, några OpenAI-händelser stöds inte, och SpaceXAI lägger till sina egna tillägg:force_messageför en skriptad informationsrad,resumptionför återanslutningar, ochreplaceför att rätta feluttalade varumärken innan text-to-speech.Bygga röstagenten i realtid
Nog med protokoll. Här är klienten som pratar med det.
Ansluta och konfigurera sessionen
Anslutningen öppnas med en bearer-token och en modellfrågeparameter, och det första meddelandet du skickar konfigurerar allt om hur agenten beter sig:
instructionsär systemprompten, och den här modellen vill ha korta sådana. SpaceXAIs migrationsnotiser säger att man ska förenkla promptar skrivna för äldre röstmodeller från GPT-eran i stället för att porta dem ordagrant. Min säger åt agenten att hålla svaren korta, ställa en fråga åt gången och läsa upp varje skrivning innan den agerar. En muntlig bekräftelse är en UX-finess, inte en säkerhetskontroll. Din applikation upprätthåller fortfarande behörighet på själva skrivningen.En sak som överraskade mig: en okänd modellsträng utlöser inte ett fel vid anslutning, den faller tyst tillbaka till
grok-voice-think-fast-1.0. Att nedgradera en betald begäran på grund av ett stavfel, utan att säga det, är ett märkligt standardbeteende. Loggasession.createds fältsession.modelen gång vid uppstart och kontrollera att du fick det du bad om.Terminalutskrift som visar session.created efter anslutning. Bild av författaren.
Strömma användarljud
Med
turn_detection.typesatt tillserver_vadbehöver du bara fortsätta lägga till ljud. Servern avgör när uppringaren har slutat prata och triggar svaret åt dig. Sätt det tillnulli stället så äger du det beslutet själv och committar bufferten uttryckligen när du tycker att turen är över.Server-VAD har tre rattar, och att få dem fel är det vanligaste sättet jag har sett en röstagent kännas trasig trots att inget i loggarna gav fel. Ingen av dem dyker upp i
session.updateds eko som standard, så kontrollera dem mot dokumenten i stället för att anta.threshold(0,1 till 0,9, standard 0,85), hur högt ljud måste vara för att räknas som tal; höj i bullriga rum, sänk om tysta talare missassilence_duration_ms, hur länge uppringaren är tyst innan servern avslutar deras tur; för kort kapar folk mitt i tanken, för lång känns segprefix_padding_ms(standard 333), en skiva ljud bevarad precis före talsdetektion, så att första stavelsen inte klippsTrimma
silence_duration_msförst om uppringare ständigt kapas när de pausar för att tänka. Det är den jag tar till innan jag rör de andra två.Ta emot och spela upp svaret
Ljud anländer i små bitar som
response.output_audio.delta, och poängen med strömning är att du spelar varje bit i samma ögonblick som den landar i stället för att vänta påresponse.done.Behåll transkriptet även i produktion. Det är det billigaste felsökningsverktyget du har när en uppringare säger att agenten "sa något konstigt".
Lägga till verktyg i röstagenten
En röstagent som bara pratar är en chatbot med mikrofon.
Skapa orderverktygen
Varje verktyg är ett JSON-schema plus en enkel Python-funktion på vår sida. Modellen rör aldrig databasen, den ser bara vad vår funktion returnerar.
Läsoperationer som
check_order_statusär säkra att göra om vid timeout. Skrivoperationer är det inte: att försöka igen medupdate_delivery_instructionsefter en tvetydig timeout kan tillämpa samma ändring två gånger. En bekräftelserad i prompten stoppar inte det, så ge skrivningar en idempotensnyckel eller en duplicatkontroll i stället.Lägg in vägran i funktionen också.
cancel_orderreturnerar en anledning och ett alternativ i stället för att avbryta en skickad order, för en prompt som säger "avbryt aldrig skickade order" är ett förslag och en funktion som vägrar är det inte.Hantera loopen för verktygsanrop
Fyra steg, och ordningen spelar större roll än det ser ut som. Modellen skickar
response.function_call_arguments.done, din kod kör funktionen, du skickar tillbaka resultatet som ettfunction_call_output-objekt, och först därefter ber du modellen fortsätta.Om modellen behöver mer än ett verktyg för en begäran, avfyrar den flera
function_call_arguments.done-händelser innan något ljud spelas. Lös alla och skicka alla resultat innan en endaresponse.create. Skickar du den för tidigt svarar modellen utan kontexten från anrop som fortfarande pågår.Det finns en fallgrop här som SpaceXAI dokumenterar och som jag ändå stötte på första gången: att skicka
response.createi samma ögonblick som ditt verktygsresultat går ut kan överlappa med den inledande meningen agenten fortfarande spelar upp. I en körning öppnade den med "Jag ska kolla status för order ORD-1042 direkt" och kallade på verktyget mitt i meningen, så ett omedelbart svar hade talat över sin egen öppning.Vänta tills det aktuella turens ljud är klart, och visa ett kort "tänker"-läge däremellan.
Flöde för verktygsanrop innan svaret fortsätter. Bild av författaren.
Hantera avbrott och samtalstillstånd
Två separata problem här. En uppringare pratar över agenten mitt i ett svar, och en WebSocket bryts och behöver plockas upp igen.
Stöd för naturliga avbrott
Med
server_vadpå är avbrott (barge-in) automatiskt på serversidan: i samma ögonblick som den upptäcker att uppringaren pratar igen signalerar deninput_audio_buffer.speech_startedoch slutar generera det gamla svaret. Din uppgift är klienthalvan av det handslaget, att rensa vad som redan ligger i kö så att agenten blir tyst i stället för att avsluta en mening ingen bad om att få höra.För manuella, icke-VAD-sessioner gör
response.cancelsamma jobb på begäran. Det finns ocksåconversation.item.truncateför att trimma ett assistentobjekt ner till vad som faktiskt hördes. Dokumenten bekräftar att det finns men inte när man ska avfyra det under ett live-avbrott, så testa timingen själv.Jag testade detta med en ändring av leveransinstruktion mitt i ett svar: starta begäran, avbryt med en annan adressdel halvvägs genom agentens bekräftelse. Det som spelar roll är om agenten tillämpar den korrigerade instruktionen i stället för att tyst slutföra den gamla, inte om ljudet stoppade. Verifiera mot orderposten, inte tystnaden. Webbläsardemon i slutet låter dig höra detta.
Återuppta en frånkopplad session
Sessionsåterupptagning är opt-in och det är inte minne. Sätt
resumption.enabled: trueisession.update, ta ID:t från händelsenconversation.created, och om socketen bryts, återanslut med?conversation_id=<id>i URL:en och välj in igen på den nya anslutningen.De cachade turerna, transkripten, verktygsanropen och verktygsresultaten spelas upp innan din nästa fråga, och cachen försvinner efter 30 minuters inaktivitet. Jag testade genom att fråga om en order, tappa anslutningen och återansluta för en uppföljning utan att upprepa mig; agenten plockade upp ETA:n korrekt.
En odokumenterad hake: uppspelningen landar inte omedelbart, så en fråga som skjuts i samma ögonblick som socketen öppnas kan slå den och komma tillbaka utan minne av den tidigare turen. Ge det en sekund innan du skuldbelägger återupptagningen.
Terminal-logg för en återupptagen session. Bild av författaren.
Använd inte detta i stället för att spara ordertillstånd i din egen databas. Om cachen löper ut eller uppringaren ringer tillbaka i morgon börjar du från noll kontext, och det är avsiktligt.
Säkra och övervaka agenten
Lägg aldrig en permanent API-nyckel i webbläsar- eller mobilkod. Om en klient ansluter direkt i stället för att gå via din server, mynta en kortlivad token:
En webbläsare kan inte sätta en anpassad
Authorization-header på ett WebSocket-handslag, så den skickar token via headernsec-websocket-protocoli stället, med prefixetxai-client-secret..Servern myntar token, webbläsaren ansluter till samtalet. Bild av författaren.
Debitering körs på två mätare. Ljud, skickat eller mottaget, debiteras med de 0,08 $ per minut jag nämnde tidigare, vilket är 4,80 $ per timme, och varje
conversation.item.createsom inte är ljud och inte ettfunction_call_outputkostar en fast avgift på 0,004 $.response.createdebiteras inte alls. Varjeresponse.donebär ettusage-objekt, som i mitt test rapporteradeoutput_audio_secondstillsammans med separatabillable_audio_seconds. Fakturera från dem, inte från uppskattningar.Dokumenterade gränser på Speech to Speech-API:t är 10 samtidiga sessioner per team och ett 120-minuters sessionslock, båda i
us-east-1. Planera inte kapacitet utifrån Voice Agent API:ts siffror, som är annorlunda.Var precis när det gäller integritet. SpaceXAIs säkerhets-FAQ säger att API-förfrågningar och svar behålls krypterade i 30 dagar för missbruksövervakning och inte används för träning utan tillstånd, och att team kan aktivera Zero Data Retention, även om ZDR tar bort ihållande röstagent-samtalshistorik och därför inte fungerar med återupptagning.
Om du informerar om att ett samtal spelas in eller hanteras av AI, är det det
force_message-tillägget jag listade tidigare gör. Raden spelas upp exakt som den skrivs i stället för vad modellen parafraserar den till.Testa röstagenten
En 200-status på WebSocket-handslaget säger ingenting om huruvida agenten gjorde rätt sak. Testa utfallet, inte bara anslutningen.
Jag körde de flesta av dessa mot en live-nyckel medan jag skrev detta. De intressanta felen var beteendemässiga, inte fel: timingen för återupptagning ovan, och en VAD-tröskel utanför intervallet som accepterades i stället för att avvisas, den typen av sak som skeppas tyst trasig om du bara testar happy path. Lägg till ett flerspråkigt test också, och se FAQ för en knorr i hur du namnger språket.
Två av dessa kan du inte testa genom att skriva.
app_streamlit.pyär en Streamlit-sida som lägger ett live-samtal i webbläsaren: mikrofonen strömmar in i samma WebSocket över WebRTC, agentens röst strömmar tillbaka, och socketen förblir öppen hela tiden.