> Discover all available pages from the documentation index: https://mastra.zisheng.pro/fr/llms.txt # Voix en temps réel La voix en temps réel transforme un Agent Mastra en appel en direct auquel un utilisateur peut participer, dans le navigateur ou par téléphone. Mastra s’appuie sur [LiveKit](https://livekit.io), une plateforme WebRTC open source pour l’audio et la vidéo en temps réel. Le package [`@mastra/livekit`](https://mastra.zisheng.pro/fr/reference/voice/livekit) connecte les Agents Mastra au [framework LiveKit Agents](https://docs.livekit.io/agents/) : LiveKit gère la boucle audio, notamment la détection d’activité vocale, la transcription en continu, la détection sémantique des tours de parole, l’interruption et la synthèse vocale. Votre Agent Mastra génère chaque réponse avec son propre modèle, ses Tools et sa mémoire. Utilisez la voix en temps réel lorsque vous avez besoin de conversations vocales interrompables et à faible latence. Pour de la parole à parole fondée sur un fournisseur sans LiveKit, consultez [Parole à parole](https://mastra.zisheng.pro/fr/guides/voice/speech-to-speech). ## Démarrage rapide Ces étapes vous font passer d’un projet vide à un Agent vocal avec lequel vous pouvez parler. Une session vocale comprend deux éléments à configurer ici : une route API sur votre serveur Mastra qui fournit les jetons d’accès, et un processus worker distinct qui exécute le pipeline audio et appelle votre Agent à chaque tour. 1. Installez le package d’intégration ainsi que les plugins LiveKit de détection d’activité vocale et de détection des tours de parole : **npm**: ```bash npm install @mastra/livekit @livekit/agents @livekit/agents-plugin-silero @livekit/agents-plugin-livekit ``` **pnpm**: ```bash pnpm add @mastra/livekit @livekit/agents @livekit/agents-plugin-silero @livekit/agents-plugin-livekit ``` **Yarn**: ```bash yarn add @mastra/livekit @livekit/agents @livekit/agents-plugin-silero @livekit/agents-plugin-livekit ``` **Bun**: ```bash bun add @mastra/livekit @livekit/agents @livekit/agents-plugin-silero @livekit/agents-plugin-livekit ``` 2. Définissez vos identifiants LiveKit dans un fichier `.env`. Créez un projet gratuit sur [LiveKit Cloud](https://cloud.livekit.io), ou exécutez un serveur local avec [`livekit-server --dev`](https://docs.livekit.io/home/self-hosting/local/) : ```bash LIVEKIT_URL=wss://your-project.livekit.cloud LIVEKIT_API_KEY=your-api-key LIVEKIT_API_SECRET=your-api-secret ``` 3. Ajoutez un Agent vocal à votre instance Mastra et exposez une route de connexion. L’assistant `liveKitConnectionRoute()` ajoute un point de terminaison `POST /voice/livekit/connection-details` qui crée un jeton LiveKit et répartit votre Agent dans une salle : ```typescript import { Mastra } from '@mastra/core/mastra' import { Agent } from '@mastra/core/agent' import { liveKitConnectionRoute } from '@mastra/livekit' const supportAgent = new Agent({ id: 'support', name: 'Support', instructions: 'You are a friendly phone support agent. Keep replies short and conversational.', model: 'openai/gpt-5-mini', }) export const mastra = new Mastra({ agents: { support: supportAgent }, server: { apiRoutes: [liveKitConnectionRoute({ agentName: 'mastra-voice' })], }, }) ``` 4. Créez le worker. Il s’exécute comme processus distinct, répond aux sessions LiveKit et appelle votre Agent à chaque tour. Les API de worker se trouvent au point d’entrée `@mastra/livekit/worker`, de sorte que le serveur Mastra ne charge jamais le runtime LiveKit Agents. Cet exemple utilise les chaînes de modèles LiveKit Inference pour la transcription et la synthèse vocale ; aucun plugin de fournisseur n’est donc requis : ```typescript import { fileURLToPath } from 'node:url' import { createLiveKitWorker, runLiveKitWorker } from '@mastra/livekit/worker' import { mastra } from './index' export default createLiveKitWorker({ mastra, agent: 'support', stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', turnDetection: 'multilingual', greeting: 'Hi! How can I help you today?', }) if (process.argv[1] === fileURLToPath(import.meta.url)) { runLiveKitWorker({ entry: import.meta.url, agentName: 'mastra-voice' }) } ``` L’option `agent` sélectionne l’Agent Mastra qui répond à chaque session. Transmettez une clé fixe, comme dans l’exemple, ou omettez-la pour utiliser l’`agentId` des métadonnées de répartition ; un seul worker peut ainsi servir tous les Agents de votre instance Mastra. 5. Téléchargez une fois les modèles de détection des tours de parole et d’activité vocale. Exécutez ensuite le worker dans un terminal et votre serveur Mastra dans un autre : ```bash npx livekit-agents download-files npx tsx src/mastra/voice-worker.ts dev ``` **npm**: ```bash npm run dev ``` **pnpm**: ```bash pnpm run dev ``` **Yarn**: ```bash yarn dev ``` **Bun**: ```bash bun run dev ``` Le worker s’enregistre auprès de votre serveur LiveKit et attend des sessions, tandis que `mastra dev` sert la route de connexion. 6. Parlez à votre Agent. Ouvrez le [LiveKit Agents Playground](https://agents-playground.livekit.io) hébergé, puis connectez-le à votre projet pour démarrer un appel sans créer de frontend. Pour connecter votre propre application, appelez plutôt la route de connexion pour obtenir un jeton. `POST /voice/livekit/connection-details` accepte les champs facultatifs `agentId`, `threadId` et `resourceId` dans le corps de requête, puis renvoie : ```json { "serverUrl": "wss://your-project.livekit.cloud", "roomName": "mastra-voice-a1b2c3d4", "participantName": "user-1", "participantToken": "eyJhbGci..." } ``` Cette réponse correspond au contrat employé par les kits de démarrage frontend de LiveKit ; les applications créées depuis [agent-starter-react](https://github.com/livekit-examples/agent-starter-react) ou les [composants LiveKit React](https://docs.livekit.io/reference/components/react/) fonctionnent donc sans modification. ## Détection des tours de parole et interruptions LiveKit détermine quand l’utilisateur a fini de parler et quand l’Agent a été interrompu. Les valeurs par défaut fonctionnent bien ; ajustez-les avec `turnHandling` : ```typescript export default createLiveKitWorker({ mastra, agent: 'support', stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', turnDetection: 'multilingual', turnHandling: { endpointing: { mode: 'dynamic', minDelay: 300, maxDelay: 3000 }, interruption: { minDuration: 500, resumeFalseInterruption: true }, }, }) ``` - `turnDetection: 'multilingual'` : exécute localement sur le processeur le modèle sémantique LiveKit de fin de tour. Il lit la transcription en direct afin d’éviter de couper l’utilisateur au milieu de sa pensée. Utilisez plutôt `'vad'` ou `'stt'` pour une détection de fin fondée sur le silence. - `endpointing` : limite le temps d’attente de l’Agent après que l’utilisateur a cessé de parler. - `interruption` : contrôle l’interruption. Lorsque l’utilisateur parle en même temps que l’Agent, LiveKit arrête la lecture et annule le flux Mastra en cours ; la génération de tokens s’arrête donc elle aussi. - `preemptiveGeneration` : démarre la réponse de l’Agent Mastra alors que l’utilisateur finit encore de parler, masquant le délai avant le premier token. Le worker la désactive par défaut : chaque tentative anticipée exécute l’Agent Mastra sur une transcription provisoire, et chaque exécution conserve le message de l’utilisateur, ce qui duplique les messages dans le thread. Réactivez-la avec `preemptiveGeneration: { enabled: true }` si la latence importe davantage que l’exactitude de l’historique du thread. Consultez la [documentation LiveKit sur la détection des tours](https://docs.livekit.io/agents/logic/turns/) pour toutes les options. ## Voix et transcription par appel Les options `stt` et `tts` de premier niveau s’appliquent à chaque appel. Pour les choisir par appel, avec une voix ou une langue par locataire, définissez plutôt les résolveurs `configuration.stt` et `configuration.tts`. Chaque résolveur s’exécute une fois par appel avec les métadonnées de répartition, le contexte de requête, le nom de la salle et le contexte du job, puis renvoie une valeur acceptée par l’option de premier niveau correspondante. Cette valeur est une instance de plugin ou une chaîne de modèle d’inférence. Renvoyez `undefined` pour revenir à l’option de premier niveau. L’exemple suivant attribue à chaque locataire sa propre voix de synthèse vocale, à partir de l’entrée `tenant` dans les métadonnées de répartition : ```typescript import * as cartesia from '@livekit/agents-plugin-cartesia' // One voice id per tenant, resolved from the dispatch metadata on each call. const tenantVoices: Record = { meridian: 'your-cartesia-voice-id-1', coastal: 'your-cartesia-voice-id-2', } // The resolver runs during call setup, so cache plugin instances across calls. const ttsByVoice = new Map() export default createLiveKitWorker({ mastra, agent: 'support', stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', configuration: { tts: ({ requestContext }) => { const voice = tenantVoices[requestContext?.tenant as string] if (!voice) return undefined // fall back to the top-level `tts` let tts = ttsByVoice.get(voice) if (!tts) { tts = new cartesia.TTS({ voice }) ttsByVoice.set(voice, tts) } return tts }, }, }) ``` `configuration.stt` fonctionne de la même manière pour la transcription par appel, par exemple avec un modèle de transcription ou une langue différente par locataire. Le message d’accueil possède une forme équivalente par appel : `configuration.greeting.text` accepte un résolveur ayant le même contexte d’appel, ce qui permet à un worker de commencer avec la formulation propre à chaque locataire. ## Mémoire et threads Lorsque l’Agent Mastra résolu possède une mémoire configurée, chaque appel devient un thread de mémoire : - `thread` prend par défaut le `threadId` des métadonnées de répartition, puis le nom de la salle. - `resource` prend par défaut le `resourceId` des métadonnées de répartition, puis le thread. Transmettez ici l’ID de votre utilisateur final afin de regrouper les appels sous le bon utilisateur. Mastra Studio envoie l’ID de l’Agent, conformément à la manière dont sa barre latérale liste les threads. - Lorsque le thread n’existe pas encore, le worker le crée avec le titre « Voice call » et les métadonnées `{ source: 'livekit' }`. Le message d’accueil parlé est enregistré comme premier message de l’assistant afin que le thread constitue une transcription complète de l’appel ; désactivez cela avec `persistGreeting: false`. Chaque tour envoie uniquement la nouvelle entrée de l’utilisateur ; Mastra Memory fournit l’historique, le rappel sémantique et la mémoire de travail. Épinglez une session à un thread existant en transmettant `threadId` dans le corps de la requête de connexion ; c’est utile pour poursuivre une conversation texte par la voix. Dans Studio, démarrer un appel depuis une discussion ouverte lie l’appel à ce thread, et la transcription est ajoutée à la discussion après chaque échange. Lorsqu’un utilisateur interrompt l’Agent, la génération en cours est abandonnée et aucun élément de ce tour n’est alors conservé. LiveKit garde dans sa transcription la partie que l’utilisateur a réellement entendue et, au tour suivant, le worker renvoie ce fragment entendu afin de compléter le thread pour qu’il corresponde à l’appel. Un utilisateur qui raccroche juste après l’interruption laisse ce dernier fragment non enregistré. Consultez [les tours interrompus](https://mastra.zisheng.pro/fr/reference/voice/livekit) pour les détails et une méthode de réconciliation. ## Parler pendant l’exécution des Tools Les conversations vocales ne peuvent pas rester silencieuses lorsqu’un Tool lent s’exécute. Utilisez `toolFeedback` pour prononcer une courte phrase lorsque l’Agent Mastra démarre un appel de Tool : ```typescript export default createLiveKitWorker({ mastra, agent: 'support', stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', toolFeedback: ({ toolName }) => toolName === 'searchOrders' ? 'Let me look that up.' : undefined, }) ``` La phrase est prononcée dans la réponse et enregistrée dans la transcription. ## Générer des réponses avec un Workflow Par défaut, le worker génère chaque réponse avec un Agent Mastra. Pour exécuter une logique en plusieurs étapes à chaque tour, par exemple classifier l’intention, router, appeler des Tools en séquence puis composer une réponse, générez plutôt les réponses avec un [Workflow](https://mastra.zisheng.pro/fr/docs/workflows/overview) Mastra. Définissez `workflow` à la place de `agent`. LiveKit gère toujours la boucle audio et appelle Mastra une fois par tour : le Workflow s’exécute donc jusqu’à son terme à chaque tour. Le Workflow ne peut ni être suspendu ni reprendre, et aucun état de conversation n’est conservé entre les tours. Transmettez la transcription via `workflowInput` afin que le Workflow reste sans état : ```typescript import { createLiveKitWorker, chatContextToMessages } from '@mastra/livekit/worker' import { mastra } from './index' export default createLiveKitWorker({ mastra, workflow: 'phoneConversation', workflowInput: ({ chatCtx }) => ({ history: chatContextToMessages(chatCtx) }), replyStep: 'generateResponse', stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', turnDetection: 'multilingual', }) ``` Un Workflow diffuse des événements d’étape structurés, et non du texte. Pour prononcer les tokens au fur et à mesure de leur génération, l’étape de réponse transfère le texte de son Agent vers le `writer` de l’étape : ```typescript const generateResponse = createStep({ id: 'generateResponse', // input and output schemas omitted execute: async ({ inputData, mastra, writer, abortSignal }) => { const stream = await mastra.getAgent('voice').stream(inputData.history, { abortSignal }) await stream.textStream.pipeTo(writer) return { assistantMessage: await stream.text } }, }) ``` - `replyStep` : limite la sortie prononcée à une étape. Omettez-le pour prononcer chaque étape qui écrit dans son `writer`. - `resultText` : solution de repli qui dérive la réponse du résultat final de l’exécution lorsqu’aucune étape ne diffuse de texte. La diffusion via `writer` réduit le délai avant le premier token ; privilégiez-la. - `abortSignal` : transmettez l’`abortSignal` de l’étape à `agent.stream()` afin que l’interruption arrête rapidement la génération. Lorsque l’utilisateur interrompt, le worker annule l’exécution. - `generate` : pour un contrôle total, transmettez plutôt une fonction `generate`. Il peut s’agir de tout générateur de réponses qui transforme un tour en flux texte. Avec un Workflow, le worker ne conserve pas automatiquement les tours comme le fait `stream()` d’un Agent. Conservez l’historique de conversation dans le Workflow, ou gardez la transcription LiveKit comme source de vérité et transmettez-la à chaque tour. ## Utiliser Mastra comme composant LLM `createLiveKitWorker()` gère la session LiveKit pour vous. Pour gérer vous-même la session, utilisez plutôt [`MastraLLM`](https://mastra.zisheng.pro/fr/reference/voice/livekit) : un plugin LLM LiveKit standard qui place un Agent Mastra dans l’emplacement `llm` de votre propre `voice.AgentSession`. L’application Mastra, la boucle d’Agent, les Tools, la mémoire et l’observabilité s’exécutent sur votre serveur Mastra, auquel le worker accède via HTTP. Le processus worker n’a besoin ni d’application Mastra, ni de base de données, ni de clés de fournisseur de modèles. ```typescript import { fileURLToPath } from 'node:url' import { defineAgent, voice } from '@livekit/agents' import * as silero from '@livekit/agents-plugin-silero' import { MastraLLM } from '@mastra/livekit/plugin' import { runLiveKitWorker } from '@mastra/livekit/worker' export default defineAgent({ entry: async ctx => { await ctx.connect() const session = new voice.AgentSession({ llm: new MastraLLM({ remote: { baseUrl: process.env.MASTRA_URL!, agentId: 'support' }, memory: { thread: ctx.room.name!, resource: 'user-7' }, }), stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', vad: await silero.VAD.load(), // Required with `memory`: LiveKit enables preemptive generation by default. turnHandling: { preemptiveGeneration: { enabled: false } }, }) await session.start({ // These instructions never reach the Mastra agent; its own instructions apply. agent: new voice.Agent({ instructions: 'Replies come from the Mastra agent.' }), room: ctx.room, }) session.say('Hi! How can I help you today?') }, }) if (process.argv[1] === fileURLToPath(import.meta.url)) { runLiveKitWorker({ entry: import.meta.url, agentName: 'mastra-voice' }) } ``` Les deux approches partagent le même pipeline de réponse ; choisissez selon qui doit gérer la session : | | `createLiveKitWorker()` | `MastraLLM` | | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | Gestion de session | L’assistant worker crée et gère l’`AgentSession` | Votre code crée la session ; toutes les options et tous les hooks LiveKit vous appartiennent | | Où l’application Mastra s’exécute | Dans le processus worker | Sur votre serveur Mastra, accessible via HTTP ou dans le processus via `agent` | | Besoins du processus worker | Votre application Mastra, votre stockage et les clés de fournisseur de modèles | Uniquement le SDK LiveKit et un accès réseau à votre serveur | | Commodités intégrées | Message d’accueil, contrôle du consentement, raccrochage déclenché par l’Agent, initialisation de thread, agrégation de l’observabilité | Recréez ce dont vous avez besoin avec les [assistants de session](https://mastra.zisheng.pro/fr/reference/voice/livekit) | | Idéal pour | Le chemin le plus rapide vers un Agent vocal fonctionnel ; le mode vocal Studio | Applications LiveKit existantes et contrôle total de la session | Les Tools restent sur l’Agent Mastra et s’exécutent sur le serveur. Les Tools côté LiveKit transmis à la session sont ignorés. L’activité des Tools parvient au worker via `toolFeedback` (formule d’attente prononcée), `onToolCall` (se déclenche au début de chaque appel de Tool) et `onTurnComplete` (se déclenche après chaque réponse avec le texte, les appels de Tools et l’usage de tokens). Le raccrochage déclenché par l’Agent ne prend que quelques lignes : associez `onToolCall` à [`runEndCall()`](https://mastra.zisheng.pro/fr/reference/voice/livekit). > **Attention:** Ne combinez pas l’option `memory` avec `preemptiveGeneration` de LiveKit, que LiveKit active par défaut dans les sessions que vous créez vous-même. Un tour spéculatif qui se termine avant que LiveKit ne l’écarte conserve dans le thread un message utilisateur et une réponse jamais prononcée. Définissez `turnHandling: { preemptiveGeneration: { enabled: false } }`, ou exécutez sans `memory` et transmettez la transcription complète à chaque tour. `MastraLLM` accepte également une instance `agent` Mastra dans le processus, la gestion de session sans second déploiement, ou une fonction `generate` personnalisée. Le transport distant est disponible de façon autonome sous la forme de [`createRemoteAgentReplyGenerator()`](https://mastra.zisheng.pro/fr/reference/voice/livekit), qui se branche aussi sur l’option `generate` de `createLiveKitWorker` pour exécuter le worker complet sur un serveur distant. ## Sessions initiées par le serveur Utilisez `dispatchVoiceSession()` pour ajouter un Agent vocal à une salle depuis votre propre code, par exemple afin de rejoindre une salle existante ou de lancer un [appel SIP](https://docs.livekit.io/sip/) sortant : ```typescript import { dispatchVoiceSession } from '@mastra/livekit' await dispatchVoiceSession({ roomName: 'support-call-42', agentName: 'mastra-voice', metadata: { agentId: 'support', threadId: 'thread-42', resourceId: 'user-7' }, }) ``` ## Observabilité Lorsque l’[observabilité](https://mastra.zisheng.pro/fr/docs/observability/overview) est configurée sur l’instance Mastra, le worker trace chaque appel. Il ouvre une span `voice call` par session et y imbrique tous les éléments : - L’exécution de l’Agent Mastra à chaque tour, avec la génération du modèle, les appels de Tools et les opérations de mémoire, exactement comme ils sont consignés par une discussion texte. - Une span enfant pour chaque métrique du pipeline LiveKit : transcription, synthèse vocale, fin d’énoncé (détection de tour), détection d’activité vocale et délai du modèle avant le premier token. Elles contiennent les mesures de latence et d’audio que les traces texte ne peuvent pas montrer. - Une agrégation d’utilisation par modèle, totaux de tokens, caractères et audio pour tout l’appel, écrite dans la span à la fin de la session. Le worker est un processus distinct : pointez donc le stockage vers un backend qui accepte les écritures simultanées du serveur et du worker. [LibSQL](https://mastra.zisheng.pro/fr/reference/storage/libsql), basé sur SQLite, convient ; les stockages à un seul écrivain ne conviennent pas. Les traces, la mémoire et les threads peuvent partager un même stockage : ```typescript import { Mastra } from '@mastra/core/mastra' import { LibSQLStore } from '@mastra/libsql' import { Observability, MastraStorageExporter } from '@mastra/observability' export const mastra = new Mastra({ storage: new LibSQLStore({ id: 'voice-agent-storage', url: 'file:./voice-agent.db' }), observability: new Observability({ configs: { default: { serviceName: 'voice-agent', exporters: [new MastraStorageExporter()], }, }, }), }) ``` Le traçage est activé par défaut. Transmettez `observability: false` à `createLiveKitWorker` pour le désactiver. ## Déploiement Le worker est un processus distinct de votre serveur Mastra ; `mastra build` doit donc le produire avec son propre point d’entrée. Ajoutez-le à [`bundler.entries`](https://mastra.zisheng.pro/fr/reference/configuration) : ```typescript import { Mastra } from '@mastra/core' export const mastra = new Mastra({ bundler: { entries: { 'voice-worker': './voice-worker.ts' }, // Keep LiveKit's native modules out of the bundle. `mastra build` only applies // this default when you set no other bundler options, so set it explicitly here. externals: true, }, }) ``` `mastra build` écrit désormais les deux processus dans `.mastra/output`, avec un même `package.json` et une seule installation des dépendances : ```text .mastra/output/ index.mjs # Mastra server voice-worker.mjs # LiveKit worker ``` Déployez ce répertoire comme un artefact unique et démarrez chaque processus avec sa propre commande : ```bash node .mastra/output/index.mjs # server node .mastra/output/voice-worker.mjs start # worker ``` Le worker a besoin des mêmes variables d’environnement que le serveur, auxquelles s’ajoutent `LIVEKIT_URL`, `LIVEKIT_API_KEY` et `LIVEKIT_API_SECRET`. Les recommandations de LiveKit sur le dimensionnement, l’arrêt gracieux et l’hébergement s’appliquent telles quelles. Consultez [Deploying agents](https://docs.livekit.io/agents/ops/deployment/). Les workers se connectent à LiveKit en sortie et n’ont donc pas besoin de ports entrants. ## Fonctionnement Une session vocale LiveKit comporte trois éléments : 1. Votre serveur Mastra crée un jeton d’accès LiveKit et répartit votre Agent dans une salle. La répartition transporte des métadonnées telles que l’ID d’Agent Mastra, le thread de mémoire et la ressource. 2. Un worker d’Agent LiveKit, processus distinct de longue durée, reçoit le job et exécute le pipeline audio. L’audio circule entre le navigateur et le worker via WebRTC et ne passe jamais par votre serveur HTTP Mastra. 3. Chaque fois que l’utilisateur termine un tour, le worker appelle `stream()` de l’Agent Mastra avec la nouvelle entrée et prononce le texte diffusé. Lorsque l’utilisateur interrompt, LiveKit annule le flux et Mastra cesse de générer. L’historique de conversation se trouve dans Mastra Memory ; les sessions vocales et les discussions texte peuvent donc partager un même thread. ## À consulter également - [Référence de `@mastra/livekit`](https://mastra.zisheng.pro/fr/reference/voice/livekit) - [Parole à parole](https://mastra.zisheng.pro/fr/guides/voice/speech-to-speech) - [Mémoire des Agents](https://mastra.zisheng.pro/fr/docs/memory/overview) - [Documentation LiveKit Agents](https://docs.livekit.io/agents/)