fix(voice): 改用火山「新版控制台」API Key 鉴权(ASR 握手真机验通✅)
一直误用旧版 5 头方案(X-Api-App-Key+Access-Key+App-ID),火山始终 401 grant not found。 用户提供官方文档「新版本控制台」证实:新版只需**单个 X-Api-Key** 头。改对后真机联调: - frame.go setVolcAuthHeaders: X-Api-Key + X-Api-Resource-Id + X-Api-Request-Id + X-Api-Sequence:-1 (删旧 App-Key/Access-Key/App-ID/Connect-Id;去 appID 参数) - asr/tts 调用点同步;config.go 删 Config.AppID;voicecheck/voiceconfig 删 VOLC_APP_ID - 删临时诊断工具 voiceprobe 真机结果:ASR 握手✅(volc.bigasr.sauc.duration 有效);TTS 用 seed-tts-2.0 会话正常建立 (不再 resource mismatch),但暂无音频返回→双向 TTS 事件/payload 待调(下一步)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,6 @@ import (
|
||||
func main() {
|
||||
cfg := voice.Config{
|
||||
APIKey: os.Getenv("VOLC_API_KEY"),
|
||||
AppID: os.Getenv("VOLC_APP_ID"),
|
||||
ASRResourceID: os.Getenv("VOLC_ASR_RESOURCE_ID"),
|
||||
TTSResourceID: os.Getenv("VOLC_TTS_RESOURCE_ID"),
|
||||
TTSVoiceType: os.Getenv("VOLC_TTS_VOICE"),
|
||||
|
||||
@@ -32,7 +32,6 @@ func main() {
|
||||
}
|
||||
cfg := voice.Config{
|
||||
APIKey: os.Getenv("VOLC_API_KEY"),
|
||||
AppID: os.Getenv("VOLC_APP_ID"),
|
||||
ASRResourceID: os.Getenv("VOLC_ASR_RESOURCE_ID"),
|
||||
TTSResourceID: os.Getenv("VOLC_TTS_RESOURCE_ID"),
|
||||
TTSVoiceType: os.Getenv("VOLC_TTS_VOICE"),
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
// 火山引擎 V3 大模型流式语音识别(SeedASR / SAUC)客户端。协议见 voice/frame.go 与 voice-jarvis 记忆。
|
||||
// 端点固定;鉴权走**新版 API Key**(X-Api-Access-Key + 固定 App Key + App ID,见 frame.go)。
|
||||
// 端点固定;鉴权走**新版控制台 API Key**(单个 X-Api-Key,见 frame.go)。
|
||||
|
||||
const asrEndpoint = "wss://openspeech.bytedance.com/api/v3/sauc/bigmodel"
|
||||
|
||||
@@ -91,7 +91,7 @@ func StartASR(ctx context.Context, cfg Config, uid string) (*ASRSession, error)
|
||||
return nil, fmt.Errorf("ASR 未配置")
|
||||
}
|
||||
hdr := http.Header{}
|
||||
setVolcAuthHeaders(hdr, cfg.APIKey, cfg.AppID, cfg.ASRResourceID)
|
||||
setVolcAuthHeaders(hdr, cfg.APIKey, cfg.ASRResourceID)
|
||||
|
||||
dialer := websocket.Dialer{HandshakeTimeout: 10 * time.Second}
|
||||
conn, resp, err := dialer.DialContext(ctx, asrEndpoint, hdr)
|
||||
|
||||
@@ -8,8 +8,7 @@ import "github.com/sundynix/sundynix-shared/secrets"
|
||||
// Authorization(Bearer <APIKey>)+ X-Api-Resource-Id(区分服务,ASR 与双向 TTS 各一个)。
|
||||
// 端点/音频格式(PCM 16k 单声道等)由代码固定,不入用户配置。
|
||||
type Config struct {
|
||||
APIKey string `json:"api_key"` // 新版 API Key(X-Api-Access-Key,密文入库)
|
||||
AppID string `json:"app_id"` // 火山 App ID(X-Api-App-ID,解析账号资源授权用;V3 流式端点需要)
|
||||
APIKey string `json:"api_key"` // 新版控制台 API Key(X-Api-Key,密文入库)
|
||||
ASRResourceID string `json:"asr_resource_id"` // 流式语音识别 X-Api-Resource-Id
|
||||
TTSResourceID string `json:"tts_resource_id"` // 双向流式 TTS X-Api-Resource-Id
|
||||
TTSVoiceType string `json:"tts_voice_type"` // 音色(如 zh_male_… / BV700_streaming)
|
||||
|
||||
@@ -8,25 +8,18 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// volcAppKey 是火山语音网关 V3 端点(ASR/TTS/实时对话)**固定**的 App Key 常量——所有客户端
|
||||
// 都用同一个值(官方 demo 硬编码),与账号无关;真正的账号鉴权走 X-Api-Access-Key(新版 API Key)。
|
||||
// 缺这个头握手会被拒:HTTP 400 "app key not found in header or query"。
|
||||
const volcAppKey = "PlgvMymc7f3tQnJ6"
|
||||
|
||||
// setVolcAuthHeaders 按火山 V3 二进制流端点的鉴权规范挂头:固定 App Key + 账号 App ID +
|
||||
// 新版 API Key(Access Key) + 资源 ID + 连接 ID。**不是** Authorization: Bearer(那是 OpenAI
|
||||
// 兼容端点用的,流式二进制端点不认)。App ID 用于解析账号资源授权(缺它会 401 grant not found)。
|
||||
func setVolcAuthHeaders(hdr http.Header, apiKey, appID, resourceID string) {
|
||||
// 直接赋 map(不走 Set)以**保留精确大小写**:Go 的 Header.Set 会把 "X-Api-App-ID"
|
||||
// 规范化成 "X-Api-App-Id",而火山网关按精确大小写匹配(官方 demo 用 X-Api-App-ID)。
|
||||
// 新版 API Key 走 X-Api-Access-Key;X-Api-App-Key 是网关固定常量,缺它 400 "app key not found"。
|
||||
hdr["X-Api-App-Key"] = []string{volcAppKey}
|
||||
hdr["X-Api-Access-Key"] = []string{apiKey}
|
||||
// setVolcAuthHeaders 按火山「新版控制台」API Key 鉴权挂头(文档 6561/1354869「新版本控制台」表)。
|
||||
// 新版只需**单个 X-Api-Key**(控制台的 APP Key)——**不用**旧版的 X-Api-App-Key+X-Api-Access-Key+App-ID,
|
||||
// 也不是 Authorization: Bearer。此前误用旧 5 头方案,火山始终 401 "grant not found in SaaS storage"。
|
||||
// - X-Api-Key = 控制台 API Key(用户那把 key)
|
||||
// - X-Api-Resource-Id= 资源 ID(如 volc.bigasr.sauc.duration / volc.seedasr.sauc.duration / volc.service_type.10029)
|
||||
// - X-Api-Request-Id = 随机 UUID
|
||||
// - X-Api-Sequence = 固定 "-1"
|
||||
func setVolcAuthHeaders(hdr http.Header, apiKey, resourceID string) {
|
||||
hdr["X-Api-Key"] = []string{apiKey}
|
||||
hdr["X-Api-Resource-Id"] = []string{resourceID}
|
||||
hdr["X-Api-Connect-Id"] = []string{newConnectID()}
|
||||
if appID != "" {
|
||||
hdr["X-Api-App-ID"] = []string{appID}
|
||||
}
|
||||
hdr["X-Api-Request-Id"] = []string{newConnectID()}
|
||||
hdr["X-Api-Sequence"] = []string{"-1"}
|
||||
}
|
||||
|
||||
// handshakeDetail 从失败的 WS 握手响应里榨出可诊断信息:HTTP 状态 + 火山排障用的
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// 火山「双向流式 TTS V3」客户端(seed-tts-2.0)。边推文字边收音频,配 LLM token 流做连续朗读。
|
||||
// 帧协议见 tts_frame.go;鉴权走**新版 API Key**(X-Api-Access-Key + 固定 App Key + App ID,见 frame.go)。
|
||||
// 帧协议见 tts_frame.go;鉴权走**新版控制台 API Key**(单个 X-Api-Key,见 frame.go)。
|
||||
//
|
||||
// 一轮朗读的生命周期:StartTTS(连接+StartSession) → Speak(逐句推文字/事件200) → Finish(FinishSession)
|
||||
// → 客户端 range Audio() 收音频直到 channel 关闭 → Close()。一个 TTSSession = 一次 Agent 回答。
|
||||
@@ -58,7 +58,7 @@ func StartTTS(ctx context.Context, cfg Config) (*TTSSession, error) {
|
||||
return nil, fmt.Errorf("TTS 未配置")
|
||||
}
|
||||
hdr := http.Header{}
|
||||
setVolcAuthHeaders(hdr, cfg.APIKey, cfg.AppID, cfg.TTSResourceID)
|
||||
setVolcAuthHeaders(hdr, cfg.APIKey, cfg.TTSResourceID)
|
||||
|
||||
dialer := websocket.Dialer{HandshakeTimeout: 10 * time.Second}
|
||||
conn, resp, err := dialer.DialContext(ctx, ttsEndpoint, hdr)
|
||||
|
||||
Reference in New Issue
Block a user