outputFormat controls two things: the instruction appended to the system prompt (what you're asking the model to produce), and the exact key(s) the step's result is returned under — which is what downstream steps reference with {{yourStep....}}. Every format also always includes tokensUsed and a usage object (inputTokens, outputTokens, totalTokens, model, provider, protocol, finishReason, and truncated — a boolean, true when the response was cut off by maxTokens, worth checking if output looks incomplete).
Shape per format
- text →
{ response: string }— reference as{{step.response}}. - json → flat, spread directly —
{ ...yourDeclaredFields }, noresponsewrapper. If you declared fieldssender_nameandtopic, reference{{step.sender_name}}directly, not{{step.response.sender_name}}. If the model's output fails to parse as JSON, you get{ _raw: "<the raw text>" }instead — check for_rawif you need to handle malformed output gracefully. - score →
{ score: number }(0-100, clamped). - category →
{ category: string }— matched against your declared categories, case-insensitive: an exact match wins, otherwise the longest declared category the answer contains (so "not spam" is never read as "spam"); falls back to the raw trimmed text if nothing matches. - boolean →
{ decision: boolean }— true only for a response starting with "yes"/"true"/"1" (case-insensitive). - list →
{ items: string[] }— reference the whole thing as{{step}}(which resolves to the{items:[...]}object) or{{step.items}}directly. - image →
{ url: string }— the generated image, stored and returned as a URL. The image model is picked automatically (see Config → Which model runs);usage.provider,usage.modelandusage.modelSourcesay which one ran. No tokens are reported, since it isn't a chat completion. - audio →
{ url, voice, speech }— the text is generated normally, then spoken by a speech model and stored as an audio file.speechis{ provider, model, modelSource }for the speech model,voiceis the voice actually used, andusagedescribes the text model.
json is flat — this trips people up
Every other format nests its result under one named key (response/score/category/decision/items). json is the one exception — your declared fields are spread directly onto the result object. Don't reference {{step.response.fieldName}} for a json-format step; it's {{step.fieldName}}.
Using a list output as a Loop source
An ai_call step with outputFormat: list produces exactly the same {items:[...]} shape as list_entities and a collection variable — so it works as a Loop step's source the same way. In the builder, add your ai_call step, set its Output format to List, then in a following Loop step's "Collection to iterate" field, select that ai_call step directly.
s1: ai_call intent=generate outputFormat=list
contextFields=[{ label: topic, value: {{input.topic}} }]
instructions="Generate 5 short blog post title ideas."
s2: loop source={{s1}} itemAs=title
s2a: create_entity entity=draft_post fields=[{ title: {{title}} }, { status: draft }]