GPT-Live: речь в реальном времени
Ведите полнодуплексное голосовое взаимодействие через GPT-Live WebSocket и обрабатывайте события сеанса.
GPT-Live обеспечивает полнодуплексное голосовое взаимодействие через постоянное соединение WebSocket. В одном сеансе можно отправлять входное аудио, получать речь и транскрипции, обновлять настройки и сохранять контекст нескольких ходов. Сейчас зарегистрирована модель gpt-live-1; фактическая доступность определяется текущим аккаунтом и каталогом моделей.
wss://api.agtcloud.cn/v1/live/sessionsАутентификация и подключение
Серверное приложение передаёт Bearer-ключ API в Header рукопожатия WebSocket. Модель задаётся не в параметрах запроса URL, а в первом событии session.start после подключения.
| Параметр | Значение |
|---|---|
| Адрес подключения | wss://api.agtcloud.cn/v1/live/sessions |
| Authorization | Bearer your-api-key |
| Расположение модели | session.start.session.model |
| Входное аудио | Блоки необёрнутого PCM-аудио в кодировке Base64 |
Долгосрочный ключ API должен храниться только на доверенном сервере. Этот документ описывает только указанный маршрут WebSocket, без WebRTC, SIP, sideband, fork сеанса или загрузки записей.
Процесс сеанса
- Установите соединение WebSocket с Bearer-ключом API.
- Отправьте
session.startс настройками модели, инструкций, аудио и делегирования. - После получения
session.startedначните отправлять аудио или команды приложения. - Непрерывно обрабатывайте входные и выходные транскрипции, выходное аудио и события использования.
- Остановите создание аудио и отправьте
session.close. - Продолжайте получать события; завершите обработку по окончательному использованию в
session.closed.
Окончание речевого хода не означает окончания сеанса. Для многоходового диалога сохраняйте одно соединение и отправляйте session.close только тогда, когда приложение готово завершить весь сеанс.
Параметры запуска
| Поле | Тип | Описание |
|---|---|---|
type | string | Первый кадр всегда session.start |
event_id | string | Необязательный идентификатор клиентского события для связи с ошибками |
session.model | string | Обязательный ID модели GPT-Live |
session.instructions | string | Необязательные инструкции голосового ассистента |
session.audio.format | object | Формат аудио, например audio/pcm, 24 kHz |
session.audio.output.voice | string | Выходной голос, например marin; допустимые значения зависят от модели |
session.delegation | object | Способ делегирования: используйте client или responses |
После запуска соединения можно обновлять инструкции и другие изменяемые настройки через session.update, ожидая подтверждения session.updated. Не отправляйте session.start повторно по тому же соединению и не меняйте голосовую модель верхнего уровня через событие обновления.
Подготовка аудио и примеры вызова
В примере используется аудио PCM signed 16-bit little-endian, 24 kHz, моно, без заголовка WAV. Каждые 100 ms содержат 4800 байтов; перед отправкой каждый аудиоблок кодируется в Base64.
# Подготовьте входное аудио
ffmpeg -i input.wav -f s16le -acodec pcm_s16le -ac 1 -ar 24000 audio.pcm
# Воспроизведите сохранённое выходное аудио
ffplay -f s16le -ar 24000 -ac 1 reply.pcmДля примера Node.js требуется ws; пример Python проверяет только запуск и закрытие сеанса, не отправляя аудио.
npm install ws
python -m pip install websocket-clientimport fs from 'node:fs';
import WebSocket from 'ws';
const key = process.env.API_KEY;
const file = process.argv[2];
if (!key || !file) throw new Error('Требуются API_KEY и путь к файлу PCM');
const pcm = fs.readFileSync(file);
if (!pcm.length || pcm.length % 2) {
throw new Error('PCM должен быть непустым и выровненным по 16-битным сэмплам');
}
const ws = new WebSocket('wss://api.agtcloud.cn/v1/live/sessions', {
headers: { Authorization: 'Bearer ' + key },
handshakeTimeout: 10000,
});
const audioOut = fs.createWriteStream('reply.pcm');
let started = false;
let closing = false;
let finalized = false;
let sender;
let drain;
let offset = 0;
let inputFinishedAt = 0;
let lastOutputAt = 0;
function send(event) {
ws.send(JSON.stringify(event));
}
function closeSession(reason) {
if (closing) return;
closing = true;
clearInterval(sender);
console.error('Запрос завершения:', reason);
if (started && ws.readyState === WebSocket.OPEN) {
send({ type: 'session.close' });
drain = setTimeout(() => ws.terminate(), 15000);
} else {
ws.terminate();
}
}
const limit = setTimeout(() => closeSession('Ограничение примера: 60 секунд'), 60000);
ws.on('open', () => send({
type: 'session.start',
event_id: 'start_001',
session: {
model: 'gpt-live-1',
instructions: 'Отвечайте кратко на китайском языке и дождитесь, пока пользователь заговорит первым.',
audio: {
format: { type: 'audio/pcm', rate: 24000 },
output: { voice: 'marin' },
},
delegation: { type: 'client' },
},
}));
ws.on('message', (raw) => {
let event;
try {
event = JSON.parse(raw.toString());
} catch {
closeSession('Недействительный JSON');
return;
}
if (event.type !== 'session.output_audio.delta') {
console.log(raw.toString());
}
if (event.type === 'session.started' && !started) {
started = true;
sender = setInterval(() => {
if (closing || ws.readyState !== WebSocket.OPEN) return;
if (offset >= pcm.length) {
inputFinishedAt ||= Date.now();
if (
lastOutputAt &&
Date.now() - Math.max(lastOutputAt, inputFinishedAt) >= 3000
) {
closeSession('Вывод завершён');
return;
}
send({
type: 'session.input_audio.append',
audio: Buffer.alloc(4800).toString('base64'),
});
return;
}
const chunk = pcm.subarray(offset, offset + 4800);
offset += chunk.length;
send({
type: 'session.input_audio.append',
audio: chunk.toString('base64'),
});
}, 100);
}
if (event.type === 'session.output_audio.delta') {
lastOutputAt = Date.now();
audioOut.write(Buffer.from(event.delta, 'base64'));
}
if (event.type === 'session.output_transcript.delta') {
lastOutputAt = Date.now();
}
if (event.type === 'session.closed') {
finalized = true;
ws.close(1000);
}
if (event.type === 'error') closeSession('Ошибка сервиса');
});
ws.on('error', (error) => {
console.error(error.message);
closeSession('Ошибка подключения');
});
ws.on('close', (code, reason) => {
clearTimeout(limit);
clearTimeout(drain);
clearInterval(sender);
audioOut.end();
console.error({ code, reason: reason.toString(), finalized });
if (!finalized || code !== 1000) process.exitCode = 1;
});
process.on('SIGINT', () => closeSession('Остановлено пользователем'));Справочник событий
Этот интерфейс отправляет аудио Base64 через session.input_audio.append. Он не использует двоичные аудиокадры Muse, endStream или события OpenAI Realtime input_audio_buffer.*.
session.start
Запускает сеанс GPT-Live и отправляет настройки модели, инструкций, аудио и делегирования.
| Поле | Тип | Обязательно | Описание | Пример |
|---|---|---|---|---|
type | string | Да | Всегда session.start. | session.start |
event_id | string | Нет | Идентификатор клиентского события. | start_001 |
session.model | string | Да | ID модели GPT-Live. | gpt-live-1 |
session.instructions | string | Нет | Инструкции диалога для голосового ассистента. | Отвечайте кратко на китайском языке. |
session.audio.format.type | string | Нет | Тип формата аудио. | audio/pcm |
session.audio.format.rate | integer | Нет | Частота дискретизации аудио. | 24000 |
session.audio.output.voice | string | Нет | Выходной голос. | marin |
session.delegation.type | string | Да | Способ делегирования. | client |
{
"type": "session.start",
"event_id": "start_001",
"session": {
"model": "gpt-live-1",
"instructions": "Отвечайте кратко на китайском языке.",
"audio": {
"format": {
"type": "audio/pcm",
"rate": 24000
},
"output": {
"voice": "marin"
}
},
"delegation": {
"type": "client"
}
}
}Использование и тарификация
session.usage.updated возвращает накопленное значение текущего сеанса, а не новое использование отдельного хода. Клиент не должен суммировать значения разных этапов; при нормальном закрытии окончательную длительность определяет session.closed.usage.seconds.
Стоимость может включать длительность голосового сеанса, использование фоновой модели и вызовы инструментов. Точные цены и доступность моделей определяются Каталогом моделей.
Способ делегирования
client: Приложение само обрабатывает делегированные задачи; подходит для первоначальной проверки базового голосового сеанса.responses: Сервер выполняет настроенные модель Responses и инструменты. Целевая модель, инструменты и разрешения должны быть доступны на текущем сайте.
При использовании responses стоимость моделей и инструментов фоновых задач может начисляться отдельно. Не считайте MODEL_ID в примере конкретной гарантированно доступной моделью.
Обработка ошибок
| Симптом | Проверка и действия |
|---|---|
| Рукопожатие возвращает 401 | Проверьте Bearer-ключ API, права на модель и адрес текущего сайта |
| Маршрут возвращает 404 | Убедитесь, что итоговый путь — /v1/live/sessions, без повторного добавления /v1 |
| Ошибка первого кадра или тайм-аут запуска | После подключения сначала отправьте session.start; отправляйте аудио только после session.started |
| Нет звука или звук искажён | Проверьте разрядность PCM, порядок байтов, частоту дискретизации, каналы и заголовок WAV |
Не получено session.closed | Сохраните последнее накопленное использование, событие ошибки и причину закрытия; не воспроизводите автоматически всю аудиозапись повторно |
| Фоновое делегирование отклонено | Проверьте модель Responses, инструменты, разрешения и поддержку на текущем сайте |