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