Skip to content

Models and providers

A provider is a vendor, such as openai, anthropic or google. A model is the configured instance returned by its factory, such as openai("gpt-5-mini").

Set model to a single instance or a named map. A single instance is shorthand for { default: … }. In a map, default is required and handles a plain ask. Choose names for the other entries and use them with ask with <name> or .withModel("<name>").

The simplest config lets the Nola platform serve inference:

nola.config.ts
import { defineConfig } from "@nola-lang/runtime";
export default defineConfig({ model: "nola" }); // reads NOLA_API_KEY; the platform picks the model

The string "nola" is shorthand for nola.infer(). It is the only string accepted directly as a model.

When the default model is "nola", ask with <name> first looks for a matching model-map entry. If there is none, Nola sends the name as an inference profile for the platform to resolve into a model and parameters. This lets you use names such as fast or careful without choosing the underlying model in your code. To choose it explicitly, use nola.infer("<provider>/<model>").

With a local default model, every name must match an entry in the map. An unknown name produces NOLA3004.

nola.config.ts
import { anthropic, openai } from "@nola-lang/providers";
import { defineConfig } from "@nola-lang/runtime";
export default defineConfig({
model: {
default: openai({ model: "gpt-5-mini" }),
fast: openai({ model: "gpt-5-nano" }),
careful: anthropic({ model: "claude-sonnet-4-5" }),
},
});
summarize.tsi
export infer function summarize(.text: string) {
const draft = ask with fast `a rough summary`: string;
return ask with careful `a polished summary of: ${draft}`: string;
}

In this example, fast and careful select the corresponding entries. An unknown name produces NOLA3004, with a list of configured names.

Import openai, anthropic and google from @nola-lang/providers. Pass a model string when you only need to choose a model: openai("gpt-5-mini") is equivalent to openai({ model: "gpt-5-mini" }). Use the object form for additional options.

The platform factories, nola.infer() and nola.tracer(), come from @nola-lang/runtime. See Providers API.

Option openai anthropic google
model (required) e.g. "gpt-5-mini" e.g. "claude-sonnet-4-5" e.g. "gemini-2.5-flash"
apiKeyEnv (default) OPENAI_API_KEY ANTHROPIC_API_KEY GEMINI_API_KEY
apiKey inline key (prefer the env var) same same
baseUrl any Chat-Completions-compatible endpoint Anthropic-compatible endpoint Gemini-compatible endpoint
fetch an injectable fetch implementation same same
maxOutputTokens — provider-level default cap —

Factories read API keys at the first request, so the config can load without a key. A missing key produces a provider error at the first ask. The platform model reads NOLA_API_KEY in the same way. It lets the hosted service choose the model; trial keys are limited to the models allowed by the service. Set NOLA_API_URL to use a different platform endpoint during development.

Use openai({ baseUrl }) for services that support the Chat Completions API:

nola.config.ts
import { openai } from "@nola-lang/providers";
import { defineConfig } from "@nola-lang/runtime";
export default defineConfig({
model: {
// Ollama (local; the key is required by the dialect but ignored by the server)
default: openai({ baseUrl: "http://localhost:11434/v1", apiKey: "ollama", model: "llama3.2" }),
// OpenRouter (one key, every model)
router: openai({ baseUrl: "https://openrouter.ai/api/v1", apiKeyEnv: "OPENROUTER_API_KEY", model: "deepseek/deepseek-chat" }),
// Groq
groq: openai({ baseUrl: "https://api.groq.com/openai/v1", apiKeyEnv: "GROQ_API_KEY", model: "llama-3.3-70b-versatile" }),
// DeepSeek
deepseek: openai({ baseUrl: "https://api.deepseek.com/v1", apiKeyEnv: "DEEPSEEK_API_KEY", model: "deepseek-chat" }),
// xAI
xai: openai({ baseUrl: "https://api.x.ai/v1", apiKeyEnv: "XAI_API_KEY", model: "grok-4" }),
},
});

Some backends generate an answer before checking its schema. For Groq-style json_validate_failed errors, openai() recovers that answer so Nola can validate it. Nola always checks the returned JSON against the requested schema, even if the backend has already checked it.

Use mockProvider for examples and tests that need no API key or network access. Pass a queue of values, consumed one per provider call, or a function that receives the request. A correction attempt consumes another queue entry:

nola.config.ts
import { mockProvider } from "@nola-lang/providers";
import { defineConfig } from "@nola-lang/runtime";
export default defineConfig({
model: {
default: mockProvider([{ name: "Alice Smith", age: 32 }]),
echo: mockProvider((req) => ({ prompt: req.prompt.messages[0]?.content })),
},
});

See Testing for examples.

Nola chooses a model in this order:

  1. forceModel overrides every ask, including those that use ask with or .withModel(). It must name an entry in the model map; otherwise Nola raises NOLA3004.
  2. ask with <name> or .withModel(…) selects a model for that ask. If the default model is the platform and the name has no matching map entry, the name becomes an inference profile and the request goes to the default model.
  3. model.default handles asks with no explicit selection. A single model value becomes the default automatically.

An inference profile is still sent when forceModel is set, although a mock provider ignores it. The profile is part of the ask fingerprint, so ask with fast and a plain ask have separate replay entries.

For CI, use forceModel to send every ask to a mock:

nola.config.ts
import { mockProvider, openai } from "@nola-lang/providers";
import { defineConfig } from "@nola-lang/runtime";
export default defineConfig({
model: {
default: openai({ model: "gpt-5-mini" }),
mock: mockProvider(() => ({ ok: true })),
},
// hermetic — EVERY ask goes here, even .withModel()-pinned ones
forceModel: process.env.CI ? "mock" : undefined,
});

Because forceModel also overrides model choices inside dependencies, those asks use the mock during a test run.

To implement a provider, return an object with a name and an infer(request) method that resolves to { text }; render the request’s intent yourself or with renderPrompt. See the contracts and example in Providers API.

Next: Resilience