> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aihubmix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversation en temps réel

> Établissez une connexion WebSocket persistante pour une interaction vocale et textuelle bidirectionnelle à faible latence avec un modèle de conversation

## Introduction

La conversation en temps réel établit une connexion persistante via WebSocket (un protocole qui maintient une connexion durable et bidirectionnelle entre le client et le serveur), envoie votre audio ou votre texte au modèle de conversation en temps réel, et le modèle renvoie progressivement du texte et de la voix. Elle convient aux assistants vocaux, aux questions-réponses en temps réel, à la pratique orale et aux autres scénarios qui exigent des échanges successifs.

Comme la [transcription en temps réel](/fr/api/realtime-transcription), elle passe par WebSocket, mais les deux ont des usages différents :

| Dimension                         | Transcription en temps réel                    | Conversation en temps réel (cette page)                                   |
| --------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------- |
| Objectif                          | Convertir la parole en texte                   | Mener une conversation à plusieurs tours où le modèle génère des réponses |
| Sens                              | Unidirectionnel : audio entrant, texte sortant | Bidirectionnel : audio ou texte entrant, texte et voix sortants           |
| Paramètres de connexion           | `?intent=transcription&model=...`              | `?model=...` uniquement (sans `intent`)                                   |
| Détection d'activité vocale (VAD) | Non pris en charge, doit être `null`           | Pris en charge, détection automatique des tours disponible                |
| Cas d'usage typiques              | Sous-titres de réunion, dictée de direct       | Assistants vocaux, interaction orale en temps réel                        |

**Modèle disponible :**

* **gpt-realtime-2.1** : modèle de conversation vocale, prend en charge l'audio et le texte en entrée et produit du texte et de la voix en temps réel.

<Warning>
  **Cette API est destinée à une intégration côté serveur ; les navigateurs ne peuvent pas s'y connecter directement.** Pour des raisons de sécurité, la passerelle valide et refuse les connexions qui portent un en-tête `Origin`, refuse le sous-protocole `openai-insecure-api-key` et n'accepte la clé que via l'en-tête standard `Authorization`. Un WebSocket initié par un navigateur ajoute automatiquement un en-tête `Origin` et se voit donc refusé. Pour faire de la conversation en temps réel dans un frontend, établissez la connexion vers la passerelle depuis votre propre serveur, puis transférez l'audio et les résultats entre votre frontend et votre serveur.
</Warning>

## Démarrage rapide

### Point de terminaison

```text theme={null}
wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1
```

* `model=gpt-realtime-2.1` : **obligatoire**, le modèle est fixé au moment de la connexion par le paramètre d'URL et ne peut plus être modifié pendant la session (voir les contraintes ci-dessous).
* **Attention à la différence avec la transcription** : le point de terminaison de conversation ne prend **pas** `intent=transcription`.

### Authentification

Transmettez la clé dans un en-tête HTTP standard lors de la poignée de main :

```text theme={null}
Authorization: Bearer $AIHUBMIX_API_KEY
```

### Format audio requis

L'audio en entrée et en sortie ne prend actuellement en charge qu'un seul format. Convertissez votre audio avant l'envoi :

* **Encodage** : PCM16 (entier signé 16 bits, petit-boutiste)
* **Fréquence d'échantillonnage** : 24000 Hz
* **Canaux** : mono

Soit `audio/pcm@24000`. Déclarer un autre format (comme G.711/µ-law) en entrée ou en sortie est refusé et entraîne la fermeture de la session.

<Note>
  Contrairement à la transcription, les sessions de conversation **prennent en charge** la détection d'activité vocale (turn\_detection / VAD). Lorsqu'elle est activée, le modèle détermine automatiquement la fin d'une prise de parole et déclenche une réponse ; lorsqu'elle est désactivée (valeur `null`), vous contrôlez vous-même le moment de valider l'audio et de demander une réponse. Choisissez selon vos besoins.
</Note>

## Configuration de session (session.update)

Une fois la connexion établie, le client peut envoyer une trame `session.update` pour configurer les paramètres de conversation (voix, instructions système, activation du VAD). Le modèle de la session de conversation est déjà ancré par l'URL de connexion, donc **vous pouvez converser sans envoyer `session.update`** ; envoyez-la lorsque vous avez besoin d'une voix ou d'instructions personnalisées.

```json theme={null}
{
  "type": "session.update",
  "session": {
    "type": "realtime",
    "instructions": "You are a helpful voice assistant. Keep answers concise.",
    "audio": {
      "input": {
        "format": { "type": "audio/pcm", "rate": 24000 },
        "turn_detection": { "type": "server_vad" }
      },
      "output": {
        "format": { "type": "audio/pcm", "rate": 24000 },
        "voice": "alloy"
      }
    }
  }
}
```

### Paramètres de configuration

<ParamField body="session.type" type="string" required>
  Type de session, `realtime` pour le scénario de conversation.
</ParamField>

<ParamField body="session.instructions" type="string">
  Instructions système qui définissent le rôle, le ton et les contraintes de réponse du modèle.
</ParamField>

<ParamField body="session.output_modalities" type="string[]">
  Modalités de sortie, `["audio"]` ou `["text"]` : avec `["audio"]` (par défaut), le modèle produit de la voix et le texte de la réponse arrive via l'événement `response.output_audio_transcript.delta` ; avec `["text"]`, il ne produit que du texte, transmis via `response.output_text.delta`. Toute autre combinaison (par exemple `["audio", "text"]`) est refusée et renvoie un événement `error`.
</ParamField>

<ParamField body="session.audio.input.format" type="object" required>
  Format audio d'entrée, fixé à `{ "type": "audio/pcm", "rate": 24000 }`.
</ParamField>

<ParamField body="session.audio.input.turn_detection" type="object | null">
  Détection d'activité vocale. Transmettez `{ "type": "server_vad" }` pour activer la détection automatique des tours ; transmettez `null` pour la désactiver et valider l'audio et demander les réponses manuellement depuis le client.
</ParamField>

<ParamField body="session.audio.output.format" type="object" required>
  Format audio de sortie, fixé à `{ "type": "audio/pcm", "rate": 24000 }`.
</ParamField>

<ParamField body="session.audio.output.voice" type="string">
  Voix de la réponse audio. **Elle ne peut plus être modifiée une fois la première réponse commencée** : une fois la session en état de génération, un `voice` renvoyé est ignoré (les autres réglages restent appliqués). Définissez donc la voix avant de demander la première réponse.
</ParamField>

<Note>
  Les capacités suivantes ne sont **pas prises en charge dans cette version** et ferment la session lorsqu'elles sont configurées (code de fermeture `1008`) : activer la transcription intégrée dans une session de conversation (`audio.input.transcription`, raison `input_transcription_not_supported`), injecter de l'audio (raison `item_audio_not_supported`) ou des images (raison `image_input_not_supported`) via les items de conversation, et tout type d'item de contenu autre que le texte (raison `unsupported_content_part`). Envoyez tout l'audio via le canal `input_audio_buffer.append`.
</Note>

## Envoi de l'entrée

### Envoi d'audio

Découpez l'audio PCM16 en petits fragments (par exemple un fragment toutes les 100 ms), encodez-les en base64 et envoyez-les en continu avec l'événement `input_audio_buffer.append` :

```json theme={null}
{
  "type": "input_audio_buffer.append",
  "audio": "<fragment audio PCM16 encodé en base64>"
}
```

Lorsque le VAD est activé, le modèle détecte automatiquement la fin de la prise de parole et déclenche une réponse. Lorsqu'il est désactivé, validez et demandez une réponse manuellement après avoir envoyé un segment d'audio :

```json theme={null}
{ "type": "input_audio_buffer.commit" }
{ "type": "response.create" }
```

### Envoi de texte

Vous pouvez aussi injecter directement un message texte puis demander une réponse :

```json theme={null}
{
  "type": "conversation.item.create",
  "item": {
    "type": "message",
    "role": "user",
    "content": [{ "type": "input_text", "text": "Introduce yourself in one sentence." }]
  }
}
{ "type": "response.create" }
```

## Réception des réponses

Le serveur continue de pousser des événements. Types d'événements clés :

<ParamField body="session.created / session.updated" type="event">
  Confirmation de la création de la session ou de la mise à jour de sa configuration. Vous pouvez commencer à envoyer de l'audio et du texte dès réception de `session.created`.
</ParamField>

<ParamField body="conversation.item.added / conversation.item.done" type="event">
  Un item de conversation a été écrit : chaque entrée utilisateur et chaque réponse du modèle ajoute un item.
</ParamField>

<ParamField body="input_audio_buffer.speech_started / input_audio_buffer.speech_stopped" type="event">
  Lorsque la VAD est activée, le serveur a détecté que l'utilisateur commence ou arrête de parler. Un événement `speech_started` signifie généralement que l'utilisateur interrompt le modèle ; voir [Interruption et troncature](#interruption).
</ParamField>

<ParamField body="response.created" type="event">
  Une réponse a commencé à être générée.
</ParamField>

<ParamField body="response.output_item.added / response.output_item.done" type="event">
  L'item de sortie de la réponse a commencé et s'est terminé. Le champ `item.id` de l'événement `added` est l'ID d'item de conversation à référencer pour tronquer l'audio ensuite.
</ParamField>

<ParamField body="response.output_audio.delta / response.output_audio.done" type="event">
  Un fragment **incrémental** de l'audio de la réponse (PCM16 encodé en base64) et son marqueur de fin, que vous pouvez lire au fil de l'arrivée.
</ParamField>

<ParamField body="response.output_audio_transcript.delta / response.output_audio_transcript.done" type="event">
  La transcription **incrémentale** correspondant à l'audio de la réponse, phrase par phrase, et son marqueur de fin ; le champ `delta` contient le texte ajouté. **Lorsque la sortie audio est activée, prenez le texte de la réponse dans cet événement**, par exemple pour afficher des sous-titres pendant la lecture.
</ParamField>

<ParamField body="response.output_text.delta / response.output_text.done" type="event">
  Un fragment **incrémental** d'une réponse en texte seul et son marqueur de fin, émis uniquement lorsque la modalité de sortie est le texte seul (`output_modalities: ["text"]`).
</ParamField>

<ParamField body="response.done" type="event">
  Une réponse est terminée. Cet événement porte l'usage de tokens du tour (`usage`), sur lequel repose la facturation.
</ParamField>

<ParamField body="conversation.item.truncated" type="event">
  Confirmation que la demande de troncature a pris effet ; voir [Interruption et troncature](#interruption).
</ParamField>

<ParamField body="error" type="event">
  Événement d'erreur, avec un code et une description. Un problème lié à la requête elle-même (par exemple une valeur `output_modalities` invalide) renvoie un seul événement `error` et la session reste utilisable ; les questions de politique (changement de modèle, solde épuisé) ferment la session.
</ParamField>

<Note>
  **Choisissez le bon événement pour le texte de la réponse.** Par défaut (sortie incluant l'audio), le modèle ne pousse que `response.output_audio_transcript.delta` et **ne pousse pas** `response.output_text.delta` ; définissez la modalité de sortie sur texte seul et le texte passe à `response.output_text.delta`. Écoutez les deux dans chaque mode pour ne jamais perdre de texte (voir la [sortie mesurée](#run-output) ci-dessous).
</Note>

<h2 id="interruption">
  Interruption et troncature
</h2>

Lorsque l'utilisateur se met à parler pendant que le modèle parle, le contenu déjà généré mais pas encore lu entre en conflit avec la phrase suivante de l'utilisateur. Sur une connexion WebSocket, la lecture est gérée par le client : c'est donc au client de terminer le nettoyage après une interruption.

Lorsque la VAD est activée, le serveur envoie `input_audio_buffer.speech_started` dès qu'il détecte que l'utilisateur a commencé à parler. À réception de cet événement, le client doit :

1. **Arrêter immédiatement la lecture locale** et noter jusqu'où la réponse avait été lue (en millisecondes).
2. Envoyer `conversation.item.truncate` pour retirer de la conversation l'audio non lu, afin que le modèle ne le considère pas comme prononcé au tour suivant.

```json theme={null}
{
  "type": "conversation.item.truncate",
  "item_id": "item_ABC123",
  "content_index": 0,
  "audio_end_ms": 1500
}
```

* `item_id` : l'ID d'item de conversation de cette réponse, repris de `item.id` dans l'événement `response.output_item.added`.
* `content_index` : l'index de la partie de contenu audio, toujours `0`.
* `audio_end_ms` : la longueur d'audio à conserver, en millisecondes, selon la position réellement lue par le client.

Le serveur répond `conversation.item.truncated` une fois la demande traitée. La troncature n'affecte que l'audio de cette réponse et sa transcription ; la session n'est pas modifiée et vous pouvez enchaîner le tour suivant. Avec le SDK OpenAI, appelez `conn.conversation.item.truncate(item_id=..., content_index=0, audio_end_ms=...)`.

Lorsque la VAD est désactivée (par exemple en push-to-talk), l'appui sur le bouton constitue l'interruption : envoyez `response.cancel` pour annuler la réponse en cours, puis tronquez comme décrit ci-dessus ; au relâchement, envoyez `input_audio_buffer.append`, `input_audio_buffer.commit`, puis `response.create` dans cet ordre.

## Exemples complets

Trois approches sont présentées ci-dessous ; choisissez-en une :

* **SDK OpenAI officiel (recommandé)** : pas besoin d'écrire les trames WebSocket à la main ; pointez `websocket_base_url` (le paramètre d'URL de base WebSocket du SDK) vers la passerelle et réutilisez la bibliothèque officielle.
* **SDK OpenAI Agents** : la forme vocale temps réel du framework d'agents officiel ; remplacez l'`url` de `model_config` par l'adresse de la passerelle.
* **websockets brut** : sans SDK, échangez les trames directement selon le protocole. Le moins de dépendances et le plus simple à déboguer.

<Note>
  **Pourquoi transmettre le nom du modèle à la connexion ?** La passerelle AiHubMix a besoin du `model` au moment de la poignée de main WebSocket pour sélectionner le fournisseur de modèles, authentifier et réserver le quota, alors que `session.update` n'arrive qu'après la fin de la poignée de main. Avec un SDK, vous devez donc passer `model` explicitement à `connect()` (le SDK l'insère dans la requête URL) ; sans lui, la passerelle **refuse pendant la poignée de main** et aucune connexion ne s'établit. Contrairement à la transcription, le point de terminaison de conversation n'a **pas** besoin de `intent=transcription`.
</Note>

<CodeGroup>
  ```python Python (SDK OpenAI) theme={null}
  # Dépendance : pip install "openai[realtime]"
  import asyncio
  import base64
  from openai import AsyncOpenAI

  # Réutiliser le SDK officiel ne demande que deux adaptations :
  #   1) pointer websocket_base_url vers la passerelle AiHubMix (et non l'adresse OpenAI par défaut)
  #   2) passer model explicitement à connect(), requis pour la poignée de main (aucun intent pour la conversation)
  client = AsyncOpenAI(
      api_key="sk-***",  # Remplacez par votre clé d'API AiHubMix
      websocket_base_url="wss://aihubmix.com/v1",
  )

  async def main():
      async with client.realtime.connect(model="gpt-realtime-2.1") as conn:
          # 1) Configurer la session : instructions système + voix + VAD (détection automatique des tours)
          await conn.session.update(session={
              "type": "realtime",
              "instructions": "You are a helpful voice assistant. Keep answers concise.",
              "audio": {
                  "input": {
                      "format": {"type": "audio/pcm", "rate": 24000},
                      "turn_detection": {"type": "server_vad"},
                  },
                  "output": {
                      "format": {"type": "audio/pcm", "rate": 24000},
                      "voice": "alloy",
                  },
              },
          })

          # 2) Envoyer un message texte et demander une réponse (pour l'audio, voir append/commit dans l'exemple brut)
          await conn.conversation.item.create(item={
              "type": "message",
              "role": "user",
              "content": [{"type": "input_text", "text": "What is the capital of France?"}],
          })
          await conn.response.create()

          # 3) Recevoir la réponse de ce tour : le texte arrive en deltas de transcription, la voix en deltas audio
          async for event in conn:
              if event.type == "response.output_audio_transcript.delta":
                  print(event.delta, end="", flush=True)  # texte de la réponse (sortie incluant l'audio)
              elif event.type == "response.output_audio_transcript.done":
                  print()  # transcription de ce tour terminée
              elif event.type == "response.output_text.delta":
                  print(event.delta, end="", flush=True)  # sortie texte seul
              elif event.type == "response.output_text.done":
                  print()
              elif event.type == "response.output_audio.delta":
                  pass  # fragment audio PCM16 encodé en base64, à décoder pour lecture
              elif event.type == "input_audio_buffer.speech_started":
                  pass  # interruption utilisateur : arrêter la lecture puis appeler conn.conversation.item.truncate(...)
              elif event.type == "response.done":
                  print("\n[tour terminé]", event.response.usage)
                  break
              elif event.type == "error":
                  print("\n[erreur]", event.to_dict())
                  break

  asyncio.run(main())
  ```

  ```python Python (websockets) theme={null}
  import asyncio
  import base64
  import json
  import websockets

  API_KEY = "sk-***"  # Remplacez par votre clé d'API AiHubMix
  URL = "wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1"

  async def main():
      # websockets >= 13 utilise additional_headers ; les versions plus anciennes utilisent extra_headers
      async with websockets.connect(
          URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}
      ) as ws:
          # 1) Configurer la session (VAD désactivé, contrôle manuel du moment de la réponse)
          await ws.send(json.dumps({
              "type": "session.update",
              "session": {
                  "type": "realtime",
                  "instructions": "You are a helpful voice assistant.",
                  "audio": {
                      "input": {
                          "format": {"type": "audio/pcm", "rate": 24000},
                          "turn_detection": None,
                      },
                      "output": {
                          "format": {"type": "audio/pcm", "rate": 24000},
                          "voice": "alloy",
                      },
                  },
              },
          }))

          # 2) Lire un fichier PCM16 / 24 kHz / mono brut local, l'envoyer par fragments, puis commit + demande de réponse
          async def send_audio():
              with open("audio_pcm16_24k.raw", "rb") as f:
                  pcm = f.read()
              chunk = 24000 * 2 // 10  # 100 ms = fréquence d'échantillonnage x 2 octets / 10
              for i in range(0, len(pcm), chunk):
                  await ws.send(json.dumps({
                      "type": "input_audio_buffer.append",
                      "audio": base64.b64encode(pcm[i:i + chunk]).decode(),
                  }))
                  await asyncio.sleep(0.1)  # simuler le rythme temps réel
              # VAD désactivé : valider et demander une réponse manuellement
              await ws.send(json.dumps({"type": "input_audio_buffer.commit"}))
              await ws.send(json.dumps({"type": "response.create"}))

          asyncio.create_task(send_audio())

          # 3) Recevoir la réponse
          async for msg in ws:
              evt = json.loads(msg)
              etype = evt.get("type", "")
              if etype == "response.output_audio_transcript.delta":
                  print(evt.get("delta", ""), end="", flush=True)  # texte de la réponse (sortie incluant l'audio)
              elif etype == "response.output_audio_transcript.done":
                  print()  # transcription de ce tour terminée
              elif etype == "response.output_text.delta":
                  print(evt.get("delta", ""), end="", flush=True)  # sortie texte seul
              elif etype == "input_audio_buffer.speech_started":
                  pass  # interruption utilisateur : arrêter la lecture puis envoyer conversation.item.truncate
              elif etype == "response.output_audio.delta":
                  pass  # fragment audio PCM16 encodé en base64, à décoder pour lecture
              elif etype == "response.done":
                  print("\n[tour terminé]", evt.get("response", {}).get("usage"))
                  break
              elif etype == "error":
                  print("\n[erreur]", evt.get("error"))
                  break

  asyncio.run(main())
  ```

  ```python Python (SDK Agents) theme={null}
  # Dépendance : pip install openai-agents
  import asyncio
  from agents.realtime import (
      OpenAIRealtimeWebSocketModel,
      RealtimeAgent,
      RealtimeSession,
  )

  agent = RealtimeAgent(
      name="Assistant",
      instructions="You are a helpful voice assistant. Keep answers concise.",
  )

  async def main():
      session = RealtimeSession(
          OpenAIRealtimeWebSocketModel(),
          agent,
          None,
          model_config={
              "api_key": "sk-***",  # Remplacez par votre clé d'API AiHubMix
              "url": "wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1",
          },
          run_config={
              "model_settings": {
                  "modalities": ["audio"],
                  "output_audio_format": "pcm16",
                  # À désactiver impérativement : la transcription audio en entrée activée par défaut
                  # n'est pas prise en charge par cette API, sinon la session est fermée avec 1008
                  "input_audio_transcription": None,
              },
          },
      )
      async with session:
          await session.send_message("What is the capital of France?")
          async for event in session:
              if getattr(event, "type", "") == "raw_model_event":
                  data = getattr(event, "data", None)
                  if getattr(data, "type", "") == "transcript_delta":
                      print(getattr(data, "delta", ""), end="", flush=True)
                  elif getattr(data, "type", "") == "turn_ended":
                      print("\n[tour terminé]")
                      return

  asyncio.run(main())
  ```

  ```bash Test de connexion (wscat) theme={null}
  # Vérifiez rapidement la connectivité et l'authentification avec wscat (nécessite npm i -g wscat)
  wscat -c "wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1" \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY"

  # Une fois connecté, collez une trame session.update, puis envoyez un message texte + response.create pour démarrer
  ```
</CodeGroup>

<Tip>
  Pour convertir n'importe quel audio au format PCM brut exigé par cette API, utilisez ffmpeg :

  ```bash theme={null}
  ffmpeg -i input.mp3 -f s16le -acodec pcm_s16le -ac 1 -ar 24000 audio_pcm16_24k.raw
  ```
</Tip>

### Réutiliser les exemples officiels

La plupart des exemples de conversation en temps réel publiés par OpenAI ne dépendent que du paramètre d'URL de base du SDK : il suffit de pointer l'adresse vers le point de terminaison AiHubMix pour les réutiliser :

| Exemple officiel                                                                    | Réutilisable en changeant seulement l'adresse | Ce qu'il faut modifier                                                                                                                                                                                                                                          |
| ----------------------------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Exemple de conversation en temps réel du SDK Python officiel                        | Oui                                           | Définir `websocket_base_url` sur `wss://aihubmix.com/v1` et passer `model` à `connect()`                                                                                                                                                                        |
| Exemple de conversation en temps réel du SDK Node / JS officiel                     | Oui                                           | Définir `baseURL` sur `https://aihubmix.com/v1` (le SDK le convertit en `wss` et construit `/realtime?model=...`)                                                                                                                                               |
| Exemple vocal temps réel du SDK Agents officiel                                     | Oui                                           | Définir `model_config.url` sur `wss://aihubmix.com/v1/realtime?model=gpt-realtime-2.1` et désactiver la transcription audio en entrée activée par défaut                                                                                                        |
| Exemples officiels pour navigateur (console temps réel, exemple vocal multi-agents) | Non                                           | Ces exemples se connectent directement depuis le navigateur et sont refusés par la vérification `Origin` de la passerelle ; ils dépendent en outre de justificatifs temporaires émis par un serveur, alors que cette API n'expose que le WebSocket côté serveur |

<h2 id="run-output">
  Sortie mesurée (en production)
</h2>

Voici le résultat réel de l'exemple du SDK OpenAI sur l'environnement de production `aihubmix.com` avec le modèle `gpt-realtime-2.1`. La session active `server_vad`, et les prompts comme l'audio sont en anglais.

**Entrée texte**

```text theme={null}
# Envoi du texte "What is the capital of France?"
[tour 1, texte] terminé en 2,33 s | sortie audio de 2,70 s en PCM16
  réponse : The capital of France is Paris.
  usage : input_tokens 29 (text 29) / output_tokens 81 (audio 54 + text 27, dont reasoning 8)
```

**Entrée audio**

```text theme={null}
# Envoi par fragments de 6,3 s de parole anglaise ; le modèle détecte la fin du tour et répond
[tour 2, audio] terminé en 9,59 s | sortie audio de 4,35 s en PCM16
  réponse : Everything sounds good on my side, and I'm ready to keep this conversation going.
  usage : input_tokens 117 (audio 67 + text 50) / output_tokens 129 (audio 87 + text 42, dont reasoning 11)
Événements VAD : input_audio_buffer.speech_started / input_audio_buffer.speech_stopped
```

**Différence mesurée entre deux configurations**

| Configuration                            | Résultat mesuré                                                                                                                                                                                       |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `turn_detection: {"type": "server_vad"}` | Après l'envoi de l'audio, vous recevez `input_audio_buffer.speech_started` et `speech_stopped`, et le modèle détecte la fin du tour et commence à répondre sans `commit` ni `response.create` manuels |
| `output_modalities: ["text"]`            | Le texte de la réponse passe à `response.output_text.delta`, aucun événement de sortie audio n'a lieu pour ce tour et `audio_tokens` vaut 0 dans l'usage                                              |

<Note>
  Confirmation mesurée : par défaut (sortie incluant l'audio), le texte de la réponse n'arrive caractère par caractère que via `response.output_audio_transcript.delta`, et `response.output_text.delta` n'apparaît jamais ; `response.done` porte l'`usage` du tour ; la poignée de main prend environ 2 à 4 secondes, soit le coût de la réservation de quota à l'établissement de la session.
</Note>

## Facturation

* **Facturation au token** : une session de conversation renvoie l'usage de tokens de chaque tour (`usage`) avec l'événement `response.done`, et la facturation s'appuie dessus. L'usage est mesuré séparément par composant, notamment **entrée audio, sortie audio, entrée texte et sortie texte**. Le prix unitaire de chaque composant suit le prix affiché en direct sur la [page de détail du modèle](https://aihubmix.com/model/gpt-realtime-2.1).
* **Règlement à l'usage** : il s'agit d'une connexion durable, et les frais sont déduits en temps réel à chaque tour pendant la session, sans règlement unique à la fin. L'établissement d'une session effectue d'abord une **réservation de quota** d'environ une minute d'usage (un contrôle d'admission uniquement, pas une facturation réelle), et la réservation restante est libérée à la fin de la session. **Le solde disponible de votre compte doit donc couvrir au moins environ une minute d'usage pour qu'une session puisse s'établir.**
* Chaque enregistrement de facturation de conversation en temps réel est consultable ligne par ligne dans [Usage et facturation](https://aihubmix.com).

## Limites et contraintes

1. **Durée d'une session** : une connexion WebSocket dure au maximum **62 minutes**, après quoi le serveur la ferme (code de fermeture `1000`, raison `session_duration_limit`) ; découpez en segments si vous avez besoin de plus longtemps.
2. **Déconnexion pour inactivité** : lorsque ni le client ni le modèle n'a d'activité pendant **5 minutes**, le serveur ferme la session (code de fermeture `1008`, raison `idle_timeout`). L'activité d'un côté ou de l'autre réinitialise le minuteur : une longue réponse diffusée en continu n'est donc pas interrompue.
3. **Solde insuffisant** : **à l'établissement de la session**, si le solde disponible ne couvre pas la réservation d'environ une minute, la poignée de main est refusée d'emblée (HTTP 403) et aucune session ne s'établit ; **pendant la session**, si le solde s'épuise, la connexion établie est fermée immédiatement.
4. **Côté serveur uniquement** : les connexions directes depuis un navigateur ne sont pas prises en charge (l'en-tête `Origin` est vérifié) ; intégrez depuis votre serveur.
5. **Modèle verrouillé** : le modèle est fixé dans l'URL de connexion, et le changer avec `session.update` pendant la session est refusé et ferme la session.
6. **Format verrouillé** : l'audio en entrée et en sortie ne prend en charge que `audio/pcm@24000` mono ; les autres formats sont refusés.
7. **Voix verrouillée** : `voice` ne peut pas être modifié après le début de la première réponse ; définissez-le avant de demander la première réponse.
8. **Pas encore pris en charge** : la transcription intégrée dans une session de conversation, l'injection d'audio ou d'images via les items de conversation, et tout type d'item de contenu autre que le texte.
9. **Une réponse à la fois** : une session n'autorise qu'une seule réponse en cours ; envoyer un autre `response.create` avant la fin du tour en cours est refusé et ferme la session (code de fermeture `1008`, raison `response_already_active`).

## Erreurs courantes

| Scénario                                                          | Code de fermeture / statut                 | Description                                                                                              |
| ----------------------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| Service non activé                                                | HTTP 403 `realtime_disabled`               | La conversation en temps réel n'est pas ouverte pour cet environnement                                   |
| En-tête `Origin` présent / connexion directe depuis un navigateur | Poignée de main refusée                    | Utilisez une connexion côté serveur                                                                      |
| Solde insuffisant à la connexion                                  | HTTP 403 `insufficient_user_quota`         | Le solde ne couvre pas la réservation d'environ une minute ; rechargez puis réessayez                    |
| Changement de modèle en session                                   | `1008` `model_override_forbidden`          | Le modèle ne peut être indiqué que dans l'URL de connexion                                               |
| Format audio non PCM                                              | `1008` `audio_format_unsupported`          | Convertissez en `audio/pcm@24000`                                                                        |
| Changement de voix après la première réponse                      | Champ `voice` ignoré                       | Définissez `voice` avant de demander la première réponse                                                 |
| Activation de la transcription intégrée                           | `1008` `input_transcription_not_supported` | Non pris en charge dans les sessions de conversation pour cette version                                  |
| Injection d'audio via les éléments de conversation                | `1008` `item_audio_not_supported`          | Envoyez l'audio via `input_audio_buffer.append`                                                          |
| Injection d'images via les éléments de conversation               | `1008` `image_input_not_supported`         | Non pris en charge dans cette version                                                                    |
| Item de conversation avec un type de contenu autre que le texte   | `1008` `unsupported_content_part`          | Le contenu des items de conversation est limité au texte                                                 |
| Valeur `output_modalities` invalide                               | `error` événement `invalid_value`          | Seules les valeurs `["audio"]` et `["text"]` sont prises en charge ; la session reste ouverte            |
| Durée maximale de session atteinte                                | `1000` `session_duration_limit`            | La limite de 62 minutes est atteinte ; reconnectez-vous pour continuer                                   |
| Les deux côtés inactifs pendant 5 minutes                         | `1008` `idle_timeout`                      | L'activité d'un côté ou de l'autre réinitialise le minuteur                                              |
| Nouvelle requête pendant une réponse en cours                     | `1008` `response_already_active`           | Attendez `response.done` avant d'envoyer `response.create`                                               |
| `event_id` en double                                              | `1008` `duplicate_event_id`                | L'`event_id` de chaque événement client doit être unique dans la session                                 |
| `response.conversation` défini sur une valeur non par défaut      | `1008` `conversation_mode_not_supported`   | Seul le mode de conversation par défaut est pris en charge                                               |
| Événement réservé au serveur envoyé par le client                 | `1008` `client_forged_lifecycle_event`     | Dans l'espace de noms `response.*`, le client ne peut envoyer que `response.create` et `response.cancel` |
| Solde épuisé en session                                           | Connexion fermée                           | Rechargez puis reconnectez-vous                                                                          |

***

Dernière mise à jour : 2026-09-21
