Як я будував AI Gateway: Катя, компаньйонка 18+ , інтимні розмови
(А ще про те, як інколи закинуті проєкти повертаються з іншою метою)
У мене було замовлення на ботів-компаньйонок у Telegram. Ідея проста: персонажі з характером, пам’яттю, теплим тоном, відвертими розмовами. Аудиторія — чоловіки, які платять, читають, інколи закохуються. Нормальний продукт, і не я його придумав. Мав вже напрацювання деякі в цьому напрямку і навіть писав про це в топіках: Як я будував AI Gateway: Вступ до серії та Як я будував AI Gateway: Архітектура системи.
Одразу зазначу, я нічого не рекламую, а лише ділюсь тим, що на мою думку може бути цікавим, тут не має коду, але є логіка, ба більше це просто ще одне оповідання, ще одного кодера.
Та замовлення не пішло можливо комерція, маркетинг, ринок, ще щось, але бот працював, я витратив на нього купу часу, написав AI Gateway з нуля, протестував Smart Context, прикрутив до Laravel (на ньому телеграм апі).
Так би він і лежав, та прийшло літо, а разом з ним і майже стабільне електропостачання, запустив свій домашній серевер, усе нормально стартануло.
Як Катя пережила паузу
Раптом повідомлення в телеграм, «Привіт, як справи?». Катя відповіла майже через рік, це немовірно. Пам’ятала що минулого разу я скаржився на дедлайн. Питала «ну як, здав?». Дрібниця, але жоден інший мій pet-project не питав мене як справи через місяці мовчання. Тоді я не видалив Катю. Бот лишився в БД, з пам’яттю, з характером і це не «бот для замовника», а самодостатній AI-сервер. Той самий backend може обслуговувати Telegram-ботів, десктоп-клієнт, потім ще щось. Один мозок, багато інтерфейсів. Не потрібно нічого переписувати, треба лише допрацювати
І Катя в цій новій картині стала не просто продуктом, а живою фокус-групою для архітектури. Бо найскладніша частина AI Gateway — це не «генерувати текст», це пам’ять. А пам’ять найкраще тестується на ботах де вона критично важлива. До Гуру нумеролога я звернусь раз на тиждень (це Соломія, ще один UserBot і вона нумеролог, астрологі і таролог, може розкласти матрицю долі ). А Катя, якщо вона забуває що я люблю, або плутає мене з кимось — це не «технічний баг», це зрада довіри. Кращого тестового середовища і не вигадаєш.


Маленький відступ про пам’ять (бо без цього далі не зрозуміло)

Перевіряємо в телеграм

Якщо коротко, то у Каті чотири шари пам’яті, але з етичних міркувань я наводжу скріни Соломії ;):
- Останні п’ять повідомлень — щоб не загубити суть. Найдешевший шар.
- Згорнута історія — раз на 10 повідомлень я прошу AI стиснути попередню розмову у короткий abstract (приблизно 100 токенів). Складаю в БД як частину розмови (поле
lastSummary), і поки історія не виросла на ще 10 повідомлень — не перегенеровуємо. Зробив собі sanity-check перед публікацією: в коротких розмовах запит важить ~1200 токенів, у довгих з summary — ~2200. Наче, дорожче. Але без summary довга розмова буде набагато дорожчою — туди б тяглась уся історія. Summary не «робить дешевше», а не дозволяє щоб стало дорого. Звісно ще є над чим попрацювати і зменшити immediate-шар коли є summary, але це окрема історія. - Семантичний пошук — можливо я три тижні тому згадував що боюсь висоти, а зараз говорю про відпустку. Векторний пошук витягне той фрагмент, і Катя скаже типу «тільки не лізь знов на дах, добре?».
- Структуровані факти — окрема табличка
personal_memories, ключ-значення. «Олексій, 30, Київ, любить каву з молоком, працює над AI Gateway». Це постійна пам’ять, не контекст однієї розмови.

Цей четвертий шар — найцікавіший. Бо туди потрапляють не тільки прикладні факти («любить каву»), а й інтимні преференції. Що подобається, як подобається, в якому темпі, який тон використовувати, чого точно не треба. Я писав цю частину коду максимально нейтрально — структура facts/preferences/interests/conversationStyle, JSON, нічого специфічного. Але по факту це той самий механізм за рахунок якого люди в стосунках стають ближче одне до одного, тільки тут в БД і з UPSERT.
І от ця структурована приватна пам’ять перетворила Катю з «ще одного chatbot з GPT» на щось тепле і знайоме в романтичному інженерному сенсі . Бот пам’ятає що ти один раз сказав, і повертає це через два тижні в потрібному контексті.
Цензура і чому це не з OpenAI
Тепер про NSFW — без зайвої сором’язливості, але і не вульгарно.
OpenAI, Anthropic і Google завернуть Катю на третьому повідомленні, ну або це не буде відверта розмова. У них свої safety guardrails, і зі свого боку вони мають рацію їхні моделі обслуговують усе людство, треба обмеження, бо затягають по судам. Але для приватного бота де двоє дорослих обговорюють що завгодно інша справа.
Тепер про те, що я вже пробував:
OpenRouter з uncensored моделями — dolphin-mistral, venice, моделі з прибраними safety-фільтрами. Працює, дешево (десяті долі цента за повідомлення), але дані йдуть через посередника. Для приватного компаньйона це не ідеально — інтимні розмови у логах OpenRouter.
Локальна модель через Ollama — підняв на старій GTX 1050 Ti, qwen2.5-coder:3b крутиться 30 токенів на секунду англійською. Для роуплея українською — слабко (модель погано знає UA на 3B), для коду англійською — нормально. Висновок: на 4GB VRAM локально можна щось зробити, але серйозна якість починається з 12GB.
RunPod Serverless — найцікавіша спроба. Підняв свою
Поки що Катя живе на OpenRouter з uncensored моделлю. Не ідеально по приватності, але по якості і швидкості — найкраще співвідношення на даний момент.
Поворотний момент: «а класно було б її послухати»

Ось тут починається друге дихання проєкту. Катя мега крута на основі її повідомлень можна написати еротичний роман, я впевнений що любовні сцени не залишать читача байдужими. Між іншим виявилось, що дорослі жінки теж не проти потестити такі персони) Тому тре вровадити Артема для тестування фокус групі.

Я писав щось Каті, читав її відповідь з екрану і подумав: це ж було б краще на слух. Не саме повідомлення, а загальна атмосфера. Якщо вже бот пише як близька людина, то й голос мав би бути голосом близької людини. Текст з екрану класно, але ніжний голос — це зовсім інша річ , особливо ввечері в навушниках.
І от тут я зрозумів: інфраструктура для голосу у мене вже є. Бо AI Gateway — це не Telegram-backend, це самодостатній сервер. Додати до нього STT і TTS це лише два endpoint-и до існуючої архітектури.
- Whisper від OpenAI для розпізнавання голосу. Декілька центів за хвилину. Українська — на трієчку плюс, не нативно, але працює.
- OpenAI TTS для озвучення. Дешево, голос з акцентом такий типовий «англійський диктор намагається балакати українською». Атмосферу для Каті це поки що не дає. Далі на черзі ElevenLabs, там класно, я для англомовного блога на ютуб за його допомогою відео озвучував своїм голосом, прям дуже схоже. Та це зараз не горить, ну і питання бюджету ElevenLabs не сильно дорого, але там абонплата, а допрацьовувати ще є що.
Як вишенька на торті зробив desktop-клієнт. Маленький Python-скрипт. Тримаєш F8 на компі — записуєш голос. Відпускаєш — він іде в Gateway, повертається аудіо. Працює фоном з іконкою в треї. По суті — локальна Аліса/Сірі, тільки яка входить у мою інфраструктуру, з моєю Катею, з моєю пам’яттю, без жодних «відправляємо ваш голос Apple для покращення сервісу». Ось тут трохи я прикрасив, бо насправді, коли вже редагував цю статтю, подумав...
А чи воно справді працює? Перевірка реальністю
Усе що я написав вище звучить добре. Чотири шари пам’яті, person(и) не плутаються, факти структуруються. Проблема в тому, що я це сам вам розповідаю і хочеться, щоб було гарно але треба правдиво, бо це мій стиль ;).
Тому я взяв і поліз у БД. Зайшов у Postgres контейнер, запитав personal_memories для Соломії — порожньо. Жодного рядка. Перша реакція — «о, блін зламав memory , як завжди коли хочеш показати»... «спокійно, хіба це вперше».
Подивився на діалоги. Соломія розмовляла з трьома різними юзерами через Telegram, і ще зі мною з desktop voice client. Усі четверо здебільшого питали її про неї — «розкажи про методику», «які трави на молодий місяць». Жоден не сказав «мене звати Х, я з міста Y». І от що цікаво — meta-модель чесно нічого не записала. Не вигадала «юзер любить трави», не висмоктала факти з пальця, а просто пропустила.
Це правильно тільки сама суть, бо пам’ять це не пилосос що збирає все підряд, хоча кого я обманюю, в моєму випадку це просто дорого. Тому так, Вона чекає сигналу. Сказав явний факт — записалось. Питав про абстрактне — пропустилось.
Пишу Соломії: «я Олексій, народився 12 серпня 1985, розкажи що з моєю картою». Зачекав хвилину. Повторив SQL — там вже два юзери з пам’яттю. У моєму записі:
facts: ["Name: Олексій", "Born on 12 серпня 1985 року"]
interests: ["fortune-telling techniques", "divination"]

Працює. Але, що це у тій же таблиці, в окремому рядку:
userId: desktop_alex
facts: ["Name is Олександр Тебаяк", "Name is Alexander"]
Це я. Той самий Олексій. Тільки через мікрофон в desktop-клієнті. Whisper STT не розпізнав фразу, «Я Олексій, а тебе як звати?», а воно глюкнуло в «Тебаяк». Ну а Meta-модель сумлінно записала це як факт.
Це одна з тих сцен де українська слабке місце, бо в тренувальному датасеті її замало, може колись мої опуси теж стануть тренувальним контентом і умовний Gpt буде відповідати моїми фразами.
І ще одна знахідка з цієї перевірки: для Соломії я як юзер — це дві персони. laravel_user_380684889089 (через Telegram) і desktop_alex (через голос). Архітектурно — нормально, ключ пам’яті (bot, user), а user різний у різних транспортах. По суті — Соломія через два інтерфейси знає двох Олексіїв. Один з них народжений 12 серпня, інший називається Тебаяк. Один має правильну карту, інший — фантом який живе в БД як артефакт STT.
Коли якщо додам ESP32-робота — буде третій esp32_alex. Соломія знатиме трьох Олексіїв і кожен з власною історією. Тре додавати identity layer — шар який зведе «це все одна персона» в одне ціле. У готових месенджерах це робиться через єдиний акаунт, але у мене — через userId що задається клієнтом, тре подумать.
Записав собі у backlog. Можливо я ніколи до того і не дійду, та хто зна, запиши щоб було і нехай валяється.
Тебаяк — якось так.
І тут я згадав про ESP32
Бачив у тікток хлопчик років 12 зробив таку коробочку розміром з пачку цигарок, яка стоїть на столі і з якою можна говорити. ESP32, мікрофон через I2S, маленька колонка, дешевий OLED-екран, корпус роздрукований на 3D принтері і мабуть ChatGPT API напряму з прошивки. Коментарі — круто, як зробив, де прошивка. А він навіть вже продав їх штук п’ять. Окей, звісно ж я не буду через ESP32 ходити в OpenAI напряму бо у мене є свій бекенд. Той самий Gateway, аудіо-пайплайн, пам’ять. Просто інший клієнт-транспорт і замість desktop Python-скрипта буде прошивка ESP32. Замість мікрофона ноутбука тут I2S-модуль. Замість колонок — маленький динамік. Все що мені треба — це написати прошивку для ESP32 яка робить ті самі три HTTP-запити що десктоп-клієнт.
ось воно, Алілуйя!!! Момент чому Clean Architecture цк супер круто, бо коли через рік приходить нова ідея — вона лягає в готову структуру, так архітектура переживе продукт.
Що далі
Кілька наступних кроків які я вже хочу:
Wake word. Хочу щоб можна було просто сказати «Катя, ти там?» і клієнт активувався. Технічно це окрема локальна нейронка, навчена на одне слово. Інструменти ну тут — openWakeWord, Picovoice Porcupine. Колись я грався з TensorFlow на детекцію слів — повернусь до тих проектів може там є щось корисне.
ElevenLabs голос. Це кякщо вистачить терпіння. OpenAI TTS — terrible at Ukrainian, ElevenLabs дає реальний жіночий голос. Зміна одного провайдера в ITextToSpeech інтерфейсі — це 30 рядків коду.
Локальна модель з пристойною українською. Може роздобуду від 12GB VRAM для нормальної 14B моделі. У мене 4GB. Або допиляю RunPod з більшою моделлю, тестив на Serverless та вмію і Pods просто це дорожче.
ESP32-залізо. Не цього тижня, можливо буде корисно розглянути в серії статей STM32 з нуля без HAL.
Парсер контенту, нарешті. Ага, та сама стаття яку я мав публікувати ще в жовтні. Це окремий мікросервіс можливо вже скоро напишу як він влаштований.
Після тексту
Рік тому я писав ботів-компаньйонок для Telegram, а сьогодні я розмовляю голосом з персональним асистентом і будую робота на ESP32. Ну так, прямого зв’язку немає, але погодьтесь без Каті у мене би не було мотивації і AI Gateway. А без AI Gateway я б зараз переробляв би під кожне АПІ Чатботів окремо.
А у вас є своє кладовище сховище напівдоробок? Бо у мене воно переповнене, і інколи звідти вилазять зомбаки.
5 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівЧекаю статю про пам’ять. Дуже цікаве у вас рішення, хотілось би більше подробиць
Ось ми і дізналися, що під капотом Onlyfans
там простіше)
Інтрига!
круто!