title: Configuration fields description: Every chat.Config and chat.Settings field: what it is, what it defaults to when you leave it alone, and what happens when the value cannot be applied. tags: [reference, configuration, defaults, config]
Configuration fields¶
Every field of chat.Config and chat.Settings, what it defaults to when left
alone, and what happens when the value cannot be applied. chat.Config is the
whole of the module's configuration surface: the module reads no config file and
has no flags of its own, because a host application owns config schema and maps
it into these fields.
Only Provider is meaningful on its own, and even that defaults. Every other
field has a working zero value.
How to read the "if it cannot be applied" column¶
chat.New classifies a problem into one of three outcomes, and the tables below
name which one each field produces:
| Outcome | client |
err |
Meaning |
|---|---|---|---|
| Applied | usable | nil |
the value reached the provider |
| Dropped | usable | non-nil | the value was not applied, the provider's own default is used, and the error names the field |
| Fatal | nil |
non-nil, wraps chat.ErrUnableToConstruct |
nothing was built |
A dropped setting is never clamped or approximated. Temperature: 3.0
samples at the provider's default, not at 2.0. The split between dropped and
fatal is ownership, not severity: a parameter the module forwards to a vendor
degrades, and a feature the module implements itself is fatal. See
Handle a partially-applied configuration
for the code that tells the three apart.
Provider and model fields¶
| Field | Type | Default when unset | If it cannot be applied |
|---|---|---|---|
Provider |
Provider |
the AI_PROVIDER environment variable, then ProviderClaude |
Fatal. A name with no registered factory yields unsupported provider: <name>, which usually means the provider module's blank import is missing |
Model |
string |
the provider's own default (claude-opus-5, gpt-5.6-sol, gemini-3.7-flash); the claude CLI's own default for claude-local; none for openai-compatible |
Fatal when empty on ProviderOpenAICompatible, which has no default to fall back on. Otherwise the name is not validated here: a model the capability table knows refuses a generation control causes that control to be dropped, and a name the vendor does not recognise fails at request time |
BaseURL |
string |
empty (each provider uses its vendor endpoint) | Fatal. A URL failing any of the seven endpoint rules yields chat.ErrInvalidBaseURL. Empty is also fatal for ProviderOpenAICompatible, which requires it |
Project |
string |
empty, then GOOGLE_CLOUD_PROJECT |
Addressing for ProviderGeminiVertex, which is fatal when both are empty. Ignored by every other provider |
Location |
string |
empty, then GOOGLE_CLOUD_LOCATION |
Addressing for ProviderGeminiVertex, which is fatal when both are empty. Named for the concept, so a future region-addressed provider reuses it |
SystemPrompt |
string |
empty, and no system instruction is sent | Applied by every provider; there is no failure mode |
ProviderOpenAICompatible is the one provider with two required fields:
BaseURL (no default endpoint exists) and Model (model names are
backend-specific, so the module has no default to offer).
Credential fields¶
| Field | Type | Default when unset | If it cannot be applied |
|---|---|---|---|
Token |
string |
resolution falls through to Credentials, then the provider's well-known environment variable |
Fatal when the whole cascade resolves to nothing: each API provider refuses to construct, with Anthropic API key is required but not provided, OpenAI token is required but not provided or Gemini API key is required but not provided. A key that resolves but is wrong is not detected here and surfaces as the vendor's authentication error on the first call |
Credentials.Env |
string |
unused | Skipped when the named variable is unset or empty; resolution continues down the cascade |
Credentials.Keychain |
string |
unused | Skipped when Credentials.Lookup is nil, the reference is not service/account shaped, or the lookup errors; resolution continues down the cascade |
Credentials.Key |
string |
unused | Used verbatim; this is the legacy literal-in-config path |
Credentials.Lookup |
KeychainLookup |
nil, so the keychain step is skipped | Never decoded from config (mapstructure:"-"); a host injects it |
The five sources are tried in order and the first non-empty one wins:
Token → Credentials.Env → Credentials.Keychain → Credentials.Key →
the provider's well-known variable. Every value is whitespace-trimmed, so a
half-configured source cannot mask a working one below it. ProviderClaudeLocal
uses none of this, because the claude binary carries its own authentication.
Structured-output fields¶
| Field | Type | Default when unset | If it cannot be applied |
|---|---|---|---|
ResponseSchema |
*jsonschema.Schema |
nil (Ask unmarshals the model's raw text into the target, which must then be a *string or implement json.Unmarshaler) |
Not checked at construction. There is no marker interface for structured output, so a provider that ignores the field does so silently, and see What this module does not do |
SchemaName |
string |
empty, and Claude then names its structured-output tool submit_response |
Provider-owned. Claude uses it as the tool name, OpenAI sends it as the JSON-schema name, Gemini ignores it |
SchemaDescription |
string |
empty | Provider-owned. Claude and OpenAI attach it to the schema; Gemini ignores it |
Build a schema with chat.GenerateSchema[T](), which returns
*jsonschema.Schema directly (assignable to both Config.ResponseSchema and
Tool.Parameters with no type assertion. It returned any before v0.8.0; code
carrying a .(*jsonschema.Schema) assertion no longer compiles.
Generation-control fields¶
| Field | Type | Default when unset | If it cannot be applied |
|---|---|---|---|
Temperature |
*float64 |
nil (the provider samples at its own default) | Dropped when outside 0–2, or when the selected model reports SupportNo. Fatal when the provider has no sampling concept at all (claude-local). chat.ErrModelRejectedParameter at request time when the model was not in the capability table |
TopP |
*float64 |
nil, and the provider samples at its own default | Dropped when outside 0–1, or when the model reports SupportNo. Fatal on a provider with no sampling concept |
Effort |
Effort |
empty, so the provider's own reasoning default | Dropped when the value is not one of low, medium, high, xhigh, max, or when the model reports SupportNo. Fatal when the provider module does not implement chat.EffortCapable |
Config.Seed was removed in v0.10.0
It was OpenAI-only, and the core never read it. Config carried it purely
as a pass-through. Set a seed through chat-openai's own option instead.
See spec 0011 D10.
The 0–2 bound on Temperature is the module's outer limit, wider than any one
provider, because provider ranges differ (Claude 0–1, OpenAI and Gemini 0–2)
and the adapters narrow it further. A value inside 0–2 but outside the selected
provider's range is refused by the adapter, not here.
Tool-loop fields¶
| Field | Type | Default when unset | If it cannot be applied |
|---|---|---|---|
MaxSteps |
int |
chat.DefaultMaxSteps (20) ReAct iterations per Chat/StreamChat call |
A run that exhausts the cap fails, with <Provider> reached maximum ReAct steps (20) without a final answer (no partial text is returned, and any streamed deltas have already reached your callback) |
ParallelTools |
bool |
false, and tool calls in one step run sequentially |
Has no effect on claude-local, which refuses tools outright |
MaxParallelTools |
int |
5 when zero or negative; effective only while ParallelTools is true |
No failure mode |
Parallel execution engages only when a single step returns more than one call. A lone call always takes the sequential path.
Transport and timeout fields¶
| Field | Type | Default when unset | If it cannot be applied |
|---|---|---|---|
RequestTimeout |
time.Duration |
chat.DefaultChatRequestTimeout (5 minutes) per request |
Ignored entirely when HTTPClient is set. An injected client keeps its own timeouts. Also unused by claude-local, whose subprocess is bounded only by the context you pass |
HTTPClient |
*http.Client |
nil, so the module builds a plain stdlib client whose overall and response-header timeouts are RequestTimeout |
Used verbatim when non-nil; the module neither wraps nor tunes it |
MaxTokens |
int |
the provider's default (4096 on OpenAI, 8192 on Claude and Gemini) | Provider-owned; the core never reads it, and claude-local passes no token cap to the CLI |
MaxRetries |
*int |
nil, and the provider's own default, which is 2 on Claude and OpenAI and matched by Gemini | 0 disables retry, which is the setting that makes a fallback composite advance on the first throttle rather than waiting the primary out |
Nothing bounds a retry sequence as a whole. RequestTimeout bounds one request,
so a high MaxRetries against a provider returning long Retry-After hints can
run for a while, so cancel the context to bound it.
Caching fields¶
| Field | Type | Default when unset | If it cannot be applied |
|---|---|---|---|
CacheTTL |
time.Duration |
zero, so the provider's own retention default, which is 5 minutes on Claude | Fatal when the provider module does not implement chat.CachingChatClient, with a hint naming the upgrade. Providers pick the nearest retention they offer rather than honouring an arbitrary duration |
Setting CacheTTL does not cache anything on its own. Content is cached only
when it is added through CachingChatClient.AddCached, and every provider
silently declines to cache blocks below a per-model minimum. Confirm with
Usage().CachedTokens.
Conversation-mode fields¶
| Field | Type | Default when unset | If it cannot be applied |
|---|---|---|---|
Stateless |
bool |
false (the client is a conversation, and every call re-sends the history) |
Fatal when the provider module does not implement chat.StatelessCapable. This is deliberately not a drop: a silently ignored flag would bill the caller for the history they asked not to send |
HistoryPolicy |
HistoryPolicy |
nil, and the conversation grows without bound, as it always did | Never dropped. A policy that returns an invalid edit fails the call rather than corrupting the transcript. chat.CompactOldest also calls the model, so it can fail for the ordinary reasons a call fails |
Stateless and HistoryPolicy answer different problems and are not
alternatives. Stateless is for calls that were never a conversation (batch
classification, extraction, judging) and sends no history at all.
HistoryPolicy is for a conversation that genuinely accumulates and needs
bounding. chat.TruncateOldest drops the oldest turns, chat.CompactOldest
summarises them instead. See
Bound a long conversation.
Observability fields¶
| Field | Type | Default when unset | If it cannot be applied |
|---|---|---|---|
UsageObserver |
func(Usage) |
nil, so usage is still accumulated for Usage(), but nothing is emitted per round-trip |
Called synchronously on the calling goroutine once per provider round-trip, including with a Known == false value from providers that report no counts. A slow observer slows the call |
ChatClient.History() is the other half of this, and needs no configuration: it
reports the turns a client will re-send and the provider's own input-token count
for the last call it made. See
Bound a long conversation.
Test-only fields¶
| Field | Type | Default when unset | Notes |
|---|---|---|---|
AllowInsecureBaseURL |
bool |
false |
Permits an http:// BaseURL for an httptest.Server. Production callers leave it false |
AllowInsecureBaseURL is excluded from every decoder (json:"-",
mapstructure:"-" and yaml:"-"), so a host decoding chat.Config wholesale
from its own config file cannot relax the HTTPS requirement from input it does
not control. Every other field tagged json:"-" carries the same three
exclusions, and a test enforces that pairing so the next one cannot drift.
The provider test seams moved in v0.10.0
ExecLookPath, ExecCommand and GenaiNewClient have left Config. Each
was readable by exactly one provider, and GenaiNewClient was an any that
the Gemini adapter type-asserted at runtime, a mistake to repeat rather
than to generalise.
The two claude-local seams are now typed options on that provider's own
constructor:
client, err := chat.NewClaudeLocal(ctx, settings,
chat.WithClaudeBinaryLookup(fakeLookPath),
chat.WithClaudeCommand(fakeCommand),
)
NewClaudeLocal runs the same validation chat.New does, so a setting the
CLI cannot express (Config.Temperature, which it has no flag for at any
version) is still a construction error rather than a silent miss.
GenaiNewClient moves to chat-gemini's own construction option when that
module next adopts a core release.
chat.Settings: the construction dependencies¶
Settings is what chat.New takes. It carries the Config above plus the one
dependency that is not configuration:
| Field | Type | Default when unset |
|---|---|---|
Config |
Config |
the zero Config, which resolves to the default provider |
Logger |
*slog.Logger |
nil ⇒ a slog.DiscardHandler logger; nothing is logged and nothing panics |
Host-config shapes and their mapstructure keys¶
The module owns typed shapes so a host can decode its own config into them
without the module depending on a config library. The mapstructure tags are
the key names a decoder will look for:
| Shape | Field | Key | Notes |
|---|---|---|---|
RuntimeConfig |
Provider |
provider |
mapped onto Config.Provider |
RuntimeConfig |
RequestTimeout |
request_timeout |
mapped onto Config.RequestTimeout |
RuntimeConfig |
Fallback |
fallback |
a nested FallbackConfig |
FallbackConfig |
Enabled |
enabled |
false builds a single client, not a composite |
FallbackConfig |
Providers |
providers |
ordered; providers[0] is the primary and overrides Config.Provider, which is logged at WARN |
CredentialConfig |
Env |
env |
the name of an environment variable |
CredentialConfig |
Keychain |
keychain |
a service/account reference |
CredentialConfig |
Key |
key |
a literal secret |
CredentialConfig |
Lookup |
- |
never decoded; injected in code |
These are shapes, not a config system. Nothing in this module reads a file, watches for changes, or applies precedence between sources. A host does that and passes the result in. go-tool-base ships such an adapter, and it lives there rather than here.
The order chat.New checks things in¶
The order matters because it determines which problems you learn about together. Config-only checks all run before anything is built, so a caller with three mistakes learns all three from one call:
- Default the provider from
AI_PROVIDER, then toProviderClaude. - Validate
BaseURL, and require it forProviderOpenAICompatible, which is fatal. - Validate
Effort,TemperatureandTopPvalues, which is dropped. - Check the selected model's known capabilities, which is dropped.
- Stop here if anything so far was fatal, returning no client.
- Remove every dropped setting from the config, so the provider never sees a value the module has already reported as unapplied, and log each one at WARN.
- Look up the provider factory, which is fatal if it is unregistered.
- Call the factory, which is fatal if it errors.
- Assert the built client implements the marker interface for every capability the config asked for, which is fatal if it cannot.
- Log the endpoint host at INFO and return the client, plus an error listing anything that was not applied.
Related¶
- Defaults and limits: the constant values, and the caps that are not configurable.
- Errors: every sentinel these failures carry.
- Environment variables: the four variables the module and its providers read.
- Handle a partially-applied configuration: reading the error
chat.Newreturns.