> ## Documentation Index
> Fetch the complete documentation index at: https://docs2.speech.palatine.ru/llms.txt
> Use this file to discover all available pages before exploring further.

# Анализ звонка: роли, эмоции, фильтр мата

К транскрипции можно подключить три опции анализа разговора:

| Опция | Что делает | Требует диаризации |
| - | - | - |
| `roles` | Определяет роль каждого говорящего («Оператор», «Клиент», …) | да |
| `emotions` | Определяет эмоцию каждой реплики (только русская речь) | да |
| `profanity_filter` | Маскирует нецензурную и грубую лексику в тексте | нет |

Отдельных эндпоинтов нет — опции передаются в обычный запрос транскрипции. Анализ
выполняют собственные модели Palatine. Без опций запрос и ответ не меняются.

## Где доступны

| Эндпоинт | Как передать опции | Что поддерживается |
| - | - | - |
| `POST /api/v1/transcribe/do_transcribe` (асинхронный) | поля формы (`-F`) **или** query-параметры; если указаны оба — берётся поле формы | все три |
| `POST /api/v1/audio/transcriptions` (OpenAI-совместимый, синхронный) | поля формы | все три |
| `POST /api/v1/transcribe/do_transcribe_by_url` (асинхронный, файл по ссылке) | поле JSON-тела | только `profanity_filter`; `roles` / `emotions` → `422` |

<Note>
  На `/transcribe/do_transcribe` прочие параметры (`model`, `diarization_model`,
  `num_speakers`, …) по-прежнему передаются **только в query** — полями формы
  принимаются лишь `roles`, `emotions` и `profanity_filter`. На `/audio/transcriptions`
  все параметры, как и раньше, — поля формы.
</Note>

<Warning>
  `roles` и `emotions` работают только вместе с диаризацией: укажите `diarization_model`
  (см. [Разметка говорящих](/documentation/quick_start/diarization)). Без неё запрос
  вернёт `422`.
</Warning>

## Параметры

### `roles` — роли говорящих

| Значение | Как работает |
| - | - |
| `auto` | Роли называет сервис сам, на языке разговора (для русского — по-русски) |
| JSON-массив имён | Каждому говорящему назначается одна из ролей списка: `["Оператор", "Клиент"]` |
| JSON-объект `{имя: описание}` | То же, но с подсказкой, кто есть кто: `{"Оператор": "сотрудник колл-центра", "Клиент": "звонит с вопросом"}` |

Ограничения: не больше **10** ролей; имя роли — непустое, до 64 символов, имена не
должны повторяться (без учёта регистра); описание — до 500 символов. Переводы строк,
табуляция и другие управляющие символы в именах и описаниях не допускаются. Нарушение
любого ограничения — `422`.

Если говорящего не удаётся уверенно отнести ни к одной роли из списка, его роль — `null`.

### `emotions` — эмоции реплик

Логическое значение: `true` / `false` (также `1`/`0`, `yes`/`no`, `on`/`off`).

Каждой реплике назначается одна из эмоций: `neutral`, `positive`, `sad`, `angry`.

<Note>
  Эмоции определяются **только для русской речи**. Для записей на другом языке поле
  `emotion` будет `null`, а опция не тарифицируется. Очень короткие реплики (около секунды
  речи) по возможности объединяются с соседними репликами того же говорящего; если
  объединить не с чем — `emotion: null`.
</Note>

### `profanity_filter` — фильтр мата

| Значение | Что маскируется |
| - | - |
| `soft` | Только нецензурная брань (мат) и её производные |
| `hard` или `true` | Мат **и** грубая, оскорбительная лексика |
| `false` | Фильтр выключен (по умолчанию) |

Значения не зависят от регистра; любое другое значение — `422`.

* Эвфемизмы и смягчённые формы («блин», «хрен» и т. п.) не маскируются ни на одном уровне.
* В слове сохраняются первая и последняя буквы, вся середина заменяется на `*`
  (слово из шести букв превратится в `Х****Х`, где `Х` — исходные первая и последняя
  буквы). Схема маски одна для обоих уровней — уровень определяет только, какие слова
  маскируются. Маска **необратима** — исходный текст не сохраняется.
* Маскируется весь текст результата: `text`, `segments[].text`, `words[].word`, а у
  обычной транскрипции (без диаризации) — и `segments[].words`; поле
  `segments[].tokens` очищается. Маска сохраняется и в выгрузках
  (`/download_as_file`, форматы `srt`/`vtt` и др.).
* Фильтр работает и без диаризации — с обычной транскрипцией.

## Пример запроса

<Tabs>
  <Tab title="cURL (async)">
    ```bash theme={null}
    curl -X POST "https://api.palatine.ru/api/v1/transcribe/do_transcribe?diarization_model=palatine_diarize&num_speakers=2" \
      -H "Authorization: Bearer <YOUR_TOKEN>" \
      -F "file=@call.mp3" \
      -F 'roles=["Оператор", "Клиент"]' \
      -F "emotions=true" \
      -F "profanity_filter=soft"
    # → {"status": "scheduled", "task_id": "..."}
    ```
  </Tab>

  <Tab title="Python (async)">
    ```python theme={null}
    import json
    import requests

    resp = requests.post(
        "https://api.palatine.ru/api/v1/transcribe/do_transcribe",
        params={"diarization_model": "palatine_diarize", "num_speakers": 2},
        headers={"Authorization": "Bearer <YOUR_TOKEN>"},
        files={"file": open("call.mp3", "rb")},
        data={
            "roles": json.dumps(
                {"Оператор": "сотрудник колл-центра", "Клиент": "звонит с вопросом"},
                ensure_ascii=False,
            ),
            "emotions": "true",
            "profanity_filter": "soft",
        },
    )
    task_id = resp.json()["task_id"]
    ```
  </Tab>

  <Tab title="cURL (OpenAI-совместимый)">
    ```bash theme={null}
    curl -X POST "https://api.palatine.ru/api/v1/audio/transcriptions" \
      -H "Authorization: Bearer <YOUR_TOKEN>" \
      -F "file=@call.mp3" \
      -F "diarization_model=palatine_diarize" \
      -F "response_format=diarized_json" \
      -F "roles=auto" \
      -F "emotions=true" \
      -F "profanity_filter=true"
    ```
  </Tab>

  <Tab title="cURL (файл по ссылке)">
    ```bash theme={null}
    curl -X POST "https://api.palatine.ru/api/v1/transcribe/do_transcribe_by_url" \
      -H "Authorization: Bearer <YOUR_TOKEN>" \
      -H "Content-Type: application/json" \
      -d '{"source_url": "https://example.com/call.mp3", "profanity_filter": "hard"}'
    # → {"status": "scheduled", "task_id": "..."}
    ```
  </Tab>
</Tabs>

Результат асинхронной задачи — через `GET /api/v1/transcribe/task_status/{task_id}`
(см. [Polling API](/documentation/technical_information/polling_principles)).

<Note>
  На синхронном `/audio/transcriptions` опции `roles` и `emotions` увеличивают время
  ответа. Для длинных записей используйте асинхронный `/transcribe/do_transcribe`.
</Note>

## Формат ответа

В примере ниже `Х***` — условное обозначение замаскированного слова (уровень `soft`).

Поля опций появляются в ответе, **только если опция запрошена** (тогда поле есть
всегда, при неудаче — со значением `null`). Поле `speaker` в сегментах ролью не заменяется.

```json theme={null}
{
  "status": "success",
  "data": {
    "text": "Добрый день, компания «Ромашка». ...",
    "language": "ru",
    "duration": 184.2,
    "segments": [
      {
        "id": 0, "speaker": "SPEAKER_00", "start": 0.4, "end": 3.1,
        "text": "Добрый день, компания «Ромашка».",
        "role": "Оператор",
        "emotion": {
          "label": "neutral", "confidence": 0.91,
          "probs": { "angry": 0.02, "neutral": 0.91, "positive": 0.05, "sad": 0.02 }
        },
        "profanity": false
      },
      {
        "id": 1, "speaker": "SPEAKER_01", "start": 3.5, "end": 9.8,
        "text": "Х***, третий раз звоню, а заказа всё нет!",
        "role": "Клиент",
        "emotion": {
          "label": "angry", "confidence": 0.84,
          "probs": { "angry": 0.84, "neutral": 0.1, "positive": 0.01, "sad": 0.05 }
        },
        "profanity": true
      }
    ],
    "words": [ "..." ],
    "models": { "...": "..." },
    "speaker_roles": { "SPEAKER_00": "Оператор", "SPEAKER_01": "Клиент" },
    "speakers": {
      "SPEAKER_00": {
        "talk_time": 96.3, "turns": 14, "role": "Оператор", "profanity_count": 0,
        "emotions": { "angry": 0.0, "neutral": 0.82, "positive": 0.18, "sad": 0.0 }
      },
      "SPEAKER_01": {
        "talk_time": 71.5, "turns": 13, "role": "Клиент", "profanity_count": 2,
        "emotions": { "angry": 0.41, "neutral": 0.52, "positive": 0.0, "sad": 0.07 }
      }
    },
    "included_processing": ["speaker_attribution", "speaker_roles", "emotions", "profanity_filter"],
    "warnings": []
  }
}
```

| Поле | Значение |
| - | - |
| `segments[].role` | Роль говорящего реплики; `null` — роль не определена |
| `segments[].emotion` | `label` — эмоция, `confidence` — уверенность `[0, 1]`, `probs` — вероятности всех эмоций; `null` — эмоция не определена |
| `segments[].profanity` | `true` — в реплике была замаскирована лексика |
| `speaker_roles` | `SPEAKER_xx` → роль (или `null`); `null` целиком — роли определить не удалось |
| `speakers` | Сводка по говорящим: `talk_time` (секунды речи), `turns` (число реплик) и поля запрошенных опций — `role`, `profanity_count` (число замаскированных слов), `emotions` (доли эмоций по времени речи). Поле есть, только если в записи выделены говорящие |
| `included_processing` | Опции, которые **фактически выполнились** — `speaker_roles`, `emotions`, `profanity_filter` |
| `warnings` | Почему опция не выполнилась (коды ниже) |

<Note>
  Без диаризации (`profanity_filter` на обычной транскрипции) ответ имеет прежний
  формат: текст маскируется, в `included_processing` появляется `profanity_filter`;
  флагов `profanity` у сегментов и сводки `speakers` нет.
</Note>

### Коды в `warnings`

Сбой ролей или эмоций не валит задачу: транскрипция возвращается, соответствующие поля
равны `null`, в `warnings` — причина.

| Код | Значение |
| - | - |
| `speaker_roles_unavailable` | Роли временно не удалось определить |
| `speaker_roles_skipped:single_speaker` | В записи один говорящий — роли не определяются |
| `speaker_roles_skipped:no_speakers` | Говорящие не выделены |
| `speaker_roles_skipped:no_text` | Недостаточно распознанного текста, чтобы различить роли |
| `emotions_unavailable` | Эмоции не удалось определить: временный сбой или все реплики слишком короткие для анализа |
| `emotions_skipped:language` | Язык записи не русский |
| `emotions_skipped:no_speakers` | Говорящие не выделены |

## Тарификация

Каждая опция тарифицируется **отдельно и поминутно** — дополнительно к транскрипции и
диаризации:

| Опция | Цена |
| - | - |
| `roles` | 0,048 ₽ / мин |
| `emotions` | 0,048 ₽ / мин |
| `profanity_filter` | 0,0096 ₽ / мин |

* Сумма по каждой опции округляется **вверх до копейки**, минимум — 0,01 ₽ за опцию.
  Например, 15 с записи: фильтр мата — 0,01 ₽, роли — 0,02 ₽; 61 с записи: роли — 0,05 ₽.
* Списывается **только выполнившаяся** опция (та, что есть в `included_processing`).
  Если роли или эмоции не определились (см. `warnings`), за них ничего не списывается.
* Перед запуском обработки проверяется, что баланса хватает на всё запрошенное, включая опции.

## Ошибки

| Код | Когда |
| - | - |
| `403` с `"code": "feature_disabled"` | Опция не подключена для вашего аккаунта. Запрос отклоняется до создания задачи, без списаний. Чтобы подключить — обратитесь в поддержку |
| `422` | Неверное значение опции; `roles`/`emotions` без `diarization_model`; `roles`/`emotions` на `/do_transcribe_by_url` |

Проверки выполняются по порядку, до создания задачи и без списаний:

1. Значения опций (и `roles`/`emotions` на `/do_transcribe_by_url`) — иначе `422`.
2. Подключена ли опция для аккаунта — иначе `403 feature_disabled`.
3. Для `roles`/`emotions` указан `diarization_model` — иначе `422`.

Поэтому если опция не подключена, запрос с `roles`/`emotions` без диаризации вернёт `403`, а не `422`.

```json theme={null}
{ "detail": "...", "code": "feature_disabled" }
```

Значение `false` у опции (`emotions=false`, `profanity_filter=false`) равносильно её
отсутствию и ошибок не вызывает.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.