Vocal - devana.ai/docs
Rechercher la documentation ... Rechercher Ctrl K
© 2026 Devana. All rights reserved.
Architecture
Composants Composant Rôle Technologie WebRTC Transport audio bidirectionnel temps réel RTCPeerConnection DataChannel Canal de données pour événements et configuration RTCDataChannel VAD Détection d'activité vocale côté serveur Algorithme RMS adaptatif STT Transcription parole → texte Whisper (faster-whisper) LLM Génération de réponse API Devana Completions TTS Synthèse texte → parole Piper TTS
Protocole WebRTC
Établissement de la connexion
Création de l'offre SDP (côté client)
Envoi au serveur via HTTP POST
Réception de la réponse SDP
Établissement du flux audio bidirectionnel
Endpoint POST /v1/chat/vocal
Content-Type: application/json
Authorization: Bearer <token>
{
"sdp": "<SDP offer string>",
"agentId": "<ID de l'agent Devana>"
}
Réponse {
"sdp" : "<SDP answer string>" ,
"sessionId" : "<UUID de la session>"
}
Configuration ICE Le serveur utilise un serveur STUN public pour la traversée NAT :
iceServers : [{ urls : "stun:stun.l.google.com:19302" }];
DataChannel Le DataChannel doit être créé côté client avec le label "events" :
const channel = peerConnection.createDataChannel ("events" , { ordered : true });
Détection vocale (VAD) Le VAD (Voice Activity Detection) est un algorithme adaptatif basé sur l'énergie RMS du signal audio.
Fonctionnement
Calibration initiale (~1 seconde) : Mesure du bruit de fond ambiant
Calcul des seuils dynamiques : Basés sur le noise floor
Détection de début de parole : Énergie > seuil de démarrage pendant ~80ms
Détection de fin de parole : Silence > durée configurée
Paramètres adaptatifs Paramètre Description Valeur par défaut speechStartMultiplierMultiplicateur noise floor pour détecter la parole 3.5x speechEndMultiplierMultiplicateur pour détecter la fin 1.8x silenceDurationMsDurée de silence avant traitement 1500ms speechStartDebounceFramesAnti-rebond (évite faux positifs) 8 frames (~80ms) maxSpeechDurationMsDurée max avant traitement forcé 2000ms
Protection contre les faux positifs
Debounce : 8 frames consécutives au-dessus du seuil requises
Validation de pic : Un pic d'énergie minimum doit être atteint
Tolérance micro-pics : Les pics < 30ms pendant le silence sont ignorés
Modèles utilisés
STT (Speech-to-Text)
Modèle : Whisper (via faster-whisper)
Variante : faster-whisper-small
Langues : Multi-langue avec détection automatique
Format d'entrée : WAV 16kHz mono 16-bit
TTS (Text-to-Speech)
Moteur : Piper TTS
Modèle : fr_FR-upmc-medium (français)
Format de sortie : WAV 22050Hz
LLM
API : Devana Completions (/v1/chat/completions)
Streaming : Oui (Server-Sent Events)
Contexte : Historique de conversation maintenu
Événements DataChannel Le serveur envoie des événements JSON via le DataChannel pour informer le client de l'état du système.
Événements serveur → client
user_speech_startL'utilisateur a commencé à parler.
{ "type" : "user_speech_start" }
user_speech_endL'utilisateur a arrêté de parler.
{ "type" : "user_speech_end" }
processingIndique l'étape de traitement en cours.
{ "type" : "processing" , "stage" : "stt" | "llm" | "tts" }
transcriptionTexte transcrit de la parole utilisateur.
{ "type" : "transcription" , "text" : "Bonjour, comment ça va ?" }
response_textTexte de la réponse de l'IA (streaming).
{ "type" : "response_text" , "text" : "Je vais " , "partial" : true }
tts_startDébut de la synthèse vocale.
{ "type" : "tts_start" , "text" : "Je vais bien, merci !" }
tts_endFin de la synthèse vocale.
vad_statsStatistiques VAD en temps réel (pour debug/visualisation).
{
"type" : "vad_stats" ,
"energy" : 245.5 ,
"noiseFloor" : 52.3 ,
"speechStartThreshold" : 183.1 ,
"speechEndThreshold" : 94.1 ,
"peakEnergy" : 412.8 ,
"isSpeaking" : true ,
"calibrationProgress" : 100
}
errorErreur survenue côté serveur.
{ "type" : "error" , "message" : "STT transcription failed" }
Événements client → serveur
settings_updateMise à jour des paramètres.
{
"type" : "settings_update" ,
"settings" : {
"vadSensitivity" : 7 ,
"silenceDuration" : 1000 ,
"language" : "fr"
}
}
files_updateMise à jour des fichiers attachés.
{
"type" : "files_update" ,
"fileIds" : [ "file-id-1" , "file-id-2" ]
}
Intégration client
Prérequis
Navigateur compatible WebRTC (Chrome, Firefox, Safari, Edge)
Accès au microphone de l'utilisateur
Token d'authentification Devana valide
Étapes d'intégration
1. Demander l'accès au microphone const stream = await navigator.mediaDevices .getUserMedia ({ audio : true });
2. Créer la connexion WebRTC const peerConnection = new RTCPeerConnection ({
iceServers : [{ urls : "stun:stun.l.google.com:19302" }],
});
stream.getTracks ().forEach ((track ) => {
peerConnection.addTrack (track, stream);
});
3. Créer le DataChannel const dataChannel = peerConnection.createDataChannel ("events" , {
ordered : true ,
});
dataChannel.onmessage = (event ) => {
const data = JSON .parse (event.data );
console .log ("Event:" , data.type , data);
switch (data.type ) {
case "user_speech_start" :
break ;
case "transcription" :
break ;
case "response_text" :
break ;
case "tts_start" :
break ;
case "tts_end" :
break ;
}
};
4. Recevoir l'audio du serveur const remoteAudio = new Audio ();
remoteAudio.autoplay = true ;
peerConnection.ontrack = (event ) => {
if (event.streams && event.streams [0 ]) {
remoteAudio.srcObject = event.streams [0 ];
}
};
5. Créer et envoyer l'offre SDP const offer = await peerConnection.createOffer ();
await peerConnection.setLocalDescription (offer);
await waitForIceGathering (peerConnection);
const response = await fetch ("https://api.devana.ai/v1/chat/vocal" , {
method : "POST" ,
headers : {
"Content-Type" : "application/json" ,
Authorization : `Bearer ${userToken} ` ,
},
body : JSON .stringify ({
sdp : peerConnection.localDescription .sdp ,
agentId : "votre-agent-id" ,
}),
});
const { sdp, sessionId } = await response.json ();
await peerConnection.setRemoteDescription (
new RTCSessionDescription ({ type : "answer" , sdp }),
);
6. Fonction utilitaire pour ICE gathering function waitForIceGathering (pc, timeout = 5000 ) {
return new Promise ((resolve ) => {
if (pc.iceGatheringState === "complete" ) {
resolve ();
return ;
}
const checkState = ( ) => {
if (pc.iceGatheringState === "complete" ) {
pc.removeEventListener ("icegatheringstatechange" , checkState);
resolve ();
}
};
pc.addEventListener ("icegatheringstatechange" , checkState);
setTimeout (() => {
pc.removeEventListener ("icegatheringstatechange" , checkState);
resolve ();
}, timeout);
});
}
Paramètres configurables Les paramètres peuvent être envoyés via le DataChannel après connexion.
Sensibilité VAD dataChannel.send (
JSON .stringify ({
type : "settings_update" ,
settings : {
vadSensitivity : 5 ,
},
}),
);
Délai avant envoi dataChannel.send (
JSON .stringify ({
type : "settings_update" ,
settings : {
silenceDuration : 1500 ,
},
}),
);
Langue dataChannel.send (
JSON .stringify ({
type : "settings_update" ,
settings : {
language : "fr" ,
},
}),
);
Gestion des fichiers Vous pouvez attacher des fichiers (PDF, documents) que l'IA pourra consulter pendant la conversation vocale.
Upload de fichiers const formData = new FormData ();
formData.append ("file" , fileBlob);
const response = await fetch ("https://api.devana.ai/api/upload" , {
method : "POST" ,
headers : {
Authorization : `Bearer ${userToken} ` ,
conversation : sessionId,
wait : "true" ,
},
body : formData,
});
const { data } = await response.json ();
const fileIds = data.ids ;
Notifier le serveur dataChannel.send (
JSON .stringify ({
type : "files_update" ,
fileIds : fileIds,
}),
);
Exemples de code
Exemple complet (JavaScript vanilla) class DevanaVocalClient {
constructor (userToken, agentId ) {
this .userToken = userToken;
this .agentId = agentId;
this .peerConnection = null ;
this .dataChannel = null ;
this .sessionId = null ;
this .onEvent = null ;
}
async connect ( ) {
const stream = await navigator.mediaDevices .getUserMedia ({ audio : true });
this .peerConnection = new RTCPeerConnection ({
iceServers : [{ urls : "stun:stun.l.google.com:19302" }],
});
stream.getTracks ().forEach ((track ) => {
this .peerConnection .addTrack (track, stream);
});
this .dataChannel = this .peerConnection .createDataChannel ("events" , {
ordered : true ,
});
this .dataChannel .onmessage = (e ) => {
const data = JSON .parse (e.data );
this .onEvent ?.(data);
};
const remoteAudio = new Audio ();
remoteAudio.autoplay = true ;
this .peerConnection .ontrack = (e ) => {
remoteAudio.srcObject = e.streams [0 ] || new MediaStream ([e.track ]);
};
const offer = await this .peerConnection .createOffer ();
await this .peerConnection .setLocalDescription (offer);
await this ._waitForIce ();
const response = await fetch ("https://api.devana.ai/v1/chat/vocal" , {
method : "POST" ,
headers : {
"Content-Type" : "application/json" ,
Authorization : `Bearer ${this .userToken} ` ,
},
body : JSON .stringify ({
sdp : this .peerConnection .localDescription .sdp ,
agentId : this .agentId ,
}),
});
const { sdp, sessionId } = await response.json ();
this .sessionId = sessionId;
await this .peerConnection .setRemoteDescription (
new RTCSessionDescription ({ type : "answer" , sdp }),
);
return sessionId;
}
updateSettings (settings ) {
if (this .dataChannel ?.readyState === "open" ) {
this .dataChannel .send (
JSON .stringify ({
type : "settings_update" ,
settings,
}),
);
}
}
disconnect ( ) {
this .dataChannel ?.close ();
this .peerConnection ?.close ();
this .peerConnection = null ;
this .dataChannel = null ;
this .sessionId = null ;
}
async _waitForIce (timeout = 5000 ) {
return new Promise ((resolve ) => {
if (this .peerConnection .iceGatheringState === "complete" ) {
resolve ();
return ;
}
const check = ( ) => {
if (this .peerConnection .iceGatheringState === "complete" ) {
this .peerConnection .removeEventListener (
"icegatheringstatechange" ,
check,
);
resolve ();
}
};
this .peerConnection .addEventListener ("icegatheringstatechange" , check);
setTimeout (() => {
this .peerConnection .removeEventListener (
"icegatheringstatechange" ,
check,
);
resolve ();
}, timeout);
});
}
}
const client = new DevanaVocalClient ("votre-token" , "votre-agent-id" );
client.onEvent = (event ) => {
switch (event.type ) {
case "transcription" :
console .log ("Vous avez dit:" , event.text );
break ;
case "response_text" :
console .log ("IA:" , event.text );
break ;
case "tts_start" :
console .log ("L'IA parle..." );
break ;
case "tts_end" :
console .log ("L'IA a fini de parler" );
break ;
}
};
await client.connect ();
Limitations et bonnes pratiques
Limitations
Navigateurs supportés : Chrome 74+, Firefox 66+, Safari 14.1+, Edge 79+
Durée de parole : Max ~30 secondes par segment
Format audio : Le serveur attend du PCM 48kHz (géré automatiquement par WebRTC)
Bonnes pratiques
Gestion des erreurs : Toujours écouter l'événement error du DataChannel
Reconnexion : Implémenter une logique de reconnexion en cas de déconnexion
Indicateurs visuels : Utiliser les événements VAD pour montrer à l'utilisateur quand il est entendu
Permissions : Demander la permission micro avant d'initier la connexion
Cleanup : Toujours fermer proprement la connexion (arrêter les tracks, fermer le PeerConnection)
Gestion du bruit ambiant Le VAD s'adapte automatiquement au bruit de fond, mais pour de meilleurs résultats :
Utilisez un micro de qualité
Évitez les environnements très bruyants
Attendez la fin de la calibration (~1 seconde) avant de parler
Support Pour toute question technique concernant l'intégration :