title: Control sampling and reasoning effort description: Temperature, TopP and Effort: what each does, which models still accept them, and why a refused setting now surfaces at construction rather than in traffic. tags: [how-to, sampling, effort, capabilities]
Control sampling and reasoning effort¶
By default the model generates however the provider's defaults say. Three
Config fields let you influence that: Temperature, TopP and Effort.
They are all nil/empty by default, and leaving them alone is the right choice for most work. Reach for them when you are doing something where variance itself is the problem: extraction against a fixed document, classification you intend to measure, or a judging pass whose consistency you need to defend.
Reduce variance for extraction¶
cfg := chat.Config{
Provider: chat.ProviderGemini,
ResponseSchema: chat.GenerateSchema[Extraction](),
Temperature: ptr(0.0), // sample as narrowly as the provider allows
}
Spend more reasoning on a hard problem¶
What temperature actually does¶
The model produces a score for every token in its vocabulary, and temperature rescales those scores before they become probabilities:
- T below 1 exaggerates the gaps, so likely tokens become likelier and the tail collapses. Output gets focused, and repetitive at the extreme.
- T above 1 compresses the gaps, so unlikely tokens get a real chance. Output gets varied, then incoherent.
- T approaching 0 approaches always taking the top token.
TopP is the sibling control: sort tokens by probability, keep adding until the
cumulative mass reaches p, discard the rest. It truncates the tail rather than
reshaping the curve. Providers recommend adjusting one or the other, not
both, because they interact in ways that are hard to reason about.
Low temperature is not determinism¶
This matters more than anything else on this page.
Even at T=0 the same prompt can produce different output across runs.
Floating-point addition is not associative, so a request batched alongside
different traffic can reduce the same scores in a different order and differ in
the last bits. Where two candidate tokens are nearly tied that is enough to flip
which one wins, and a single flipped token early in a response changes
everything after it.
So pinning the sampler narrows the distribution you are drawing from. It does not collapse it to a point.
That still buys you something specific and useful. It separates two causes of instability that otherwise look identical:
- Sampling variance. The model was genuinely uncertain and the sampler chose differently each time. Lowering temperature shrinks this.
- Genuine ambiguity. Your prompt or your document does not determine one answer. Lowering temperature does not fix this. It makes the same near-tie land more consistently on whichever side is marginally ahead, which looks like a fix while hiding the real problem.
If output is still unstable at T=0, the prompt is the thing to change.
What effort does¶
Effort selects how much reasoning the model does before answering. It is an
ordinal ladder:
chat.EffortLow // least the provider will accept
chat.EffortMedium
chat.EffortHigh
chat.EffortXHigh
chat.EffortMax // most the provider will accept
An ordinal is genuinely portable in a way a number is not: low means the same
thing on every provider, whereas 0.5 means different things on a 0–1 scale and
a 0–2 one.
Effort is the most direct cost dial in Config. Reasoning tokens are
billed, and Max on a reasoning model can multiply output substantially. Watch
it with the field that already reports it:
Measure rather than assume. The multiplier varies by provider, by model, and by how hard your prompt actually is.
Which providers take which¶
| Provider | Temperature / TopP | Effort |
|---|---|---|
gemini |
✅ 0–2 / 0–1 | ✅ xhigh and max clamp to its highest level |
openai |
⚠️ 0–2 / 0–1, model-dependent | ✅ all five |
claude |
⚠️ 0–1, model-dependent | ⚠️ model-dependent |
claude-local |
❌ never | ✅ all five |
None of the default models accepts temperature or top-p
The defaults are claude-opus-5, gpt-5.6-sol and gemini-3.7-flash, and
only Gemini's still takes sampling controls. claude-opus-5 deprecates
temperature; gpt-5.6-sol permits only its default of 1 and rejects top-p
outright, where the previous default gpt-5.4 accepted both.
So if you set Config.Temperature or Config.TopP without also setting
Config.Model, chat.New reports it: the setting is dropped and named in
the returned error, and you still get a working client. Use Config.Effort
instead (it is supported everywhere and is what these models expect to be
steered with) or name an older model explicitly.
Support is per model, not per provider¶
The awkward part, and worth understanding before you hit it.
Whether a model accepts these controls is a property of the model, not the provider. Two models from the same vendor can differ completely:
claude-sonnet-4-5accepts temperature and rejects effortclaude-opus-5accepts effort and rejects temperaturegpt-5.4accepts temperature and top-p;gpt-5.6-solrejects both
Vendors are replacing sampling controls with effort controls on reasoning models. It is tempting to read that as a clean generational cutover you could predict from a version number, but the measured OpenAI data says otherwise:
| Model | Temperature / TopP |
|---|---|
gpt-5, gpt-5-mini, gpt-5-nano |
❌ |
gpt-5.1 … gpt-5.4-pro |
✅ |
gpt-5.5 |
❌ |
gpt-5.5-pro |
✅ |
gpt-5.6-sol / -terra / -luna |
❌ |
Support goes away, comes back for three generations, then goes away again, and
gpt-5.5-pro accepts what plain gpt-5.5 refuses. There is no rule here to
infer, which is why the module measures each model rather than guessing from its
name. So there are two different failures, and they want different fixes:
Construction error: the provider has no such concept at all.
_, err := chat.New(ctx, chat.Settings{Config: chat.Config{
Provider: chat.ProviderClaudeLocal,
Temperature: ptr(0.2),
}})
// error: provider claude-local cannot carry Config.Temperature or Config.TopP
The claude CLI has no temperature flag at any version, so this can never work. Fix: change provider, or drop the field.
Construction error: this model is known not to accept it.
Each provider module ships a generated table of what its models accept. When
that table says no for the model you selected, chat.New drops the setting and
tells you, rather than letting the request fail later:
client, err := chat.New(ctx, chat.Settings{Config: chat.Config{
Provider: chat.ProviderOpenAI,
Temperature: ptr(0.2),
}})
// client is non-nil and usable; Temperature was not applied
// error: model gpt-5.6-sol (the openai default) does not accept Config.Temperature
You still get a client. Only the unsupported setting is dropped; everything else you configured is applied. Inspect what went, and act on it or don't:
var cfgErr *chat.ConfigErrors
if errors.As(err, &cfgErr) {
for _, d := range cfgErr.Dropped() {
log.Warn("setting not applied", "fields", d.Fields, "why", d.Reason)
}
}
Request error: the model is not in the table.
A model too new to have been measured reports unknown, not no. The module does not guess: it applies your setting and lets the provider answer.
_, err := client.Chat(ctx, "…")
if errors.Is(err, chat.ErrModelRejectedParameter) {
// this model will not take the value that was set
}
Fix: change model, or drop the field. The underlying provider error is still reachable by unwrapping if you want the vendor's own wording.
That split is what keeps a stale table from doing harm. A table that is merely behind costs you nothing. An unmeasured model behaves exactly as it did before the tables existed. Only a confident no changes anything.
Nothing is dropped silently. A setting is either applied, or it is named in
the error chat.New returns. A control that is silently ignored is
indistinguishable from one that does nothing, and you would have no way to tell
which you were looking at.
Related¶
- Check what a model supports: ask before you set it, rather than finding out at construction.
- Process a batch of documents: the other half of making batch work measurable.
- Configuration fields: the exact defaults and failure modes of these three fields.
- Defaults and limits: the five effort levels and where they clamp.