Skip to Content
开发指南语音对话开发指南

语音对话(Chat Completion 接口)

阶跃星辰端到端语音模型支持在对话过程中传入音频内容,并生成语音回复。用户可以通过 Chat Completion API 发送语音输入,模型会同时返回文本和音频响应,实现真正的语音到语音交互体验。

推荐使用 step-audio-2step-audio-2-mini 模型,这些模型具备最新的语音理解和生成能力。

能力概述

  • 语音输入理解:支持用户通过语音提问,模型可以理解语音中的内容和语调
  • 语音输出生成:模型可以直接生成语音回复,无需额外的 TTS 步骤
  • 多种声音选择:支持多种预置声音风格,满足不同场景需求
  • 流式输出:支持流式返回音频数据,实现低延迟的语音交互
  • 工具调用:支持用户自定义函数调用

核心参数

在调用 chat completions 接口时,指定 model 为以下模型,可以获得语音回复能力。

模型名称说明
step-audio-2最新的端到端语音模型,支持语音输入输出、toolcall
step-audio-2-mini轻量版端到端语音模型,轻便快速
step-1o-audio上一代语音模型,音色多样

audio 参数

audio 参数用于控制音频输出,只在端到端语音模型下生效。

  • voice string required
    指定生成音频的声音ID。对于 step-audio-2 系列模型,可用以下声音:

    • wenrounansheng(温柔男声)
    • qingchunshaonv(青春少女)
    • livelybreezy-female(活力少女)
    • elegantgentle-female(高雅女声)

    对于 step-1o-audio 模型,可通过获取声音列表接口查询可用的声音ID。

  • format string required
    指定生成音频的格式:

    • wav:在非流式场景下(stream=false)支持
    • pcm:在流式场景下(stream=true)使用,格式为 24kHz、单声道、16bit

modalities 参数

modalities 参数指定输出的模态类型,支持 textaudio 两种类型。

如果需要模型输出音频,则需要将 audio 添加到该参数中,例如:["text", "audio"]

快速开始

文本输入,语音输出

最简单的使用方式是发送文本消息,让模型返回语音回复。

import base64 from openai import OpenAI client = OpenAI(api_key="STEP_API_KEY", base_url="https://api.stepfun.com/v1") completion = client.chat.completions.create( model="step-audio-2", modalities=["text", "audio"], messages=[ { "role": "system", "content": "你是一个老北京大爷,喜欢用京片子和用户聊天。", }, { "role": "user", "content": "今儿个北京的天气咋样啊?", } ], audio={ "voice": "wenrounansheng", "format": "wav", }, stream=False, ) # 获取文字回复 print(completion.choices[0].message.audio.transcript) # 保存音频文件 with open("response.wav", "wb") as f: f.write(base64.b64decode(completion.choices[0].message.audio.data)) # 打开 response.wav 即可收听音频回复

语音输入,语音输出

支持用户通过语音提问,模型返回语音回复,实现端到端的语音对话。

import base64 from openai import OpenAI client = OpenAI(api_key="STEP_API_KEY", base_url="https://api.stepfun.com/v1") # 读取本地音频文件并转换为 base64 with open("question.wav", "rb") as f: audio_data = base64.b64encode(f.read()).decode('utf-8') completion = client.chat.completions.create( model="step-audio-2", modalities=["text", "audio"], messages=[ { "role": "system", "content": "你是一个动物声音识别专家,请根据用户提供的声音进行识别和回答。", }, { "role": "user", "content": [ { "type": "text", "text": "听听这是什么声音?", }, { "type": "input_audio", "input_audio": { "data": f"data:audio/wav;base64,{audio_data}", } } ], } ], audio={ "voice": "qingchunshaonv", "format": "wav", }, stream=False, ) # 获取文字回复 print(completion.choices[0].message.audio.transcript) # 保存音频文件 with open("response.wav", "wb") as f: f.write(base64.b64decode(completion.choices[0].message.audio.data))

流式语音输出

stream=true 时,API 会以流式方式返回音频数据,实现超低延迟的语音交互体验。

import base64 import struct from openai import OpenAI client = OpenAI(api_key="STEP_API_KEY", base_url="https://api.stepfun.com/v1") completion = client.chat.completions.create( model="step-audio-2", modalities=["text", "audio"], messages=[ { "role": "user", "content": "请用温柔的语气给我讲一个睡前故事", } ], audio={ "voice": "elegantgentle-female", "format": "pcm", # 流式场景下使用 pcm 格式 }, stream=True, ) audio_chunks = [] transcript = "" for chunk in completion: delta = chunk.choices[0].delta # 处理音频数据 if hasattr(delta, 'audio') and delta.audio: if hasattr(delta.audio, 'data') and delta.audio.data: audio_chunks.append(base64.b64decode(delta.audio.data)) if hasattr(delta.audio, 'transcript') and delta.audio.transcript: transcript += delta.audio.transcript print(delta.audio.transcript, end="", flush=True) print("\n\n完整文字回复:", transcript) # 合并所有 PCM 数据 pcm_data = b''.join(audio_chunks) # 生成 WAV 文件头(24kHz,单声道,16bit) def create_wav_header(pcm_size, sample_rate=24000, channels=1, bits_per_sample=16): byte_rate = sample_rate * channels * bits_per_sample // 8 block_align = channels * bits_per_sample // 8 return struct.pack( '<4sI4s4sIHHIIHH4sI', b'RIFF', 36 + pcm_size, # 文件大小 - 8 b'WAVE', b'fmt ', 16, # fmt chunk 大小 1, # 音频格式 (1 = PCM) channels, sample_rate, byte_rate, block_align, bits_per_sample, b'data', pcm_size ) # 保存为 WAV 文件 with open("response.wav", "wb") as f: f.write(create_wav_header(len(pcm_data))) f.write(pcm_data) # 打开 response.wav 即可收听音频回复

流式返回的音频格式为 24kHz、单声道、16bit 的 PCM 数据,你可以直接将数据流式送入播放器进行实时播放,也可以按上述示例将其保存为标准的 WAV 文件。

多轮语音对话

支持在多轮对话中保持语音上下文。你可以将用户的语音历史放入 messages 中,AI 的输出推荐将文字部分作为 assistant 回复填入 content。

import base64 from openai import OpenAI client = OpenAI(api_key="STEP_API_KEY", base_url="https://api.stepfun.com/v1") # 读取当前轮用户语音 with open("current_question.wav", "rb") as f: current_audio = base64.b64encode(f.read()).decode('utf-8') completion = client.chat.completions.create( model="step-audio-2", modalities=["text", "audio"], messages=[ { "role": "system", "content": "你是一个友好的助手,请用自然的语气回答用户的问题。", }, # 第一轮对话(可以用 ASR 文本表示历史语音) { "role": "user", "content": "你好,请问今天天气怎么样?", }, { "role": "assistant", "content": "你好!今天天气晴朗,气温适宜,非常适合出门活动。", }, # 当前轮语音输入 { "role": "user", "content": [ { "type": "input_audio", "input_audio": { "data": f"data:audio/wav;base64,{current_audio}", } } ], } ], audio={ "voice": "wenrounansheng", "format": "wav", }, stream=False, ) print(completion.choices[0].message.audio.transcript)

上下文限制说明

  • step-audio-2 支持上下文传入 wav 格式音频
  • 上下文最大支持 10MB
  • 最多支持 30 段声音(如果单段声音超过 25s,会被自动分段,每 25s 视为一段)
  • step-1o-audio 目前不支持上下文中传入 wav(但支持语音输出)

结合 Tool Call 使用

语音模型支持 Tool Call 功能,可以在语音对话中调用外部工具。完整流程包括:检测函数调用、执行函数、返回结果并获取最终语音回复。

import base64 import json from openai import OpenAI client = OpenAI(api_key="STEP_API_KEY", base_url="https://api.stepfun.com/v1") # 定义工具函数 def get_weather(city: str) -> str: """模拟获取天气信息""" # 实际应用中这里调用真实的天气 API return f"{city}:晴,气温 25°C,空气质量良好" tools = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的天气信息", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如:北京、上海" } }, "required": ["city"] } } } ] messages = [ { "role": "user", "content": "北京今天天气怎么样?", } ] # 第一次调用:模型可能返回 tool_calls completion = client.chat.completions.create( model="step-audio-2", modalities=["text", "audio"], messages=messages, tools=tools, audio={ "voice": "wenrounansheng", "format": "wav", }, stream=False, ) assistant_message = completion.choices[0].message # 检查是否有 tool_calls if assistant_message.tool_calls: # 将 assistant 消息加入上下文 messages.append({ "role": "assistant", "content": assistant_message.audio.transcript if assistant_message.audio else None, "tool_calls": [ { "id": tc.id, "type": "function", "function": { "name": tc.function.name, "arguments": tc.function.arguments } } for tc in assistant_message.tool_calls ] }) # 执行每个工具调用并添加结果 for tool_call in assistant_message.tool_calls: function_name = tool_call.function.name function_args = json.loads(tool_call.function.arguments) # 执行函数 if function_name == "get_weather": result = get_weather(function_args["city"]) else: result = "未知函数" # 将工具执行结果加入上下文 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result, }) # 第二次调用:获取最终的语音回复 final_completion = client.chat.completions.create( model="step-audio-2", modalities=["text", "audio"], messages=messages, tools=tools, audio={ "voice": "wenrounansheng", "format": "wav", }, stream=False, ) # 获取最终回复 final_message = final_completion.choices[0].message print("文字回复:", final_message.audio.transcript) # 保存音频 with open("response.wav", "wb") as f: f.write(base64.b64decode(final_message.audio.data)) else: # 没有 tool_calls,直接输出回复 print("文字回复:", assistant_message.audio.transcript) with open("response.wav", "wb") as f: f.write(base64.b64decode(assistant_message.audio.data))

Tool Call 返回示例

第一次调用返回(text+audio 模态,带 tool_call 字段):

{ "id": "019b55f32eb27db7a0cc933be0216686", "choices": [ { "finish_reason": null, "index": 0, "message": { "role": "assistant", "audio": { "data": "[base64 wav data]", "transcript": "让我帮您查一下北京的天气。" }, "tool_calls": [ { "id": "call-1766673564415832077", "function": { "arguments": "{\"city\": \"北京\"}", "name": "get_weather" }, "type": "function", "index": 0 } ] } } ], "model": "step-audio-2", "usage": { "completion_tokens": 106, "prompt_tokens": 410, "total_tokens": 516 } }

注意:模型可能会在返回 tool_calls 的同时输出语音(如”让我帮您查一下”),你可以在等待函数执行时播放这段语音,提升用户体验。

音频格式说明

输入音频格式

  • 支持 mp3 和 wav 格式
  • Base64 编码格式示例:data:audio/mpeg;base64,${base64_string}data:audio/wav;base64,${base64_string}

输出音频格式

场景格式规格
非流式(stream=false)wav标准 wav 文件格式
流式(stream=true)pcm24kHz,单声道,16bit

Chat Completions vs Realtime API

阶跃星辰提供两种语音对话方案,适用于不同的场景需求:

特性Chat Completions APIRealtime API
连接方式HTTP 请求WebSocket 长连接
语音识别 (ASR)需自行实现或使用第三方服务内置,自动识别用户语音
上下文管理需自行维护 messages 列表内置,自动管理对话历史
语音活动检测 (VAD)需自行实现内置,自动检测用户说话
联网搜索需自行实现搜索接口内置 web_search 工具
知识库检索需自行实现内置 retrieval 工具
延迟较低(流式输出)极低(双向流式交互)
适用场景离线处理、批量任务、简单集成实时对话、语音助手、客服机器人

使用 Realtime API 的优势

如果你需要构建实时语音对话应用,推荐使用 Realtime API

  • 开箱即用:内置 ASR、VAD、上下文管理,无需额外集成
  • 超低延迟:基于 WebSocket 的实时双向通信
  • 内置工具:支持 web_search 联网搜索和 retrieval 知识库检索
  • 自定义 Tool Call:同样支持自定义函数调用
实时语音互动开发指南

常见问题

流式播放 PCM 音频

在浏览器环境中播放流式 PCM 音频,需要使用 Web Audio API。以下是一个简单的播放器示例:

class SimplePCMPlayer { constructor() { this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); this.bufferedData = []; this.isPlaying = false; this.nextStartTime = 0; } // 追加 PCM 数据(16位有符号小端,24kHz,单声道) appendPCM(pcmArrayBuffer) { const int16Array = new Int16Array(pcmArrayBuffer); const float32Array = new Float32Array(int16Array.length); for (let i = 0; i < int16Array.length; i++) { float32Array[i] = int16Array[i] / 32768; } this.bufferedData.push(float32Array); if (!this.isPlaying) { this.playNext(); } } playNext() { if (this.bufferedData.length === 0) { this.isPlaying = false; return; } this.isPlaying = true; const data = this.bufferedData.shift(); const audioBuffer = this.audioContext.createBuffer(1, data.length, 24000); audioBuffer.copyToChannel(data, 0); const source = this.audioContext.createBufferSource(); source.buffer = audioBuffer; source.connect(this.audioContext.destination); const startTime = Math.max(this.audioContext.currentTime, this.nextStartTime); this.nextStartTime = startTime + audioBuffer.duration; source.start(startTime); source.onended = () => this.playNext(); } // 清空缓冲并停止播放 clear() { this.bufferedData = []; this.isPlaying = false; this.nextStartTime = this.audioContext.currentTime; } }

优化首字延迟

如果需要更低的首字延迟:

  1. 使用 step-audio-2-mini 模型,它的响应速度更快
  2. 使用流式输出(stream=true),可以边生成边播放
  3. 考虑使用 Realtime API 获得最低延迟体验
Last updated on