Skip to content

Mapping Seed and Determinism Parameters Between APIs

9 min read · updated August 11, 2026

Renaming seed to random_seed takes a minute. The part of the migration that takes a week is discovering that the guarantee you thought you were porting was never documented as a guarantee anywhere.

What a seed parameter actually fixes

Sampling a token from the model’s output distribution needs a source of randomness. A seed pins that source, so that two requests with the same distribution at every step draw the same tokens. That is the whole of what a seed controls: the draw, not the distribution.

Everything that shapes the distribution is outside its reach — the weights, the numerical details of how the forward pass was computed, the prompt, the sampling parameters. If any of those differs between two runs, the distributions differ, and identical seeds sample different tokens from them. This is why every provider that offers a seed describes it in hedged language. OpenAI’s documentation frames it as a best-effort mechanism rather than a promise, and Cohere’s describes the backend as making a best effort to sample deterministically. Neither is being evasive; the hedge is accurate.

How each API spells it

  • OpenAI. An integer seed on the request. The response carries system_fingerprint, a string identifying the backend configuration the request was served with, so that a change in output can be attributed to a change on their side rather than yours. The library covers it in the system fingerprint page and the parameter itself in the seed parameter page.
  • Mistral. The same idea under the name random_seed. A pure rename in the adapter, with no fingerprint equivalent to key on.
  • Cohere. A seed integer on the chat request, documented as best-effort determinism for repeated requests with the same seed and parameters.
  • Google Gemini. A seed field inside the generation config object rather than at the top level of the request, alongside temperature and the other sampling controls.
  • Anthropic. No seed parameter is documented on the Messages API. There is nothing to map to, and no shim recovers it — see the section below.
Parameter surfaces move, and a provider adding or removing a seed is exactly the kind of change that ships without an announcement. Treat this list as the state at the time of writing and confirm against the request schema for the specific model you are calling.

The rename is the easy half and an adapter handles it in a few lines. What an adapter cannot do is make the two behave the same, because the providers are not promising the same thing — and none of them is promising very much.

The other seven things that must be identical

A seed only helps if everything upstream of the draw is byte-identical. In practice a reproducibility failure is almost never the seed; it is one of these:

  1. The model string. Not the alias. An alias that resolves to “the current version” moves under you, and the day it moves your seeded output changes with no other cause. Pin a dated or versioned identifier everywhere reproducibility matters.
  2. Temperature and top-p. Both, explicitly. Provider defaults differ, and a request that omits temperature is not requesting the same thing on two providers.
  3. The complete prompt, byte for byte. Including trailing whitespace, including the order of keys in any serialised JSON you interpolate, including a timestamp somebody put in the system prompt.
  4. The system prompt’s placement. Moving it from a message in the array to a top-level parameter changes the input, even when the text is identical.
  5. Tool definitions. They are part of the input. A reordered tools array or a regenerated JSON Schema with keys in a different order is a different prompt.
  6. The maximum-output parameter. Which is also renamed on some surfaces — max_tokens against max_completion_tokens against max_output_tokens — and a mismatch changes where generation stops.
  7. The backend configuration. The one you do not control. Where a fingerprint is exposed, log it with every response; where it is not, you have no way to distinguish a provider-side change from a bug in your own code, which is itself a reason to prefer the surface that exposes one.

Measuring what the seed is worth

Rather than trusting either the documentation or a hunch, measure the repeat rate on your own prompts. The procedure is short and it produces a number you can put in a decision:

import hashlib, collections

def repeat_rate(call, prompt, n=20, seed=7):
    hashes = collections.Counter()
    fps = collections.Counter()
    for _ in range(n):
        out, fp = call(prompt, seed=seed, temperature=0)
        hashes[hashlib.sha256(out.encode()).hexdigest()] += 1
        fps[fp] += 1
    top = hashes.most_common(1)[0][1]
    return {"distinct": len(hashes), "modal_share": top / n, "fingerprints": dict(fps)}

Run it on twenty or so prompts that are representative of your traffic, not on one short prompt — long outputs diverge more often than short ones, because a single differing token early changes every token after it. Record the fingerprint distribution alongside the hashes: if the fingerprints differ within a run, the provider changed backends mid-experiment and the result says nothing about the seed.

Do this on the source provider before the migration and on the target after. The comparison you want is not “is the target deterministic” but “is the target meaningfully worse than what we already had”, and the answer is frequently that the source was not as reproducible as the team believed either. That result is useful: it means the tests that are about to start failing were already fragile, and the fix belongs in the tests. The library’s treatment of that fragility is temperature zero and nondeterminism, which is the argument not to repeat here.

When the target has no seed at all

If the target provider accepts no seed, you have lost a variance reducer, not a correctness mechanism. Three things partially replace it, in descending order of how much they actually help.

Constrain the output. A schema with enums and fixed fields removes most of the space in which two runs can differ. Two responses that must both be one of three labels agree far more often than two paragraphs of prose, without any seed involved. This is the replacement that does the most work and it is available everywhere.

Cache deterministically. For fixtures, evaluation runs and demonstrations, the reproducible artefact can be a recorded response keyed by a hash of the full request. That gives exact reproduction, which is more than any seed offers, at the cost of not exercising the model. It is the right answer for a test suite and the wrong one for an evaluation of the model itself.

Assert on properties instead of bytes. The tests that break without a seed are the ones comparing output strings. Rewriting them to assert structure, extracted values, or a similarity threshold costs a day and removes the dependency permanently. The library’s fallback for providers with no seed parameter covers that pattern, and the page on seeds that stop reproducing after a migration covers the case where the parameter exists on both sides and still does not carry.