Mock mode

With no provider configured, ai answers from a deterministic mock instead of failing. See Mock mode in Getting Started for the introduction; this page is the detail that matters once you are building.

What mock mode replaces

The provider call. Nothing else.

still runs for realreplaced by a mock
@tool functions - your actual codethe provider's choice of tool
Boolean contractsstring contracts (they pass, and the run says they went unchecked)
Coercion to the requested typethe model's reply
Retry and backoff
Budget counting
Tracing

That split is why mock mode is worth trusting: the machinery around the call is exercised exactly as it will be in production, so a schema mistake, a broken tool signature or a missing branch fails offline.

Determinism

The same program produces the same bytes every run. This is what makes an ai pipeline testable at all - ecko test forces mock mode by clearing the API keys, the provider and the base URL, so a test can assert on output.

callmock result
ai "..."[AI Mock] <prompt>
ai[Int] / ai[Float]42 / 3.14
ai[Bool]true
ai[Enum]the first variant
ai[Struct]each field's mock
ai[json<List<T>>]a one-element list
ai "..." on img[AI Mock] <prompt> [image WxH] with the real dimensions

Tools in mock mode

The loop is deterministic rather than absent: it invokes every tool named in the prompt and passes the prompt as each argument a model would have to supply, so a two-parameter tool gets two arguments, as it would live. A parameter with a default keeps it. Enough to prove the wiring works. An untyped call returns the last tool's result; a typed ai[T] call returns the usual mock value for T, as it would without tools.

One deliberate difference: a failing tool throws offline, where live mode feeds the error back to the model. Live, the model is the recovery mechanism; offline, you are, so the failure surfaces instead of being swallowed.

The tools really run, with the prompt as their arguments. A tool that fetches a URL, writes a file or spends money does it offline too, with a paragraph of prompt text as its input. llm.is_mock() lets it skip that:

import std.llm
import std.http

@tool("fetch a web page")
fn fetch_page(url) {
    if llm.is_mock() { return "[offline] page text for {url}" }
    http.get(url).body
}

What it cannot tell you

Whether the answer is any good. Mock mode validates structure, not content. A prompt that produces confident nonsense from a real model produces a schema-perfect 42 offline.

Two things close that gap: boolean contracts, which are real checks that run identically on and offline, and running against a provider before you ship. Mock mode makes the program trustworthy; it says nothing about the model.

Turning it off

Set ECKO_AI_API_KEY. ecko test ignores it by design - a test run never spends money.

To force it back on - in CI, or to try a program that names its own models with via - set ECKO_AI_MOCK=1. It wins over every key, provider and via, and it is what ecko test sets.