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 real | replaced by a mock |
|---|---|
@tool functions - your actual code | the provider's choice of tool |
| Boolean contracts | string contracts (they pass, and the run says they went unchecked) |
| Coercion to the requested type | the 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.
| call | mock 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.