--- description: Parse, filter, and stream AI-generated Markdown safely in Python with Wenmode's AST, explicit rules, and safer HTML renderer defaults. --- (ai-markdown)= # AI-generated Markdown ```{rst-class} lead Parse, filter, and stream Markdown from LLMs before rendering it into your application. ``` --- LLMs often return Markdown because it is compact, readable, and easy to display. Do not render generated Markdown directly in a production application. You may need to: - restrict the allowed Markdown syntax; - remove images, raw HTML, or unknown extension nodes; - validate link and image URLs; - stream a preview before the complete answer is available; - store AST data for search, citations, or analytics. Wenmode supports this workflow because parsing, AST inspection, filtering, and rendering are separate steps. ## Choose a rule set Use the `streaming` preset to render the response as tokens arrive. It supports common block and inline syntax, tables, strikethrough, direct links, and direct images, but disables features that need the complete document, such as reference-style links and footnotes. ```python from wenmode import Wenmode from wenmode.presets import streaming wen = Wenmode(streaming) ``` If the answer uses reference-style links, footnotes, or document-wide transforms, parse the complete answer with `commonmark`, `github`, or a custom rule list before you render it. ## Filter nodes before rendering Use `Parser.parse_iter()` with a streaming-compatible preset when each completed top-level block can be filtered and rendered independently. ```python from collections.abc import Iterable, Iterator from wenmode import HTMLRenderer, Wenmode from wenmode.nodes import Node from wenmode.presets import streaming ALLOWED_NODE_TYPES = { 'blockquote', 'break', 'code', 'delete', 'emphasis', 'heading', 'inlineCode', 'link', 'list', 'listItem', 'paragraph', 'root', 'strong', 'table', 'tableCell', 'tableRow', 'text', 'thematicBreak', } wen = Wenmode(streaming, renderer=HTMLRenderer()) def filter_node(node: Node) -> Node | None: if node.type not in ALLOWED_NODE_TYPES: return None children = getattr(node, 'children', None) if isinstance(children, list): children[:] = [ child for child in (filter_node(child) for child in children) if child is not None ] return node def iter_filtered_nodes(source: str | Iterable[str]) -> Iterator[Node]: for node in wen.parser.parse_iter(source): filtered = filter_node(node) if filtered is not None: yield filtered def render_ai_markdown(source: str | Iterable[str]) -> Iterator[str]: yield from wen.renderer.render_iter(iter_filtered_nodes(source)) ``` The allowlist above removes images and raw HTML because `image` and `html` are not included in `ALLOWED_NODE_TYPES`. ## Render streamed output The function accepts a string, a line iterator, or another iterable that yields Markdown chunks. ```python markdown = ''' # Answer Use **Markdown** safely.  ''' html = ''.join(render_ai_markdown(markdown)) assert '