Chat
Send messages and read normalized results with ai.chat() — roles, sampling parameters, the max-tokens and pass-through caveats, and the ChatResult shape.
ai.chat() runs one non-streaming completion against the unified shape and
resolves to a ChatResult. It's the workhorse of the SDK: the same
call drives all ten providers.
const res = await ai.chat({
messages: [
{ role: "system", content: "You are a terse assistant." },
{ role: "user", content: "Capital of Japan?" },
],
});
res.content; // "Tokyo"Messages and roles
A conversation is an ordered array of messages. Each has a role and content.
systemroleInstructions that steer the model. Radon routes these to each provider's own
home for them — a system message on OpenAI, the top-level system param on
Anthropic, systemInstruction on Gemini. Multiple system messages are joined.
userroleInput from the end user. Can be a plain string or, for vision, an array of content parts.
assistantroleA prior model turn. Carries toolCalls when that turn invoked tools — pass it
back verbatim to continue a tool-calling loop.
toolroleThe result of one tool call, keyed to it by toolCallId. See
Tools.
You build the array yourself and grow it as the conversation continues:
const messages = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello!" },
];
const res = await ai.chat({ messages });
// Append the model's reply to continue the conversation:
messages.push({ role: "assistant", content: res.content });
messages.push({ role: "user", content: "Say that again in French." });
const next = await ai.chat({ messages });Choosing a model
Set model per call, or a defaultModel per provider in config. If you set
neither, the provider's built-in default is used.
await ai.chat({ messages, model: "gpt-4o-mini" });See the Providers reference for every default model.
Sampling parameters
All sampling parameters are optional and passed on the request itself:
temperaturenumberSampling temperature. Higher is more random.
maxTokensnumberMaximum tokens to generate.
topPnumberNucleus-sampling top-p.
stopstring | string[]Stop sequence(s). Radon maps this to Anthropic's stop_sequences and Gemini's
stopSequences for you.
seednumberDeterministic-sampling seed, where the provider supports it.
frequencyPenaltynumberPenalize token frequency (OpenAI family).
presencePenaltynumberPenalize token presence (OpenAI family).
await ai.chat({
messages,
temperature: 0.2,
maxTokens: 500,
stop: ["\n\n"],
});Sampling parameters are pass-through, not guaranteed
Radon forwards each parameter to providers that support it and silently ignores
it on providers that don't. seed and the frequency/presence penalties aren't
universal; Anthropic uses stop_sequences and top_k rather than penalties.
Radon maps the names it can and passes the rest through — it does not emulate a
parameter a provider lacks.
Anthropic requires max_tokens
Anthropic's Messages API rejects a request with no max_tokens. When you omit
maxTokens, Radon supplies a default of 4096 so the call still works. Set it
explicitly if you need a different ceiling.
Reasoning tokens aren't normalized
Reasoning models (OpenAI o1/o3, deepseek-reasoner, Anthropic extended
thinking, Gemini thinking) expose their intermediate reasoning differently, and
some ignore temperature/topP. Radon returns the final answer in content and
the full payload in res.raw; reach reasoning traces and thinking-budget controls
via providerOptions or native().
Selecting a provider
Override the default provider per call with provider:
await ai.chat({ messages, provider: "anthropic" });For automatic backups if the primary fails, see Fallback chains Pro.
Provider-specific fields
For request fields with no home in the normalized shape, use providerOptions —
it's shallow-merged into the outgoing request body, after Radon's own fields, so
it can override them.
await ai.chat({
messages,
provider: "openai",
providerOptions: { logit_bias: { "50256": -100 } },
});providerOptions ties that call to one provider — it's a documented but
discouraged escape hatch. Prefer the normalized options where they exist.
The result
Every call resolves to the same ChatResult:
idstringThe provider's id for this completion.
providerstringThe provider that produced the result.
modelstringThe model that produced it.
contentstringConcatenated assistant text. Empty string when the turn was only tool calls.
toolCallsToolCall[]Tool calls the model made this turn ([] when none).
finishReason"stop" | "length" | "tool_calls" | "content_filter" | "error" | "other"Why generation stopped, normalized.
usageUsage | undefined{ promptTokens, completionTokens, totalTokens }. Fields are 0 when a
provider doesn't report them.
rawunknownThe untouched provider response.
const res = await ai.chat({ messages });
if (res.finishReason === "length") {
// hit maxTokens — the answer was truncated
}Cancellation
Pass an AbortSignal to cancel an in-flight request — it's forwarded to the
underlying fetch.
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
await ai.chat({ messages, signal: controller.signal });Next steps
Core concepts
The mental model behind Radon AI — providers, the one message and result shape, models, streaming, tool calling, and the escape hatch to raw provider access.
Streaming
Stream a chat completion token-by-token with ai.stream() — textStream(), the normalized chunk union, final(), the single-consumption rule, and the no-fallback caveat.