- 作者:老汪软件技巧
- 发表时间:2024-10-05 07:00
- 浏览量:
OpenAI 于 10 月 1 日的 DevDay上发布了多项重磅更新,包括ChatGPT的高级语音功能、实时API、模型蒸馏、视觉微调和Playground新功能。看之前的演示(比如 …)馋这个实时语音模式很久了,终于是等来了 API。作为开发者,我们自然是要基于 API 写东西的。为了方便咱国内的开发者(包括我自己)看这个文档,本文就翻译一下
原文: /docs/guides…
实时 API 测试版
实时 API 使您能够构建低延迟、多模态的对话体验。它目前支持 文本和音频 作为 输入 和 输出,以及 函数调用。
该 API 的一些显著优势包括:
原生语音到语音: 无需文本作为中转,意味着低延迟、细致的输出。自然、可控的语音: 模型具有自然的语调,可以笑、轻声说话,并遵循语调指示。同时多模态输出: 文本有助于内容审核,比实时更快的音频确保稳定播放。快速开始
实时 API 是一个 WebSocket 接口,专为在服务器上运行而设计。为了帮助您快速上手,我们创建了一个控制台 Demo,展示了此 API 的一些功能。虽然我们不建议在生产环境中使用此种前端模式,但该应用将帮助您可视化和检查实时 API 的事件流。
开始使用实时控制台
要快速开始,请下载并配置此演示 Demo。
概述
实时 API 是一个有状态、基于事件的 API,通过 WebSocket 进行通信。WebSocket 连接需要以下参数:
以下是使用流行的 ws 库在 Node.js 中建立套接字连接、从客户端发送消息并从服务器接收响应的简单示例。它要求在系统环境变量中有 OPENAI_API_KEY。
import WebSocket from "ws";
const url = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01";
const ws = new WebSocket(url, {
headers: {
"Authorization": "Bearer " + process.env.OPENAI_API_KEY,
"OpenAI-Beta": "realtime=v1",
},
});
ws.on("open", function open() {
console.log("已连接到服务器。");
ws.send(JSON.stringify({
type: "response.create",
response: {
modalities: ["text"],
instructions: "请协助用户。",
}
}));
});
ws.on("message", function incoming(message) {
console.log(JSON.parse(message.toString()));
});
服务器发出的事件以及客户端可以发送的事件的完整列表,可以在 API 参考 中找到。一旦连接成功,您将发送和接收代表文本、音频、函数调用、中断、配置更新等的事件。
示例
以下是一些常见的 API 功能示例,帮助您入门。这些示例假设您已经实例化了一个 WebSocket。
发送用户文本
const event = {
type: 'conversation.item.create',
item: {
type: 'message',
role: 'user',
content: [
{
type: 'input_text',
text: 'Hello!'
}
]
}
};
ws.send(JSON.stringify(event));
ws.send(JSON.stringify({type: 'response.create'}));
发送用户音频
import fs from 'fs';
import decodeAudio from 'audio-decode';
// Converts Float32Array of audio data to PCM16 ArrayBuffer
function floatTo16BitPCM(float32Array) {
const buffer = new ArrayBuffer(float32Array.length * 2);
const view = new DataView(buffer);
let offset = 0;
for (let i = 0; i < float32Array.length; i++, offset += 2) {
let s = Math.max(-1, Math.min(1, float32Array[i]));
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
}
return buffer;
}
// Converts a Float32Array to base64-encoded PCM16 data
base64EncodeAudio(float32Array) {
const arrayBuffer = floatTo16BitPCM(float32Array);
let binary = '';
let bytes = new Uint8Array(arrayBuffer);
const chunkSize = 0x8000; // 32KB chunk size
for (let i = 0; i < bytes.length; i += chunkSize) {
let chunk = bytes.subarray(i, i + chunkSize);
binary += String.fromCharCode.apply(null, chunk);
}
return btoa(binary);
}
// Using the "audio-decode" library to get raw audio bytes
const myAudio = fs.readFileSync('./path/to/audio.wav');
const audioBuffer = await decodeAudio(myAudio);
const channelData = audioBuffer.getChannelData(0); // only accepts mono
const base64AudioData = base64EncodeAudio(channelData);
const event = {
type: 'conversation.item.create',
item: {
type: 'message',
role: 'user',
content: [
{
type: 'input_audio',
audio: base64AudioData
}
]
}
};
ws.send(JSON.stringify(event));
ws.send(JSON.stringify({type: 'response.create'}));
流式传输用户音频
import fs from 'fs';
import decodeAudio from 'audio-decode';
// 将 Float32Array 格式的音频数据转换为 PCM16 ArrayBuffer
function floatTo16BitPCM(float32Array) {
const buffer = new ArrayBuffer(float32Array.length * 2);
const view = new DataView(buffer);
let offset = 0;
for (let i = 0; i < float32Array.length; i++, offset += 2) {
let s = Math.max(-1, Math.min(1, float32Array[i]));
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
}
return buffer;
}
// 将 Float32Array 转换为 base64 编码的 PCM16 数据
base64EncodeAudio(float32Array) {
const arrayBuffer = floatTo16BitPCM(float32Array);
let binary = '';
let bytes = new Uint8Array(arrayBuffer);
const chunkSize = 0x8000; // 32KB 块大小
for (let i = 0; i < bytes.length; i += chunkSize) {
let chunk = bytes.subarray(i, i + chunkSize);
binary += String.fromCharCode.apply(null, chunk);
}
return btoa(binary);
}
// 用三个文件的内容填充音频缓冲区,
// 然后请求模型生成响应。
const files = [
'./path/to/sample1.wav',
'./path/to/sample2.wav',
'./path/to/sample3.wav'
];
for (const filename of files) {
const audioFile = fs.readFileSync(filename);
const audioBuffer = await decodeAudio(audioFile);
const channelData = audioBuffer.getChannelData(0);
const base64Chunk = base64EncodeAudio(channelData);
ws.send(JSON.stringify({
type: 'input_audio_buffer.append',
audio: base64Chunk
}));
});
ws.send(JSON.stringify({type: 'input_audio_buffer.commit'}));
ws.send(JSON.stringify({type: 'response.create'}));
一些概念
实时 API 是有状态的,这意味着它会在会话的生命周期内维护交互的状态。
客户端通过 WebSocket 连接到 wss:///v1/realtime,并在会话开启期间推送或接收 JSON 格式的事件。
状态
会话状态包括:
请阅读以下内容以获取有关这些对象的更多信息。
会话 (Session)
会话指的是客户端与服务器之间的单个 WebSocket 连接。
客户端创建会话后,会发送包含文本和音频块的 JSON 格式事件。服务器将以包含语音输出的音频、该语音输出的文本转录以及函数调用(如果客户端提供了函数)作为回应。
实时会话 (Realtime Session) 代表了客户端与服务器之间的整体交互,并包含默认配置。它有一组默认值,可以随时通过 session.update 进行更新。或者也可以对单个响应更新(通过 response.create)。
示例会话对象:
{
id: "sess_001",
object: "realtime.session",
...
model: "gpt-4o",
voice: "alloy",
...
}
对话
实时对话由一系列项目组成。
默认情况下,只有一个对话,它会在会话开始时创建。未来,我们可能会增加对额外对话的支持。
示例对话对象:
{
id: "conv_001",
object: "realtime.conversation",
}
项目
实时项目有三种类型:message、function_call 或 function_call_output。
客户端可以使用 conversation.item.create 和 conversation.item.delete 添加和删除 message 和 function_call_output 项目。
示例项目对象:
{
id: "msg_001",
object: "realtime.item",
type: "message",
status: "completed",
role: "user",
content: [{
type: "input_text",
text: "你好,最近怎么样?"
}]
}
输入音频缓冲区
服务器维护一个输入音频缓冲区,其中包含客户端提供的尚未提交到对话状态的音频。客户端可以使用 input_audio_buffer.append 将音频附加到缓冲区。
在服务器决策模式下,待处理的音频将被附加到对话历史中,并在VAD检测到语音结束时用于响应生成。此时,会触发一系列事件:input_audio_buffer.speech_started、input_audio_buffer.speech_stopped、mitted 和 conversation.item.created。
客户端也可以使用 mit 命令手动提交缓冲区到对话历史,而不生成模型响应。
响应
服务器的响应时间取决于 turn_detection 配置(在会话启动后通过 session.update 设置):
服务器VAD模式
在这种模式下,服务器将对传入的音频进行语音活动检测(VAD),并在语音结束时,即 VAD 触发开启和关闭后,作出响应。此模式适用于客户端到服务器始终开放音频通道的情况,并且是默认模式。
无轮次检测
在这种模式下,客户端发送一个显式消息,表示希望从服务器获得响应。此模式可能适用于按下即说的界面,或者客户端正在运行自己的 VAD。
函数调用
客户端可以在 session.update 消息中为服务器设置默认函数,或在 response.create 消息中为每个响应设置函数。 服务器将在适当的情况下响应 function_call 项。
函数作为工具传递,格式遵循 Chat Completions API,但无需指定工具的类型。
您可以在会话配置中这样设置工具:
{
tools: [
{
name: "get_weather",
description: "获取指定地点的天气",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "获取天气的地点",
},
scale: {
type: "string",
enum: ['celsius', 'farenheit']
},
},
required: ["location", "scale"],
},
},
...
}
当服务器调用一个函数时,它可能会同时响应音频和文本,例如 “好的,让我为您提交那个订单”。
description 字段在指导服务器处理这些情况时非常有用,例如“不要确认订单已完成”或“在调用工具之前回应用户”。
在发送 type 为 "function_call_output" 的 conversation.item.create 消息前,客户端必须响应函数调用
The client must respond to the function call before by sending a conversation.item.create message with type: "function_call_output".
添加函数调用输出(function call output)不会自动触发另一个模型响应,因此客户端可能希望使用 response.create 立即触发一个响应。