使用 OpenAI 构建实时音频服务

힘센캥거루
2025년 2월 7일(수정됨)
5
77

虽然 Deepseek R1 已经发布,但 Open AI 提供的 API 依然优秀且具有吸引力,这无可争辩。

今天,我们将使用 Open AI 的实时 API 来构建一个实时音频网络服务。

1. 什么是实时 API?

使用 OpenAI 构建实时音频服务-1

这是 OpenAI 于 2024 年 10 月 1 日推出的服务,支持实时语音输入输出。

以前,为了使用语音与 chatGPT 进行互动,需要利用 Whisper 等语音识别模型将音频转换为文本,然后发送文本,再使用文本-语音转换输出模型的响应。

这种方式往往会产生较长的延迟。

实时 API 可以直接实现音频输入输出。

使用 GPT-4o 模型和 Websocket、WebRTC,可以实时实现音频输入输出。

详细信息请查看官方网站

2. Open AI 实时模块

使用 OpenAI 构建实时音频服务-2

虽然刚推出没多久,但哪位天使已经实现了 API 并作为开源项目上传到 GitHub。

主页本身也很美,我以为是 vercel 提供的 SDK。

以下是创作者主页

使用 OpenAI 构建实时音频服务-3

进入安装部分,没有 yarn 或 npm 之类的,只是让你取所需部分使用。

代码也都有。

稍微阅读一下,只需将需要的部分移到我的项目中。

有 Classic、Dock、Siri 等多种格式,其中我最喜欢的是 ChatGPT 版本。

使用 OpenAI 构建实时音频服务-4

3. 实现 (Ctrl + C & V)

这几乎不能算作实现,更像是简单复制粘贴。

首先,安装所有依赖项。

我选择的模型只需要一个依赖。

yarn add framer-motion

然后增加一个钩子。

完整代码请查看文档中的创建 WebRTC 钩子

使用 OpenAI 构建实时音频服务-5
"use client";
 
import { useState, useRef, useEffect } from "react";
import { Tool } from "@/lib/tools";

const useWebRTCAudioSession = (voice: string, tools?: Tool[]) => {
  const [status, setStatus] = useState("");
  const [isSessionActive, setIsSessionActive] = useState(false);
  const audioIndicatorRef = useRef<HTMLDivElement | null>(null);
  const audioContextRef = useRef<AudioContext | null>(null);
  const audioStreamRef = useRef<MediaStream | null>(null);
  const peerConnectionRef = useRef<RTCPeerConnection | null>(null);

生略...

老实说,为了快速开发,我盲目地增加...

根据使用的模型,在 modalities 中适当增加或删除音频或文本。

如果不喜欢日志输出的话,可以所有 console.log 都去掉。

为创建 websocket 生成 session 路径。

import { NextResponse } from 'next/server';

export async function POST() {
    try {        
        if (!process.env.OPENAI_API_KEY){
            throw new Error(`OPENAI_API_KEY is not set`);

        }
        const response = await fetch("https://api.openai.com/v1/realtime/sessions", {
            method: "POST",
            headers: {
                Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                model: "gpt-4o-mini-realtime-preview",
                voice: "alloy",
                modalities: ["audio", "text"],
                // instructions:"You are a helpful assistant for the website named OpenAI Realtime Blocks, a UI Library for Nextjs developers who want to integrate pre-made UI components using TailwindCSS, Framer Motion into their web projects. It works using an OpenAI API Key and the pre-defined 'use-webrtc' hook that developers can copy and paste easily into any Nextjs app. There are a variety of UI components that look beautiful and react to AI Voice, which should be a delight on any modern web app.",
                tools: tools,
                tool_choice: "auto",
            }),
        });

        if (!response.ok) {
            throw new Error(`API request failed with status ${response.status}`);
        }

        const data = await response.json();

        // 返回 JSON 响应给客户端
        return NextResponse.json(data);
    } catch (error) {
        console.error("Error fetching session data:", error);
        return NextResponse.json({ error: "Failed to fetch session data" }, { status: 500 });
    }
}

const tools = [
    {
        "type": "function",
        "name": "getPageHTML",
        "description": "获取当前页面的 HTML",
        "parameters": {
            "type": "object",
            "properties": {}
        }
    },
    {
        "type": "function", 
        "name": "getWeather",
        "description": "获取当前天气",
        "parameters": {
            "type": "object",
            "properties": {}
        }
    },
    {
        "type": "function",
        "name": "getCurrentTime",
        "description": "获取当前时间",
        "parameters": {
            "type": "object",
            "properties": {}
        }
    },
];

出于谨慎提醒一下,请勿忘记在 .env.local 文件中设置 API 密钥

进入文档 可以看到 chatgpt.tsx 和 page.tsx 完整源码。

让我们复制这些。

如下所示配置。

import React, { useEffect, useState } from "react";
import { motion } from "framer-motion";
import useWebRTCAudioSession from "@/hooks/use-webrtc";
 
const ChatGPT: React.FC = () => {
  const { currentVolume, isSessionActive, handleStartStopClick, msgs } =
    useWebRTCAudioSession("alloy");
 
  const silenceTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
 
  const [mode, setMode] = useState<"idle" | "thinking" | "responding" | "volume" | "">(
    ""
  );
  const [volumeLevels, setVolumeLevels] = useState([0, 0, 0, 0]);

  ....生略

还创建了一个 page.tsx。

import ChatGPT from "@/components/aiChat/AudioChatGPT";
 
export default function Page() {
  return (
    <main className="flex items-center justify-center h-screen">
      <ChatGPT />
    </main>
  );
}

现在测试运行,可以看到正常工作。

我还没有实现暗模式,所以将 svg 的颜色更改为灰色。

使用 OpenAI 构建实时音频服务-6

4. 经验

费用是每分钟音频输入 $0.06,每次输出 $0.24。

这并不直观,但今天简单测试了一下,发现如下成本。

使用 OpenAI 构建实时音频服务-7

过去两个月使用 streamText 花了 2 美元,但今天一天就花了 1.5 美元...

当然比直接付款便宜得多,但还是比预期贵。

我太太想让我实现一个用于英语学习的 GPT,可以试试这个。

관련 글

Next.js 全栈博客开发记
Next.js 全栈博客开发记
在第一次接触网页开发大约 1 年之后,我开始想着想要拥有一个属于自己的博客。于是我就大概花了 6 个月几乎只埋头做这件事。前端部分的功能,参考金度亨先生的博客就已经足够了。我用 mdx 搭建起一个博客,其实连一周都没花到。事实上,仅靠前端也并不会给博客运营带来什么大问题。有一阵子我也觉得,只要写 m...
使用 Caddy 实现 Next.js 无停机部署(本地服务器)
使用 Caddy 实现 Next.js 无停机部署(本地服务器)
每次一有想在主页上加点什么的念头就去 build,结果中间好像偶尔会有人访问。于是 Search Console 上的分数开始一点点往下掉。觉得这样不行,就开始思考要怎么做无停机部署。1. 两个项目文件夹 + 两个终端答案出乎意料地简单:开两个终端。在一个终端里 build,另一个终端里让服务器跑着...
谷歌搜索索引自动化 - Web Search Indexing API
谷歌搜索索引自动化 - Web Search Indexing API
继上次搞完 IndexNow 之后,也决定在谷歌这边做一下自动化。查了一下,发现谷歌是通过一个叫 Web Search Indexing 的 API 来支持这个功能的。1. 适用范围官方文档中,这个 API 正式支持的范围是招聘公告和流媒体视频服务。说是为了给对实时性要求高的内容创建索引用的,但搜了...
搜索索引生成自动化 - IndexNow
搜索索引生成自动化 - IndexNow
在向 Bing 提交站点收录时才发现,Bing 提供了一个叫做 IndexNow 的功能。核心在于,可以利用 API Key,在写完文章的瞬间就立刻发起索引请求。只要用 fetch 写出类似下面这样的请求,并把它串联到「写文章」流程里,就可以在把文章保存到 DB 的同时发送索引生成请求。POST /...
被入侵的 Nextjs、React 服务器经历
被入侵的 Nextjs、React 服务器经历
我最初接触到这次安全问题是在 12 月 5 日凌晨。据说在 React 中可以在未认证的情况下进行远程代码执行。看到这条新闻后,我虽然告诉了别人,但总觉得自己应该没事,就什么想法也没有地略过了。1. 发现被入侵的痕迹结果当我想登录更新博客代码时,在终端里发现了有命令被执行过的痕迹。/bin/sh:...
在博客中添加多语言功能(NextJS、next-intl、Vercel AI SDK)
在博客中添加多语言功能(NextJS、next-intl、Vercel AI SDK)
最近我觉得博客需要多语言功能。于是决定用 next-intl 来实现多语言服务。1.i18n首先,在做多语言服务时有一些必须遵守的原则。这被称为 internationalization,单词太长,所以把首字母 i 和尾字母 n,以及中间 18 个字母合在一起,写成 i18n。1) i18n 的原则...

댓글을 불러오는 중...