AAYMA
RU
Войти

Voice Router

Голосовой робот контакт-центра страховой компании: распознаёт ru/kk/смешанную речь, LLM выбирает сценарий из 40 по контексту диалога, отвечает…

Доведённый продукт
Команда «IITU+SPARTANS» 21💬 0 комментариевс 9 сентября 2026 г.

О проекте

Команда: 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.md

Voice Router — гибридный голосовой AI-робот с LLM-слоем выбора сценария

HackAlem AI 2026 Трек 09 TypeScript 7 Next.js 16 PostgreSQL 16 TDD

Хакатон 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 --waithttp://localhost:3000
Проверить кодpnpm verifyПроверка типов и тесты в терминале
Проверить запущенное приложениеpnpm smoke или pnpm smoke http://localhost:3001БД, журнал и настройка серверного голоса
Править интерфейс с горячим обновлениемПосле pnpm install --frozen-lockfile: pnpm dev:webhttp://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. Установка

  1. Установить Docker с поддержкой Compose.
  2. Клонировать репозиторий.
  3. 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_KEYweb, router, pnpm stt:evalключ живого режима; пусто — демо-режим, кнопка звонка скрыта
DATABASE_URLweb, pnpm db:migrateстрока подключения к PostgreSQL; в Compose задана в docker-compose.yml
ROUTER_URLwebадрес сервиса выбора сценария; в Compose — http://router:8000; пусто — маршрутизатор ядра
ROUTER_MODELweb, routerмодель выбора сценария; пусто — gpt-5.4-mini
ROUTER_BASE_URL, ROUTER_API_KEYwebлюбой OpenAI-совместимый вход (Chat Completions) для маршрутизатора ядра: тот же промпт и та же проверка ответа; пусто — OpenAI и OPENAI_API_KEY
ROUTER_REASONING_EFFORTweb, routerглубина рассуждения моделей gpt-5; пусто — none (быстрее)
GEMINI_API_KEY, GEMINI_MODELwebвторой движок окна «Сравнить ChatGPT и Gemini» (раздел 12.1): Google Gemini через OpenAI-совместимый вход; пусто — окно сообщает, что ключ не задан; модель по умолчанию gemini-3.8-flash
GEMINI_LIVE_MODELwebмодель звонка на странице /live (Gemini Live API, раздел 12.2); пусто — gemini-3.8-live
STT_MODELwebмодель распознавания речи; пусто — gpt-transcribe
TTS_MODEL, TTS_VOICEwebмодель и голос синтеза; пусто — gpt-4o-mini-tts, marin
DATA_DIR, DATASET_DIRweb, routerкаталог стартового набора; пусто — data/kit (в контейнере router — /data/kit)
STT_GLOSSARYwebпуть к словарю терминов; пусто — packages/core/src/stt/stt-glossary.json
KIT_DIRpnpm glossary:build, pnpm stt:evalкаталог набора для сборки словаря и оценки распознавания

Секреты в репозиторий не попадают: .env исключён в .gitignore.

8. Порядок проверки основного сценария

  1. pnpm verify — установка по lock-файлу, проверка типов, тесты (раздел 4).

  2. cp .env.example .env, вписать свой OPENAI_API_KEY, затем docker compose up --build; проверить curl http://localhost:3000/api/health — {"ok":true,"db":true,"migrations":1}.

  3. Открыть http://localhost:3000, нажать «Позвонить роботу» и разрешить микрофон. Сказать, например: «Здравствуйте, я вчера оплатил, деньги списались, а полис не оформлен… и ещё адрес поменять надо». Робот определит конец фразы по тишине и ответит голосом; справа панель супервизора покажет транскрипт, язык, источник решения, два сценария по порядку (SC30, затем SC29) с обоснованием и уверенностью, альтернативы и задержку по этапам.

  4. Продолжить разговор на казахском и со смешением языков, например «Сәлеметсіз бе, мен кеше оплатил, а полис әлі жоқ, что делать?». Перебить робота голосом — ответ остановится, реплика будет записана.

  5. Ниже в панели — статистика журнала: доли запуска, уточнения и передачи оператору, частые сценарии и ходы, где робот сомневался (раздел 12).

  6. Тот же ход без интерфейса:

    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
    
  7. Замер точности на размеченных репликах организаторов (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_accfull_matchintent_recallмедиана / p90 роутера, мс
v0gpt-4.1-mini0.9710.9811.0001736 / 2461
v4, 2 прогонаgpt-5.4-mini1.000 / 1.0000.981 / 1.0000.962 / 1.0001476 / 2012 · 1585 / 2277
v5 (сервис /route), 2 прогонаgpt-5.4-mini1.000 / 1.0000.981 / 0.9901.000 / 0.9621603 / 2373 · 1656 / 2544
v6 (обоснование на русском), 2 прогонаgpt-5.4-mini1.000 / 1.0000.990 / 0.9900.962 / 0.9621813 / 2517 · 1852 / 2834
весь стек, сервис напрямую POST :8000/route, 3 параллельноgpt-5.4-mini1.0000.9901.0001598 / 2209
весь стек, через POST /api/turn после #34, 3 параллельноgpt-5.4-mini1.0000.9900.9621736 / 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: без / с подсказкой
ru862.5 % / 87.5 %0.060 / 0.010
kk540.0 % / 100.0 %0.460 / 0.050
mixed250.0 % / 100.0 %0.205 / 0.000
все1553.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-слоя (сценарии, уверенность, обоснование, слоты), действие политики — и, после разметки супервизором, верный сценарий. Конвейер, который на это ложится без смены архитектуры:

  1. Выгрузка из PostgreSQL пар «реплика + контекст → сценарий» с поправками супервизора; объединение с dev_utterances.json и dialogs_sample.json набора.
  2. Дообучение открытой модели (LoRA на одной GPU банка: семейства Qwen или Gemma) под тот же контракт RouteDecision: промпт и проверка ответа не меняются, потому что маршрутизатор ядра принимает любой OpenAI-совместимый вход (ROUTER_BASE_URL, раздел 7) — дообученная модель подключается одной переменной.
  3. Замер evaluate.py на dev-наборе и окно сравнения (раздел 12.1): своя модель против gpt-5.4-mini на одних и тех же репликах, с задержкой и вердиктом совпадения.
  4. Гибридный путь из ТЗ: маленькая локальная модель — для очевидных запросов (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 с, Gemini gemini-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), Алматы.

УчастникGitHubTelegram
Сырым Жакыпбеков (лидер)@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.
  • Ранее созданный код в решении не используется: код пишется в этом репозитории в ходе хакатона.
Звёзды
⭐ 1
Форки
0
Язык
TypeScript
Последний push
в прошлом месяце
Коммиты
146
Авторы
4
Файлы
169
Ветки
12

Авторы коммитов

Syrym Zhakypbekov 115Batyrkhan 13zhmus00 11Batyrkhan 7

История коммитов

  • 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
Ветки (12)

Обсуждение 0 комментариев

Войдите, чтобы комментировать

Пока нет комментариев — будьте первым.