Files
fckbot/core/functions/check_spelling.py
Markov Andrey 32a24a1cf6 Add new file
2026-06-30 09:10:17 +00:00

135 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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