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

View file

@ -1,3 +1,4 @@
import { ThinkingLevel } from "@google/genai";
import { type AnthropicOptions, streamAnthropic } from "./providers/anthropic.js";
import { type GoogleOptions, streamGoogle } from "./providers/google.js";
import { type OpenAICompletionsOptions, streamOpenAICompletions } from "./providers/openai-completions.js";
@ -159,15 +160,26 @@ function mapOptionsForApi<TApi extends Api>(
case "google-generative-ai": {
if (!options?.reasoning) return base as any;
const googleBudget = getGoogleBudget(
model as Model<"google-generative-ai">,
clampReasoning(options.reasoning)!,
);
const googleModel = model as Model<"google-generative-ai">;
const effort = 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 {
...base,
thinking: {
enabled: true,
level: getGoogleThinkingLevel(effort),
},
} satisfies GoogleOptions;
}
return {
...base,
thinking: {
enabled: true,
budgetTokens: googleBudget,
budgetTokens: getGoogleBudget(googleModel, effort),
},
} satisfies GoogleOptions;
}
@ -182,6 +194,23 @@ function mapOptionsForApi<TApi extends Api>(
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 {
// See https://ai.google.dev/gemini-api/docs/thinking#set-budget
if (model.id.includes("2.5-pro")) {