feat(jivo-bot): память разговора + статья про ручное добавление конкурентов

Живой урок владельца 12.07.2026 («а сам руками я могу?» — бот талдычил про ИИ за 300₽):
- ProcessJivoMessageJob: последние 6 реплик чата → история для LLM (хвосты
  «👉 Показать…» срезаются), только СВОЙ чат
- BotAnswerService/YandexGptClient: история между system и текущим вопросом;
  пустой поиск досыпается прошлой репликой клиента
- статья konkurentnoe-pole: ручное добавление конкурента/точек/источников — бесплатно,
  кнопка «+ Добавить вручную» (фича существует, бот о ней не знал)
- TDD: 3 новых теста, все 37 зелёные

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-12 10:15:09 +03:00
parent 0fd0645dad
commit 7dacdd68c0
6 changed files with 122 additions and 5 deletions
@@ -52,6 +52,45 @@ it('эскалация: BOT_MESSAGE-прощание + INVITE_AGENT, журна
expect(BotDialog::where('direction', 'out')->firstOrFail()->escalated)->toBeTrue();
});
it('второе сообщение чата уходит в LLM вместе с историей первого', function () {
// Живой урок 12.07.2026: «а сам руками я могу?» без памяти разговора — бессмыслица.
Http::fake([
'llm.api.cloud.yandex.net/*' => Http::response([
'result' => ['alternatives' => [['message' => ['role' => 'assistant', 'text' => 'Конечно.']]]],
]),
'bot.jivosite.com/*' => Http::response(['ok' => true]),
]);
(new ProcessJivoMessageJob('chat-h', 'client-1', 'что такое проект?'))->handle();
(new ProcessJivoMessageJob('chat-h', 'client-1', 'а расскажи про проект подробнее'))->handle();
$llmCalls = collect(Http::recorded())
->filter(fn ($pair) => str_contains($pair[0]->url(), 'llm.api.cloud.yandex.net'));
$lastMessages = collect($llmCalls->last()[0]['messages']);
expect($llmCalls)->toHaveCount(2)
->and($lastMessages->pluck('text'))->toContain('что такое проект?')
->and($lastMessages->pluck('role'))->toContain('assistant');
});
it('история — только своего чата, чужие диалоги в LLM не утекают', function () {
Http::fake([
'llm.api.cloud.yandex.net/*' => Http::response([
'result' => ['alternatives' => [['message' => ['role' => 'assistant', 'text' => 'Ок.']]]],
]),
'bot.jivosite.com/*' => Http::response(['ok' => true]),
]);
(new ProcessJivoMessageJob('chat-alien', 'client-9', 'что такое проект?'))->handle();
(new ProcessJivoMessageJob('chat-mine', 'client-1', 'расскажи про проект'))->handle();
$last = collect(collect(Http::recorded())
->filter(fn ($pair) => str_contains($pair[0]->url(), 'llm.api.cloud.yandex.net'))
->last()[0]['messages']);
expect($last->pluck('text'))->not->toContain('что такое проект?');
});
it('джоба объявлена с queue=bot и timeout ≤ 12 сек', function () {
$job = new ProcessJivoMessageJob('c', 'c', 'q');