Skip to main content

llm-markdown-sanitizer (Python)

PyPI Python versions Downloads CI License: MIT

Fix broken markdown that LLMs generate — tables, lists, headings, emphasis, code fences, quotes. Zero dependencies, one function.

A Java binding with the same behavior is also available — see the repository root for both.

Install

PyPI page: https://pypi.org/project/llm-markdown-sanitizer/ (release history, file hashes, full metadata).

Requires Python 3.9+. No other dependencies get pulled in.

pip install llm-markdown-sanitizer

Using uv (if that's your workflow):

uv add llm-markdown-sanitizer

Using a virtual environment (recommended for any real project):

python3 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install llm-markdown-sanitizer

Pin a specific version if you want reproducible builds — see the PyPI release history for available versions:

pip install "llm-markdown-sanitizer==0.2.5"

Add it to requirements.txt / pyproject.toml the normal way:

llm-markdown-sanitizer>=0.2.5

Verify it installed correctly:

python -c "from llm_markdown_sanitizer import clean_markdown; print(clean_markdown('**hi**there'))"
# **hi** there

Use

The whole API is one function:

from llm_markdown_sanitizer import clean_markdown

clean_markdown("**Note**this needs a space")
# "**Note** this needs a space"

clean_markdown("| A | B | | --- | --- | | 1 | 2 |")
# "| A | B |\n| --- | --- |\n| 1 | 2 |"

Default settings handle the common failure modes without additional configuration.

In a FastAPI endpoint

A typical place to call this is right before a stored or freshly-generated LLM response goes out to a client:

from fastapi import FastAPI
from llm_markdown_sanitizer import clean_markdown

app = FastAPI()

@app.get("/lectures/{lecture_id}/summary")
def get_summary(lecture_id: int):
    raw = db.get_ai_summary(lecture_id)  # however you fetch/generate it
    return {"summary": clean_markdown(raw)}

Streaming/multi-part LLM responses

Some SDKs return responses as a list of {"text": ...}-shaped chunks instead of one string. clean_markdown accepts that directly:

chunks = [{"text": "# Hello"}, {"text": "\n\nWorld"}]
clean_markdown(chunks)
# "# Hello\n\nWorld"

Why this exists

Ask an LLM to answer in markdown and eventually you'll get: the whole answer wrapped in a stray ```markdown fence (or left unclosed at the end), **bold**text glued directly onto the next word, a heading missing its space or its blank line, curly quotes inside a code sample, list indentation that's inconsistent within the same response, and tables that are either collapsed onto one line or missing a separator row. Rendering that output as-is breaks the UI.

clean_markdown() fixes all of the above in a single left-to-right pass over the text — no whole-string regex backtracking, so it stays fast on long documents.

What it fixes

Problem Before After
Wrapping code fence ```markdown\n# Title\n``` # Title
<br> outside tables Line one<br>Line two Line one\nLine two (left untouched inside table cells, where it's usually intentional)
Bold glued to text **Note**this breaks **Note** this breaks
Inconsistent list indent mixed 2/3/tab indents normalized to 4 spaces per nesting level
Collapsed table | A | B | | --- | --- | | 1 | 2 | proper one-row-per-line table
Broken table (no separator / mismatched columns) renders as a wall of | dropped instead of rendering broken
| inside a table cell (escaped or in inline code) miscounted as an extra column, table dropped preserved, table kept
Missing blank line before a list/heading renders as a paragraph continuation blank line inserted
Missing space after # #Heading stays plain text # Heading
Smart quotes inside code `print(“hi”)` fails to parse `print("hi")`
Unclosed trailing code fence rest of the answer swallowed as code closing fence appended

Protecting your own syntax

If your prompts produce custom tokens — a [[wiki]]-style syntax, template placeholders, etc. — that the cleanup passes above might mangle, they can be excluded explicitly:

import re

clean_markdown(text, protect_patterns=[re.compile(r"\[\[.*?\]\]")])

CLI / pre-commit

There's also a thin CLI wrapper around clean_markdown(), for tooling that expects a runnable command rather than a library import:

python -m llm_markdown_sanitizer file1.md file2.md

Rewrites each file in place and prints the ones it changed. To run it automatically before every commit, add this repo as a pre-commit hook (requires pip install llm-markdown-sanitizer in the environment pre-commit runs in):

repos:
  - repo: https://github.com/stlahxm/llm-markdown-sanitizer
    rev: python-v0.2.5
    hooks:
      - id: llm-markdown-sanitizer

Origin

Extracted from the markdown-cleanup layer of a production RAG service, after months of hardening against real LLM output. The domain-specific parts — a custom wiki syntax, a Korean-language note pattern — were removed in favor of the general protect_patterns mechanism above, so callers can supply their own domain syntax instead.

Contributing

Bug fixes and small improvements are welcome. No CLA/DCO required — see CONTRIBUTING.md for guidelines and how to run the test suite locally. AI coding agents should pick up AGENTS.md automatically.

License

MIT

Metadata

Release files for llm-markdown-sanitizer 0.2.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for llm-markdown-sanitizer 0.2.5
File Size Uploaded
llm_markdown_sanitizer-0.2.5.tar.gz 19.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for llm-markdown-sanitizer 0.2.5
File Interpreter ABI Platform
llm_markdown_sanitizer-0.2.5-py3-none-any.whl Python 3 none any Details

Total release size: 38.2 kB

Release files / llm_markdown_sanitizer-0.2.5.tar.gz

Download URL llm_markdown_sanitizer-0.2.5.tar.gz
Size 19.8 kB
Tags Source
SHA-256 checksum
How to use checksums
23afb992d2a4d648a70e5c3197f3d8461efd4e997b90124e21bc18328f24b75a
BLAKE2b-256 checksum
How to use checksums
7ab1cc40405c8e5f532271580beb1eaf9b1f37cd74dc6d3d8150f158638a50c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Release files / llm_markdown_sanitizer-0.2.5-py3-none-any.whl

Download URL llm_markdown_sanitizer-0.2.5-py3-none-any.whl
Size 18.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5d60c3eb16430dc23ab06888005e6a72c80a7d1a0ca1a0915ba4c95592f20e50
BLAKE2b-256 checksum
How to use checksums
bc2b430310d9071f2a01123336889889ada1a6af851500341d080f2778af61fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.5 This release

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page