Support thinking level configuration for Gemini 3 Pro models (#176)

* support Google thinking level configuration for Gemini 3 Pro models

* relax model ID check for gemini 3 pro
This commit is contained in:
Markus Ylisiurunen 2025-12-13 03:09:54 +02:00 committed by GitHub
parent 38119ffbb0
commit 6b48fa58d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 44 additions and 9 deletions

View file

@ -6,6 +6,8 @@ import {
type GenerateContentParameters, type GenerateContentParameters,
GoogleGenAI, GoogleGenAI,
type Part, type Part,
type ThinkingConfig,
type ThinkingLevel,
} from "@google/genai"; } from "@google/genai";
import { calculateCost } from "../models.js"; import { calculateCost } from "../models.js";
import type { import type {
@ -31,6 +33,7 @@ export interface GoogleOptions extends StreamOptions {
thinking?: { thinking?: {
enabled: boolean; enabled: boolean;
budgetTokens?: number; // -1 for dynamic, 0 to disable budgetTokens?: number; // -1 for dynamic, 0 to disable
level?: ThinkingLevel;
}; };
} }
@ -293,10 +296,13 @@ function buildParams(
} }
if (options.thinking?.enabled && model.reasoning) { if (options.thinking?.enabled && model.reasoning) {
config.thinkingConfig = { const thinkingConfig: ThinkingConfig = { includeThoughts: true };
includeThoughts: true, if (options.thinking.level !== undefined) {
...(options.thinking.budgetTokens !== undefined && { thinkingBudget: options.thinking.budgetTokens }), thinkingConfig.thinkingLevel = options.thinking.level;
}; } else if (options.thinking.budgetTokens !== undefined) {
thinkingConfig.thinkingBudget = options.thinking.budgetTokens;
}
config.thinkingConfig = thinkingConfig;
} }
if (options.signal) { if (options.signal) {

View file

@ -1,3 +1,4 @@
import { ThinkingLevel } from "@google/genai";
import { type AnthropicOptions, streamAnthropic } from "./providers/anthropic.js"; import { type AnthropicOptions, streamAnthropic } from "./providers/anthropic.js";
import { type GoogleOptions, streamGoogle } from "./providers/google.js"; import { type GoogleOptions, streamGoogle } from "./providers/google.js";
import { type OpenAICompletionsOptions, streamOpenAICompletions } from "./providers/openai-completions.js"; import { type OpenAICompletionsOptions, streamOpenAICompletions } from "./providers/openai-completions.js";
@ -159,15 +160,26 @@ function mapOptionsForApi<TApi extends Api>(
case "google-generative-ai": { case "google-generative-ai": {
if (!options?.reasoning) return base as any; if (!options?.reasoning) return base as any;
const googleBudget = getGoogleBudget( const googleModel = model as Model<"google-generative-ai">;
model as Model<"google-generative-ai">, const effort = clampReasoning(options.reasoning)!;
clampReasoning(options.reasoning)!,
); // Gemini 3 Pro models use thinkingLevel exclusively instead of thinkingBudget.
// https://ai.google.dev/gemini-api/docs/thinking#set-budget
if (isGemini3ProModel(googleModel)) {
return { return {
...base, ...base,
thinking: { thinking: {
enabled: true, enabled: true,
budgetTokens: googleBudget, level: getGoogleThinkingLevel(effort),
},
} satisfies GoogleOptions;
}
return {
...base,
thinking: {
enabled: true,
budgetTokens: getGoogleBudget(googleModel, effort),
}, },
} satisfies GoogleOptions; } satisfies GoogleOptions;
} }
@ -182,6 +194,23 @@ function mapOptionsForApi<TApi extends Api>(
type ClampedReasoningEffort = Exclude<ReasoningEffort, "xhigh">; type ClampedReasoningEffort = Exclude<ReasoningEffort, "xhigh">;
function isGemini3ProModel(model: Model<"google-generative-ai">): boolean {
// Covers gemini-3-pro, gemini-3-pro-preview, and possible other prefixed ids in the future
return model.id.includes("3-pro");
}
function getGoogleThinkingLevel(effort: ClampedReasoningEffort): ThinkingLevel {
// Gemini 3 Pro only supports LOW/HIGH (for now)
switch (effort) {
case "minimal":
case "low":
return ThinkingLevel.LOW;
case "medium":
case "high":
return ThinkingLevel.HIGH;
}
}
function getGoogleBudget(model: Model<"google-generative-ai">, effort: ClampedReasoningEffort): number { function getGoogleBudget(model: Model<"google-generative-ai">, effort: ClampedReasoningEffort): number {
// See https://ai.google.dev/gemini-api/docs/thinking#set-budget // See https://ai.google.dev/gemini-api/docs/thinking#set-budget
if (model.id.includes("2.5-pro")) { if (model.id.includes("2.5-pro")) {