Every developer using Claude, ChatGPT, or DeepSeek to write technical papers, documentation, or math proofs runs into the same wall: LLM-generated LaTeX is notoriously sloppy.
LLMs don't run compilers. They guess the next token. That means when you ask an AI for a quick equation breakdown, you often get a mix of raw LaTeX environments, broken delimiters, and unescaped characters that choke standard renderers like MathJax, KaTeX, or Pandoc.
If you’ve ever pasted AI output into Obsidian, Quarto, or a custom web app and watched the rendering break instantly, here is why it happens—and how to fix it cleanly.
*The 3 Big Reasons LLM Math Output Breaks
*
- Delimiter Mismatches ([ ... ] vs $$) LLMs love switching back and forth between LaTeX display style [ ... ], TeX double-dollars $$ ... $$, and inline brackets ( ... ). Many Markdown parsers (including GitHub-flavored Markdown and static site generators) expect clean, unified $ and $$ boundaries. **
- Hallucinated Packages & Macros** Ask an LLM for a multi-line matrix proof, and it might throw in \begin{align*} inside a block that KaTeX tries to parse as inline math, or use custom macros like \argmax without including \DeclareMathOperator.
3. Unescaped Markdown Special Characters
When an LLM outputs LaTeX commands containing underscores (_) or carets (^) inside a Markdown code block or text stream, the Markdown parser often intercepts them first—turning your subscripts into tags before LaTeX ever sees them.
Quick Python Fix: Cleaning LLM Math Delimiters
If you're ingesting LLM text via API, you can run a pre-processing pass to normalize math delimiters before sending them to your frontend parser:import re
def normalize_llm_math(text: str) -> str:
# Convert display brackets [ ... ] to standard $$ ... $$
text = re.sub(r'\[\s*', '\n$$\n', text)
text = re.sub(r'\s*\]', '\n$$\n', text)
# Convert inline brackets ( ... ) to standard $ ... $
text = re.sub(r'\(\s*', '$', text)
text = re.sub(r'\s*\)', '$', text)
Clean up empty block artifacts left by the LLM
text = re.sub(r'\$\$\s*\$\$', '', text)
Example
raw_ai_output = r"The variance is given by: [ \sigma^2 = \frac{1}{N} \sum_{i=1}^{N} (x_i - \mu)^2 ]"
print(normalize_llm_math(raw_ai_output))
Automated Parsing & Preflighting
Writing custom regex rules works fine for simple inline math, but once you start dealing with complex tables, full .tex documents, and mismatched BibTeX keys, manual scripts get messy fast.
To automate this whole cleanup and preflight process, I’ve been working on stemdoc.org.
It acts as a conversion and syntax validation layer specifically for STEM documents. It catches broken environments, resolves syntax mismatches, and converts raw LaTeX/LLM streams into clean Markdown, Typst, or Word formats without having to manually patch Pandoc ASTs.
How are you handling AI math rendering?
Are you running regex pipelines on your API responses, relying on frontend MathJax hacks, or doing something else? Let’s chat in the comments!

Top comments (1)
I totally understand the pain of dealing with LLM-generated LaTeX output, especially when it comes to delimiter mismatches and unescaped Markdown special characters. The
normalize_llm_mathfunction you provided is a great starting point for addressing these issues, and I appreciate the example use case. One potential improvement could be to add additional rules for handling hallucinated packages and macros, such as detecting and removing unused\DeclareMathOperatordeclarations. Have you considered integrating this function with popular Markdown parsers like GitHub-flavored Markdown or static site generators to provide a more seamless experience for users?