Перейти к содержимому

Объяснимость — «почему именно эта задача»

Главное правило учительского доверия:

Если я не понимаю, почему система предлагает Ивану задачу 147 — я ей не доверюсь.

То же верно для внешней аудитории: без понятного «почему именно эта задача» остаётся общий шум про «ещё одну модель». Поэтому объяснимость — не nice-to-have, а ключевой критерий.

Не используем LLM (Claude / GPT) для объяснения числовых фактов. Причина — модели иногда галлюцинируют:

  • называют не те микро-навыки;
  • путают P(L)P(L) с P(solve)P(\text{solve});
  • придумывают «причины», которые не следуют из данных.

В контексте, где объяснение должно быть точным, это неприемлемо. Поэтому факты собираем детерминированно, шаблонами.

Шаблон-объяснение собирается из BKT-состояния по простым правилам:

  1. Найти самый слабый навык задачи — он причина рекомендации.
  2. Найти самый сильный навык задачи — он гарантирует, что ученик не утонет.
  3. Сообщить PjointP_{\text{joint}} как ZPD-индикатор.
  4. Если есть rareSkillBonus — упомянуть, что задача затрагивает неосвоенный навык.
Ülesanne T-147 Ivanile
Põhjus: kõige nõrgem mikrooskus on "sulgude avamine" (P=0.41).
Tugevaim — "aritmeetika märkidega" (P=0.82). Ülesanne
treenib just nõrka kohta, kuid ei jää aritmeetika peale
kinni. Lahenduse tõenäosus ≈ 0.55 — see on parajalt
keeruline.

Перевод:

«Самый слабый микро-навык — раскрытие скобок (P=0.41P = 0.41). Самый сильный — арифметика со знаками (P=0.82P = 0.82). Задача тренирует именно слабое место, но не застрянет на арифметике. P(solve)0.55P(\text{solve}) \approx 0.55 — в самый раз сложно.»

В web/lib/explain.ts (стрётчно — пока стаб):

export function explainRecommendation(
scored: ScoredTask,
microskills: Record<MicroSkillId, MicroSkill>,
lang: 'et' | 'ru' | 'en' = 'et'
): string {
const sorted = Object.entries(scored.perSkillPL)
.sort(([, a], [, b]) => a - b);
const [weakest, weakP] = sorted[0];
const [strongest, strongP] = sorted[sorted.length - 1];
const targetSkill = microskills[weakest].title_et;
const supportSkill = microskills[strongest].title_et;
return T[lang]({
targetSkill,
weakP: weakP.toFixed(2),
supportSkill,
strongP: strongP.toFixed(2),
pSolve: scored.pSolve.toFixed(2),
});
}
const T = {
et: ({ targetSkill, weakP, supportSkill, strongP, pSolve }) =>
`Põhjus: nõrgim — "${targetSkill}" (P=${weakP}). Tugevaim — ` +
`"${supportSkill}" (P=${strongP}). Lahenduse tõenäosus ≈ ${pSolve}.`,
ru: ...,
en: ...,
};

Готовый шаблон можно прогонять через Claude только для стилистики — чтобы текст звучал не как из роботического справочника, а как от коллеги:

Sa oled MATx assistent. Kirjuta järgnev õpetajale 1-2 lauseks, sõbralikult,
eesti keeles. Ära muuda numbreid ega oskuste nimesid.
[шаблон]

При этом:

  • Числа и навыки — закрыты в шаблоне (Claude может их прочитать, но не должен менять);
  • Risk «галлюцинации» — низкий, потому что Claude получает все факты;
  • Tone — natural, не «database output».

Это безопасный гибрид: факты от нас, причёска от Claude.

ПолеОткудаЗачем
Имя задачиtask.idидентификация
Топ-2 микро-навыка с Pmastery vectorцелевой и поддерживающий
P(solve)P(\text{solve}) ученикаscoreTaskForStudentZPD-индикатор
1-2 предложения текстаexplainRecommendationчеловеческое объяснение
Альтернативы (top-3)recommend()[1..2]«или вот ещё варианты»
  • У задачи 1 навык → нет «самого сильного» как контраста. Шаблон: «Целевой навык — X (P=Y). P(solve)ZP(\text{solve}) \approx Z
  • Все навыки сильные (P>0.85P > 0.85) → почему вообще рекомендуем? Шаблон признаётся: «Закрепление, все навыки уже усвоены».
  • Все навыки слабые (P<0.3P < 0.3) → задача fits frustration zone. Шаблон предупреждает: «Возможна фрустрация, рассмотри лёгкую альтернативу».

«Объяснения генерируются детерминированно из состояния BKT. Это гарантирует, что цифры точные — никакого риска LLM-галлюцинации в математическом контексте. Опционально для естественности можно прогнать готовый шаблон через Claude, оставляя факты неизменными.»