Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Azure AI Voice Live SDK for JavaScript/TypeScript. Build real-time voice AI applications with bidirectional WebSocket communication.
.claude/skills/azure-ai-voicelive-ts/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-20 | ✗→✓ | ▲ Improved | — | — |
| case-05 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-10 | ✗→✓ | ▲ Improved | — | — |
Real-time voice AI SDK for building bidirectional voice assistants with Azure AI in Node.js and browser environments.
bashnpm install @azure/ai-voicelive @azure/identity # TypeScript users npm install @types/node
Current Version: 1.0.0-beta.3
Supported Environments:
bashAZURE_VOICELIVE_ENDPOINT=https://<resource>.cognitiveservices.azure.com # Optional: API key if not using Entra ID AZURE_VOICELIVE_API_KEY=<your-api-key> # Optional: Logging AZURE_LOG_LEVEL=info
typescriptimport { DefaultAzureCredential } from "@azure/identity"; import { VoiceLiveClient } from "@azure/ai-voicelive"; const credential = new DefaultAzureCredential(); const endpoint = "https://your-resource.cognitiveservices.azure.com"; const client = new VoiceLiveClient(endpoint, credential);
typescriptimport { AzureKeyCredential } from "@azure/core-auth"; import { VoiceLiveClient } from "@azure/ai-voicelive"; const endpoint = "https://your-resource.cognitiveservices.azure.com"; const credential = new AzureKeyCredential("your-api-key"); const client = new VoiceLiveClient(endpoint, credential);
VoiceLiveClient
└── VoiceLiveSession (WebSocket connection)
├── updateSession() → Configure session options
├── subscribe() → Event handlers (Azure SDK pattern)
├── sendAudio() → Stream audio input
├── addConversationItem() → Add messages/function outputs
└── sendEvent() → Send raw protocol eventstypescriptimport { DefaultAzureCredential } from "@azure/identity"; import { VoiceLiveClient } from "@azure/ai-voicelive"; const credential = new DefaultAzureCredential(); const endpoint = process.env.AZURE_VOICELIVE_ENDPOINT!; // Create client and start session const client = new VoiceLiveClient(endpoint, credential); const session = await client.startSession("gpt-4o-mini-realtime-preview"); // Configure session await session.updateSession({ modalities: ["text", "audio"], instructions: "You are a helpful AI assistant. Respond naturally.", voice: { type: "azure-standard", name: "en-US-AvaNeural", }, turnDetection: { type: "server_vad", threshold: 0.5, prefixPaddingMs: 300, silenceDurationMs: 500, }, inputAudioFormat: "pcm16", outputAudioFormat: "pcm16", }); // Subscribe to events const subscription = session.subscribe({ onResponseAudioDelta: async (event, context) => { // Handle streaming audio output const audioData = event.delta; playAudioChunk(audioData); }, onResponseTextDelta: async (event, context) => { // Handle streaming text process.stdout.write(event.delta); }, onInputAudioTranscriptionCompleted: async (event, context) => { console.log("User said:", event.transcript); }, }); // Send audio from microphone function sendAudioChunk(audioBuffer: ArrayBuffer) { session.sendAudio(audioBuffer); }
typescriptawait session.updateSession({ // Modalities modalities: ["audio", "text"], // System instructions instructions: "You are a customer service representative.", // Voice selection voice: { type: "azure-standard", // or "azure-custom", "openai" name: "en-US-AvaNeural", }, // Turn detection (VAD) turnDetection: { type: "server_vad", // or "azure_semantic_vad" threshold: 0.5, prefixPaddingMs: 300, silenceDurationMs: 500, }, // Audio formats inputAudioFormat: "pcm16", outputAudioFormat: "pcm16", // Tools (function calling) tools: [ { type: "function", name: "get_weather", description: "Get current weather", parameters: { type: "object", properties: { location: { type: "string" } }, required: ["location"] } } ], toolChoice: "auto", });
The SDK uses a subscription-based event handling pattern:
typescriptconst subscription = session.subscribe({ // Connection lifecycle onConnected: async (args, context) => { console.log("Connected:", args.connectionId); }, onDisconnected: async (args, context) => { console.log("Disconnected:", args.code, args.reason); }, onError: async (args, context) => { console.error("Error:", args.error.message); }, // Session events onSessionCreated: async (event, context) => { console.log("Session created:", context.sessionId); }, onSessionUpdated: async (event, context) => { console.log("Session updated"); }, // Audio input events (VAD) onInputAudioBufferSpeechStarted: async (event, context) => { console.log("Speech started at:", event.audioStartMs); }, onInputAudioBufferSpeechStopped: async (event, context) => { console.log("Speech stopped at:", event.audioEndMs); }, // Transcription events onConversationItemInputAudioTranscriptionCompleted: async (event, context) => { console.log("User said:", event.transcript); }, onConversationItemInputAudioTranscriptionDelta: async (event, context) => { process.stdout.write(event.delta); }, // Response events onResponseCreated: async (event, context) => { console.log("Response started"); }, onResponseDone: async (event, context) => { console.log("Response complete"); }, // Streaming text onResponseTextDelta: async (event, context) => { process.stdout.write(event.delta); }, onResponseTextDone: async (event, context) => { console.log("\n--- Text complete ---"); }, // Streaming audio onResponseAudioDelta: async (event, context) => { const audioData = event.delta; playAudioChunk(audioData); }, onResponseAudioDone: async (event, context) => { console.log("Audio complete"); }, // Audio transcript (what assistant said) onResponseAudioTranscriptDelta: async (event, context) => { process.stdout.write(event.delta); }, // Function calling onResponseFunctionCallArgumentsDone: async (event, context) => { if (event.name === "get_weather") { const args = JSON.parse(event.arguments); const result = await getWeather(args.location); await session.addConversationItem({ type: "function_call_output", callId: event.callId, output: JSON.stringify(result), }); await session.sendEvent({ type: "response.create" }); } }, // Catch-all for debugging onServerEvent: async (event, context) => { console.log("Event:", event.type); }, }); // Clean up when done await subscription.close();
typescript// Define tools in session config await session.updateSession({ modalities: ["audio", "text"], instructions: "Help users with weather information.", tools: [ { type: "function", name: "get_weather", description: "Get current weather for a location", parameters: { type: "object", properties: { location: { type: "string", description: "City and state or country", }, }, required: ["location"], }, }, ], toolChoice: "auto", }); // Handle function calls const subscription = session.subscribe({ onResponseFunctionCallArgumentsDone: async (event, context) => { if (event.name === "get_weather") { const args = JSON.parse(event.arguments); const weatherData = await fetchWeather(args.location); // Send function result await session.addConversationItem({ type: "function_call_output", callId: event.callId, output: JSON.stringify(weatherData), }); // Trigger response generation await session.sendEvent({ type: "response.create" }); } }, });
| Voice Type | Config | Example | |------------|--------|---------| | Azure Standard | { type: "azure-standard", name: "..." } | "en-US-AvaNeural" | | Azure Custom | { type: "azure-custom", name: "...", endpointId: "..." } | Custom voice endpoint | | Azure Personal | { type: "azure-personal", speakerProfileId: "..." } | Personal voice clone | | OpenAI | { type: "openai", name: "..." } | "alloy", "echo", "shimmer" |
| Model | Description | Use Case | |-------|-------------|----------| | gpt-4o-realtime-preview | GPT-4o with real-time audio | High-quality conversational AI | | gpt-4o-mini-realtime-preview | Lightweight GPT-4o | Fast, efficient interactions | | phi4-mm-realtime | Phi multimodal | Cost-effective applications |
typescript// Server VAD (default) turnDetection: { type: "server_vad", threshold: 0.5, prefixPaddingMs: 300, silenceDurationMs: 500, } // Azure Semantic VAD (smarter detection) turnDetection: { type: "azure_semantic_vad", } // Azure Semantic VAD (English optimized) turnDetection: { type: "azure_semantic_vad_en", } // Azure Semantic VAD (Multilingual) turnDetection: { type: "azure_semantic_vad_multilingual", }
| Format | Sample Rate | Use Case | |--------|-------------|----------| | pcm16 | 24kHz | Default, high quality | | pcm16-8000hz | 8kHz | Telephony | | pcm16-16000hz | 16kHz | Voice assistants | | g711_ulaw | 8kHz | Telephony (US) | | g711_alaw | 8kHz | Telephony (EU) |
| Type | Purpose | |------|---------| | VoiceLiveClient | Main client for creating sessions | | VoiceLiveSession | Active WebSocket session | | VoiceLiveSessionHandlers | Event handler interface | | VoiceLiveSubscription | Active event subscription | | ConnectionContext | Context for connection events | | SessionContext | Context for session events | | ServerEventUnion | Union of all server events |
typescriptimport { VoiceLiveError, VoiceLiveConnectionError, VoiceLiveAuthenticationError, VoiceLiveProtocolError, } from "@azure/ai-voicelive"; const subscription = session.subscribe({ onError: async (args, context) => { const { error } = args; if (error instanceof VoiceLiveConnectionError) { console.error("Connection error:", error.message); } else if (error instanceof VoiceLiveAuthenticationError) { console.error("Auth error:", error.message); } else if (error instanceof VoiceLiveProtocolError) { console.error("Protocol error:", error.message); } }, onServerError: async (event, context) => { console.error("Server error:", event.error?.message); }, });
typescriptimport { setLogLevel } from "@azure/logger"; // Enable verbose logging setLogLevel("info"); // Or via environment variable // AZURE_LOG_LEVEL=info
typescript// Browser requires bundler (Vite, webpack, etc.) import { VoiceLiveClient } from "@azure/ai-voicelive"; import { InteractiveBrowserCredential } from "@azure/identity"; // Use browser-compatible credential const credential = new InteractiveBrowserCredential({ clientId: "your-client-id", tenantId: "your-tenant-id", }); const client = new VoiceLiveClient(endpoint, credential); // Request microphone access const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const audioContext = new AudioContext({ sampleRate: 24000 }); // Process audio and send to session // ... (see samples for full implementation)
DefaultAzureCredential — Never hardcode API keys["text", "audio"] for voice assistantssubscription.close() when done| Resource | URL | |----------|-----| | npm Package | https://www.npmjs.com/package/@azure/ai-voicelive | | GitHub Source | https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/ai/ai-voicelive | | Samples | https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/ai/ai-voicelive/samples | | API Reference | https://learn.microsoft.com/javascript/api/@azure/ai-voicelive |
This skill is applicable to execute the workflow or actions described in the overview.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-20 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +78 percentage points is the difference between those two pass rates over the 23 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.