This skill helps an LLM generate correct audio code with @ax-llm/ax. Use when the user asks about ai.transcribe(), ai.speak(), signature audio inputs or outputs, agent audio behavior, .chat() conversational audio, OpenAI audio or realtime models, Gemini Live native audio, Grok Voice Agent models, voices, formats, transcripts, or how audio fits with structured outputs.
Use this skill for audio in Ax. Pick the smallest audio surface that matches the job:
ai.transcribe(...) for batch speech-to-text.ai.speak(...) for batch text-to-speech.speech:audio signature outputs for structured programs that should return synthesized audio artifacts..chat() audio config for conversational or realtime audio turns.:audio is an audio input value: { data, format?, mimeType?, sampleRate?, channels? }.:audio is a scripted audio artifact. The model returns plain text for that field; Ax synthesizes it after structured output parsing.string, not a binary object..chat() and modelConfig.audio.speech options, not modelConfig.audio.import { ai } from '@ax-llm/ax';
const llm = ai({ name: 'openai', apiKey: process.env.OPENAI_APIKEY! });
const transcript = await llm.transcribe({
audio: { data: base64Wav, format: 'wav' },
model: 'gpt-4o-mini-transcribe',
language: 'en',
prompt: 'Product support call',
});
const speech = await llm.speak({
text: transcript.text,
model: 'gpt-4o-mini-tts',
voice: 'alloy',
format: 'mp3',
});
console.log(transcript.text);
console.log(speech.data);
console.log(speech.transcript);Providers without the requested batch audio capability throw AxMediaNotSupportedError.
import { ai, ax } from '@ax-llm/ax';
const llm = ai({ name: 'openai', apiKey: process.env.OPENAI_APIKEY! });
const say = ax('question:string -> speech:audio, summary:string');
const result = await say.forward(
llm,
{ question: 'Explain retries in one sentence.' },
{
speech: {
speak: { voice: 'alloy', format: 'mp3' },
fields: {
speech: { voice: 'alloy' },
},
},
}
);
console.log(result.summary);
console.log(result.speech.data);
console.log(result.speech.mimeType);
console.log(result.speech.transcript);The model emits a text script for speech; Ax replaces it with AxChatAudioOutput after result selection. If the field already contains an audio artifact with { data } or { id }, Ax leaves it alone.
import { agent, ai } from '@ax-llm/ax';
const llm = ai({ name: 'openai', apiKey: process.env.OPENAI_APIKEY! });
const voiceAgent = agent(
'recording:audio, question:string -> speech:audio, summary:string',
{
agentIdentity: {
name: 'Voice Assistant',
description: 'Answers spoken requests with spoken and written output',
},
contextFields: [],
}
);
const result = await voiceAgent.forward(
llm,
{
recording: { data: base64Wav, format: 'wav' },
question: 'What should I do next?',
},
{
speech: {
transcribe: { model: 'gpt-4o-mini-transcribe' },
speak: { voice: 'alloy', format: 'mp3' },
},
}
);
console.log(result.summary);
console.log(result.speech.data);The agent runtime transcribes recording first and passes the transcript through the internal agent stages. Use direct ax(...) or .chat() when you specifically want native audio understanding in the model call.
.chat() AudioUse modelConfig.audio for conversational audio turns where audio is part of the chat response instead of a structured signature field.
const res = await llm.chat({
chatPrompt: [{ role: 'user', content: 'Say hello out loud.' }],
modelConfig: {
audio: { output: { enabled: true, voice: 'alloy', format: 'wav' } },
},
});
console.log(res.results[0]?.content);
console.log(res.results[0]?.audio?.data);
console.log(res.results[0]?.audio?.transcript);type AxAudioFormat =
| 'wav'
| 'mp3'
| 'flac'
| 'opus'
| 'aac'
| 'pcm16'
| 'pcm'
| 'ogg'
| 'raw'
| 'mulaw'
| 'ulaw'
| 'alaw';
type AxSpeechConfig = {
transcribe?: {
model?: string;
language?: string;
prompt?: string;
};
speak?: {
model?: string;
voice?: string;
format?: AxAudioFormat;
};
fields?: Record<
string,
{
model?: string;
voice?: string;
format?: AxAudioFormat;
}
>;
};Use axAIOpenAIAudioDefaultConfig() for OpenAI request-based audio chat:
gpt-audio-minialloywavwav, mp3wav, mp3, flac, opus, aac, pcm16import { ai, axAIOpenAIAudioDefaultConfig } from '@ax-llm/ax';
const openai = ai({
name: 'openai',
apiKey: process.env.OPENAI_APIKEY!,
config: axAIOpenAIAudioDefaultConfig(),
});
const res = await openai.chat({
chatPrompt: [
{
role: 'user',
content: [
{ type: 'text', text: 'What is in this recording?' },
{ type: 'audio', data: base64Wav, format: 'wav' },
],
},
],
});
console.log(res.results[0]?.content);
console.log(res.results[0]?.audio?.data);Use axAIOpenAIRealtimeDefaultConfig() for OpenAI realtime speech-to-speech:
gpt-realtime-2marinpcm16audio/pcm, mono, 24000 Hz30000Use axAIOpenAIRealtimeTranscriptionDefaultConfig() for realtime transcript deltas:
gpt-realtime-whisperaudio/pcm, mono, 24000 HzcontentRealtime models use a one-turn WebSocket call under .chat(). In Node, pass a WebSocket constructor through request options:
import WebSocket from 'ws';
import { ai, axAIOpenAIRealtimeDefaultConfig } from '@ax-llm/ax';
const openai = ai({
name: 'openai',
apiKey: process.env.OPENAI_APIKEY!,
config: axAIOpenAIRealtimeDefaultConfig(),
});
const stream = await openai.chat(
{
chatPrompt: [{ role: 'user', content: 'Say hello out loud.' }],
},
{ stream: true, webSocket: WebSocket }
);For follow-up turns, keep the assistant audio reference in history:
await openai.chat({
chatPrompt: [
{ role: 'assistant', audio: { id: previousAudioId } },
{ role: 'user', content: 'Repeat that more slowly.' },
],
});Use axAIGoogleGeminiLiveAudioDefaultConfig() for Gemini native audio:
gemini-2.5-flash-native-audio-preview-12-2025Korepcm1624000audio/pcm;rate=16000, mono30000import { ai, axAIGoogleGeminiLiveAudioDefaultConfig } from '@ax-llm/ax';
const gemini = ai({
name: 'google-gemini',
apiKey: process.env.GOOGLE_APIKEY!,
config: axAIGoogleGeminiLiveAudioDefaultConfig(),
});
const res = await gemini.chat({
chatPrompt: [
{
role: 'user',
content: [
{ type: 'text', text: 'Answer this spoken question.' },
{
type: 'audio',
data: base64Pcm16,
format: 'pcm16',
sampleRate: 16000,
channels: 1,
},
],
},
],
});
console.log(res.results[0]?.content);
console.log(res.results[0]?.audio?.data);Gemini Live uses a one-turn WebSocket call under .chat(). It expects PCM input for native audio turns; use format: 'pcm16' or mimeType: 'audio/pcm;rate=16000'.
Use axAIGrokVoiceDefaultConfig() for xAI Grok Voice Agent:
grok-voice-think-fast-1.0evepcm1624000audio/pcm, mono, 24000 Hz30000import WebSocket from 'ws';
import { ai, axAIGrokVoiceDefaultConfig } from '@ax-llm/ax';
const grok = ai({
name: 'grok',
apiKey: process.env.GROK_API_KEY!,
config: axAIGrokVoiceDefaultConfig(),
});
const res = await grok.chat(
{
chatPrompt: [{ role: 'user', content: 'Say hello out loud.' }],
},
{ webSocket: WebSocket }
);
console.log(res.results[0]?.content);
console.log(res.results[0]?.audio?.data);Grok Voice uses a one-turn WebSocket call under .chat(). It expects PCM input for spoken input turns; use format: 'pcm16' or mimeType: 'audio/pcm'.
Use the meta profile for both Muse Voice surfaces. Batch transcription stays
on transcribe() and can request endpointing mode, language bias, keywords,
partial behavior, audio progress, and a caller session ID:
import { ai, AxAIMetaModel } from '@ax-llm/ax';
const meta = ai({
name: 'meta',
apiKey: process.env.MODEL_API_KEY!,
});
const transcript = await meta.transcribe({
model: AxAIMetaModel.MuseVoiceTranscribe10,
audio: { data: base64Wav, format: 'wav' },
mode: 'diarization',
languageBias: ['en', 'es'],
keywords: ['Ax'],
partialMode: 'delta',
emitAudioProgress: true,
});
console.log(transcript.text, transcript.segments, transcript.sessionId);For realtime transcription, select muse-voice-transcribe-1.0, pass PCM16
audio through streaming chat(), and provide a WebSocket implementation on
runtimes that need one. Ax folds cumulative or delta partials, overlapping
turns, speaker labels, progress events, errors, and graceful endStream
completion into ordinary streaming chat results. Muse Voice is speech-to-text
only; it does not synthesize audio.
Meta partials can revise or remove earlier words. For live captions, replace the
snapshot for each result id with result.transcript.text; isFinal marks a
final snapshot (is_final in generated languages). Do not concatenate partial
snapshots. content contains each finalized turn exactly once, in speech-start
order, and can be accumulated normally. Sending PCM and receiving partials run
concurrently; cancelling a stream stops pending audio transmission.
Declare the sample rate and channel count on audio items or in audio.input;
when both are present, they must agree. Meta requires mono PCM16 at 16 or 24 kHz.
A normal connection close succeeds only after the audio upload and endStream;
an earlier close is reported as an error.
OpenAI audio chat, OpenAI Realtime, Gemini Live, and Grok Voice default to non-streaming. Meta's Responses profile defaults to streaming. Pass { stream: true } explicitly when consuming audio or transcription deltas.
const stream = await llm.chat(
{
chatPrompt: [{ role: 'user', content: 'Say hello.' }],
},
{ stream: true }
);
for await (const chunk of stream) {
const audio = chunk.results[0]?.audio;
if (audio?.isDelta) {
playAudioChunk(audio.data);
}
}Use signature audio outputs for structured speech artifacts:
const gen = ax('question:string -> answer:string, speech:audio');Use .chat() audio when the response itself is a conversational audio turn. Do not combine .chat() audio output with provider-native structured response formats unless that provider explicitly supports the combination.
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.