"""Очистка ответа LLM перед озвучкой. По ТЗ: ИИ должен говорить чистым «человеческим» текстом — без звёздочек, решёток, списков, ссылок и эмодзи. Первая линия обороны — системный промпт (запрещает разметку), эта функция — вторая (убирает то, что просочилось). """ from __future__ import annotations import re # Эмодзи и декоративные символы (TTS их либо молчит, либо читает мусором) _EMOJI = re.compile( "[" "\U0001F000-\U0001FAFF" # эмодзи и пиктограммы "\U00002600-\U000027BF" # misc symbols "\U0001F1E6-\U0001F1FF" # флаги "\U00002B00-\U00002BFF" # стрелки/звёзды "\U0000FE00-\U0000FE0F" # variation selectors "\U00002190-\U000021FF" # стрелки "]+" ) _CODE_FENCE = re.compile(r"```[\s\S]*?(```|$)") # Служебный маркер многочастных ответов: [ЧАСТЬ 2 ИЗ 5] — вырезается до озвучки _PART_MARKER = re.compile(r"\s*\[ЧАСТЬ\s+\d+\s+ИЗ\s+\d+\]\s*", re.I) _IMAGE = re.compile(r"!\[([^\]]*)\]\([^)]*\)") _LINK = re.compile(r"\[([^\]]*)\]\([^)]*\)") _HEADER = re.compile(r"^[ \t]{0,3}#{1,6}[ \t]*", re.M) _BOLD = re.compile(r"(\*\*\*|\*\*|__)(?=\S)(.+?\S)\1") _ITALIC = re.compile(r"(?[ \t]?") _HRULE = re.compile(r"(?:^|\n)[ \t]*([-_=*])[ \t]*\1[ \t]*\1[\1 \t=-]*(?:\n|$)") _INLINE_CODE = re.compile(r"`([^`]*)`") _MULTI_WS = re.compile(r"[ \t\f\v]+") _MULTI_NL = re.compile(r"\s*\n\s*") def clean_for_speech(text: str) -> str: """Markdown/эмодзи → одна чистая строка, пригодная для TTS.""" if not text: return "" t = _CODE_FENCE.sub(" ", text) t = _PART_MARKER.sub(" ", t) # служебный маркер части — не для ушей t = _IMAGE.sub(r"\1", t) t = _LINK.sub(r"\1", t) t = _HEADER.sub("", t) t = _BOLD.sub(r"\2", t) t = _ITALIC.sub(r"\2", t) t = _STRIKE.sub(r"\1", t) t = _BULLET.sub(" ", t) t = _NUMBERED.sub(" ", t) t = _BLOCKQUOTE.sub(" ", t) t = _HRULE.sub(" ", t) t = _INLINE_CODE.sub(r"\1", t) t = _EMOJI.sub(" ", t) # Переносы строк — в пробелы (для озвучки это одна речь) t = t.replace("\r", "") t = _MULTI_WS.sub(" ", t) t = _MULTI_NL.sub(" ", t) t = t.strip() # Двойные знаки препинания после чистки («…!» и т.п.) t = re.sub(r"\s+([,.!?;:])", r"\1", t) t = re.sub(r"([,.!?;:])\1+", r"\1", t) return t