Summary
Under responseMimeType: "application/json" + responseSchema, gemini-3.7-flash
intermittently enters a decode loop on prompts containing many near-identical
items: it emits a valid JSON prefix, then an integer field’s digits repeat
(...“score”: 20000000000000...) endlessly until maxOutputTokens
(finishReason: MAX_TOKENS, truncated/unparseable JSON). Without an explicit
cap it runs to the model’s 65,536 default on every occurrence.
The identical request never degenerates on gemini-3-flash-preview.
Environment
- Model:
gemini-3.7-flash(fails) vsgemini-3-flash-preview(clean) - API: Gemini Developer API (generativelanguage), paid tier
- SDK:
@google/genai1.37.0, Node.js 20 - Config:
responseMimeType: application/json,responseSchema(array of
{id: STRING, score: INTEGER, comment: STRING}with min/maxItems),
thinkingConfig: { thinkingLevel: LOW }
Reproduction
Attached standalone script (repro.js, fully synthetic data): 10 grading items
that differ only in a decimal value, schema-constrained JSON output.
gemini-3.7-flash: ~1 in 3 runs endsMAX_TOKENSwith a tail of repeated
zeros (measured 33% on the synthetic payload; up to 100% per attempt on
equivalent production payloads).gemini-3-flash-preview: 0 degenerate runs (dozens attempted).temperature: 0does NOT avoid it — the zero-loop appears to be a
deterministic attractor for some inputs.
Expected: STOP with ~400 output tokens, as gemini-3-flash-preview produces.
Impact
Production education workload (grading student papers, JSON verdicts): 241
runaway calls across 3 days, each billed to the 65,536-token default cap
(~US$60 of unwanted output tokens) before we deployed maxOutputTokens caps,
per-chunk retries, and a fallback to gemini-3-flash-preview. The failure rate
made several papers temporarily ungradable (every retry re-entered the loop on
the same content).
Secondary observations on the same model/API (can file separately if preferred)
- Intermittent bare
400 INVALID_ARGUMENT(no detail) on well-formed
generation requests — 3–10/day across our traffic; the identical request
succeeds on immediate reissue. - Rare
200responses with an empty candidate (no text parts,
finishReason: STOP) on requests that normally return full JSON.
Ask
Is the constrained-decoding repetition loop a known issue in the 3.7-flash
decoder, and is a fix planned? Happy to provide more failing payloads or run
canary tests.
Standalone reproduction script
// Minimal reproduction: gemini-3.7-flash degenerates into repeated "0" digits
// until maxOutputTokens under schema-constrained JSON decoding when the prompt
// contains many near-identical items. gemini-3-flash-preview never does on the
// identical request. Fully synthetic data — safe to share.
//
// Usage: GEMINI_API_KEY=<key> node _gemini-37-degeneration-repro.js [trials]
// Expected: roughly 1 in 3 gemini-3.7-flash trials ends with
// finishReason=MAX_TOKENS and a tail of repeated zeros; all
// gemini-3-flash-preview trials end STOP with ~400 output tokens.
// temperature: 0 does NOT avoid the failure mode.
const { GoogleGenAI, ThinkingLevel, Type } = require('@google/genai')
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY })
const TRIALS = Number(process.argv[2] || 5)
// 10 near-identical grading items differing only in decimal values — the
// repetitive-input shape that triggers the decode loop.
const items = [18.9, 23.8, 7.25, 6.04, 12.5, 3.75, 9.2, 15.6, 4.85, 21.4].map((v, i) => ({
id: 'q' + (i + 1),
totalScore: 2,
question:
'<p>Convert the following decimal into the simplest fraction.<br />\\(' +
v +
' =\\) __________</p>',
studentAns: '<p><strong>' + v * 20 + '/20</strong></p>',
}))
const PROMPT =
'You are an AI that checks the answers of students in a practice paper. ' +
'For each question return a score between 0 and totalScore, and a concise ' +
'HTML comment (use MathJax \\( \\) for math) explaining the score. ' +
'The comment must be non-empty.'
const schema = {
type: Type.ARRAY,
minItems: 10,
maxItems: 10,
items: {
type: Type.OBJECT,
properties: {
id: { type: Type.STRING },
score: { type: Type.INTEGER },
comment: { type: Type.STRING },
},
},
}
const runTrial = async (model) => {
const r = await ai.models.generateContent({
model,
contents: [PROMPT, 'Questions and answers:\n' + JSON.stringify(items)],
config: {
responseMimeType: 'application/json',
responseSchema: schema,
// Cap only bounds the damage — without it the runaway emits zeros all
// the way to the model's 65,536 default.
maxOutputTokens: 8192,
thinkingConfig: { thinkingLevel: ThinkingLevel.LOW },
},
})
const fin = r.candidates[0].finishReason
const out = (r.usageMetadata && r.usageMetadata.candidatesTokenCount) || 0
const tail = String(r.text || '').slice(-40)
return { fin, out, tail }
}
;(async () => {
for (const model of ['gemini-3.7-flash', 'gemini-3-flash-preview']) {
let degenerate = 0
for (let t = 1; t <= TRIALS; t++) {
const { fin, out, tail } = await runTrial(model)
const bad = fin === 'MAX_TOKENS'
if (bad) degenerate++
console.log(
model +
' trial ' +
t +
': ' +
fin +
' outputTokens=' +
out +
(bad ? ' tail=' + JSON.stringify(tail) : '')
)
}
console.log('=> ' + model + ': ' + degenerate + '/' + TRIALS + ' degenerate runs\n')
}
process.exit(0)
})().catch((e) => {
console.error(e)
process.exit(1)
})