Multimodal : Entrée Vidéo
Un agent en temps réel peut voir. Parallèlement au flux audio, vous pouvez envoyer des images d'une caméra (ou d'un écran) au modèle, afin que l'utilisateur puisse montrer quelque chose à l'agent — un produit endommagé pour un retour, une erreur sur un écran, un document — au lieu de le décrire.
L'API
Une méthode sur le runner, reflétant send_audio :
runner.send_video_frame("image/jpeg", &base64_jpeg).await?;
Elle est également disponible sur le RealtimeRunner de niveau inférieur et sur RealtimeSession directement. L'implémentation de trait par défaut est une opération nulle (no-op), de sorte que les fournisseurs/modèles qui n'acceptent pas d'entrée visuelle ignorent simplement les trames.
Comment chaque fournisseur gère les trames
Les deux backends traitent la vision différemment, ce qui devrait influencer la fréquence d'envoi des trames :
| Fournisseur | Mécanisme | Cadence |
|---|---|---|
| Gemini Live | Blocs média realtimeInput continus | Diffusez-les — ~1–2 ips est naturel |
| OpenAI Realtime | Une partie input_image sur un élément de conversation | Instantanés — limitez à ~1 toutes les quelques secondes |
Gemini est conçu pour la vidéo continue : les trames circulent comme l'audio et le modèle raisonne sur l'image en mouvement. La vision en temps réel d'OpenAI est image-dans-contexte : chaque trame devient un élément de conversation, donc l'envoi de nombreuses trames par seconde inonde le contexte et coûte plus cher. Envoyez une trame à OpenAI lorsque c'est utile (par exemple, l'utilisateur a dit "regardez ça"), pas à chaque tic.
Les interfaces utilisateur d'exemple encodent ceci :
const intervalMs = provider === 'gemini' ? 700 : 2500; // ~1.4 fps vs ~0.4 fps
Capturer des trames dans un navigateur
Prenez la caméra, dessinez le <video> sur un canevas et envoyez les trames JPEG via votre WebSocket (le pont côté serveur les transmet à send_video_frame) :
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480 } });
video.srcObject = stream;
const canvas = document.createElement('canvas');
setInterval(() => {
if (ws.readyState !== WebSocket.OPEN) return;
canvas.width = 640; canvas.height = 480;
canvas.getContext('2d').drawImage(video, 0, 0, 640, 480);
const data = canvas.toDataURL('image/jpeg', 0.6).split(',')[1]; // strip data: prefix
ws.send(JSON.stringify({ type: 'video_frame', mime: 'image/jpeg', data }));
}, intervalMs);
Sur le serveur, transmettez au modèle :
ClientMsg::VideoFrame { mime, data } => {
let _ = runner.send_video_frame(&mime, &data).await; // non-fatal on error
}
Gardez les images modestes (640×480, qualité JPEG ~0.6) — la vision n'a pas besoin d'une résolution complète, et des images plus petites signifient une latence et un coût inférieurs.
Inviter à la vision
Dites à l'agent qu'il peut voir, afin qu'il utilise la caméra naturellement :
You can hear the customer and SEE what they show their camera. When they show
you an item (e.g. a damaged product for a return), briefly describe what you see
and use it to help resolve the issue.
Essayez
L'exemple customer_service dispose d'un panneau de caméra : démarrez la caméra, tenez un objet, et dites "Je veux retourner ça — pouvez-vous le voir ?". Cela fonctionne mieux sur Gemini (vidéo continue) ; sur OpenAI, l'agent raisonne sur les instantanés périodiques.
Suivant : Dialogue affectif →