Формат ответа
GET /v1/jobs/{id}/result возвращает JSON. Пример для стерео-звонка со всеми опциями, сокращённый:
{
"schema_version": "gapi_task_result.v0",
"audio_seconds": 282.7,
"speech_seconds": 230.4,
"channels": 2,
"stereo_parties": true,
"speakers": 2,
"vocabulary_terms": 4,
"vocabulary_dropped": false,
"text": "Assalomu alaykum, Metasell kompaniyasi. …",
"segments": [
{
"index": 0,
"start": 0.42,
"end": 3.9,
"speaker": 1,
"text": "Assalomu alaykum, Metasell kompaniyasi.",
"raw_asr_text": "assalomu alaykum metasell kompaniyasi",
"formatted_text": "Assalomu alaykum, Metasell kompaniyasi.",
"emotion": {"mood": "neutral", "label": "neutral", "confidence": 0.88, "valence": 0.52, "arousal": 0.31, "dominance": 0.47, "windows": 1,
"probabilities": {"neutral": 0.88, "happy": 0.05, "sad": 0.02, "angry": 0.02, "fearful": 0.01, "disgusted": 0.01, "surprised": 0.01}},
"pii": {"entities": [], "redacted_text": "Assalomu alaykum, Metasell kompaniyasi."}
}
],
"speaker_info": [
{
"index": 0,
"speech_seconds": 149.2,
"gender": {"label": "male", "confidence": 0.97},
"role": "client",
"identity": {"status": "new", "voice_id": 100, "speaker_id": "e3db…", "name": null},
"has_voiceprint": true,
"emotion": {"dominant_mood": "negative", "mood_share": {"negative": 0.46, "neutral": 0.41, "positive": 0.13}, "valence": 0.38, "arousal": 0.61, "dominance": 0.44}
}
],
"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}
],
"options_report": {"requested": ["gender", "speaker_roles", "speaker_identification", "emotion", "pii"], "errors": {}, "seconds": 1.8},
"timing": {"decode_seconds": 0.3, "vad_seconds": 0.4, "stt_seconds": 2.1, "total_seconds": 4.9}
}Текст приходит латиницей: модель обучена на латинской записи узбекского, и все опции (пунктуация, числа, персональные данные) работают с ней. Кириллицы на выходе нет.
Верхний уровень
| Поле | Что это |
|---|---|
audio_seconds | Длина записи; из неё считается списание в g |
speech_seconds | Сколько в ней речи |
channels, stereo_parties | Каналов в файле; true, если стороны разделены по каналам |
speakers | Сколько голосов нашло разделение по спикерам |
text | Весь текст подряд |
segments | Реплики по порядку |
speaker_info | Сводка по каждому спикеру записи |
voices | Постоянные номера голосов проекта, при «Узнавать по голосу» |
options | Настройки, с которыми запись реально считалась: проект плюс settings запроса. Здесь все ключи со страницы Опции — включая stereo_channels_as_speakers, max_speakers, call_direction и fuzzy_itn (нестрогая нормализация чисел; по умолчанию выключена) |
vocabulary_terms, vocabulary_dropped | Сколько терминов словаря взяла задача; true во втором, если после чистки не осталось ни одного |
diarization_error | true, если разделение по спикерам не отработало: номеров спикеров в записи не будет. При удачном разделении поле есть и равно null |
options_report | Какие опции запрашивались, какие не посчитались и почему |
segment_voiceprints | У каких реплик есть голосовой отпечаток: {"dims", "format", "min_seconds", "segments": [индексы]}. Сами отпечатки наружу не отдаются — это список реплик, из которых можно завести голос через POST /v1/speakers/{id}/enroll с segment_indexes |
timing | Сколько секунд заняла обработка: decode_seconds, vad_seconds, stt_seconds, format_seconds, diarization_seconds, total_seconds. Считаем по ним себя, а не вас |
options_report показывает, что запрашивалось (requested), что не посчиталось и почему (errors), и сколько секунд ушло на опции (seconds). Если запрашивались роли, там же лежит roles — ответ модели ролей как есть, вместе с её score, first_speaker, status и direction. Это диагностика: числа в ней приходят строками, состав может поменяться. Роль каждого спикера берите из speaker_info[].role.
Рядом лежат служебные поля — word_timing, formatting, partial. Они для разбора наших же обращений: состав может поменяться, не стройте на них логику.
Реплика
| Поле | Что это |
|---|---|
index | Порядковый номер, с 0 |
start, end | Секунды от начала записи |
speaker | Номер спикера записи или null |
text | Итоговый текст с включёнными опциями |
raw_asr_text | Как услышала модель: строчные, без знаков |
formatted_text | С пунктуацией и заглавными |
words | Слова с временем, при word_timestamps; см. ниже |
speaker_share | Какая доля речи реплики размечена самим разделением по спикерам, от 0 до 1; остальное унаследовано от соседних слов. Есть только там, где спикеры считались |
contextual_text | Текст после словаря, до пунктуации и чисел; между raw_asr_text и formatted_text |
duration_bucket_seconds | Служебная корзина длительности для нашей телеметрии |
emotion | {"mood", "label", "confidence", "probabilities", "valence", "arousal", "dominance", "windows"}, при «Эмоциях»; у реплики короче секунды — {"label": null, "reason": "too_short"}; см. Шкалы эмоций |
pii | {"entities", "redacted_text"}, при «Персональных данных» |
Значения mood: neutral, negative, distressed, positive, surprised. Метки pii: NAME, PHONE, ADDRESS, DATE, DOCUMENT_ID, CARD_NUMBER.
Слово
{"text": "Uzum", "raw": "uzim", "formatted": "Uzum", "start": 4.12, "end": 4.51, "speaker": 1, "aligned": "term"}| Поле | Что это |
|---|---|
text | Слово после словаря, до пунктуации |
raw | Что услышала модель на этом месте; у вставленного слова поля нет |
formatted | Оно же с заглавными и знаками, когда реплика отформатирована |
start, end | Секунды от начала записи |
speaker | Номер спикера, если спикеры считались |
aligned | Откуда взято время слова |
aligned стоит читать: exact — кадры самой модели на этом слове; term — слово пришло из словаря и размечено по кадрам поисковика терминов, то есть это прямое доказательство, что термин применился; span — доля от переписанного куска; chunk — на этот кусок модель не дала слов, время у всего куска общее; none — слово вставлено без акустического подтверждения, длина нулевая.
Спикер записи
| Поле | Что это |
|---|---|
index | Номер спикера, тот же, что в репликах |
speech_seconds | Сколько он говорил |
gender | {"label": "female" | "male" | "unknown", "confidence", "reason", "windows", "speech_seconds", "probabilities"}. У unknown поле confidence равно null, а reason говорит почему |
role | "client", "manager" или "unknown" |
identity | {"status", "voice_id", "speaker_id", "name", "score", "threshold", "runner_up", "kind"} |
has_voiceprint | У спикера хватило речи на голосовой отпечаток: его можно назвать через Голоса и спикеры. Сам отпечаток наружу не отдаётся |
emotion | {"dominant_mood", "mood_share", "valence", "arousal", "dominance"} |
identity.status отвечает на один вопрос: узнали ли этого человека и, если нет, почему.
| Статус | Что произошло | Есть ли voice_id |
|---|---|---|
matched | Отпечаток совпал с голосом проекта | да, номер знакомого голоса |
new | Не совпал, но речи хватило: голос заведён этой записью и дальше будет узнаваться | да, новый номер |
unknown | Не совпал, и речи меньше 10 секунд — заводить голос по такому отпечатку ненадёжно | нет |
insufficient_speech | Отпечаток не удалось посчитать вовсе: речи почти нет | нет |
no_enrolled_voices | В проекте ещё нет ни одного голоса, сравнивать не с чем | нет |
Разница между unknown и insufficient_speech в том, что в первом случае отпечаток есть и сравнение прошло, просто человек незнаком и речи мало для регистрации; во втором считать было нечего.
У matched и unknown рядом лежат score, близость к ближайшему голосу от 0 до 1, и threshold, порог, начиная с которого совпадение засчитывается. Если score регулярно чуть ниже порога, голосу не хватает отпечатков: добавьте ещё один из хорошей записи.
Там же runner_up — близость ко второму по похожести голосу (-1, если сравнивать было не с чем). Совпадение засчитывается, только если первый обошёл второго с запасом: два похожих голоса не должны выигрывать друг у друга случайно. У matched и new есть ещё kind — какой это голос в проекте: candidate, recurring или named, см. Голоса и спикеры.
Шкалы эмоций
Модель эмоций слушает реплику и отвечает двумя способами сразу.
Класс. label это один из семи классов модели: neutral, happy, sad, angry, fearful, disgusted, surprised. probabilities даёт вероятность каждого, confidence это вероятность выбранного. Уверенность 0.5 при семи классах уже уверенный ответ; ниже 0.35 класс лучше не показывать оператору. mood это класс, свёрнутый до пяти понятных состояний: angry и disgusted в negative, sad и fearful в distressed, happy в positive.
Три шкалы от 0 до 1. Они не зависят от класса и описывают состояние непрерывно:
| Шкала | 0 | 1 | Как читать |
|---|---|---|---|
valence | неприятно | приятно | Ниже 0.4 клиент недоволен, выше 0.6 доволен. Самая полезная шкала для оценки разговора |
arousal | спокойно | напряжённо | Высокий arousal при низком valence это раздражение или спор, при высоком valence воодушевление |
dominance | неуверенно | напористо | Кто ведёт разговор. Низкое значение у клиента с высоким arousal часто означает растерянность |
Значения около 0.5 нейтральны. Смотрите на динамику по репликам, а не на одно число: падение valence у клиента за разговор говорит больше, чем его абсолютное значение. windows показывает, из скольких окон по 15 секунд усреднена реплика; для коротких реплик оно равно 1 и оценка шумнее.
Реплика короче секунды не получает оценки вовсе: вместо чисел приходит {"label": null, "reason": "too_short"} — по обрывку модель не гадает. Короткие реплики одного спикера, идущие подряд, сначала пробуют упаковаться в одно окно, так что «слишком коротко» достаётся в основном одиночным репликам вроде «Ha» посреди чужой речи.
В speaker_info[].emotion те же три шкалы усреднены по всем репликам спикера, рядом dominant_mood и mood_share, доля каждого состояния. В кабинете шкалы показаны полосками: короткими в строке реплики и карточкой на каждого спикера под плеером. Наведите указатель на полоску, и она скажет, какой это показатель, что значат его концы и само число; там же рядом с репликой видна уверенность модели в эмоции.
Пока задача не готова
GET /v1/jobs/{id}/result отдаёт результат только у готовой задачи; пока она идёт, приходит 409 result_not_ready. Реплики по мере готовности берутся из GET /v1/jobs/{id}/events: см. API.