{"slug": "i-stopped-fighting-prompts-locking-down-markdown-with-jinja2", "title": "I Stopped Fighting Prompts: Locking Down Markdown with Jinja2", "summary": "A developer solved malformed Markdown output from LLMs by switching from probabilistic prompt engineering to a deterministic pipeline. The LLM now outputs structured JSON data, which is rendered into Markdown using Jinja2 templates, with regex-based post-processing as a safety net. The approach decouples content generation from formatting, ensuring consistent syntax.", "body_md": "We faced a recurring issue in our content generation pipeline: the LLM frequently outputted malformed Markdown. Unclosed code blocks, broken list levels—you name it. Relying solely on Prompt engineering became a game of whack-a-mole that we couldn't win.\n\nThe core problem? Asking an LLM to generate Markdown is a probabilistic process. A Prompt is a \"soft constraint.\" No matter how well you phrase it, a slight token fluctuation can break the syntax, causing frontend crashes.\n\nWe realized we were violating the Single Responsibility Principle. We were asking the model to do two jobs:\n\nModels are great at semantics but terrible at strict formatting rules. So, we decoupled them.\n\nInstead of asking the LLM to write Markdown, we switched to JSON output and let Jinja2 handle the rendering.\n\n**Before (Probabilistic):**\n\n```\n# LLM generates raw text - hope for the best\nprompt = \"Write an article about {topic} in Markdown format.\"\nresponse = llm.generate(prompt)\n```\n\n**After (Deterministic):**\n\n```\n# LLM outputs structured data only\nprompt = \"Output data about {topic} in JSON format.\"\njson_data = llm.generate(prompt) \n\n# Jinja2 enforces the syntax\nmd_content = jinja_env.get_template('article.md').render(data=json_data)\n```\n\nThis moved the formatting from a \"maybe\" to a \"definitely.\" If the template is correct, the Markdown is correct.\n\nJust in case (and for legacy compatibility), we added a post-processing layer with regex validation. It acts as a safety net for unclosed code fences.\n\n``` python\ndef sanitize_markdown(text):\n    # Check if code blocks are properly closed\n    if not re.search(r'```\n\n[\\s\\S]*?\n\n```', text):\n        # Attempt to wrap raw code in fences\n        text = re.sub(r'(^.*$)', r'```\n\n\\n\\1\\n\n\n```', text)\n    return text\n\nfinal_markdown = sanitize_markdown(llm_output)\n```\n\nWhile fixing the text generation, we also noticed a logic gap in our stock data queries. We treated A-shares, ETFs, and Hong Kong stocks identically. This caused failures because:\n\n`.SH`\n\nor `.SZ`\n\nsuffixes.We implemented a router at the query entry point:\n\n``` python\ndef get_stock_data(code):\n    # Route HK stocks to specific API\n    if is_hk_stock(code):\n        return hk_api.get_price(code)\n\n    # Append suffix for ETFs if missing\n    elif \".SH\" not in code and \".SZ\" not in code:\n        code = f\"{code}.SH\" \n\n    return api.get_price(code)\n```\n\nBy shifting from \"Prompt Optimization\" to \"Engineering Hard Constraints\":\n\nIf you are fighting with LLMs to output perfect HTML or Markdown, stop. Use the LLM for what it's good at—generating structured JSON data—and use a template engine like Jinja2 to enforce the view layer. It turns a probabilistic headache into a deterministic pipeline.", "url": "https://wpnews.pro/news/i-stopped-fighting-prompts-locking-down-markdown-with-jinja2", "canonical_source": "https://dev.to/quarktimes/i-stopped-fighting-prompts-locking-down-markdown-with-jinja2-2951", "published_at": "2026-06-15 03:03:59+00:00", "updated_at": "2026-06-15 03:10:51.204582+00:00", "lang": "en", "topics": ["large-language-models", "developer-tools", "generative-ai"], "entities": ["Jinja2", "LLM"], "alternates": {"html": "https://wpnews.pro/news/i-stopped-fighting-prompts-locking-down-markdown-with-jinja2", "markdown": "https://wpnews.pro/news/i-stopped-fighting-prompts-locking-down-markdown-with-jinja2.md", "text": "https://wpnews.pro/news/i-stopped-fighting-prompts-locking-down-markdown-with-jinja2.txt", "jsonld": "https://wpnews.pro/news/i-stopped-fighting-prompts-locking-down-markdown-with-jinja2.jsonld"}}