AGTCloud API Docs
文档指南API Reference集成与资源更新与帮助
实时交互

GPT-Live 实时语音

通过 GPT-Live WebSocket 进行全双工语音交互并处理会话事件。

GPT-Live 通过持久 WebSocket 提供全双工语音交互,可在同一会话中发送输入音频、接收语音与转写结果、更新会话配置并保持多轮上下文。当前模型记录为 gpt-live-1,实际可用范围以当前账户和模型目录为准。

WSS
wss://api.agtcloud.cn/v1/live/sessions

认证与连接

服务端应用在 WebSocket 握手 Header 中使用 Bearer API Key。模型不放在查询参数中,而是在连接后的首个 session.start 事件中配置。

项目值
连接地址wss://api.agtcloud.cn/v1/live/sessions
AuthorizationBearer your-api-key
模型位置session.start.session.model
输入音频Base64 编码的裸 PCM 音频块

长期 API Key 只应保存在可信服务端。本文只覆盖该 WebSocket 路由,不包含 WebRTC、SIP、sideband、会话 fork 或录音下载。

会话流程

  1. 使用 Bearer API Key 建立 WebSocket 连接。
  2. 发送 session.start,配置模型、指令、音频和委托方式。
  3. 收到 session.started 后开始发送音频或应用命令。
  4. 持续处理输入转写、输出转写、输出音频和用量事件。
  5. 停止音频生产后发送 session.close。
  6. 继续接收事件,以 session.closed 中的最终用量完成收尾。

说话轮次结束不等于会话结束。多轮对话应保持同一连接,只有业务准备结束整个会话时才发送 session.close。

启动参数

字段类型说明
typestring首帧固定为 session.start
event_idstring可选的客户端事件标识,用于关联错误
session.modelstring必填,GPT-Live 模型 ID
session.instructionsstring可选的语音助手指令
session.audio.formatobject音频格式,例如 audio/pcm、24 kHz
session.audio.output.voicestring输出音色,例如 marin;可选值取决于模型
session.delegationobject委托方式,使用 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-client
WSSGPT-Live 会话示例
import 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('用户停止'));

事件 Reference

本接口使用 session.input_audio.append 发送 Base64 音频,不使用 Muse 的二进制音频帧和 endStream,也不使用 OpenAI Realtime 的 input_audio_buffer.* 事件。

客户端事件 · Session

session.start

启动 GPT-Live 会话并提交模型、指令、音频和委托配置。

字段类型必填说明示例值
type字符串是固定为 session.start。session.start
event_id字符串否客户端事件标识。start_001
session.model字符串是GPT-Live 模型 ID。gpt-live-1
session.instructions字符串否语音助手的对话指令。请用简短中文回答。
session.audio.format.type字符串否音频格式类型。audio/pcm
session.audio.format.rate整数否音频采样率。24000
session.audio.output.voice字符串否输出音色。marin
session.delegation.type字符串是委托方式。client
JSON 示例
{
  "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 Key、模型权限和当前站点地址
路径返回 404确认最终连接路径为 /v1/live/sessions,没有重复拼接 /v1
首帧错误或启动超时连接后先发送 session.start,收到 session.started 后再发送音频
无声音或声音失真检查 PCM 位深、字节序、采样率、声道和 WAV 文件头
未收到 session.closed保存最后一个累计用量、错误事件和关闭原因,不自动重放整段音频
后台委托被拒绝检查 Responses 模型、工具、权限和当前站点支持范围

参考资料