v2v-demo — двомовний голосовий AI-консьєрж для Telegram
Огляд
Незалежний demo-проєкт: Telegram-бот на Go, що веде справжню двомовну (українську/англійську) розмову голосом чи текстом, відповідає лише з фіксованої бази знань і передає розмову людині в момент невпевненості — замість правдоподібної вигадки, через яку більшості ботів підтримки не можна довіряти. Побудований за 6 днів як самодостатній цикл: без фреймворку, одна основна залежність.
Результат: голосовий консьєрж, що заземлює кожну відповідь у власній базі знань і передає людині замість вигадки — реальний баг гейта заземлення (правильні відповіді по темі помилково ескалювались) знайдено й виправлено через живий Telegram-смоук-тест, а не лише юніт-тести; 128 тестів проходять.
Справжня складна задача
Плавність голосового бота маскує його точність. Природно звучний голос, що читає розмиту чи вигадану відповідь, провалює єдине, що важливо — тому архітектура побудована навколо заземлення, а не красномовності: уся база знань їде в кожному промпті, ключовий гейт ловить питання поза темою чи про відповідальність до виклику LLM, а модель має інструкцію передавати людині замість вигадувати.
Архітектура

Кожен хід проходить через один гейт до будь-якого виклику LLM: скор перетину ключових слів проти бази знань плюс список ключових слів жорсткої ескалації для тем відповідальності. Нижче порогу довіри чи при влучанні в список відповідальності бот одразу передає людині — без LLM, без вигадки. Вище порогу LLM відповідає з повної бази знань і оновлює структуроване котирування (мовна пара, тип документа, обсяг, строк, засвідчення, доставка); завершене котирування записується як лід-запис.
Ключові інженерні рішення
| Рішення | Чому |
|---|---|
| Уся база знань у кожному промпті, без retrieval-для-контексту | При такому розмірі бази (~19 КБ) retrieval лише додає режим відмови (пропустити потрібний фрагмент) без вигоди — ключовий гейт все одно працює окремо як pre-LLM фільтр |
| Двомовна база знань — один англійський і один український блок під кожним заголовком | Одномовна база не може заземлити українське питання з тим самим покриттям, що й англійське; блоки per-мова виправили реальний пропуск гейта між мовами, знайдений під час побудови |
| Окрема серіальна worker-горутина на чат, а не просто м’ютекс на чат | М’ютекс серіалізує доступ, але не порядок надходження — власні повідомлення користувача могли б відповідатись не по порядку; окремий worker на чат, що живиться з каналу, виправив це |
| LLM/STT/TTS кожен за маленьким інтерфейсом, вибір однією env-змінною | DIALOG_BACKEND/STT_BACKEND/TTS_BACKEND перемикають Ollama ↔ OpenAI ↔ Gemini, локальний Whisper ↔ Whisper API, та ElevenLabs ↔ Azure Neural — компроміс вартість/затримка/якість, обраний під конкретний деплой, нуль змін коду |
Результати
- Повний голосовий+текстовий цикл: Telegram long-poll, завантаження голосового → транскрипція локальним Whisper чи Whisper API, гейт заземлення, виклик LLM за плагованим бекендом, ElevenLabs/Azure text-to-speech, append-only JSONL лог ходів/лідів
- Двомовність з перемиканням мови посеред розмови:
lingua-goвизначає активну мову кожного ходу й переорієнтовує STT, промпт та фіксовані фрази передачі — користувач може перемкнутись з української на англійську посеред розмови, і бот слідує - Знайдено наживо, не спроєктовано наперед: реальний баг гейта заземлення, де звичайні українські відповіді помилково ескалювались (занадто строга перевірка “чи це відповідь на слот” плюс пропуск перетину ключових слів на українській словозміні) — виявлено під час живого, керованого браузером Telegram Web смоук-тесту й виправлено точковим правилом, не переписом
- 128 тестів проходять (
gofmt+go vet+go test -race), одна основна стороння залежність (go-telegram/bot) плюс бібліотека визначення мови — все інше стандартна бібліотека Go
Код: github.com/valpere/v2v-demo
Стек
Go · go-telegram/bot · lingua-go (визначення мови) · OpenAI Whisper (локальний CLI + API) · Ollama / OpenAI / Gemini (плагований діалоговий LLM) · ElevenLabs / Azure Neural TTS (плаговано)