OpenAI
Streaming chat completions with OpenAILLMService, the base for every OpenAI-compatible API.
Overview
OpenAILLMService streams replies from OpenAI's Chat Completions API, with function calling,
token usage metrics and out-of-band inference (runInference).
OpenAILLMService: the OpenAI chat service. It extendsBaseOpenAILLMService, which builds the client and the request. PointbaseUrlat any server that speaks the Chat Completions API (Ollama, vLLM, OpenRouter, and so on) to use it with that provider.
For the Responses API, see OpenAI Responses.
Upstream page
Pipecat's documentation for the same service.
Source
packages/openai/src/llm.ts and base_llm.ts.
OpenAI docs
The Chat Completions API reference.
Installation
bun add @pipedog/openaiimport { OpenAILLMService } from "@pipedog/openai/llm";
import { OpenAILLMSettings } from "@pipedog/openai/base_llm";Prerequisites
- An OpenAI account and an API key from the OpenAI dashboard.
- Pass the key as
apiKey. If you leave it out, the OpenAI SDK readsOPENAI_API_KEY. The examples read it fromOPENAI_API_KEY.
OpenAILLMService
Configuration
Prop
Type
The client options (apiKey, baseUrl, organization, project, defaultHeaders) come from
OpenAIClientOptions:
Prop
Type
Settings
Runtime-updatable settings, passed as settings: new OpenAILLMService.Settings({...})
(OpenAILLMSettings). Any setting you leave out keeps the default below. Fields marked
"not sent" hold the OpenAI SDK's sentinel and are left out of the request, so OpenAI's own
default applies.
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "gpt-4.1" | Chat model. |
systemInstruction | string | null | null | System prompt, added ahead of the context's messages. |
temperature | number | null | not sent | Sampling temperature. |
topP | number | null | not sent | Nucleus sampling. |
maxTokens | number | null | not sent | max_tokens. |
maxCompletionTokens | number | null | not sent | max_completion_tokens. |
frequencyPenalty | number | null | not sent | Frequency penalty. |
presencePenalty | number | null | not sent | Presence penalty. |
seed | number | null | not sent | Sampling seed. |
topK | number | null | null | Not part of the OpenAI request. |
filterIncompleteUserTurns | boolean | false | Filter incomplete user turns. |
userTurnCompletionConfig | UserTurnCompletionConfig | null | null | Turn completion config used with filterIncompleteUserTurns. |
extra | Record<string, unknown> | {} | Extra request fields, merged into the request last (they override the fields above). |
Usage
import { LLMUpdateSettingsFrame } from "@pipedog/core/frames/frames";
import { OpenAILLMService } from "@pipedog/openai/llm";
const llm = new OpenAILLMService({
apiKey: process.env.OPENAI_API_KEY ?? "",
settings: new OpenAILLMService.Settings({
model: "gpt-4.1",
systemInstruction: "You are a helpful assistant in a voice conversation.",
temperature: 0.7,
}),
});An OpenAI-compatible server, through baseUrl:
// Any server that speaks the Chat Completions API, here a local Ollama.
const local = new OpenAILLMService({
apiKey: "ollama",
baseUrl: "http://localhost:11434/v1",
settings: new OpenAILLMService.Settings({ model: "llama3.2" }),
});Re-issue a request that produces no output in time:
const withRetry = new OpenAILLMService({
apiKey: process.env.OPENAI_API_KEY ?? "",
retryOnTimeout: true,
retryTimeoutSecs: 3,
});Change settings mid-call with an LLMUpdateSettingsFrame. The next completion uses them:
// Queue it on the worker, or push it from a processor upstream of the service.
const update = new LLMUpdateSettingsFrame({
delta: new OpenAILLMService.Settings({ temperature: 0.2, maxCompletionTokens: 300 }),
});Event handlers
| Event | Description |
|---|---|
on_completion_timeout | A completion timed out. |
on_function_calls_started | The model asked for one or more function calls. |
on_function_calls_cancelled | Function calls were cancelled. |
These come from the LLMService base class.
Notes
- Runs a completion for each
LLMContextFrame, between anLLMFullResponseStartFrameand anLLMFullResponseEndFrame, and streams the reply asLLMTextFrames. Other frames pass through. - Requests always stream, with
stream_options: { include_usage: true }, so token usage metrics are reported. serviceTieris sent asservice_tierwhen set.retryOnTimeoutis off by default. With it on, an attempt that produces nothing withinretryTimeoutSecs(5 seconds by default) is abandoned and re-issued once.- Models older than
gpt-4o-miniandgpt-4o-2024-08-06can't enforce a response schema, sorunInferenceignores aresponseSchemafor them, with a warning. - OpenAI-compatible subclasses override
createClient()to build the client their own way, and can setsupportsDeveloperRole = falseto send "developer" messages as "user" messages.
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 client is the official
openaiJS SDK, which talks throughfetch. It works on Bun, Node, Deno and Cloudflare Workers. - Upstream builds the client with an httpx connection pool.
fetchhas no such object, so connection reuse is left to the runtime. - The JS SDK has no
extra_bodyoption. Provider-specificextra_bodyfields are merged into the request JSON just before it is sent, as the Python client does. - The deprecated
modelandparamsconstructor options andBaseOpenAILLMService.InputParamsare not ported. Usesettings. - For Azure OpenAI, use
AzureLLMService. Other OpenAI-compatible providers have their own subclasses where ported (see LLM); for the rest,OpenAILLMServicewithbaseUrlcovers most of them.
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).