Speech-to-Speech capabilities in Mastra
IntroductionIntroduction的直接链接
Speech-to-Speech (STS) in Mastra provides a standardized interface for real-time interactions across multiple providers. STS enables continuous bidirectional audio communication through listening to events from Realtime models. Unlike separate TTS and STT operations, STS maintains an open connection that processes speech continuously in both directions.
ConfigurationConfiguration的直接链接
apiKey: Your OpenAI API key. Falls back to theOPENAI_API_KEYenvironment variable.model: The model ID to use for real-time voice interactions (e.g.,gpt-5.1-realtime).speaker: The default voice ID for speech synthesis. You can specify which voice to use for the speech output.
const voice = new OpenAIRealtimeVoice({
apiKey: 'your-openai-api-key',
model: 'gpt-5.1-realtime',
speaker: 'alloy', // Default voice
})
// If using default settings the configuration can be simplified to:
const voice = new OpenAIRealtimeVoice()
Using STSUsing STS的直接链接
import { Agent } from '@mastra/core/agent'
import { OpenAIRealtimeVoice } from '@mastra/voice-openai-realtime'
import { playAudio, getMicrophoneStream } from '@mastra/node-audio'
const agent = new Agent({
id: 'agent',
name: 'OpenAI Realtime Agent',
instructions: `You are a helpful assistant with real-time voice capabilities.`,
model: 'openai/gpt-5.6-sol',
voice: new OpenAIRealtimeVoice(),
})
// Connect to the voice service
await agent.voice.connect()
// Listen for agent audio responses
agent.voice.on('speaker', ({ audio }) => {
playAudio(audio)
})
// Initiate the conversation
await agent.voice.speak('How can I help you today?')
// Send continuous audio from the microphone
const micStream = getMicrophoneStream()
await agent.voice.send(micStream)
For a broader overview of voice providers on agents, see Voice in Mastra.
Use tools in realtime sessionsUse tools in realtime sessions的直接链接
Realtime voice providers can use tools configured on the agent. Add the tools to the Agent definition, then connect and send audio through the voice provider:
import { Agent } from '@mastra/core/agent'
import { OpenAIRealtimeVoice } from '@mastra/voice-openai-realtime'
import { calculate, search } from '../tools'
export const agent = new Agent({
id: 'speech-to-speech-agent',
name: 'Speech-to-Speech Agent',
instructions: 'You are a helpful assistant with speech-to-speech capabilities.',
model: 'openai/gpt-5.6-sol',
tools: {
search,
calculate,
},
voice: new OpenAIRealtimeVoice(),
})
Listen for realtime eventsListen for realtime events的直接链接
Realtime voice providers emit events you can use to update your UI and play assistant audio, while logging transcriptions and handling errors:
agent.voice.on('speaking', ({ audio }) => {
playAudio(audio)
})
agent.voice.on('writing', ({ text, role }) => {
console.log(`${role}: ${text}`)
})
agent.voice.on('error', error => {
console.error('Voice error:', error)
})
Event names and payloads vary by provider. Check the provider section below or the provider reference for the full event list.
Per-session voice instancesPer-session voice instances的直接链接
A static voice instance is shared across every request. This works for one-shot text-to-speech, but real-time and speech-to-speech providers store session state such as the WebSocket connection, tools, instructions, and request context. If one agent handles several live sessions at once, a shared instance can let one session overwrite another session's state.
Provide voice as a resolver when each live session needs its own voice instance. Mastra runs the resolver on each getVoice() call and returns a fresh instance for that request context:
import { Agent } from '@mastra/core/agent'
import { RequestContext } from '@mastra/core/request-context'
import { OpenAIRealtimeVoice } from '@mastra/voice-openai-realtime'
export const agent = new Agent({
id: 'support-line',
name: 'Support Line',
instructions: ({ requestContext }) => `Help user ${requestContext.get('user')}.`,
model: 'openai/gpt-5.6-sol',
voice: ({ requestContext }) =>
new OpenAIRealtimeVoice({
apiKey: requestContext.get('apiKey'),
}),
})
const requestContext = new RequestContext()
requestContext.set('user', 'user-123')
requestContext.set('apiKey', process.env.OPENAI_API_KEY)
const voice = await agent.getVoice({ requestContext })
await voice.connect()
When you use a resolver:
- Each call to
getVoice()returns a new instance, so concurrent sessions don't share state. - Mastra doesn't add tools or instructions to a resolver instance. Configure them inside the resolver or on the provider.
- You own the returned instance lifecycle, so call
disconnect()orclose()when the session ends.
The agent.voice getter has no request context, so it throws when voice is a resolver. Use agent.getVoice({ requestContext }) instead.
Google Gemini Live (Realtime)Google Gemini Live (Realtime)的直接链接
import { Agent } from '@mastra/core/agent'
import { GeminiLiveVoice } from '@mastra/voice-google-gemini-live'
import { playAudio, getMicrophoneStream } from '@mastra/node-audio'
const agent = new Agent({
id: 'agent',
name: 'Gemini Live Agent',
instructions: 'You are a helpful assistant with real-time voice capabilities.',
// Model used for text generation; voice provider handles realtime audio
model: 'openai/gpt-5.6-sol',
voice: new GeminiLiveVoice({
apiKey: process.env.GOOGLE_API_KEY,
model: 'gemini-2.0-flash-exp',
speaker: 'Puck',
debug: true,
// Vertex AI option:
// vertexAI: true,
// project: 'your-gcp-project',
// location: 'us-central1',
// serviceAccountKeyFile: '/path/to/service-account.json',
}),
})
await agent.voice.connect()
agent.voice.on('speaker', ({ audio }) => {
playAudio(audio)
})
agent.voice.on('writing', ({ role, text }) => {
console.log(`${role}: ${text}`)
})
await agent.voice.speak('How can I help you today?')
const micStream = getMicrophoneStream()
await agent.voice.send(micStream)
Note:
- Live API requires
GOOGLE_API_KEY. Vertex AI requires project/location and service account credentials. - Events:
speaker(audio stream),writing(text),turnComplete,usage, anderror.
AWS Nova Sonic (Realtime)AWS Nova Sonic (Realtime)的直接链接
import { Agent } from '@mastra/core/agent'
import { NovaSonicVoice } from '@mastra/voice-aws-nova-sonic'
import { playAudio, getMicrophoneStream } from '@mastra/node-audio'
const agent = new Agent({
id: 'agent',
name: 'Nova Sonic Agent',
instructions: 'You are a helpful assistant with real-time voice capabilities.',
// Model used for text generation; voice provider handles realtime audio
model: 'openai/gpt-5.6-sol',
voice: new NovaSonicVoice({
region: 'us-east-1',
speaker: 'matthew',
// Static credentials are optional. The default AWS credential provider
// chain is used when none are passed.
}),
})
await agent.voice.connect()
// Assistant audio is emitted as 16-bit PCM on the `speaking` event
agent.voice.on('speaking', ({ audioData }) => {
if (audioData) playAudio(audioData)
})
agent.voice.on('writing', ({ role, text }) => {
console.log(`${role}: ${text}`)
})
await agent.voice.speak('How can I help you today?')
const micStream = getMicrophoneStream()
await agent.voice.send(micStream)
Note:
- Available regions:
us-east-1,us-west-2, andap-northeast-1. - Authenticates through the standard AWS credential provider chain. Pass
credentialsto override. - Events:
speaking(Int16Array audio),writing(text withgenerationStage),toolCall,interrupt,turnComplete,usage,session, anderror.
Inworld RealtimeInworld Realtime的直接链接
import { Agent } from '@mastra/core/agent'
import { InworldRealtimeVoice } from '@mastra/voice-inworld'
import { playAudio, getMicrophoneStream } from '@mastra/node-audio'
const agent = new Agent({
id: 'agent',
name: 'Inworld Realtime Agent',
instructions: 'You are a helpful assistant with real-time voice capabilities.',
// Model used for text generation; voice provider handles realtime audio
model: 'openai/gpt-5.6-sol',
voice: new InworldRealtimeVoice({
apiKey: process.env.INWORLD_API_KEY,
model: 'inworld/models/gemma-4-26b-a4b-it',
speaker: 'Sarah',
// Typed Inworld realtime knobs (semantic VAD, playback speed, etc.)
// session: {
// audio: {
// output: { speed: 1.1 },
// input: { turn_detection: { type: 'semantic_vad', eagerness: 'high' } },
// },
// },
}),
})
await agent.voice.connect()
agent.voice.on('speaker', stream => {
playAudio(stream)
})
agent.voice.on('writing', ({ role, text }) => {
console.log(`${role}: ${text}`)
})
await agent.voice.speak('How can I help you today?')
const micStream = getMicrophoneStream()
await agent.voice.send(micStream)
Note:
- Requires
INWORLD_API_KEY. Inworld API keys are pre-Basic-encoded: paste them verbatim. - The WebSocket URL appends a client-generated
?key=...&protocol=realtime. The model is configured via the initialsession.update, not in the URL. - Inworld's wire protocol is the OpenAI Realtime GA spec, so event names match
@mastra/voice-openai-realtime. - Typed Inworld realtime knobs (MCP tool routing, semantic VAD eagerness, playback speed, transcription model, output modalities, …) are exposed through the
sessionconstructor field. An untypedproviderDataescape hatch is also deep-merged for forward compatibility with new Inworld features. - Events:
speaker(PCM audio stream),speaking(audio Buffer per delta),writing(text),conversation.item.added,conversation.item.done,function_call.arguments,tool-call-start,tool-call-result, anderror.