GUIDE · LE CLUB IA VIP
Construire un agent vocal utile
Exemple fil rouge : Margot, hôtesse vocale du Petit Bistro, qui répond au téléphone et réserve dans Google Calendar. Modèle : grok-voice-think-fast-1.0.
1. Comment ça marche, en 30 secondes
- Deux portes d'entrée, une seule config d'agent :
- Web — le navigateur ouvre un WebSocket directement vers
wss://api.x.ai/v1/realtime. Le serveur ne fait que minter un token éphémère et servir l'app. - Téléphone (Twilio) — Twilio ouvre un WebSocket vers notre serveur. Le serveur relaie l'audio µ-law 8 kHz vers xAI dans les deux sens, et gère les tool calls lui-même.
- Web — le navigateur ouvre un WebSocket directement vers
- VAD côté serveur (
turn_detection: server_vad) détecte automatiquement la fin des paroles — pas de bouton "send". - Un seul modèle pour tout : ASR, raisonnement, TTS, orchestration d'outils. Latence sub-seconde.
2. Les fichiers à connaître
| Fichier | Rôle | Edit |
|---|---|---|
web/config/system_prompt.txt | Personnalité, règles, conversation flow. | Souvent |
web/config/tools.json | Outils que l'agent peut appeler (function, MCP, web_search…). | Oui |
web/static/voice.js | Implémentations des function tools côté navigateur (map FUNCTIONS). | Si tu ajoutes un tool |
web/server.py | Token éphémère, /config, relais Twilio, handlers serveur des function tools. | Si tu ajoutes un tool (path Twilio) |
.env | XAI_API_KEY, COMPOSIO_*, TWILIO_*. Gitignored. | Une fois |
system_prompt.txt ou tools.json — /config relit le disque à chaque session. En prod, le dossier web/config est bind-mounté dans le container, donc git pull suffit (pas de docker compose up).
3. Écrire un bon system prompt
Suivre la structure 8 sections du Realtime Prompting Guide d'OpenAI (xAI mirroirent l'API) :
- Role & Objective — qui tu es, quel est l'appel réussi.
- Personality & Tone — ton, débit, longueur cible des phrases, vouvoiement.
- Unclear audio — "ne devine pas, demande de répéter". Critique pour le téléphone.
- Reference Pronunciations — comment dire les horaires, numéros, noms propres.
- Tools — chaque outil + ses paramètres + quand l'appeler.
- Rules — les ABSOLUES (ne jamais mentir, tirer sur la gâchette dès que les infos sont là).
- Conversation Flow — un état par étape (Greeting / Collect / Check / Book / Goodbye), avec sample phrases et "Exit when".
- Safety & Escalation — quoi dire quand c'est hors-cadre.
- Bullets > paragraphes — le modèle suit mieux les listes courtes.
- MAJUSCULES pour l'emphase des règles critiques.
- Donne des exemples de phrases par état — le modèle imite le style.
- Pin la langue dans une section dédiée si tu as des dérapages.
4. Les outils — le cœur d'un vrai agent
Un agent qui ne peut que parler n'est pas un agent. xAI accepte plusieurs types d'outils dans session.update.tools :
4.1 function — tools maison
Tu déclares un nom + un schéma JSON. Quand le modèle appelle, il émet response.function_call_arguments.done. Ton code exécute et renvoie le résultat.
// tools.json
{
"type": "function",
"name": "book_reservation",
"description": "Réserve une table. À appeler après confirmation orale.",
"parameters": {
"type": "object",
"properties": {
"name": { "type": "string" },
"phone": { "type": "string" },
"date": { "type": "string", "description": "YYYY-MM-DD" },
"time": { "type": "string", "description": "HH:MM 24h" },
"party_size": { "type": "integer" },
"notes": { "type": "string" }
},
"required": ["name","phone","date","time","party_size"]
}
}
function tool tu DOIS ajouter un handler à deux endroits :
web/static/voice.js— mapFUNCTIONS(path navigateur)web/server.py— fonction_server_tool_call(path Twilio, sans navigateur dans la boucle)
4.2 web_search — recherche web (zéro code)
xAI exécute la recherche, pas toi.
{ "type": "web_search" }
4.3 x_search — recherche sur X / Twitter
{ "type": "x_search", "allowed_x_handles": ["votre_resto"] }
4.4 file_search — RAG sur tes documents
Upload ton menu via la Collections API, récupère un collection_id, puis :
{
"type": "file_search",
"vector_store_ids": ["ton-collection-id"],
"max_num_results": 8
}
4.5 mcp — serveurs Model Context Protocol
Le plus puissant. Tu plug n'importe quel serveur MCP et xAI s'occupe de tout (list_tools, appels, retours). Voir section 5 pour la liste active + comment en ajouter un.
5. MCP — la liste active et comment en ajouter
5.1 Outils MCP actuellement câblés dans tools.json
| Serveur | Outil exposé | Usage par Margot |
|---|---|---|
Composio · Google Calendar${COMPOSIO_MCP_URL} |
GOOGLECALENDAR_FIND_FREE_SLOTS |
Vérifier la dispo d'un créneau avant de proposer une heure. Lecture seule — la création passe par book_reservation (voir 5.3). |
Le bloc dans tools.json :
{
"type": "mcp",
"server_url": "${COMPOSIO_MCP_URL}",
"server_label": "google_calendar",
"server_description": "Read-only Google Calendar access via Composio. Use GOOGLECALENDAR_FIND_FREE_SLOTS to check availability before quoting a time. Do NOT use this for creating events — use the book_reservation function tool instead.",
"headers": { "x-api-key": "${COMPOSIO_API_KEY}" },
"allowed_tools": ["GOOGLECALENDAR_FIND_FREE_SLOTS"]
}
${ENV_VAR}danstools.json— résolu côté serveur parstring.Template.safe_substitutedans/config, donc tu peux pousser le fichier sur GitHub sans fuiter ta clé Composio.allowed_tools— filtre explicite pour n'exposer que ce dont tu as besoin. Sans ce filtre, xAI charge tous les tools du serveur MCP — souvent inutile et coûteux en tokens.server_description— explique au modèle quand utiliser ce serveur. Le ton "do NOT use this for X" évite que Grok hallucine unGOOGLECALENDAR_CREATE_EVENT.
5.2 Ajouter un nouveau MCP (recette en 4 étapes)
Exemple : connecter un MCP Notion pour que Margot puisse lire les "Spéciaux du jour" dans une page.
- Trouver un serveur MCP — soit officiel (référentiel MCP), soit via une plateforme comme Composio qui héberge des centaines d'intégrations (Notion, Slack, HubSpot, Stripe, Linear…) avec OAuth déjà géré.
- Récupérer l'URL + l'auth :
- Composio → connecte le compte cible → crée un MCP server → tu obtiens une URL
https://mcp.composio.dev/...et tu utilises taCOMPOSIO_API_KEYcomme headerx-api-key. - Serveur MCP auto-hébergé → URL +
Bearer <token>enauthorization.
- Composio → connecte le compte cible → crée un MCP server → tu obtiens une URL
- Ajouter le bloc dans
tools.json:{ "type": "mcp", "server_url": "${NOTION_MCP_URL}", "server_label": "notion", "server_description": "Read-only access à la base Notion du restaurant. Utilise NOTION_QUERY_DATABASE pour les Spéciaux du jour.", "headers": { "x-api-key": "${COMPOSIO_API_KEY}" }, "allowed_tools": ["NOTION_QUERY_DATABASE"] } - Mettre les vars dans
.env(et redémarrer le container une seule fois, ensuite tout est hot-reloaded) :NOTION_MCP_URL=https://mcp.composio.dev/composio/server/...
Mentionne le nouvel outil dans system_prompt.txt section Tools avec un exemple d'appel — sans ça, le modèle ne saura pas qu'il existe même s'il est listé techniquement.
[xai] session.updated tools=[..., mcp:notion]. Si notion n'apparaît pas, ton URL ou tes headers sont faux.
5.3 Pourquoi book_reservation est une function, pas un appel direct au MCP ?
Composio expose aussi GOOGLECALENDAR_CREATE_EVENT, mais on ne le branche pas dans tools.json. Raison : le schéma complet a beaucoup de champs (recurrence, working location, attendees…) et Grok a tendance à les remplir avec des valeurs hallucinées. On préfère une function maison qui construit le payload côté serveur (dans /api/calendar/book) à partir des 5 champs métier, et POST elle-même vers l'API REST Composio. Le modèle ne voit que les 5 champs qui comptent.
Garde-fou supplémentaire côté serveur : si le modèle passe quand même phone="anonymous" (cas masked CallerID), book_reservation retourne {"status":"error","message":"INVALID_PHONE: …"} pour forcer Margot à redemander.
6. Connecter un vrai numéro de téléphone (Twilio)
Implémenté dans ce projet. Architecture :
PSTN → Twilio → wss://<ton-domaine>/twilio/stream → wss://api.x.ai/v1/realtime
↓
tool calls server-side
(book_reservation, end_call, ...)
- Configurer le webhook dans la console Twilio sur ton numéro : Voice & Fax → A call comes in → Webhook →
https://<ton-domaine>/twilio/voice(POST). Pas de TwiML Bin à créer, le serveur génère la TwiML dynamiquement. - Ajouter dans
.env:TWILIO_ACCOUNT_SID+TWILIO_AUTH_TOKEN— nécessaires pour raccrocher via l'API REST (close WS seul ≠ raccrocher). - Récupération du numéro appelant : Twilio envoie
From=+33...dans le body du webhook./twilio/voicele passe en<Parameter name="from">dans la TwiML, et/twilio/streaml'injecte dans le system prompt comme contexte. Si l'appelant masque son numéro (Twilio envoieFrom=anonymous), un bloc différent est injecté : "demande le numéro explicitement". - Format audio — Twilio parle µ-law 8 kHz nativement. xAI accepte aussi via
audio.input.format = audio.output.format = "audio/pcmu". Zéro transcoding, on relaye juste les bytes base64. - Filet de sécurité au raccrochage — Grok-voice oublie souvent d'appeler
end_callaprès avoir dit "au revoir". Le serveur scanne le transcript de l'assistant et déclenche le hangup lui-même si une phrase d'au revoir est détectée mais queend_calln'a pas été émis.
3651 avant le numéro — ça force la présentation du CallerID pour cet appel uniquement.
7. Déployer 24/7 sur un VPS (Docker + Caddy)
Le projet livre tout ce qu'il faut : Dockerfile, docker-compose.yml, et un example Nginx (deploy/nginx.conf.example). En prod sur ce démo on tourne sur Caddy (TLS automatique, WebSockets natifs).
- Cloner sur ton VPS :
git clone https://github.com/Thomas-Berton/template-grok-voice-agent.git /opt/margot-voice - Créer
.envavecXAI_API_KEY,COMPOSIO_API_KEY,COMPOSIO_MCP_URL,TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN. - Lancer le container :
Le container écoute surcd /opt/margot-voice docker compose up -d --build docker logs -f margot-voice127.0.0.1:8001uniquement — il ne touche pas les autres apps de la machine. - Configurer le reverse proxy — exemple Caddy minimaliste, à ajouter dans
/etc/caddy/Caddyfile:
Puisvoiceagents.tondomaine.com { reverse_proxy 127.0.0.1:8001 { header_up Host {host} } }systemctl reload caddy. Caddy provisionne automatiquement le cert Let's Encrypt. - Pointer ton DNS (enregistrement
A) sur l'IP du VPS, attendre la propagation, et c'est jouable.
header_up Host {host} est obligatoire — /twilio/voice lit le header Host pour construire l'URL wss://<public-host>/twilio/stream retournée dans la TwiML. Sans ça, Twilio essaierait de joindre wss://127.0.0.1:8001.
8. Logs et debug
Le serveur logue des lignes taggées pour que docker logs -f margot-voice suffise à diagnostiquer :
[twilio] ...— cycle de vie du WS Twilio (connecté, start avecfrom='...', stop).[caller] ...— transcript final de l'appelant (Whisper).[margot] ...— transcript final de l'assistant.[tool] → name(args)/[tool] ← name → result— function tools côté serveur.[xai] session.updated tools=[...]— outils que xAI a vraiment accepté (super utile pour vérifier qu'un MCP est bien chargé).[xai] response.mcp_call.in_progress / .completed— appels MCP gérés par xAI eux-mêmes (utile pour debug Composio).[xai] ERROR (...)— toute anomalie remontée par xAI.
- Pas de
[tool] → book_reservationalors que tu as donné les 5 infos → la règle R2 du prompt n'est pas suivie. - Caractères en alphabets non-latins dans
[caller]→ le pinlanguage: "fr"du Whisper a sauté. - Pas de
mcp:google_calendardanssession.updated→ ton MCP URL ou ton header API key est faux. - Côté navigateur : DevTools → Network → onglet WS → tu vois chaque event en clair.
9. Astuces voix & latence
- Voix — on utilise
ara(voix française de la Composio Voice Library). xAI fournit aussieve,ara,rex,sal,leoen stock. - Sample rates — 24 kHz PCM partout côté browser, 8 kHz µ-law côté Twilio. Ne pas mélanger, audio crachote sinon.
- Latence mic — démarre la capture en parallèle du WS open, bufferise, flush au
onopen(déjà fait dansvoice.js). - Audio coupé sur le "au revoir" — ne ferme jamais le WS dès que
end_callest invoqué : attends la fin de la lecture (response.done+ drain). - Sécurité — jamais la
XAI_API_KEYdans le navigateur. Toujours via/token(secret éphémère 5 min). Côté Twilio, le serveur s'authentifie directement avec sa clé serveur.
10. Ressources
- Doc Voice Agent API (xAI)
- Référence des events realtime (xAI)
- Realtime Prompting Guide (OpenAI cookbook) — la bible du system prompt vocal, applicable à xAI.
- Model Context Protocol — spec
- Référentiel des serveurs MCP officiels
- Composio — MCP managé pour 300+ apps
- Twilio Media Streams (TwiML <Stream>)
- Caddyfile reference