From a1f420d61cbac0500ce12aa3106c2608afcd7ec3 Mon Sep 17 00:00:00 2001 From: Vladimir Bryzgalov Date: Sun, 16 Aug 2026 01:15:11 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BB=D0=BE=D0=B2=D0=B0=D1=80=D1=8C=20?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D1=88=D0=BA=D0=BE=D0=BB=D1=8B=20=D0=B8?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D1=81=20Talkscore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Словарь замен расширен под нишу: категории прав, документы, госорганы, термины обучения и оплаты, частые ошибки на плохом звуке. В docs - инструкция для агента Talkscore: что выключить на своей стороне, как ставить задачи и принимать вебхук, и главное - почему полю speaker доверять нельзя и как восстанавливать роли через LLM. Co-Authored-By: Claude Opus 5 (1M context) --- app/version.py | 2 +- docs/ИНТЕГРАЦИЯ-TALKSCORE.md | 208 +++++++++++++++++++++++++++++++++++ replacements.txt | 153 +++++++++++++++++++++----- 3 files changed, 336 insertions(+), 27 deletions(-) create mode 100644 docs/ИНТЕГРАЦИЯ-TALKSCORE.md diff --git a/app/version.py b/app/version.py index 777f190..3e2f46a 100644 --- a/app/version.py +++ b/app/version.py @@ -1 +1 @@ -__version__ = "0.8.0" +__version__ = "0.9.0" diff --git a/docs/ИНТЕГРАЦИЯ-TALKSCORE.md b/docs/ИНТЕГРАЦИЯ-TALKSCORE.md new file mode 100644 index 0000000..d2acf09 --- /dev/null +++ b/docs/ИНТЕГРАЦИЯ-TALKSCORE.md @@ -0,0 +1,208 @@ +# Интеграция Talkscore с локальным ASR-сервисом + +Документ для агента, который будет дорабатывать Talkscore. Описывает, что +изменить на стороне Talkscore, чтобы использовать локальный сервис распознавания +`talkscore-asr` вместо облачного ASR. + +## Что это за сервис + +Локальный сервис на Windows-машине: принимает аудиофайл, возвращает расшифровку +с разделением по говорящим. Работает офлайн, аудио наружу не уходит. + +- Распознавание: GigaAM v3 (SaluteDevices), русский язык, с пунктуацией +- Разделение говорящих: sherpa-onnx с моделями pyannote и NeMo TitaNet +- Скорость: около 60 минут записи за 2,5 минуты + +Адрес и токен спросите у владельца: сервис закрыт списком разрешённых адресов, +поэтому сервер Talkscore нужно в этот список внести. + +## Главное, что нужно изменить в Talkscore + +### 1. Отключить предобработку аудио + +Сейчас в Talkscore включена нормализация громкости (RMS до -20 dBFS) перед +отправкой в ASR. **Её нужно выключить** - сервис делает нормализацию сам, +причём более подходящую (`dynaudnorm`), и лишний проход только тратит ресурсы +сервера. + +Замер на восьми разговорах: нормализация `dynaudnorm` даёт долю второго +участника 24,3 % против 3,5 % у нормализации RMS. Двойная обработка не ломает +результат, но и не улучшает его. + +Что оставить включённым: ничего из блока предобработки не требуется. VAD и +шумоподавление сервису не нужны - у него свой VAD внутри диаризации. + +### 2. Заменить вызов ASR + +Было: отправка в облачный ASR и ожидание ответа. +Стало: постановка задачи и получение результата вебхуком. + +```python +import requests + +ASR_URL = "http://АДРЕС:8756" +ASR_TOKEN = "токен из config.toml сервиса" + +def send_to_asr(file_path: str, call_id: str) -> str: + """Ставит запись в очередь распознавания. Возвращает id задачи.""" + with open(file_path, "rb") as f: + response = requests.post( + f"{ASR_URL}/v1/jobs", + headers={"Authorization": f"Bearer {ASR_TOKEN}"}, + params={ + "speakers": 2, + "webhook": f"https://talkscore.ru/api/asr-callback?call_id={call_id}", + }, + files={"file": f}, + timeout=300, + ) + response.raise_for_status() + return response.json()["job_id"] +``` + +### 3. Принять результат вебхуком + +Сервис сам постучится, когда задача готова. Подпись тела лежит в заголовке +`X-Talkscore-Signature`, секрет задаётся в настройках сервиса. + +```python +import hashlib +import hmac + +WEBHOOK_SECRET = "тот же секрет, что в config.toml сервиса" + +def asr_callback(request): + signature = request.headers.get("X-Talkscore-Signature", "") + expected = hmac.new(WEBHOOK_SECRET.encode(), request.body, hashlib.sha256).hexdigest() + if not hmac.compare_digest(expected, signature): + return 403 + + payload = request.json() + if payload["status"] != "done": + # обработать ошибку: payload["error"] + return 200 + + save_transcript(payload) + return 200 +``` + +Если вебхук не дошёл (три попытки: сразу, через 30 секунд, через 5 минут), +результат остаётся в сервисе и его можно забрать опросом: +`GET /v1/jobs/{job_id}` с тем же заголовком авторизации. + +## Формат результата + +```json +{ + "job_id": "fd47ba135eae429cbf37fb6ec1d8c34c", + "status": "done", + "filename": "call.mp3", + "duration_sec": 1003.0, + "turns": [ + { + "speaker": 1, + "start": 7.2, + "end": 9.4, + "text": "Ну, давайте послушаю вас ещё.", + "acoustics": {"loudness_db": -18.4, "hf_ratio": 0.208, "centroid_hz": 1706} + } + ], + "stats": { + "speakers": 2, + "speech_sec": 557.0, + "silence_sec": 446.1, + "turns_count": 137, + "separation_quality": 0.27, + "speakers_reliable": false, + "by_speaker": [ + {"speaker": 1, "speech_sec": 236.8, "share_pct": 42.5}, + {"speaker": 2, "speech_sec": 320.2, "share_pct": 57.5} + ] + }, + "timing": {"diarization_sec": 29.1, "asr_sec": 9.7, "realtime_factor": 25.9} +} +``` + +## Самое важное: полю `speaker` доверять нельзя + +Записи делаются одним микрофоном на столе, оба участника в одной акустике. +Проверка на восьми разговорах: **разделение по голосам сработало только на одном +из восьми**. На остальных один участник получал от 91 до 99 процентов речи, +то есть модель просто не различает голоса. + +Что с этим делать: + +1. **Смотрите на `stats.speakers_reliable`.** Если `false` (а это обычный случай), + разметку по говорящим нужно строить заново - по смыслу реплик. +2. **Роли определяет LLM.** Готовый промпт - в разделе ниже. +3. **Границы реплик и тайм-коды достоверны** - их даёт детектор речи, и он + работает хорошо. Опираться можно на них, а не на номер говорящего. +4. **`acoustics` - подсказка.** У того, кто ближе к микрофону, громкость и доля + высоких частот стабильно выше. Это дополнительный сигнал для LLM. + +## Промпт для восстановления ролей + +Расставлять роли поверх реплик диаризации бесполезно: когда она провалилась, +в одной реплике оказываются и вопрос, и ответ. Поэтому текст режется на +предложения, а модель отвечает одной буквой на предложение - так выход +получается в десятки раз короче, чем при возврате текста. + +Подготовка: склейте текст всех реплик, разрежьте по границам предложений +(GigaAM ставит точки и вопросительные знаки), пронумеруйте. + +``` +Ты разбираешь запись разговора в автошколе: менеджер и клиент. +Микрофон стоял на столе, автоматическое разделение по голосам не сработало, +поэтому реплики склеены - в одной строке может быть и вопрос одного, +и ответ другого. + +Ниже пронумерованные предложения по порядку. Определи для каждого, кто его +произнёс. + +Как отличить: +- МЕНЕДЖЕР (M): рассказывает об условиях, ценах, документах, расписании; + отвечает на вопросы; предлагает записаться; говорит «у нас», «мы», + «вам нужно принести», называет суммы и сроки. +- КЛИЕНТ (C): спрашивает про стоимость, сроки, расписание; рассказывает + о себе и своей ситуации; сомневается; сравнивает с другими автошколами; + соглашается или уточняет; говорит «а если», «мне нужно», «я слышал». + +Подсказки: +- Вопрос и ответ на него принадлежат разным людям. +- Короткие «да», «ага», «понятно», «конечно» обычно принадлежат слушающему, + то есть тому, кто НЕ произносил предыдущее длинное объяснение. +- Менеджер говорит больше, но не непрерывно: клиент постоянно вставляет + короткие реплики. + +Ответь строкой ровно из {count} символов, только M и C, без пробелов и переносов, +по одному символу на предложение в том же порядке. + +Предложения: +{sentences} +``` + +После ответа склейте соседние предложения с одинаковой ролью обратно в реплики, +сохранив тайм-коды первого предложения каждой группы. + +## Словарь замен терминов + +В сервисе лежит файл `replacements.txt` со словарём под автошколу: категории +прав, документы, госорганы, термины обучения и оплаты, частые ошибки +распознавания. Формат `что слышно = как надо`, замена по целым словам, +регистр не важен. + +Файл перечитывается перед каждой задачей, перезапуск не нужен. Если в +расшифровках попадаются устойчивые ошибки - дописывайте строки туда. + +## Что ещё стоит знать + +- **Форматы**: принимается всё, что читает ffmpeg - mp3, wav, m4a, ogg, opus, wma. +- **Размер**: по умолчанию до 500 МБ, настраивается. +- **Очередь**: задачи обрабатываются по очереди, статус и место в очереди + видны в `GET /v1/jobs/{id}`. +- **Диагностика**: `GET /health` работает без токена, показывает версию, + очередь и то, каким сервис видит адрес обратившегося. +- **Журнал**: `GET /v1/logs?level=ERROR` - последние записи, чтобы разбирать + сбои не заходя на машину. +- **Описание методов**: `http://АДРЕС:8756/docs` в браузере, открывается + с разрешённых адресов. diff --git a/replacements.txt b/replacements.txt index dc40587..625e9e5 100644 --- a/replacements.txt +++ b/replacements.txt @@ -1,35 +1,136 @@ -# Словарь замен: что модель слышит = как должно быть в тексте +# Словарь замен для расшифровок разговоров автошколы # -# GigaAM обучена только на русском и коверкает английские термины предсказуемо - -# здесь это чинится. Регистр при поиске не важен, замена идёт только по целым -# словам, поэтому «лед» не тронет «лидер». +# GigaAM обучена только на русском и предсказуемо коверкает термины, аббревиатуры +# и английские слова. Здесь это чинится. Правила применяются к целым словам, +# регистр не важен: правило «лед = лид» не тронет слово «лидер». # -# Файл перечитывается перед каждой задачей: правки применяются без перезапуска. +# Файл перечитывается перед каждой задачей - правки применяются без перезапуска. +# Порядок значения не имеет: длинные правила применяются раньше коротких. -# Инструменты и сервисы -гугл так менеджер = Google Tag Manager -гугл тэг менеджер = Google Tag Manager -google так менеджер = Google Tag Manager -google tack-менеджер = Google Tag Manager -так менеджер = Tag Manager -джава-скрипт = JavaScript -джаваскрипт = JavaScript -битрикс 24 = Битрикс24 -амо црм = amoCRM -амоцрм = amoCRM +# ── Документы и категории ─────────────────────────────────────────────────── +ву = В/У +в у = В/У +водительское удостоверение = водительское удостоверение +ноль три ву = 003 В/У +справка ноль три = справка 003 В/У +мед справка = медсправка +мед комиссия = медкомиссия +мед заключение = медзаключение +снилс = СНИЛС +инн = ИНН +категория а = категория A +категория б = категория B +категория бэ = категория B +категория в = категория B +категория це = категория C +категория эс = категория C +категория д = категория D +категория дэ = категория D +подкатегория а один = подкатегория A1 +b категория = категория B +би категория = категория B +а категория = категория A -# Термины продаж и маркетинга +# ── Госорганы и системы ───────────────────────────────────────────────────── +гибдд = ГИБДД +гаи = ГАИ +агаи = ГАИ +мрэо = МРЭО +мреа = МРЭО +госуслуги = Госуслуги +гос услуги = Госуслуги +мфц = МФЦ +цодд = ЦОДД + +# ── Обучение ──────────────────────────────────────────────────────────────── +теоретический экзамен = теоретический экзамен +внутренний экзамен = внутренний экзамен +теория онлайн = теория онлайн +дистанционка = дистанционное обучение +очка = очное обучение +видео уроки = видеоуроки +видио уроки = видеоуроки +рубежка = рубежное тестирование +рубежи = рубежные тестирования +пдд = ПДД +пэдэдэ = ПДД +билеты пдд = билеты ПДД +автодром = автодром +площадка = автодром +город = город +змейка = змейка +эстакада = эстакада +параллельная парковка = параллельная парковка +разворот в ограниченном пространстве = разворот в ограниченном пространстве + +# ── Люди и роли ───────────────────────────────────────────────────────────── +инструктор = инструктор +старший инструктор = старший инструктор +главный инспектор = старший инструктор +курсант = курсант +курсанты = курсанты +мастер производственного обучения = мастер производственного обучения +мпо = МПО + +# ── Деньги и оплата ───────────────────────────────────────────────────────── +рассрочка = рассрочка +предоплата = предоплата +доплата = доплата +безнал = безналичный расчёт +безналичка = безналичный расчёт +нал = наличные +терминал = терминал +эквайринг = эквайринг +чек = чек +квитанция = квитанция +материнский капитал = материнский капитал +налоговый вычет = налоговый вычет + +# ── Английские термины и сервисы ──────────────────────────────────────────── +вотсап = WhatsApp +ватсап = WhatsApp +воцап = WhatsApp +телеграм = Telegram +телега = Telegram +вайбер = Viber +смс = СМС +онлайн = онлайн +оффлайн = офлайн +кэшбэк = кешбэк +кешбек = кешбэк +приложуха = приложение +личный кабинет = личный кабинет +црм = CRM +срм = CRM +кпи = KPI + +# ── Продажи и переговоры ──────────────────────────────────────────────────── ледами = лидами леды = лиды лед = лид -кол-трекинг = коллтрекинг -колл трекинг = коллтрекинг -ретаргет = ретаргетинг -конверсионка = конверсия +заявка = заявка +запись на пробное = запись на пробное занятие +пробное занятие = пробное занятие +акция = акция +скидка = скидка +абонемент = абонемент +договор = договор +оферта = оферта +предоплату = предоплату -# Часто путаемые сокращения -црм = CRM -кпи = KPI -срм = CRM +# ── Частые ошибки распознавания на плохом звуке ───────────────────────────── +щас = сейчас +чё = что +чо = что +тыща = тысяча +тыщи = тысячи +тыщ = тысяч +пятсот = пятьсот +шестсот = шестьсот +восемсот = восемьсот +сюдой = сюда +тудой = туда +ага = ага +угу = угу -# Добавляйте свои строки ниже. Формат: что слышно = как надо +# ── Добавляйте свои строки ниже. Формат: что слышно = как надо ──────────────