NexaraNexara

Спикеры и роли

Как разделить аудио по говорящим и разметить их роли.

Диаризация разделяет аудио по говорящим: API определяет, какой фрагмент речи кому принадлежит, и присваивает каждому сегменту метку говорящего (speaker_0, speaker_1) — или понятную роль (Клиент, Агент). Концептуально режим описан на странице диаризация.

Разделение по говорящим

Передайте task=diarize в запросе на POST /v1/audio/transcriptions. Замените nx-XXXXXXXXXXXXXXXXXXXXXXXX на свой API-ключ, а meeting.mp3 — на путь к вашему файлу.

curl https://api.nexara.ru/v1/audio/transcriptions \
  -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \
  -F "file=@meeting.mp3" \
  -F "model=whisper-1" \
  -F "task=diarize"
import requests

with open("meeting.mp3", "rb") as audio_file:
    response = requests.post(
        "https://api.nexara.ru/v1/audio/transcriptions",
        headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"},
        files={"file": audio_file},
        data={
            "model": "whisper-1",
            "task": "diarize",
        },
    )

print(response.json())

Node.js 18+:

import fs from "fs";

const form = new FormData();
form.append("file", new Blob([fs.readFileSync("meeting.mp3")]), "meeting.mp3");
form.append("model", "whisper-1");
form.append("task", "diarize");

const response = await fetch(
  "https://api.nexara.ru/v1/audio/transcriptions",
  {
    method: "POST",
    headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" },
    body: form,
  },
);

console.log(await response.json());
package main

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)

func main() {
	file, _ := os.Open("meeting.mp3")
	defer file.Close()

	var buf bytes.Buffer
	writer := multipart.NewWriter(&buf)
	part, _ := writer.CreateFormFile("file", "meeting.mp3")
	io.Copy(part, file)
	writer.WriteField("model", "whisper-1")
	writer.WriteField("task", "diarize")
	writer.Close()

	req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf)
	req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX")
	req.Header.Set("Content-Type", writer.FormDataContentType())

	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
require "net/http"
require "uri"
require "json"

uri = URI("https://api.nexara.ru/v1/audio/transcriptions")

request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"
request.set_form(
  [
    ["file", File.open("meeting.mp3")],
    ["model", "whisper-1"],
    ["task", "diarize"],
  ],
  "multipart/form-data",
)

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

puts response.body

В ответе каждый сегмент содержит метку говорящего в поле speaker:

{
  "task": "diarize",
  "language": "ru",
  "duration": 12.4,
  "text": "Здравствуйте! Чем могу помочь? Хочу оформить возврат.",
  "segments": [
    {
      "start": 0.0,
      "end": 2.1,
      "text": "Здравствуйте! Чем могу помочь?",
      "speaker": "speaker_0"
    },
    {
      "start": 2.5,
      "end": 5.0,
      "text": "Хочу оформить возврат.",
      "speaker": "speaker_1"
    }
  ]
}

Метки говорящих сохраняются во всех форматах ответа. Например, при response_format=text:

Speaker 0: Здравствуйте! Чем могу помочь?
Speaker 1: Хочу оформить возврат.

Минимальная длительность аудио для диаризации — 3 секунды. Более короткие записи выдают ошибку 400. Если вы заранее знаете число говорящих, передайте num_speakers — это повысит качество разделения.

Разметка ролей

По умолчанию говорящие обозначаются безликими метками speaker_0, speaker_1. Разметка ролей заменяет их на понятные роли — например, Клиент и Агент. Она включается параметром roles и доступна только вместе с диаризацией (task=diarize). Поддерживаются три режима:

Автоматический

roles=auto — модель сама определяет и назначает подходящие роли на основе содержания разговора.

Список ролей

JSON-массив строк, например ["Клиент", "Агент"] — вы отправляете набор ролей, а модель распределяет их между говорящими.

Роли с описаниями

JSON-объект {"Клиент": "Клиент, который обратился в поддержку", "Агент": "Оператор поддержки"} — фиксированные роли с описаниями, которые помогают модели точнее их распознать.

Достаточно добавить поле roles к запросу на диаризацию. Значение — либо auto, либо JSON-строка со списком или объектом ролей.

curl https://api.nexara.ru/v1/audio/transcriptions \
  -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \
  -F "file=@call.mp3" \
  -F "model=whisper-1" \
  -F "task=diarize" \
  -F 'roles=["Клиент", "Агент"]'
import requests

with open("call.mp3", "rb") as audio_file:
    response = requests.post(
        "https://api.nexara.ru/v1/audio/transcriptions",
        headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"},
        files={"file": audio_file},
        data={
            "model": "whisper-1",
            "task": "diarize",
            "roles": '["Клиент", "Агент"]',
        },
    )

print(response.json())
import fs from "fs";

const form = new FormData();
form.append("file", new Blob([fs.readFileSync("call.mp3")]), "call.mp3");
form.append("model", "whisper-1");
form.append("task", "diarize");
form.append("roles", JSON.stringify(["Клиент", "Агент"]));

const response = await fetch(
  "https://api.nexara.ru/v1/audio/transcriptions",
  {
    method: "POST",
    headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" },
    body: form,
  },
);

console.log(await response.json());
package main

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)

func main() {
	file, _ := os.Open("call.mp3")
	defer file.Close()

	var buf bytes.Buffer
	writer := multipart.NewWriter(&buf)
	part, _ := writer.CreateFormFile("file", "call.mp3")
	io.Copy(part, file)
	writer.WriteField("model", "whisper-1")
	writer.WriteField("task", "diarize")
	writer.WriteField("roles", `["Клиент", "Агент"]`)
	writer.Close()

	req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf)
	req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX")
	req.Header.Set("Content-Type", writer.FormDataContentType())

	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
require "net/http"
require "uri"
require "json"

uri = URI("https://api.nexara.ru/v1/audio/transcriptions")

request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"
request.set_form(
  [
    ["file", File.open("call.mp3")],
    ["model", "whisper-1"],
    ["task", "diarize"],
    ["roles", ["Клиент", "Агент"].to_json],
  ],
  "multipart/form-data",
)

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

puts response.body

В результате метки speaker_N заменяются на назначенные роли:

{
  "task": "diarize",
  "segments": [
    {
      "start": 0.0,
      "end": 2.1,
      "text": "Здравствуйте! Чем могу помочь?",
      "speaker": "Агент"
    },
    {
      "start": 2.5,
      "end": 5.0,
      "text": "Хочу оформить возврат.",
      "speaker": "Клиент"
    }
  ]
}

Если модель определила больше говорящих, чем было передано в списке, остальные спикеры будут помечены как неизвестные. Например, если в записи говорило 4 человека, а в списке передано только 2, остальные будут помечены как unknown_1 и unknown_2.

Ограничения

  • В одном запросе можно задать не более 10 ролей.
  • Каждое название роли — непустая строка длиной не более 64 символов, роли не должны повторяться.
  • Описание роли (в режиме с описаниями) не должно превышать 500 символов.

Разметка ролей тарифицируется дополнительно к стоимости диаризации. Если разметку не удалось применить (например, в записи не оказалось говорящих), надбавка не списывается, а в ответе остаются исходные метки speaker_N. Стоимость услуги смотрите на странице тарифов.

Что учитывать

  • Хранение ключа. Не вставляйте API-ключ прямо в код. Храните его в переменной окружения (например, export NEXARA_API_KEY="nx-...") и ссылайтесь на неё. Подробнее — на странице аутентификации.

On this page