Files
playbook/antigravity-awesome-skills/skills/n8n-agents/references/STRUCTURED_OUTPUT.md
T
2026-07-22 10:09:47 +00:00

164 lines
6.8 KiB
Markdown

# Structured output
Non-negotiable: the output parser must **parse AND retry on failure**. Without retry, one malformed model response halts the entire workflow.
The parser is the `@n8n/n8n-nodes-langchain.outputParserStructured` node, wired into the agent (or Basic LLM Chain) via the `ai_outputParser` connection.
---
## The pattern (node objects)
The parser, with `autoFix` and its own fixer model:
```json
{
"parameters": {
"schemaType": "manual",
"inputSchema": "{ \"type\": \"object\", \"properties\": { \"score\": { \"type\": \"integer\", \"minimum\": 1, \"maximum\": 5 }, \"reason\": { \"type\": \"string\" } }, \"required\": [\"score\", \"reason\"] }",
"autoFix": true
},
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.3,
"name": "Structured Output Parser"
}
```
Wire the parser to the agent, and a **coding-capable fixer model** to the parser:
```json
"Structured Output Parser": {
"ai_outputParser": [[{ "node": "AI Agent", "type": "ai_outputParser", "index": 0 }]]
},
"Fixer LLM": {
"ai_languageModel": [[{ "node": "Structured Output Parser", "type": "ai_languageModel", "index": 0 }]]
}
```
On the agent, set `hasOutputParser: true` so the slot is active.
---
## Why a schema, not an example
`schemaType: 'manual'` with a real JSON Schema is the default. `jsonSchemaExample` (`schemaType: 'fromJson'`) looks easier, but an example **cannot** express:
- **Required vs optional fields** — an example is one snapshot; the parser can't tell which keys are mandatory.
- **Enums** — `"category": "compliance"` doesn't constrain the model to `compliance | history | risk`; it will invent new categories.
- **Numeric ranges** — `"score": 3` doesn't say `1-5`; the model returns `7` or `0.85` and passes.
- **Array constraints** — min/max items, item-type uniformity.
- **String formats** — email, UUID, ISO date, regex.
A schema gives the model clearer rules and the parser real validation:
```json
{
"type": "object",
"properties": {
"decision": { "type": "string", "enum": ["approve", "reject", "escalate"] },
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
"reasons": {
"type": "array",
"items": {
"type": "object",
"properties": {
"category": { "type": "string", "enum": ["compliance", "history", "risk"] },
"weight": { "type": "number", "minimum": 0, "maximum": 1 },
"note": { "type": "string" }
},
"required": ["category", "weight"]
}
},
"follow_up_required": { "type": "boolean" }
},
"required": ["decision", "confidence", "reasons", "follow_up_required"]
}
```
Reach for `fromJson` + `jsonSchemaExample` only for one-off shapes you're certain will never grow constraints. Once a field needs to be optional, enum-ed, or range-bounded, you're rewriting the parser anyway — start with the schema.
---
## `autoFix: true` and the fixer model
The model can produce almost-but-not-quite-valid JSON: trailing comma, missing field, wrong type, or JSON wrapped in a markdown code block. Without `autoFix`, the workflow halts. With it, the parser sends the bad output to a model with a "fix this" prompt, retries, and continues.
The fixer is wired as a **separate** sub-node into the parser's `ai_languageModel` slot. **Use a coding-capable model** (Sonnet-class or better). Reconciling broken JSON against a schema with enums, ranges, and required fields is a structured-output / coding task — a weak or generic model routinely produces another malformed retry, defeating the point and burning tokens.
When you want to customize the retry prompt, set `customizeRetryPrompt: true` and provide `prompt`. The placeholders `{instructions}`, `{completion}`, `{error}` are filled at retry time:
```
Instructions:
--------------
{instructions}
--------------
Completion:
--------------
{completion}
--------------
Above, the Completion did not satisfy the constraints in the Instructions.
Error:
--------------
{error}
--------------
Please try again with an answer that satisfies the constraints.
This is a structured output parser tool in n8n. Ensure the output format is correct to pass parsing.
DO NOT wrap the output in a markdown code block.
```
Generally, leave the retry prompt as default unless you have a specific reason to override it.
---
## "DO NOT wrap the output in a markdown code block"
This line is **load-bearing**. Models default to wrapping JSON in triple-backtick `json` fences, which breaks the parser. If you see parse failures on output that's clearly valid JSON inside a code block, this instruction is the fix — in both the retry prompt and, if the main model wraps aggressively, the **main** system prompt:
> When responding with structured output, return raw JSON only. DO NOT wrap in markdown code blocks. DO NOT include any prose before or after the JSON.
---
## System prompt + parser: belt and suspenders
The parser tells the model the schema; the system prompt should ALSO state the shape:
```
## Output Format
Respond with a JSON object matching this exact shape:
{ "score": 1-5 integer, "reason": "brief explanation" }
ONLY output the JSON. No prose, no markdown wrapping.
```
It's repetition, but the model takes the system prompt seriously and reinforcement helps. The parser catches what slips through.
---
## Common parse failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| "Failed to parse output" but the text looks like JSON | Wrapped in a markdown code block | Add "DO NOT wrap in markdown" to retry prompt and system prompt |
| Empty fields where the schema expects values | Model thinks it can omit unknowns | "Use empty string '' or null for unknown fields, never omit" |
| Wrong types (number as string) | Schema/example wasn't typed clearly | Use a real number in the schema, not a string |
| Truncated JSON (unclosed brace) | Hit max tokens mid-response | Increase max tokens, tighten the prompt to produce shorter output |
| Field names paraphrased ("Score" vs "score") | Schema didn't pin the name | "Field names are exactly as shown" in the system prompt |
| `autoFix` retries forever | Fixer model too weak for the schema | Swap in a coding-capable (Sonnet-class) fixer; tighten the retry prompt |
---
## When NOT to use a parser
- **Free-form chat replies to the user** — conversational text doesn't need parsing.
- **Tool calls only, no final structured output** — if the user-visible output is text, skip it.
- **Trivial key-value extraction** — a Set node with `JSON.parse($json.output)` covers it.
The parser is for when downstream nodes must consume strict JSON.
---
## Cross-references
- Why and where to use agents at all → parent **SKILL.md**
- The system-prompt half of structured output → **SYSTEM_PROMPT.md**
- Block Kit / adaptive cards need the manual schema even more (union types) → **CHAT_AGENT_PATTERNS.md**