Aller au contenu principal

Assistant vocal multimodal

Assistant vocal multimodal

Un assistant vocal multimodal combine la voix avec d’autres modalités : texte, images, vidéo, données structurées. L’utilisateur parle, mais l’agent peut aussi afficher des informations visuelles, analyser des images envoyées par l’utilisateur, ou combiner plusieurs sources de données. C’est l’avenir de l’interaction homme-machine.

Texte et audio en parallèle

La Realtime API supporte nativement les modalités texte et audio simultanément. Vous pouvez injecter du texte dans la conversation tout en maintenant l’échange vocal :

async def inject_text_context(ws, context: str):
    """Ajoute du contexte textuel à la conversation vocale."""
    await ws.send(json.dumps({
        "type": "conversation.item.create",
        "item": {
            "type": "message",
            "role": "user",
            "content": [
                {
                    "type": "input_text",
                    "text": context
                }
            ]
        }
    }))

    # Déclencher une réponse vocale basée sur ce contexte
    await ws.send(json.dumps({"type": "response.create"}))

# L'utilisateur demande vocalement "Lis-moi mon dernier email"
# Vous injectez le contenu de l'email en texte
await inject_text_context(ws, """
Voici le dernier email reçu :
De : [email protected]
Objet : Demande de devis
Contenu : Bonjour, je souhaiterais obtenir un devis pour
l'intégration d'un agent vocal dans notre centre d'appels.
""")

Combiner Realtime API et Vision

Pour analyser des images pendant une conversation vocale, combinez la Realtime API avec la Responses API :

import base64

class MultimodalAgent:
    """Agent vocal capable d'analyser des images."""

    def __init__(self, ws, client):
        self.ws = ws
        self.client = client  # Client OpenAI standard

    async def analyze_image(self, image_path: str, question: str):
        """Analyse une image et répond vocalement."""
        # Lire et encoder l'image
        with open(image_path, "rb") as f:
            image_b64 = base64.b64encode(f.read()).decode()

        # Appel à la Responses API avec vision
        response = self.client.responses.create(
            model="gpt-4o-mini",
            input=[{
                "role": "user",
                "content": [
                    {"type": "input_text", "text": question},
                    {
                        "type": "input_image",
                        "image_url": f"data:image/jpeg;base64,{image_b64}"
                    }
                ]
            }]
        )

        description = response.output_text

        # Injecter la description dans la session Realtime
        await self.ws.send(json.dumps({
            "type": "conversation.item.create",
            "item": {
                "type": "message",
                "role": "assistant",
                "content": [{
                    "type": "input_text",
                    "text": f"[Analyse d'image] {description}"
                }]
            }
        }))

        # Répondre vocalement
        await self.ws.send(json.dumps({
            "type": "response.create",
            "response": {
                "instructions": f"Décrivez à l'utilisateur ce que montre l'image : {description}"
            }
        }))

Interface web multimodale

Pour une application web, combinez le WebSocket Realtime avec une interface visuelle :

from fastapi import FastAPI, WebSocket
from fastapi.staticfiles import StaticFiles

app = FastAPI()

@app.websocket("/ws/agent")
async def multimodal_agent(client_ws: WebSocket):
    """Agent multimodal avec interface web."""
    await client_ws.accept()

    headers = {
        "Authorization": f"Bearer {OPENAI_API_KEY}",
        "OpenAI-Beta": "realtime=v1"
    }

    async with websockets.connect(REALTIME_URL, extra_headers=headers) as openai_ws:
        await openai_ws.recv()  # session.created

        await openai_ws.send(json.dumps({
            "type": "session.update",
            "session": {
                "modalities": ["text", "audio"],
                "instructions": "Assistant multimodal. Répondez en français.",
                "voice": "nova"
            }
        }))
        await openai_ws.recv()

        async def client_to_openai():
            async for message in client_ws.iter_text():
                data = json.loads(message)

                if data["type"] == "audio":
                    # Relayer l'audio
                    await openai_ws.send(json.dumps({
                        "type": "input_audio_buffer.append",
                        "audio": data["audio"]
                    }))

                elif data["type"] == "text":
                    # Message texte (chat)
                    await openai_ws.send(json.dumps({
                        "type": "conversation.item.create",
                        "item": {
                            "type": "message",
                            "role": "user",
                            "content": [{
                                "type": "input_text",
                                "text": data["text"]
                            }]
                        }
                    }))
                    await openai_ws.send(json.dumps({
                        "type": "response.create"
                    }))

        async def openai_to_client():
            async for message in openai_ws:
                event = json.loads(message)
                t = event["type"]

                if t == "response.audio.delta":
                    await client_ws.send_json({
                        "type": "audio",
                        "delta": event["delta"]
                    })

                elif t == "response.audio_transcript.delta":
                    await client_ws.send_json({
                        "type": "transcript",
                        "delta": event["delta"]
                    })

                elif t == "response.text.delta":
                    await client_ws.send_json({
                        "type": "text",
                        "delta": event["delta"]
                    })

        await asyncio.gather(client_to_openai(), openai_to_client())

Scénarios d’utilisation multimodale

Voici comment un agent multimodal gère différents scénarios dans un outil de function calling :

tools = [
    {
        "type": "function",
        "name": "show_chart",
        "description": "Affiche un graphique de données à l'utilisateur",
        "parameters": {
            "type": "object",
            "properties": {
                "chart_type": {"type": "string", "enum": ["bar", "line", "pie"]},
                "title": {"type": "string"},
                "data": {"type": "object"}
            },
            "required": ["chart_type", "title", "data"]
        }
    },
    {
        "type": "function",
        "name": "send_document",
        "description": "Envoie un document au client par email",
        "parameters": {
            "type": "object",
            "properties": {
                "document_type": {"type": "string"},
                "recipient": {"type": "string"}
            },
            "required": ["document_type", "recipient"]
        }
    }
]

L’utilisateur dit vocalement : « Montre-moi les ventes du dernier trimestre sous forme de graphique ». L’agent appelle show_chart, votre frontend affiche le graphique, et l’agent commente vocalement les résultats.

Points clés à retenir

  • La Realtime API supporte les modalités texte et audio simultanément dans une même session
  • L’injection de texte via conversation.item.create permet d’enrichir le contexte vocal
  • L’analyse d’images combine la Responses API (vision) avec la Realtime API (voix)
  • Les outils (function calling) permettent d’afficher des éléments visuels tout en répondant vocalement
  • L’architecture WebSocket côté client permet de mixer audio, texte et données structurées