Add new file
This commit is contained in:
135
core/functions/check_spelling.py
Normal file
135
core/functions/check_spelling.py
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user