本协议描述客户端与语音对话服务 (Speech-to-Speech,下文简称 STS) 之间的实时通信规范。
LLM 模式 (默认,mode=llm):上行流式音频 → 实时语音识别 (ASR) → 大模型多轮对话 (LLM) → 语音合成 (TTS) → 下行流式音频。
Simul 同传模式 (mode=simul):上行流式音频 → 实时语音识别 (ASR) → 文本翻译服务 (Translate) → 语音合成 (TTS) → 下行流式音频。
测试工具
1. 连接与鉴权
1.1 连接地址
URL 格式如下:
wss://mps.cloud.tencent.com/sts/v1/<appid>?{请求参数}
请求参数格式如下:
key1=value1&key2=value2...(key 和 value 都需要进行 urlencode)1.2 URL 请求参数
鉴权参数(必填)
参数 | 类型 | 说明 |
secretId | string | 云 API 访问密钥 ID。 |
signature | string | |
timeStamp | int64 | 请求发起时的 UNIX 时间戳(秒)。 |
expired | int64 | 签名到期 UNIX 时间戳(秒),必须 > timeStamp 且 > 当前时间。 |
nonce | string | 一次性随机串,防重放。 |
通用业务参数
下列参数在 mode=llm 与 mode=simul 下均生效,语义与默认值完全一致。
参数 | 类型 | 必填 | 默认值 | 说明 |
mode | string | 否 | llm | 工作模式选择,可选 llm / simul;不传或非法值均按 llm 处理。 |
voice | string | 是 | – | 音色 ID (两种模式都用它做 TTS)。 |
inputFormat | string | 否 | pcm | 上行音频编码,当前仅支持 pcm。 |
inputSampleRate | int | 否 | 16000 | 上行采样率,允许区间 [8000, 48000]。 |
outputFormat | string | 否 | pcm | 下行音频编码,可选 pcm / mp3 / wav。 |
outputSampleRate | int | 否 | 16000 | 下行采样率(Hz)。 |
lang | string | 否 | zh | 源语言标识,例如 zh / en ;ASR 使用;LLM 模式下 TTS 语言与其保持一致。 |
timeoutSec | int | 否 | 30 | 会话空闲超时(秒),取值区间 [1, 120]。 |
maxSpeakMs | int | 否 | 15000 | 单句最长时长(毫秒), 0 或超过服务端上限时使用上限 (60000)。 |
resId | string | 否 | – | 资源 ID, 用于区分计费。 |
LLM 模式专属参数(仅在 mode=llm 下生效)
下列参数仅在 LLM 模式下解析并生效,在同传模式下即使传入也会被服务端忽略。
参数 | 类型 | 必填 | 默认值 | 说明 |
llmApiKey | string | 否 | – | 自定义 LLM 服务的鉴权 API Key。留空则使用自定义大模型接入的配置。 |
maxHistoryRounds | int | 否 | 50 | 单会话最多保留的对话轮数(一问一答 = 1 轮),上限100,超过将被截断。 |
同传模式专属参数(仅在 mode=simul 下生效)
下列参数仅在同传模式下解析并生效,在 LLM 模式下即使传入也会被服务端忽略。
参数 | 类型 | 必填 | 默认值 | 说明 |
dstLang | string | 是 | – | 同传目标语言(BCP47 / ISO 639-1 短码),例如 en / zh / ja;缺失时握手直接失败并返回 4001。 |
同传模式下,源语言由通用参数 lang 指定 (默认 zh );TTS 语言/音色由通用参数 voice 决定,请自行选择与 dstLang 匹配的音色。
说明:
未列出的参数以服务端默认为准。
1.3 签名算法
1. CanonicalQueryString:取 URL 上除
signature 之外的所有 query 参数,按 key 字典序升序,对每个 value 做 URL 编码,以 & 拼接。2. CanonicalRequest:
HTTPRequestMethod:post
CanonicalURI:
/sts/v1/{appid}CanonicalQueryString:第 1 步结果
CanonicalHeaders:
content-type:application/json; charset=utf-8\\nhost:mps.cloud.tencent.com\\nSignedHeaders:
content-type;hostHashedRequestPayload:
sha256("")3. 流程参考:签名生成。
4. 把参与签名的 query 参数与
signature 一并附在 URL Query 上。警告:
SecretKey 严禁出现在客户端 / 浏览器侧的明文环境。生产环境签名必须由接入方后端代签,再下发给前端用于连接。
2. 会话时序
说明:
下行音频,是否有空包,来标记语句结束,依赖音色背后的 tts 引擎,有些引擎不支持。
3. 上行消息
3.1 音频帧(ws.Binary)
编码:16-bit 有符号 PCM,小端序 (Little Endian)。
声道:单声道。
采样率:与
inputSampleRate 一致。分片大小:无固定要求,建议每 20–100 ms 发送一次以获得较低的端到端延迟。
3.2 控制信令(ws.Text,JSON)
{ "Command": "Finish" }
字段 | 类型 | 必填 | 说明 |
Command | string | 是 | 目前仅支持 Finish。 |
Finish:客户端主动通知本轮说话结束,服务端立即触发端点检测并提交最后一句 ASR。发送 Finish 后:客户端应停止再发送任何 PCM 音频帧。
服务端会尽快跑完最后一句 ASR / LLM / TTS,随后下发
ProcessEof 关闭连接。若下游长时间无响应,服务端将强制结束会话。
4. 下行消息
下行分两类通道:
通道 | 载体 | 用途 |
ws.Text | JSON | 控制信令 / 识别文本 / 模型文本 / 结束通知。 |
ws.Binary | 二进制 | TTS 音频帧、句末边界标记。 |
4.1 通用外层结构
所有 Text 类通知均为如下 JSON:
{"NotificationType": "<类型>","TaskId": "<会话 ID>","<对应字段>": { ... }}
NotificationType | 说明 | 对应字段 |
Handshake | 握手结果。 | HandshakeResult |
AsrResult | 语音识别结果。 | AsrResult |
LLMResult | 模型回复文本。 | LLMResult |
ProcessEof | 会话结束 / 错误。 | ProcessEofInfo |
4.2 Handshake
客户端连接建立后,服务端返回的第一条消息即为
Handshake。只有 Code=0 才表示可继续发音频,否则连接将被服务端关闭。{"NotificationType": "Handshake","TaskId": "app123-xxxxxxxx","HandshakeResult": {"Code": 0,"Message": "success","Format": "pcm","SampleRate": 16000,"Channels": 1,"BitDepth": 16,"InputFormat": "pcm","InputSampleRate": 16000}}
字段 | 说明 |
Code | |
Message | 错误描述。 |
Format / SampleRate / Channels / BitDepth | 协商后的下行音频参数,客户端按此解析 Binary 帧。 |
InputFormat / InputSampleRate | 服务端期望的上行音频参数。 |
4.3 AsrResult
同一句话可能多次下发,
SentenceId 相同:SteadyState=false:临时结果,Text 会不断变化,可用作实时字幕。SteadyState=true:稳态最终文本,此后同一 SentenceId 不再更新。{"NotificationType": "AsrResult","TaskId": "app123-xxxxxxxx","AsrResult": {"Text": "你好,现在天气怎么样","SteadyState": true,"SentenceId": 3,"StartPtsTime": 12.36,"EndPtsTime": 14.02}}
字段 | 类型 | 说明 |
Text | string | 识别文本;临时结果时为累计中的内容。 |
SteadyState | bool | 是否为稳态。 |
SentenceId | uint64 | 句子递增序号,同一句所有下发共享同一 ID。 |
StartPtsTime | float | 句子在会话时间轴上的起点(秒),可能省略。 |
EndPtsTime | float | 句子在会话时间轴上的终点(秒),可能省略。 |
4.4 LLMResult
模型输出以增量方式下发:
Finished=false:一片增量文本,需要客户端累加。Finished=true:本轮回答已结束,Text 可能为空,仅作结束标记。{"NotificationType": "LLMResult","TaskId": "app123-xxxxxxxx","LLMResult": {"Text": "现在是","Finished": false,"SentenceId": 3}}
SentenceId 与产生该回答的 AsrResult.SentenceId 一致,客户端可据此把问答对齐。4.5 TTS 音频帧(ws.Binary)
编码:由
HandshakeResult.Format 决定,默认为 16-bit PCM,小端。声道:单声道。
采样率:由
HandshakeResult.SampleRate 决定。句末边界标记:一帧长度为0的 Binary 包 (依赖音色,有些 tts 引擎不支持)表示当前 TTS 句子已合成完毕,与紧邻的上一批音频帧属于同一句。客户端通常无需将其送入解码器/播放器,仅用于句末计数、UI 状态切换等。
4.6 ProcessEof
会话结束通知(正常或异常),下发后服务端会立即关闭 WebSocket:
{"NotificationType": "ProcessEof","TaskId": "app123-xxxxxxxx","ProcessEofInfo": {"Code": 0,"Message": "finish"}}
Code | 含义 |
0 | 正常结束(通常是客户端发 Finish 后收尾完成)。 |
其他 |
5. 音频格式
5.1 上行
项 | 值 |
编码 | 16-bit PCM,小端。 |
声道 | 单声道。 |
采样率 | 8000 – 48000 Hz(由 inputSampleRate 指定)。 |
分片 | 建议 20–100 ms/帧。 |
5.2 下行
项 | 值 |
编码 | pcm / mp3 / wav / flac / opus / ulaw / alaw(由 outputFormat 指定,握手结果确认)。 |
声道 | 单声道。 |
采样率 | 由 outputSampleRate 指定,握手结果确认。 |
分片 | 每帧长度不固定,客户端应按流处理。 |
6. 自定义大模型接入
为满足业务侧对模型选型、私域知识注入、多模态编排等需求,服务支持将 STS 全链路中的 LLM 环节替换为您自建或第三方托管的大模型服务。您只需在自己的模型服务上实现一套与 OpenAI Chat Completions 兼容的 HTTP 接口,即可无缝接入本服务,无需改动客户端 SDK。
6.1 整体架构
STS Service:本服务,负责 ASR / LLM 调度 / TTS 全链路;LLM 环节以 HTTP 客户端身份调用您的后台。
Customer Backend:由您部署与维护,对外只需实现 OpenAI 兼容的
/chat/completions 接口;内部可自由承载 Prompt 管理、上下文管理、RAG 检索、Function Call 编排等能力。Upstream LLM:可任意选择 OpenAI / hunyuan / gemini / moonshot / minimax / deepseek 等厂商,甚至自研模型;STS 侧完全不感知具体厂商。
6.2 接入原理
说明:
STS 服务作为 客户端 主动向您的模型服务发起 HTTP 请求;您的服务只需要按下方约定返回 SSE 流式响应即可。
6.3 启用方式
1. 部署您的模型服务,暴露一个可通过公网访问的 HTTPS
baseURL。2. 提交工单开通"自定义 LLM"能力,并在工单中告知:
baseURL:您的服务根地址(要求为完整的 v1 版本前缀,例如 https://llm.example.com/v1)。model:默认模型名(对应 OpenAI 请求体中的 model 字段)。systemPrompt:可选,默认的 system 提示词。temperature:可选,默认采样温度。apiKey:可选,作为服务端下发的默认 API Key。3. 会话时通过 URL 参数
llmApiKey 传入本次会话使用的 API Key;未传时使用工单中提交的默认 apiKey。说明:
通过 URL 参数
llmApiKey 可以做到 每会话/每用户级 的密钥隔离,工单里提交的 apiKey 仅作为兜底默认值。6.4 接口约定
您的模型服务需要在
POST {baseURL}/chat/completions 上实现一个与 OpenAI 兼容的 Chat Completions 接口。请求方法
POST {baseURL}/chat/completions?taskId={taskId}Authorization: Bearer {apiKey}Content-Type: application/jsonAccept: text/event-stream
STS 服务会在 query 中额外附带
taskId(即本次 WebSocket 会话 ID),方便您做请求追踪与日志排查。Authorization 头固定为 Bearer {apiKey},取值为工单中的默认 apiKey 或 URL 参数 llmApiKey。请求体(OpenAI 兼容)
{"model": "your-model-name","stream": true,"temperature": 0.7,"messages": [{ "role": "system", "content": "你是一位乐于助人的语音助手。" },{ "role": "user", "content": "你好" },{ "role": "assistant", "content": "你好呀,有什么我可以帮你的吗?" },{ "role": "user", "content": "上海今天天气怎么样?" }]}
字段 | 类型 | 说明 |
model | string | 目标模型名,由工单中 model 决定。 |
stream | bool | 恒为 true,服务端必须以 SSE 方式流式返回。 |
temperature | float | 采样温度,可选;由工单默认配置。 |
messages | array | 完整对话上下文,包含可选的 system、历史 user/assistant 交替消息,最后一条固定为本轮 user 输入。 |
messages 数组的顺序:[system?] + 历史 (user/assistant 交替) + 本轮 user。历史轮数受 URL 参数 maxHistoryRounds 控制(一问一答 = 1 轮),最多保留最近100轮。6.5 响应格式(SSE 流式)
服务端必须以
Content-Type: text/event-stream 返回 SSE 流,每条事件形如:data: {"id":"...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null}]}data: {"id":"...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null}]}data: {"id":"...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}data: [DONE]
关键约定:
每个
data: 行是一个独立 JSON 对象,STS 服务只读取 choices[0].delta.content 作为增量文本。首片可以只带角色(
delta.role="assistant")而不含 content,STS 会忽略。生成结束时必须发送
finish_reason 非空的 chunk(stop / length / content_filter 等),并额外发送 data: [DONE] 一行作为终止标记。单条 SSE 事件之间以空行(
\\n\\n)分隔。出错时应返回 HTTP 非 2xx 状态码,或在 SSE 流中提前中断连接。
6.6 鉴权与安全
鉴权方式:Bearer Token(
Authorization: Bearer {apiKey})。建议为每个租户/子账号签发独立的
apiKey,并具备回收/轮换能力。建议启用 HTTPS 并校验
taskId 是否合法、是否重复。建议对模型服务本身做 QPS/并发限流,避免恶意占用。
6.7 错误处理
您的服务返回非 2xx 或 SSE 中途异常断开时,STS 会:
1. 立即结束本轮 LLM,向客户端下发
LLMResult (Finished=true, Text="")。2. 向客户端下发
ProcessEof (Code=4201) 并关闭 WebSocket。建议错误响应示例(HTTP 4xx/5xx,非 SSE,
application/json):{"error": {"code": "invalid_api_key","message": "invalid api key","type": "authentication_error"}}
6.8 端到端示例
STS → 您的模型服务
POST https://llm.example.com/v1/chat/completions?taskId=1258344699-wsss2s-xxxx HTTP/1.1Authorization: Bearer sk-your-keyContent-Type: application/jsonAccept: text/event-stream{"model": "your-model-name","stream": true,"messages": [{ "role": "system", "content": "你是一位乐于助人的语音助手。" },{ "role": "user", "content": "上海今天天气怎么样?" }]}
您的模型服务 → STS(SSE 流)
HTTP/1.1 200 OKContent-Type: text/event-streamdata: {"choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}data: {"choices":[{"index":0,"delta":{"content":"上海"},"finish_reason":null}]}data: {"choices":[{"index":0,"delta":{"content":"今天多云,"},"finish_reason":null}]}data: {"choices":[{"index":0,"delta":{"content":"最高 28℃。"},"finish_reason":null}]}data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}data: [DONE]
客户端最终收到(经 STS 转发)
{"NotificationType":"LLMResult","LLMResult":{"Text":"上海","Finished":false,"SentenceId":1}}{"NotificationType":"LLMResult","LLMResult":{"Text":"今天多云,","Finished":false,"SentenceId":1}}{"NotificationType":"LLMResult","LLMResult":{"Text":"最高 28℃。","Finished":false,"SentenceId":1}}{"NotificationType":"LLMResult","LLMResult":{"Text":"","Finished":true,"SentenceId":1}}
随后 STS 会把上述文本送入 TTS 引擎并将合成音频以 Binary 帧下发给客户端。
6.9 常见问题
可以不返回 SSE,而是直接返回一个完整 JSON 吗?
不可以。STS 依赖流式增量文本驱动 TTS 边生成边合成,非流式响应会导致端到端延迟显著增加,服务端会当作错误处理。
messages 里的 system 提示词能否由客户端覆盖?
暂不支持客户端级覆盖,
system 提示词以工单中的 systemPrompt 为准。temperature 能否会话级指定?
暂不支持通过 URL 参数指定,统一使用工单默认值。
如何在自己的服务里做业务鉴权?
STS 会透传
Authorization: Bearer {llmApiKey} 与 ?taskId=;您可以基于 llmApiKey 做单次会话的鉴权、基于 taskId 来做追踪记录。7. 错误码
错误码 | 语义 |
0 | 成功。 |
4001 | 请求参数不合法。 |
4002 | 空闲超时(长时间未收到上行音频)。 |
4003 | 上行音频数据格式不合法。 |
4004 | 并发连接数超限,默认是2。 |
4005 | 账户状态不可用(欠费 / 冻结等)。 |
4100 | 鉴权失败:签名无效。 |
4101 | 未授权访问该接口。 |
4102 | 未授权访问该资源。 |
4104 | secretId 不存在。 |
4105 | 会话 ID 错误。 |
4106 | MFA 校验失败。 |
4110 | 其它鉴权失败。 |
4111 | AppID 不合法。 |
4200 | 语音识别上游错误。 |
4201 | 大模型上游错误。 |
4202 | 语音合成上游错误。 |
4203 | 上行控制信令 JSON 非法。 |
4500 | 重放攻击。 |
5000 | 服务内部错误。 |
4xxx 均为客户端可自查/可重试类错误。
5xxx 表示服务侧临时故障,客户端可延迟后重试。
说明:
8. 断开与超时
空闲超时:
timeoutSec 秒内未收到上行音频,服务端下发 ProcessEof (Code=4002) 并关闭连接。业务错误:任一环节出错,服务端下发
ProcessEof (Code=4xxx/5xxx) 并关闭连接。主动结束:客户端发送
{"Command":"Finish"} 后,服务端跑完最后一句再下发 ProcessEof (Code=0) 并关闭连接。客户端关闭:客户端可以随时直接 close WebSocket,服务端将释放本会话所有资源。
9. 完整示例
以下为一次典型会话的消息流:
▶ Client → Server (WS Upgrade)wss://mps.cloud.tencent.com/sts/v1/1258344699/?inputFormat=pcm&inputSampleRate=16000&outputFormat=pcm&outputSampleRate=16000&lang=zh&voice=xxx&timeoutSec=30&secretId=AKIDxxx&nonce=1234567890&timeStamp=1783603200&expired=1783606800&signature=<hex>◀ Server → Client (Text){ "NotificationType":"Handshake", "TaskId":"...","HandshakeResult":{"Code":0,"Message":"success","Format":"pcm","SampleRate":16000,"Channels":1,"BitDepth":16,"InputFormat":"pcm","InputSampleRate":16000} }▶ Client → Server (Binary × N) ← 16kHz/16bit/mono PCM◀ Server → Client (Text){ "NotificationType":"AsrResult", "TaskId":"...","AsrResult":{"Text":"你好","SteadyState":false,"SentenceId":1} }◀ Server → Client (Text){ "NotificationType":"AsrResult", "TaskId":"...","AsrResult":{"Text":"你好呀","SteadyState":true,"SentenceId":1,"StartPtsTime":1.20,"EndPtsTime":2.05} }◀ Server → Client (Text){ "NotificationType":"LLMResult", "TaskId":"...","LLMResult":{"Text":"你好,","Finished":false,"SentenceId":1} }◀ Server → Client (Text){ "NotificationType":"LLMResult", "TaskId":"...","LLMResult":{"Text":"很高兴见到你。","Finished":false,"SentenceId":1} }◀ Server → Client (Text){ "NotificationType":"LLMResult", "TaskId":"...","LLMResult":{"Text":"","Finished":true,"SentenceId":1} }◀ Server → Client (Binary × M) ← TTS 音频帧◀ Server → Client (Binary, length=0) ← SentenceEnd 边界▶ Client → Server (Text){ "Command":"Finish" }◀ Server → Client (Text){ "NotificationType":"ProcessEof", "TaskId":"...","ProcessEofInfo":{"Code":0,"Message":"finish"} }✖ Server closes WebSocket