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.
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.
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 |
|---|---|
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.
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.
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.
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 |
|---|---|
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:
- Wenn Eingaben über den lokalen Medientrack gesendet werden, erhältst du
input_audio_buffer.speech_started-Ereignisse vom Server. - Wenn die lokale Audioeingabe endet, erhältst du das
input_audio_buffer.speech_stopped-Ereignis. - Du erhältst Delta-Ereignisse für das laufend erstellte Audiotranskript.
- Du erhältst ein
response.done-Ereignis, sobald das Modell eine Antwort transkribiert und vollständig gesendet hat.
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 | ||
| Audioeingabe der nutzenden Person | (vollständige Audionachricht senden) (Audio in Blöcken streamen) (wird bei deaktivierter VAD verwendet) (wird bei deaktivierter VAD verwendet) |
|
| Audioausgabe des Servers | (wird bei deaktivierter VAD verwendet) |
|
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.
- Sitzung:
session.input_audio_formatinsession.update - Antwort:
response.input_audio_formatinresponse.create
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.
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.
- Sitzung:
session.audio.output.formatinsession.update - Antwort:
response.audio.output.formatinresponse.create
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.
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:
- Sende
input_audio_buffer.commitmanuell. Dadurch wird ein neues Element für eine Nutzereingabe im Gespräch erstellt. - Sende
response.createmanuell, um eine Audioantwort des Modells auszulösen. - Sende
input_audio_buffer.clear, bevor eine neue Nutzereingabe beginnt.
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.
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.
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.
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.
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:
- Wenn du die Sitzung aktualisierst oder eine Antwort erstellst, kannst du eine Liste der verfügbaren Funktionen angeben, die das Modell aufrufen kann.
- 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.
- Wenn der Client Unterhaltungselemente mit Argumenten für einen Funktionsaufruf erkennt, führt er eigenen Code mit diesen Argumenten aus
- 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.
- Sitzung: Eigenschaft
session.toolsinsession.update - Antwort: Eigenschaft
response.toolsinresponse.create
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.
{
"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.
{
"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:
| Eigenschaft | Zweck beim Funktionsaufruf |
|---|---|
response.output[0].type | Der Wert function_call zeigt an, dass diese Antwort Argumente für den Aufruf einer benannten Funktion enthält. |
response.output[0].name | Der Name der konfigurierten Funktion, die aufgerufen werden soll, in diesem Fall generate_horoscope |
response.output[0].arguments | Ein JSON-String mit den Argumenten für die Funktion. In unserem Fall: "{\"sign\":\"Aquarius\"}". |
response.output[0].call_id | Eine 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_idist dieselbe ID, die wir im obigenresponse.done-Ereignis erhalten habenitem.outputist 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:
- 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 einresponse.cancelled-Ereignis. - 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.
- 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:
- Deaktiviere VAD, indem du in einem
session.update-Ereignis"turn_detection": nullsetzt. - Starte beim Drücken der Taste die Audioaufnahme auf dem Client.
- Falls das Modell gerade eine Antwort generiert, brich sie ab, indem du ein
response.cancel-Ereignis sendest. - 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.
- Falls das Modell gerade eine Antwort generiert, brich sie ab, indem du ein
- Sende beim Loslassen der Taste eine
input_audio_buffer.append-Nachricht mit den Audiodaten, um das neue Audio in den Eingabepuffer zu schreiben. - 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. - 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:
- Deaktiviere VAD, indem du in einem
session.update-Ereignis"turn_detection": nullfestlegst. - Sende beim Drücken der Taste ein
input_audio_buffer.clear-Ereignis, um alle vorherigen Audioeingaben zu löschen.- Falls das Modell gerade eine Antwort generiert, brich sie ab, indem du ein
response.cancel-Ereignis sendest. - 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.
- Falls das Modell gerade eine Antwort generiert, brich sie ab, indem du ein
- 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. - Löse anschließend mit einem
response.create-Ereignis eine Antwort aus.