Files
portal/app/tests/Feature/Bot/LiveDialogFixesTest.php
T
Дмитрий aa2ceb8a13 fix(bot): 9 тупостей из живого прогона владельца (журнал bot_dialogs 127-182)
Владелец прогнал бота вживую: «там тупежа жуть какая-то». Разобрал журнал —
каждая тупость закрыта тестом.

1. 🔴 «откуда приходят номера телефонов?» — бот плёл про Конкурентное поле вместо
   честного ответа (операторы + согласие абонента). Статья усилена.
2. «как получить номер человека, зашедшего на сайт» — теперь прямой ответ: НЕЛЬЗЯ,
   мы не ставим счётчик на чужой сайт и не снимаем посетителей.
3. «законно ли брать телефоны с сайтов конкурентов» — да: это контакты фирм
   из открытых источников, не персональные данные граждан.
4. Формат сайта и номера в проекте — бот сдавался, хотя это в статье. Дописано явно.
5. Клиентское слово «выгрузка» (= поставка заявок) — новая статья «Когда пойдут
   заявки и когда прекратятся».
6. 🔴 «могут ли работать 2-3 человека?» — ЛОЖНАЯ эскалация: голое слово «человек»
   стояло в стоп-темах. Стоп-паттерн переписан: ловим просьбу позвать ЖИВОГО
   (позовите/соедините/живой человек), а не любое упоминание. Плюс статья про команду.
7. «а что ты вообще знаешь?» — бот нёс чушь про паузу проектов. Новая статья-оглавление.
8. «придут ли СМС?» — теперь прямо: СМС/Телеграм/пуш нет, только почта и колокольчик.
9. 🔴 ИИ начинал ответ с «в инструкции этого нет», а СЛЕДОМ отвечал верно — и мы
   глушили весь ответ эскалацией. Теперь отговорку выкидываем; в промпт добавлено:
   «нет» и «нельзя» — это ТОЖЕ ответ, а не отсутствие ответа.

Проверено вручную стоп-темы: «позовите живого человека», «какие операторы», «поставщик»
— по-прежнему эскалация. Pest бот 54/54 (12 новых тестов), Vitest каталога 8/8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 15:30:49 +03:00

162 lines
9.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use App\Models\KnowledgeChunk;
use App\Services\Bot\BotAnswerService;
use App\Services\Bot\KnowledgeSearch;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
uses(RefreshDatabase::class);
/**
* Живой прогон владельца 12.07.2026 (журнал bot_dialogs id 127182): «тупежа жуть».
* Каждый тест — конкретная тупость из переписки.
*/
beforeEach(function () {
config()->set('services.yandexgpt', [
'api_key' => 'k', 'folder_id' => 'f', 'model' => 'yandexgpt-lite/latest',
'endpoint' => 'https://llm.api.cloud.yandex.net/foundationModels/v1/completion',
'timeout_seconds' => 8,
]);
KnowledgeChunk::query()->delete();
Artisan::call('help:rebuild-knowledge');
});
function topTitle(string $q): string
{
$hits = app(KnowledgeSearch::class)->search($q, 3);
expect($hits)->not->toBeEmpty();
return $hits[0]->title;
}
it('«откуда приходят номера телефонов» → статья о происхождении заявок, а не о Конкурентном поле', function () {
// Тупость id 136: бот сказал, что номера берутся из подбора конкурентов.
foreach ([
'а откуда приходят номера телефонов?',
'откуда у вас номера людей',
'как вы получаете номер телефона человека, заходящего на сайт',
] as $q) {
expect(topTitle($q))->toContain('Откуда берутся заявки');
}
});
it('«законно ли брать телефоны с сайтов конкурентов» → есть ответ, а не «не знаю»', function () {
// Тупость id 164: сдался на прямом юридическом вопросе клиента.
$hits = app(KnowledgeSearch::class)->search('законно выгружать номера телефонов с сайтов конкурентов?', 3);
expect($hits)->not->toBeEmpty();
expect(collect($hits)->pluck('content')->implode(' '))->toContain('открытых источник');
});
it('формат сайта и номера в проекте — находится', function () {
// Тупости id 142 и id 156: формат прямо написан в статье, а бот его не нашёл.
expect(topTitle('какой формат номера телефона должен быть в проекте?'))->toContain('Создать проект');
expect(topTitle('в каком формате в проекте должен быть написан сайт?'))->toContain('Создать проект');
});
it('клиентское слово «выгрузка» понимается как поставка заявок', function () {
// Тупости id 146 и id 150: «через сколько придёт выгрузка», «когда остановится выгрузка».
foreach ([
'через сколько придёт выгрузка по проекту',
'когда остановится выгрузка по проекту',
] as $q) {
expect(topTitle($q))->toContain('Выгрузка по проекту');
}
});
it('«что ты умеешь» → оглавление возможностей, а не случайная статья', function () {
// Тупость id 166: на «а что ты вообще знаешь?» бот рассказал про паузу проектов.
foreach (['а что ты вообще знаешь?', 'что ты умеешь', 'чем ты можешь помочь'] as $q) {
expect(topTitle($q))->toContain('Чем я могу помочь');
}
});
it('«могут ли работать несколько человек» — отвечает, а не зовёт специалиста', function () {
// Тупость id 170: слово «человек» било в стоп-тему «позовите человека».
Http::fake(['*' => Http::response([
'result' => ['alternatives' => [['message' => ['text' => 'Сейчас вход в кабинет — по одной учётной записи.']]]],
], 200)]);
$answer = app(BotAnswerService::class)->answer('может в кабинете одновременно работать 2-3 человека?');
expect($answer->text)->not->toBe(BotAnswerService::ESCALATE_TEXT)
->and($answer->text)->not->toBe(BotAnswerService::UNKNOWN_TEXT);
});
it('просьба позвать живого человека — по-прежнему эскалация', function () {
Http::fake();
foreach (['позовите человека', 'соедините с оператором', 'хочу поговорить с менеджером'] as $q) {
expect(app(BotAnswerService::class)->answer($q)->text)->toBe(BotAnswerService::ESCALATE_TEXT);
}
});
it('вопрос «какие операторы» — по-прежнему стоп-тема (коммерческая тайна)', function () {
Http::fake();
foreach (['какие операторы в вашей системе?', 'назовите оператора связи', 'кто ваш поставщик'] as $q) {
expect(app(BotAnswerService::class)->answer($q)->text)->toBe(BotAnswerService::ESCALATE_TEXT);
}
});
it('ответ по делу не глушится из-за фразы «в инструкции нет» в середине', function () {
// Живой баг 12.07.2026: ИИ отвечал верно («СМС не приходят»), но добавлял хвост
// «про Телеграм в инструкции нет» — и весь ответ подменялся эскалацией.
KnowledgeChunk::create([
'source_path' => 'help/x.md', 'title' => 'Уведомления', 'tour' => 'notifications',
'topics' => 'смс, уведомления', 'chunk_index' => 0,
'content' => 'СМС о новых заявках не приходят — только почта и колокольчик.',
]);
Http::fake(['*' => Http::response([
'result' => ['alternatives' => [['message' => ['text' => 'СМС о новых заявках не приходят — только письма и колокольчик. Про Телеграм в инструкции этого нет.']]]],
], 200)]);
$answer = app(BotAnswerService::class)->answer('придут ли смс о новых сделках?');
expect($answer->text)->toContain('СМС о новых заявках не приходят')
->and($answer->text)->not->toContain('в инструкции')
->and($answer->escalate)->toBeFalse();
});
it('ИИ начал с «в инструкции этого нет», но следом ответил по делу — отговорку выкидываем', function () {
// Ровно то, что модель выдала живьём 12.07.2026:
// «В инструкции этого нет. СМС о новых заявках не приходят.»
KnowledgeChunk::create([
'source_path' => 'help/x.md', 'title' => 'Уведомления', 'tour' => 'notifications',
'topics' => 'смс, уведомления', 'chunk_index' => 0,
'content' => 'СМС о новых заявках не приходят — только почта и колокольчик.',
]);
Http::fake(['*' => Http::response([
'result' => ['alternatives' => [['message' => ['text' => 'В инструкции этого нет. СМС о новых заявках не приходят — только письма и колокольчик в портале.']]]],
], 200)]);
$answer = app(BotAnswerService::class)->answer('придут ли смс о новых сделках?');
expect($answer->text)->toContain('СМС о новых заявках не приходят')
->and($answer->text)->not->toContain('В инструкции этого нет')
->and($answer->escalate)->toBeFalse();
});
it('честное «не знаю» с первых слов — по-прежнему эскалация', function () {
Http::fake(['*' => Http::response([
'result' => ['alternatives' => [['message' => ['text' => 'В инструкции этого нет.']]]],
], 200)]);
$answer = app(BotAnswerService::class)->answer('какой у вас юридический адрес в австралии?');
expect($answer->text)->toBe(BotAnswerService::UNKNOWN_TEXT)
->and($answer->escalate)->toBeTrue();
});
it('«придут ли смс о новых сделках» — честное «СМС нет»', function () {
// Тупость id 178: бот рассказал про каналы, но прямо не сказал, что СМС не бывает.
$q = 'мне будут приходить смс о том что появились новые сделки?';
expect(topTitle($q))->toContain('Уведомления');
expect(app(KnowledgeSearch::class)->search($q, 3)[0]->content)
->toContain('СМС о новых заявках не приходят');
});