Freedom Speech Realtime API

Обновлено 2026-11-02

Голосовой агент «речь в речь», совместимый по протоколу с OpenAI Realtime API. Клиент, который уже работает с OpenAI Realtime, подключается к нам сменой адреса и ключа.

Обзор

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

Для старта нужны три вещи:

Параметр Значение
Адрес (базовый URL для SDK) wss://realtime.freedomspeech.kz/v1
Модель local
Ключ выдаём лично, передаётся как Authorization: Bearer <ключ>

Пример на стоковом SDK openai для Python (версия 2.x, GA-диалект). Именно таким клиентом мы проверяли сервис: звук PCM16 24 кГц моно, кусками по 20 мс, инструменты исполняет клиент.

import asyncio, base64, json
from openai import AsyncOpenAI

TOOLS = [{
    "type": "function", "name": "get_order",
    "description": "Статус заказа по номеру",
    "parameters": {"type": "object",
                   "properties": {"order_id": {"type": "string"}},
                   "required": ["order_id"]},
}]

async def main(pcm_chunks):  # pcm_chunks: куски PCM16 24 кГц по 20 мс (960 байт)
    client = AsyncOpenAI(api_key="<КЛЮЧ>",
                         websocket_base_url="wss://realtime.freedomspeech.kz/v1")
    async with client.realtime.connect(model="local") as conn:
        await conn.session.update(session={
            "type": "realtime",
            "instructions": "Ты оператор службы доставки. Отвечай коротко, по-русски.",
            "tools": TOOLS,
            "audio": {"input":  {"format": {"type": "audio/pcm", "rate": 24000}},
                      "output": {"format": {"type": "audio/pcm", "rate": 24000}}},
        })

        async def reader():
            async for ev in conn:
                if ev.type == "response.output_audio.delta":
                    play(base64.b64decode(ev.delta))          # ваш плеер
                elif ev.type == "conversation.item.input_audio_transcription.completed":
                    print("клиент:", ev.transcript)
                elif ev.type == "response.output_audio_transcript.done":
                    print("агент:", ev.transcript)
                elif ev.type == "response.function_call_arguments.done":
                    result = call_my_backend(ev.name, json.loads(ev.arguments or "{}"))
                    await conn.conversation.item.create(item={
                        "type": "function_call_output", "call_id": ev.call_id,
                        "output": json.dumps(result, ensure_ascii=False)})
                    await conn.response.create()
                elif ev.type == "error":
                    print("ошибка:", ev.error)

        task = asyncio.create_task(reader())
        for chunk in pcm_chunks:                      # звук идёт постоянно,
            await conn.input_audio_buffer.append(       # в том числе тишина
                audio=base64.b64encode(chunk).decode())
            await asyncio.sleep(0.02)
        task.cancel()

Конец реплики определяет сервер, commit вызывать не нужно. Пока идёт разговор, стримьте звук без перерывов, в том числе тишину, как с микрофона или телефонной линии.

Подключение и авторизация

Полный адрес WebSocket: wss://realtime.freedomspeech.kz/v1/realtime?model=local. Список доступных моделей: GET https://realtime.freedomspeech.kz/v1/models с тем же ключом.

Ключ передаётся одним из двух способов, как у OpenAI:

Из браузера напрямую не подключиться. Сервер отвечает 403 на любой чужой заголовок Origin. Ставьте ретранслятор на своём бэкенде: он добавляет ключ и не передаёт Origin. Так ключ и не попадёт в браузер.

Диалект. Поддерживаются оба: beta (openai < 2.0, Azure) и GA (openai ≥ 2.0, livekit-plugins-openai ≥ 1.6).

Ошибки подключения. Это обычные HTTP-ответы до установки WebSocket:

Ситуация HTTP Тело
Нет ключа или неверный ключ 401 JSON, code: unauthorized
У ключа уже открыто 10 сессий 429 не JSON
Сервис перегружен или перезапускается 503 JSON, code: session_limit
Чужой Origin (браузер) 403 —

JSON-ошибки имеют вид {"error": {"type", "code", "message"}}.

Закрытие соединения. Ошибки в событиях соединение не рвут, они приходят событием error. Сам сервер закрывает WebSocket только в трёх случаях:

Настройка сессии (session.update)

Первый session.update запускает разговор, поэтому шлите его сразу после session.created. Повторные session.update работают по ходу разговора.

Поле Что происходит
instructions Работает. Жёсткий предел около 60 тыс. знаков (40 тыс. приняты, 70 тыс. отклонены ошибкой instructions_too_long).
tools Работает. Тип function с JSON Schema параметров, как у OpenAI. См. «Вызов инструментов».
voice (GA: audio.output.voice) Работает: alua (русский и казахский, по умолчанию), ramil (только русский). Имена OpenAI (alloy и др.) ошибки не дают, а дают голос по умолчанию.
Аудиоформаты (beta: input_audio_format, output_audio_format; GA: audio.input.format, audio.output.format) Работает, см. таблицу ниже.
turn_detection Принимается, но игнорируется. Конец реплики всегда определяет сервер, выключить это нельзя.
input_audio_transcription Принимается, но игнорируется. Расшифровка речи клиента приходит всегда, модель выбрать нельзя.
temperature, max_response_output_tokens / max_output_tokens, tool_choice, modalities / output_modalities Проверяются и возвращаются в session.updated, но на работу не влияют. Ответ всегда голос + текстовая расшифровка.
GA noise_reduction, speed, prompt, include, truncation, tracing Игнорируются.
GA type Допустимо только realtime, иначе invalid_value.

Аудиоформаты (вход и выход задаются независимо):

Формат beta / GA Частота Для чего
pcm16 / audio/pcm 24 кГц (по умолчанию), также 8, 16, 48 кГц веб, мобильные приложения
g711_ulaw / audio/pcmu 8 кГц телефония
g711_alaw / audio/pcma 8 кГц телефония

События

Названия событий и их поля совпадают с OpenAI Realtime. Неизвестный type от клиента даёт error с code: unknown_event, соединение при этом не рвётся.

Клиент → сервер

Событие Как работает
session.update см. предыдущий раздел
input_audio_buffer.append звук собеседника, сразу идёт в распознавание
input_audio_buffer.commit не обязателен; принудительно заканчивает текущую реплику (серверное определение конца реплики при этом продолжает работать)
input_audio_buffer.clear очищает буфер
conversation.item.create message (текст от user, assistant, system), function_call_output, function_call. Добавляет контекст и не запускает ответ. Звук внутри input_audio не используется: берётся только поле transcript
conversation.item.truncate обрезает ответ агента в истории по audio_end_ms
conversation.item.delete удаляет элемент из истории
response.create запускает ответ. Учитываются только instructions, metadata и conversation (auto / none); остальные параметры игнорируются
response.cancel останавливает речь агента, ответ заканчивается со статусом cancelled

Сервер → клиент. Названия приведены в GA; в beta — response.audio.*, response.audio_transcript.*, conversation.item.created.

Группа События
Сессия session.created, session.updated
Речь клиента input_audio_buffer.speech_started, .speech_stopped, .committed, .cleared
Расшифровка клиента conversation.item.input_audio_transcription.delta (устойчивые промежуточные), .completed (итоговая, с полем language)
История conversation.item.added, conversation.item.done, .truncated, .deleted
Ответ агента response.created, response.output_item.added, response.output_audio.delta, .done, response.output_audio_transcript.delta, .done, response.output_item.done, response.done
Инструменты response.function_call_arguments.delta, .done; расширение response.function_call.cancel
Ошибки error
Служебное x_glassbox — внутренняя телеметрия. Приходит всегда, пропускайте его

Не приходят: response.output_text.* (текстового режима нет), response.content_part.*, output_audio_buffer.*, rate_limits.updated, conversation.item.retrieved.

Несколько деталей:

Вызов инструментов

Инструменты описываются и возвращаются так же, как в OpenAI Realtime. Исполняете их вы.

Последовательность:

  1. Агент решает вызвать инструмент. Вы получаете response.output_item.added → conversation.item.added → response.function_call_arguments.delta → response.function_call_arguments.done (с call_id, name, arguments) → response.output_item.done.
    • Аргументы приходят целиком одним delta, а не по кускам.
    • Если вызов случился вне текущего ответа, он приходит в отдельной паре response.created … response.done.
  2. Вы исполняете вызов и шлёте conversation.item.create с {"type": "function_call_output", "call_id": …, "output": "<JSON-строка>"}.
  3. Агент озвучивает ответ по результату.
    • response.create после результата можно слать, как с OpenAI, но не обязательно: без него агент заговорит сам через ~0,5 с.
    • Если response.create пришёл, пока агент ещё обдумывает результат, сервер сразу отвечает пустым response.created / response.done. Настоящий ответ придёт отдельным ответом.

Ошибки и таймауты.

Подтверждение действий. Это главное отличие от OpenAI в поведении.

Перебивания и очерёдность

Конец реплики и перебивания сервер определяет сам. Ручного режима (turn_detection: null) нет.

Лимиты, задержки, языки

Лимиты тестового доступа

Что Значение
Одновременные сессии 10 на ключ (одиннадцатое подключение получит HTTP 429). Сессия освобождает место сразу после закрытия WebSocket. Нужно больше — напишите, поднимем лимит
Длительность сессии без ограничения; сессия 116 с проверена без обрывов
Инструкции до ~60 тыс. знаков
Один input_audio_buffer.append до 15 МБ; одно сообщение WebSocket — до 32 МБ
Ожидание результата инструмента 30 с
Ограничение частоты запросов нет

Задержки. Замеряли на нашем стенде в ноябре 2026, по одному разговору за раз. Отсчёт от конца речи клиента.

Ситуация Первый звук агента
Обычная реплика, сервис свободен ~0,9–1,0 с. Часто это короткая связка («Ага», «Сейчас посмотрю»)
Ответ после результата инструмента без паузы: пока агент договаривает связку («Сейчас уточню…»), ответ готовится и звучит сразу после неё
Первые содержательные слова ~1,5 с; если реплика кончается цифрами (номер, телефон) — ~3 с: сервер ждёт, не продолжит ли клиент диктовать

Для сравнения: в том же замере gpt-realtime дал первый звук через ~1,6 с. Сеть туда и обратно от клиента в Алматы до нашего движка в замере занимала ~0,15 с.

Языки.

Отличия от OpenAI Realtime

Клиент OpenAI Realtime работает без доработок, если учесть пункты ниже. Самые важные стоят первыми.

Что У нас Что делать
Действия с последствиями агент всегда переспрашивает перед ними учитывать лишний шаг в сценариях
turn_detection игнорируется, серверное определение конца реплики включено всегда push-to-talk делать через input_audio_buffer.commit
Текстовый режим (modalities: ["text"]) нет, ответ всегда голосом + расшифровка текст брать из response.output_audio_transcript.*
temperature, max_output_tokens, tool_choice принимаются, на работу не влияют —
Параметры в response.create учитываются только instructions, metadata, conversation —
response.create после результата инструмента не обязателен; может прийти пустой ответ-подтверждение не считать пустой response.done ошибкой
Аргументы инструмента приходят одним delta —
Событие x_glassbox приходит всегда пропускать неизвестные события
Неизвестные model и voice ошибки нет, берутся значения по умолчанию передавать model=local
session.created в GA приходит в beta-форме —
response.cancel не принимает audio_end_ms точку обрезки передавать в conversation.item.truncate
Расшифровка речи клиента всегда включена, модель не выбирается —
Интонация клиента смех, иронию, раздражение в голосе агент улавливает слабее, чем gpt-realtime —

Стоковый пакет openai для Python (GA) мы проверили на распознавании, инструментах и подтверждениях. Другие SDK (LiveKit, Pipecat, Twilio) по протоколу должны работать, но мы их не гоняли. Если что-то не стыкуется, пришлите лог событий.

Данные, безопасность, поддержка