fix(gemini): drop HARM_CATEGORY_CIVIC_INTEGRITY from the default Gemini safety settings (#8231) (#8238)

Co-authored-by: Probe Test <probe@example.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-23 00:45:56 -03:00
committed by GitHub
parent b0704752d9
commit 917314156e
7 changed files with 85 additions and 15 deletions

View File

@@ -0,0 +1 @@
- fix(gemini): drop HARM_CATEGORY_CIVIC_INTEGRITY from the default Gemini safety settings (#8231)

View File

@@ -90,13 +90,23 @@ export const GEMINI_UNSUPPORTED_SCHEMA_KEYS = new Set([
export const UNSUPPORTED_SCHEMA_CONSTRAINTS = [...GEMINI_UNSUPPORTED_SCHEMA_KEYS];
// Default safety settings
// Default safety settings for the standard Gemini API surface.
//
// HARM_CATEGORY_CIVIC_INTEGRITY is intentionally NOT included here (#8231): the
// dynamic validation on some models/endpoints rejects it with a hard 400
// (`safety_settings[N]: element predicate failed`), taking down every request
// through that model. The Antigravity/Cloud Code surface already worked around
// this for #5003 (see ANTIGRAVITY_UNSUPPORTED_SAFETY_CATEGORIES in
// open-sse/executors/antigravity.ts) — this drops the same category from the
// standard-path default so behavior is consistent across Gemini surfaces. A
// caller that explicitly supplies safetySettings (including one that itself
// requests CIVIC_INTEGRITY) is still honored as-is — only this unconditional
// default is scoped down.
export const DEFAULT_SAFETY_SETTINGS = [
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "OFF" },
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "OFF" },
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "OFF" },
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "OFF" },
{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "OFF" },
];
function normalizeAudioMimeType(format: unknown): string {

View File

@@ -41,7 +41,10 @@ export function claudeToGeminiRequest(model, body, stream, credentials = null) {
model: model,
contents: [],
generationConfig: {},
safetySettings: DEFAULT_SAFETY_SETTINGS,
// Honor an explicit caller-supplied safetySettings (including one that itself
// requests HARM_CATEGORY_CIVIC_INTEGRITY — the caller's explicit choice), matching
// the openai-to-gemini.ts standard-path behavior. See DEFAULT_SAFETY_SETTINGS (#8231).
safetySettings: body.safetySettings || DEFAULT_SAFETY_SETTINGS,
};
// ── Generation config ──────────────────────────────────────────

View File

@@ -33,10 +33,6 @@
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "OFF"
},
{
"category": "HARM_CATEGORY_CIVIC_INTEGRITY",
"threshold": "OFF"
}
]
}

View File

@@ -33,10 +33,6 @@
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "OFF"
},
{
"category": "HARM_CATEGORY_CIVIC_INTEGRITY",
"threshold": "OFF"
}
]
}

View File

@@ -33,10 +33,6 @@
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "OFF"
},
{
"category": "HARM_CATEGORY_CIVIC_INTEGRITY",
"threshold": "OFF"
}
],
"systemInstruction": {

View File

@@ -0,0 +1,68 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { claudeToGeminiRequest } from "../../open-sse/translator/request/claude-to-gemini.ts";
import { openaiToGeminiRequest } from "../../open-sse/translator/request/openai-to-gemini.ts";
// Regression guard for #8231: the standard Gemini API surface (non-Antigravity)
// unconditionally injected HARM_CATEGORY_CIVIC_INTEGRITY into the default
// safetySettings, which upstream dynamic validation rejects for some
// models/endpoints with `safety_settings[4]: element predicate failed` — a hard
// 400 on every request through that model. #5003 already fixed this for the
// Antigravity/Cloud Code surface (open-sse/executors/antigravity.ts); this test
// pins the same behavior for claude-to-gemini.ts and openai-to-gemini.ts.
test("[repro #8231] claude-to-gemini default safetySettings must not force HARM_CATEGORY_CIVIC_INTEGRITY", () => {
const result = claudeToGeminiRequest(
"gemini-2.5-pro",
{ messages: [{ role: "user", content: "hi" }] },
false,
null
);
const categories = (result.safetySettings as Array<{ category: string }>).map(
(s) => s.category
);
assert.equal(
categories.includes("HARM_CATEGORY_CIVIC_INTEGRITY"),
false,
`got ${JSON.stringify(categories)}`
);
});
test("[repro #8231] openai-to-gemini default safetySettings must not force HARM_CATEGORY_CIVIC_INTEGRITY", () => {
const result = openaiToGeminiRequest(
"gemini-2.5-pro",
{ messages: [{ role: "user", content: "hi" }] },
false,
null
);
const categories = (result.safetySettings as Array<{ category: string }>).map(
(s) => s.category
);
assert.equal(
categories.includes("HARM_CATEGORY_CIVIC_INTEGRITY"),
false,
`got ${JSON.stringify(categories)}`
);
});
test("[repro #8231] claude-to-gemini preserves caller-supplied safetySettings that explicitly request HARM_CATEGORY_CIVIC_INTEGRITY", () => {
const explicit = [{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_NONE" }];
const result = claudeToGeminiRequest(
"gemini-2.5-pro",
{ messages: [{ role: "user", content: "hi" }], safetySettings: explicit },
false,
null
);
assert.deepEqual(result.safetySettings, explicit);
});
test("[repro #8231] openai-to-gemini preserves caller-supplied safetySettings that explicitly request HARM_CATEGORY_CIVIC_INTEGRITY", () => {
const explicit = [{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_NONE" }];
const result = openaiToGeminiRequest(
"gemini-2.5-pro",
{ messages: [{ role: "user", content: "hi" }], safetySettings: explicit },
false,
null
);
assert.deepEqual(result.safetySettings, explicit);
});