NVIDIA
NvidiaLLMService streams chat completions from NVIDIA NIM, and NvidiaOmniLLMService hears the user's speech with Nemotron Omni.
Overview
NVIDIA NIM serves models such as Nemotron over an OpenAI-compatible API, on NVIDIA's cloud or on your own hardware. pipedog ports both of Pipecat's NVIDIA LLM services:
NvidiaLLMService: anOpenAILLMServicesubclass with NIM's base URL and a default Nemotron model. It turns a model's reasoning intoLLMThought*Frames, from areasoning_contentfield or a leading<think>block, so TTS never reads it.NvidiaOmniLLMService: Nemotron Omni, which takes speech, images and video as well as text, and replies with text. By default it hears the user directly: it transcribes and answers each spoken turn in one request, in place of an STT service.
Riva speech-to-text and text-to-speech are not ported yet
Upstream's NVIDIA package also has Riva services: NvidiaSTTService,
NvidiaSegmentedSTTService and NvidiaTTSService, and SageMaker variants of them. They aren't
ported yet. See
NVIDIA Riva speech-to-text and
NVIDIA Riva text-to-speech. Use another TTS service with Omni.
Upstream page
Pipecat's documentation for the same service.
Source
packages/nvidia/src/llm.ts, omni/llm.ts and nvidia_omni_adapter.ts.
NIM LLM APIs
Models and the API reference.
Nemotron Omni
The Omni model card: supported inputs and deployment options.
Installation
bun add @pipedog/nvidiaIt depends on @pipedog/openai, which is installed with it.
import { NvidiaLLMService } from "@pipedog/nvidia/llm";
import { NvidiaOmniLLMService } from "@pipedog/nvidia/omni/llm";Prerequisites
- For NVIDIA's cloud endpoint, an API key from build.nvidia.com,
passed as
apiKey. The examples read it fromNVIDIA_API_KEY. The service logs a warning when it uses the cloud endpoint without a key. - A local NIM deployment needs no key: pass its address as
baseUrl.
NvidiaLLMService
Configuration
Prop
Type
The other options (organization, project, defaultHeaders, serviceTier, retryOnTimeout,
retryTimeoutSecs, function calling) are OpenAILLMService's. See
OpenAI.
| Base URL | https://integrate.api.nvidia.com/v1 |
| Default model | nvidia/nemotron-3.5-super-vl-120b-a12b |
Settings
Runtime-updatable settings, passed as settings: new NvidiaLLMService.Settings({...})
(NvidiaLLMSettings). They are OpenAI's settings, with
model defaulting to "nvidia/nemotron-3.5-super-vl-120b-a12b".
Usage
import { SileroVADAnalyzer } from "@pipedog/audio-silero";
import { CartesiaTTSService } from "@pipedog/cartesia/tts";
import { LLMUpdateSettingsFrame } from "@pipedog/core/frames/frames";
import { Pipeline } from "@pipedog/core/pipeline/pipeline";
import { LLMContext } from "@pipedog/core/processors/aggregators/llm_context";
import {
LLMContextAggregatorPair,
LLMUserAggregatorParams,
type UserTurnMessageAddedMessage,
} from "@pipedog/core/processors/aggregators/llm_response_universal";
import type { BaseTransport } from "@pipedog/core/transports/base_transport";
import { NvidiaLLMService } from "@pipedog/nvidia/llm";
import { NvidiaOmniLLMService } from "@pipedog/nvidia/omni/llm";
const llm = new NvidiaLLMService({
apiKey: process.env.NVIDIA_API_KEY ?? "",
settings: new NvidiaLLMService.Settings({
model: "nvidia/nemotron-3.5-super-vl-120b-a12b",
systemInstruction: "You are a helpful assistant in a voice conversation. Keep replies short.",
}),
});Point it at a NIM you run yourself:
// A NIM running on your own machine takes no API key.
const local = new NvidiaLLMService({ baseUrl: "http://localhost:8000/v1" });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 NvidiaLLMService.Settings({ temperature: 0.4 }),
});Notes
- Runs a completion for each
LLMContextFrameand streams the reply asLLMTextFrames, asOpenAILLMServicedoes. Function calls, token usage metrics andrunInferencework the same way. - Reasoning sent in the delta's
reasoning_content(orreasoning) field, as Nemotron Nano models do, is pushed asLLMThoughtStartFrame,LLMThoughtTextFrameandLLMThoughtEndFrame. - A reply that starts with a
<think>...</think>block, as DeepSeek-R1 and some Nemotron models write, has the block pushed as thought frames and only the rest sent on as text.
NvidiaOmniLLMService
Configuration
Prop
Type
| Base URL | https://integrate.api.nvidia.com/v1 |
| Default model | nvidia/nemotron-3-nano-omni-30b-a3b-reasoning |
| Request timeout | 120 seconds (requestTimeoutSecs) |
Settings
Passed as settings: new NvidiaOmniLLMService.Settings({...}) (NvidiaOmniLLMSettings): the
NvidiaLLMService settings, with these defaults and additions.
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning" | Model name. |
temperature | number | 0.6 | Sampling temperature. |
topP | number | 0.95 | Nucleus sampling. |
audioResponseInstruction | string | null | null | Instruction sent with the user's speech in audio input mode. null uses DEFAULT_AUDIO_RESPONSE_INSTRUCTION, which asks for a <transcript> and a <response> section. A custom one should keep asking for both. |
minUserAudioSecs | number | 0.3 | Shortest buffered utterance that starts a turn. |
preSpeechBufferSecs | number | 0.2 | Audio kept from before the user started speaking, so the start of an utterance isn't clipped. |
Usage
In audio input mode (the default), Omni takes the place of the STT service. The user aggregator still needs a VAD analyzer, to mark the turns:
// Omni hears the user's speech itself, so there is no STT service.
const omni = new NvidiaOmniLLMService({
apiKey: process.env.NVIDIA_API_KEY ?? "",
settings: new NvidiaOmniLLMService.Settings({
systemInstruction: "You are a helpful assistant in a voice conversation. Keep replies short.",
}),
});
const tts = new CartesiaTTSService({ apiKey: process.env.CARTESIA_API_KEY ?? "" });
const context = new LLMContext();
const aggregators = new LLMContextAggregatorPair(context, {
userParams: new LLMUserAggregatorParams({ vadAnalyzer: new SileroVADAnalyzer() }),
});
const userAggregator = aggregators.user();
const pipeline = new Pipeline([
transport.input(),
userAggregator,
omni, // hears the user and answers
tts,
transport.output(),
aggregators.assistant(),
]);
// What the user said is known once Omni has transcribed it.
userAggregator.eventHandler(
"on_user_turn_message_added",
async (_aggregator, message: UserTurnMessageAddedMessage) => {
console.log("user said:", message.content);
},
);With audioInput: false, Omni is a text LLM for a cascaded pipeline, with an STT service in front
of it. Media placed in the context is sent in either mode:
// With an STT service in the pipeline, Omni is a text LLM that also reads media in the context.
const textOmni = new NvidiaOmniLLMService({
apiKey: process.env.NVIDIA_API_KEY ?? "",
audioInput: false,
});runMultimodalInference() runs one request outside the pipeline, for a worker that analyzes media.
It returns the text, the reasoning and the finish reason, and can stream deltas to
onTextDelta and onReasoningDelta:
// Analyze a video outside the pipeline, with a cap on the reasoning.
async function describeVideo(base64Mp4: string): Promise<string> {
const media = new LLMContext({
messages: [
await LLMContext.createFileMessage({
type: "bytes",
format: "video/mp4",
file: `data:video/mp4;base64,${base64Mp4}`,
text: "Describe what happens in this clip.",
}),
],
});
const result = await textOmni.runMultimodalInference(media, {
reasoningBudget: 512,
maxTokens: 1024,
});
return result.text;
}Notes
- In audio input mode the service announces itself as a realtime service, so the user aggregator works in realtime mode. It buffers the user's audio between turn boundaries and answers each spoken turn once it ends, after any turn still running.
- Omni writes what the user said in a
<transcript>section. The service pushes it upstream as aTranscriptionFrame, which the user aggregator writes to the context, and sends only the<response>section on to TTS. Read the user's text fromon_user_turn_message_added, noton_user_turn_stopped. - The speech of a turn interrupted before Omni transcribed it is answered together with the user's next utterance.
- Context frames that need an answer (tool results, developer messages, typed user input) still
start a completion, so the
LLMRunFramegreeting works in both modes. - Messages go through
NvidiaOmniLLMAdapter(@pipedog/nvidia/nvidia_omni_adapter), which sends audio fromLLMContext.createAudioMessage()and audio and video files fromLLMContext.createFileMessage()asaudio_urlandvideo_urldata URLs. "developer" messages are sent as "user" messages. pipedog initoffersNvidiaLLMService, not the Omni service, as upstream's CLI does.
Runtime notes
| Runtime | Works | What it needs |
|---|---|---|
| Bun | ✅ yes | Nothing. |
| Node.js | ✅ yes | Nothing. |
| Deno | — not verified | Nothing. |
| Cloudflare Workers | ✅ yes | Nothing. |
Generated from pipecat.runtimes and pipecat.compat in packages/nvidia/package.json: ✅ means the compat smoke test passes on that runtime.
- The client is the official
openaiJS SDK (through@pipedog/openai), which talks throughfetch. - The deprecated
modelconstructor option is not ported. Usesettings. - Upstream keeps an Omni turn's audio in a Python
ContextVar. pipedog keeps it per task instead, so tasks started from a turn don't inherit it. Only the turn's own request sends it, as upstream. - Unit-tested with mocked clients (the ported upstream tests), not against NVIDIA itself.
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).