b1b1686b0e
Владелец: «проверь их обоих на тех 250 вопросах» → «правь все». Разбор: docs/superpowers/findings/2026-07-14-bot-250-both-modes-run.md Цены оказались вылечены полностью (0 выдуманных цен на 500 ответов), но вылез другой пласт — бот путал гостя с вошедшим клиентом и импровизировал там, где правды нет в статьях. ЧТО ПОЧИНЕНО (было → стало на перепрогоне): - Гостю приписывали счёт и подарок («у вас уже есть 1000 ₽, они лежат на счёте») 2 → 0. У гостя нет ни счёта, ни подарка: он не зарегистрирован. - Вошедшему называли его баланс подарком портала 2 → 0 и подсовывали сценарий новичка («живите на подарок», «создайте первый проект») — подарок он потратил давно. - Вошедшему врали про состояние проектов («два работают», а работает один) 4 → 0 и про заявки («сегодня 12», а сегодня 0) 1 → 0. - «Сотая заявка уже дешевле» 2 → 0: ступень меняется по ОБЪЁМУ (первая — 500 заявок). - «1000 ₽ хватит на несколько десятков заявок» 1 → 0. По решению владельца число заявок на подарок НЕ называем вообще: сколько придёт, зависит от источника. - «Поднимите лимит — заявок будет больше» 1 → 0: лимит это ПОТОЛОК, а не источник. - «Такого не бывает» про списание без заявки 1 → 0: на проде фантомные списания БЫЛИ. - «Для Москвы — Московскую область» 1 → 0: Москва и СПб — самостоятельные субъекты. - Обрубки фраз после чистки сторожем («Они лежат…», «Нажмите её…») 3 → 0. ЗАМЕЧАНИЕ ВЛАДЕЛЬЦА ПО ЖИВОМУ ЛОГУ: «номера это и есть заявки, а он их делит». Фраза «мы не продаём номера поштучно» была ВЫДУМКОЙ бота — в статьях её нет. Теперь: номер = заявка = лид = контакт, продаём любой объём (хоть одну штуку), минимального заказа нет; спросили цену — бот называет цифру ПЕРВОЙ фразой. Живьём: «Сколько стоит один номер?» → «Заявка стоит от 55 ₽ до 25 ₽». ПРАВДА ПРО ВОЗВРАТ ДЕНЕГ вынесена в статью vozvrat-deneg.md — дословно из «Политики возврата» (/legal/refund): остаток по заявлению на почту, до 3 рабочих дней, за вычетом комиссий; стоимость переданных заявок не возвращается. Раньше бот придумывал процедуру. Плюс: сторож больше не считает выдумкой число, названное САМИМ клиентом («можно купить всего 5 номеров?»); демо-стенд перестал сам себе противоречить (счётчик проекта «сегодня» разошёлся с числом сделок); добавлен прогонщик bot:run-questions для таких проверок. Тесты: бот 258/258, весь бэкенд 2738/2744 (0 падений). Сторож не перестарался: из 250 старых одобренных ответов режет целиком 1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
58 lines
2.5 KiB
PHP
58 lines
2.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\KnowledgeChunk;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
/**
|
|
* Прогонщик вопросов — наша проверка ИИ-фичи (массовый прогон + слепые судьи).
|
|
* Продуктом не является: в бою не вызывается, но ломаться не должен, иначе прогон
|
|
* молча соврёт нам самим.
|
|
*/
|
|
beforeEach(function () {
|
|
config()->set('services.support.email', 'support@liderra.ru');
|
|
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::create([
|
|
'source_path' => 'help/b.md', 'title' => 'Как пополнить баланс', 'tour' => null,
|
|
'topics' => 'пополнить, баланс', 'chunk_index' => 0,
|
|
'content' => 'Пополнить баланс можно картой или по счёту.',
|
|
]);
|
|
});
|
|
|
|
it('прогоняет вопросы и пишет ответы построчно', function () {
|
|
Http::fake(['llm.api.cloud.yandex.net/*' => Http::response([
|
|
'result' => ['alternatives' => [['message' => ['role' => 'assistant', 'text' => 'Пополнить баланс можно в разделе «Биллинг».']]]],
|
|
])]);
|
|
|
|
$dir = sys_get_temp_dir().'/bot-run-'.uniqid();
|
|
mkdir($dir);
|
|
$file = $dir.'/q.json';
|
|
$out = $dir.'/out.jsonl';
|
|
file_put_contents($file, json_encode(['как пополнить баланс?', 'а картой можно?'], JSON_UNESCAPED_UNICODE));
|
|
|
|
$this->artisan('bot:run-questions', ['--file' => $file, '--out' => $out, '--as' => 'guest'])
|
|
->assertExitCode(0);
|
|
|
|
$lines = array_filter(explode("\n", (string) file_get_contents($out)));
|
|
expect($lines)->toHaveCount(2);
|
|
|
|
$first = json_decode((string) $lines[0], true);
|
|
expect($first['n'])->toBe(1)
|
|
->and($first['as'])->toBe('guest')
|
|
->and($first['q'])->toBe('как пополнить баланс?')
|
|
->and($first['a'])->toContain('Биллинг');
|
|
});
|
|
|
|
it('без файла вопросов честно падает, а не делает вид, что прогнал', function () {
|
|
$this->artisan('bot:run-questions', ['--file' => 'нет-такого.json', '--out' => 'x.jsonl'])
|
|
->assertExitCode(1);
|
|
});
|