Wrenbench

Get an LLM to return a usable video script as strict JSON, with retries

By Vijay Kumar, software developer · 2026-10-27 · build · llm, json, node, automation, build-in-public

The first stage of the reel factory is a function that takes a topic and returns a script. The downstream stages (image generation, voice, ffmpeg) need structured data, so the script has to be real JSON every time, not prose that looks like JSON.

There was no finished reel to write up this week, so this post describes the code in src/script.js instead of a specific video.

The problem

A model asked for a script will happily wrap it in a markdown fence, add a friendly sentence before it, or return three scenes when you needed four. Any of those breaks the pipeline halfway through, after you have already paid for images.

The steps

  1. Keep the brand rules in files, not in code. The factory reads style.md (voice, banned topics, scene structure), pillars.md and products.yml (the only links a call to action may point at) and pastes them into the system prompt. Changing the tone is an edit to a text file.
  2. Put the schema in the user prompt. The prompt lists the exact keys: hook, title, scenes (each with on_screen_text, voiceover and image_prompt), caption, hashtags, cta, ai_disclosure and pillar. It says to return only a JSON object, with no fences and no commentary, and spells out the limits: four scenes, a maximum of nine words for the hook, and a total voiceover length.
  3. Parse defensively. The parser trims the text, strips a leading code fence if there is one, then slices from the first { to the last } before calling JSON.parse. That handles the common case of a polite preamble.
  4. Validate the parts you depend on. After parsing, the code checks that scenes is an array with at least three entries and throws otherwise. It then trims to five scenes at most, lowercases hashtags and removes any leading #, and caps them at ten.
  5. Force the fields that must never vary. ai_disclosure is set to true in code regardless of what the model said, and pillar falls back to the topic’s pillar if it is missing. Rules that matter should not depend on the model remembering them.
  6. Retry, and cool down. The function makes up to three attempts. The first uses temperature 0.7, and the retries use 0.4, so a failed parse gets a more conservative second try. If all three fail, it throws one clear error that includes the last parse message.

What breaks

The model ignores a count. Asking for exactly four scenes does not guarantee four. The factory only enforces a floor of three and a ceiling of five, so a script with a different count still goes through. If you need an exact count, check it and retry.

Trailing prose with braces. Slicing between the first and last curly brace is a heuristic. If the model adds a closing remark that contains a brace, the slice will include it and parsing fails. The retry usually fixes it, but it is a reason to keep the instructions firm about “only a JSON object”.

Silent rule drift. The prompt says the voiceover should be a certain length and that every claim must be verifiable, but the code does not measure either. Those checks are on me when I approve each reel on Telegram. Anything you can check in code, check in code.

My take

Add a small schema validator (for example, a few lines that check every scene has all three string fields) before you spend money on images. It is cheap, and it moves failures to the start of the pipeline where they cost nothing.

Try it

The free workflows and the tools I use are at wrenbench.com.

More builds and the tools behind them: wrenbench.com/links