Соло зробив AI-бота для саморефлексії: міграція Claude → Gemini, промпти на психо-рамках і війна з галюцинаціями

💡 Усі статті, обговорення, новини про продукти — в одному місці. Приєднуйтесь до Product спільноти!

Привіт! Мене звати Єгор, я назараз вільний продакт-менеджер. В якості практики будую власні пет-проєкти — про один із них ця стаття.

Півроку пишу Telegram-бота, який допомагає рефлексувати. Ви розповідаєте, що в голові, а він пам’ятає контекст, помічає патерни і раз на тиждень повертає вам спостереження про самого себе. Робив його соло — від логики продукту, промптів, бекенду CICD до розгрібання фідбеку. Розповім не про продукт, а на які граблі наступав, про свої рішення і те, чого навчився, будуючи це соло.

Чому взагалі бот, а не застосунок

Перше рішення було продуктовим, але воно визначило весь стек. Я не хотів змушувати людину ставити окремий застосунок заради рефлексії — бар’єр входу вбиває такі речі. Telegram уже стоїть у всіх, підтримує голос (а наговорити простіше, ніж надрукувати), і дає безкоштовну інфраструктуру повідомлень. Тож стек вийшов простий: Node.js + grammy для бота, Supabase (Postgres) для даних, node-cron для розкладу, плюс Playwright для окремого допоміжного завдання.

Звучить як вечір роботи. Але насправді 90% складності виявилось не в Telegram API, а в тому, як змусити LLM звучати по-людськи та не брехати.

AI під капотом: чому я мігрував з Claude на Gemini

Спочатку вся генерація — відповіді в діалозі, тижневі підсумки, витяг патернів — крутилась на Claude. Якість була чудова, але коли скінчилися кредити, я опинився перед фактом: на ранній стадії, без виручки, платити за преміум-модель на кожне повідомлення — неприйнятно.

Рішенням було перевести генерацію на Gemini. І тут почалось найцікавіше.

Усі мої ~22 виклики моделі йшли через одну функцію — це врятувало. Я зробив окремий бекенд із тим самим контрактом (системний промпт, повідомлення, опції → рядок) і перемикач через змінну середовища AI_PROVIDER. За замовчуванням лишився старий провайдер, тож відкат — один рядок. Урок № 1: якщо всі виклики до зовнішнього сервісу йдуть через одну точку, зміна провайдера — це дрібниця; якщо ні — це переписування половини коду.

Граблі № 1: модель, яка думає, але мовчить

Перший же живий тест мене спантеличив. Короткі виклики (на швидкій моделі) працювали, а от глибокі підсумки на старшій моделі поверталися... порожніми. Статус 200, помилок нема, тексту — нуль.

Я почав дивитися в метадані відповіді та побачив, що модель витратила майже весь ліміт вихідних токенів на внутрішнє мислення (thinking), а на видиму відповідь не лишилось нічого. У Gemini 2.5 maxOutputTokens покриває thinking + видимий вихід разом. Із моїм лімітом у ~400 токенів усе йшло в роздуми, і фінальний finishReason був MAX_TOKENS ще до першого слова відповіді.

Я вирішив керувати thinking-бюджетом явно. Для коротких структурних викликів (підтвердження, відкривашки) вимкнути мислення взагалі. Для глибоких — виділити окремий бюджет на роздуми поверх того, що просить виклик, щоб видима відповідь ніколи не голодувала. Урок № 2: «thinking»-моделі — це не безкоштовний бонус; токени на роздуми треба закладати в бюджет окремо, інакше отримаєш впевнену тишу.

Граблі № 2: впевнена брехня замість «не знаю»

Коли я переписував тижневий звіт, щоб він давав не переказ («ти втомився»), а інсайт, я зіткнувся з гіршим, ніж порожня відповідь — з галюцинацією.

У тесті я дав боту контекст про людину, яка бігає (кілометри, травма ноги, втома). А на виході отримав глибокий, переконливий «інсайт» про... сварку з чоловіком і підтримку подруги — нічого з цього в даних не було. Модель просто вигадала чужу історію, ще й зачепилась за приклади з мого ж промпту (там у правилах безпеки фігурував чоловік) і збудувала навколо них наратив.

Це гірше за плоский переказ. Звучить розумно, але вигадано, і користувач одразу відчуває, що цей бот його взагалі не знає. Я додав жорсткий anti-hallucination гард прямо в промпт: будуй усе лише з реальних записів, ніколи не вигадуй людей чи події, приклади в промпті — це ілюстрація тону, а не факти про користувача; якщо даних мало — зроби менше, але правдиве спостереження.

(Тут була ще й моя власна помилка в тесті — я забув підставити контекст у промпт, тож частина «галюцинацій» була насправді моїм багом. Урок № 2.5: перш ніж звинувачувати модель — перевір, що ти взагалі дав їй дані.)

Маршрутизація моделей: швидкість там, де чекають; глибина там, де ні

Із двох провалів вийшов чіткий принцип. Інтерактивні виклики (діалог, онбординг — людина сидить і чекає) пішли на швидку модель, бо латентність важливіша за максимальну глибину. А офлайн-синтез (тижневі/місячні підсумки, витяг патернів) — на старшу думаючу модель: там ніхто не чекає, об’єм маленький, і саме там потрібна глибина, щоб з’явився неочевидний інсайт, а не констатація факту.

Урок № 3: яку модель брати — неправильне питання. Правильне — що для цього виклику важливіше: швидкість чи глибина, і відповідь часто різна, навіть у межах одного продукту.

Промпти на психологічних рамках — без «розумних слів»

Найбільше часу пішло не на код, а на промпт-інжиніринг. Витяг патернів побудований на психологічних рамках — теорія прив’язаності, когнітивні викривлення, спіральна динаміка, екзистенційні теми. Але ключове правило: користувач ніколи не бачить термінів. Не «у вас установка „я недостатньо хороший“», а «той голос, що каже „недостатньо“ — поміть, коли він вмикається». Рамка — це лінза для моделі, а не лексикон для людини.

Окремо я тримаю структуровану карту користувача: головна напруга, повторювані патерни, тригери, обмежувальні переконання, самосаботаж. Вона оновлюється раз на тиждень і живить кожен діалог — так бот пам’ятає вас, а розмови будуються одна на одній, а не починаються з нуля.

Продуктові граблі, які LLM не виправить

Найболючіший фідбек прийшов не про код. Користувач обрав режим щоденника, сів писати свою історію і вперся в ліміт довжини повідомлення (стояв маленький, бо задумувався під короткі ранкові чекіни). Він поділив історію на частини — не бере. На вісім частин — не бере навіть першу. А в описі написано «безліміт». Класична невідповідність, бо «безліміт» стосувався кількості повідомлень, а не довжини.

Полагодив: підняв ліміт під максимум Telegram, переписав повідомлення (замість «вкладись у кілька речень» — «велику історію зручніше завантажити файлом»), і додав голос до 3 хвилин, оскільки одна голосова на 3 хвилини — це один хід і одна відповідь на всю історію, а не вісім обірваних. Урок № 4: ліміти, придумані під один сценарій, тихо вбивають інший. І користувач судить не за твоєю архітектурою, а за обіцянкою на екрані.

Ще одна дрібниця, яка дорого коштує довірі. Бот у кінці діалогу казав «завтра напишу першим», а для частини користувачів проактивних повідомлень не було взагалі. Тобто він обіцяв те, чого не робив. Прибрав обіцянку, лишив нейтральне «до завтра», бо вважаю, що маленька брехня ламає довіру сильніше за відсутній фічер.

Що я виніс із цього соло

Будувати соло — це постійний перемикач між ролями. Годину ти промпт-інженер, потім бекендер, потім продакт, що читає злий фідбек. Найцінніше, що я зрозумів: AI — це інструмент, який чесно віддзеркалює, наскільки добре ти поставив задачу. Порожня відповідь, галюцинація, плоский переказ — річ майже завжди була не в моделі, а недостатній точності запиту.

І друге. Жоден промпт не врятує від продуктових дрібниць. Наприклад, ліміту не за сценарієм, обіцянки, якої не тримаєш, часового поясу, який ти вгадав по мові інтерфейсу і не спитав. Користувач не бачить твоєї архітектури, а отримує «доброго ранку» о третій ночі.

Якщо стало цікаво подивитися, що з усього цього вийшло на практиці, або захочеться накидати багів чи ідей — бот живе тут. Є також сайт. Буду щиро радий будь-якому фідбеку.

👍ПодобаєтьсяСподобалось8
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Підписатись на коментарі