Files
portal/app/tests/Feature/Bot/PersonalAnswersTest.php
T

67 lines
3.6 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\KnowledgeChunk;
use App\Services\Bot\BotAnswerService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
uses(RefreshDatabase::class);
/**
* Личный вопрос («сколько у меня на балансе») раньше ВСЕГДА уходил к живому специалисту
* (PERSONAL_PATTERN → эскалация). Дорого и медленно: у вошедшего клиента цифры уже
* посчитаны кодом (ClientFacts) — бот должен отвечать сам, положив карточку фактов
* в контекст модели ПЕРВЫМ блоком. У гостя с лендинга карточки нет — звать специалиста
* ради цифры, которую тот всё равно не знает, глупо: вместо этого зовём в кабинет.
*/
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,
]);
});
it('отвечает вошедшему клиенту по его карточке фактов', function () {
KnowledgeChunk::create([
'source_path' => 'help/balance.md', 'title' => 'Баланс и списания', 'tour' => null,
'topics' => 'баланс, списание, пополнение', 'chunk_index' => 0,
'content' => 'Баланс — это сумма на вашем счёте в Лидерре, из неё списываются заявки.',
]);
// Модель отвечает по делу ТОЛЬКО если увидела карточку фактов в системном промпте —
// так тест доказывает, что карточка реально дошла до модели, а не потерялась по пути.
Http::fake(function (Request $request) {
$systemPrompt = (string) ($request['messages'][0]['text'] ?? '');
$hasFacts = str_contains($systemPrompt, 'ДАННЫЕ ЭТОГО КЛИЕНТА');
return Http::response([
'result' => ['alternatives' => [['message' => ['role' => 'assistant', 'text' => $hasFacts
? 'Ваш баланс: 3 400 ₽ ≈ 40 заявок.'
: 'В инструкции этого нет.',
]]]],
]);
});
$facts = "ДАННЫЕ ЭТОГО КЛИЕНТА (посчитаны только что):\nБаланс: 3 400 ₽ ≈ 40 заявок.";
$answer = app(BotAnswerService::class)->answer('какой у меня баланс?', [], true, $facts);
// Сторож AnswerGuard режет числа, которых нет в $context, — «3 400 ₽» пройдёт только
// если карточка реально попала в $context (а не потерялась где-то по пути).
expect($answer->escalate)->toBeFalse()
->and($answer->text)->toContain('3 400 ₽');
});
it('гостю с лендинга предлагает войти в кабинет, а не зовёт специалиста', function () {
Http::fake();
$answer = app(BotAnswerService::class)->answer('сколько у меня на балансе?', [], false, null);
expect($answer->escalate)->toBeFalse()
->and($answer->text)->toBe(BotAnswerService::LOGIN_TEXT);
Http::assertNothingSent();
});