You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

90 lines
4.3 KiB

"""Генерация черновика теста из извлечённого текста (порт части `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],
}