登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

GPT-Live双工语音会话接入后端

来源:17golang原创

时间:2026-11-15 12:55:53 470浏览 收藏

GPT-Live 双工语音会话接入后端时,关键不是把一段音频“发过去”就结束,而是把连接、会话、媒体事件和后台委派分开管理。当前官方 Live 主 WebSocket 使用 wss://api.openai.com/v1/live/sessions,API key 只放在可信后端;连接建立后必须先发送 session.start,等到 session.started 再发送后续应用事件。

官方地址:https://developers.openai.com/api/docs/guides/voice-websockets

下面以 Go 服务端为例,先把会话生命周期跑通,再说明双工音频和后台 Agent 应该放在哪里。示例使用官方 Go SDK 的 Live 接口,不把 API key 暴露给浏览器。

先判断连接问题出在哪一层

实际排查时,最容易混淆的是三种状态:WebSocket 是否连上、Live 会话是否启动、媒体或后台任务是否已经工作。TCP/WS 连接成功只能说明传输层可用;收到 session.started 才说明服务端接受了模型和会话配置;音频播放、转写或委派结果还要继续观察各自的事件。

Go后端、Live主WebSocket与GPT-Live会话生命周期的静态结构图
图1:说明图。把 Go 后端、主 WebSocket、会话启动确认和关闭事件放在同一条生命周期边界中,避免把连接成功误当成会话可用。

这也是后端接入的第一条判断线:如果没有 session.started,就不要向连接写入音频或委派控制;如果连接在 session.closed 之前断开,最终用量和结束原因都不能当成已确认结果。

用官方 Go 客户端建立主 WebSocket

安装 SDK 时把依赖固定在项目自己的 Go 模块中,密钥通过环境变量注入。不要把密钥写进前端、日志或提交记录。

# 安装官方 Go SDK;版本应按项目变更记录固定
go get github.com/openai/openai-go/v3@v3.98.0

# 只在后端进程环境中提供密钥,不要写入源码
export OPENAI_API_KEY="你的后端密钥"

官方 Go 示例使用 client.Live.Connect 建立 Live 连接。与传统 Realtime WebSocket 不同,Live 主连接不需要把模型拼进 URL 查询参数,模型名放在第一条 session.start 的会话配置中。

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	openai "github.com/openai/openai-go/v3"
	"github.com/openai/openai-go/v3/live"
)

func main() {
	if err := observeLiveSession(); err != nil {
		log.Fatal(err)
	}
}

func observeLiveSession() error {
	// 给连接设置总超时,避免上游失联后 goroutine 和连接长期悬挂。
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	// SDK 默认从 OPENAI_API_KEY 读取密钥;客户端只在后端创建。
	client := openai.NewClient()
	connection, err := client.Live.Connect(ctx, live.LiveConnectionOptions{})
	if err != nil {
		return fmt.Errorf("连接 GPT-Live 失败: %w", err)
	}
	// 无论启动、接收还是关闭出现错误,都释放底层传输资源。
	defer connection.Close()

	// Live 主 WebSocket 的第一条应用消息必须是 session.start。
	if err := connection.Send(ctx,
		live.ClientEventParamOfSessionStart(
			live.SessionConfigParam{Model: "gpt-live-1"},
		),
	); err != nil {
		return fmt.Errorf("发送 session.start 失败: %w", err)
	}

	closeRequested := false
	for {
		received, err := connection.Recv(ctx)
		if err != nil {
			// 没等到 session.closed,就不能声称本次会话已正常结束。
			return fmt.Errorf("在 session.closed 前断开,最终用量未确认: %w", err)
		}

		switch event := received.AsAny().(type) {
		case live.ErrorEvent:
			return fmt.Errorf("GPT-Live 返回会话错误")
		case live.SessionStartedEvent:
			// 只有服务端确认启动后,才进入音频和应用事件阶段。
			fmt.Println("session ready:", event.Session.ID, event.Session.Model)
			err = connection.Send(ctx, live.ClientEventUnionParam{
				OfSessionClose: &live.SessionCloseEventParam{},
			})
			closeRequested = err == nil
		case live.SessionClosedEvent:
			// 只接受自己请求的正常关闭,其他原因按异常收尾。
			if !closeRequested || event.Reason != "close_requested" {
				return fmt.Errorf("会话异常结束: reason=%s usage=%v", event.Reason, event.Usage)
			}
			fmt.Println("final usage:", event.Usage)
			return nil
		}
		if err != nil {
			return err
		}
	}
}

这段代码故意在收到 session.started 后立即关闭,目的是展示最小可控生命周期。接入真实音频时,把关闭分支替换为业务会话循环即可;不要删除超时和终端事件判断。

把双工音频和会话控制分开

双工的含义是模型可以同时听和说,但后端仍要分别处理媒体与控制:输入音频以事件形式追加到会话,输出音频增量交给播放器或下游媒体层;session.update、静音、提示追加和关闭则属于控制面。这样做的好处是音频高频到达时,不会把会话状态机和业务数据库操作混在同一个写循环中。

GPT-Live来话音频、输出音频、会话控制与后台Agent的静态边界图
图2:结构图。媒体边界承载输入与输出音频,控制与委派边界承载会话更新、后台任务和播报补充信息,两者共享会话但不共享业务职责。

如果使用底层 WebSocket 自己编码事件,事件本体是 JSON,音频块通常以 Base64 字段传递。不要把音频字节直接当作 JSON 文本发送;也不要在接收循环里同步执行耗时的订单查询、数据库写入或外部 HTTP 请求。

一个简化的输入事件形状如下,字段含义以当前 API 文档为准:

{
  "type": "input_audio.append",
  "audio": "BASE64_PCM_CHUNK"
}

推荐把“读事件”和“业务处理”拆成两个有界通道:读循环只负责及时接收并分派;音频播放、转写展示和后台任务各自消费自己的事件类型。通道满时要有明确的丢弃、背压或断开策略,不要无限增长内存队列。

把后台委派写进会话协议

GPT-Live 适合连续对话,但详细业务流程、工具权限和确认规则应该放在后端。官方提示建议让 session.instructions 保持短小,说明角色、说话风格、打断策略和何时请求后台工作;真正的查订单、改预约或执行工具,交给后端 Agent。

这个边界尤其重要:语音模型说“我正在查询”不等于后台任务已经启动。后端需要以明确的委派事件、工具调用记录和返回结果作为事实,再通过会话评论或结果事件让模型继续播报。涉及下单、改签、扣费等操作时,还应由应用服务检查权限和必要确认,不能只依赖提示词。

# 会话提示只描述对话行为与委派条件
You are a calm support voice assistant.
Backchannel policy: use short acknowledgements without interrupting the caller.
Interruption policy: stop speaking when the caller interrupts.
Delegation policy: request backend work for account lookup or booking changes.
Do not announce a completed action until the backend returns its result.

后端 Agent 的输入应包含可追踪的会话 ID、用户授权上下文和幂等键;工具返回后再决定是继续语音回答、要求补充信息,还是结束会话。不要把密钥、内部错误堆栈和未经筛选的数据库结果直接塞进语音提示。

错误与关闭要按“证据”收尾

接入后常见的误判有三种:

  • 连接成功但没有 session.started:只能说明握手完成,不能开始发送应用事件。
  • 收到一个错误事件后继续写音频:错误已经属于会话协议层,应停止当前会话并记录关联的事件 ID。
  • 本地 Close 返回后立即记成功:真正的正常结束应等待服务端 session.closed,并检查关闭原因。

生产代码还应给读循环加取消信号,避免客户端断开后后台任务继续持有音频缓冲;对可重试的连接错误区分“尚未发送”和“可能已经发送”,不要盲目重放带副作用的业务命令。重连后也要重新恢复会话配置和必要上下文,而不是假设服务端仍保留旧连接状态。

一张表记住后端接入边界

对象后端责任不能直接推断的结论
主 WebSocket使用服务端 API key 建立和释放连接握手成功不代表会话已启动
session.start / started发送启动配置并等待服务端确认started 之前不要写应用事件
输入/输出音频分派、缓冲、播放或转码音频到达不代表后台任务完成
后台 Agent权限检查、工具调用、结果和幂等语音模型的口头承诺不是执行凭证
session.closed读取最终原因和用量后再记账连接断开不等于正常关闭

因此,GPT-Live 接入后端的最小可靠路径是:后端保管密钥,建立主 WebSocket,发送 session.start,等待 session.started,将媒体与控制分流,把需要事实确认的工作交给后台 Agent,最后只以 session.closed 或明确错误作为会话终态。

相关问题

GPT-Live 一定要从浏览器直连吗?

不需要。服务端到服务端可以由后端使用标准 API key 连接;浏览器或移动端通常应采用官方推荐的 WebRTC 方案,避免把长期 API key 放到客户端。

为什么不能把模型名放进 Live WebSocket URL?

当前 Live 主 WebSocket 的模型属于 session.start 配置,连接地址不带模型查询参数。不要把 Realtime API 的旧连接习惯直接套到 Live。

没有收到 session.closed 时如何记一次会话?

把它记为未确认结束,保留超时、传输错误和最后一个服务端事件。只有收到终端事件并检查原因后,才把正常关闭和最终用量写入结算或审计记录。

参考资料:https://developers.openai.com/api/reference/go/resources/live/methods/connect、https://developers.openai.com/api/docs/guides/live-prompting、https://developers.openai.com/api/docs/models/gpt-live-1

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>