ZvenoAI
Руководства

Голосовой диалог (Realtime)

Как подключиться к realtime-модели по WebSocket, отправить запрос и получить текст или аудио в ответ.

Realtime-модель держит открытое соединение и отвечает по мере того, как вы говорите или пишете. Это основа для голосовых помощников, синхронного перевода и звонков с ботом.

Как это устроено

  1. Клиент открывает WebSocket на wss://api.zveno.ai/v1/realtime и называет модель в адресе.
  2. Клиент настраивает сессию событием session.update: что вернуть — текст или аудио.
  3. Клиент отправляет сообщение и просит ответ событием response.create.
  4. Сервер присылает ответ частями: response.output_text.delta для текста, response.output_audio.delta для аудио.

ZvenoAI говорит на протоколе OpenAI Realtime. Клиент, написанный для него, работает без правок: поменяйте адрес, ключ и название модели.

Из браузера подключиться нельзя: ключ передаётся в заголовке Authorization, а браузерный WebSocket заголовки задавать не умеет. Открывайте соединение на своём сервере или в приложении.

Подключение

ПараметрЗначение
Адресwss://api.zveno.ai/v1/realtime?model=<модель>
Подпротоколrealtime
Авторизациязаголовок Authorization: Bearer <ZVENOAI_API_KEY>
Формат аудиоaudio/pcm: PCM16, моно, 24 кГц

Почта аккаунта должна быть подтверждена. Какие модели работают в этом режиме, смотрите в каталоге моделей.

Быстрый старт

Пример отправляет текстовый вопрос и печатает ответ по мере поступления.

import asyncio
import json

import websockets

URL = "wss://api.zveno.ai/v1/realtime?model=qwen/qwen-audio-3.1-realtime-plus"


async def main():
    async with websockets.connect(
        URL,
        subprotocols=["realtime"],
        additional_headers={"Authorization": "Bearer <ZVENOAI_API_KEY>"},
    ) as ws:
        await ws.send(json.dumps({
            "type": "session.update",
            "session": {"type": "realtime", "output_modalities": ["text"]},
        }))
        await ws.send(json.dumps({
            "type": "conversation.item.create",
            "item": {
                "type": "message",
                "role": "user",
                "content": [{"type": "input_text", "text": "Привет! Расскажи о себе."}],
            },
        }))
        await ws.send(json.dumps({"type": "response.create"}))

        async for message in ws:
            event = json.loads(message)
            if event["type"] == "response.output_text.delta":
                print(event["delta"], end="", flush=True)
            if event["type"] in ("response.done", "error"):
                break


asyncio.run(main())
import WebSocket from 'ws'

const url = 'wss://api.zveno.ai/v1/realtime?model=qwen/qwen-audio-3.1-realtime-plus'
const ws = new WebSocket(url, 'realtime', {
  headers: { Authorization: 'Bearer <ZVENOAI_API_KEY>' },
})

ws.on('open', () => {
  ws.send(JSON.stringify({
    type: 'session.update',
    session: { type: 'realtime', output_modalities: ['text'] },
  }))
  ws.send(JSON.stringify({
    type: 'conversation.item.create',
    item: {
      type: 'message',
      role: 'user',
      content: [{ type: 'input_text', text: 'Привет! Расскажи о себе.' }],
    },
  }))
  ws.send(JSON.stringify({ type: 'response.create' }))
})

ws.on('message', (data) => {
  const event = JSON.parse(data.toString())
  if (event.type === 'response.output_text.delta') process.stdout.write(event.delta)
  if (event.type === 'response.done' || event.type === 'error') ws.close()
})

Настройка сессии

Событие session.update задаёт поведение модели на всё соединение. ZvenoAI читает эти поля:

ПолеЧто задаёт
instructionsСистемная инструкция для модели
output_modalitiesЧто вернуть: ["text"] или ["audio"]
toolsИнструменты, которые модель может вызвать
audio.input.formatФормат входящего аудио
audio.input.transcription.languageЯзык распознавания речи
audio.input.turn_detectionКак модель понимает, что вы договорили
audio.output.formatФормат аудио в ответе
audio.output.voiceГолос ответа

Сессия с аудио в ответе:

{
  "type": "session.update",
  "session": {
    "type": "realtime",
    "output_modalities": ["audio"],
    "audio": {
      "input": { "format": { "type": "audio/pcm", "rate": 24000 } },
      "output": { "format": { "type": "audio/pcm", "rate": 24000 } }
    }
  }
}

Формат аудио один: audio/pcm, PCM16 little-endian, моно, 24 кГц. Поле rate сервер не читает и частоту не проверяет: аудио с другой частотой он примет, но исказит. Форматы pcmu и pcma сервер отклоняет событием error.

Голос ответа задаётся в audio.output.voice. Список голосов смотрите в документации провайдера модели.

Отправка аудио

Аудио с микрофона уходит частями в событиях input_audio_buffer.append. Поле audio — base64 от PCM16 little-endian, моно, 24 кГц.

{ "type": "input_audio_buffer.append", "audio": "<base64 PCM16>" }

Кто решает, что реплика закончена, задаёт поле audio.input.turn_detection:

ЗначениеПоведение
nullВручную: отправьте input_audio_buffer.commit и response.create
{ "type": "semantic_vad" }Модель сама определяет конец реплики и отвечает

Событие input_audio_buffer.clear сбрасывает накопленное аудио.

Инструменты

Передайте функции в session.tools в том же виде, что в OpenAI Realtime:

{
  "type": "session.update",
  "session": {
    "type": "realtime",
    "tools": [
      {
        "type": "function",
        "name": "get_weather",
        "description": "Погода в городе",
        "parameters": {
          "type": "object",
          "properties": { "city": { "type": "string" } },
          "required": ["city"]
        }
      }
    ]
  }
}

События ответа

СобытиеКогда приходит
response.createdМодель начала отвечать
conversation.item.addedСообщение добавлено в диалог
response.output_text.deltaОчередная часть текста, поле delta
response.output_text.doneТекст ответа закончен
response.output_audio.deltaОчередная часть аудио в base64, поле delta
response.output_audio_transcript.deltaОчередная часть расшифровки аудиоответа
response.doneОтвет закончен целиком
errorОшибка, см. раздел ниже

Тарификация

  • Деньги списываются с баланса за каждый ответ модели отдельно: от response.created до response.done.
  • Если средств не хватает, сервер отменяет ответ и закрывает соединение с кодом 1008.
  • Подписка на realtime-модели не действует: они работают только с оплатой по факту.
  • Текстовые и аудиотокены стоят по-разному, аудио дороже. Цены модели — на её странице в каталоге.

Обработка ошибок

До установки соединения сервер отвечает обычной HTTP-ошибкой в формате JSON:

КодПричина
400Нет параметра model, модель не realtime или запрос не WebSocket
401Нет ключа или ключ неверный
402Не хватает средств на балансе
404Модель не найдена
502Провайдер модели не ответил
503Сервис занят или перезапускается, повторите позже

После установки соединения сервер присылает событие error и закрывает соединение с кодом:

Код закрытияПричина
1008Закончились средства, ключ отозван или исчерпан бюджет ключа
1011Сбой на стороне провайдера модели
1001Сервер перезапускается, откройте соединение заново

Формат ошибок и общие правила повторов описаны в руководстве «Обработка ошибок».

Решение типичных проблем

Что дальше

On this page