"""Генерация черновика теста из извлечённого текста (порт части `documentGenService.js`).""" from __future__ import annotations from .draft_validator import ( normalize_draft_to_shape, parse_json_from_llm_text, shuffle_options_in_questions, validate_and_normalize_draft, ) from .llm_client import LlmError, chat_completion_text_content, get_llm_config MAX_EXTRACT_CHARS = 14000 def generation_for_import_document(extracted_text: str, user_hint: str = '', shape: list[dict] | None = None) -> dict: text = (extracted_text or '').strip() if not text: return { 'available': False, 'message': 'Нет извлечённого текста — нечего передавать в модель.', } cfg = get_llm_config() if cfg is None: return { 'available': False, 'message': ( 'Автогенерация выключена: задайте DEEPSEEK_API_KEY или OPENAI_API_KEY ' 'в .env. Превью текста ниже — можно вставить вручную.' ), 'textPreview': text[:4000], } if len(text) > MAX_EXTRACT_CHARS: slice_ = text[:MAX_EXTRACT_CHARS] + '\n\n[…фрагмент обрезан для API]' else: slice_ = text try: system = ( 'Ты помощник для составления тестов. Отвечай ТОЛЬКО одним JSON-объектом ' 'без пояснений. Схема: {"title": string, "description"?: string, ' '"questions": array}. Каждый вопрос: {"text", "hasMultipleAnswers": boolean, ' '"options": [{"text", "isCorrect": boolean}, ...]}. Минимум 2 варианта. ' 'Для одиночного выбора ровно один isCorrect: true. ' 'Текст и формулировки — на русском, по содержанию входного материала.' ) hint_block = f'\n\nДополнительные инструкции от автора теста:\n{user_hint.strip()}' if user_hint and user_hint.strip() else '' shape_block = '' if shape: rows = [] for i, sh in enumerate(shape): if sh.get('hasMultipleAnswers'): rows.append( f'- Вопрос {i + 1}: ровно {sh["optionsCount"]} вариантов, ' f'правильных от {sh.get("minCorrect", 1)} до {sh.get("maxCorrect", sh["optionsCount"])}.' ) else: rows.append(f'- Вопрос {i + 1}: ровно {sh["optionsCount"]} вариантов, ровно 1 правильный.') shape_block = '\n\nСтрого соблюди шаблон:\n' + '\n'.join(rows) user = ( 'Составь тест с вопросами с одним или несколькими правильными ответами ' 'на основе текста:\n\n' + slice_ + hint_block + shape_block ) raw = chat_completion_text_content(cfg, system, user, 0.25) parsed = parse_json_from_llm_text(raw) draft = validate_and_normalize_draft(parsed) if shape: draft = normalize_draft_to_shape(draft, shape) shuffle_options_in_questions(draft['questions']) return { 'available': True, 'message': ( f'Сгенерировано: «{draft["title"]}», вопросов: ' f'{len(draft["questions"])}. Нажмите «Применить сгенерированный черновик».' ), 'draft': draft, } except LlmError as e: return { 'available': False, 'message': f'Генерация не удалась: {e}', 'errorCode': e.code, 'textPreview': text[:4000], } except Exception as e: return { 'available': False, 'message': f'Генерация не удалась: {e}', 'errorCode': 'unknown', 'textPreview': text[:4000], }