блоки 2 и 3 доработки интерфейса системы тестирования

This commit is contained in:
Константин Лебединский
2026-04-29 21:06:17 +05:00
parent eff3fda5b0
commit bba96f8f9f
37 changed files with 4440 additions and 1292 deletions
+105
View File
@@ -290,13 +290,80 @@ def improve_test_full(test_title: str, test_description: str, questions: list[di
return {'items': items}
def generate_question_hint(
*,
question_text: str,
options: list[dict],
) -> str:
"""Универсальная подсказка к вопросу: 2–4 предложения, объясняет правильный ответ."""
cfg = get_llm_config()
if cfg is None:
return ''
correct_list = '; '.join(o['text'] for o in options if o.get('isCorrect'))
all_list = '; '.join(o['text'] for o in options)
system = (
'Ты опытный преподаватель. Отвечай по-русски, кратко (2–4 предложения), '
'без markdown и без вступлений. Объясни почему правильный вариант — правильный.'
)
user = (
f'Вопрос: {question_text}\n'
f'Варианты: {all_list}\n'
f'Правильный ответ: {correct_list or ""}\n\n'
'Дай краткое объяснение для подсказки во всплывающем окне.'
)
try:
raw = chat_completion_text_content(cfg, system, user, 0.3, as_json=False)
return (raw or '').strip()
except Exception as e:
import logging
logging.getLogger(__name__).warning('generate_question_hint failed: %s', e)
return ''
def explain_answer(
*,
question_text: str,
options: list[dict],
selected_texts: list[str],
is_correct: bool,
) -> str:
"""Генерирует короткое объяснение результата ответа на вопрос (для попапа подсказки)."""
cfg = get_llm_config()
if cfg is None:
return ''
correct_list = '; '.join(o['text'] for o in options if o.get('isCorrect'))
sel_list = '; '.join(selected_texts) if selected_texts else '(ничего не выбрано)'
verdict = 'верно' if is_correct else 'неверно'
system = (
'Ты опытный преподаватель. Отвечай по-русски, кратко (2–4 предложения). '
'Объясни почему правильный ответ именно такой, без лишней воды и без markdown.'
)
user = (
f'Вопрос: {question_text}\n'
f'Правильный ответ: {correct_list or ""}\n'
f'Ответ ученика ({verdict}): {sel_list}\n\n'
'Дай краткое объяснение для подсказки во всплывающем окне.'
)
try:
raw = chat_completion_text_content(cfg, system, user, 0.3, as_json=False)
return (raw or '').strip()
except Exception as e:
import logging
logging.getLogger(__name__).warning('explain_answer failed: %s', e)
return ''
def generate_or_rephrase_question(
test_title: str,
test_description: str,
question_text: str,
options_count: Any,
has_multiple_answers: bool,
mode: str | None = None,
existing_options: list[dict] | None = None,
) -> dict:
import json as _json
cfg = _require_cfg()
try:
n = int(float(options_count))
@@ -308,6 +375,43 @@ def generate_or_rephrase_question(
topic = (((test_title or '').strip() or 'Тест') + '. ' + (test_description or '').strip()).strip()
qt = (question_text or '').strip()
# ── Режим дистракторов: есть вопрос + часть вариантов пуста ─────────────
if qt and mode == 'distractors' and existing_options:
filled = [o for o in existing_options if (o.get('text') or '').strip()]
empty_count = len([o for o in existing_options if not (o.get('text') or '').strip()] )
if empty_count > 0:
filled_lines = '\n'.join(
f'- {"" if o.get("isCorrect") else ""} {o["text"]}'
for o in filled
) or '(нет)'
system = (
'Ты составитель учебных тестов. Отвечай ТОЛЬКО JSON: '
f'{{"options": [{{"text": string, "isCorrect": false}}, ...]}}'
f'ровно {empty_count} объекта в массиве. '
'Все тексты на русском, без нумерации, без кавычек.'
)
user = (
f'Тема теста: {topic}\n\n'
f'Вопрос: {qt}\n\n'
f'Уже заполненные варианты:\n{filled_lines}\n\n'
f'Придумай ровно {empty_count} правдоподобных, но НЕВЕРНЫХ дистракторов '
f'(isCorrect: false), которые не повторяют уже существующие варианты '
f'и выглядят похоже на реальные ответы.'
)
raw = chat_completion_text_content(cfg, system, user, 0.45)
parsed = parse_json_from_llm_text(raw)
opts = []
if isinstance(parsed, dict):
opts = parsed.get('options') or []
elif isinstance(parsed, list):
opts = parsed
opts = [
{'text': str(o.get('text') or '').strip(), 'isCorrect': False}
for o in opts if (o.get('text') or '').strip()
][:empty_count]
return {'mode': 'distractors', 'text': qt, 'options': opts}
# ── Режим улучшения: вопрос есть → только переформулировать текст ────────
if qt:
system = (
'Ты редактор учебных материалов. Отвечай ТОЛЬКО JSON: {"text": string} — '
@@ -325,6 +429,7 @@ def generate_or_rephrase_question(
raise LlmError('Пустой text в ответе модели.', code='llm_shape')
return {'mode': 'rephrase', 'text': text}
# ── Полная генерация: вопрос пуст ────────────────────────────────────────
system = (
'Ты составитель тестов. Отвечай ТОЛЬКО JSON: {"text", "hasMultipleAnswers", '
'"options": [{ "text", "isCorrect" }]}. Все на русском.'