Google Gemini
Streaming Gemini chat completions with GoogleLLMService, with thinking, safety settings and Google Search grounding.
Overview
GoogleLLMService runs Gemini models through the Gemini API (Google AI Studio keys). It
converts an LLMContext to Gemini's Content/Part format, streams the reply and handles
function calls.
GoogleLLMService: streaming Gemini inference with tools, thinking (GoogleThinkingConfig), safety settings, image output and search grounding.LLMSearchResponseFrame(frames.ts): pushed when a reply carries grounding metadata, for example from the Google Search tool.GoogleRTVIProcessor/GoogleRTVIObserver(rtvi.ts): an RTVI processor whose observer also forwardsLLMSearchResponseFrames to the client.
For Vertex AI, see Google Vertex AI. For speech-to-speech, see Gemini Live.
Upstream page
Pipecat's documentation for the same service.
Source
packages/google/src/llm.ts, frames.ts and rtvi.ts.
Gemini API docs
Models, thinking, safety settings and grounding.
Installation
bun add @pipedog/googleimport { GoogleLLMService, GoogleThinkingConfig } from "@pipedog/google/llm";
import { LLMSearchResponseFrame } from "@pipedog/google/frames";
import { GoogleRTVIProcessor } from "@pipedog/google/rtvi";Prerequisites
- A Gemini API key from Google AI Studio.
- Pass the key as
apiKey. The examples read it fromGOOGLE_API_KEY.
GoogleLLMService
Configuration
Prop
Type
Settings
Runtime-updatable settings, passed as settings: new GoogleLLMService.Settings({...}). Any
setting you leave out keeps the default below. Extra Gemini GenerateContentConfig fields can go
in extra, with the Python SDK's snake_case keys (they are converted to camelCase when the
request is sent).
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "gemini-3.6-flash" | Gemini model. |
systemInstruction | string | null | null | System prompt. |
maxTokens | number | null | 4096 | Sent as maxOutputTokens. |
temperature | number | null | null | Sampling temperature. |
topK | number | null | null | Top-k sampling. |
topP | number | null | null | Nucleus sampling. |
seed | number | null | null | Random seed. Gemini treats it as best effort. |
frequencyPenalty | number | null | null | Frequency penalty. Not every Gemini model accepts it. |
presencePenalty | number | null | null | Presence penalty. Not every Gemini model accepts it. |
thinking | GoogleThinkingConfig | null | null | Thinking configuration. See below. |
safetySettings | SafetySetting[] | null | null | Content safety filters. Categories you leave out keep the Gemini API defaults. |
useProviderThinkingDefault | boolean | false | Send no thinking default; run at Google's default. |
GoogleThinkingConfig takes thinkingLevel (Gemini 3 and later: "minimal", "low",
"medium", "high"), thinkingBudget (Gemini 2.5: a token count, 0 off, -1 dynamic) and
includeThoughts. Set a level or a budget, not both.
When thinking is null, the service sends the lowest thinking setting Google documents for
the model, for the fastest replies, and logs what it sent. For example gemini-2.5-flash gets
thinking_budget: 0, gemini-2.5-pro gets 128, gemini-3.6-flash gets
thinking_level: "minimal" and gemini-3.7-flash gets "low". Models not in that table
(including aliases such as gemini-flash-latest) run at Google's default.
Usage
import { LLMUpdateSettingsFrame } from "@pipedog/core/frames/frames";
import { GoogleLLMService, GoogleThinkingConfig } from "@pipedog/google/llm";
import { GoogleRTVIProcessor } from "@pipedog/google/rtvi";
const llm = new GoogleLLMService({
apiKey: process.env.GOOGLE_API_KEY ?? "",
settings: new GoogleLLMService.Settings({
model: "gemini-3.6-flash",
systemInstruction: "You are a helpful voice assistant. Keep answers short.",
temperature: 0.7,
}),
});Thinking, for Gemini 3 and Gemini 2.5 models:
// Gemini 3 models take a thinking level.
const gemini3 = new GoogleLLMService({
apiKey: process.env.GOOGLE_API_KEY ?? "",
settings: new GoogleLLMService.Settings({
model: "gemini-3.6-flash",
thinking: new GoogleThinkingConfig({ thinkingLevel: "low", includeThoughts: true }),
}),
});
// Gemini 2.5 models take a token budget (0 turns thinking off).
const gemini25 = new GoogleLLMService({
apiKey: process.env.GOOGLE_API_KEY ?? "",
settings: new GoogleLLMService.Settings({
model: "gemini-2.5-flash",
thinking: new GoogleThinkingConfig({ thinkingBudget: 1024 }),
}),
});Google Search grounding, with the RTVI processor that forwards search results to the client:
// Tools in the SDK's camelCase shape are sent as given.
const grounded = new GoogleLLMService({
apiKey: process.env.GOOGLE_API_KEY ?? "",
tools: [{ googleSearch: {} }],
});
// Pass it to PipelineWorker as `rtviProcessor` to forward each
// LLMSearchResponseFrame to the client as a "bot-llm-search-response" message.
const rtvi = new GoogleRTVIProcessor();Change settings mid-call with an LLMUpdateSettingsFrame:
// Queue it on the worker, or push it from a processor upstream of the service.
const update = new LLMUpdateSettingsFrame({
delta: new GoogleLLMService.Settings({ temperature: 0.2, maxTokens: 1024 }),
});Event handlers
Inherited from LLMService.
| Event | Description |
|---|---|
on_completion_timeout | The stream stalled past streamIdleTimeoutSecs (or the retry window). |
on_function_calls_started | Function calls were received and are about to run. |
on_function_calls_cancelled | In-progress function calls were cancelled. |
Notes
- Emits
LLMFullResponseStartFrame,LLMTextFrames andLLMFullResponseEndFrame. Thoughts (withincludeThoughts) arrive whole, each as anLLMThoughtStartFrame,LLMThoughtTextFrameandLLMThoughtEndFrame. - When a reply has grounding metadata, the service pushes an
LLMSearchResponseFramewith the reply text (searchResult), the search entry point'srenderedContent, andorigins(site URI, title and the supported text segments with confidence scores).GoogleRTVIObserversends it to the client as abot-llm-search-responsemessage. - Gemini thought signatures are saved into the context (through an
LLMMessagesAppendFrame), so they go back to the model on the next turn. - Inline image output becomes an
AssistantImageRawFrame, but only with animageDecoder. The default decoder throws, so without one an image reply surfaces as a pushed error. streamIdleTimeoutSecs(default 20) bounds the gap between chunks, including the wait for the first one. WithretryOnTimeout, a first chunk that doesn't arrive withinretryTimeoutSecs(default 5) re-issues the request once.- Newer models reject a request that ends with a model turn. For any model whose name doesn't
start with
gemini-2.,gemini-3-,gemini-3.1-orgemini-pro-latest, the service adds a trailing user message when needed. - A reply that stops for a reason other than
STOP(safety, recitation, token limit, ...) logs a warning with the finish reason. The text that did arrive is still pushed. - Setting
thinkingBudgeton a Gemini 3, Gemini 4 or Gemma 4 model logs a warning: usethinkingLevelthere. runInference()accepts aresponseSchema(JSON schema) on Gemini 2.5 and later.- An
INVALID_ARGUMENTrejection removes a pending invalid file message from the context and is reported as an application error. - Token usage includes tool-use prompt tokens in the prompt count and thinking tokens in the completion count.
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/google/package.json: ✅ means the compat smoke test passes on that runtime.
The client is GoogleGenAI from @google/genai, which uses fetch, so the service runs on
Bun, Node, Deno and Cloudflare Workers. Subclasses can override createClient(). Image
decoding (upstream uses Pillow) is not ported: pass an imageDecoder built on sharp,
@jsquash/* or similar to handle image output.
The deprecated upstream InputParams class and the model, params and systemInstruction
constructor options are not ported; use settings.
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).