feat(jivo-bot): всеобъемлющая клиентская инструкция — 15 статей + защита секретов

Разведка портала 5 агентами (экраны, биллинг/настройки, Конкурентное поле,
все подсказки (?), внутренние правила) → база знаний бота выросла 4 → 15 статей:
начало работы (что такое заявка, подарок 1000₽), проекты (лимиты/регионы/дни,
правило 18:00 МСК), тарифы (7 ступеней, списание по факту), пополнение (100₽,
карта/счёт/акт), баланс и пауза (разморозка «всё-или-ничего»), сделки и канбан
(5 статусов, экспорт), Конкурентное поле (сбор 300₽/изучение 50₽, очередь,
ручной режим, дубли, метки номеров), смена источника, настройки и 2FA,
уведомления, импорт CSV, API/webhook, мобильная версия, дашборд, поддержка.

Защита коммерческой тайны (проверено агентом-цензором — 15/15 статей чисто):
ни слова про поставщика/каналы/закупку и про то, что заявка может уйти не только
одному клиенту; вопросы об эксклюзивности и перепродаже — жёсткая стоп-тема
(бот не отвечает НИ В КАКУЮ сторону, зовёт живого специалиста), внутренние
движки не упоминаются, ложных обещаний (авто-возврат, «Команда») нет.

Стоп-темы разделены: HARD (деньги/скидки/юр/эксклюзив — всегда) и PERSONAL
(«мой баланс») с исключением HOWTO — «перенести мои заявки из амоцрм» больше
не уходит к человеку, бот рассказывает про импорт.

TDD: 40/40 зелёные. Живая проверка 20+ вопросов на стенде.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-12 11:02:23 +03:00
parent dd16edde8e
commit 1a686d07c8
18 changed files with 483 additions and 65 deletions
@@ -100,6 +100,42 @@ it('LLM ответил «в инструкции этого нет» → чес
->and($answer->text)->toContain('живому специалисту');
});
it('вопрос про эксклюзивность заявок → эскалация без похода в LLM', function () {
Http::fake();
$variants = [
'а заявки эксклюзивные или как?',
'вы эти лиды кому-то ещё продаёте?',
'эти заявки только мои или вы их ещё кому-то отдаёте?',
'заявки достаются только мне?',
'вы одну заявку нескольким продаёте?',
];
foreach ($variants as $q) {
expect(app(BotAnswerService::class)->answer($q)->escalate)->toBeTrue();
}
Http::assertNothingSent();
});
it('«перенести МОИ заявки из другой CRM» → это про функцию импорта, а не личные данные', function () {
// Живой урок 12.07.2026: стоп-тема ловила «мои заявки» и уводила к человеку вопрос
// про импорт. Слова «перенести/импорт» — признак вопроса «как это делается».
KnowledgeChunk::create([
'source_path' => 'help/i.md', 'title' => 'Импорт своих заявок из CSV', 'tour' => null,
'topics' => 'импорт, перенести заявки, амоцрм', 'chunk_index' => 0,
'content' => 'Базу заявок из другой системы можно перенести: раздел «Импорт данных», файл CSV.',
]);
Http::fake([
'llm.api.cloud.yandex.net/*' => Http::response([
'result' => ['alternatives' => [['message' => ['role' => 'assistant', 'text' => 'Да, через «Импорт данных» — файл CSV.']]]],
]),
]);
$answer = app(BotAnswerService::class)->answer('можно перенести мои заявки из амоцрм?');
expect($answer->escalate)->toBeFalse()
->and($answer->text)->toContain('Импорт данных');
});
it('просьба позвать человека → эскалация', function () {
Http::fake();