FixPoint — Saqta Voice Agent
Голосовой агент контакт-центра вымышленной страховой компании: распознаёт речь (RU/KK/смешанную), маршрутизирует обращение в один из 40 сценариев…
О проекте
Команда: FixPoint
Кейс HackAlem.ai: Коммуникации — Halyk Bank · кейс 2
Стадия: Рабочий MVP
Задача кейса: «Voice Router»: голосовой AI, маршрутизирующий звонки в контакт-центре страховой компании
Суть
Голосовой агент контакт-центра вымышленной страховой компании: распознаёт речь (RU/KK/смешанную), маршрутизирует обращение в один из 40 сценариев, собирает параметры, выполняет мок-действия и отвечает голосом.
Как устроено
Браузер записывает речь до паузы (собственный энергетический VAD) и отправляет запись по HTTP/WebSocket в FastAPI. STT gpt-transcribe переводит речь в текст, LLM-маршрутизатор на gpt-4o-mini через структурированный ответ по Pydantic-схеме выбирает сценарии, язык и параметры. Контроллер диалога собирает недостающие слоты, проверяет идентификаторы (телефон, ИИН), перед необратимыми операциями делает preview и запрашивает согласие, затем вызывает 31 мок-действие. Ответ озвучивается gpt-4o-mini-tts. Трассировки и история сохраняются в JSON-архив.
Ключевые решения
- LLM intent routing
- structured outputs (Pydantic)
- slot filling
- dialogue state machine
- preview + consent
- idempotency keys
- energy-based VAD
- spoken identifier validation
Особенности
LLM не пишет данные напрямую: формулы, согласие и изменения выполняет код, а у каждого разговора своя копия мок-данных. Есть защита от повторного исполнения по id операции и повторный запрос неполностью распознанных ИИН и телефонов. Известные ограничения README честно перечисляет.
Стек
Python · FastAPI · Uvicorn · Pydantic · React · TypeScript · Vite · WebSocket · Streamlit · Docker Compose · Caddy
Модели
gpt-4o-mini · gpt-transcribe · gpt-4o-mini-tts
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-19a8b765-fixpoint.
README
README.mdFixPoint — Saqta Voice Agent
Голосовой ИИ-помощник контакт-центра вымышленной страховой компании Saqta Insurance. Хакатонный прототип для клиентов, которым нужно узнать условия страхования, рассчитать стоимость, получить сведения о полисе или заявлении, изменить данные либо обратиться к оператору.
Проект решает задачу выбора страхового сценария из разговорной речи: похожие формулировки могут означать покупку полиса, страховой случай или жалобу. Агент использует LLM для понимания русского, казахского и смешанного ввода, а расчёты и изменения выполняет программный контроллер на тестовых данных.
Статус: работающий прототип с реальными AI-интеграциями и локальными моками — имитациями страхового бэкенда. Полная приёмка всех сценариев и голосового общения RU/KK/mixed не завершена. Известные ошибки перечислены ниже; наличие сценария в каталоге не означает, что все его варианты работают корректно.
Что реализовано
- Веб-интерфейс с кнопкой начала/завершения разговора, записью с микрофона, определением паузы и автоматическим воспроизведением ответа. После ответа запись возобновляется.
- Текстовый ввод для диагностики через тот же серверный конвейер, история реплик и панель
devс трассировкой. - LLM-маршрутизатор с каталогом 40 бизнес-сценариев и 3 системных намерений. Он возвращает структурированные данные: сценарии, уверенность, язык, извлечённые параметры и краткое основание выбора.
- Состояние диалога: сбор недостающих параметров, идентификация клиента, очередь запросов, сохранение прерванной темы и возврат к ней. Сложные сочетания этих возможностей ещё требуют доработки.
- 31 мок-действие: чтение полисов и заявлений, расчёты, создание и изменение записей, бронирование, демонстрационные SMS и передача контекста оператору.
- Подготовка необратимой операции до исполнения (
preview), запрос согласия и защита от повторного исполнения по идентификаторам операции и запроса. - Проверка формата идентификаторов, в том числе продиктованных телефонов, ИИН и номеров документов. Неполное распознанное значение должно запрашиваться повторно.
- Локальный архив разговоров: история, трассировки, результаты моков и экспорт JSON. Аудиозаписи и ключи API в архив не включаются.
SMS, почта, платежи, CRM и звонок живому оператору не подключены. Их результаты имитируются и фиксируются в журнале действий. Статус handoff_prepared означает подготовленную мок-передачу, а не реальное соединение.
Как работает решение
- Пользователь открывает сайт, нажимает кнопку микрофона и разрешает запись.
- Браузер записывает речь до паузы и отправляет завершённую запись на сервер. STT преобразует её в текст.
- LLM выбирает сценарии с учётом каталога и контекста, определяет язык ответа и извлекает параметры запроса.
- Контроллер проверяет параметры, задаёт уточняющие вопросы и вызывает нужные мок-действия. Перед необратимым изменением он подготавливает операцию и запрашивает согласие.
- Ответ формируется по базе знаний и результатам действий, преобразуется в аудио и воспроизводится в браузере. Разговор продолжается автоматически.
- В панели
devможно проверить выбранный сценарий, параметры, действия и задержки, а затем открыть сохранённый разговор.
Например, запрос «Где находится офис в Алматы?» должен пройти через SC33 и get_offices, после чего агент сообщит адрес из базы знаний. Текстовый ввод начинает обработку с шага маршрутизации; его ответы автоматически не озвучиваются.
Технологии
| Компонент | Используется в репозитории |
|---|---|
| Backend | Python, FastAPI, Uvicorn, Pydantic / pydantic-settings, HTTPX |
| Frontend | TypeScript, React 19, Vite 7, CSS, pnpm |
| Запись и воспроизведение | Браузерные MediaRecorder, Web Audio API, HTMLAudioElement; собственный энергетический детектор активности речи |
| Обмен данными | HTTP API и WebSocket |
| LLM | OpenAI Python SDK, Responses API, структурированный ответ по Pydantic-схеме; LLM_MODEL=gpt-4o-mini |
| Распознавание речи (STT) | OpenAI Audio Transcriptions; STT_MODEL=gpt-transcribe |
| Синтез речи (TTS) | OpenAI Audio Speech, MP3; TTS_MODEL=gpt-4o-mini-tts, голос marin |
| Данные | JSON-датасет, мок-состояние в памяти, JSON-архивы на диске; отдельной БД нет |
| Проверки | pytest, встроенный test runner Node.js, TypeScript, скрипт оценки из датасета |
| Дополнительный интерфейс | Streamlit для разработки и диагностики |
| Подготовка развёртывания | Docker Compose, Caddy, Cloudflare Quick Tunnel |
Имена AI-моделей выше — значения конфигурации репозитория, а не гарантия доступности для любого API-ключа. Нужны доступ к соответствующим моделям и сеть. Модели настраиваются отдельно, но текущие адаптеры используют общий клиент OpenAI и один серверный ключ. Groq в активной конфигурации не используется.
Архитектура
Браузер: React, микрофон, детектор паузы
│ завершённая запись / текст через HTTP и WebSocket
▼
FastAPI: сессии, запросы, события
├─ STT → текст
└─ LLM-маршрутизатор → сценарии, язык, параметры
│
▼
Контроллер диалога
проверка → сбор данных → preview → согласие
│
▼
Мок-действия и база знаний
│
▼
Текст ответа → TTS → браузер
Состояние, события и результаты → трассировка → JSON-архив
LLM не записывает данные напрямую. Формулы, проверки согласия и изменения выполняются кодом. Каждый разговор получает собственную копию исходных мок-данных.
| Каталог | Назначение |
|---|---|
backend/app/core/ | Каталог, схемы, маршрутизатор, нормализация, состояние и исполнитель диалога |
backend/app/services/ | Мок-действия, адаптеры OpenAI и архив сессий |
backend/app/main.py | HTTP/WS API и жизненный цикл сессии |
backend/fixtures/ | Отдельное синтетическое расписание для записей |
frontend/src/ | Экран разговора, запись, воспроизведение, трассировка и архив |
case/voice_router_dataset/ | Исходные требования и страховые данные |
tests/, scripts/ | Проверки и диагностические прогоны |
docs/ | Решения, план разработки, отчёты и примеры для проверки |
Установка и запуск
Требования
- Python 3.12+.
- Node.js 22.12+, pnpm. В Docker-сборке frontend закреплён pnpm 10.17.1.
- API-ключ OpenAI с доступом к настроенным моделям и доступ в интернет.
- Браузер с поддержкой записи и воспроизведения аудио, микрофон и динамики/наушники.
Команды ниже выполняются из корня скачанного или клонированного проекта. Основной локальный вариант — FastAPI и React в двух терминалах.
1. Подготовить Python-окружение
Windows / PowerShell:
python -m venv .venv
.venv\Scripts\python.exe -m pip install -r backend/requirements.txt
if (-not (Test-Path .env)) { Copy-Item .env.example .env }
Linux / macOS:
python3 -m venv .venv
.venv/bin/python -m pip install -r backend/requirements.txt
test -f .env || cp .env.example .env
2. Настроить сервер
В корневом .env заполнить OPENAI_API_KEY. Исходные настройки доступны в .env.example:
OPENAI_API_KEY=ваш_ключ
LLM_PROVIDER=openai
STT_PROVIDER=openai
TTS_PROVIDER=openai
LLM_MODEL=gpt-4o-mini
STT_MODEL=gpt-transcribe
TTS_MODEL=gpt-4o-mini-tts
TTS_VOICE=marin
MOCK_MODE=false
REQUEST_TIMEOUT=35
Ключ читается сервером. Не помещайте его во frontend или Git. Без ключа приложение может запуститься, но реальные LLM/STT/TTS-запросы не работают. MOCK_MODE=true предназначен для диагностики: он не заменяет понимание произвольной речи и не отключает отдельно настроенные STT/TTS.
3. Запустить backend — терминал 1
Windows:
.venv\Scripts\python.exe -m uvicorn app.main:app --app-dir backend --host 127.0.0.1 --port 8000
Linux / macOS:
.venv/bin/python -m uvicorn app.main:app --app-dir backend --host 127.0.0.1 --port 8000
Проверка запуска: API health. Описание HTTP API: Swagger UI. Поля *_ready в health показывают наличие конфигурации и ключа, а не успешный тест внешнего сервиса.
4. Запустить frontend — терминал 2
Если pnpm ещё не установлен:
npm install --global [email protected]
Затем из корня проекта:
cd frontend
pnpm install --frozen-lockfile
pnpm dev
Открыть http://127.0.0.1:4173. Vite перенаправляет /api и WebSocket на локальный backend. Оба терминала должны оставаться запущенными; остановка — Ctrl+C.
Микрофону нужен localhost или HTTPS. Для удалённого запуска предусмотрена отдельная инструкция VPS. start.ps1, run.py и основной docker-compose.yml запускают вспомогательный Streamlit; для главного React-демо используйте команды выше или docker-compose.vps.yml. Docker-сборка и запуск на VPS в текущем отчёте проекта ещё не подтверждены.
Как проверить решение жюри
Быстрая проверка: адрес офиса
- Открыть сайт и нажать кнопку микрофона с подписью «Начать разговор». Разрешить доступ к микрофону.
- Сказать: «Где находится офис в Алматы и когда он работает?», затем сделать паузу.
- Проверить ответ по данным
knowledge_base.json: Abai Ave 150; понедельник–пятница 09:00–18:00, суббота 10:00–15:00. Это адрес и расписание вымышленной компании из датасета. - Завершить разговор и открыть dev → Диагностика разговора. Ожидаются сценарий SC33, действие get_offices, распознанный текст и измерения. Формулировка ответа может отличаться; факты должны совпадать.
- В dev → Сохранённые разговоры обновить список, выбрать этот разговор и проверить историю или скачать JSON.
При проблеме с микрофоном ту же фразу можно ввести в текстовое поле. Это проверит текстовый конвейер, но не засчитывается как проверка голоса.
Расчёт и подтверждение операции
Для каждой независимой проверки начинайте новый разговор. Все идентификаторы ниже — синтетические данные из мока.
| Проверка | Реплики и ожидаемый результат |
|---|---|
| Расчёт ОГПО | «Рассчитайте ОГПО на год для легковой машины в Алматы». На вопрос об ИИН: [ИИН скрыт]. Ожидается SC01, calc_ogpo_price и 38 000 тенге; новый полис не создаётся. |
| Изменение адреса | «Хочу изменить контактный адрес». Для идентификации: +77010000001; новый адрес: Алматы, Тестовая улица, 10. До согласия должен быть только update_contact:preview. Ответить отдельной репликой «Да»: ожидается update_contact:execute и изменение адреса в мок-данных архива. |
| Отказ | Повторить изменение адреса в новой сессии, но на подтверждение ответить «Нет». Изменения быть не должно. |
| Казахский ввод | В новой сессии: «Алматыдағы кеңсенің мекенжайы қандай?». Ожидаются SC33 и ответ на казахском с тем же адресом. Это проверочный пример, а не заявление о завершённой приёмке KK. |
ИИН и телефон можно сначала ввести текстом, затем повторить голосом, чтобы раздельно проверить исполнение сценария и качество STT. Не добавляйте новый вопрос к «Да» в базовом демонстрационном пути: составное подтверждение — известная недоработка.
Больше примеров: 40 бизнес-сценариев и системные намерения для ручной проверки. В этом документе описано ожидаемое поведение, включая ещё не закрытые требования; это не протокол успешных прогонов.
Тесты и оценка маршрутизатора
Из корня проекта, Windows:
.venv\Scripts\python.exe -m pytest -q
.venv\Scripts\python.exe -X utf8 scripts/validate_dataset.py
На Linux / macOS замените .venv\Scripts\python.exe на .venv/bin/python. Проверки frontend:
cd frontend
pnpm test
pnpm run build
Полный прогон 104 реплик через реальный платный API — отдельно, из корня:
.venv\Scripts\python.exe -X utf8 scripts/evaluate_router.py
Прогнозы, трассы и метрики сохраняются в reports/, который исключён из Git. Если прогнозы уже есть, их можно оценить без повторных API-запросов:
.venv\Scripts\python.exe -X utf8 case/voice_router_dataset/evaluate.py reports/predictions.json case/voice_router_dataset/dev_utterances.json
По аудиту от 23.09.2026, локально проходили 149 backend-тестов, 20 frontend-тестов и production build. Часть тестов подаёт заранее подготовленные решения маршрутизатора: эти тесты проверяют контроллер, а не качество LLM или распознавания речи.
Сохранённый прогон маршрутизатора показал:
| Метрика | Результат |
|---|---|
| Правильный первый сценарий | 91/104 — 87,5% |
| Полное совпадение набора сценариев | 76/104 — 73,1% |
| Полнота распознавания намерений в многоцелевых запросах | 18/26 — 69,2% |
После этого прогона код менялся. Эти числа относятся к сохранённому прогону, а не доказывают качество последней версии. Полная приёмка десяти примерных диалогов с реальной LLM и голоса RU/KK/mixed ещё не выполнена.
Данные и интеграции
Единственный источник страховых фактов — case/voice_router_dataset. Все данные синтетические; расчётное «сегодня» всегда 2026-10-01, независимо от даты компьютера.
| Источник | Использование |
|---|---|
scenarios.json | 40 бизнес-сценариев, 3 системных намерения, границы выбора и условия передачи оператору |
slots.json | 43 параметра: типы, форматы и вопросы на RU/KK |
actions.json | 31 мок-действие, включая 9 необратимых, ошибки и очереди операторов |
knowledge_base.json | Продукты, тарифы, правила, офисы, клиники и документы |
mock_backend.json | 11 клиентов, 11 полисов, 4 заявления и 2 платежа |
dialogs_sample.json | 10 примеров диалогов |
dev_utterances.json, evaluate.py | 104 размеченные реплики и эталонный оценщик |
backend/fixtures/schedule.json | Отдельное синтетическое расширение: доступность записей |
Внешний AI-сервис — OpenAI: в него передаются аудио для STT, текст и контекст для LLM, текст ответа для TTS. Cloudflare используется только в подготовленной схеме публичного доступа; страховых интеграций с реальными организациями нет.
Исходные JSON не изменяются действиями агента. Новая сессия получает исходные моки; изменения других разговоров в неё не переносятся. Архивы сохраняются в runtime/sessions/<UUID>.json; путь можно изменить серверной настройкой SESSION_ARCHIVE_DIR. После рестарта старый разговор доступен для просмотра, но автоматически не продолжается. Хранилище рассчитано на один серверный процесс.
Ограничения, ошибки и недоработки
Это демо на тестовых данных. Ни реальные страховые операции, ни реальные платежи, SMS, почта, CRM или телефония не выполняются. У приложения нет авторизации: пользователи с доступом к серверу могут обращаться к API и архивам.
В аудите зафиксированы следующие проблемы, которые сохраняются в проверенной реализации:
| Проблема | Проявление |
|---|---|
| Роль полиса в SC12 | Может подставиться собственный полис потерпевшего вместо полиса виновника. |
| Срочная помощь в SC15 | При недостающих данных исполнителю передаются факты о мошенничестве вместо медицинского случая за границей. |
| Параметры продукта | Явные 6 месяцев ОГПО и пакет КАСКО Lite теряются при валидации; расчёт может использовать неверный срок или пакет. |
| Запись после preview | Если показанное время стало занято, execute может выбрать другое без нового согласия. |
| Составное подтверждение | «Да, подтверждаю. А где офис?» повторяет подготовку операции вместо полного исполнения обеих частей запроса. |
| Документы в SC18 | Для вопроса о конкретном заявлении не вызывается необходимое чтение get_claim. |
| Ошибки действий | Нет требуемой автоматической повторной попытки при service_unavailable; обработка разных кодов недостаточно разделена. |
| Условность SMS | Некоторые справочные сценарии отправляют мок-SMS по известному телефону без отдельного запроса пользователя. |
| Трассировка | Некоторые ранние передачи оператору отсутствуют в trace.actions; предупреждения TTS не включаются в trace.errors, хотя сохраняются в событиях. |
| Потеря соединения | Frontend предлагает новый разговор вместо восстановления текущего из серверного снимка. |
Другие ограничения текущей версии:
- Многоцелевые и неясные запросы распознаются нестабильно. В сохранённом прогоне полностью совпали 5 из 13 многоцелевых запросов и ни один из трёх
SYS_UNCLEAR. - STT обрабатывает законченную запись; промежуточных транскрипций нет. Микрофон не записывает во время ответа бота. Отдельного повтора только озвучивания сохранённого ответа нет.
- Распознавание госномеров и аббревиатур нестабильно; полная проверка живой RU/KK/mixed-речи, чисел и смены языка не завершена. Наличие адаптеров не гарантирует качество речи.
- В архивном срезе аудита медиана 19 голосовых ходов с измерением составляла 6,17 с. Это неконтролируемая выборка, не финальный бенчмарк. Бонус ТЗ за задержку до 3 секунд не подтверждён.
- Смена автомобиля с перерасчётом, часть изменений премии, КАСКО для возраста без ставки и запись к специалистам кроме терапевта ограничены пробелами данных и требуют оператора. Успешный SC16 не проверен: в исходном моке нет подходящего полиса от несчастного случая.
- В текущем интерфейсе текстовый ввод и история видны на основном экране.
?dev=1и?jury=1открывают одну модальную панель; отдельной постоянной панели жюри нет. Чтобы управлять разговором, её нужно закрыть. Confidence находится вdev, а не на главном экране. - JSON-архив не является транзакционной БД. После аварии во время хода результат может быть неизвестен; операции из архива автоматически не повторяются.
Развёрнутая версия
Постоянная работающая deployed-версия в репозитории не подтверждена. В локальном runtime/tunnel/url.txt сохранена временная ссылка Quick Tunnel; её доступность при подготовке README подтвердить не удалось. Она зависит от работающего туннеля и может устареть.
Для самостоятельного развёртывания подготовлены docker-compose.vps.yml, deploy/ и инструкция VPS. По текущему журналу работ, Docker-сборка и запуск на VPS ещё не проверены. Скрипт развёртывания сохраняет полученный временный адрес в runtime/public-url.txt на машине запуска.
Дополнительные материалы
Авторы коммитов
История коммитов
- 2026-09-23 17:56docs: prepare README for hackathon juryRakhim
- 2026-09-23 17:47Add VPS deployment and scenario audit documentationRakhim
- 2026-09-23 17:39Fix voice endpointing under background noiseRakhim
- 2026-09-23 17:31feat(frontend): preview confirmation and handoff statesAdil
- 2026-09-23 17:25docs: point Streamlit deployment to integration branchAleks-48
- 2026-09-23 17:18Merge remote-tracking branch 'origin/codex/voice-dialogue-confidence' into codex/streamlit-cloudAleks-48
- 2026-09-23 17:16Validate spoken identifiers before scenario actionsRakhim
- 2026-09-23 16:58Add session archive and improve voice interactionRakhim
Все коммиты (16)
- 2026-09-23 16:10docs: align README and harden backend acceptanceAleks-48
- 2026-09-23 15:48Integrate OpenAI voice dialogue and confidence displayRakhim
- 2026-09-23 15:18feat(frontend): add design preview and conversation completion stateAdil
- 2026-09-23 15:10fix(backend): complete dialogue action flows and acceptance checksAleks-48
- 2026-09-23 14:22Ignore local pnpm store cacheRakhim
- 2026-09-23 14:20feat(backend): add session-based voice router API and Streamlit demoCodex
- 2026-09-23 13:57feat(frontend): add Saqta chat UI with browser speech recognitionAdil
- 2026-09-23 13:35Add voice agent dataset, architecture and implementation planRakhim
Структура репозитория (99 файлов)
.dockerignore .env.example .gitignore AGENTS.md Dockerfile README.md backend/app/__init__.py backend/app/config.py backend/app/core/__init__.py backend/app/core/context.py backend/app/core/contracts.py backend/app/core/engine.py backend/app/core/executor.py backend/app/core/fast_answers.py backend/app/core/identifiers.py backend/app/core/latency.py backend/app/core/models.py backend/app/core/router.py backend/app/core/scenarios.py backend/app/core/slots.py backend/app/main.py backend/app/services/__init__.py backend/app/services/actions.py backend/app/services/llm.py backend/app/services/session_archive.py backend/app/services/stt.py backend/app/services/tts.py backend/fixtures/schedule.json backend/requirements.txt case/voice_router_dataset/README.kz.md case/voice_router_dataset/README.md case/voice_router_dataset/README.ru.md case/voice_router_dataset/actions.json case/voice_router_dataset/dev_utterances.json case/voice_router_dataset/dialogs_sample.json case/voice_router_dataset/evaluate.py case/voice_router_dataset/knowledge_base.json case/voice_router_dataset/mock_backend.json case/voice_router_dataset/scenarios.json case/voice_router_dataset/slots.json deploy/Caddyfile deploy/Dockerfile.web docker-compose.vps.yml docker-compose.yml docs/backend-api.md docs/backend-validation.md docs/compliance-audit-2026-09-23.md docs/dataset-validation.json docs/decisions.md docs/frontend-integration.md docs/iin-recognition-2026-09-23.md docs/implementation-plan.md docs/manual-scenario-dialogues.md docs/progress.md docs/project-audit-2026-09-23.md docs/roadmap.md docs/scenario-validation-2026-09-23.md docs/vps.md frontend/index.html frontend/package.json frontend/pnpm-lock.yaml frontend/pnpm-workspace.yaml frontend/src/App.tsx frontend/src/components/AudioVisualizer.tsx frontend/src/components/SessionArchive.tsx frontend/src/components/TracePanel.tsx frontend/src/confidence.test.ts frontend/src/confidence.ts frontend/src/hooks/useMicrophone.ts frontend/src/hooks/useSession.ts frontend/src/main.tsx frontend/src/state/sessionReducer.test.ts frontend/src/state/sessionReducer.ts frontend/src/styles.css frontend/src/types.ts frontend/src/voiceActivity.test.ts frontend/src/voiceActivity.ts frontend/tsconfig.json frontend/vite.config.ts pytest.ini requirements.lock.txt requirements.txt run.py scripts/audit_compliance.py scripts/evaluate_router.py scripts/probe_iin_route.py scripts/probe_iin_stt.py scripts/probe_spoken_identifiers.py scripts/validate_dataset.py scripts/vps-up.sh start.ps1 streamlit_app.py tests/conftest.py tests/test_api.py tests/test_domain.py tests/test_identifiers.py tests/test_scenario_paths.py tests/test_session_archive.py tests/test_trace_regression.py
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.