OpenAI Live
Full-duplex speech-to-speech with OpenAILiveLLMService, which delegates work to a backend model through ResponsesDelegation or ClientDelegation.
Overview
OpenAILiveLLMService: a session with OpenAI's Live API. The live model (gpt-live-1) listens and speaks at the same time. It decides when to answer, when to stop when the user talks over it, and when to delegate work (search, reasoning, tool use) to a backend text model while the conversation goes on. There is no client-side turn detection or response trigger: the pipeline streams audio in and plays audio out.
Where delegated work runs is set with delegation:
ResponsesDelegation(OpenAILiveLLMService.ResponsesDelegation): OpenAI hosts the backend model on the Responses API. Its function calls run here, with the handlers registered for the tools in the pipeline'sLLMContext.ClientDelegation(OpenAILiveLLMService.ClientDelegation): aBackendLLMWorkerthat runs any pipedog LLM service is the backend.null(the default): client delegation with no backend. Delegated requests are declined.
Upstream page
Pipecat's documentation for the same service.
Source
packages/openai/src/live/llm.ts and events.ts.
OpenAI docs
The Live API guide.
Installation
bun add @pipedog/openaiimport { ClientDelegation, OpenAILiveLLMService, ResponsesDelegation } from "@pipedog/openai/live/llm";
import { BackendLLMWorker } from "@pipedog/core/workers/llm/backend_llm_worker";Prerequisites
- An OpenAI account and a project API key from the OpenAI dashboard.
- Pass the key as
apiKey(required). The examples read it fromOPENAI_API_KEY.
OpenAILiveLLMService
Configuration
Prop
Type
ResponsesDelegation options:
Prop
Type
ClientDelegation options:
Prop
Type
Settings
Settings passed as settings: new OpenAILiveLLMService.Settings({...}) (OpenAILiveLLMSettings).
model, voice, store and systemInstruction are fixed once the session has started.
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "gpt-live-1" | Live model. |
systemInstruction | string | null | null | Instructions, used when the context has no leading system message. |
voice | string | null | null | Output voice, for example "marin" or "cedar". null leaves it to the API. |
store | boolean | null | null | Keep the session for 30 days (recording and forking). The API default is false; it needs a project whose data policy allows it. |
temperature, maxTokens, topP, topK, frequencyPenalty, presencePenalty, seed | number | null | null | Not used by the live model. |
filterIncompleteUserTurns | boolean | false | Filter incomplete user turns. |
userTurnCompletionConfig | UserTurnCompletionConfig | null | null | Turn completion config used with filterIncompleteUserTurns. |
The backend model of a ResponsesDelegation takes an OpenAIResponsesLLMService.Settings
(see OpenAI Responses). model is required.
systemInstruction becomes the backend's instructions, maxCompletionTokens its
max_output_tokens, reasoning is sent when set, and extra is merged into the delegation
config. The sampling fields, topK, maxTokens and the user turn fields are dropped with a
warning.
Usage
import { LLMContext } from "@pipedog/core/processors/aggregators/llm_context";
import { BackendLLMWorker } from "@pipedog/core/workers/llm/backend_llm_worker";
import { OpenAILiveLLMService } from "@pipedog/openai/live/llm";
import { OpenAILLMService } from "@pipedog/openai/llm";
import { OpenAIResponsesLLMService } from "@pipedog/openai/responses/llm";
const llm = new OpenAILiveLLMService({
apiKey: process.env.OPENAI_API_KEY ?? "",
settings: new OpenAILiveLLMService.Settings({
systemInstruction: "You are a helpful assistant in a voice conversation.",
voice: "marin",
}),
delegation: new OpenAILiveLLMService.ResponsesDelegation({
settings: new OpenAIResponsesLLMService.Settings({ model: "gpt-5.4-mini" }),
}),
});
llm.addEventHandler("on_session_started", async (_service, session: unknown) => {
console.log("live session started:", session);
});Client delegation, with a pipedog LLM service as the backend:
// Any pipedog LLM service can be the backend. Run the pipeline under a WorkerRunner.
const backend = new BackendLLMWorker({
llm: new OpenAILLMService({ apiKey: process.env.OPENAI_API_KEY ?? "" }),
context: new LLMContext({
messages: [{ role: "system", content: "You look things up for the assistant." }],
}),
});
const live = new OpenAILiveLLMService({
apiKey: process.env.OPENAI_API_KEY ?? "",
delegation: new OpenAILiveLLMService.ClientDelegation({ backend, timeoutSecs: 60 }),
});Event handlers
| Event | Description |
|---|---|
on_session_started | The session is ready for audio. Called with the session resource. |
on_delegation_created | The model delegated work. Called with the delegation item. |
Notes
- The session starts on the first
LLMContextFrame(usually queued as anLLMRunFrame). The context's leading system message (orsystemInstruction) becomes the instructions, and the other text messages seed the session as prior conversation. - Audio input is resampled to 24 kHz. The bot's audio comes out as
SpeechOutputAudioRawFrames betweenTTSStartedFrameandTTSStoppedFrame, withLLMTextFrame/TTSTextFramefor its words andInterimTranscriptionFrame/TranscriptionFramefor the user's. - The service proposes user turns from the API's transcript turns and asks for
ExternalUserTurnStrategies({ enableInterruptions: false }). NoInterruptionFrameis broadcast: the model handles being talked over itself, and a delegation keeps running. So every tool behaves ascancelOnInterruption: false; usecancellableByLlm: truefor tools the model should be able to cancel. - Unlike the Realtime services, it is not flagged as a realtime service for the aggregators. It closes each user turn itself once the user falls quiet, so the user aggregator writes the turn to the context before any tool calls it triggers.
- With
ClientDelegation, each delegation is sent to the backend with the transcript since the previous one, and delegations run one at a time. A backend worker given as an object is added as a child of the pipeline worker at setup, so the pipeline must run under aWorkerRunner(see pipelines and workers). The backend's output is appended to the session as spoken commentary or as silent context, by itsprefersSpokenflag. - An
LLMUpdateSettingsFrameis not sent to the session: the service logs a warning for each changed field.resetConversation()drops the session and opens a new one from the current context. - Constructing a
ResponsesDelegationwithoutsettings.model, or the service without amodel, throws.
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.webSocketFactory(a pipedog addition) lets you open it some other way. - The backend model's
OpenAIResponsesReasoningConfigis sent as its set fields (effort,summary,mode).
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).