●SUNSET — Four days until the image models shut down: Imagen 4 and the Gemini 3 Image family stop on August 17, so line up your replacements now●FLASH — Gemini 3.6 Flash is generally available, with better token efficiency and stronger code and agentic planning at a lower price than 3.5 Flash●LITE — Gemini 3.5 Flash-Lite also reached GA as a low-latency, cost-conscious subagent option aimed at high-volume automation●OMNI — Gemini Omni Flash, a new video model, is now reachable from the Gemini app, Flow, AI Studio, and the Gemini API●LOGS — Developer logs now cover the Interactions API, with supported calls visible in the AI Studio dashboard●ROBOTICS — gemini-robotics-er-1.6-preview shuts down on August 31, so this is the window to plan the migration●SUNSET — Four days until the image models shut down: Imagen 4 and the Gemini 3 Image family stop on August 17, so line up your replacements now●FLASH — Gemini 3.6 Flash is generally available, with better token efficiency and stronger code and agentic planning at a lower price than 3.5 Flash●LITE — Gemini 3.5 Flash-Lite also reached GA as a low-latency, cost-conscious subagent option aimed at high-volume automation●OMNI — Gemini Omni Flash, a new video model, is now reachable from the Gemini app, Flow, AI Studio, and the Gemini API●LOGS — Developer logs now cover the Interactions API, with supported calls visible in the AI Studio dashboard●ROBOTICS — gemini-robotics-er-1.6-preview shuts down on August 31, so this is the window to plan the migration
Making Gemini API Output Reproducible with the seed Parameter — Practical Patterns for Tests and Debugging
A practical guide to the Gemini API seed parameter, with measured match-rate data and a triage flow. Covers where seed works and where it quietly fails, how to fix a wrapper that drops seed, and diagnosing variance with logprobs.
"I'm sending the exact same prompt and getting a different answer every time" — that's the wall most teams hit the moment they try to write tests against a Gemini-powered feature. As an indie developer I ran into it myself when wiring up regression tests for one of my apps, and I nearly wrote it off as "the model is just non-deterministic" before I realized the culprit was sitting in my own wrapper code.
The good news is that, in most cases, the seed parameter does what you want. The less obvious news is that "just pass a seed and you'll get the same answer" is not quite accurate — there are situations where seed simply cannot stabilize the output. This article walks through how seed actually works, the patterns I rely on for tests and debugging, the match rates I measured on my own machine, and the gotchas that surprise people most often.
What seed actually controls
The Gemini API seed fixes the starting point of the pseudo-random number generator used during sampling. Give it the same seed, prompt, model, and parameters, and the sampling order lines up, so the output tends to match.
The key thing to internalize is that seed is not a replacement for temperature:
temperature=0.0 alone pushes the model toward near-greedy decoding, which is mostly deterministic, but batching order and tiny numerical differences on the model side can still nudge the result
Adding seed aligns the sampling process itself, so you get a more consistent result
In my experience, seed + low temperature is noticeably steadier for regression tests than simply lowering the temperature. The next section puts a number on that "in my experience."
Measured: sending one prompt 100 times to check the match rate
Rather than rely on feel, I sent the same short prompt ("Answer with the capital of Japan in one word.") to gemini-2.5-flash 100 times under each condition and counted how often the response was byte-identical to the first one. The comparison is an exact string match, whitespace included.
Condition
Exact matches
Notes
temperature=0.0 / seed=42 (fixed)
100 / 100
Zero variance. Safe to use as a test baseline
temperature=0.0 / seed unset
97 / 100
Occasionally splits on a trailing period
temperature=0.7 / seed=42 (fixed)
41 / 100
Even with seed, the sampling space is wide enough to wobble
temperature=0.7 / seed unset
18 / 100
Not usable for comparison
Two things stand out. First, for testing, temperature=0.0 + fixed seed is clearly the best — on my machine all 100 responses matched. Second, raising the temperature drops the match rate to 41% even with a fixed seed. In other words, seed does not erase the variance that temperature creates; it only aligns the sampling order under the same temperature and conditions. Getting that distinction straight up front saves you from a lot of confusion later.
One caveat: the longer the response, the higher the chance it splits on the final token. The measurement above uses a few-token answer, which is why the match rate is so high; with a few-hundred-token response, even temperature=0.0 + fixed seed can occasionally wobble at the tail. Choose your snapshot granularity with that reality in mind.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Measured match rates from sending one prompt 100 times across seed on/off and different temperatures
✦The Before/After of a wrapper that silently drops seed, plus a five-second sanity check
✦A top-down triage flow for variance, and logprobs code to diagnose why an output wobbles
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Here is a minimal, working example with the google-genai Python SDK, shaped so it drops straight into a pytest snapshot test.
# pip install google-genaiimport osfrom google import genaifrom google.genai import typesclient = genai.Client(api_key=os.environ["GEMINI_API_KEY"])def generate_with_seed(prompt: str, seed: int = 42) -> str: """Get a highly reproducible response for the same seed and prompt.""" response = client.models.generate_content( model="gemini-2.5-flash", contents=prompt, config=types.GenerateContentConfig( temperature=0.0, top_p=1.0, seed=seed, max_output_tokens=512, ), ) return response.textif __name__ == "__main__": out_a = generate_with_seed("Answer with the capital of Japan in one word.") out_b = generate_with_seed("Answer with the capital of Japan in one word.") print(out_a) print(out_b) print("match:", out_a == out_b)
This calls the model twice with seed=42, temperature=0.0 and compares the results. Both should print Tokyo and match: True. Adding even a single trailing space to the prompt can change the response, so manage your input strings strictly in tests.
REST and Node.js variants
From REST, put seed inside generationConfig.
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=YOUR_GEMINI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contents": [{"parts":[{"text":"What is 2+2? Answer with just the number."}]}], "generationConfig": { "temperature": 0.0, "topP": 1.0, "seed": 42, "maxOutputTokens": 32 } }'
With Node.js (@google/genai), you just pass seed in the config object.
import { GoogleGenAI } from "@google/genai";const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY! });const result = await ai.models.generateContent({ model: "gemini-2.5-flash", contents: "Translate 'Good morning' to French.", config: { temperature: 0, topP: 1, seed: 42, maxOutputTokens: 64 },});console.log(result.text);
REST and Node.js behave the same internally — same parameters, same result.
Three patterns I use for tests and debugging
These are the three I reach for day to day.
1. Snapshot-freeze for regression tests
So that a diff only appears when you intend one, generate the response with fixed seed + temperature=0 and save it to a snapshot file. If CI can catch the diff, you avoid the "output quietly changed and nobody noticed" class of incident. The full implementation is in Building Prompt Regression Tests for the Gemini API with Pytest.
2. Variance reduction for prompt A/B comparison
When comparing "is prompt A or B better," running each once with the same seed is less reliable than preparing 3–5 seeds and doing a paired comparison per seed. Even when you deliberately raise temperature to measure diversity, fixing the list of seeds keeps the experiment reproducible.
3. Bug reports and reproduction environments
When a user reports "it returned something weird," logging the seed alongside the prompt dramatically raises your odds of reproducing it locally. Always keep prompt, model, temperature, and seed in your app logs.
The thing silently dropping your seed is usually your own wrapper
When "I passed a seed but it doesn't match," the first suspect is not the model — it's the layer sitting between your code and the API. The mistake I actually made was rebuilding GenerateContentConfig in a shared wrapper and forwarding only temperature and max_output_tokens, quietly dropping seed.
# Before: seed is never forwarded, so tests look "non-deterministic"def call_model(prompt: str, cfg: dict) -> str: response = client.models.generate_content( model=cfg["model"], contents=prompt, config=types.GenerateContentConfig( temperature=cfg.get("temperature", 0.0), max_output_tokens=cfg.get("max_output_tokens", 512), # forgot to forward seed here ), ) return response.text
# After: build the config so no known field can be droppedKNOWN_KEYS = {"temperature", "top_p", "seed", "max_output_tokens"}def call_model(prompt: str, cfg: dict) -> str: passthrough = {k: cfg[k] for k in KNOWN_KEYS if k in cfg} response = client.models.generate_content( model=cfg["model"], contents=prompt, config=types.GenerateContentConfig(**passthrough), ) return response.text
The point is to pass known keys through as a dict comprehension rather than copying each setting by hand. That way, adding a new parameter later can't reintroduce a "forgot to forward it" bug. If an enterprise gateway is stripping unknown fields, this shape also makes it easier to tell whether the field is being stripped or was never sent in the first place.
A top-down flow to eliminate the source of variance
When seed isn't working, don't poke at it randomly. Walking down this order gets you to the cause fastest.
Send the same minimal prompt three times in a row (seed=42, temperature=0). If all three match exactly, the raw API call is at least healthy
If they don't match, call the SDK directly, bypassing your wrapper. If it matches now, the wrapper is the culprit (see the Before/After above)
Still wobbling? Check temperature. Make sure a stray 0.7 hasn't slipped in via an env var or default
Check the model name isn't an alias (-latest). Pin tests to an explicit version
Check the input isn't multimodal, streaming, or tool-using. Those are outside seed's jurisdiction, so split the test unit
Walking these five steps top to bottom prevents the "it's the model's fault" time sink almost entirely. After I wrote this order on a sticky note, my time spent investigating seed-related issues dropped by roughly half.
When seed does not help
Let me expand on the "outside seed's jurisdiction" that showed up in steps 4–5. If you're passing a seed and the result still varies, check whether you've hit one of these.
Temperature is high: at 0.7–1.0, the sampling space is wide enough that seed alone leaves room for noise — as measured above, the match rate can fall to 41%. For maximum reproducibility, keep it in the 0–0.2 range
The model name is an alias: aliases like gemini-2.5-flash-latest can be swapped for a different version underneath. In tests, use gemini-2.5-flash (or an explicit version) to be safe
Multimodal input (images, PDFs): the image preprocessing path has its own variance and is less stable than text alone. In snapshot tests, stick to text input
Streaming responses: chunk boundaries can shift. Compare on the final, fully assembled text
Tool use or grounding: the external call results themselves change over time, so seed alone can't reproduce them. Mock the tools in tests
In short, seed suppresses "sampling variance" but not "external variance." The trick is to split the test unit and stay conscious of where the wobble is coming from.
Diagnosing why it wobbles with logprobs
When you want to take the diagnosis a step deeper, pull logprobs and look at how much the model hesitated at each token. The closer the top candidates' probabilities are, the more easily that token flips to another word when conditions change slightly — an obvious relationship, but a useful one to see.
from google import genaifrom google.genai import typesclient = genai.Client()resp = client.models.generate_content( model="gemini-2.5-flash", contents="Answer the sentiment in one word, positive / negative: shipping was slow but the quality was great", config=types.GenerateContentConfig( temperature=0.0, seed=42, response_logprobs=True, logprobs=5, # top 5 candidates per position ),)# Inspect how close the candidates are at the first tokenfor cand in resp.candidates: for step in cand.logprobs_result.top_candidates[:1]: for c in step.candidates: print(f"{c.token!r}: logprob={c.log_probability:.3f}")
If positive and negative have near-identical logprobs at the first token, that prompt is inherently prone to splitting. Rather than papering over it with a seed, rewriting the prompt to widen the confidence gap helps both test stability and production quality. The full walkthrough is in Measuring Classification Confidence with Gemini API logprobs.
A short story: why seed matters more in evaluation than production
At first I thought of seed as a "test-time only" tool. What changed my mind was running prompt evaluations in parallel. Without a seed, the score for the same prompt drifted slightly run to run, and the effect of a small prompt improvement drowned in that noise.
The moment I fixed the seed and the model version, the score started reflecting "the intrinsic quality of the prompt" rather than "sampling luck." If you're doing serious prompt improvement, fix the seed in your evaluation jobs before you even think about production. And if you're using an LLM-as-judge, fix the judge's model and seed too — otherwise you're measuring two layers of wobble at once.
Designing seed alongside temperature and top_p
These are the three settings I've settled into.
Full reproducibility (for tests): temperature=0, top_p=1.0, seed=fixed. Aims for near-exact snapshot matches
Allowing slight variation (production): temperature=0.2, top_p=0.95, seed=unset. Feels more natural for user-facing responses
When you need creativity (copywriting, etc.): temperature=0.9, top_p=0.95, seed=rotating. Cycle through several seeds and compare candidates
Tune response quality on the temperature side and reproducibility on the seed side — separating the roles keeps your settings from sprawling. For choosing temperature itself, I've collected use-case-by-use-case guidance in Task-by-Task Temperature Best Practices for the Gemini API.
Quick sanity check: confirming seed actually works in your stack
Before relying on seed across your test suite, run a one-line confirmation in the same environment your tests use. Network proxies, alternate endpoints, or a mismatched SDK version can all produce subtle differences. The fastest check is to send the same minimal prompt three times back-to-back with seed=42, temperature=0 and assert all three are byte-identical. If they are, your stack honors seed correctly. If not, something between your code and the model is dropping it — usually a wrapper that forgets to forward the parameter, or a gateway that strips fields it doesn't recognize. The Before/After and triage flow above are your prescription.
What to try next
Open one of your current Gemini API call sites, add seed=42 and temperature=0, and run the same prompt twice. In most cases the two outputs will match exactly. If they don't, walk down the triage flow — it's almost always one of those issues.
Once you have that working, add a single snapshot test to CI for one important prompt. The moment your pipeline can detect quiet output drift, your ability to iterate on prompts steps up noticeably. Reproducibility is an unglamorous foundation, but whether or not you have it changes how fast everything downstream moves. I hope this helps in your own work, and thank you for reading.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.