NexaraNexara

Телефонные звонки

Узнайте, как обрабатывать телефонные звонки через Nexara API.

Телефонные звонки — самый частый сценарий для колл-центров и отделов продаж. Из одной записи можно получить сразу всё: расшифровку, разделение на оператора и клиента, эмоции говорящих и готовую аналитику звонка. Ниже разберём блок за блоком, а в конце соберём всё в один запрос и поделимся лайфхаками.

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

Для звонков включите диаризацию (task=diarize) и передайте num_speakers=2 — в разговоре обычно двое.

from nexara import Nexara

client = Nexara()  # ключ из переменной окружения NEXARA_API_KEY

result = client.transcriptions.create(
    file="call.mp3",
    task="diarize",
    num_speakers=2,
)
import { Nexara } from "nexara-sdk";

const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY

const result = await client.transcriptions.create({
  file: "call.mp3",
  task: "diarize",
  num_speakers: 2,
});
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 "num_speakers=2"

Если телефония умеет писать звонки в двухканальном (стерео) формате, где каждый участник на своём канале, — используйте именно его. В связке с num_speakers=2 это даёт самое точное разделение.

Роли вместо speaker_0 и speaker_1

Чтобы в расшифровке сразу были понятные роли, а не безликие speaker_0/speaker_1, передайте roles со своими метками. Для аналитики это гораздо удобнее.

result = client.transcriptions.create(
    file="call.mp3",
    task="diarize",
    num_speakers=2,
    roles=["Оператор", "Клиент"],
)
const result = await client.transcriptions.create({
  file: "call.mp3",
  task: "diarize",
  num_speakers: 2,
  roles: ["Оператор", "Клиент"],
});
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 "num_speakers=2" \
  -F 'roles=["Оператор", "Клиент"]'

В сегментах вместо speaker_N появятся Оператор и Клиент. Подробнее — на странице спикеры и роли.

Русские звонки

Если разговор на русском языке, используйте модель nexara-ru — она заточена под русскую речь и даёт более точную расшифровку, чем универсальная whisper-1.

result = client.transcriptions.create(
    file="call.mp3",
    model="nexara-ru",
    task="diarize",
    num_speakers=2,
)
const result = await client.transcriptions.create({
  file: "call.mp3",
  model: "nexara-ru",
  task: "diarize",
  num_speakers: 2,
});
curl https://api.nexara.ru/v1/audio/transcriptions \
  -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \
  -F "file=@call.mp3" \
  -F "model=nexara-ru" \
  -F "task=diarize" \
  -F "num_speakers=2"

Фильтрация мата

В звонках нередко проскакивает нецензурная лексика. Чтобы замаскировать её в расшифровке (например, для отчётов и выгрузок), добавьте profanity_filter=true.

result = client.transcriptions.create(
    file="call.mp3",
    model="nexara-ru",
    task="diarize",
    num_speakers=2,
    profanity_filter=True,
)
const result = await client.transcriptions.create({
  file: "call.mp3",
  model: "nexara-ru",
  task: "diarize",
  num_speakers: 2,
  profanity_filter: true,
});
curl https://api.nexara.ru/v1/audio/transcriptions \
  -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \
  -F "file=@call.mp3" \
  -F "model=nexara-ru" \
  -F "task=diarize" \
  -F "num_speakers=2" \
  -F "profanity_filter=true"

Эмоции в звонке

Чтобы видеть не только что сказали, но и как, добавьте emotions=true. К каждому сегменту диаризации добавится объект emotion с меткой (angry, sad, neutral, positive) и уверенностью модели — сразу видно, на какой минуте клиент начал злиться.

Параметр работает только с моделью nexara-ru и диаризацией — то есть ровно в той конфигурации, которую мы собрали выше.

result = client.transcriptions.create(
    file="call.mp3",
    model="nexara-ru",
    task="diarize",
    num_speakers=2,
    roles=["Оператор", "Клиент"],
    emotions=True,
)

for segment in result.segments:
    if segment.emotion:
        print(f"{segment.speaker} [{segment.emotion.label}]: {segment.text}")
    else:
        print(f"{segment.speaker}: {segment.text}")
const result = await client.transcriptions.create({
  file: "call.mp3",
  model: "nexara-ru",
  task: "diarize",
  num_speakers: 2,
  roles: ["Оператор", "Клиент"],
  emotions: true,
});

for (const segment of result.segments) {
  if (segment.emotion) {
    console.log(`${segment.speaker} [${segment.emotion.label}]: ${segment.text}`);
  } else {
    console.log(`${segment.speaker}: ${segment.text}`);
  }
}
curl https://api.nexara.ru/v1/audio/transcriptions \
  -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \
  -F "file=@call.mp3" \
  -F "model=nexara-ru" \
  -F "task=diarize" \
  -F "num_speakers=2" \
  -F 'roles=["Оператор", "Клиент"]' \
  -F "emotions=true"

В сегментах появится эмоция рядом с ролью:

{
  "segments": [
    {
      "start": 0.0,
      "end": 2.1,
      "text": "Служба поддержки, здравствуйте.",
      "speaker": "Оператор",
      "emotion": { "label": "neutral", "confidence": 0.88 }
    },
    {
      "start": 2.5,
      "end": 6.4,
      "text": "Я третий раз звоню по одному и тому же вопросу!",
      "speaker": "Клиент",
      "emotion": { "label": "angry", "confidence": 0.91 }
    }
  ]
}

Эмоция приходит не для каждого сегмента — если модель не смогла оценить фрагмент, поля emotion в нём не будет, поэтому проверяйте его наличие. Подробнее — на странице распознавание эмоций.

Эмоции усиливают аналитику ниже. Если в том же запросе передан prompt, языковая модель получает расшифровку уже с метками — реплики приходят к ней в виде Клиент [angry]: …. Поэтому и sentiment в карточке звонка, и оценка работы оператора опираются не только на слова, но и на тон голоса: вежливая по тексту фраза, сказанная раздражённо, больше не выглядит нейтральной.

Аналитика звонка

Самое ценное — сразу получить из звонка структурированную аналитику. Для этого добавьте prompt (и, для строгой структуры, json_schema). Модель получает расшифровку с ролями — а если включены эмоции, то и с ними, — поэтому анализ учитывает, кто что сказал и с какой интонацией. Подробно про режимы — на странице LLM-анализ. Ниже — три самых частых сценария.

Карточка звонка

Готовая карточка для записи в CRM: тема, тональность, резюме, решён ли вопрос и список задач.

{
  "type": "object",
  "properties": {
    "topic": { "type": "string", "description": "Причина обращения" },
    "sentiment": {
      "type": "string",
      "enum": ["положительная", "нейтральная", "отрицательная"]
    },
    "summary": { "type": "string", "description": "Резюме разговора" },
    "resolved": { "type": "boolean", "description": "Решён ли вопрос клиента" },
    "action_items": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["topic", "sentiment", "summary", "resolved", "action_items"]
}

Prompt: Проанализируй телефонный разговор в поддержке и заполни карточку звонка.

Оценка качества (QA)

Проверка работы оператора по чек-листу для контроля качества.

{
  "type": "object",
  "properties": {
    "greeting": { "type": "boolean", "description": "Оператор поздоровался и представился" },
    "polite": { "type": "boolean", "description": "Оператор был вежлив" },
    "resolved": { "type": "boolean", "description": "Вопрос клиента решён" },
    "score": { "type": "integer", "description": "Оценка звонка от 1 до 5" },
    "comment": { "type": "string", "description": "Короткий комментарий к оценке" }
  },
  "required": ["greeting", "polite", "resolved", "score", "comment"]
}

Prompt: Оцени работу оператора в этом звонке по чек-листу.

Извлечение данных в CRM

Вытащить из разговора конкретные поля для автоматического заполнения карточки сделки.

{
  "type": "object",
  "properties": {
    "client_name": { "type": "string" },
    "phone": { "type": "string" },
    "product": { "type": "string", "description": "Обсуждаемый товар или услуга" },
    "amount": { "type": "number", "description": "Сумма сделки, если названа" },
    "next_step": { "type": "string", "description": "О чём договорились" }
  },
  "required": ["client_name", "product"]
}

Prompt: Извлеки данные о клиенте и сделке из разговора.

Собираем всё вместе

Один запрос для русского звонка: распознавание nexara-ru, диаризация с num_speakers=2, роли Оператор/Клиент и карточка звонка через prompt + json_schema.

В Python SDK роли и схема передаются обычными списками и словарями — без ручной сериализации в JSON.

from nexara import Nexara

client = Nexara()  # ключ из переменной окружения NEXARA_API_KEY

result = client.transcriptions.create(
    file="call.mp3",
    model="nexara-ru",
    task="diarize",
    num_speakers=2,
    roles=["Оператор", "Клиент"],
    prompt="Проанализируй телефонный разговор в поддержке и заполни карточку звонка.",
    json_schema={
        "type": "object",
        "properties": {
            "topic": {"type": "string"},
            "sentiment": {
                "type": "string",
                "enum": ["положительная", "нейтральная", "отрицательная"],
            },
            "summary": {"type": "string"},
            "resolved": {"type": "boolean"},
            "action_items": {"type": "array", "items": {"type": "string"}},
        },
        "required": ["topic", "sentiment", "summary", "resolved", "action_items"],
    },
)

print(result.llm_output)  # готовая карточка звонка (dict)

В TypeScript SDK роли и схема передаются обычными массивами и объектами — без ручной сериализации в JSON.

import { Nexara } from "nexara-sdk";

const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY

const result = await client.transcriptions.create({
  file: "call.mp3",
  model: "nexara-ru",
  task: "diarize",
  num_speakers: 2,
  roles: ["Оператор", "Клиент"],
  prompt: "Проанализируй телефонный разговор в поддержке и заполни карточку звонка.",
  json_schema: {
    type: "object",
    properties: {
      topic: { type: "string" },
      sentiment: {
        type: "string",
        enum: ["положительная", "нейтральная", "отрицательная"],
      },
      summary: { type: "string" },
      resolved: { type: "boolean" },
      action_items: { type: "array", items: { type: "string" } },
    },
    required: ["topic", "sentiment", "summary", "resolved", "action_items"],
  },
});

console.log(result.llm_output); // готовая карточка звонка (объект)
curl https://api.nexara.ru/v1/audio/transcriptions \
  -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \
  -F "file=@call.mp3" \
  -F "model=nexara-ru" \
  -F "task=diarize" \
  -F "num_speakers=2" \
  -F 'roles=["Оператор", "Клиент"]' \
  -F "prompt=Проанализируй телефонный разговор в поддержке и заполни карточку звонка." \
  -F 'json_schema={"type":"object","properties":{"topic":{"type":"string"},"sentiment":{"type":"string","enum":["положительная","нейтральная","отрицательная"]},"summary":{"type":"string"},"resolved":{"type":"boolean"},"action_items":{"type":"array","items":{"type":"string"}}},"required":["topic","sentiment","summary","resolved","action_items"]}'
import json
import requests

schema = {
    "type": "object",
    "properties": {
        "topic": {"type": "string"},
        "sentiment": {
            "type": "string",
            "enum": ["положительная", "нейтральная", "отрицательная"],
        },
        "summary": {"type": "string"},
        "resolved": {"type": "boolean"},
        "action_items": {"type": "array", "items": {"type": "string"}},
    },
    "required": ["topic", "sentiment", "summary", "resolved", "action_items"],
}

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": "nexara-ru",
            "task": "diarize",
            "num_speakers": "2",
            "roles": json.dumps(["Оператор", "Клиент"]),
            "prompt": "Проанализируй телефонный разговор в поддержке и заполни карточку звонка.",
            "json_schema": json.dumps(schema),
        },
    )

result = response.json()
print(result["llm_output"])

Node.js 18+:

import fs from "fs";

const schema = {
  type: "object",
  properties: {
    topic: { type: "string" },
    sentiment: {
      type: "string",
      enum: ["положительная", "нейтральная", "отрицательная"],
    },
    summary: { type: "string" },
    resolved: { type: "boolean" },
    action_items: { type: "array", items: { type: "string" } },
  },
  required: ["topic", "sentiment", "summary", "resolved", "action_items"],
};

const form = new FormData();
form.append("file", new Blob([fs.readFileSync("call.mp3")]), "call.mp3");
form.append("model", "nexara-ru");
form.append("task", "diarize");
form.append("num_speakers", "2");
form.append("roles", JSON.stringify(["Оператор", "Клиент"]));
form.append("prompt", "Проанализируй телефонный разговор в поддержке и заполни карточку звонка.");
form.append("json_schema", JSON.stringify(schema));

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

const result = await response.json();
console.log(result.llm_output);
package main

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

func main() {
	schema := `{"type":"object","properties":{"topic":{"type":"string"},"sentiment":{"type":"string","enum":["положительная","нейтральная","отрицательная"]},"summary":{"type":"string"},"resolved":{"type":"boolean"},"action_items":{"type":"array","items":{"type":"string"}}},"required":["topic","sentiment","summary","resolved","action_items"]}`

	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", "nexara-ru")
	writer.WriteField("task", "diarize")
	writer.WriteField("num_speakers", "2")
	writer.WriteField("roles", `["Оператор", "Клиент"]`)
	writer.WriteField("prompt", "Проанализируй телефонный разговор в поддержке и заполни карточку звонка.")
	writer.WriteField("json_schema", schema)
	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"

schema = {
  "type" => "object",
  "properties" => {
    "topic" => { "type" => "string" },
    "sentiment" => {
      "type" => "string",
      "enum" => ["положительная", "нейтральная", "отрицательная"],
    },
    "summary" => { "type" => "string" },
    "resolved" => { "type" => "boolean" },
    "action_items" => { "type" => "array", "items" => { "type" => "string" } },
  },
  "required" => ["topic", "sentiment", "summary", "resolved", "action_items"],
}

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", "nexara-ru"],
    ["task", "diarize"],
    ["num_speakers", "2"],
    ["roles", ["Оператор", "Клиент"].to_json],
    ["prompt", "Проанализируй телефонный разговор в поддержке и заполни карточку звонка."],
    ["json_schema", schema.to_json],
  ],
  "multipart/form-data",
)

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

puts JSON.parse(response.body)["llm_output"]

В ответе придёт расшифровка с ролями и готовая карточка звонка:

{
  "transcription": {
    "task": "diarize",
    "segments": [
      { "start": 0.0, "end": 2.1, "text": "Служба поддержки, здравствуйте.", "speaker": "Оператор" },
      { "start": 2.5, "end": 5.0, "text": "Здравствуйте, хочу оформить возврат.", "speaker": "Клиент" }
    ]
  },
  "llm_output": {
    "topic": "Возврат товара",
    "sentiment": "нейтральная",
    "summary": "Клиент попросил оформить возврат, оператор объяснил условия и создал заявку.",
    "resolved": true,
    "action_items": ["Отправить клиенту инструкцию по возврату"]
  }
}

Лайфхаки

  • Двухканальная запись. Если телефония поддерживает раздельную запись каналов, включите её — вместе с num_speakers=2 это даёт максимально точное разделение.
  • Точные роли. В сложных звонках передавайте роли с описаниями ({"Оператор": "сотрудник поддержки", "Клиент": "позвонивший"}) — так модель распределяет их точнее.
  • Русская речь — nexara-ru. Для звонков на русском эта модель точнее универсальной. Эмоциональный анализ поддерживается только на этой модели.
  • Передавайте большие файлы по ссылке. Удобно отдавать запись по URL (например, pre-signed ссылке на хранилище), а не загружать файлом.

Диаризация, разметка ролей, распознавание эмоций и LLM-анализ тарифицируются дополнительно к стоимости распознавания. Итоговую стоимость смотрите на странице тарифов.

On this page