feat(assistant): сократический / анти-чит режим (фича 3/6)
- тумблер учителя «Сократический режим» (/admin#assistant): для УЧЕНИКОВ Квантик объясняет теорию полно, но конкретные задачи не решает «под ключ» — даёт метод, первый шаг и наводящий вопрос (assistant_socratic в app_settings) - авто-анти-чит: явная просьба «сделай за меня / реши моё дз / do my homework» включает сократический режим даже без тумблера (_CHEAT_RE) - учителей/админов и режимы hint/check не ограничивает; работает и в /ask, и в стриме _socraticFor(role,mode,q) + проброс socratic в buildAskMessages. Бэкенд+админ-UI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -556,8 +556,12 @@ const META_RE = new RegExp('(' + _SELF + '[\\sа-яёa-z0-9,?!.-]{0,25}' + _TERM
|
||||
'|на\\s+ч[её]м\\s+ты\\s+(?:работа|сдела|постро|основ)|кто\\s+тебя\\s+(?:сделал|создал|обуч|разработ|написал)|систем[а-яё]*\\s+промпт|what\\s+model\\s+are\\s+you|which\\s+(?:ai\\s+)?model|your\\s+system\\s+prompt)', 'i');
|
||||
const META_ANSWER = 'Я — Квантик, помощник LearnSpace. Помогаю с учёбой и навигацией по платформе. Давай вернёмся к делу — что объяснить или подсказать?';
|
||||
|
||||
// Анти-чит: явная просьба «сделай за меня» (а не «помоги разобраться»).
|
||||
const _CHEAT_RE = /за\s+меня|вместо\s+меня|do\s+my\s+homework|(сделай|реши|выполни|напиши)\s+([а-яёА-ЯЁ]+\s+)?(дз|домашк|контрольн)/i;
|
||||
function _socraticOn() { return _setting('assistant_socratic') === '1'; }
|
||||
|
||||
// Сборка messages+cap для модели — общая для обычного и стримингового ответа.
|
||||
function buildAskMessages(q, hits, context, history, role, mode, mem) {
|
||||
function buildAskMessages(q, hits, context, history, role, mode, mem, socratic) {
|
||||
const ref = hits.map((h, i) => `${i + 1}. ${h.q}\n${h.a}${h.url ? ` (раздел: ${h.url})` : ''}`).join('\n') || '(пусто)';
|
||||
const user = (context ? `Контекст (опирайся на него, если относится к вопросу):\n${context}\n\n` : '') +
|
||||
`Справка по платформе:\n${ref}\n\nВопрос: ${q}`;
|
||||
@@ -571,6 +575,12 @@ function buildAskMessages(q, hits, context, history, role, mode, mem) {
|
||||
sys += ' РЕЖИМ ПОДСКАЗКИ: дай ТОЛЬКО наводящую подсказку или следующий шаг к решению. Не давай готовый ответ — пусть ученик додумает сам.';
|
||||
} else if (mode === 'check') {
|
||||
sys += ' РЕЖИМ ПРОВЕРКИ: ученик прислал своё решение. Скажи, верно оно или нет, и укажи КОНКРЕТНО, где ошибка (если есть). Не выдавай сразу полный правильный ответ — дай шанс исправить.';
|
||||
} else if (socratic) {
|
||||
// Сократический режим (для учеников): теория — полно, но задачи не решаем «под ключ».
|
||||
sys += ' СОКРАТИЧЕСКИЙ РЕЖИМ: понятия, определения и теорию объясняй полно и по существу. ' +
|
||||
'Но если просят РЕШИТЬ конкретную задачу/пример/уравнение или «сделать» задание — НЕ выдавай готовое решение и итоговый ответ. ' +
|
||||
'Вместо этого назови нужный метод/формулу, разбери первый шаг и задай наводящий вопрос, предложи ученику продолжить самому. ' +
|
||||
'Если ученик пришлёт свой шаг или ответ — проверь и мягко направь дальше. Будь доброжелателен, подбадривай.';
|
||||
}
|
||||
const msgs = [{ role: 'system', content: sys }];
|
||||
(history || []).forEach(m => { if (m && (m.role === 'user' || m.role === 'assistant') && m.content) msgs.push({ role: m.role, content: String(m.content).slice(0, 1500) }); });
|
||||
@@ -580,11 +590,18 @@ function buildAskMessages(q, hits, context, history, role, mode, mem) {
|
||||
return { msgs, cap };
|
||||
}
|
||||
|
||||
async function askModel(q, hits, context, history, role, mode, mem) {
|
||||
const { msgs, cap } = buildAskMessages(q, hits, context, history, role, mode, mem);
|
||||
async function askModel(q, hits, context, history, role, mode, mem, socratic) {
|
||||
const { msgs, cap } = buildAskMessages(q, hits, context, history, role, mode, mem, socratic);
|
||||
return callLLMFailover(msgs, cap);
|
||||
}
|
||||
|
||||
// Сократический режим включается для УЧЕНИКА: если включён тумблер ИЛИ явная просьба «сделай за меня».
|
||||
function _socraticFor(role, mode, q) {
|
||||
if (role && role !== 'student') return false; // учителям/админам не ограничиваем
|
||||
if (mode !== 'answer') return false; // hint/check уже наводящие
|
||||
return _socraticOn() || _CHEAT_RE.test(q || '');
|
||||
}
|
||||
|
||||
/* ── POST /api/assistant/ask { q, context?, history? } ── «Спроси Квантика» ─
|
||||
* Грунтуем ответ топ-FAQ (+ опц. контекст страницы + история диалога). Если
|
||||
* LLM настроена — её ответ (source:'model'), иначе FAQ (source:'faq'). */
|
||||
@@ -617,8 +634,9 @@ async function ask(req, res) {
|
||||
let context = pageCtx;
|
||||
if (rag.text) context = (context ? context + '\n\n' : '') + 'Из учебников:\n' + rag.text;
|
||||
|
||||
const socratic = _socraticFor(req.user && req.user.role, mode, q);
|
||||
let r = { text: null, error: 'network' };
|
||||
try { r = await askModel(q, hits, context, history, req.user && req.user.role, mode, mem); } catch (e) { r = { text: null, error: 'network' }; }
|
||||
try { r = await askModel(q, hits, context, history, req.user && req.user.role, mode, mem, socratic); } catch (e) { r = { text: null, error: 'network' }; }
|
||||
const answer = r && r.text;
|
||||
|
||||
if (answer) {
|
||||
@@ -675,7 +693,8 @@ async function askStream(req, res) {
|
||||
|
||||
let context = pageCtx;
|
||||
if (rag.text) context = (context ? context + '\n\n' : '') + 'Из учебников:\n' + rag.text;
|
||||
const { msgs, cap } = buildAskMessages(q, hits, context, history, req.user && req.user.role, mode, mem);
|
||||
const socratic = _socraticFor(req.user && req.user.role, mode, q);
|
||||
const { msgs, cap } = buildAskMessages(q, hits, context, history, req.user && req.user.role, mode, mem, socratic);
|
||||
|
||||
let full = '';
|
||||
let r = { text: null, error: 'network' };
|
||||
|
||||
Reference in New Issue
Block a user