aa2ceb8a13
Владелец прогнал бота вживую: «там тупежа жуть какая-то». Разобрал журнал — каждая тупость закрыта тестом. 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>
162 lines
9.3 KiB
PHP
162 lines
9.3 KiB
PHP
<?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 127–182): «тупежа жуть».
|
||
* Каждый тест — конкретная тупость из переписки.
|
||
*/
|
||
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('СМС о новых заявках не приходят');
|
||
});
|