From 32a24a1cf64a05b6f3f3cd6eaa13f5ab9cf881c1 Mon Sep 17 00:00:00 2001 From: Markov Andrey Date: Tue, 30 Jun 2026 09:10:17 +0000 Subject: [PATCH] Add new file --- core/functions/check_spelling.py | 135 +++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 core/functions/check_spelling.py diff --git a/core/functions/check_spelling.py b/core/functions/check_spelling.py new file mode 100644 index 0000000..af4bf35 --- /dev/null +++ b/core/functions/check_spelling.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +""" +Модуль проверки орфографии и пунктуации в тексте документа. +Разбивает документ на чанки, обрабатывает каждый чанк через GigaChat, +собирает словарь замен (старое -> новое) и список изменений. + +АДАПТАЦИЯ: теперь функция принимает промпт и конфиг как аргументы. +""" + +import asyncio +import logging +import re +from core.utils.text_utils import split_into_chunks +from core.services.giga_client import GigaClient + +logger = logging.getLogger(__name__) + + +async def check_spelling( + giga: GigaClient, + original_text: str, + prompt_text: str, + config, +) -> tuple[dict, list[str]]: + """ + Проверяет орфографию документа, возвращает замены и список изменений. + + Аргументы: + giga (GigaClient): клиент GigaChat + original_text (str): исходный текст документа + prompt_text (str): содержимое промпта проверки орфографии + config (BotConfig): объект конфигурации + + Возвращает: + tuple: (словарь_замен, список_изменений_в_виде_строк) + В случае, если парсинг изменений не удался, возвращается: + ({"_raw": сырой_ответ_LLM}, ["Сообщение для пользователя"]) + """ + if not prompt_text: + logger.error("Промпт проверки орфографии не загружен") + return {}, ["❌ Промпт проверки орфографии не загружен."] + + # Параметры чанкинга из конфига + chunk_size_tokens = config.chunk_size_tokens + overlap_tokens = config.overlap_tokens + approx_chunk_chars = config.chunking_approx_chunk_chars + approx_overlap_chars = config.chunking_approx_overlap_chars + + # Разбиваем текст на чанки + chunks = split_into_chunks( + original_text, + chunk_size_tokens=chunk_size_tokens, + overlap_tokens=overlap_tokens, + approx_chunk_chars=approx_chunk_chars, + approx_overlap_chars=approx_overlap_chars, + strategy="recursive_split_by_sentences" + ) + logger.info(f"Разбито на {len(chunks)} чанков для проверки орфографии") + + all_replacements = {} + all_changes = [] + temperature = getattr(config, 'spellcheck_temperature', 0.1) + + # Обрабатываем каждый чанк + for idx, chunk in enumerate(chunks): + logger.info(f"Обработка чанка {idx+1}/{len(chunks)}") + full_prompt = f"{prompt_text}\n\nТЕКСТ ДЛЯ ПРОВЕРКИ (часть {idx+1} из {len(chunks)}):\n{chunk}" + + try: + response = await giga.chat( + history=[], + query=full_prompt, + system_prompt=None, + file_id=None, + temperature=temperature + ) + + corrected_match = re.search(r'\[CORRECTED\](.*?)\[/CORRECTED\]', response, re.DOTALL) + changes_match = re.search(r'\[CHANGES\](.*?)\[/CHANGES\]', response, re.DOTALL) + + if changes_match: + changes_text = changes_match.group(1).strip() + for line in changes_text.split('\n'): + line = line.strip() + if not line or line.startswith('#'): + continue + + patterns = [ + r'было\s*["«](.+?)["»]\s*[→-]>\s*стало\s*["«](.+?)["»]', + r'было\s*:\s*["«](.+?)["»]\s*[→-]>\s*стало\s*:\s*["«](.+?)["»]', + r'было\s*["«](.+?)["»]\s*[→-]>\s*(.+)', + r'было\s*:\s*(.+?)\s*[→-]>\s*стало\s*:\s*(.+)', + r'было\s*(.+?)\s*[→-]>\s*стало\s*(.+)' + ] + match = None + for pattern in patterns: + match = re.search(pattern, line, re.IGNORECASE) + if match: + old, new = match.group(1).strip(), match.group(2).strip() + break + + if match: + all_replacements[old] = new + all_changes.append(f'было "{old}" → стало "{new}"') + else: + all_changes.append(line) + + # Если список изменений не распарсился, но есть исправленный текст + if corrected_match and (not changes_match or not changes_match.group(1).strip()): + logger.warning(f"Не удалось распарсить список изменений для чанка {idx+1}, возвращаем сырой ответ") + return {"_raw": response}, [ + f"⚠️ Не удалось распарсить список изменений (чанк {idx+1}). Вот сырой ответ LLM:\n{response}" + ] + + except Exception as e: + logger.error(f"Ошибка при проверке чанка {idx+1}: {e}", exc_info=True) + all_changes.append(f"⚠️ Ошибка в части {idx+1}: {e}") + + await asyncio.sleep(0.5) + + # Удаляем дубликаты + unique_replacements = {} + unique_changes = [] + seen = set() + for old, new in all_replacements.items(): + key = f"{old}→{new}" + if key not in seen: + seen.add(key) + unique_replacements[old] = new + for change in all_changes: + if change not in seen: + seen.add(change) + unique_changes.append(change) + + return unique_replacements, unique_changes \ No newline at end of file