• 作者:老汪软件技巧
  • 发表时间: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 立即触发一个响应。