Голоса и спикеры
Три разных вещи, которые легко перепутать:
- Спикер записи это номер голоса внутри одной записи:
segments[].speaker= 0, 1, 2… В следующей записи тот же человек может оказаться под другим номером. - Голос проекта это постоянный номер человека во всех записях проекта: #100, #101… Появляется при включённой опции «Узнавать по голосу».
- Имя это то, как вы назвали голос проекта: «Азимбек (менеджер)». Имя не обязательно: безымянный голос тоже узнаётся.
Как голоса получают номера
Когда запись обработана с включённой опцией «Узнавать по голосу»:
- Для каждого спикера записи считается голосовой отпечаток.
- Отпечаток сравнивается с голосами проекта. Совпал: спикер получает номер этого голоса, статус
matched. - Не совпал, а речи у спикера 10 секунд и больше: регистрируется новый голос проекта со следующим номером, статус
new. - Речи меньше 10 секунд: статус
unknown, номера нет. Отпечаток с нескольких секунд ненадёжен, и мы не заводим по нему голос.
Связь спикеров записи с голосами проекта лежит в voices:
"voices": [
{"label": "SPEAKER_1", "speaker": 0, "voice_id": 100, "name": null, "status": "new"},
{"label": "SPEAKER_2", "speaker": 1, "voice_id": 101, "name": "Азимбек", "status": "matched", "score": 0.97}
]speaker это номер из segments[].speaker, voice_id номер голоса проекта, speaker_id его идентификатор для API спикеров.
Сколько голос живёт
Не каждый услышанный голос стоит хранить вечно, поэтому у голоса есть вид, и он виден в kind:
| Вид | Что это | Сколько живёт |
|---|---|---|
candidate | Голос, услышанный один раз | 30 дней с последней встречи, потом исчезает |
recurring | Услышан в трёх записях и более | Хранится, пока его не удалят |
named | Голос, которому дали имя | Хранится, пока его не удалят |
Кандидат, который встретился ещё раз, обновляет last_seen_at и отсчёт начинается заново; на третьей записи он становится повторяющимся, и срок с него снимается. Имя снимает срок сразу: назвали голос — он остаётся. Поэтому голос сотрудника лучше назвать в первый же раз, а не ждать третьей записи.
expires_at говорит, когда голос исчезнет, у recurring и named он пустой.
Дать имя
Вкладка «Спикеры» кабинета показывает все голоса проекта: именованные и «Голос #100», узнанные автоматически. Кнопка «Дать имя» превращает номер в имя. Через API: PATCH /v1/speakers/{id} с {"name": "…"}.
Отметить фрагменты как одного человека
Иногда разделение по спикерам ошибается: один человек попадает в два номера, или наоборот. В результате записи есть режим «Отметить фрагменты одного человека»: выберите реплики, которые точно принадлежат одному человеку, и укажите, кто это: существующий голос или новое имя. Из выбранных реплик считается отпечаток и добавляется этому голосу. Чем больше речи выбрано, тем надёжнее; кабинет предупредит, если меньше 10 секунд.
Через API: POST /v1/speakers/{id}/enroll с {"job_id": "…", "segment_indexes": [3, 5, 8]}. Достаточно указать номера реплик: отпечаток считается на нашей стороне из хранимой записи. Самих отпечатков в ответе нет — это биометрия и сотни чисел на голос, работать с ними напрямую незачем.
Загрузить образец голоса
На вкладке «Спикеры» можно загрузить короткую запись одного человека (например, 20–30 секунд речи менеджера) и сразу привязать её к голосу. Запись распознаётся как обычная задача, то есть тратит баланс наравне с остальными.
Несколько бизнесов в одном проекте
Интегратор, который обслуживает много бизнесов через один проект, указывает бизнес каждой записи как её тенант: "tenant": "biz-86233275". Голоса двух тенантов никогда не сравниваются и не нумеруются вместе: для голосов тенант это отдельный проект, и в каждом нумерация идёт с #100. Без tenant запись работает с голосами самого проекта, как раньше.
Порогом похожести этого не добиться. Одинаковые офисные линии, гарнитуры и кодеки делают сотрудников разных бизнесов похожими: на настоящих звонках менеджеры другого бизнеса подходили к записанному голосу менеджера на 0,87.
Только сотрудники
Если вы знаете, чья линия вела звонок (пользователь в вашей CRM), передайте её вместе с записью:
"settings": {
"speaker_roles": true,
"tenant": "biz-86233275",
"known_speakers": [{"external_id": "crm-9612766", "role": "manager"}]
}external_id это ваш собственный идентификатор линии, 1–128 символов из A–Z a–z 0–9 . _ : @ -. Линия может быть одним человеком или до четырёх людей, которые работают на ней посменно. role это manager (ещё operator или supervisor), name не обязательно, в одной записи не больше четырёх линий. known_speakers требуют tenant: без него загрузка отклоняется с 400 tenant_required, чтобы забытый тенант никогда не сравнил клиентов одного бизнеса с сотрудниками другого.
С known_speakers запись сравнивается только с сотрудниками тенанта:
- Узнаются и получают номер только сотрудники. Клиент никогда не регистрируется, а голосовые отпечатки записи выбрасываются до того, как сохраняется её результат.
- Спикер считается сотрудником линии, если близость к одному из голосов линии не ниже 0,65 и хотя бы на 0,05 выше, чем у другого спикера звонка.
identityговорит, кто это:{"status": "matched", "external_id": "crm-9612766", "role": "manager", "voice": 2, "expected": true, "score": 0.91, "threshold": 0.65, "runner_up": 0.39, "voice_id": 100}.voiceговорит, кто из людей линии это был, от 1 до 4; их имён мы не знаем.expected: falseзначит, что голос принадлежит другой линии того же бизнеса, а не названной. Это повод проверить CRM, а не приговор: коллег голос различает гораздо хуже, чем сотрудника и клиента.- Другие статусы:
not_enrolled(у названной линии ещё нет голоса),not_matched(никто из сотрудников),insufficient_speech. - Роли включаются вместе с
known_speakers. Звонок двух сторон, в котором узнана ровно одна сторона, получает роли по голосу: эта сторона берёт роль линии, другая становитсяclient.options_report.roles.sourceговорит, что решило (voice,textилиnone), аroles.textхранит рядом собственный ответ текстовой модели сagrees.
"enroll": "off" узнаёт уже выученные голоса и ничему новому не учится.
Как линия запоминает голоса
Загружать образцы не нужно. Из каждого звонка, где названа линия, мы берём сторону, которую текстовая модель называет менеджером. Голос, услышанный так в двух из последних 20 звонков линии, становится одним из её голосов; у линии до четырёх голосов, самых частых. Голос клиента не повторяется от звонка к звонку, поэтому не становится голосом линии, даже когда текстовая модель приняла клиента за менеджера. Новый человек на линии узнаётся с третьего звонка, а тот, кто перестал брать её звонки, выпадает по мере того, как окно сдвигается. Пока у линии нет голоса, роли берутся из текста, как без голосов.
Что мы храним: для каждого из последних 20 звонков линии номер задания и где говорила эта сторона, а голосовой отпечаток только когда он принадлежит одному из голосов линии. Сторону, которая ещё не повторилась, мы заново печатаем из хранимой записи, когда это нужно, поэтому отпечаток клиента, которого текстовая модель выбрала по ошибке, не хранится.
Если за 20 звонков ни один голос не повторился, линия получает статус inconsistent. На настоящих звонках сотрудники линии узнавались примерно в 95 звонках из 100 — и когда на линии один человек, и когда несколько посменно, на стерео и на моно, — а клиента голос принимал за сотрудника реже чем в 1 звонке из 100. На учётных записях CRM, где люди всё время менялись, узнавание падает примерно до 80 из 100, клиент принимается за сотрудника в 7–10 звонках из 100, и голос исправляет роли не чаще, чем портит. Поэтому передавайте known_speakers только для личных учётных записей и постоянных смен.
Чтобы поправить звонок, в котором текстовая модель выбрала не ту сторону: POST /v1/tenants/{tenant}/staff/{external_id}/enroll с {"job_id": "…", "speaker_index": 1}. Список линий с их голосами и удаление: API.
Голос это биометрические данные
Добавляйте голоса только с согласия людей. Удаление голоса удаляет все его отпечатки: DELETE /v1/speakers/{id}. У голоса не больше 10 отпечатков, у проекта не больше 500 голосов.
Удаление линии, DELETE /v1/tenants/{tenant}/staff/{external_id}, сразу убирает все её голоса, их отпечатки и запомненные звонки; …/voices/{n} убирает одного из её людей. DELETE /v1/tenants/{tenant} удаляет всё, что относится к одному бизнесу, включая отпечатки внутри его сохранённых результатов. Удалённая запись уходит из окна каждой линии. Сами записи хранятся как у любого задания, а результат сохраняет identity с идентификатором линии, пока не истечёт через 30 дней.