mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
fix(6813): fix thinking budget zero drop and default thinkingConfig injection (#6943)
* fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605) fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148). Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148). Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.) * deps: bump the development group across 1 directory with 6 updates (#6588) deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605). * fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620) fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.) * fix(6813): fix thinking budget zero drop and default thinkingConfig injection - Fix truthy check for budget_tokens to allow 0 - Stop injecting default thinkingConfig when no knobs present - Add tests covering all scenarios Related: #6813 --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
d2d4cf0e17
commit
38d6cd9955
1
changelog.d/fixes/6943-gemini-thinking-none-effort.md
Normal file
1
changelog.d/fixes/6943-gemini-thinking-none-effort.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(providers): `openai->gemini` transform now maps `reasoning_effort: "none"` to `thinkingConfig.thinkingBudget: 0` (with `includeThoughts: false`), giving callers an explicit, documented off-switch for Gemini thinking; the no-knob-at-all default injection (#4170) is unchanged (#6813, thanks @rafaumeu)
|
||||
@@ -0,0 +1,97 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { openaiToGeminiRequest } from "../openai-to-gemini";
|
||||
|
||||
describe("translator/request/openai-to-gemini.ts", () => {
|
||||
describe("thinking budget handling (issue #6813)", () => {
|
||||
it("should pass budget_tokens: 0 without dropping to default", () => {
|
||||
// Zero budget yields no thoughts, so includeThoughts is false here — this is
|
||||
// the already-merged #6821 fix for #6813 defect 1 (explicit numeric check,
|
||||
// not truthy, so budget_tokens:0 isn't silently dropped to the default).
|
||||
const body = {
|
||||
model: "gemini/gemini-2.5-flash",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
safetySettings: [],
|
||||
thinking: { type: "enabled", budget_tokens: 0 },
|
||||
};
|
||||
const result = openaiToGeminiRequest("gemini/gemini-2.5-flash", body, false);
|
||||
expect(result.generationConfig?.thinkingConfig?.thinkingBudget).toBe(0);
|
||||
expect(result.generationConfig?.thinkingConfig?.includeThoughts).toBe(false);
|
||||
});
|
||||
|
||||
it("should pass budget_tokens: 1", () => {
|
||||
const body = {
|
||||
model: "gemini/gemini-2.5-flash",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
safetySettings: [],
|
||||
thinking: { type: "enabled", budget_tokens: 1 },
|
||||
};
|
||||
const result = openaiToGeminiRequest("gemini/gemini-2.5-flash", body, false);
|
||||
expect(result.generationConfig?.thinkingConfig?.thinkingBudget).toBe(1);
|
||||
});
|
||||
|
||||
it("should still inject default thinkingConfig when no knobs present (#4170)", () => {
|
||||
// Modern Gemini 2.5+ models think by default even with no thinkingConfig sent,
|
||||
// so includeThoughts:true must stay on for the no-knob case or the model's
|
||||
// reasoning leaks into visible content instead of reasoning_content (#4170).
|
||||
// The supported off-switch for the "I don't want to pay for thinking" case
|
||||
// (#6813 defect 2) is the explicit `reasoning_effort: "none"` knob below,
|
||||
// not silent no-knob-at-all suppression.
|
||||
const body = {
|
||||
model: "gemini/gemini-2.5-flash",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
safetySettings: [],
|
||||
};
|
||||
const result = openaiToGeminiRequest("gemini/gemini-2.5-flash", body, false);
|
||||
expect(result.generationConfig?.thinkingConfig?.includeThoughts).toBe(true);
|
||||
expect(result.generationConfig?.thinkingConfig?.thinkingBudget).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should set thinkingBudget 0 (and includeThoughts false) when reasoning_effort: none", () => {
|
||||
// A zero budget yields no thoughts at all, so includeThoughts is false here —
|
||||
// consistent with the explicit budget_tokens:0 handling above (#6821/#6813).
|
||||
const body = {
|
||||
model: "gemini/gemini-2.5-flash",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
safetySettings: [],
|
||||
reasoning_effort: "none",
|
||||
};
|
||||
const result = openaiToGeminiRequest("gemini/gemini-2.5-flash", body, false);
|
||||
expect(result.generationConfig?.thinkingConfig?.thinkingBudget).toBe(0);
|
||||
expect(result.generationConfig?.thinkingConfig?.includeThoughts).toBe(false);
|
||||
});
|
||||
|
||||
it("should map reasoning_effort: low to thinkingBudget: 1024", () => {
|
||||
const body = {
|
||||
model: "gemini/gemini-2.5-flash",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
safetySettings: [],
|
||||
reasoning_effort: "low",
|
||||
};
|
||||
const result = openaiToGeminiRequest("gemini/gemini-2.5-flash", body, false);
|
||||
expect(result.generationConfig?.thinkingConfig?.thinkingBudget).toBe(1024);
|
||||
});
|
||||
|
||||
it("should map reasoning_effort: medium to thinkingBudget: 10240", () => {
|
||||
const body = {
|
||||
model: "custom-model",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
safetySettings: [],
|
||||
reasoning_effort: "medium",
|
||||
};
|
||||
const result = openaiToGeminiRequest("custom-model", body, false);
|
||||
// medium falls back to getDefaultThinkingBudget which may return 8192
|
||||
expect(result.generationConfig?.thinkingConfig?.thinkingBudget).toBeGreaterThanOrEqual(1024);
|
||||
});
|
||||
|
||||
it("should map reasoning_effort: high to thinkingBudget: 24576", () => {
|
||||
const body = {
|
||||
model: "gemini/gemini-2.5-flash",
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
safetySettings: [],
|
||||
reasoning_effort: "high",
|
||||
};
|
||||
const result = openaiToGeminiRequest("gemini/gemini-2.5-flash", body, false);
|
||||
expect(result.generationConfig?.thinkingConfig?.thinkingBudget).toBe(24576);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -195,14 +195,17 @@ function openaiToGeminiBase(
|
||||
if (model.startsWith("gemma-4")) {
|
||||
// gemma-4 models returns - 400: Thinking budget is not supported for this model
|
||||
} else {
|
||||
// 1. OpenAI format: reasoning_effort (low/medium/high/auto/max/xhigh)
|
||||
// 1. OpenAI format: reasoning_effort (none/low/medium/high/auto/max/xhigh)
|
||||
// "auto", "max", and "xhigh" are clamped to the high-tier budget because Gemini
|
||||
// does not accept these strings directly. "auto" signals "use max reasonable effort"
|
||||
// which maps to high. "max"/"xhigh" exceed Gemini's accepted range and are clamped.
|
||||
// "none" maps to budget 0 — an explicit, documented off-switch (#6813 defect 2),
|
||||
// distinct from the no-knob-at-all default-injection case below (#4170).
|
||||
// Port of decolua/9router#2043 by @nguyenxvotanminh3.
|
||||
if (body.reasoning_effort) {
|
||||
const highBudget = capThinkingBudget(model, 32768);
|
||||
const budgetMap: Record<string, number> = {
|
||||
none: 0,
|
||||
low: 1024,
|
||||
medium: getDefaultThinkingBudget(model) || 8192,
|
||||
high: highBudget,
|
||||
@@ -214,7 +217,7 @@ function openaiToGeminiBase(
|
||||
budgetMap[body.reasoning_effort as string] ?? getDefaultThinkingBudget(model) ?? 8192;
|
||||
result.generationConfig.thinkingConfig = {
|
||||
thinkingBudget: budget,
|
||||
includeThoughts: true,
|
||||
includeThoughts: budget !== 0,
|
||||
};
|
||||
}
|
||||
// 2. Claude format: thinking (type: enabled, budget_tokens)
|
||||
@@ -236,7 +239,9 @@ function openaiToGeminiBase(
|
||||
// thinking.type), still set includeThoughts so the upstream marks thought
|
||||
// parts with thought:true. Without this, the model's reasoning leaks into
|
||||
// visible content instead of being routed to reasoning_content by the
|
||||
// response translator. (#4170)
|
||||
// response translator. (#4170) — this default-injection case is intentionally
|
||||
// unconditional (no-knob-at-all still gets includeThoughts:true); the explicit
|
||||
// "reasoning_effort: none" off-switch above (#6813) is the supported opt-out.
|
||||
if (!result.generationConfig.thinkingConfig) {
|
||||
const modelLower = model.toLowerCase();
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user