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'); }); it('в чат не уходит markdown-разметка — Jivo её не рисует', function () { $cleaner = app(ChatTextCleaner::class); expect($cleaner->clean('**Важно:** пишите на `support@liderra.ru`')) ->toBe('Важно: пишите на support@liderra.ru') ->and($cleaner->clean("Причины:\n* первая\n* вторая")) ->toBe("Причины:\n— первая\n— вторая") ->and($cleaner->clean('### Заголовок'))->toBe('Заголовок'); }); it('ответ бота приходит клиенту без звёздочек и кавычек', function () { Http::fake(['*' => Http::response([ 'result' => ['alternatives' => [['message' => ['text' => '**Списания** видно в разделе `Биллинг`.']]]], ], 200)]); $answer = app(BotAnswerService::class)->answer('где посмотреть списания'); expect($answer->text)->not->toContain('**') ->and($answer->text)->not->toContain('`') ->and($answer->text)->toContain('Списания'); }); it('статья честно предупреждает: повторный сбор платный даже без результата', function () { // Судья 12.07.2026: обе модели соврали на «источников не нашлось — деньги спишутся?». // Первый сбор без результата бесплатен, ПОВТОРНЫЙ списывается всегда. $hits = app(KnowledgeSearch::class)->search('источников не нашлось, деньги спишутся?', 3); $text = collect($hits)->pluck('content')->implode(' '); expect($text)->toContain('Повторный сбор списывается всегда') ->and($text)->toContain('даже если ничего не нашлось'); }); it('правила для модели запрещают гарантии, цены по памяти и «приглашу специалиста»', function () { // Правила живут в системном промпте — проверяем, что они там есть и доезжают до модели. Http::fake(['*' => Http::response([ 'result' => ['alternatives' => [['message' => ['text' => 'Ответ.']]]], ], 200)]); app(BotAnswerService::class)->answer('сколько стоит заявка'); Http::assertSent(function ($request) { $system = (string) ($request->data()['messages'][0]['text'] ?? ''); return str_contains($system, 'Никогда не называй цены по памяти') && str_contains($system, 'Не обещай «приглашу специалиста»') && str_contains($system, 'Не давай гарантий') && str_contains($system, 'Без markdown'); }); });