Free-form output is the part of an AI pipeline you cannot sign. When the model writes the final prose, the HTML, or the Markdown, the same input can come back different the next run, and nothing in the result can be diffed, versioned, or audited. I hit this in every automation I built, and the fix was not a better prompt. It was to move rendering out of the model: the model returns JSON, and code produces everything a reader sees. That rule is the spine of Content at scale.
TL;DR
The model returns JSON only; the code renders every prose, HTML, or Markdown artifact; and the output becomes auditable and reproducible. A call that returns prose, HTML, or Markdown instead of JSON is a regression, not a feature. The same split reaches a decision stage, where a deterministic rule in code owns the keep-or-drop call. The proof is a model choice: a dense Mandarin-trained model off, a European model in.
The problem, and the answer it rules out
The problem is not that the writing is poor; it is that free-form output is not reproducible. Ask the same question twice and the prose, the HTML, or the Markdown comes back different, so a re-run changes the layout and a change nobody intended shows up in the result. Nothing in a paragraph of generated prose can be diffed against the last run.
Auditability and reproducibility are the requirement, and they rule out the obvious answer, letting the model produce the final rendering, because rendering must not vary between runs.
The decision: the model returns JSON, the code renders the rest
The decision is a two-stage pattern, and I hold it as non-negotiable: the LLM returns JSON only, and code renders everything else, emojis, units, and layout included. Its job ends at the structured value; rendering belongs to a deterministic layer that cannot drift.
The enforceable line is blunt: any LLM call returning prose, HTML, or Markdown instead of JSON is a regression, not a feature. The alternative I rejected was letting the model produce the final rendering, rejected on exactly that reason. It puts presentation back into the model and out of the code.
The contract, at the level a reader could rebuild it
The contract is the checklist that makes every new LLM prompt obey the pattern:
- Return JSON only. The prompt carries an explicit instruction to return JSON, with no Markdown and no prose wrapper.
- Ship a schema or an example. The prompt includes a JSON schema or an example structure, so the shape is stated, not inferred.
- Define the failure value. On error, the model returns
{}or a null-safe fallback, never a prose apology. - Mark the parse step. The calling code notes where the parse happens, so the model-renderer boundary is visible in the source.
The renderer answers the other half. Presentation logic owns emojis, rounding, units, and layout, and it never touches the prompt. The prompt keeps the substantive selection rules, as ordered priorities, because choosing what to include is a judgment a model can carry, while formatting is arithmetic a model should not. What that standing effort removes against manual work is a decision of its own.
The pattern deployed
A scheduler I run shows the split. Its prompt asks for structured JSON only, no formatting and no prose: a JSON object with a slots field (exactly 2) and an advice line of at most 8 words. Everything a person reads is rendered in code, in format_notify_message(). The selection rules, precipitation, temperature including felt temp, wind, and continuity, are encoded as ordered priorities in the base prompt, so the model chooses within a stated order rather than inventing one.
The alternative I rejected was free-form LLM output with presentation in the prompt, rejected as non-deterministic. The message rendering is deterministic and auditable; the LLM supplies only the slot choices plus a short advice line, independently validated against forecast data.
The regression that forced the model choice
The rendering contract governs shape; it says nothing about whether the content inside that shape is any good. On a French article-digest stage, the output carried regressions: bad accords, invented verbs, missing elisions, English calques, and English decimal notation.
The stage was running on a Qwen model, qwen/qwen3-32b, and that choice was the cause. It is a dense 32B model trained mostly on Mandarin, unlike the mixture-of-experts (MoE) qwen3-30b-a3b whose routing acted as a natural filter against non-native artifacts. The failure is a property of the model, not a prompt you can tune away.
I moved the digest stage off qwen3-32b to Mistral Small, and later to Mistral Small 4 primary with Qwen fallback after the 2026-07-24 migration. Mistral Small 4 is now primary for every role. Which model holds which role is a decision of its own, and so is which executor should take a given task.
The same split on a decision stage
The split is not rendering-specific. A daily AI-newsletter digest had to decide which items to keep without requiring the words “AI”, “LLM”, or “model” to appear, so that tangential or codename-only items were not lost.
The decision was an exclusion-list default posture: keep an item, score at least 5, unless it falls in a non-AI exclusion category, with a known-entity floor where a named AI lab, model, or product forces a score of at least 7 and can never be filtered. The alternative I rejected was keyword-based inclusion, rejected because “AI”, “LLM”, and “model” are not required. The value comes from the model; a deterministic rule in code owns the keep-or-drop call. Wherever code can own the mechanical decision, the model stops being the arbiter of it, the same impulse that drives deterministic output guards.
What this does not solve
Three boundaries, drawn rather than glossed.
First, the JSON contract makes the output’s shape deterministic, not its content. Valid JSON can still be wrong: it gives me an artifact I can diff and sign, not a check on whether the fields are true. That check is a separate layer.
Second, it does not replace the review pass. The contract governs how an artifact is rendered, not whether it is fit to publish. A rendered artifact still goes through the review pass before a reader sees it. Getting a system like this approved to run at all is a further discipline.
Third, it does not free the operator. The person who reads the result stays answerable, so running unattended is not the goal. Model choice sits above the contract. Keeping the machine the pipeline runs on out of the way is its own boundary, and so is how a chosen automation is run day to day.
All four records behind this piece are partial, narrated from a wiki page rather than read off the code, so I state the design, never a measured rate. What the rule gives the engineer is narrow and worth having: an artifact whose shape does not change between runs, so it can be diffed, reviewed, and signed. The operator behind this pattern is on the about page. If your model output has to be reproducible before it can be trusted, let’s talk.