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.1"

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

llm-markdown-sanitizer>=0.2.1

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, **bold**text glued directly onto the next word, 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"\[\[.*?\]\]")])

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

llm_markdown_sanitizer-0.2.1.tar.gz (18.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

llm_markdown_sanitizer-0.2.1-py3-none-any.whl (17.2 kB view details)

Uploaded Python 3

File details

Details for the file llm_markdown_sanitizer-0.2.1.tar.gz.

File metadata

  • Download URL: llm_markdown_sanitizer-0.2.1.tar.gz
  • Upload date:
  • Size: 18.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for llm_markdown_sanitizer-0.2.1.tar.gz
Algorithm Hash digest
SHA256 2059874f821268029fe28bd8c38d4b50c9639e457e8a87eb4447d0f510463ed3
MD5 d353251a29f35600a46ad11abbbbcadd
BLAKE2b-256 1bb9a608d4f3c8831175f2afe199b40dce00effd6fb0aff86751342b575a5262

See more details on using hashes here.

Provenance

The following attestation bundles were made for llm_markdown_sanitizer-0.2.1.tar.gz:

Publisher: python-publish.yml on stlahxm/llm-markdown-sanitizer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file llm_markdown_sanitizer-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for llm_markdown_sanitizer-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ab64ae59c779a0b69a5cb1a38cf18113ce50a9ef4b9e0e7c6c82e5696b4df436
MD5 6efe1ca1489c294668e7738576620e57
BLAKE2b-256 701616721ed5957455c94e3dffc190b8560a6b85916e79ca91e78a454bb437f1

See more details on using hashes here.

Provenance

The following attestation bundles were made for llm_markdown_sanitizer-0.2.1-py3-none-any.whl:

Publisher: python-publish.yml on stlahxm/llm-markdown-sanitizer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page