For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Hauptnavigation

Echtzeitkonversationen

Erfahre, wie du Speech-to-Speech-Konversationen in Echtzeit verwaltest.

Sobald du über WebRTC oder WebSocket eine Verbindung zur Realtime API hergestellt hast, kannst du ein Realtime-Modell (wie gpt-realtime-2.1) aufrufen, um Speech-to-Speech-Konversationen zu führen. Dazu musst du Client-Ereignisse senden , um Aktionen auszulösen, und auf Server-Ereignisse lauschen , um auf Aktionen der Realtime API zu reagieren.

Diese Anleitung erläutert die Ereignisabläufe, die du für Modellfunktionen wie Audio- und Textgenerierung, Bildeingaben und Funktionsaufrufe benötigst. Außerdem erfährst du, wie der Zustand einer Echtzeitsitzung aufgebaut ist.

Wenn du keine Konversation mit dem Modell führen möchtest, also keine Antwort erwartest, kannst du die Realtime API im Modus zur Transkription verwenden.

Echtzeitsitzungen mit Speech-to-Speech

Eine Echtzeitsitzung ist eine zustandsbehaftete Interaktion zwischen dem Modell und einem verbundenen Client. Die wichtigsten Bestandteile einer Sitzung sind:

  • Das Session -Objekt steuert die Parameter der Interaktion, etwa das verwendete Modell, die Stimme für die Audioausgabe und weitere Einstellungen.
  • Eine Konversation bildet die Elemente mit Nutzereingaben und Modellausgaben ab, die während der aktuellen Sitzung entstehen.
  • Antworten sind vom Modell generierte Audio- oder Textelemente, die der Konversation hinzugefügt werden.

Audioeingabepuffer und WebSockets

Wenn du WebRTC verwendest, unterstützen dich die WebRTC-APIs bei einem Großteil der Medienverarbeitung, die zum Senden von Audiodaten an das Modell und zum Empfangen seiner Audioausgaben erforderlich ist.


Wenn du WebSockets für Audio verwendest, musst du den Audioeingabepuffer selbst ansteuern. Dazu sendest du Audiodaten in Form von JSON-Ereignissen mit Base64-kodierten Audiodaten an den Server.

Zusammen bilden diese Komponenten eine Echtzeitsitzung. Mit Client-Ereignissen aktualisierst du den Zustand der Sitzung. Indem du auf Server-Ereignisse lauschst, kannst du auf Zustandsänderungen innerhalb der Sitzung reagieren.

Diagramm zum Zustand einer Echtzeitsitzung

Ereignisse im Lebenszyklus einer Sitzung

Nachdem du eine Sitzung über WebRTC oder WebSockets gestartet hast, sendet der Server ein session.created-Ereignis, das die Bereitschaft der Sitzung signalisiert. Auf dem Client kannst du die aktuelle Sitzungskonfiguration mit dem session.update-Ereignis aktualisieren. Die meisten Sitzungseigenschaften lassen sich jederzeit ändern. Eine Ausnahme ist die Einstellung voice, die die Stimme für die Audioausgabe des Modells festlegt: Sie lässt sich nicht mehr ändern, sobald das Modell in der Sitzung einmal mit Audio geantwortet hat. Eine Echtzeitsitzung dauert maximal 60 Minuten.

Das folgende Beispiel zeigt, wie du die Sitzung mit einem session.update-Client-Ereignis aktualisierst. Weitere Informationen zum Senden von Client-Ereignissen über diese Kanäle findest du in der Anleitung zu WebRTC oder WebSocket.

Systemanweisungen aktualisieren, die das Modell in dieser Sitzung verwendet
const event = {
  type: "session.update",
  session: {
    type: "realtime",
    model: "gpt-realtime-2.1",
    // Lock the output to audio (set to ["text"] if you want text without audio)
    output_modalities: ["audio"],
    audio: {
      input: {
        format: {
          type: "audio/pcm",
          rate: 24000,
        },
        turn_detection: {
          type: "semantic_vad",
        },
      },
      output: {
        format: {
          type: "audio/pcm",
        },
        voice: "marin",
      },
    },
    // Use a server-stored prompt by ID. Optionally pin a version and pass variables.
    prompt: {
      id: "pmpt_123", // your stored prompt ID
      version: "89", // optional: pin a specific version
      variables: {
        city: "Paris", // example variable used by your prompt
      },
    },
    // You can still set direct session fields; these override prompt fields if they overlap:
    instructions:
      "Speak clearly and briefly. Confirm understanding before taking actions.",
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

Sobald die Sitzung aktualisiert wurde, sendet der Server ein session.updated-Ereignis mit dem neuen Zustand der Sitzung.

Zugehörige Client-Ereignisse Zugehörige Server-Ereignisse

session.update

session.created

session.updated

Texteingaben und -ausgaben

Um mit einem Realtime-Modell Text zu generieren, kannst du der aktuellen Konversation Texteingaben hinzufügen, das Modell zum Generieren einer Antwort auffordern und auf Server-Ereignisse lauschen, die den Fortschritt der Antwort anzeigen. Für die Textgenerierung muss die Sitzung konfiguriert sein, sodass die Modalität text aktiviert ist. Das ist standardmäßig der Fall.

Erstelle mit dem Client-Ereignis conversation.item.create ein neues Textelement in der Konversation. Das ähnelt dem Senden einer Nutzernachricht (eines Prompts) in Chat Completions über die REST-API.

Konversationselement mit einer Nutzereingabe erstellen
const event = {
  type: "conversation.item.create",
  item: {
    type: "message",
    role: "user",
    content: [
      {
        type: "input_text",
        text: "What Prince album sold the most copies?",
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

Nachdem du die Nutzernachricht zur Konversation hinzugefügt hast, sende das response.create-Ereignis, um eine Antwort des Modells auszulösen. Wenn für die aktuelle Sitzung sowohl Audio als auch Text aktiviert sind, antwortet das Modell mit Audio- und Textinhalten. Wenn du nur Text generieren möchtest, kannst du das beim Senden des response.create-Client-Ereignisses angeben, wie unten gezeigt.

Eine reine Textantwort generieren
const event = {
  type: "response.create",
  response: {
    output_modalities: ["text"],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

Sobald die Antwort vollständig abgeschlossen ist, sendet der Server das response.done-Ereignis. Dieses Ereignis enthält den gesamten vom Modell generierten Text, wie unten gezeigt.

Auf response.done lauschen, um die endgültigen Ergebnisse zu sehen
function handleEvent(message) {
  const data = "data" in message ? message.data : message.toString();
  const serverEvent = JSON.parse(data);
  if (serverEvent.type === "response.done") {
    console.log(serverEvent.response.output[0]);
  }
}

// Listen for server messages (WebRTC)
dataChannel.addEventListener("message", handleEvent);

// Listen for server messages (WebSocket)
// ws.on("message", handleEvent);

Während das Modell die Antwort generiert, sendet der Server verschiedene Ereignisse zum Lebenszyklus. Du kannst auf diese Ereignisse, etwa response.output_text.delta, lauschen, um Nutzenden während der Generierung Rückmeldungen in Echtzeit zu geben. Eine vollständige Liste der vom Server gesendeten Ereignisse findest du unten unter Zugehörige Server-Ereignisse. Sie sind ungefähr in der Reihenfolge aufgeführt, in der sie gesendet werden, zusammen mit den relevanten Client-Ereignissen für die Textgenerierung.

Zugehörige Client-Ereignisse Zugehörige Server-Ereignisse

conversation.item.create

response.create

conversation.item.added

conversation.item.done

response.created

response.output_item.added

response.content_part.added

response.output_text.delta

response.output_text.done

response.content_part.done

response.output_item.done

response.done

rate_limits.updated

Audioeingaben und -ausgaben

Eine der leistungsfähigsten Funktionen der Realtime API ist die direkte Sprachinteraktion mit dem Modell, ohne zwischengeschaltete Text-zu-Sprache- oder Sprache-zu-Text-Umwandlung. Das ermöglicht eine geringere Latenz für Sprachoberflächen und liefert dem Modell mehr Informationen über den Tonfall und die Betonung der Spracheingabe.

Verfügbare Stimmen

Echtzeitsitzungen lassen sich so konfigurieren, dass sie eine von mehreren integrierten Stimmen für die Audioausgabe verwenden. Du kannst voice beim Erstellen der Sitzung (oder in einem response.create-Ereignis) festlegen, um zu steuern, wie das Modell klingt. Derzeit stehen die Stimmen alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin und cedar zur Verfügung. Sobald das Modell in einer Sitzung Audio ausgegeben hat, lässt sich voice für diese Sitzung nicht mehr ändern. Für die beste Qualität empfehlen wir marin oder cedar.

Audio mit WebRTC verarbeiten

Wenn du dich über WebRTC mit der Realtime API verbindest, stellt die Realtime API eine Peer-Verbindung zu deinem Client her. Die Audioausgabe des Modells wird deinem Client als entfernter Medienstream übermittelt. Die Audioeingabe für das Modell wird über Audiogeräte erfasst (getUserMedia), und die Medienstreams werden der Peer-Verbindung als Tracks hinzugefügt.

Der Beispielcode aus der Anleitung für WebRTC-Verbindungen zeigt eine einfache Konfiguration für lokales und entferntes Audio mit Browser-APIs:

// Create a peer connection
const pc = new RTCPeerConnection();

// Set up to play remote audio from the model
const audioEl = document.createElement("audio");
audioEl.autoplay = true;
pc.ontrack = (e) => (audioEl.srcObject = e.streams[0]);

// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
  audio: true,
});
pc.addTrack(ms.getTracks()[0]);

Der obige Codeausschnitt ermöglicht die Interaktion mit der Realtime API. Darüber hinaus gibt es viele weitere Möglichkeiten. Weitere Beispiele für verschiedene Arten von Benutzeroberflächen findest du im Repository WebRTC samples. Live-Demos dieser Beispiele findest du auch hier.

Mit Medienerfassung und -streams im Browser kannst du unter anderem Mikrofone stummschalten und die Stummschaltung aufheben oder das Gerät auswählen, von dem Eingaben erfasst werden sollen.

Client- und Server-Ereignisse für Audio in WebRTC

Standardmäßig müssen WebRTC-Clients keine Client-Ereignisse an die Realtime API senden, bevor sie Audioeingaben übertragen. Sobald der Peer-Verbindung ein lokaler Audiotrack hinzugefügt wurde, können deine Nutzenden einfach lossprechen!

WebRTC-Clients empfangen dennoch verschiedene vom Server gesendete Ereignisse zum Lebenszyklus, während Audiodaten über die Peer-Verbindung zwischen Client und Server ausgetauscht werden. Dazu gehören beispielsweise:

Die WebRTC-APIs für Medienstreams bieten dir möglicherweise bereits alle Steuerungsmöglichkeiten, die du benötigst. Gelegentlich kann es jedoch notwendig sein, für Audioeingaben und -ausgaben auf Schnittstellen einer niedrigeren Ebene zurückzugreifen. Im folgenden Abschnitt zu WebSockets findest du weitere Informationen und eine Liste der Ereignisse, die du für eine detaillierte Steuerung der Audioeingabe benötigst.

Audio mit WebSockets verarbeiten

Wenn du Audio über einen WebSocket sendest und empfängst, musst du etwas mehr selbst übernehmen, um Medien vom Client zu senden und vom Server zu empfangen. Die folgende Tabelle beschreibt den Ablauf der Ereignisse während einer WebSocket-Sitzung, die zum Senden und Empfangen von Audio über den WebSocket erforderlich sind.

Die folgenden Ereignisse sind in der Reihenfolge ihres Auftretens im Lebenszyklus aufgeführt. Einige Ereignisse (etwa die delta-Ereignisse) können jedoch gleichzeitig auftreten.

Phase im Lebenszyklus Client-Ereignisse Server-Ereignisse
Initialisierung der Sitzung

session.update

session.created

session.updated

Audioeingabe der nutzenden Person

conversation.item.create


  (vollständige Audionachricht senden)

input_audio_buffer.append


  (Audio in Blöcken streamen)

input_audio_buffer.commit


  (wird bei deaktivierter VAD verwendet)

response.create


  (wird bei deaktivierter VAD verwendet)

input_audio_buffer.speech_started

input_audio_buffer.speech_stopped

input_audio_buffer.committed

Audioausgabe des Servers

input_audio_buffer.clear


  (wird bei deaktivierter VAD verwendet)

conversation.item.added

conversation.item.done

response.created

response.output_item.added

response.content_part.added

response.output_audio.delta

response.output_audio.done

response.output_audio_transcript.delta

response.output_audio_transcript.done

response.output_text.delta

response.output_text.done

response.content_part.done

response.output_item.done

response.done

rate_limits.updated

Audioeingaben an den Server streamen

Um Audioeingaben an den Server zu streamen, kannst du das Client-Ereignis input_audio_buffer.append verwenden. Dafür sendest du Base64-kodierte Audiobytes in Blöcken über den Socket an die Realtime API. Jeder Block darf höchstens 15 MB groß sein.

Das Format der Eingabeblöcke lässt sich für die gesamte Sitzung oder für jede Antwort einzeln konfigurieren.

Bytes der Audioeingabe an das Gespräch anhängen
import fs from "fs";
import decodeAudio from "audio-decode";

// Converts Float32Array of audio data to PCM16 ArrayBuffer
function floatTo16BitPCM(float32Array) {
  const buffer = new ArrayBuffer(float32Array.length * 2);
  const view = new DataView(buffer);
  let offset = 0;
  for (let i = 0; i < float32Array.length; i++, offset += 2) {
    let s = Math.max(-1, Math.min(1, float32Array[i]));
    view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
  }
  return buffer;
}

// Converts a Float32Array to base64-encoded PCM16 data
function base64EncodeAudio(float32Array) {
  const arrayBuffer = floatTo16BitPCM(float32Array);
  let binary = "";
  let bytes = new Uint8Array(arrayBuffer);
  const chunkSize = 0x8000; // 32KB chunk size
  for (let i = 0; i < bytes.length; i += chunkSize) {
    let chunk = bytes.subarray(i, i + chunkSize);
    binary += String.fromCharCode(...chunk);
  }
  return btoa(binary);
}

// Fills the audio buffer with the contents of three files,
// then asks the model to generate a response.
const files = [
  "fixtures/sample1.wav",
  "fixtures/sample2.wav",
  "fixtures/sample3.wav",
];

for (const filename of files) {
  const audioFile = fs.readFileSync(filename);
  const audioBuffer = await decodeAudio(audioFile);
  const channelData = audioBuffer.channelData[0];
  const base64Chunk = base64EncodeAudio(channelData);
  ws.send(
    JSON.stringify({
      type: "input_audio_buffer.append",
      audio: base64Chunk,
    })
  );
}

ws.send(JSON.stringify({ type: "input_audio_buffer.commit" }));
ws.send(JSON.stringify({ type: "response.create" }));

Vollständige Audionachrichten senden

Du kannst auch Nachrichten aus vollständigen Audioaufnahmen im Gespräch erstellen. Verwende das Client-Ereignis conversation.item.create, um Nachrichten mit Inhalten vom Typ input_audio zu erstellen.

Gesprächselemente mit vollständigen Audioeingaben erstellen
const fullAudio = "<a base64-encoded string of audio bytes>";

const event = {
  type: "conversation.item.create",
  item: {
    type: "message",
    role: "user",
    content: [
      {
        type: "input_audio",
        audio: fullAudio,
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

Audioausgaben über einen WebSocket verarbeiten

Für die Wiedergabe von Audioausgaben auf einem Client, etwa in einem Webbrowser, empfehlen wir WebRTC statt WebSockets. WebRTC überträgt Medien bei instabilen Netzwerkbedingungen zuverlässiger an Client-Geräte.

Wenn du Audioausgaben in Server-zu-Server-Anwendungen über einen WebSocket verarbeiten möchtest, musst du auf response.output_audio.delta-Ereignisse lauschen. Diese enthalten die Base64-kodierten Audiodatenblöcke des Modells. Du musst diese Blöcke entweder zwischenspeichern und in eine Datei schreiben oder direkt weiterstreamen, etwa in ein Telefonat mit Twilio.

Beachte, dass die Ereignisse response.output_audio.done und response.done keine Audiodaten enthalten, sondern nur Transkriptionen der Audioinhalte. Um die eigentlichen Bytes zu erhalten, musst du auf die response.output_audio.delta-Ereignisse lauschen.

Das Format der Ausgabeblöcke lässt sich für die gesamte Sitzung oder für jede Antwort einzeln konfigurieren.

Auf response.output_audio.delta-Ereignisse lauschen
function handleEvent(message) {
  const serverEvent = JSON.parse(message.toString());
  if (serverEvent.type === "response.output_audio.delta") {
    // Access Base64-encoded audio chunks
    // console.log(serverEvent.delta);
  }
}

// Listen for server messages (WebSocket)
ws.on("message", handleEvent);

Bildeingaben

gpt-realtime-2 und gpt-realtime unterstützen auch Bildeingaben. Du kannst einer Nutzernachricht ein Bild als Inhaltsbestandteil hinzufügen. Das Modell kann dann den Bildinhalt in seiner Antwort berücksichtigen.

Dem Gespräch ein Bild hinzufügen
const base64Image = "<a base64-encoded string of image bytes>";

const event = {
  type: "conversation.item.create",
  item: {
    type: "message",
    role: "user",
    content: [
      {
        type: "input_image",
        image_url: `data:image/{format};base64,${base64Image}`,
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

Sprachaktivitätserkennung

In Echtzeitsitzungen ist die Sprachaktivitätserkennung (VAD) standardmäßig aktiviert. Die API erkennt damit, wann die nutzende Person zu sprechen beginnt oder aufhört, und antwortet automatisch.

Mehr zur Konfiguration von VAD erfährst du in unserem Leitfaden zur Sprachaktivitätserkennung.

VAD deaktivieren

Du kannst VAD deaktivieren, indem du turn_detection mit dem Client-Ereignis session.update auf null setzt. Das kann für Benutzeroberflächen sinnvoll sein, bei denen du die Audioeingabe gezielt steuern möchtest, etwa bei Push-to-talk.

Bei deaktivierter VAD muss der Client einige zusätzliche Client-Ereignisse manuell senden, um Audioantworten auszulösen:

VAD beibehalten, aber automatische Antworten deaktivieren

Wenn du VAD aktiviert lassen, aber selbst entscheiden möchtest, wann eine Antwort generiert wird, kannst du turn_detection.interrupt_response und turn_detection.create_response mit dem Client-Ereignis session.update auf false setzen. Dadurch bleibt das gesamte Verhalten von VAD erhalten, es werden jedoch keine neuen Antworten automatisch erstellt. Clients können diese manuell mit einem response.create-Ereignis auslösen.

Das kann für Moderation, Eingabevalidierung oder RAG-Muster sinnvoll sein, wenn du für mehr Kontrolle über die Eingaben eine etwas höhere Latenz bei der Interaktion in Kauf nehmen möchtest.

Antworten außerhalb des Standardgesprächs erstellen

Standardmäßig werden alle während einer Sitzung generierten Antworten dem Gesprächszustand der Sitzung hinzugefügt (dem „Standardgespräch“). Möglicherweise möchtest du jedoch Modellantworten außerhalb des Kontexts dieses Standardgesprächs oder mehrere Antworten gleichzeitig generieren lassen. Vielleicht möchtest du auch genauer steuern, welche Gesprächselemente das Modell beim Generieren einer Antwort berücksichtigt, beispielsweise nur die letzten N Gesprächsbeiträge.

Du kannst Antworten außerhalb des Standardgesprächs generieren, die dessen Gesprächszustand nicht hinzugefügt werden. Setze dazu das Feld response.conversation auf die Zeichenfolge none, wenn du eine Antwort mit dem Client-Ereignis response.create erstellst.

Wenn du eine Antwort außerhalb des Standardgesprächs erstellst, möchtest du wahrscheinlich auch erkennen können, welche vom Server gesendeten Ereignisse zu dieser Antwort gehören. Dazu kannst du metadata für deine Modellantwort angeben. So lässt sich zuordnen, welche Antwort für dieses vom Client gesendete Ereignis generiert wird.

Eine Modellantwort außerhalb des Standardgesprächs erstellen
const prompt = `
Analyze the conversation so far. If it is related to support, output
"support". If it is related to sales, output "sales".
`;

const event = {
  type: "response.create",
  response: {
    // Setting to "none" indicates the response is out of band
    // and will not be added to the default conversation
    conversation: "none",

    // Set metadata to help identify responses sent back from the model
    metadata: { topic: "classification" },

    // Set any other available response fields
    output_modalities: ["text"],
    instructions: prompt,
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

Wenn du nun auf das Server-Ereignis response.done lauschst, kannst du das Ergebnis deiner Antwort außerhalb des Standardgesprächs zuordnen.

Eine Modellantwort außerhalb des Standardgesprächs erstellen
function handleEvent(message) {
  const data = "data" in message ? message.data : message.toString();
  const serverEvent = JSON.parse(data);
  if (
    serverEvent.type === "response.done" &&
    serverEvent.response.metadata?.topic === "classification"
  ) {
    // this server event pertained to our OOB model response
    console.log(serverEvent.response.output[0]);
  }
}

// Listen for server messages (WebRTC)
dataChannel.addEventListener("message", handleEvent);

// Listen for server messages (WebSocket)
// ws.on("message", handleEvent);

Einen eigenen Kontext für Antworten erstellen

Du kannst auch einen eigenen Kontext außerhalb des Standardgesprächs bzw. des aktuellen Gesprächs zusammenstellen, den das Modell zum Generieren einer Antwort verwendet. Nutze dafür das Array input in einem response.create-Client-Ereignis. Du kannst neue Eingaben verwenden oder über deren ID auf vorhandene Eingabeelemente im Gespräch verweisen.

Auf eine Modellantwort mit eigenem Kontext außerhalb des Standardgesprächs lauschen
const event = {
  type: "response.create",
  response: {
    conversation: "none",
    metadata: { topic: "pizza" },
    output_modalities: ["text"],

    // Create a custom input array for this request with whatever context
    // is appropriate
    input: [
      // potentially include existing conversation items:
      {
        type: "item_reference",
        id: "some_conversation_item_id",
      },
      {
        type: "message",
        role: "user",
        content: [
          {
            type: "input_text",
            text: "Is it okay to put pineapple on pizza?",
          },
        ],
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

Antworten ohne Kontext erstellen

Du kannst auch Antworten in die Standardunterhaltung einfügen und dabei alle anderen Anweisungen und den Kontext ignorieren. Setze dazu input auf ein leeres Array.

Modellantworten ohne Kontext in die Standardunterhaltung einfügen
const prompt = `
Say exactly the following:
I'm a little teapot, short and stout!
This is my handle, this is my spout!
`;

const event = {
  type: "response.create",
  response: {
    // An empty input array removes existing context
    input: [],
    instructions: prompt,
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

Funktionsaufruf

Die Realtime-Modelle unterstützen auch Funktionsaufrufe. Damit kannst du eigenen Code ausführen, um die Funktionen des Modells zu erweitern. Der Ablauf im Überblick:

  1. Wenn du die Sitzung aktualisierst oder eine Antwort erstellst, kannst du eine Liste der verfügbaren Funktionen angeben, die das Modell aufrufen kann.
  2. Wenn das Modell bei der Verarbeitung einer Eingabe entscheidet, eine Funktion aufzurufen, fügt es der Unterhaltung Elemente mit den Argumenten für den Funktionsaufruf hinzu.
  3. Wenn der Client Unterhaltungselemente mit Argumenten für einen Funktionsaufruf erkennt, führt er eigenen Code mit diesen Argumenten aus
  4. Sobald der eigene Code ausgeführt wurde, erstellt der Client neue Unterhaltungselemente mit der Ausgabe des Funktionsaufrufs und fordert das Modell auf, zu antworten.

Sehen wir uns das in der Praxis an: Wir fügen eine aufrufbare Funktion hinzu, die den Nutzenden des Modells ihr Tageshoroskop liefert. Wir zeigen, wie die zu sendenden Client-Ereignisobjekte aufgebaut sind und was der Server daraufhin sendet.

Aufrufbare Funktionen konfigurieren

Zunächst müssen wir dem Modell eine Auswahl an Funktionen bereitstellen, die es anhand der Nutzereingabe aufrufen kann. Du kannst die verfügbaren Funktionen für die gesamte Sitzung oder für einzelne Antworten konfigurieren.

Hier ist eine beispielhafte Nutzlast für das Client-Ereignis session.update. Sie konfiguriert eine Funktion zur Erstellung von Horoskopen, die ein einziges Argument entgegennimmt: das Sternzeichen, für das das Horoskop erstellt werden soll.

session.update

{
  "type": "session.update",
  "session": {
    "tools": [
      {
        "type": "function",
        "name": "generate_horoscope",
        "description": "Give today's horoscope for an astrological sign.",
        "parameters": {
          "type": "object",
          "properties": {
            "sign": {
              "type": "string",
              "description": "The sign for the horoscope.",
              "enum": [
                "Aries",
                "Taurus",
                "Gemini",
                "Cancer",
                "Leo",
                "Virgo",
                "Libra",
                "Scorpio",
                "Sagittarius",
                "Capricorn",
                "Aquarius",
                "Pisces"
              ]
            }
          },
          "required": ["sign"]
        }
      }
    ],
    "tool_choice": "auto"
  }
}

Die Felder description für die Funktion und ihre Parameter helfen dem Modell zu entscheiden, ob es die Funktion aufrufen soll und welche Daten es für die einzelnen Parameter verwenden soll. Wenn aus der Eingabe hervorgeht, dass die nutzende Person ihr Horoskop abrufen möchte, ruft das Modell diese Funktion mit einem Parameter sign auf.

Erkennen, wann das Modell eine Funktion aufrufen möchte

Anhand der Eingaben kann das Modell entscheiden, eine Funktion aufzurufen, um die bestmögliche Antwort zu generieren. Angenommen, unsere Anwendung fügt mit einem conversation.item.create-Ereignis das folgende Unterhaltungselement hinzu und erstellt anschließend eine Antwort:

{
  "type": "conversation.item.create",
  "item": {
    "type": "message",
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "What is my horoscope? I am an aquarius."
      }
    ]
  }
}

Anschließend folgt ein response.create-Client-Ereignis, um eine Antwort zu generieren:

{
  "type": "response.create"
}

Anstatt sofort eine Text- oder Audioantwort zurückzugeben, generiert das Modell eine Antwort mit den Argumenten, die an eine Funktion in deiner Anwendung übergeben werden sollen. Über das Server-Ereignis response.function_call_arguments.delta kannst du Aktualisierungen der Funktionsargumente in Echtzeit empfangen. Aber auch response.done enthält alle Daten, die wir zum Aufruf unserer Funktion benötigen.

response.done

{
    "type": "response.done",
    "event_id": "event_AeqLA8iR6FK20L4XZs2P6",
    "response": {
        "object": "realtime.response",
        "id": "resp_AeqL8XwMUOri9OhcQJIu9",
        "status": "completed",
        "status_details": null,
        "output": [
            {
                "object": "realtime.item",
                "id": "item_AeqL8gmRWDn9bIsUM2T35",
                "type": "function_call",
                "status": "completed",
                "name": "generate_horoscope",
                "call_id": "call_sHlR7iaFwQ2YQOqm",
                "arguments": "{\"sign\":\"Aquarius\"}"
            }
        ],
        ...
    }
}

Anhand der vom Server gesendeten JSON-Daten können wir erkennen, dass das Modell eine eigene Funktion aufrufen möchte:

EigenschaftZweck beim Funktionsaufruf
response.output[0].typeDer Wert function_call zeigt an, dass diese Antwort Argumente für den Aufruf einer benannten Funktion enthält.
response.output[0].nameDer Name der konfigurierten Funktion, die aufgerufen werden soll, in diesem Fall generate_horoscope
response.output[0].argumentsEin JSON-String mit den Argumenten für die Funktion. In unserem Fall: "{\"sign\":\"Aquarius\"}".
response.output[0].call_idEine vom System generierte ID für diesen Funktionsaufruf. Du benötigst diese ID, um das Ergebnis eines Funktionsaufrufs an das Modell zurückzugeben.

Mit diesen Informationen können wir in unserer Anwendung Code ausführen, der das Horoskop erstellt. Anschließend geben wir das Ergebnis an das Modell zurück, damit es eine Antwort generieren kann.

Die Ergebnisse eines Funktionsaufrufs an das Modell übergeben

Sobald deine Anwendung eine Modellantwort mit Argumenten für einen Funktionsaufruf erhält, kann sie den dazugehörigen Code ausführen. Was dieser Code tut, bestimmst du selbst. Er kann zum Beispiel externe APIs aufrufen oder auf Datenbanken zugreifen.

Sobald du die Ergebnisse deines eigenen Codes an das Modell übergeben möchtest, kannst du über das Client-Ereignis conversation.item.create ein neues Unterhaltungselement mit dem Ergebnis erstellen.

{
  "type": "conversation.item.create",
  "item": {
    "type": "function_call_output",
    "call_id": "call_sHlR7iaFwQ2YQOqm",
    "output": "{\"horoscope\": \"You will soon meet a new friend.\"}"
  }
}
  • Das Unterhaltungselement hat den Typ function_call_output
  • item.call_id ist dieselbe ID, die wir im obigen response.done-Ereignis erhalten haben
  • item.output ist ein JSON-String mit den Ergebnissen unseres Funktionsaufrufs

Nachdem wir das Unterhaltungselement mit den Ergebnissen unseres Funktionsaufrufs hinzugefügt haben, senden wir vom Client erneut das Ereignis response.create. Dadurch wird eine Modellantwort ausgelöst, die die Daten aus dem Funktionsaufruf verwendet.

{
  "type": "response.create"
}

Fehlerbehandlung

Der Server sendet das Ereignis error, sobald während der Sitzung ein Fehler auf dem Server auftritt. Gelegentlich lassen sich diese Fehler auf ein Client-Ereignis zurückführen, das deine Anwendung gesendet hat.

Bei HTTP-Anfragen und -Antworten ist eine Antwort implizit einer Anfrage des Clients zugeordnet. Hier müssen wir dagegen die Eigenschaft event_id für Client-Ereignisse verwenden, um zu erkennen, welches Ereignis einen Fehler auf dem Server ausgelöst hat. Der folgende Code zeigt dieses Vorgehen anhand eines Clients, der versucht, einen nicht unterstützten Ereignistyp zu senden.

const event = {
  event_id: "my_awesome_event",
  type: "scooby.dooby.doo",
};

dataChannel.send(JSON.stringify(event));

Dieses vom Client gesendete, fehlgeschlagene Ereignis löst ein Fehlerereignis wie das folgende aus:

{
  "type": "invalid_request_error",
  "code": "invalid_value",
  "message": "Invalid value: 'scooby.dooby.doo' ...",
  "param": "type",
  "event_id": "my_awesome_event"
}

Unterbrechung und Kürzung

In vielen Sprachanwendungen können Nutzende das Modell unterbrechen, während es spricht. Wenn VAD aktiviert ist, verarbeitet die Realtime API solche Unterbrechungen: Sie erkennt, wenn die nutzende Person spricht, bricht die laufende Antwort ab und startet eine neue. Dabei sollte das Modell wissen, an welcher Stelle es unterbrochen wurde, damit es das Gespräch natürlich fortsetzen kann, etwa wenn die Person fragt: „Was war das Letzte, was du gesagt hast?“ Dazu wird die letzte Antwort des Modells gekürzt : Der noch nicht abgespielte Teil dieser Antwort wird aus dem Gespräch entfernt.

Bei WebRTC- und SIP-Verbindungen verwaltet der Server einen Puffer für die Audioausgabe und weiß daher zu jedem Zeitpunkt, wie viel davon bereits abgespielt wurde. Bei einer Unterbrechung durch die nutzende Person entfernt der Server automatisch den noch nicht abgespielten Teil.

Bei einer WebSocket-Verbindung verwaltet der Client die Audiowiedergabe. Deshalb muss er die Wiedergabe stoppen und die Antwort kürzen. Das funktioniert so:

  1. Der Client wartet auf neue input_audio_buffer.speech_started-Ereignisse vom Server. Diese zeigen an, dass die nutzende Person zu sprechen begonnen hat. Der Server bricht automatisch jede laufende Modellantwort ab und sendet ein response.cancelled-Ereignis.
  2. Wenn der Client dieses Ereignis erkennt, sollte er die laufende Audiowiedergabe des Modells sofort stoppen. Er sollte festhalten, wie viel von der letzten Audioantwort vor der Unterbrechung abgespielt wurde.
  3. Der Client sollte ein conversation.item.truncate-Ereignis senden, um den noch nicht abgespielten Teil der letzten Modellantwort aus der Unterhaltung zu entfernen.

Hier ist ein Beispiel:

{
    "type": "conversation.item.truncate",
    "item_id": "item_1234", # this is the item ID of the model's last response
    "content_index": 0,
    "audio_end_ms": 1500 # truncate audio after 1.5 seconds
}

Wie sieht es mit dem Kürzen des Transkripts aus? Das Realtime-Modell verfügt nicht über genügend Informationen, um Transkript und Audio exakt einander zuzuordnen. Deshalb schneidet conversation.item.truncate das Audio an einer bestimmten Stelle ab und entfernt das Texttranskript für den noch nicht abgespielten Teil. So wird zwar das noch nicht abgespielte Audio entfernt, ein gekürztes Transkript steht jedoch nicht zur Verfügung.

Push-to-Talk

Die Realtime API verwendet standardmäßig Sprachaktivitätserkennung (VAD). Das bedeutet, dass Audioeingaben Modellantworten auslösen. Du kannst auch Push-to-Talk umsetzen: Deaktiviere dazu VAD und steuere auf Anwendungsebene, wann Audioeingaben an das Modell gesendet werden. Beispielsweise kannst du Audio aufnehmen, solange die Leertaste gedrückt wird, und beim Loslassen eine Antwort auslösen. Für manche Apps funktioniert das überraschend gut. Nutzende behalten die Kontrolle über die Interaktion, VAD-Fehler werden vermieden, und die Anwendung reagiert schnell, weil sie nicht auf einen VAD-Timeout warten muss.

Die Implementierung von Push-to-Talk unterscheidet sich bei WebSockets und WebRTC etwas. Bei einer WebSocket-Verbindung zur Realtime API werden alle Ereignisse über denselben Kanal und in derselben Reihenfolge gesendet. Eine WebRTC-Verbindung hat dagegen getrennte Kanäle für Audio- und Steuerungsereignisse.

WebSockets

Um Push-to-Talk mit einer WebSocket-Verbindung zu implementieren, muss der Client die Audiowiedergabe stoppen, Unterbrechungen verarbeiten und eine neue Antwort auslösen. Das Vorgehen im Detail:

  1. Deaktiviere VAD, indem du in einem session.update-Ereignis "turn_detection": null setzt.
  2. Starte beim Drücken der Taste die Audioaufnahme auf dem Client.
    1. Falls das Modell gerade eine Antwort generiert, brich sie ab, indem du ein response.cancel-Ereignis sendest.
    2. Falls gerade eine Audioausgabe des Modells abgespielt wird, stoppe die Wiedergabe sofort und sende ein conversation.item.truncate-Ereignis, um alle noch nicht abgespielten Audiodaten aus dem Gespräch zu entfernen.
  3. Sende beim Loslassen der Taste eine input_audio_buffer.append-Nachricht mit den Audiodaten, um das neue Audio in den Eingabepuffer zu schreiben.
  4. Sende ein input_audio_buffer.commit-Ereignis. Dadurch werden die Audiodaten aus dem Eingabepuffer übernommen und die Transkription der Eingabe gestartet, sofern sie aktiviert ist.
  5. Löse anschließend mit einem response.create-Ereignis eine Antwort aus.

WebRTC und SIP

Push-to-talk wird mit WebRTC ähnlich implementiert, allerdings muss der Audio-Eingabepuffer explizit geleert werden. Gehe dazu so vor:

  1. Deaktiviere VAD, indem du in einem session.update-Ereignis "turn_detection": null festlegst.
  2. Sende beim Drücken der Taste ein input_audio_buffer.clear-Ereignis, um alle vorherigen Audioeingaben zu löschen.
    1. Falls das Modell gerade eine Antwort generiert, brich sie ab, indem du ein response.cancel-Ereignis sendest.
    2. Falls gerade eine Audioausgabe des Modells abgespielt wird, sende ein output_audio_buffer.clear-Ereignis, um die noch nicht abgespielten Audiodaten zu löschen. Dadurch wird auch das Gespräch entsprechend gekürzt.
  3. Sende beim Loslassen der Taste ein input_audio_buffer.commit-Ereignis. Dadurch werden die Audiodaten im Eingabepuffer übernommen und die Transkription der Eingabe gestartet, sofern sie aktiviert ist.
  4. Löse anschließend mit einem response.create-Ereignis eine Antwort aus.