Radondocs

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.

chat.ts
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.

systemrole

Instructions 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.

userrole

Input from the end user. Can be a plain string or, for vision, an array of content parts.

assistantrole

A prior model turn. Carries toolCalls when that turn invoked tools — pass it back verbatim to continue a tool-calling loop.

toolrole

The 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:

temperaturenumber

Sampling temperature. Higher is more random.

maxTokensnumber

Maximum tokens to generate.

topPnumber

Nucleus-sampling top-p.

stopstring | string[]

Stop sequence(s). Radon maps this to Anthropic's stop_sequences and Gemini's stopSequences for you.

seednumber

Deterministic-sampling seed, where the provider supports it.

frequencyPenaltynumber

Penalize token frequency (OpenAI family).

presencePenaltynumber

Penalize 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:

idstring

The provider's id for this completion.

providerstring

The provider that produced the result.

modelstring

The model that produced it.

contentstring

Concatenated 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.

rawunknown

The 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

On this page