OpenAI Realtime
Speech-to-speech with OpenAIRealtimeLLMService, over OpenAI's Realtime API WebSocket.
Overview
OpenAIRealtimeLLMService: one WebSocket session with OpenAI's Realtime API, which hears the user's audio and answers with audio. It replaces the STT, LLM and TTS services in a pipeline. It supports function calling, transcripts of both sides, video input and the reasoning-capable Realtime models.
For OpenAI's full-duplex live model, see OpenAI Live.
Upstream page
Pipecat's documentation for the same service.
Source
packages/openai/src/realtime/llm.ts and events.ts.
OpenAI docs
The Realtime API over WebSocket.
Installation
bun add @pipedog/openaiimport { OpenAIRealtimeLLMService } from "@pipedog/openai/realtime/llm";
import { SessionProperties } from "@pipedog/openai/realtime/events";@pipedog/openai/realtime/events holds the session classes (SessionProperties,
AudioConfiguration, AudioInput, AudioOutput, InputAudioTranscription, TurnDetection,
SemanticTurnDetection, Reasoning, ...) and the client and server event models.
Prerequisites
- An OpenAI account and an API key from the OpenAI dashboard.
- Pass the key as
apiKey(required). The examples read it fromOPENAI_API_KEY.
OpenAIRealtimeLLMService
Configuration
Prop
Type
Settings
Runtime-updatable settings, passed as settings: new OpenAIRealtimeLLMService.Settings({...})
(OpenAIRealtimeLLMSettings). Any setting you leave out keeps the default below.
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "gpt-realtime-2.1" | Realtime model. Fixed for the session: it goes in the connection URL. |
systemInstruction | string | null | null | Instructions. Kept in sync with sessionProperties.instructions. |
sessionProperties | SessionProperties | new SessionProperties({}) | Session config: output modalities, audio input and output, tools, tool choice, max output tokens, reasoning, and so on. |
temperature, maxTokens, topP, topK, frequencyPenalty, presencePenalty, seed | number | null | null | Not used by the Realtime API. |
filterIncompleteUserTurns | boolean | false | Filter incomplete user turns. |
userTurnCompletionConfig | UserTurnCompletionConfig | null | null | Turn completion config used with filterIncompleteUserTurns. |
A sessionProperties in an update replaces the stored one as a whole. Top-level model and
systemInstruction win over the same fields inside it.
Usage
import { LLMUpdateSettingsFrame } from "@pipedog/core/frames/frames";
import {
AudioConfiguration,
AudioInput,
AudioOutput,
type ConversationItem,
InputAudioTranscription,
SemanticTurnDetection,
SessionProperties,
} from "@pipedog/openai/realtime/events";
import { OpenAIRealtimeLLMService } from "@pipedog/openai/realtime/llm";
const llm = new OpenAIRealtimeLLMService({
apiKey: process.env.OPENAI_API_KEY ?? "",
settings: new OpenAIRealtimeLLMService.Settings({
model: "gpt-realtime-2.1",
systemInstruction: "You are a helpful assistant in a voice conversation.",
sessionProperties: new SessionProperties({
audio: new AudioConfiguration({
input: new AudioInput({
transcription: new InputAudioTranscription(),
turnDetection: new SemanticTurnDetection({ eagerness: "medium" }),
}),
output: new AudioOutput({ voice: "marin" }),
}),
}),
}),
});
llm.addEventHandler(
"on_conversation_item_created",
async (_service, itemId: string, _item: ConversationItem) => {
console.log("conversation item created:", itemId);
},
);Change the instructions mid-call with an LLMUpdateSettingsFrame. A change to
systemInstruction or sessionProperties is sent as a session.update; other fields are
ignored with a warning:
// Sends a session.update to the open session.
const update = new LLMUpdateSettingsFrame({
delta: new OpenAIRealtimeLLMService.Settings({
systemInstruction: "Answer in Spanish from now on.",
}),
});Event handlers
| Event | Description |
|---|---|
on_conversation_item_created | A conversation item was added. Called with the item id and the item. |
on_conversation_item_updated | A conversation item changed (a transcript arrived, for example). Called with the item id and the item, or null. |
Notes
- Consumes
InputAudioRawFrame(resampled to 24 kHz),InputImageRawFrame(at most one per second, at thevideoFrameDetaillevel),LLMContextFrame,LLMMessagesAppendFrameandLLMSetToolsFrame. EmitsTTSAudioRawFrame,TTSTextFrameandLLMTextFramefor the bot, andInterimTranscriptionFrame/TranscriptionFramefor the user. - With OpenAI's server-side turn detection (the session default), the service broadcasts
proposed user turn frames from the server's speech events and asks the user aggregator for
ExternalUserTurnStrategies, so no local VAD is needed.LLMContextAggregatorPairdetects that this is a realtime service. - For locally driven turns, set
turnDetection: falseinAudioInputand add a VAD to the user aggregator. After an interruption the input buffer is cleared and the lastuserAudioPrerollSecsof audio is replayed, so the start of the user's speech isn't lost. Don't run a local VAD with server-side turn detection on: both would send turn frames. setAudioInputPaused(),setVideoInputPaused()andsetVideoFrameDetail()change input at runtime.sendClientEvent()sends any Realtime client event, andretrieveConversationItem()fetches an item by id.reasoninginsessionPropertiesis only sent to models recognized as reasoning-capable (gpt-realtime-2,gpt-realtime-2.1); otherwise it is dropped with a warning, since the server would reject the whole update.
Runtime notes
| Runtime | Works | What it needs |
|---|---|---|
| Bun | ✅ yes | Nothing. |
| Node.js | ✅ yes | Nothing. |
| Deno | ✅ yes | Nothing. |
| Cloudflare Workers | ✅ yes | Nothing. |
Generated from pipecat.runtimes and pipecat.compat in packages/openai/package.json: ✅ means the compat smoke test passes on that runtime.
- The socket is opened with
websocketConnect()from core and the key is sent as anAuthorizationheader. Bun, Node and Deno send it from the globalWebSocket; on Cloudflare Workers core opens the socket throughfetch. A subclass that authenticates another way overrides_authHeaders(), as Azure Realtime does. - Video frames are encoded with core's
encodeImage()(upstream uses Pillow). - The deprecated
modelandsessionPropertiesconstructor options are not ported. Usesettings: new OpenAIRealtimeLLMService.Settings({ model, sessionProperties }). - For realtime models deployed on Azure, see
AzureRealtimeLLMService.
pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.
Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).