Freedom Speech Realtime API
Голосовой агент «речь в речь», совместимый по протоколу с OpenAI Realtime API. Клиент, который уже работает с OpenAI Realtime, подключается к нам сменой адреса и ключа.
Обзор
- Как работает. Вы открываете WebSocket, передаёте свои инструкции и инструменты (function calling) и шлёте звук собеседника. Агент отвечает голосом и текстом и вызывает ваши инструменты. Исполняете их вы и возвращаете результат.
- Разговор без пауз. Пока агент ищет данные или думает над ответом, он сразу поддерживает разговор короткой фразой («Сейчас посмотрю»), чтобы собеседник не слушал тишину.
- Страховка от лишних действий. Перед инструментом, который что-то меняет (отмена, перевод, запись), агент спрашивает подтверждение, даже если инструкции этого не требуют. Инструменты чтения (найти заказ, узнать статус) вызываются сразу.
- Где работает. Все модели работают на наших GPU-серверах. Сторонние AI-провайдеры в обработке разговора не участвуют, звук и текст разговоров не сохраняются.
- Статус. Тестовый доступ для партнёров, без SLA.
Быстрый старт
Для старта нужны три вещи:
| Параметр | Значение |
|---|---|
| Адрес (базовый 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:
- заголовок
Authorization: Bearer <ключ>— для серверных клиентов; - подпротокол WebSocket
openai-insecure-api-key.<ключ>— если заголовки задать нельзя.
Из браузера напрямую не подключиться. Сервер отвечает 403 на любой чужой заголовок Origin. Ставьте ретранслятор на своём бэкенде: он добавляет ключ и не передаёт Origin. Так ключ и не попадёт в браузер.
Диалект. Поддерживаются оба: beta (openai < 2.0, Azure) и GA (openai ≥ 2.0, livekit-plugins-openai ≥ 1.6).
- Заголовок
OpenAI-Betaне читается. - Соединение начинается в beta. На GA оно переключается навсегда, когда приходит первый
session.updateс полемtypeилиaudio. - Поэтому первое событие
session.createdвсегда приходит в beta-форме. Стоковый SDK это переносит.
Ошибки подключения. Это обычные HTTP-ответы до установки WebSocket:
| Ситуация | HTTP | Тело |
|---|---|---|
| Нет ключа или неверный ключ | 401 | JSON, code: unauthorized |
| У ключа уже открыто 10 сессий | 429 | не JSON |
| Сервис перегружен или перезапускается | 503 | JSON, code: session_limit |
Чужой Origin (браузер) |
403 | — |
JSON-ошибки имеют вид {"error": {"type", "code", "message"}}.
Закрытие соединения. Ошибки в событиях соединение не рвут, они приходят событием error. Сам сервер закрывает WebSocket только в трёх случаях:
1000— сессия завершена;1001— сервер останавливается;1008— клиент не забирает события дольше 20 с.
Настройка сессии (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 кГц | телефония |
- Моно, base64 в
input_audio_buffer.append. - Размер куска любой: неполные кадры склеиваются. Рекомендуем 20–100 мс в реальном времени.
- Один кусок — не больше 15 МБ.
События
Названия событий и их поля совпадают с 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.
Несколько деталей:
- В
response.doneу прерванного ответаstatus: cancelled,status_details.reason: turn_detected. usageможет отсутствовать.- Итоговая расшифровка может содержать пометки звуков и интонации. Чистый текст лежит в поле
text_plain.
Вызов инструментов
Инструменты описываются и возвращаются так же, как в OpenAI Realtime. Исполняете их вы.
Последовательность:
- Агент решает вызвать инструмент. Вы получаете
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.
- Аргументы приходят целиком одним
- Вы исполняете вызов и шлёте
conversation.item.createс{"type": "function_call_output", "call_id": …, "output": "<JSON-строка>"}. - Агент озвучивает ответ по результату.
response.createпосле результата можно слать, как с OpenAI, но не обязательно: без него агент заговорит сам через ~0,5 с.- Если
response.createпришёл, пока агент ещё обдумывает результат, сервер сразу отвечает пустымresponse.created/response.done. Настоящий ответ придёт отдельным ответом.
Ошибки и таймауты.
- Результат-объект с
"ok": falseили непустым"error"агент считает ошибкой инструмента и не выдаёт его за успех. - Если ответ не пришёл за 30 с, агент перестаёт ждать. Запоздавший результат всё равно учитывается дальше в разговоре.
- Пока инструмент работает, агент обычно говорит короткую связку («Сейчас посмотрю»). Держите инструменты быстрыми: на телефоне пауза дольше 2–3 с заметна.
Подтверждение действий. Это главное отличие от OpenAI в поведении.
- Инструменты, которые что-то меняют (имя вроде
cancel_order,create_booking,transfer_money), агент вызывает только после явного согласия клиента: он переспрашивает и ждёт «да». - На «нет» или неясный ответ вызов не делается. Вы его просто не получите.
- Инструменты чтения (имя вроде
get_order,find_client,check_status) вызываются сразу. - Тип инструмента агент определяет по имени и описанию. Сомнительный считается действием. Давайте инструментам понятные английские имена с глаголом. Если какой-то инструмент классифицируется неверно, напишите нам — поправим настройкой без изменений с вашей стороны.
Перебивания и очерёдность
Конец реплики и перебивания сервер определяет сам. Ручного режима (turn_detection: null) нет.
- Конец реплики. Определяется по смыслу и паузе: сервер ждёт от 0,4 с до 2,5 с тишины. Когда клиент диктует номер или адрес, сервер ждёт дольше.
-
События реплики клиента. Идут в таком порядке:
input_audio_buffer.speech_started.input_audio_buffer.speech_stopped.input_audio_buffer.committed.conversation.item.added.conversation.item.input_audio_transcription.completed.
Если слов не распознано (кашель, шум), придёт только
speech_stopped. - Перебивание. Если клиент во время речи агента сказал два и больше слова по делу, агент замолкает. Текущий ответ заканчиваетсяresponse.doneсо статусомcancelled. - Короткое «ага», «угу», «да» считается поддакиванием и речь агента не останавливает. - Воспроизведение на вашей стороне. Как и с OpenAI, поspeech_startedили прерванному ответу очистите свой буфер воспроизведения. Чтобы история соответствовала тому, что клиент услышал, пошлитеconversation.item.truncateсaudio_end_ms. Сам серверconversation.item.truncatedбез вашего запроса не шлёт. - Паузы клиента. По умолчанию агент не переспрашивает «Вы меня слышите?», если клиент долго молчит. Завершение звонка по тишине — на вашей стороне.
Лимиты, задержки, языки
Лимиты тестового доступа
| Что | Значение |
|---|---|
| Одновременные сессии | 10 на ключ (одиннадцатое подключение получит HTTP 429). Сессия освобождает место сразу после закрытия WebSocket. Нужно больше — напишите, поднимем лимит |
| Длительность сессии | без ограничения; сессия 116 с проверена без обрывов |
| Инструкции | до ~60 тыс. знаков |
Один input_audio_buffer.append |
до 15 МБ; одно сообщение WebSocket — до 32 МБ |
| Ожидание результата инструмента | 30 с |
| Ограничение частоты запросов | нет |
Задержки. Замеряли на нашем стенде в ноябре 2026, по одному разговору за раз. Отсчёт от конца речи клиента.
| Ситуация | Первый звук агента |
|---|---|
| Обычная реплика, сервис свободен | ~0,9–1,0 с. Часто это короткая связка («Ага», «Сейчас посмотрю») |
| Ответ после результата инструмента | ~0,3–0,75 с после function_call_output |
| Содержательный ответ без инструмента | 2–4 с (до него звучит связка) |
Для сравнения: в том же замере gpt-realtime дал первый звук через ~1,6 с, после результата инструмента — через 0,7–0,9 с. Серверы в Европе, сеть до Казахстана добавляет около 0,4 с в обе стороны вместе.
Языки.
- Русский — основной, на нём проведены все проверки.
- Казахский поддержан: агент переходит на язык клиента, голос
aluaговорит по-казахски. Через этот API на казахском мы ещё не тестировали — проверьте на своих сценариях. - Язык каждой реплики клиента приходит в поле
languageсобытияconversation.item.input_audio_transcription.completed.
Отличия от 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) по протоколу должны работать, но мы их не гоняли. Если что-то не стыкуется, пришлите лог событий.
Данные, безопасность, поддержка
- Записи разговоров не хранятся: ни звук, ни расшифровки, ни ваши инструкции. В технических логах остаются только время, тип событий и служебные решения агента, без текста реплик.
- Внешние сервисы не вызываются. Вся обработка речи и ответов идёт на наших серверах; исходящие соединения закрыты.
- Ваши инструменты исполняете вы. Мы видим только их описание и результат, который вы вернули в сессию. Доступов к вашим системам нам не нужно.
- Шифрование: только
wss://(TLS). - Ключ. Храните на сервере. В браузер и мобильное приложение ключ не кладите: подключайтесь через свой бэкенд. Если ключ утёк, сообщите — заменим.
- Поддержка. Пишите вашему контакту в Freedom Speech: он выдаёт ключ и помогает с интеграцией. С вопросом присылайте время сессии (UTC) и
session.idиз первого события.