cd /news/ai-tools/tg-rich-converter-streaming-llm-mark… · home topics ai-tools article
[ARTICLE · art-135295] src=github.com ↗ pub= topic=ai-tools verified=true sentiment=↑ positive

Tg-Rich-Converter: Streaming LLM Markdown and LaTeX to Telegram Bot API 10.1

A new zero-dependency Python library, tg-rich-converter, converts LLM Markdown, LaTeX formulas, reasoning tags, and tables into native Telegram Bot API 10.1 Rich HTML for the sendRichMessage endpoint. The library supports Telegram's 32,768-character Rich Message limit, native tables, inline and display LaTeX via tg-math and tg-math-block tags, and expandable details blocks for reasoning models including DeepSeek-R1, OpenAI o1/o3, Qwen, and Gemini. It is installable via pip install tg-rich-converter and ships with a tg-rich CLI, a streaming mode that auto-balances unclosed code blocks, think tags, and LaTeX delimiters to prevent Telegram 400 Bad Request parse errors, and a local preview.html generator.

read7 min views2 publishedSep 20, 2026
Tg-Rich-Converter: Streaming LLM Markdown and LaTeX to Telegram Bot API 10.1
Image: Michielbdejong (auto-discovered)

A lightweight, zero-dependency Python library that converts standard LLM Markdown, LaTeX formulas, thinking processes, and tables into native Telegram Bot API 10.1+ Rich HTML (sendRichMessage).

Starting with Telegram Bot API 10.1, Telegram introduced Rich Messages (sendRichMessage) supporting:

  • Messages up to 32,768 characters (no more 4,096-character limit!).
  • Native interactive tables with borders and striping.
  • Native LaTeX math rendering (both inline and display equations).
  • Expandable spoiler/details blocks for reasoning models (DeepSeek-R1 ,OpenAI o1/o3 ,Qwen ,Gemini ).
  • Native lists and advanced typography (<ul> ,<ol> ,<u> ,<mark> ).

However, LLMs (OpenAI, Anthropic, DeepSeek, Ollama) still output plain Markdown and LaTeX. tg-rich-converter bridges this gap seamlessly in a single function call or via the CLI command line tool.

  • 🌊 LLM Streaming Mode (streaming=True): Auto-balances unclosed code blocks (``` ), reasoning tags (<think> ), unclosed LaTeX formulas ($$ /$ ), and unclosed inline styles (** ,|| ,++ ,== ,~~ ) during token-by-token streaming, completely preventing Telegram400 Bad Request: can't parse entities errors on live message edits.
  • 📊 Robust Native Tables: Converts standard Markdown pipe tables into<table bordered striped> with column alignment (left ,center ,right ). Safely handles formulas with pipes ($|\psi\rangle$ ,$|x| \ge 0$ ) and escaped pipes (\| ) inside table cells without breaking columns.
  • 🧮 LaTeX Math: Converts$$...$$ into<tg-math-block> and$x$ into<tg-math> .
  • 🧠 Customizable AI Thinking Blocks: Converts<think>...</think> tags from reasoning models into expandable<details><summary>Размышления</summary>...</details> blocks with configurable summary titles.
  • 🛡️ HTML-Safe: Automatically escapes raw< ,> , and& in regular text (e.g. mathematical conditions likex < 5 andy > 10 ), completely preventing Telegram API400 Bad Request: can't parse entities errors.
  • 📋 Native Lists: Converts unordered (- ,* ,+ ) and ordered (1. ) lists into native<ul> and<ol> tags, avoiding conflicts between asterisk bullet markers and italics.
  • 🎨 Rich Typography: Supports bold (** ), italic (* /_ ), strikethrough (~~ ), underline (++text++ ), highlight (==text== ), and spoilers (||spoiler|| ) with snake_case protection.
  • 💻 Syntax-Highlighted Code: Converts markdown code fences into<pre><code class="language-..."> preserving language classes, indentation, and copy buttons.
  • ✂️ Smart Message Splitter: Safely splits long texts up to 32,768 (Telegram Rich limit) or 4,096 (Classic limit) characters. Automatically closes and re-opens nested tags with attributes (<pre><code class="..."> ,<blockquote> ), and protects LaTeX formulas from fragmentation.
  • 👁️ Local HTML Preview: Instantly generates a standalonepreview.html styled with authentic Telegram Web dark theme and KaTeX client-side math rendering to visually inspect output without launching a bot.
  • 🚀 Built-in CLI (tg-rich): Fast command-line utility with TrueColor ANSI logo, preview generator, auto-opening in browser, pipeline support (stdin /stdout ), and batch message splitter.
  • Thread-Safe & Zero Dependencies: Pure standard Python (re ,html ,argparse ). Fully reentrant and async-safe for high-concurrency bot environments.
pip install tg-rich-converter
python
from tg_rich_converter import to_rich

llm_output = """

| Algorithm | State | Complexity | Option |
|:----------|:-----:|:----------:|-------:|
| Linear Search | $N$ items | $O(N)$ | Mode A \\| B |
| State Vector  | $|\\psi\\rangle$ | $O(1)$ | Basic |

### Key Formula
$$|\\psi\\rangle = \\alpha |0\\rangle + \\beta |1\\rangle$$

Stability requires delta < 0.05 and alpha > 0.

<think>
Evaluating time complexity and qubit entanglement...
</think>
"""

rich_html = to_rich(llm_output)

rich_html_en = to_rich(llm_output, thinking_summary="Reasoning Process")

When streaming LLM responses token-by-token (OpenAI, DeepSeek-R1, Anthropic Claude, Ollama), intermediate tokens frequently contain unfinished Markdown/LaTeX structures:

  • Unclosed code fences: python\ndef run():`` (missing closing )
  • Open reasoning blocks: <think>Analyzing steps... (missing</think> )
  • Half-written formulas: $E = mc^2 or$$\int_0^\infty
  • Incomplete typography: **bold text ,||secret key ,++underlined

Passing streaming=True automatically balances and virtually closes all open structures in LIFO order for every frame:

import time
from tg_rich_converter import to_rich

sent_msg = await bot.send_rich_message(
    chat_id=chat_id,
    rich_message={"html": "<i>⏳ Thinking...</i>"}
)

buffer = ""
last_edit_time = time.time()
THROTTLE_SECONDS = 0.7  # Recommended: 0.6 - 0.8s to avoid Telegram 429 Flood Limits

async for chunk in openai_client.chat.completions.create(..., stream=True):
    buffer += chunk.choices[0].delta.content or ""
    now = time.time()
    
    if now - last_edit_time >= THROTTLE_SECONDS:
        safe_frame_html = to_rich(buffer, streaming=True)
        await bot.edit_message_text(
            chat_id=chat_id,
            message_id=sent_msg.message_id,
            rich_message={"html": safe_frame_html}  # Must pass rich_message, NOT text/parse_mode!
        )
        last_edit_time = now

final_html = to_rich(buffer, streaming=False)
await bot.edit_message_text(
    chat_id=chat_id,
    message_id=sent_msg.message_id,
    rich_message={"html": final_html}
)
  • Cause: CallingsendMessage oreditMessageText with legacytext="<details>..." andparse_mode="HTML" . The legacy Telegram parser does not support<details> ,<tg-math> , or<table> .
  • Fix: Use the Telegram Bot API 10.1+ Rich Message format withrich_message={"html": ...} :
await bot.edit_message_text(chat_id=chat_id, message_id=msg_id, text=rich_html, parse_mode="HTML")

await bot.edit_message_text(chat_id=chat_id, message_id=msg_id, rich_message={"html": rich_html})
  • Cause: SendingeditMessageText on every single incoming LLM token.

  • Fix: Throttle live edits to once every0.6 – 0.8 seconds (or every 40–60 characters).

  • Fix: Customize the default title viathinking_summary :

html_output = to_rich(markdown_text, thinking_summary="Chain of Thought")

After installation, the tg-rich command is available in your terminal:

Convert a markdown file and open the interactive Telegram preview directly in your default browser:

tg-rich prompt_response.md --preview --open
tg-rich document.md -o output.html
cat llm_output.md | tg-rich > telegram_message.html

Split a large document into chunks respecting Telegram character limits:

tg-rich big_report.md --split

tg-rich big_report.md --split --limit 4096 -o chunk.html
Flag Description Default
input_file Path to markdown file (or - / pipe for stdin) -
-o, --output FILE Write converted HTML to file instead of stdout stdout
-p, --preview [FILE] Generate standalone preview.html with KaTeX & Telegram Dark theme preview.html
--open Automatically open the preview in default web browser False
-s, --split Split long message into safe Telegram chunks False
-l, --limit INT Maximum character length for splitting 32768
-t, --thinking-summary TEXT Custom header for <think> reasoning blocks Размышления
--lang {ru,en} Interface and error message language auto
-q, --quiet Suppress banner and progress messages in stderr False
-v, --version Display current library version

When LLM output exceeds Telegram limits (32,768 chars for Rich Messages or 4,096 chars for standard messages), naive slicing breaks open HTML tags and crashes the bot. Use split_rich_message:

from tg_rich_converter import split_rich_message

chunks = split_rich_message(
    long_llm_response,
    max_length=32768,      # 32,768 for Rich Messages (default) or 4,096 for Classic
    is_markdown=True,      # Automatically runs to_rich()
    thinking_summary="Reasoning"
)

for chunk in chunks:
    await bot.send_rich_message(chat_id=chat_id, rich_message={"html": chunk})

Visualize how your message will look in Telegram Desktop/Mobile without running a bot or sending messages:

from tg_rich_converter import save_preview

save_preview(
    llm_output,
    file_path="preview.html",
    title="LLM Telegram Preview"
)

Double click preview.html to open it in your browser!

from aiogram import Bot
from tg_rich_converter import to_rich

bot = Bot(token="YOUR_BOT_TOKEN")

rich_html = to_rich(llm_response)
await bot.send_rich_message(
    chat_id=chat_id,
    rich_message={"html": rich_html}
)
python
import telebot
from tg_rich_converter import to_rich

bot = telebot.TeleBot("YOUR_BOT_TOKEN")

rich_html = to_rich(llm_response)
bot.send_rich_message(
    chat_id=chat_id,
    rich_message={"html": rich_html}
)
python
import requests
from tg_rich_converter import to_rich

rich_html = to_rich(llm_response)

requests.post(
    f"https://api.telegram.org/bot{BOT_TOKEN}/sendRichMessage",
    json={
        "chat_id": chat_id,
        "rich_message": {
            "html": rich_html
        }
    }
)

Run the comprehensive test suite locally (44 tests, 100% pass):

pytest

Test real-time LLM token-by-token streaming with rate-limiting throttle (0.7s) directly in your Telegram chat:

python demo_streaming.py --token "YOUR_BOT_TOKEN" --chat-id "YOUR_CHAT_ID"

export BOT_TOKEN="YOUR_BOT_TOKEN"
export CHAT_ID="YOUR_CHAT_ID"
python demo_streaming.py

$env:BOT_TOKEN="YOUR_BOT_TOKEN"; $env:CHAT_ID="YOUR_CHAT_ID"; python demo_streaming.py

Validate intermediate streaming frames in terminal without sending requests to Telegram:

python demo_streaming.py

MIT License. Free for commercial and personal use.

── more in #ai-tools 4 stories · sorted by recency
── more on @telegram 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/tg-rich-converter-st…] indexed:0 read:7min 2026-09-20 ·