
Створення голосового агента на базі OpenAI Realtime API: 7-кроковий продакшн-гайд (2026)
Наш тестовий агент відповів на дзвінок через Twilio і вимовив перше слово за 1,1 секунди після того, як абонент припинив говорити. Це медіанне значення (p50) повного циклу, виміряне під час 40 дзвінків на моделі gpt-realtime-2 з увімкненим semantic_vad. Це не магія. OpenAI Realtime API виконує перетворення «мовлення-в-мовлення» в межах одного сокету, тому ви уникаєте ланцюжка STT → LLM → TTS, який додає близько 600 мс зайвої затримки. Але стандартні налаштування не дозволять вам досягти часового порогу в одну секунду. Ось 7 кроків реалізації, яку ми запустили в продакшн, разом із кодом та таблицею затримок.
Це технічний посібник із побудови системи, а не теоретичний огляд концепцій. Якщо ви спочатку хочете розібратися в основах, прочитайте статтю що таке AI-голосовий агент насправді, а потім повертайтеся сюди. Усе нижче передбачає, що у вас є ключ API OpenAI та середовище виконання Node.js.
Ключові висновки:
- gpt-realtime-2 виконує перетворення «мовлення-в-мовлення» в одному сокеті — без ланцюжка STT/LLM/TTS, економія ~600 мс.
- Генеруйте ефемерні ключі на стороні сервера; ніколи не передавайте свій основний API-ключ у браузер.
- Медіапотік Twilio працює на частоті 8 кГц у форматі μ-law; для Realtime API необхідно ресемплювати його до 24 кГц PCM16.
- Ми виміряли затримку повного циклу p50 1,1 с / p95 1,9 с. Обробка перебивань реалізується через
response.cancel.
Що ви створите за 7 кроків
У цьому посібнику ми створимо голосового агента на базі OpenAI Realtime API, який відповідає на телефонні дзвінки із затримкою менше 1,5 секунди, викликає реальні функції під час розмови та дозволяє абоненту перебивати себе. Процес короткий: абонент дзвонить на номер, аудіопотік надходить на ваш сервер, ваш сервер передає його в gpt-realtime-2 через один сокет, модель генерує відповідь і може викликати інструменти, а аудіо повертається назад.
Ось шлях реалізації, і ви можете зупинитися на будь-якому етапі, який відповідає вашим потребам:
- Генерація ефемерного ключа (серверний маршрут)
- Відкриття та налаштування сесії
- Додавання виклику функцій
- Підключення до телефонного номера через Twilio
- Обробка перебивань (barge-in)
- Оптимізація затримки до субсекундного рівня
- Деплой та захист системи
Аудіо передається трьома способами транспортування, і вибір залежить від джерела звуку. Браузер захоплює його безпосередньо (WebRTC), ваш сервер уже має сирий потік (WebSocket) або телефонна мережа доставляє його (SIP). Для моста з Twilio ми використаємо WebSocket і згадаємо інші варіанти там, де це доречно.
Крок 1: Генерація ефемерного ключа (маршрут, який не можна пропустити)
Ніколи не надавайте свій стандартний ключ API OpenAI браузеру або клієнтському пристрою. Realtime API випускає короткострокові ефемерні ключі саме для цього. Ваш сервер викликає POST /v1/realtime/client_secrets, використовуючи ваш справжній ключ, передає клієнту токен, який дійсний приблизно хвилину, і клієнт підключається вже з ним.
Ось мінімальний маршрут Express для генерації такого ключа:
// server.js
import express from "express";
const app = express();
app.get("/session", async (req, res) => {
const r = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
session: { type: "realtime", model: "gpt-realtime-2" },
}),
});
const data = await r.json();
res.json({ client_secret: data.value, expires_at: data.expires_at });
});
app.listen(3000);Браузер робить запит до /session, зчитує короткостроковий секрет і відкриває з'єднання Realtime з ним. Якщо ваш агент працює лише на сервері (випадок із Twilio з Кроку 4), ви можете пропустити передачу ключа клієнту й відкрити сокет із бекенду безпосередньо, використовуючи стандартний ключ. Потік із ефемерними ключами існує для захисту ненадійних клієнтів.
Крок 2: Відкриття сесії та налаштування gpt-realtime-2
Відкрийте з'єднання, а потім надішліть команду session.update, яка встановлює модель, формат аудіо, голос та параметри виявлення черги мовлення. Документація OpenAI рекомендує починати з параметра reasoning.effort, встановленого на low, і підвищувати його лише якщо логіка ваших інструментів потребує більшої точності, оскільки вищий рівень зусиль збільшує затримку. Аудіо в обох напрямках працює у форматі 24 кГц PCM16.
ws.send(JSON.stringify({
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2",
output_modalities: ["audio"],
audio: {
input: { format: "pcm16", sample_rate: 24000 },
output: { format: "pcm16", sample_rate: 24000, voice: "marin" },
},
instructions: "You are a reservations agent for a restaurant. Be brief.",
reasoning: { effort: "low" },
turn_detection: { type: "semantic_vad" },
},
}));Вибір транспортного протоколу для цього сокету залежить від джерела аудіо:
| Транспорт | Коли використовувати | Джерело аудіо |
|---|---|---|
| WebRTC | Браузер або мобільний додаток безпосередньо захоплюють мікрофон | Клієнтський пристрій |
| WebSocket | Ваш сервер уже має сирий аудіопотік | Серверний пайплайн |
| SIP | Ви хочете, щоб OpenAI обробляв телефонну лінію | PSTN / телефонія |
Для повного списку полів сесії та набору функцій GA звертайтеся до документації OpenAI Realtime API як до першоджерела. Ми використаємо WebSocket, оскільки Twilio надає нам сирий аудіопотік у Кроці 4.
Крок 3: Додавання виклику функцій (щоб агент міг діяти)
Голосовий агент, який не може виконувати дії, — це просто озвучка. Виклик функцій дозволяє gpt-realtime-2 призупинити розмову, попросити ваш код виконати певну операцію та продовжити спілкування з результатом. Ви оголошуєте інструмент у сесії, модель генерує подію function_call_arguments.done, коли хоче його використати, ви виконуєте роботу та надсилаєте результат назад.
Оголосіть інструмент, а потім обробіть подію:
// in session.update -> session.tools:
tools: [{
type: "function",
name: "book_reservation",
description: "Book a table for a given party size and time.",
parameters: {
type: "object",
properties: {
party_size: { type: "integer" },
time: { type: "string", description: "ISO 8601 datetime" },
},
required: ["party_size", "time"],
},
}]
// handling the call:
if (event.type === "response.function_call_arguments.done") {
const args = JSON.parse(event.arguments);
const result = await bookTable(args); // your real logic
ws.send(JSON.stringify({
type: "conversation.item.create",
item: {
type: "function_call_output",
call_id: event.call_id,
output: JSON.stringify(result),
},
}));
ws.send(JSON.stringify({ type: "response.create" })); // let it speak the result
}Найпоширеніша причина, чому інструменти ніколи не спрацьовують: ви не слухаєте подію function_call_arguments.done і не надсилаєте response.create після цього. Модель згенерувала виклик, ви його ігнорували, і абонент чує тишу.
Якщо ваш агент використовує багато інструментів, OpenAI Agents SDK змінив правила гри. Оновлення від 15 квітня 2026 року зробило Model Context Protocol (MCP) повноцінною частиною системи та перетворило передачу завдань між субагентами на примітив часу виконання. Тож замість того, щоб намагатися вмістити всі інструменти в один промпт, агент-маршрутизатор може передати бронювання субагенту резервувань, а питання білінгу — іншому. Швидкий старт із голосовими агентами в Agents SDK обгортає ту саму сесію Realtime в RealtimeAgent і надає можливість передачі завдань без написання власного циклу оркестрації.
Крок 4: Міст до телефонного номера (Twilio)
Щоб відповідати на реальні дзвінки, потрібно підключити телеком-провайдера до сокету. З Twilio ви направляєте вхідний дзвінок на TwiML <Connect><Stream>, який відкриває WebSocket до вашого сервера, і ретранслюєте аудіофрейми між Twilio та Realtime API. Альтернативою є SIP — OpenAI Realtime приймає SIP безпосередньо, що повністю усуває необхідність у вашому медіареле, якщо вам не потрібно обробляти аудіо.
TwiML для запуску потоку:
<Response>
<Connect>
<Stream url="wss://your-server.com/twilio-stream" />
</Connect>
</Response>Ось підступний момент, який може забрати цілий день, якщо його проґавити: медіапотік Twilio працює на частоті 8 кГц у форматі μ-law, а Realtime API потребує 24 кГц PCM16. Вам потрібно ресемплювати аудіо в обох напрямках, інакше звук буде спотвореним або нагадуватиме «бурундуків».
// inbound: Twilio (8kHz μ-law base64) -> Realtime (24kHz PCM16)
const pcm16 = upsample(muLawDecode(Buffer.from(msg.media.payload, "base64")), 8000, 24000);
realtime.send(JSON.stringify({
type: "input_audio_buffer.append",
audio: pcm16.toString("base64"),
}));
// outbound: Realtime (24kHz PCM16) -> Twilio (8kHz μ-law)
const ulaw = muLawEncode(downsample(modelPcm16, 24000, 8000));
twilioWs.send(JSON.stringify({
event: "media",
media: { payload: ulaw.toString("base64") },
}));Повний формат фреймів описано в документації Twilio Media Streams. Зробіть ресемплінг ефективним, оскільки важка бібліотека тут додасть затримку, яку ви платитимете за кожен фрейм.
Крок 5: Обробка перебивань (Barge-in)
Продуктовий агент дозволяє абоненту говорити поверх його голосу. Barge-in означає виявлення того, що абонент почав говорити, поки агент ще вимовляє речення, та чисте переривання агента. Realtime API обробляє це за допомогою response.cancel: коли детектор черги повідомляє про початок мовлення під час відтворення, ви скасовуєте активну відповідь і очищаєте будь-яке аудіо, яке вже було буферизовано для абонента.
if (event.type === "input_audio_buffer.speech_started") {
realtime.send(JSON.stringify({ type: "response.cancel" }));
twilioWs.send(JSON.stringify({ event: "clear" })); // drop queued playback
}Детектор черги має два режими, і вибір має значення. server_vad спрацьовує на порогових значеннях тиші й часто перериває абонента під час природних пауз. semantic_vad чекає, поки модель вирішить, що абонент справді завершив думку, тому він видає набагато менше хибних перебивань під час пауз на обдумування. Для телефонних дзвінків саме semantic VAD звучить найбільш природно.
Крок 6: Оптимізація затримки до субсекундного рівня
Саме тут демо стає продуктом, тому ось реальні числа з нашої системи, а не теоретичні розрахунки. У травні 2026 року ми провели той самий сценарій агента для ресторану через 40 тестових дзвінків на одному невеликому сервері, розміщеному поблизу регіону OpenAI, змінюючи лише налаштування детектора черги та рівень міркувань.
| Конфігурація | p50 повного циклу | p95 | Примітки |
|---|---|---|---|
| server_vad, reasoning low | ~1,4 с | ~2,3 с | більше хибних перебивань на паузах |
| semantic_vad, reasoning low | ~1,1 с | ~1,9 с | наш продакшн-стандарт |
| semantic_vad, reasoning medium | ~1,8 с | ~3,1 с | краща точність інструментів, але повільніше |
Регулятори, які реально вплинули на результат, у порядку значущості:
- Залишайте
reasoning.effortна рівні low, якщо конкретний інструмент дійсно не потребує високої точності. Рівень medium майже подвоїв наше значення p50. - Не надсилайте аудіо швидше за реальний час. Переповнення
input_audio_buffer.appendпризводить до перевантаження буфера та дрейфу; синхронізуйте фрейми з астрономічним часом. - Тримайте сокет активним. Холодне відкриття з'єднання для кожного дзвінка додає час рукостискання до затримки першого слова. Використовуйте пул з'єднань, якщо обсяг дзвінків дозволяє.
- Ефективний ресемплінг. Наївний ресемплер у критичному шляху додав нам ~80 мс на кожну чергу мовлення.
Скільки коштує хвилина роботи такої системи в продакшні? Ми окремо прорахували математику для моделі «bring-your-own-key» — див. скільки коштує хвилина роботи голосового агента BYOK, замість того щоб виводити формули тут.
Крок 7: Деплой та захист для продакшну
Різниця між «це працювало на моєму ноутбуці» та «це витримує 500 дзвінків на день» полягає в кількох відомих типах помилок. Ось чек-лист захисту, складений на основі помилок, які реально ламають агентів Realtime:
| Пастка | Симптом | Виправлення |
|---|---|---|
| Неправильна частота дискретизації | спотворений звук / «бурундуки» | 24 кГц PCM16 в обох напрямках |
Ігнорування function_call_arguments.done | інструменти не спрацьовують | слухати подію та надсилати response.create |
| Надсилання аудіо швидше за реальний час | перевантаження буфера, дрейф | синхронізація фреймів із реальним часом |
| Відсутність логіки перепідключення | дзвінки обриваються при збої сокету | автоперепідключення + відновлення сесії |
Відсутність обробки response.done | накладання черг мовлення | блокувати наступну чергу до отримання response.done |
Ще два важливі моменти для реального трафіку. Під час довгих дзвінків оновлюйте або перезавантажуйте сесію кожні кілька черг, щоб контекст не «плив», оскільки 20-хвилинний дзвінок накопичує стан, у якому модель починає плутатися. І логуйте кожен виклик інструменту з його аргументами та результатом; коли абонент каже «агент забронював неправильний час», лише транскрипції недостатньо, щоб зрозуміти, хто помилився — модель чи ваш код.
Якщо ви обрали шлях Agents SDK з Кроку 3, його нова пісочниця контейнерів запускає код інструментів ізольовано, що важливо, коли ваші інструменти взаємодіють із файловою системою або оболонкою, а не просто з API.
Коли варто купити керовану платформу замість власної розробки
Побудова безпосередньо на Realtime API дає максимальний контроль і найнижчу вартість за хвилину, але ви берете на себе логіку перепідключення, телеком-міст, комплаєнс та спостережуваність — усі непривабливі частини кроків 4–7. Якщо вам потрібен телефонний агент уже цього тижня і ви не хочете підтримувати медіареле, керована платформа буде швидшим рішенням.
Ми створили того самого агента на трьох великих платформах і чесно порівняли їх: Retell, Vapi або Bland. Якщо ви все ще вагаєтеся, ознайомтеся з повною структурою прийняття рішення «будувати чи купувати» перед тим, як витрачати інженерний час.
Коли команди хочуть контролю власної кастомної збірки на Realtime, але не мають ресурсів для її підтримки, саме цим ми займаємося: розробка продуктових голосових агентів, від телеком-моста до оптимізації затримки, описаної вище. Будемо раді розглянути ваш кейс, якщо ви зважуєте варіанти.
Про автора — Мерт Батур Гюрбюз є співзасновником Techsy.io, де команда постачає AI-агентів, системи автоматизації та голосові/SDR-пайплайни для B2B-клієнтів. Він навчається в Бірмінгемському університеті та пише про стек інструментів LLM, який команда Techsy реально використовує в продакшні. LinkedIn
Часті запитання
Яка затримка у голосового агента на базі OpenAI Realtime API?
У нашій збірці на gpt-realtime-2 з semantic_vad та низьким рівнем міркувань затримка повного циклу склала p50 1,1 с та p95 1,9 с за результатами 40 тестових дзвінків. Перетворення «мовлення-в-мовлення» в одному сокеті уникає ланцюжка STT/LLM/TTS, що й робить можливими відповіді менше ніж за секунду.
Чи потрібен мені WebRTC, WebSocket чи SIP для мого голосового агента?
Використовуйте WebRTC, коли браузер або мобільний додаток безпосередньо захоплюють мікрофон, WebSocket, коли ваш сервер уже має сирий аудіопотік (випадок із мостом Twilio), та SIP, коли ви хочете, щоб OpenAI обробляв телефонну лінію без вашого власного медіареле. Більшість телефонних агентів використовують WebSocket або SIP.
Як підключити OpenAI Realtime API до Twilio?
Направте вхідний дзвінок Twilio на TwiML <Connect><Stream>, який відкриває WebSocket до вашого сервера, а потім ретранслюйте аудіо між Twilio та сокетом Realtime. Ресемплюйте формат Twilio 8 кГц μ-law у формат API 24 кГц PCM16 в обох напрямках, інакше аудіо буде спотвореним.
Як працює виклик функцій у Realtime API?
Ви оголошуєте інструменти в конфігурації сесії. Коли модель хоче використати один із них, вона генерує подію function_call_arguments.done. Ви виконуєте роботу, надсилаєте результат назад як елемент розмови function_call_output, а потім надсилаєте response.create, щоб агент озвучив результат. Саме забування останнього кроку є причиною «тихої» помилки інструментів.
Як обробляються перебивання (barge-in) у Realtime API?
Коли детектор черги повідомляє про input_audio_buffer.speech_started під час відтворення, надішліть response.cancel, щоб зупинити активну відповідь і очистити будь-яке queued аудіо, спрямоване до абонента. Поєднуйте це з semantic_vad, щоб природні паузи не викликали хибних перебивань посеред речення.
Яку частоту дискретизації використовує OpenAI Realtime API?
Realtime API використовує аудіо 24 кГц PCM16 в обох напрямках. Телеком-провайдери, такі як Twilio, доставляють аудіо у форматі 8 кГц μ-law, тому телефонний міст повинен ресемплювати сигнал вверх на вході та вниз на виході. Невідповідність частот дискретизації є найпоширенішою причиною спотворення звуку.
Скільки коштує запуск голосового агента на Realtime API?
Вартість залежить від хвилин вхідного та вихідного аудіо на gpt-realtime-2, а економіка моделі «bring-your-own-key» суттєво відрізняється від керованих платформ із оплатою за хвилину. Ми детально прорахували всю математику в нашому огляді цін на голосових агентів, замість того щоб наводити оцінки тут.
Чи варто будувати на Realtime API чи використовувати Retell, Vapi або Bland?
Будуйте безпосередньо, якщо вам потрібен максимальний контроль і найнижча вартість за хвилину, і ви готові взяти на себе перепідключення, телефонію та комплаєнс. Купуйте керовану платформу, якщо швидкість запуску важливіша. Наше порівняння Retell vs Vapi vs Bland та структура рішення «будувати чи купувати» охоплюють усі компроміси.
Що змінило оновлення OpenAI Agents SDK у квітні 2026 року для голосових агентів?
Оновлення від 15 квітня 2026 року зробило Model Context Protocol повноцінною частиною системи, додало пісочницю контейнерів для коду інструментів та перетворило передачу завдань між субагентами на примітив часу виконання. Для голосових агентів це означає, що агент-маршрутизатор може передавати завдання спеціалізованим субагентам замість того, щоб намагатися вмістити всі інструменти в один промпт.