Module 2: The Agent's Brain: Model and System Prompt
6. Model parameters and structured output
Description
By the end of this capsule you'll be able to adjust two of the model's sampling parameters — temperature and token limit — and, above all, you'll be able to force your agent to return a JSON with a fixed shape instead of a paragraph of prose, so the next node in the flow can read the data directly instead of having to interpret it.
This isn't a matter of tidiness for its own sake. In lesson 2 you built, on paper, an agent that receives support tickets for an online store, classifies their urgency, and drafts a reply — 5,000 tickets a month, running in the background. If that agent responds with something like "This ticket looks urgent, I'd say high priority, here's a draft reply: ...", no downstream node can automatically decide whether the ticket enters the escalation queue or not — someone would have to read it first. If instead it responds with {"urgency": 4, "category": "billing", "draft_reply": "..."}, a simple IF node routes it with a condition, without any person having read it yet.
Connection to the module: the previous lesson set the agent's behavior contract — who it is, what it shouldn't do, how it should respond — through the system prompt. That instruction, however, is a request in natural language: the model can follow it most of the time and drift right when it matters most that it doesn't. Here you're going to add the piece that enforces and validates the exact shape of the response, plus the controls that decide how predictable that response is and how much it can end up writing.
How much the model gambles, and how far it can write
Think of a simultaneous interpreter working live, word by word. Faced with an ambiguous phrase, a cautious interpreter always picks the most obvious, most common translation — never surprising, but also never conspicuously wrong. A bolder one, on the other hand, sometimes tries a less common synonym looking for more nuance, and every so often that bet goes wrong and changes the meaning of what was said.
A language model picks its next word — strictly speaking, its next token — in a similar way: at every step it computes how likely each possible option is and then decides which one to use. Temperature is the parameter that controls how closely it sticks to the most likely option. Near 0, the model almost always picks the highest-probability token — more predictable, more repeatable responses, although Anthropic notes in its own documentation that even at temperature 0 the result isn't completely deterministic. As you raise the value, the model starts taking chances on less likely tokens: more varied responses, and also more chance they drift from what you expected.
In n8n, this parameter doesn't live in the AI Agent node — it lives in the model node you connected to the Chat Model port (Anthropic Chat Model, OpenAI Chat Model, whichever applies). Open it and, in its Options section (the "Add Option" button), add Sampling Temperature. In the Anthropic Chat Model node the range goes from 0 to 1, with 0.7 as the default; in the OpenAI Chat Model node the range goes up to 2, also with 0.7 as the default. The number itself isn't comparable from one provider to another — 0.7 on Anthropic and 0.7 on OpenAI don't represent the same "risk level," because the scales have different lengths — but the criterion for choosing it is the same: near 0 for tasks where a weird response is costly (classifying, extracting data, deciding an amount), higher for tasks where variety helps (writing copy, generating variants of a message).
There's an exception worth knowing before you trust this parameter blindly. If you connected one of Anthropic's newer tiers — Claude Sonnet 5, Claude Opus 4.7 or higher, which run with extended reasoning on by default — Sampling Temperature still shows up in the options panel, but the provider ignores it. The field's own help text, inside the node, warns about it: "Not supported on newer Anthropic models (Claude Opus 4.7+, Claude Sonnet 5+) — ignored there." The same happens with Top K and Top P in that generation. The real control over how much that model "gambles" moved to the model's reasoning mode — a parameter called Effort (low, medium, high), which is beyond this lesson's scope — not a classic sampling parameter. It's the same discipline you saw in lesson 2 with retired model IDs: before assuming a field does something, confirm it against the current description of that field for the model you have connected.
The second option in that same panel is Maximum Number of Tokens — internally it's called maxTokensToSample in the Anthropic node, with 4096 as the default, and maxTokens in the OpenAI node, defaulting to -1, meaning no cap of its own on n8n's side, up to whatever maximum the model allows. Don't confuse this limit with the size of what the model can read, its context window: maxTokens only caps the response, how much the model can generate before n8n forcibly cuts it off. If that cap is set too low for what you're asking, the response gets cut in half — literally in the middle of a word or a closing brace — and if that response was a JSON, the cutoff makes it invalid before it reaches any parser.
One extra detail so it doesn't catch you off guard: Anthropic's API requires max_tokens on every call — it's a required parameter on the provider's side — which is why the Anthropic Chat Model node always sends a value (4096 if you don't touch anything) even if you never configured the option. OpenAI doesn't require it, which is why n8n leaves it at -1 by default on that node.
From free paragraph to form: forcing a structured output
Ask someone to tell you about a product on a blank sheet of paper, and they're going to write you a paragraph — maybe even a well-written one — but to pull out the price and the stock you're going to have to read the whole thing. Give them, instead, a form with fields — Price: ___, Stock: ___, Category: ___ — and that person fills in exactly those fields: you read the data, you don't interpret text.
Structured output is giving the agent that form: instead of letting it write freely, you define a schema — a JSON with fixed fields and types — and n8n validates that the response matches that schema before passing it to the next node. This is different from asking in the system prompt that it "respond in JSON," which is what you saw in the previous lesson: that instruction is a request in natural language, just as strong as the rest of the prompt and just as prone to the model ignoring it halfway through a long response. What you're building here is a mechanical piece connected to the agent, which checks the response after the model generated it.
In the AI Agent node, this piece is turned on with the Require Specific Output Format option, a toggle that's off by default. Turning it on brings up a notice to connect an output parser on the canvas: a new port at the bottom of the node, called Output Parser, alongside Chat Model, Memory, and Tool. That's where you connect the Structured Output Parser node. With the toggle off — the default — the agent returns a plain string in $json.output; with the toggle on and the parser connected, that same field stops being free text and becomes the object you defined.
In the Structured Output Parser, you choose, in the Schema Type field, how to describe that object: Generate From JSON Example — you give it a sample JSON and n8n infers the schema on its own, treating every field in the example as required — or Define using JSON Schema, where you write the schema by hand following the standard specification, with one real limitation: it doesn't support $ref syntax, so if your schema references another external schema, the type may not come through correctly. For most of the agents you build in n8n, "Generate From JSON Example" is enough and faster to write.
Worked example
Let's go back to lesson 2's support ticket agent — the one that classifies urgency and drafts a reply — and force its output to be a JSON with three fields: urgency (a number from 1 to 5), category (text), and draft_reply (the draft). For the Chat Model we use Claude Haiku 4.5: it's consistent with the high volume and narrow task you saw in lesson 2, and — unlike Sonnet 5 — it's a generation where Sampling Temperature still has a real effect, as you just saw above.
Step 1 — set the temperature and token limit on the model. Connect an Anthropic Chat Model node to the agent's Chat Model port, with the model claude-haiku-4-5. Under Options → Add Option, add Sampling Temperature and set it to 0: this task classifies and decides a priority, and you don't want the same complaint, written almost identically twice, to come back sometimes as urgency 3 and sometimes as urgency 4. Leave Maximum Number of Tokens at its default value (4096): plenty for a JSON with three short fields.
Step 2 — turn on structured output on the agent. Open the AI Agent node and turn on Require Specific Output Format. A notice is going to pop up asking you to connect an output parser on the canvas; at the new Output Parser port, add a Structured Output Parser.
Step 3 — define the schema. Open the Structured Output Parser, leave Schema Type on Generate From JSON Example (the default value) and, in the JSON Example field, write:
{
"urgency": 4,
"category": "billing",
"draft_reply": "Thanks for reaching out. We're already looking into the duplicate charge and will confirm the refund within 24 hours."
}
n8n uses this example only to infer types — urgency as a number, category and draft_reply as text — not to copy these literal values. All three fields end up required in the resulting schema, because that's how n8n treats any field generated from an example.
Step 4 — run it with a test ticket. From the Chat Trigger, send:
I got charged twice for the same order and I need the money back as soon as possible.
What to expect:
{
"output": {
"urgency": 4,
"category": "billing",
"draft_reply": "Thanks for contacting us. We found the duplicate charge on your order; we've already started the reversal and will confirm through this channel within 24 hours."
}
}
The detail worth flagging: the response doesn't arrive at $json.urgency directly, it arrives wrapped in an output key — $json.output.urgency, $json.output.category, $json.output.draft_reply. It's the next node (an IF, a Set, a Postgres) that's going to read those three fields by name, without anyone having to interpret a sentence to know whether the ticket is urgent.
Going deeper: the safety net for a JSON that doesn't fit
No parser can force the model to write valid JSON — it can only reject it if it's wrong. The Auto-Fix Format option, inside that same Structured Output Parser node, is the safety net for that case: turning it on brings up an additional port to connect a second language model (it can be the same provider, or a cheaper one). If the agent's response doesn't match the schema, n8n sends that error back to that second model, along with the failed response, so it can fix it — at the cost of an extra call. It's a retry layer, not an absolute guarantee: it's still a language model generating the fix, so in rare cases it can fail again.
If your agent runs exclusively on the OpenAI Chat Model node with the Responses API turned on — the default option in recent versions of the node — you have a stronger, provider-specific alternative: under Options → Response Format, you choose JSON Schema (recommended) instead of connecting a separate Structured Output Parser. There you define the schema directly on the model node, with a Strict toggle that requires the API itself to never deliver anything that doesn't match the schema — it's a restriction at the level of the model's generation, stronger than validating afterward. The tradeoff is that it only works with OpenAI: if tomorrow you swap the Chat Model for Anthropic or a local model on Ollama, that configuration doesn't travel with the node. The Structured Output Parser connected to the agent's Output Parser port, on the other hand, works the same no matter what model you have behind it — it's the portable option, which is why we used it in the example above.
Common mistakes
Adjusting Sampling Temperature on a newer-generation Claude expecting something to change. What happens: you change the value between 0 and 1, run the same prompt several times, and don't notice any real difference in response variability, whatever number you set. Why: as you saw above, on Claude Sonnet 5, Claude Opus 4.7, and higher — with extended reasoning on by default — Anthropic ignores Sampling Temperature, Top K, and Top P on the API side; the field itself warns about it in its description inside n8n. How to spot it: read the field's description (the info icon next to Sampling Temperature) before assuming it's working, or run the same prompt five times with the value at 0 and then at 1 — if the responses look just as variable in both cases, the parameter has no effect. How to fix it: for that generation of models, the real control over variability is no longer Sampling Temperature; if you need that fine control today, use a model where the parameter does apply — Claude Haiku 4.5, or any model from another provider.
Confusing the prompt instruction ("always respond in JSON") with the parser's structured output. What happens: the flow runs fine most of the time, but every so often — especially on a longer-than-usual response, or with an ambiguous ticket — the model adds a sentence before the JSON ("Here's the classification:") or wraps it in a code block, and the next node fails reading a field that never arrived in the exact format. Why: without Require Specific Output Format turned on and without a Structured Output Parser connected, that instruction in the system prompt is just text the model may or may not follow — there's nothing on your side validating the response before it moves on. How to spot it: check whether the AI Agent has Require Specific Output Format turned on and the Output Parser port actually connected; if it's off or empty, the "JSON" in your prompt is a suggestion, not a rule. How to fix it: turn on Require Specific Output Format and connect a Structured Output Parser with the exact schema you need — the prompt still helps point the model in the right direction, but it stops being the only control.
Reading $json.category instead of $json.output.category in the next node. What happens: the node after the agent — a Set, an IF, any expression — returns an empty field that you know the agent actually generated correctly, because you saw it in the AI Agent's execution panel. Why: the Structured Output Parser wraps the whole parsed object inside an output key — the real response is { "output": { "urgency": 4, ... } }, not { "urgency": 4, ... } directly. How to spot it: open the AI Agent node's output panel and look at the full JSON structure, not just the field names you defined in the schema; you're going to see the output key wrapping everything. How to fix it: reference the fields with the full prefix ({{$json.output.urgency}}, {{$json.output.category}}) in any downstream node.
Exercises
1. Your system prompt already tells the agent "Always respond in the JSON format: {urgency, category, draft_reply}." Is that instruction enough to guarantee the next node always receives those three fields? Justify your answer with what you saw in this capsule.
See solution
It's not enough. That instruction is a natural-language request inside the prompt — the model follows it most of the time, but nothing guarantees it won't break it on a long response, an ambiguous case, or by wrapping the JSON in a code block. The real guarantee comes from turning on Require Specific Output Format on the AI Agent and connecting a Structured Output Parser with the schema: that adds mechanical validation after the model responds, instead of relying only on it having followed the instruction.
Why it works: separating "asking the model for something" from "validating what it returned" is exactly the difference between a prompt (lesson 5) and a parser (this lesson) — the first guides, the second enforces.
2. You need the Structured Output Parser to generate a schema with these fields: urgency (an integer from 1 to 5), category (text), and escalate (true or false). Write the JSON Example you'd put in the corresponding field, using Generate From JSON Example.
See solution
{
"urgency": 3,
"category": "shipping",
"escalate": false
}
Why it works: n8n infers each field's type from the example value — number for urgency, text for category, boolean for escalate — without you having to write a JSON Schema by hand. All three fields end up required in the resulting schema, because that's how n8n treats any field generated from an example.
3. A colleague builds the same agent, connects the Structured Output Parser, and in the following Set node writes {{$json.category}} to read the ticket's category. When they run it, that field arrives empty, even though the category does show up correctly classified in the AI Agent's execution panel. What's wrong?
See solution
It's missing the output prefix. The Structured Output Parser wraps the object it builds inside an output key, so the correct path is {{$json.output.category}}, not {{$json.category}}. The data did arrive correctly — it's just that the expression points to a place in the JSON where that field doesn't exist.
Why it works: understanding that the parser wraps the response saves time from checking the prompt or the schema when the real problem is just the path of the expression reading the data.
4 (challenge). The same ticket agent does two things in a single call: classifies urgency (a task where you want the same result every time) and drafts a reply (a task where a bit of variety isn't bad). You have a single temperature control for both. What value do you choose, and what do you sacrifice on the other task with that choice?
See solution
It's worth prioritizing low temperature (close to 0), because the cost of getting the classification wrong — an urgent ticket that doesn't escalate in time — is higher than the cost of a slightly flatter or more repetitive draft. What gets sacrificed is variety in draft_reply: with low temperature, similar tickets are going to get drafts with very similar wording to each other. If that uniformity becomes a real problem — several customers get nearly identical replies and notice — the underlying fix isn't raising the temperature for the whole agent, but splitting the task into two calls: one for classification at temperature 0, and another just for drafting with a higher temperature, chaining a second AI node after the first.
Why it works: when two different goals compete for the same parameter, it's almost always worth prioritizing the one with the costlier error, and considering splitting the task into two independent calls if the tradeoff stops being acceptable.
Summary and next step
Before moving on you should be able to lower a Chat Model's temperature for a classification task — and know on which models that adjustment actually does something —, explain what maxTokens/maxTokensToSample controls and why it's not the same as the context window, turn on Require Specific Output Format on the AI Agent, and connect a Structured Output Parser with a schema that describes exactly the fields you need, knowing the response is going to arrive wrapped in output.
You now have an agent that responds with data the rest of the flow can read without interpreting it. What you still don't have is a way to compare, without re-triggering the whole flow from the Chat Trigger, what happens if you change the temperature, the model, or the prompt: that's exactly what the debugging engine you build in the next capsule is for.
Resources
- Structured Output Parser — the two ways to define the schema (JSON Example vs. JSON Schema) and their limits.
- AI Agent node — the agent's central node and its connection ports.
- Anthropic Chat Model — the node's options, including Sampling Temperature and Maximum Number of Tokens.
- OpenAI Chat Model — includes the native Response Format option with JSON Schema under the Responses API.
- Anthropic Messages API — parameters — official range and behavior of
temperatureandmax_tokens. - JSON Schema — getting started — reference syntax for when you need "Define using JSON Schema" instead of an example.