

Streaming partout, function calling pour la vérité, barge-in, opt-out conforme, handover humain : les six patterns qui transforment un agent vocal LLM en outil de production.
Brancher un agent LLM sur une ligne téléphonique via Vapi + SIP (IllyVoIP, Vonage), c'est techniquement séduisant. En production, c'est un terrain miné. Retour d'expérience.
Sur une mission d'agence, le client voulait automatiser ses appels sortants de qualification (B2B, prise de rendez-vous, relance). Cahier des charges : agent vocal naturel, intégration au CRM, opérateurs humains qui prennent le relais sur demande, conformité légale (consentement, opt-out).
Le stack final : Vapi pour l'orchestration vocale + SIP (IllyVoIP en backup, Vonage en principal) pour la connectivité PSTN, OpenAI GPT-4 pour la logique conversationnelle, N8N pour le pipeline CRM, PostgreSQL pour les logs.
L'objectif de cet article : partager ce qui a marché, ce qui a explosé en vol, et comment je relancerais aujourd'hui.
L'agent vocal LLM coche toutes les cases du buzz : automatisation, IA générative, scale, ROI marketing. Mais sous le capot, c'est l'intégration de cinq systèmes asynchrones qui dialoguent en temps réel :
À 3 secondes de latence cumulée, l'agent paraît "lent et perdu". À 1.2 seconde, "robot mais OK". Sous 800 ms, "presque humain". L'optimisation de latence est le projet, pas un détail.
| 1 | [Utilisateur] ← PSTN/SIP → [IllyVoIP / Vonage] |
| 2 | │ |
| 3 | ▼ |
| 4 | [Vapi] |
| 5 | ╱ │ ╲ |
| 6 | [Deepgram] │ [ElevenLabs] |
| 7 | STT │ TTS |
| 8 | ▼ |
| 9 | [Agent API] |
| 10 | Node.js |
| 11 | │ |
| 12 | ┌──────────┼──────────┐ |
| 13 | ▼ ▼ ▼ |
| 14 | [OpenAI] [Postgres] [N8N] |
| 15 | reasoning logs CRM |
Vapi orchestre la boucle audio (STT → agent → TTS) avec un streaming bidirectionnel. L'Agent API (mon code) reçoit le texte transcrit, appelle OpenAI, retourne la réponse à parler. Postgres trace tout. N8N intègre avec le CRM.
La règle absolue : jamais attendre la fin d'une étape pour commencer la suivante.
| 1 | // src/agent/handleTurn.ts |
| 2 | export async function handleTurn(userText: string, context: TurnContext) { |
| 3 | // 1. Stream OpenAI dès que disponible |
| 4 | const stream = await openai.chat.completions.create({ |
| 5 | model: "gpt-4o", |
| 6 | messages: [...context.history, { role: "user", content: userText }], |
| 7 | stream: true, |
| 8 | temperature: 0.4, |
| 9 | }); |
| 10 | |
| 11 | let buffer = ""; |
| 12 | for await (const chunk of stream) { |
| 13 | const delta = chunk.choices[0]?.delta?.content ?? ""; |
| 14 | buffer += delta; |
| 15 | |
| 16 | // 2. Émettre par phrase (split sur la ponctuation forte) |
| 17 | const sentenceEnd = buffer.search(/[.!?]\s/); |
| 18 | if (sentenceEnd > 0) { |
| 19 | const sentence = buffer.slice(0, sentenceEnd + 1); |
| 20 | buffer = buffer.slice(sentenceEnd + 2); |
| 21 | |
| 22 | // 3. Pousser à TTS sans attendre la fin du stream LLM |
| 23 | await vapi.speak(sentence); |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | // 4. Flush le reste |
| 28 | if (buffer.trim()) await vapi.speak(buffer); |
| 29 | } |
Bénéfice mesuré : latence perçue divisée par 3. Au lieu d'attendre 2 secondes de génération puis 1 seconde de TTS, l'utilisateur entend la première phrase au bout de 700 ms.
Un agent vocal n'est pas un chatbot textuel. Trois exigences spécifiques :
| 1 | Tu es Sarah, assistante commerciale chez Acme Corp. |
| 2 | |
| 3 | Règles obligatoires : |
| 4 | - Phrases courtes (max 15 mots) |
| 5 | - Une question par tour, jamais plus |
| 6 | - Confirme à voix haute les noms, emails, dates et numéros |
| 7 | - Si tu n'as pas compris, dis "je n'ai pas bien entendu, pouvez-vous répéter ?" |
| 8 | - N'invente jamais une information |
| 9 | |
| 10 | Objectif de l'appel : |
| 11 | - Qualifier le prospect (budget, besoin, échéance) |
| 12 | - Proposer un rendez-vous dans la semaine prochaine |
| 13 | - Récupérer email et meilleur créneau |
| 14 | |
| 15 | Si le prospect refuse : remercie poliment et propose un opt-out définitif. |
L'ordre des règles compte. Les modèles GPT-4 et Claude appliquent mieux les contraintes formulées en premier.
L'utilisateur va interrompre l'agent. C'est normal et c'est sain. L'agent doit s'arrêter immédiatement, pas continuer à parler pendant 3 secondes par-dessus l'utilisateur.
Vapi expose un événement barge-in quand le STT détecte que l'utilisateur commence à parler. Côté agent :
| 1 | vapi.on("barge-in", () => { |
| 2 | // 1. Couper le TTS immédiatement |
| 3 | vapi.stopSpeaking(); |
| 4 | |
| 5 | // 2. Marquer le tour LLM en cours comme abandonné |
| 6 | currentTurn?.abort(); |
| 7 | |
| 8 | // 3. Reprendre l'écoute |
| 9 | vapi.resumeListening(); |
| 10 | }); |
Sans ça, l'agent paraît "sourd" et l'expérience devient frustrante. C'est le détail qui fait passer "robot bizarre" à "agent acceptable".
L'agent ne doit JAMAIS s'inventer un numéro de téléphone, un email ou une disponibilité. Tout ce qui touche au monde réel passe par function calling structuré.
| 1 | const tools = [ |
| 2 | { |
| 3 | type: "function", |
| 4 | function: { |
| 5 | name: "checkAvailability", |
| 6 | description: "Vérifie les créneaux disponibles dans le calendrier", |
| 7 | parameters: { |
| 8 | type: "object", |
| 9 | properties: { |
| 10 | startDate: { type: "string", format: "date" }, |
| 11 | endDate: { type: "string", format: "date" }, |
| 12 | durationMin: { type: "number" }, |
| 13 | }, |
| 14 | required: ["startDate", "endDate", "durationMin"], |
| 15 | }, |
| 16 | }, |
| 17 | }, |
| 18 | { |
| 19 | type: "function", |
| 20 | function: { |
| 21 | name: "bookAppointment", |
| 22 | description: "Réserve un rendez-vous (à n'appeler qu'après confirmation utilisateur)", |
| 23 | parameters: { |
| 24 | type: "object", |
| 25 | properties: { |
| 26 | email: { type: "string", format: "email" }, |
| 27 | name: { type: "string" }, |
| 28 | slotIso: { type: "string", format: "date-time" }, |
| 29 | }, |
| 30 | required: ["email", "name", "slotIso"], |
| 31 | }, |
| 32 | }, |
| 33 | }, |
| 34 | ]; |
Quand GPT-4 décide d'appeler checkAvailability, mon code consulte vraiment le Google Calendar du commercial, retourne les créneaux, et GPT-4 formule la réponse. Pas de hallucination possible.
Important : bookAppointment n'est appelable qu'après confirmation explicite ("Je confirme votre RDV mardi 14h, c'est bien ça ?" → "oui"). Le system prompt impose cette discipline.
Trois exigences légales en France/UE pour l'appel sortant automatisé :
| 1 | // src/agent/optOut.ts |
| 2 | const OPT_OUT_PHRASES = [ |
| 3 | "ne m'appelez plus", |
| 4 | "ne plus me contacter", |
| 5 | "supprimez-moi", |
| 6 | "rgpd", |
| 7 | "opt out", |
| 8 | ]; |
| 9 | |
| 10 | export function detectOptOut(userText: string): boolean { |
| 11 | const normalized = userText.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); |
| 12 | return OPT_OUT_PHRASES.some((phrase) => normalized.includes(phrase)); |
| 13 | } |
| 14 | |
| 15 | // dans la boucle conversationnelle |
| 16 | if (detectOptOut(userText)) { |
| 17 | await vapi.speak("Très bien, je note votre demande. Vous ne recevrez plus d'appels de notre part. Bonne journée."); |
| 18 | await OptOut.create({ phone: context.phone, reason: "user_request", at: new Date() }); |
| 19 | await vapi.endCall(); |
| 20 | return; |
| 21 | } |
L'opt-out est non négociable. Pas de "êtes-vous sûr ?", pas de "puis-je vous proposer autre chose ?". On ferme, on log, on respecte.
Aucun agent LLM ne remplace un humain pour les cas sensibles. Détecter quand transférer :
| 1 | async function transferToHuman(reason: string, context: TurnContext) { |
| 2 | await vapi.speak("Je vais vous mettre en contact avec un conseiller, ne quittez pas."); |
| 3 | await Transfer.create({ callId: context.callId, reason, at: new Date() }); |
| 4 | await vapi.transferCall({ destination: HUMAN_QUEUE_NUMBER }); |
| 5 | } |
L'agent doit savoir "je ne sais pas faire ça" sans honte, et passer la main rapidement.
Tracer chaque appel sur trois plans :
| Métrique | Définition | Seuil acceptable |
|---|---|---|
| Latence p95 par tour | Temps user-parle-fini → agent-commence-à-parler | < 1200 ms |
| Taux de barge-in | % d'appels avec ≥1 interruption | Stable autour de 20-30 % |
| Taux d'opt-out | % d'appels finissant par opt-out | < 5 % |
| Taux de handover | % d'appels transférés à un humain | < 15 % |
| Taux de complétion | % d'appels atteignant l'objectif (RDV pris, qualif faite) | > 30 % |
Si la latence dérive, l'expérience explose en deux jours. Surveiller en continu.
| Piège | Symptôme | Correction |
|---|---|---|
| TTS sans streaming | Latence perçue insupportable | Streaming phrase par phrase |
| Pas de barge-in | Agent parle par-dessus | Listener barge-in + stopSpeaking |
| Hallucination sur dates/emails | Mauvais RDV pris, email faux | Function calling pour tout fait réel |
| Opt-out flou | Risque légal | Détection lexicale + journal + fin d'appel |
| Pas de handover | Frustration garantie | Détection multi-signaux + transfert |
| System prompt trop long | Modèle hallucine plus | Maximum 800 tokens, règles ordonnées |
| Pas de Bloctel | Risque légal | Vérification systématique avant chaque appel |
Coût mesuré par appel (qualif B2B, durée moyenne 3 min) :
Vs un commercial humain : ~3 à 5 € par appel (salaire chargé + frais). L'économie est réelle, mais elle suppose un volume d'appels conséquent — sous 200 appels/jour, l'humain reste meilleur en qualité de relation.
L'agent vocal LLM n'est pas un gadget. C'est un outil professionnel quand on respecte trois principes : streaming partout, function calling pour la vérité, conformité légale par défaut. Sans ces trois, c'est un démo qui ne tient pas en production.
L'agent n'est pas non plus un remplaçant universel du commercial humain. Il excelle sur la qualification massive et standardisée. Il échoue sur la nuance, la négociation et l'émotion. Hybride humain + agent reste le meilleur calcul économique.
Si vous démarrez : Vapi + GPT-4o + Deepgram + ElevenLabs est la stack qui a le mieux marché pour moi en 2026. Mais comptez 6 à 10 semaines pour livrer un agent réellement utilisable en production, pas le sprint week-end vendu sur LinkedIn.
Si ce sujet ressemble à un problème réel dans votre produit, je peux intervenir sur le diagnostic, l'architecture, le backend, l'interface et les automatisations qui rendent une plateforme exploitable.
Réactions des lecteurs
Aucun commentaire pour l'instant
Soyez le premier à réagir à cet article.