О проекте
Команда: IITU+SPARTANS
Кейс HackAlem.ai: Коммуникации — Halyk Bank · кейс 2
Стадия: Доведённый продукт
Задача кейса: «Voice Router»: голосовой AI, маршрутизирующий звонки в контакт-центре страховой компании
Суть
Голосовой робот контакт-центра страховой компании: распознаёт ru/kk/смешанную речь, LLM выбирает сценарий из 40 по контексту диалога, отвечает голосом. Супервизор видит трассировку решений и статистику.
Как устроено
Браузер записывает реплику и по тишине определяет конец фразы. /api/stt распознаёт её через gpt-transcribe с подсказкой-словарём ru+kk. FastAPI-сервис router через LLM со structured output возвращает сценарии с confidence, reason, quote, alternatives и slots, с учётом правил not_this_if. Политика по порогам: ≥0.75 запуск, 0.45–0.75 уточнение, два неуверенных хода подряд — передача оператору. Ответ собирается из фраз набора и озвучивается gpt-4o-mini-tts потоком, робота можно перебить. Ходы журналируются в PostgreSQL. Если router недоступен, работает TS-маршрутизатор ядра, без ключа — демо-режим.
Ключевые решения
- LLM-маршрутизация структурированным выводом
- пороговая политика решений
- STT с глоссарием
- потоковый TTS
- barge-in
- Zod-контракты
- TDD
Метрики
На размеченном наборе организаторов (104 реплики, evaluate.py): primary_accuracy 1.000, full_match 0.990
Особенности
Модель сначала разбирает ситуацию и цитату и только потом выбирает сценарий, это подняло full_match. Маршрутизация трёхуровневая с fallback. Разработка шла через TDD (сначала падающий тест), миграции неизменяемы. Первые коммиты от 09.09 — только README и AGENTS.md, сама реализация начата 23.09.
Стек
Next.js 16 · TypeScript 7 · FastAPI · PostgreSQL 16 · pnpm · Vitest · pytest · Docker Compose · GitHub Actions
Модели
OpenAI gpt-5.4-mini · OpenAI gpt-transcribe · OpenAI gpt-4o-mini-tts
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-5fefe2e0-iitu-spartans.
README
README.mdVoice Router — гибридный голосовой AI-робот с LLM-слоем выбора сценария
Хакатон HackAlem AI 2026 (Astana Hub) · трек 09 «Коммуникации» · кейс предоставлен Halyk Bank · команда IITU+SPARTANS, Международный университет информационных технологий.
Архитектура и история решений · Правила работы · Навыки для агентов · Задачи
Статус. Работает сквозной сценарий: звонок роботу голосом в веб-интерфейсе (распознавание русской, казахской и смешанной речи на сервере, ответ голосом, робота можно перебить) или текст -> выбор сценария LLM-слоем -> политика решений -> ответ из фраз набора -> панель трассировки и статистика журнала для супервизора; каждый ход пишется в PostgreSQL. Замер на размеченном наборе организаторов (104 реплики,
evaluate.py): primary_accuracy 1.000, full_match 0.990 (раздел 9). Запуск одной командой:docker compose up --build; проверка кода:pnpm verify. Без ключа модели работает демо-режим (Положение §5.6.6). Границы применимости — раздел 13.
Быстрый запуск
Нужны Docker с Compose; для режима разработки — также Node.js 24+ и pnpm из packageManager.
Перед первым запуском скопируйте .env.example в .env и задайте собственный OPENAI_API_KEY
для живого LLM и серверного голоса. Без ключа доступен явно помеченный демо-режим.
| Задача | Команда из корня репозитория | Где открыть |
|---|---|---|
| Запустить решение целиком | docker compose up --build -d --wait | http://localhost:3000 |
| Проверить код | pnpm verify | Проверка типов и тесты в терминале |
| Проверить запущенное приложение | pnpm smoke или pnpm smoke http://localhost:3001 | БД, журнал и настройка серверного голоса |
| Править интерфейс с горячим обновлением | После pnpm install --frozen-lockfile: pnpm dev:web | http://localhost:3001 |
Проверка готовности на выбранном порту: /api/health → ok: true, db: true.
Введите произвольную реплику: ответ появится слева, решение и замеры — справа; кнопка
«Полный разбор решения» откроет исходную трассировку, параметры и подробные задержки.
Раскройте «Статистика журнала за 24 часа»: число ходов должно увеличиться после запроса.
Dev-режим использует тот же PostgreSQL, автоматически применяет миграции и читает корневой .env.
После изменений для production повторите docker compose up --build -d --wait.
1. Назначение решения
Голосовой робот контакт-центра хорошо распознаёт речь, но сценарий разговора у большинства систем выбирает классификатор намерений. Он обучен на фиксированных формулировках и ошибается на живой речи: смена темы посреди диалога, запрос на стыке двух сценариев, переход с русского на казахский внутри фразы. Каждая ошибка выбора — перевод на оператора или потерянный клиент.
Voice Router заменяет классификатор LLM-слоем, который выбирает сценарий по контексту всего диалога, и объясняет каждое решение.
| Пользователь | Что получает |
|---|---|
| Клиент контакт-центра | Говорит своими словами на русском или казахском; робот с первой реплики запускает нужный сценарий, переключается при смене темы без потери контекста, закрывает вопрос без оператора или передаёт оператору вместе с контекстом |
| Супервизор | Видит по каждой реплике выбранный сценарий, обоснование, альтернативы с уверенностью и время каждого этапа |
Данные — стартовый набор организаторов: вымышленная страховая компания Saqta Insurance, 40 сценариев и 3 системных намерения, база знаний, тестовые клиенты, размеченные диалоги и реплики.
2. Архитектура
Модульный монолит на Next.js и TypeScript и отдельный сервис выбора сценария на FastAPI. Решение о сценарии
принимает LLM на сервере: основной путь — сервис router (раздел 9); при его сбое — маршрутизатор ядра на
TypeScript внутри приложения; без ключа — демо-режим. Голос в режиме звонка: браузер записывает реплику и
определяет её конец по тишине, сервер распознаёт речь (gpt-transcribe, ожидаемые языки ru и kk, подсказка из
словаря терминов набора) и озвучивает ответ (gpt-4o-mini-tts, потоком); без ключа работают распознавание и
синтез браузера (раздел 11). Журнал ходов хранится в PostgreSQL, сводку по нему видит супервизор (раздел 12).
2.1. Конвейер обработки реплики
flowchart LR
Mic["Звонок: микрофон браузера, конец фразы по тишине"] --> STT["/api/stt: gpt-transcribe, ru + kk, подсказка словаря"]
Text["Текст"] --> Turn["/api/turn: реплика + состояние диалога"]
STT --> Turn
Turn --> Router["router, FastAPI: LLM выбирает сценарии, confidence, reason, quote, slots"]
Router -. сбой сервиса .-> Core["маршрутизатор ядра, TypeScript + LLM"]
Core -. нет ключа .-> Demo["демо-режим без модели"]
Router --> Policy["policy: запуск / уточнение / оператор, срочные первыми"]
Core --> Policy
Demo --> Policy
Policy --> Reply["reply: фраза сценария из набора, ru или kk"]
Reply --> TTS["/api/tts: голос ответа потоком"]
Policy --> Trace["trace: сценарий, обоснование, альтернативы, задержка по этапам"]
Trace --> Panel["Панель супервизора"]
Turn --> DB[("PostgreSQL: журнал ходов")]
DB --> Stats["/api/stats: сводка журнала"]
Stats --> Panel
2.2. Один ход диалога с замером задержки
sequenceDiagram
autonumber
participant C as Клиент (браузер, звонок)
participant W as Приложение web
participant S as OpenAI: распознавание и синтез
participant R as Сервис router
participant D as PostgreSQL
C->>C: запись реплики, конец фразы по тишине
C->>W: POST /api/stt (запись)
W->>S: gpt-transcribe, языки ru и kk, подсказка словаря
S-->>W: текст реплики
W-->>C: текст, t_stt от конца речи
C->>W: POST /api/turn (текст + состояние диалога)
W->>R: POST /route (реплика + история)
R-->>W: сценарии, confidence, reason, quote, alternatives, slots, t_router
W->>W: политика по порогам, ответ из фраз набора, t_response
W->>D: запись хода, решения и задержек
W-->>C: ответ + трассировка для панели супервизора
C->>W: GET /api/tts (текст ответа)
W->>S: gpt-4o-mini-tts
S-->>C: голос ответа потоком — клиент может перебить
2.3. Политика принятия решений
Пороги и порядок взяты из раздела «Политика принятия решений» README стартового набора.
stateDiagram-v2
[*] --> Listening
Listening --> Routing: реплика клиента
Routing --> RunScenario: confidence не ниже 0.75
Routing --> Clarify: confidence от 0.45 до 0.75
Routing --> Handoff: confidence ниже 0.45 второй раз подряд
Routing --> Handoff: клиент просит оператора
Clarify --> Listening: один короткий вопрос с двумя вариантами
RunScenario --> CollectSlots: не хватает данных
CollectSlots --> Listening: вопрос по одному слоту
RunScenario --> Confirm: необратимое действие
Confirm --> Execute: явное подтверждение клиента
Confirm --> Listening: отказ или уточнение
RunScenario --> Execute: обратимое действие
Execute --> Listening: сценарий завершён, возврат к отложенной теме
Handoff --> [*]: передача оператору с резюме контекста
Реализовано в политике (packages/core/src/policy, 14 тестов): запуск, уточнение с двумя вариантами, передача
оператору после двух неуверенных ходов подряд или по просьбе клиента (SC37), продолжение активного сценария,
очередь нескольких намерений со срочными первыми. Сбор слотов по одному и подтверждение необратимых действий —
следующий шаг (#6): сейчас робот не выполняет действий, а открывает сценарий фразой набора и просит данные.
2.4. Модель предметной области
classDiagram
class Scenario {
+string scenarioId
+string domain
+string category
+string priority
+bool fastPathEligible
+bool requiresIdentification
+bool requiresConfirmation
+NotThisIf[] notThisIf
}
class RouteDecision {
+ScenarioScore[] scenarios
+ScenarioScore[] alternatives
+string language
+map slots
+bool isContinuation
}
class ScenarioScore {
+string scenarioId
+number confidence
+string reason
}
class DialogState {
+string dialogId
+string language
+string clientId
+string activeScenario
+string[] topicStack
+map slots
}
class TraceEntry {
+int turn
+string transcript
+RouteDecision decision
+string[] actions
+LatencyMs latencyMs
}
class LatencyMs {
+int stt
+int triage
+int router
+int response
+int ttsFirstAudio
+int total
}
RouteDecision "1" *-- "1..*" ScenarioScore
TraceEntry "1" *-- "1" RouteDecision
TraceEntry "1" *-- "1" LatencyMs
DialogState "1" o-- "0..1" Scenario : activeScenario
2.5. Схема базы данных (миграция db/migrations/001_turn_journal.sql)
Журнал ходов диалога для панели супервизора. Выбранный сценарий и уверенность вынесены в столбцы — по ним строятся статистика и фильтр; полное решение и задержка хранятся в JSONB для показа.
erDiagram
DIALOGS ||--|{ TURNS : contains
DIALOGS {
uuid dialog_id PK
timestamptz started_at
}
TURNS {
bigserial turn_id PK
uuid dialog_id FK
int turn_no
timestamptz created_at
text transcript
text language
text source
text action_kind
text top_scenario
numeric top_confidence
jsonb decision
text reply
text error
jsonb latency_ms
}
Индексы: turns (created_at DESC) — лента последних ходов; turns (top_scenario) — статистика по сценарию.
3. Используемые технологии
| Слой | Технология | Назначение |
|---|---|---|
| Среда выполнения | Node.js 24 | сервер приложения |
| Пакеты | pnpm 12 (через corepack) | установка зависимостей, рабочее пространство |
| Приложение | Next.js 16, TypeScript 7 | веб-интерфейс и серверные маршруты API |
| Контракты | Zod | валидация ответа LLM и данных между модулями |
| Сервис выбора сценария | Python 3.11, FastAPI, Pydantic, OpenAI SDK (Responses API, structured output) | POST /route, раздел 9 |
| База данных | PostgreSQL 16 | состояние диалога, решения маршрутизатора, задержки |
| LLM и голос | OpenAI API: gpt-5.4-mini — выбор сценария, gpt-transcribe — распознавание, gpt-4o-mini-tts — синтез; модели задаются в окружении | выбор сценария, режим звонка |
| Тесты | Vitest 5, pytest | модульные и контрактные тесты; эталонный замер — evaluate.py организаторов |
| Развёртывание | Docker Compose | запуск всей системы одной командой |
Точные версии фиксируются в package.json и pnpm-lock.yaml, для сервиса — в backend/requirements.txt.
4. Установка
- Установить Docker с поддержкой Compose.
- Клонировать репозиторий.
cp .env.example .env. Ключ OpenAI необязателен: без него система работает в демо-режиме. Для живого режима каждый проверяющий вписывает свой ключ вOPENAI_API_KEY(раздел 7); ключи команды в репозиторий не попадают.
Для запуска без Docker: Node.js 24, corepack enable, pnpm install, локальный PostgreSQL 16.
Проверка кода
corepack enable # включает pnpm нужной версии из поля packageManager
pnpm verify # установка по lock-файлу, проверка типов, тесты
Ожидаемый результат: Test Files 15 passed, Tests 111 passed, проверка типов без ошибок. Тест словаря
терминов сверяет словарь с данными набора в data/kit (раздел 10).
Тесты сервиса выбора сценария (Python 3.11+, без сети и без ключа — клиент модели подменяется; набор в data/kit):
cd backend
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/python -m pytest # ожидается: 70 passed
Та же цепочка описана для GitHub Actions в .github/workflows/ci.yml. Автоматический запуск в
организации хакатона сейчас недоступен (задачи не стартуют из-за блокировки биллинга организации),
поэтому проверка выполняется локально командой pnpm verify; workflow можно запустить вручную.
5. Запуск
docker compose up --build
Поднимает три сервиса: PostgreSQL 16 (db), приложение (web) и сервис выбора сценария (router).
Приложение ждёт готовности базы, применяет миграции и запускает сервер на http://localhost:3000; от router
оно не зависит. router слушает http://localhost:8000 и читает стартовый набор data/kit (только чтение).
Проверка готовности (docker compose ps показывает все три сервиса healthy):
curl http://localhost:3000/api/health
# {"ok":true,"db":true,"migrations":1}
curl http://localhost:8000/health
# {"status":"ok","model_key":true,"model":"gpt-5.4-mini","scenarios":40,"slots":43,"actions":31,"clients":11}
Голосовой канал и сводка для супервизора:
curl http://localhost:3000/api/stt
# {"available":true,"model":"gpt-transcribe"} — с ключом; без ключа available:false
curl http://localhost:3000/api/stats
# сводка журнала ходов за 24 часа (раздел 12)
Без ключа OpenAI в .env система тоже запускается: router сообщает "model_key":false и отвечает 503 на
/route, приложение выбирает сценарий в демо-режиме, кнопка «Позвонить роботу» не показывается — остаются
микрофон браузера и текст.
Остановить: docker compose down (данные базы сохраняются в томе db-data; удалить вместе с ними —
docker compose down -v).
Запуск без Docker: cp .env.example .env, указать DATABASE_URL локального PostgreSQL 16, затем
pnpm install, pnpm db:migrate, pnpm --filter @voice-router/web dev.
6. Необходимые зависимости
Docker и Docker Compose — для основного способа запуска. Для запуска без Docker — Node.js 24, pnpm 12,
PostgreSQL 16, для сервиса выбора сценария — Python 3.11. Ключ OpenAI API нужен для живого режима: выбор
сценария моделью, серверное распознавание и голос робота (раздел 7). Для голоса — браузер с микрофоном
(Chrome или Edge); микрофон браузер даёт только на localhost или по HTTPS.
7. Параметры окружения
Полный список с пояснениями — в .env.example (cp .env.example .env). Docker Compose читает .env
автоматически; строка подключения к базе в Compose задана в docker-compose.yml.
| Переменная | Где читается | Назначение, значение по умолчанию |
|---|---|---|
OPENAI_API_KEY | web, router, pnpm stt:eval | ключ живого режима; пусто — демо-режим, кнопка звонка скрыта |
DATABASE_URL | web, pnpm db:migrate | строка подключения к PostgreSQL; в Compose задана в docker-compose.yml |
ROUTER_URL | web | адрес сервиса выбора сценария; в Compose — http://router:8000; пусто — маршрутизатор ядра |
ROUTER_MODEL | web, router | модель выбора сценария; пусто — gpt-5.4-mini |
ROUTER_BASE_URL, ROUTER_API_KEY | web | любой OpenAI-совместимый вход (Chat Completions) для маршрутизатора ядра: тот же промпт и та же проверка ответа; пусто — OpenAI и OPENAI_API_KEY |
ROUTER_REASONING_EFFORT | web, router | глубина рассуждения моделей gpt-5; пусто — none (быстрее) |
GEMINI_API_KEY, GEMINI_MODEL | web | второй движок окна «Сравнить ChatGPT и Gemini» (раздел 12.1): Google Gemini через OpenAI-совместимый вход; пусто — окно сообщает, что ключ не задан; модель по умолчанию gemini-3.8-flash |
GEMINI_LIVE_MODEL | web | модель звонка на странице /live (Gemini Live API, раздел 12.2); пусто — gemini-3.8-live |
STT_MODEL | web | модель распознавания речи; пусто — gpt-transcribe |
TTS_MODEL, TTS_VOICE | web | модель и голос синтеза; пусто — gpt-4o-mini-tts, marin |
DATA_DIR, DATASET_DIR | web, router | каталог стартового набора; пусто — data/kit (в контейнере router — /data/kit) |
STT_GLOSSARY | web | путь к словарю терминов; пусто — packages/core/src/stt/stt-glossary.json |
KIT_DIR | pnpm glossary:build, pnpm stt:eval | каталог набора для сборки словаря и оценки распознавания |
Секреты в репозиторий не попадают: .env исключён в .gitignore.
8. Порядок проверки основного сценария
-
pnpm verify— установка по lock-файлу, проверка типов, тесты (раздел 4). -
cp .env.example .env, вписать свойOPENAI_API_KEY, затемdocker compose up --build; проверитьcurl http://localhost:3000/api/health—{"ok":true,"db":true,"migrations":1}. -
Открыть http://localhost:3000, нажать «Позвонить роботу» и разрешить микрофон. Сказать, например: «Здравствуйте, я вчера оплатил, деньги списались, а полис не оформлен… и ещё адрес поменять надо». Робот определит конец фразы по тишине и ответит голосом; справа панель супервизора покажет транскрипт, язык, источник решения, два сценария по порядку (SC30, затем SC29) с обоснованием и уверенностью, альтернативы и задержку по этапам.
-
Продолжить разговор на казахском и со смешением языков, например «Сәлеметсіз бе, мен кеше оплатил, а полис әлі жоқ, что делать?». Перебить робота голосом — ответ остановится, реплика будет записана.
-
Ниже в панели — статистика журнала: доли запуска, уточнения и передачи оператору, частые сценарии и ходы, где робот сомневался (раздел 12).
-
Тот же ход без интерфейса:
curl -X POST localhost:3000/api/turn -H "Content-Type: application/json" \ -d '{"utterance":"Когда будет выплата по моему заявлению?","state":{"lowConfidenceStreak":0,"history":[]}}' # ответ робота из фраз набора, trace (сценарий, обоснование, альтернативы, источник, задержка), state с dialogId curl "localhost:3000/api/turns?limit=5" # последние ходы из журнала PostgreSQL -
Замер точности на размеченных репликах организаторов (
dev_utterances.json,evaluate.py) — раздел 9.
Без ключа шаги 1, 2 и 6 работают в демо-режиме (trace.source = demo, совпадение слов с примерами каталога);
это проверка работоспособности системы, а не решение задачи маршрутизации.
9. Сервис выбора сценария
Выбор сценария — на LLM-слое: модель читает реплику, состояние диалога и каталог сценариев из данных набора и возвращает сценарии в порядке упоминания. Классификатора намерений нет.
| Путь | Где | Когда работает |
|---|---|---|
| Основной | сервис router (backend/, FastAPI): POST /route, модель gpt-5.4-mini через Responses API со строгой JSON-схемой | ROUTER_URL задан, сервис отвечает |
| Резервный | маршрутизатор ядра на TypeScript (packages/core/src/router) внутри web | сервис недоступен или вернул ошибку, ключ есть |
| Демо | demoDecision ядра, без модели | нет ни сервиса, ни ключа (Положение §5.6.6) |
Сбой сервиса не обрывает разговор: web берёт решение резервным путём, причина сбоя видна в трассировке
(trace.error). Если сервис не отвечает (имя не разрешилось, соединение отклонено, таймаут запроса), web 30 с
не обращается к нему: остановленный контейнер иначе задерживал бы каждый ход (имя router не разрешается ~5 с).
Ответ сервиса с HTTP-ошибкой (502, 503, 504) паузу не включает: следующий ход снова идёт в сервис (ADR-015).
Контракт. Ответ совпадает с RouteDecision из packages/core/src/contracts/route-decision.ts и дополнен полями
для панели трассировки: quote у каждого сценария, situation, response_language, latency_ms, model, usage.
Совпадение проверяется с двух сторон на общей фикстуре packages/core/src/contracts/fixtures/router-service-route.json:
pytest сверяет с ней ответ эндпоинта, Vitest в pnpm verify — схему ядра.
Запрос: POST /route, тело — JSON {"utterance": "...", "state": {...}}. Тело читается как JSON при любом
Content-Type. state необязателен; принимается и формат сервиса (active_scenario, pending_question,
stack, slots, response_language, last_turns), и состояние веб-интерфейса (activeScenario, history,
language).
curl -X POST localhost:8000/route -d '{"utterance":"Когда будет выплата?","state":{}}'
{"scenarios":[{"scenario_id":"SC17","confidence":0.98,
"reason":"Фраза «Когда будет выплата?» — это запрос статуса существующего заявления.","quote":"Когда будет выплата?"}],
"alternatives":[],"language":"ru","response_language":"ru","situation":"later claim status requested",
"slots":{},"is_continuation":false,"latency_ms":1905,"model":"gpt-5.4-mini",
"usage":{"input":8721,"cached":8448,"output":87}}
| Код | Когда |
|---|---|
| 200 | решение по контракту |
| 422 | тело не JSON, нет utterance, пустая или длиннее 2000 символов, state не объект |
| 502 | модель ответила ошибкой или без разобранного ответа |
| 503 | нет OPENAI_API_KEY; /health при этом работает и сообщает "model_key":false |
| 504 | модель не ответила за 6 с |
Инварианты закреплены в коде и тестах (backend/tests/test_router.py): ID сценариев только из данных набора
(enum в JSON-схеме модели), системное намерение не смешивается с бизнес-сценарием, порядок сценариев — по
позиции quote в реплике, пустой ответ модели становится SYS_UNCLEAR.
Запуск отдельно от Compose (ключ читается из корневого .env):
cd backend && .venv/bin/uvicorn app.main:app --port 8000
Оценка на dev-наборе (104 размеченные реплики data/kit/dev_utterances.json, evaluate.py организаторов;
нужен ключ; результаты — в runs/, вне git):
backend/.venv/bin/python scripts/route_batch.py
Результаты (подробно — docs/ROUTER_LOG.md):
| Версия | Модель | primary_acc | full_match | intent_recall | медиана / p90 роутера, мс |
|---|---|---|---|---|---|
| v0 | gpt-4.1-mini | 0.971 | 0.981 | 1.000 | 1736 / 2461 |
| v4, 2 прогона | gpt-5.4-mini | 1.000 / 1.000 | 0.981 / 1.000 | 0.962 / 1.000 | 1476 / 2012 · 1585 / 2277 |
| v5 (сервис /route), 2 прогона | gpt-5.4-mini | 1.000 / 1.000 | 0.981 / 0.990 | 1.000 / 0.962 | 1603 / 2373 · 1656 / 2544 |
| v6 (обоснование на русском), 2 прогона | gpt-5.4-mini | 1.000 / 1.000 | 0.990 / 0.990 | 0.962 / 0.962 | 1813 / 2517 · 1852 / 2834 |
весь стек, сервис напрямую POST :8000/route, 3 параллельно | gpt-5.4-mini | 1.000 | 0.990 | 1.000 | 1598 / 2209 |
весь стек, через POST /api/turn после #34, 3 параллельно | gpt-5.4-mini | 1.000 | 0.990 | 0.962 | 1736 / 2967 |
Последние две строки — замер 23.09 в сети площадки хакатона на запущенном docker compose up --build.
В прогоне через /api/turn 3 из 104 ответов модели не уложились в 6 с (504); эти ходы обслужил резервный
маршрутизатор ядра, и все три выбраны верно.
Dev-набор мал (104 реплики, 7 смешанных, 13 мультиинтентов): 1.000 на нём не гарантирует того же на скрытых репликах жюри.
10. Словарь терминов для распознавания речи
Названия продуктов и термины (ОГПО, КАСКО, ДМС, полис, сақтандыру) распознаются хуже обычной речи, а ошибка
распознавания ведёт к неверному выбору сценария. Словарь терминов передаётся в распознавание речи как
подсказка (issue #8). Код — packages/core/src/stt/, точка подключения — @voice-router/core/stt.
Откуда берутся термины. Только из данных набора (scenarios.json, slots.json), правилами; в коде нет
ни одного термина. Проверочные реплики dev_utterances.json в сборке не участвуют.
| Правило | Что даёт |
|---|---|
| Аббревиатура: 2–6 заглавных букв в репликах; то же слово строчными («каско») сводится к ней | ОГПО, КАСКО, ДМС, ИИН, ЖСН, ДТП, SMS |
| Доменное слово: от 4 букв, его говорят и клиент (в examples не меньше 2 раз), и оператор (responses, prompt слотов); служебные слова отсекает короткий стоп-лист по категориям (местоимения, союзы, послелоги, вежливость, вспомогательные глаголы) | страховка, сақтандыру, полис, көлік, өтініш |
| Язык термина — по языку текстов, где он встречается | списки ru, kk и общий shared |
| Значение enum-слота после транслитерации (ogpo → огпо) связывает термин со слотом | ОГПО ↔ product_type=ogpo, Алматы ↔ city=Almaty |
Формат номера — буквенный префикс из pattern слота | SQ-OGPO, SQ-CASCO, CL |
Порядок — по важности: число сценариев, в репликах клиента которых встречается термин, затем частота, затем алфавит. Для каждого термина берётся самая короткая реплика клиента с ним на ru и на kk.
Как пересобрать. Словарь хранится в packages/core/src/stt/stt-glossary.json и собирается детерминированно:
pnpm glossary:build # набор из data/kit
pnpm glossary:build --kit <каталог> # или KIT_DIR=<каталог>
pnpm glossary:build --check # артефакт соответствует данным набора
Как передаётся в распознавание. buildTranscriptionHints(glossary, { model }) возвращает поля model,
prompt, keywords, languages — одинаковые для POST /v1/audio/transcriptions и для транскрипции в
Realtime-сессии (session.audio.input.transcription).
prompt— короткие реплики клиента из набора на ru и kk с главными терминами, затем список остальных: по естественной фразе модель лучше держит стиль, чем по голому списку.gpt-transcribe,gpt-live-transcribe: термины дополнительно вkeywords(до 40), языки —languages: ["ru", "kk"].gpt-4o-transcribe,gpt-4o-mini-transcribe,whisper-1принимают только одинlanguage. Его не задаём: фиксированный язык ломает речь с переходом с языка на язык внутри фразы. Оба языка задаёт подсказка.- Бюджет подсказки: 200 символов для
whisper-1(предел 224 токена), 800 — для остальных. Точный предел для моделей gpt-* в документации OpenAI не указан, 800 символов и 40 ключевых слов — наше допущение.
Оценка. Скрипт выбирает из dev_utterances.json реплики с продуктовыми терминами (15 из 104: ru 8, kk 5,
mixed 2), синтезирует их речь (gpt-4o-mini-tts) и распознаёт дважды — без подсказки и с подсказкой. Считает
долю реплик, где все термины распознаны без искажения, полноту терминов и WER по ru, kk, mixed.
pnpm stt:eval --dry-run # выборка и подсказка, без обращений к API
pnpm stt:eval # нужен OPENAI_API_KEY; отчёт в test-results/stt-eval/report.md (вне git)
Результаты замера (распознавание gpt-transcribe, синтез gpt-4o-mini-tts, 15 реплик):
| Язык | n | Термины без искажения: без / с подсказкой | WER: без / с подсказкой |
|---|---|---|---|
| ru | 8 | 62.5 % / 87.5 % | 0.060 / 0.010 |
| kk | 5 | 40.0 % / 100.0 % | 0.460 / 0.050 |
| mixed | 2 | 50.0 % / 100.0 % | 0.205 / 0.000 |
| все | 15 | 53.3 % / 93.3 % | 0.212 / 0.022 |
Ограничение: синтезированная речь чище живой (без шума, акцента и оговорок), поэтому замер показывает вклад подсказки при прочих равных. Итоговая проверка — живым голосом.
11. Голосовой канал и режим звонка
ТЗ: «жюри говорит в микрофон — робот распознаёт и отвечает голосом»; две проверочные реплики на казахском и одна со смешением языков. Распознавание браузера слушает один заранее выбранный язык и режет смешанную фразу, а голоса для казахского в Windows часто нет, поэтому с ключом голос идёт через сервер (ADR-016).
- Режим звонка (
apps/web/components/use-voice-call.ts,CallScreen.tsx): микрофон открыт весь звонок; порог речи считается от шума, измеренного в первые 0,6 с; конец фразы — 0,8 с тишины; фраза до 12 с. Во время ответа запись продолжается: если клиент говорит поверх робота 0,3 с, голос останавливается сразу, а начало его фразы уже записано. Экран показывает фазу (слушаю, вы говорите, распознаю, выбираю сценарий, робот отвечает), таймер, субтитры и выбранный сценарий. POST /api/stt— запись реплики вgpt-transcribeсlanguages: ["ru","kk"]и подсказкой из словаря терминов (раздел 10).GET /api/sttсообщает, доступно ли распознавание.GET /api/tts— голос ответа (gpt-4o-mini-tts) потоком: воспроизведение начинается с первых байт.sttMsв трассировке считается от конца речи клиента до готового текста.
Проверка по кругу (синтез -> распознавание через /api/tts и /api/stt, 23.09):
| Реплика | Распознано | Время распознавания |
|---|---|---|
| kk: «Сәлеметсіз бе, машинамды парковкада сызып кетті, КАСКО бойынша өтініш бергім келеді» | дословно | 1,5 с |
| ru: «…я вчера оплатил, деньги списались, а полис не оформлен, и ещё адрес поменять надо» | дословно | 0,85 с |
| смешанная: «Сәлеметсіз бе, мен вчера оплатил, а полис әлі жоқ, что делать?» | «Сәлеметсіз бе, мен кеше оплатил, а полис әлі жоқ. Что делать?» | 0,9 с |
Первый звук ответа от /api/tts — 0,7–1,0 с.
12. Статистика для супервизора
ТЗ: супервизор «видит, какие сценарии выбирались, где робот сомневался и ошибся». GET /api/stats — сводка
журнала ходов за 24 часа; агрегаты считает PostgreSQL по индексу created_at, приложение не выгружает строки.
В панели: число ходов и диалогов, доли запуска, уточнения и передачи оператору, сбои источника решения,
задержка маршрутизатора p50/p95, частые сценарии со средней уверенностью и последние сомнительные ходы
(уточнение, оператор, уверенность ниже 0.75, ошибка источника). Блок обновляется после каждого хода и по кнопке,
без периодического опроса.
12.1. Окно «Сравнить ChatGPT и Gemini»
Кнопка в заголовке панели супервизора открывает модальное окно: реплика выбранного хода уходит одновременно в два
движка выбора сценария — OpenAI (gpt-5.4-mini) и Google Gemini (gemini-3.8-flash) — через один и тот же
OpenAI-совместимый вход, с одним промптом и одной проверкой ответа (контракт RouteDecision). В окне две колонки:
сценарий, обоснование, альтернативы, задержка выбора и число попыток каждого движка; сверху — совпал ли основной
сценарий. Так супервизор видит, где решение устойчиво к смене модели, а где движки расходятся.
Сравнение — инструмент супервизора, не путь диалога: состояние разговора не меняется, ход в журнал не пишется,
сервис router не задействован. Движок без ключа показывается как недоступный (окно называет переменную), а не
получает демо-решение. Код: apps/web/app/api/compare/route.ts (GET — какие движки настроены, POST — сравнение),
apps/web/lib/server/compare.ts (чистая функция, тесты compare.test.ts без сети), apps/web/components/CompareModal.tsx,
модальные окна — apps/web/components/Modal.tsx на нативном <dialog>.
12.2. Страница /live — звонок через Gemini Live API
Точное имя технологии — Gemini Live API (двунаправленный поток к модели). Google ADK (Agent Development Kit) — следующий слой поверх него: агент с инструментами и памятью; для Halyk Bank это путь к собственному голосовому ассистенту на той же основе.
Вторая реализация звонка, рядом с обычной. Обычный звонок на главной странице — конвейер: запись реплики → распознавание → выбор сценария → синтез голоса; каждый этап ждёт предыдущий, отсюда 3–4 секунды до ответа. Gemini Live API (Google, ai.google.dev/gemini-api/docs/live) — другой механизм: браузер держит один открытый WebSocket к модели, звук идёт в обе стороны одновременно, модель сама определяет конец фразы (VAD), её можно перебить, распознавание и голос — внутри модели. Это то, что в ТЗ названо «темпом живого разговора».
Как это встроено, не подменяя суть кейса: выбор сценария остаётся за нашим LLM-слоем. Модель Gemini на каждую
реплику обязана вызвать инструмент route_utterance — это POST /api/turn с тем же маршрутизатором, политикой и
фразами набора — и произнести ровно возвращённый текст. Панель супервизора на /live показывает ту же
трассировку (сценарий, обоснование, альтернативы, задержка). Ключ API в браузер не попадает: POST /api/live/token
выдаёт одноразовый эфемерный токен с зашитой конфигурацией звонка (apps/web/lib/live/config.ts).
Что нужно: GEMINI_API_KEY в .env — ключ выдаётся в Google AI Studio
(бесплатный тариф есть); если на ключе стоит ограничение по IP, адрес машины должен быть в списке — иначе Google
отвечает 403, и страница показывает эту ошибку прямо на экране. Модель — gemini-3.8-live
(переопределяется GEMINI_LIVE_MODEL). Без ключа кнопка звонка на /live неактивна, обычный звонок работает.
Статус: страница добавлена в последний час хакатона; сборка и типы проверены, живое подключение зависит от ключа
(раздел 13). Код: apps/web/app/live/page.tsx, apps/web/app/api/live/token/route.ts, apps/web/lib/live/config.ts.
12.3. Потенциал развития: дообучение выбора сценария на своих данных
Журнал ходов уже хранит всё, из чего собирается обучающая выборка: реплика и контекст диалога, решение LLM-слоя (сценарии, уверенность, обоснование, слоты), действие политики — и, после разметки супервизором, верный сценарий. Конвейер, который на это ложится без смены архитектуры:
- Выгрузка из PostgreSQL пар «реплика + контекст → сценарий» с поправками супервизора; объединение с
dev_utterances.jsonиdialogs_sample.jsonнабора. - Дообучение открытой модели (LoRA на одной GPU банка: семейства Qwen или Gemma) под тот же контракт
RouteDecision: промпт и проверка ответа не меняются, потому что маршрутизатор ядра принимает любой OpenAI-совместимый вход (ROUTER_BASE_URL, раздел 7) — дообученная модель подключается одной переменной. - Замер
evaluate.pyна dev-наборе и окно сравнения (раздел 12.1): своя модель противgpt-5.4-miniна одних и тех же репликах, с задержкой и вердиктом совпадения. - Гибридный путь из ТЗ: маленькая локальная модель — для очевидных запросов (
fast_path_eligibleв каталоге), большая — для стыков сценариев и смены темы; выигрыш по задержке замеряется той же панелью.
Что это даёт банку: реплики клиентов не покидают контур, задержка выбора сценария на своей GPU укладывается в ориентир 500 мс, точность растёт именно на живых формулировках своих клиентов, а не на примерах из каталога. Честная граница: в репозитории сейчас — данные, точки подключения и измерители; скриптов обучения нет.
13. Границы применимости и безопасность
- Задержка. Ориентир ТЗ — 1,5 с от конца реплики до начала ответа. Сейчас в режиме звонка 3–4 с: распознавание ~1 с, выбор сценария ~1,6 с (медиана), первый звук синтеза ~0,7 с, плюс 0,8 с тишины для определения конца фразы. Ускорение: потоковое распознавание, быстрый путь для очевидных запросов (#11), параллельный запуск синтеза. Задержка по ТЗ даёт дополнительные баллы и не снижает основную оценку.
- Действия. Робот не выполняет необратимых действий: он открывает сценарий фразой из набора и просит данные. Исполнитель сценария с подтверждением — следующий шаг (#6). Продолжение темы без исполнителя: названные данные подтверждаются («принял»), не названные — робот повторяет вопрос сценария.
- Окно сравнения (раздел 12.1). Оба движка проверены живьём на казахской реплике «менің сақтандыру
жағдайым қандай күйде?»: OpenAI
gpt-5.4-mini— SC17 0.93 за 1,6 с, Geminigemini-3.8-flash— SC17 0.95 за 4,8 с, основной сценарий совпал. Пока IP машины не был добавлен в ограничения ключа, колонка Gemini честно показывала403— окно не скрывает ошибки поставщика. - Страница
/live(раздел 12.2). Эфемерный токен дляgemini-3.8-liveвыдаётся, страница собрана и проверена типами в последний час хакатона; качество и задержка живого звонка на площадке не замерены. - Неуверенность видна. У каждого решения — уверенность и обоснование с цитатой клиента; при уверенности 0.45–0.75 робот уточняет, после двух неуверенных ходов подряд или по просьбе клиента передаёт разговор оператору. Модель не единственный источник истины: политика решений детерминирована (ADR-004).
- Демо-режим без ключа подбирает сценарий по совпадению слов с примерами каталога. Он нужен, чтобы система запускалась у проверяющего без личных ключей, и не является решением задачи маршрутизации.
- Точность. 1.000 по первому сценарию на 104 размеченных репликах не гарантирует того же на скрытых репликах жюри: dev-набор мал (7 смешанных, 13 мультиинтентов).
- Голос. Нужен браузер с микрофоном; вне
localhost— только по HTTPS. В шумном зале порог речи подстраивается под фон, но посторонняя речь рядом может начать ход. - Данные. Компания и клиенты вымышленные; во внешние API (LLM, распознавание, синтез) уходят только реплики разговора на синтетических данных.
Требования ТЗ и их реализация
| Требование ТЗ (Must-have) | Реализация | Issue, PR |
|---|---|---|
| Голосовое взаимодействие в вебе | режим звонка: серверное распознавание ru/kk, голос ответа, перебивание (раздел 11) | #4, #8, #33 |
| Выбор сценария на LLM-слое, без классификатора намерений | сервис router (FastAPI), резерв — маршрутизатор ядра (раздел 9) | #5, #23, #30 |
| Корректность выбора на 10 скрытых репликах жюри | замер на dev-наборе: primary 1.000, full_match 0.990 | #9 |
| Панель трассировки после каждой реплики | сценарий, обоснование с цитатой, альтернативы, задержка по этапам | #4, #18 |
| Русский и казахский, смешение языков | распознавание с ожидаемыми ru и kk, выбор сценария по смыслу | #5, #8, #20 |
| Переспросить вместо угадывания, передать оператору | политика решений (раздел 2.3) | #6 |
| Супервизор видит, где робот сомневался | статистика журнала (раздел 12) | #35 |
| Explainability: логика, а не голый вердикт | окно сравнения двух движков на одной реплике — где решение устойчиво к смене модели (раздел 12.1) | #40 |
| Опционально: потоковая обработка, темп живого разговора | страница /live — Gemini Live API, один открытый поток и перебивание, сценарий выбирает наш LLM-слой через инструмент (раздел 12.2) | #46 |
| Ответы только по данным набора | фразы сценариев и системных намерений из scenarios.json | #19 |
| Запуск одной командой | Docker Compose: db, router, web | #10 |
| Опционально: быстрый путь для простых сценариев | не реализовано, план в разделе 13 | #11 |
Критерии оценки (из ТЗ)
| Критерий | Баллы |
|---|---|
| Соответствие задаче и работоспособность | 25 |
| Техническая реализация | 25 |
| README и воспроизводимость | 25 |
| Ценность и применимость решения | 15 |
| Потенциал развития и оригинальность подхода | 10 |
Тестирование
Разработка ведётся по TDD: каждый модуль начинается с падающего теста.
| Уровень | Что проверяется | Инструмент |
|---|---|---|
| Модульный | политика решений, промпт и маршрутизатор ядра, ответ робота, словарь терминов, распознавание, сводка журнала, миграции | Vitest |
| Модульный (сервис) | инварианты маршрутизатора, промпт, HTTP-коды /route на поддельном клиенте модели | pytest |
| Контрактный | ответ LLM соответствует схеме, некорректный отклоняется; ответ /route проходит схему ядра на общей фикстуре | Vitest + Zod, pytest |
| Эталонный | выбор сценария на 104 размеченных репликах организаторов | scripts/route_batch.py + evaluate.py |
| Ручной сквозной | звонок в браузере: речь — ответ голосом — панель супервизора | Chrome, раздел 8 |
Структура репозитория
| Путь | Содержимое | Ответственный |
|---|---|---|
apps/web/ | Next.js: интерфейс разговора, режим звонка, панель трассировки и статистика, маршруты API | Батырхан (интерфейс), Сырым (API, голос) |
packages/core/ | модули triage, router, policy, executor, trace | Сырым |
backend/ | сервис выбора сценария (FastAPI): POST /route, загрузка и проверка набора | Батырхан |
scripts/route_batch.py | прогон dev-набора через сервис и оценка evaluate.py | Батырхан |
packages/core/src/stt/ | словарь терминов и подсказка для распознавания речи | Батырхан |
scripts/glossary-build.ts, scripts/stt-eval.ts | сборка словаря терминов, оценка распознавания | Батырхан |
packages/actions/ | заготовка: мок-действия поверх данных набора (#7) | Жандаулет |
db/migrations/ | нумерованные SQL-миграции | Жандаулет |
data/kit/ | стартовый набор организаторов | Жандаулет |
tests/e2e/ | заготовка сквозных тестов | Батырхан |
analytics/ | заметки команды о ходе работы и проверке Docker | Жандаулет |
docs/ | описание данных, контракты API, журнал итераций роутера (ROUTER_LOG.md) | команда |
.agents/skills/ | навыки для AI-агентов по этому репозиторию | команда |
ARCHITECTURE.md | архитектурные решения и их история (ADR) | команда |
Команда
Все участники — International Information Technology University (IITU), Алматы.
| Участник | GitHub | Telegram |
|---|---|---|
| Сырым Жакыпбеков (лидер) | @Syrym-Zhakypbekov | @syrym95kz |
| Нұрхан Батырхан | @batyrnurkhan | @batyrnurhan |
| Мусилимов Жандаулет | @zhmus00 | @zh_11_an |
Инструменты и источники
Локальная разработка интерфейса с PostgreSQL
Из корня репозитория: pnpm dev:web. Команда читает корневой .env, поднимает БД и маршрутизатор,
применяет миграции и запускает Next.js с горячим обновлением на http://localhost:3001.
Используется тот же журнал PostgreSQL, что у Docker-версии на 3000; дополнительных копий данных нет.
Опциональный docker-compose.dev.yml запускает отдельный TCP-прокси только на 127.0.0.1:15432.
Он подключается к существующей базе по Docker DNS, без копирования данных. Обычная пересборка основного
Compose-проекта не удаляет прокси; при пересоздании БД новые соединения находят её по имени db.
Проверка: /api/health возвращает ok: true, db: true, /api/stats возвращает статистику журнала.
Для другого окружения доступны DEV_DATABASE_URL, DEV_ROUTER_URL и DEV_WEB_PORT.
Не запускайте dev-сервер с пустым DATABASE_URL: диалог сможет работать, но журнал и статистика будут недоступны.
Остановить dev-сервер: Ctrl+C. Остановить только локальный прокси: docker stop voice-router-dev-access-db-access-1.
Раскрытие инструментов
Раскрытие по Положению §5.4.4 и §5.4.12.
- Данные: стартовый набор организаторов HackAlem AI по кейсу Halyk Bank (синтетические данные,
вымышленная компания Saqta Insurance). Скрипт
evaluate.py— из того же набора. - AI-инструменты разработки: Claude Code (Anthropic), OpenAI Codex.
- Иконки интерфейса: Lucide React (ISC), локальная зависимость; официальные логотипы организаторов в интерфейсе не используются.
- Модели в работе решения: OpenAI API; выбор сценария в сервисе —
gpt-5.4-mini(сравнение моделей —docs/ROUTER_LOG.md); распознавание речиgpt-transcribe, синтез речиgpt-4o-mini-tts. - Библиотеки сервиса выбора сценария: FastAPI, Uvicorn, Pydantic, pydantic-settings, OpenAI Python SDK; тесты — pytest, HTTPX.
- Ранее созданный код в решении не используется: код пишется в этом репозитории в ходе хакатона.
Авторы коммитов
История коммитов
- 2026-09-23 17:46Merge pull request #45 from BAITC-Hacks/fix/4-ui-results-devSyrym Zhakypbekov
- 2026-09-23 17:44merge: main (страница /live, allowBuilds, README 12.2–12.3) в полировку интерфейса; lock-файл пересобранSyrym Zhakypbekov
- 2026-09-23 17:40Добавить ревью обработки смешанной RU/KK речиzhmus00
- 2026-09-23 17:37Merge pull request #48 from BAITC-Hacks/fix/18-pnpm-allow-buildsSyrym Zhakypbekov
- 2026-09-23 17:37fix(build): pnpm 12 — явный allowBuilds для @google/genai и protobufjs, иначе docker compose up --build падал на install --frozen-lockfile (ERR_PNPM_IGNORED_BUILDS)Syrym Zhakypbekov
- 2026-09-23 17:35Merge pull request #47 from BAITC-Hacks/docs/17-live-prompt-trainingSyrym Zhakypbekov
- 2026-09-23 17:35feat(live): системный промпт — казахская, русская и смешанная речь дословно в маршрутизатор; распознавание ограничено ru-RU и kk-KZSyrym Zhakypbekov
- 2026-09-23 17:33Merge pull request #46 from BAITC-Hacks/feat/16-live-pageSyrym Zhakypbekov
Все коммиты (146)
- 2026-09-23 17:32feat(voice): не законченныйBatyrkhan
- 2026-09-23 17:31docs: границы применимости — Gemini в окне сравнения проверен живьём (SC17 0.95, 4,8 с), статус страницы /liveSyrym Zhakypbekov
- 2026-09-23 17:30feat(web): страница /live — звонок через Gemini Live API, сценарий выбирает наш LLM-слой через инструментSyrym Zhakypbekov
- 2026-09-23 17:26fix(web): fit the welcome state within a mobile conversation panelSyrym Zhakypbekov
- 2026-09-23 17:26new logicBatyrkhan
- 2026-09-23 17:25merge: incorporate current main and voice fixes into UI polishSyrym Zhakypbekov
- 2026-09-23 17:25fix(dev): connect hot reload to shared PostgreSQL with one-command startupSyrym Zhakypbekov
- 2026-09-23 17:25feat(web): add Lucide icons and clear decision reports in animated dialogsSyrym Zhakypbekov
- 2026-09-23 17:24Merge pull request #44 from BAITC-Hacks/docs/15-mermaid-semicolonSyrym Zhakypbekov
- 2026-09-23 17:23docs: README — диаграмма последовательности не рендерилась на GitHub (точка с запятой в подписи стрелки)Syrym Zhakypbekov
- 2026-09-23 17:18Merge pull request #43 from BAITC-Hacks/fix/14-tts-tempoSyrym Zhakypbekov
- 2026-09-23 17:18fix(web): голос робота — marin и живой темп речи вместо «без спешки»Syrym Zhakypbekov
- 2026-09-23 17:10Merge pull request #42 from BAITC-Hacks/feat/12-engine-compareSyrym Zhakypbekov
- 2026-09-23 17:10Merge pull request #41 from BAITC-Hacks/fix/13-greeting-open-questionSyrym Zhakypbekov
- 2026-09-23 17:07fix(core): на реплику, которую модель сама назвала непонятной, робот задаёт открытый вопрос, а не предлагает хвостовую альтернативуSyrym Zhakypbekov
- 2026-09-23 17:04fix(web): keep conversation controls visible and compact supervisor layoutSyrym Zhakypbekov
- 2026-09-23 17:03Merge pull request #40 from BAITC-Hacks/feat/12-engine-compareSyrym Zhakypbekov
- 2026-09-23 17:01docs: границы применимости — статус живой проверки Gemini (403 по IP ключа), поведение продолжения темы; счётчик тестов политики 14Syrym Zhakypbekov
- 2026-09-23 17:01fix(core): продолжение темы различает названные данные и пустую реплику — «принял» вместо повтора вопросаSyrym Zhakypbekov
- 2026-09-23 16:58Review LLM provider settings and identify riskszhmus00
- 2026-09-23 16:55fix(core): непонятная реплика в середине темы больше не даёт глухое «продолжаем», а клиент не слышит SYS_UNCLEARSyrym Zhakypbekov
- 2026-09-23 16:47feat(web): пульт «Настройки» в модальном окне и чистый главный экран — звонок, настройки и сброс одной строкой в шапкеSyrym Zhakypbekov
- 2026-09-23 16:43docs: раздел 12.1 «Сравнить ChatGPT и Gemini», строка ENV, ADR-018 — сравнение вне пути диалогаSyrym Zhakypbekov
- 2026-09-23 16:43chore: GEMINI_API_KEY и GEMINI_MODEL в .env.example и Compose — второй движок окна сравнения включается ключомSyrym Zhakypbekov
- 2026-09-23 16:43feat(web): окно «Сравнить ChatGPT и Gemini» в панели супервизора — две колонки, вердикт совпадения, задержка каждого движкаSyrym Zhakypbekov
- 2026-09-23 16:41feat(web): /api/compare — одна реплика уходит в OpenAI и Gemini одновременно, ответ с решениями обоих и флагом совпаденияSyrym Zhakypbekov
- 2026-09-23 16:27Merge pull request #39 from BAITC-Hacks/feat/11-router-providerSyrym Zhakypbekov
- 2026-09-23 16:25docs: переменные поставщика модели маршрутизатора в README, .env.example и ComposeSyrym Zhakypbekov
- 2026-09-23 16:25feat(web): поставщик модели маршрутизатора ядра задаётся окружениемSyrym Zhakypbekov
- 2026-09-23 16:24test(web): поставщик модели маршрутизатора задаётся окружением (падающий тест)Syrym Zhakypbekov
- 2026-09-23 16:20Merge pull request #38 from BAITC-Hacks/fix/33-empty-env-defaultsSyrym Zhakypbekov
- 2026-09-23 16:20fix(web): пустая переменная окружения ведёт к значению по умолчаниюSyrym Zhakypbekov
- 2026-09-23 16:18Document initial language and scenario checkszhmus00
- 2026-09-23 16:14Merge pull request #37 from BAITC-Hacks/docs/9-readme-truthSyrym Zhakypbekov
- 2026-09-23 16:14docs: README по фактическому состоянию — режим звонка, статистика, замеры, границы применимостиSyrym Zhakypbekov
- 2026-09-23 16:09Merge pull request #36 from BAITC-Hacks/feat/35-supervisor-statsSyrym Zhakypbekov
- 2026-09-23 16:08feat(web): блок статистики в панели супервизораSyrym Zhakypbekov
- 2026-09-23 16:08feat(web): сводка журнала для супервизора — GET /api/statsSyrym Zhakypbekov
- 2026-09-23 16:05test(web): сводка журнала для супервизора — числа из базы, доли действий, сомнительные ходы (падающий тест)Syrym Zhakypbekov
- 2026-09-23 16:02Merge pull request #33 from BAITC-Hacks/feat/8-server-sttSyrym Zhakypbekov
- 2026-09-23 16:02Merge pull request #34 from BAITC-Hacks/fix/19-greeting-not-goodbyeSyrym Zhakypbekov
- 2026-09-23 16:00fix(router): приветствие без просьбы ведёт к уточнению, прощание — только в конце разговораSyrym Zhakypbekov
- 2026-09-23 16:00test(router): одно приветствие — SYS_UNCLEAR, а не прощание (падающий тест)Syrym Zhakypbekov
- 2026-09-23 15:55merge: main в feat/8-server-stt — сервис выбора сценария (#30), исправления ответа (#31, #32)Syrym Zhakypbekov
- 2026-09-23 15:54Merge pull request #30 from BAITC-Hacks/feat/23-router-serviceSyrym Zhakypbekov
- 2026-09-23 15:54merge: main в feat/23-router-service — словарь STT (#25), обоснование на русском (#31, #32)Syrym Zhakypbekov
- 2026-09-23 15:53Merge pull request #32 from BAITC-Hacks/fix/19-unclear-open-questionSyrym Zhakypbekov
- 2026-09-23 15:53Merge pull request #31 from BAITC-Hacks/fix/18-reason-russianSyrym Zhakypbekov
- 2026-09-23 15:52Add checking_docker.md for replication checkzhmus00
- 2026-09-23 15:52fix(core): на непонятную реплику без вариантов робот задаёт открытый вопросSyrym Zhakypbekov
- 2026-09-23 15:52fix(router): обоснование выбора сценария на русском с цитатой клиентаBatyrkhan
- 2026-09-23 15:51test(core): непонятная реплика без вариантов не озвучивает поля шаблона (падающий тест)Syrym Zhakypbekov
- 2026-09-23 15:51fix(web): пауза опроса сервиса выбора сценария только когда он не отвечаетBatyrkhan
- 2026-09-23 15:50feat(web): режим звонка — разговор без кнопок с перебиванием роботаSyrym Zhakypbekov
- 2026-09-23 15:50feat(web): экран звонка в стиле интерфейса — шар состояния, таймер, субтитры, завершениеSyrym Zhakypbekov
- 2026-09-23 15:50feat(web): маршруты распознавания и синтеза речи на сервереSyrym Zhakypbekov
- 2026-09-23 15:44feat(web): серверное распознавание речи через OpenAI с подсказкой словаря терминовSyrym Zhakypbekov
- 2026-09-23 15:44test(web): серверное распознавание речи с подсказкой словаря и двумя языками (падающий тест)Syrym Zhakypbekov
- 2026-09-23 15:39Document review of changes and local verification stepszhmus00
- 2026-09-23 15:39docs(readme): фактический итог pnpm verify и замер словаря терминов для STTBatyrkhan
- 2026-09-23 15:38Merge pull request #25 from BAITC-Hacks/feat/8-stt-glossarySyrym Zhakypbekov
- 2026-09-23 15:29Merge remote-tracking branch 'origin/main' into feat/8-stt-glossaryBatyrkhan
- 2026-09-23 15:29fix(core): обоснование выбора сценария на русском со ссылкой на правило каталогаSyrym Zhakypbekov
- 2026-09-23 15:29test(core): обоснование выбора сценария на русском со ссылкой на правило каталога (падающий тест)Syrym Zhakypbekov
- 2026-09-23 15:24docs(architecture): ADR-015 — резервный путь при сбое сервиса выбора сценарияBatyrkhan
- 2026-09-23 15:23merge: main в feat/23-router-service — интерфейс, журнал ходов, ADR и навыкиBatyrkhan
- 2026-09-23 15:18infra(router): сервис router в docker-compose, окружение и раздел READMEBatyrkhan
- 2026-09-23 15:17feat(router): POST /route по контракту RouteDecision ядра, резервный путь в webBatyrkhan
- 2026-09-23 15:17feat(router): сервис выбора сценария из feat/router в общий стек, инварианты под тестамиBatyrkhan
- 2026-09-23 15:14Слияние #29: история архитектурных решений и навыки для AI-агентовSyrym Zhakypbekov
- 2026-09-23 15:13docs: README и AGENTS.md — статус системы, навигация, раздел для агентовSyrym Zhakypbekov
- 2026-09-23 15:13docs: навыки для AI-агентов по репозиторию — запуск, TDD, миграции, маршрутизаторSyrym Zhakypbekov
- 2026-09-23 15:13docs: ARCHITECTURE.md — карта модулей и журнал архитектурных решений ADR-001..014Syrym Zhakypbekov
- 2026-09-23 15:11Журнал прогрессаzhmus00
- 2026-09-23 15:10Слияние #28: точнее демо-режим, сквозной номер хода, названия без кавычекSyrym Zhakypbekov
- 2026-09-23 15:10fix(web): точнее демо-режим, сквозной номер хода, названия сценариев без кавычекSyrym Zhakypbekov
- 2026-09-23 15:10test(web): демо-режим на полном каталоге набора (падающий тест)Syrym Zhakypbekov
- 2026-09-23 15:06Слияние #27: интерфейс разговора и панель трассировки супервизораSyrym Zhakypbekov
- 2026-09-23 15:03merge: main в feat/4-web-ui — API хода и журналSyrym Zhakypbekov
- 2026-09-23 15:01Слияние #26: журнал ходов в PostgreSQL и лента для супервизораSyrym Zhakypbekov
- 2026-09-23 15:00feat(web): журнал ходов в PostgreSQL и лента /api/turns для супервизораSyrym Zhakypbekov
- 2026-09-23 15:00fix(web): подпись действия в истории ходов обычным шрифтомSyrym Zhakypbekov
- 2026-09-23 14:58feat(web): голосовой канал в браузере — микрофон, sttMs и озвучивание ответаSyrym Zhakypbekov
- 2026-09-23 14:56test(web): журнал ходов диалога — миграция и строка журнала (падающий тест)Syrym Zhakypbekov
- 2026-09-23 14:56feat(web): панель трассировки супервизора и история ходовSyrym Zhakypbekov
- 2026-09-23 14:56feat(stt): сборщик словаря терминов из данных набора и подсказка для OpenAI STTBatyrkhan
- 2026-09-23 14:56feat(stt): скрипт оценки распознавания с подсказкой и без, раздел READMEBatyrkhan
- 2026-09-23 14:54feat(web): лента разговора и вызов POST /api/turnSyrym Zhakypbekov
- 2026-09-23 14:53feat(web): каркас интерфейса, фирменные стили и локализация ru/kk/enSyrym Zhakypbekov
- 2026-09-23 14:52Слияние #24: API хода диалога — выбор сценария, политика, ответ, трассировкаSyrym Zhakypbekov
- 2026-09-23 14:52feat(web): API хода диалога /api/turn — выбор сценария, политика, ответ, трассировкаSyrym Zhakypbekov
- 2026-09-23 14:52merge: main в feat/5-turn-api — аналитика проектаSyrym Zhakypbekov
- 2026-09-23 14:51Update analytics/index.md to clarify TDD and remove detailszhmus00
- 2026-09-23 14:49Revise analytics for Voice Router projectzhmus00
- 2026-09-23 14:48feat(reply): ответ робота из фраз набора и режим проверки без ключа моделиSyrym Zhakypbekov
- 2026-09-23 14:47test(reply): ответ робота по данным набора и режим проверки без ключа (падающий тест)Syrym Zhakypbekov
- 2026-09-23 14:45feat(core): контракт хода диалога между интерфейсом и серверомSyrym Zhakypbekov
- 2026-09-23 14:42Слияние #22: Next.js 16.3.5, установка проходит политику pnpmSyrym Zhakypbekov
- 2026-09-23 14:42fix(web): Next.js 16.3.5 — установка проходит политику минимального возраста релиза pnpmSyrym Zhakypbekov
- 2026-09-23 14:37Слияние #21: ядро LLM-маршрутизатора выбора сценарияSyrym Zhakypbekov
- 2026-09-23 14:37merge: main в feat/5-llm-router — политика решений и каркас приложенияSyrym Zhakypbekov
- 2026-09-23 14:36Слияние #15: политика решений по порогам уверенностиSyrym Zhakypbekov
- 2026-09-23 14:36Слияние #16: каркас приложения, PostgreSQL и запуск одной командойSyrym Zhakypbekov
- 2026-09-23 14:36Слияние #14: рабочее пространство и контракт ответа LLM-маршрутизатораSyrym Zhakypbekov
- 2026-09-23 14:36Слияние #13: процесс веток и pull request, шаблон PRSyrym Zhakypbekov
- 2026-09-23 14:32feat(router): LLM-маршрутизатор — каталог, промпт с правилами not_this_if, проверка ответаSyrym Zhakypbekov
- 2026-09-23 14:31test(router): каталог, промпт и выбор сценария через модель (падающий тест)Syrym Zhakypbekov
- 2026-09-23 14:21feat(router): checking dialogsBatyrkhan
- 2026-09-23 14:21docs(readme): запуск одной командой и проверка готовностиSyrym Zhakypbekov
- 2026-09-23 14:20infra: запуск всей системы одной командой docker compose up --buildSyrym Zhakypbekov
- 2026-09-23 14:20feat(web): каркас Next.js 16 и проверка готовности /api/healthSyrym Zhakypbekov
- 2026-09-23 14:16feat(db): запускатель миграций PostgreSQLSyrym Zhakypbekov
- 2026-09-23 14:15test(db): порядок и проверка имён миграций (падающий тест)Syrym Zhakypbekov
- 2026-09-23 14:11feat(policy): политика решений по порогам уверенностиSyrym Zhakypbekov
- 2026-09-23 14:10test(policy): пороги уверенности и порядок сценариев (падающий тест)Syrym Zhakypbekov
- 2026-09-23 14:03docs(readme): рабочая команда проверки pnpm verify и состояние CISyrym Zhakypbekov
- 2026-09-23 14:02ci: ручной запуск из-за блокировки Actions в организации, локальная проверка pnpm verifySyrym Zhakypbekov
- 2026-09-23 14:01ci: проверка типов и тестов на каждый pull requestSyrym Zhakypbekov
- 2026-09-23 13:58feat(core): контракт RouteDecision на Zod и безопасный разбор ответа моделиSyrym Zhakypbekov
- 2026-09-23 13:57test(core): контракт ответа LLM-маршрутизатора (падающий тест)Syrym Zhakypbekov
- 2026-09-23 13:57chore: рабочее пространство pnpm, TypeScript 7, Vitest 5Syrym Zhakypbekov
- 2026-09-23 13:54docs: порядок слияния при зависимостях и неизменность миграцийSyrym Zhakypbekov
- 2026-09-23 13:54Add initial analytics documentationzhmus00
- 2026-09-23 13:53docs: процесс веток и pull request, шаблон PRSyrym Zhakypbekov
- 2026-09-23 13:51docs: архитектура Voice Router, план работ и каркас репозиторияSyrym Zhakypbekov
- 2026-09-23 13:50feat(router): gpt-5.4-mini по умолчанию, согласованность вывода — dev primary 1.000, full_match 0.981–1.000Batyrkhan
- 2026-09-23 13:40feat(router): разбор до выбора (situation + quote) — dev primary 1.000, full_match 0.990Batyrkhan
- 2026-09-23 13:38docs: README — вернуть разделы скелета, потерянные при разрешении конфликта слиянияBatyrkhan
- 2026-09-23 13:38merge: main в feat/router — трек «Коммуникации» и описание кейса из READMEBatyrkhan
- 2026-09-23 13:36feat(router): LLM-роутер сценариев (structured output) и батч-оценка на dev-набореBatyrkhan
- 2026-09-23 13:25chore: каркас Voice Router — backend (FastAPI), загрузка датасета, README-скелетBatyrkhan
- 2026-09-23 13:12Revise project track and case descriptionzhmus00
- 2026-09-23 10:26Update project track from 'Образование' to 'Мы выбираем'zhmus00
- 2026-09-09 23:47docs: AGENTS.md — что своё можно приносить (наработки/архитектура: можно с раскрытием п.6.4, каркас заранее, решение с 23.09)Syrym Zhakypbekov
- 2026-09-09 23:39docs: AGENTS.md — правила хакатона и красные линии (Положение), контекст для работы над проектомSyrym Zhakypbekov
- 2026-09-09 23:30docs: README — только имена и Telegram, без непроверенных ролей/статусовSyrym Zhakypbekov
- 2026-09-09 23:28docs: команда IITU+SPARTANS — состав, роли, трек ОбразованиеSyrym Zhakypbekov
- 2026-09-09 22:44docs: обновил README — команда IITU+SPARTANS, тест связи с репозиториемSyrym Zhakypbekov
Структура репозитория (169 файлов)
.agents/skills/add-migration/SKILL.md .agents/skills/change-router/SKILL.md .agents/skills/run-and-verify/SKILL.md .agents/skills/tdd-module/SKILL.md .dockerignore .env.example .gitattributes .github/pull_request_template.md .github/workflows/ci.yml .gitignore AGENTS.md ARCHITECTURE.md Dockerfile README.md analytics/checking_docker.md analytics/index.md analytics/progress.md apps/web/README.md apps/web/app/api/compare/route.ts apps/web/app/api/health/route.ts apps/web/app/api/live/token/route.ts apps/web/app/api/stats/route.ts apps/web/app/api/stt/route.ts apps/web/app/api/tts/route.ts apps/web/app/api/turn/route.ts apps/web/app/api/turns/route.ts apps/web/app/globals.css apps/web/app/layout.tsx apps/web/app/live/page.tsx apps/web/app/page.tsx apps/web/app/workspace.css apps/web/components/AppHeader.tsx apps/web/components/CallScreen.tsx apps/web/components/CompareModal.tsx apps/web/components/Composer.tsx apps/web/components/ConfidenceBar.tsx apps/web/components/LatencyTable.tsx apps/web/components/LocaleSwitcher.tsx apps/web/components/MessageList.tsx apps/web/components/MicButton.tsx apps/web/components/Modal.tsx apps/web/components/PolicyActionView.tsx apps/web/components/ScenarioCard.tsx apps/web/components/SettingsModal.tsx apps/web/components/SlotsTable.tsx apps/web/components/StatusLine.tsx apps/web/components/SupervisorStats.tsx apps/web/components/TracePanel.tsx apps/web/components/TurnHistory.tsx apps/web/components/VoiceBar.tsx apps/web/components/VoiceRouterApp.tsx apps/web/components/compare-client.ts apps/web/components/turn-client.ts apps/web/components/types.ts apps/web/components/use-speech-recognition.ts apps/web/components/use-speech-synthesis.ts apps/web/components/use-voice-call.ts apps/web/lib/i18n/index.tsx apps/web/lib/live/config.ts apps/web/lib/server/catalog.ts apps/web/lib/server/compare.test.ts apps/web/lib/server/compare.ts apps/web/lib/server/completion.test.ts apps/web/lib/server/completion.ts apps/web/lib/server/db.ts apps/web/lib/server/demo-real.test.ts apps/web/lib/server/journal.test.ts apps/web/lib/server/journal.ts apps/web/lib/server/router-service.test.ts apps/web/lib/server/router-service.ts apps/web/lib/server/stats.test.ts apps/web/lib/server/stats.ts apps/web/lib/server/transcribe.test.ts apps/web/lib/server/transcribe.ts apps/web/messages/en.json apps/web/messages/kk.json apps/web/messages/ru.json apps/web/next.config.ts apps/web/package.json apps/web/tsconfig.json backend/Dockerfile backend/app/__init__.py backend/app/config.py backend/app/dataset.py backend/app/dialog/__init__.py backend/app/dialog/engine.py backend/app/dialog/policy.py backend/app/dialog/state.py backend/app/executor/__init__.py backend/app/executor/fsm.py backend/app/executor/mocks.py backend/app/executor/slots.py backend/app/main.py backend/app/router/__init__.py backend/app/router/catalog.py backend/app/router/prompt.py backend/app/router/router.py backend/pytest.ini backend/requirements-dev.txt backend/requirements.txt backend/tests/__init__.py backend/tests/fakes.py backend/tests/test_api.py backend/tests/test_mocks.py backend/tests/test_prompt.py backend/tests/test_router.py data/kit/README.kz.md data/kit/README.md data/kit/README.ru.md data/kit/SOURCE.md data/kit/actions.json data/kit/dev_utterances.json data/kit/dialogs_sample.json data/kit/evaluate.py data/kit/knowledge_base.json data/kit/mock_backend.json data/kit/scenarios.json data/kit/slots.json db/migrate.test.ts db/migrate.ts db/migrations/001_turn_journal.sql db/migrations/README.md docker-compose.dev.yml docker-compose.yml docs/README.md docs/ROUTER_LOG.md package.json packages/actions/README.md packages/core/README.md packages/core/package.json packages/core/src/contracts/fixtures/router-service-route.json packages/core/src/contracts/route-decision.test.ts packages/core/src/contracts/route-decision.ts packages/core/src/contracts/router-service.test.ts packages/core/src/contracts/turn.ts packages/core/src/index.ts packages/core/src/policy/decide.test.ts packages/core/src/policy/decide.ts packages/core/src/reply/build-reply.ts packages/core/src/reply/demo-router.ts packages/core/src/reply/reply.test.ts packages/core/src/router/catalog.ts packages/core/src/router/fixtures.ts packages/core/src/router/prompt.ts packages/core/src/router/router.test.ts packages/core/src/router/router.ts packages/core/src/stt/fixtures/mini-kit/scenarios.json packages/core/src/stt/fixtures/mini-kit/slots.json packages/core/src/stt/glossary.test.ts packages/core/src/stt/glossary.ts packages/core/src/stt/index.ts packages/core/src/stt/metrics.test.ts packages/core/src/stt/metrics.ts packages/core/src/stt/stt-glossary.json packages/core/src/stt/stt-glossary.test.ts packages/core/src/stt/text.ts packages/core/src/stt/transcription-hints.test.ts packages/core/src/stt/transcription-hints.ts pnpm-lock.yaml pnpm-workspace.yaml scripts/dev-db-proxy.mjs scripts/dev-web.mjs scripts/glossary-build.ts scripts/route_batch.py scripts/smoke.mjs scripts/stt-eval.ts tests/e2e/README.md tsconfig.json vitest.config.ts
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.