Skip to content

Commit bbaada3

Browse files
committed
update
1 parent 382849b commit bbaada3

4 files changed

Lines changed: 84 additions & 1690 deletions

File tree

.changeset/chilly-views-push.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@livekit/agents-plugin-openai': patch
3+
---
4+
5+
remove realtimemodelbeta, support both preview and GA azure openai realtime modes

plugins/openai/src/realtime/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@
33
// SPDX-License-Identifier: Apache-2.0
44
export * from './api_proto.js';
55
export * from './realtime_model.js';
6-
export * as beta from './realtime_model_beta.js';

plugins/openai/src/realtime/realtime_model.ts

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ export class RealtimeModel extends llm.RealtimeModel {
234234
*
235235
* @param azureDeployment - The name of your Azure OpenAI deployment.
236236
* @param azureEndpoint - The endpoint URL for your Azure OpenAI resource. If undefined, will attempt to read from the environment variable AZURE_OPENAI_ENDPOINT.
237-
* @param apiVersion - API version to use with Azure OpenAI Service. If undefined, will attempt to read from the environment variable OPENAI_API_VERSION.
237+
* @param apiVersion - **Deprecated.** API version for legacy Azure OpenAI preview models. Will be removed on April 30, 2026. Omit for GA models.
238238
* @param apiKey - Azure OpenAI API key. If undefined, will attempt to read from the environment variable AZURE_OPENAI_API_KEY.
239239
* @param entraToken - Azure Entra authentication token. Required if not using API key authentication.
240240
* @param baseURL - Base URL for the API endpoint. If undefined, constructed from the azure_endpoint.
@@ -287,9 +287,10 @@ export class RealtimeModel extends llm.RealtimeModel {
287287
}
288288

289289
apiVersion = apiVersion || process.env.OPENAI_API_VERSION;
290-
if (!apiVersion) {
291-
throw new Error(
292-
'Must provide either the `apiVersion` argument or the `OPENAI_API_VERSION` environment variable',
290+
if (apiVersion) {
291+
log().warn(
292+
'The `apiVersion` parameter for Azure OpenAI Realtime is deprecated and will be removed on April 30, 2026. ' +
293+
'Please use the newer Azure OpenAI Realtime API without specifying an API version.',
293294
);
294295
}
295296

@@ -327,6 +328,24 @@ export class RealtimeModel extends llm.RealtimeModel {
327328
}
328329
}
329330

331+
/**
332+
* In-place normalization of client event dicts for legacy Azure compatibility.
333+
*
334+
* The legacy Azure Realtime API uses "text" for assistant content parts,
335+
* while the newer OpenAI API uses "output_text".
336+
*/
337+
function normalizeAzureClientEvent(event: Record<string, unknown>): void {
338+
const item = event['item'] as Record<string, unknown> | undefined;
339+
if (!item) return;
340+
const content = item['content'] as Record<string, unknown>[] | undefined;
341+
if (!content) return;
342+
for (const contentPart of content) {
343+
if (contentPart['type'] === 'output_text') {
344+
contentPart['type'] = 'text';
345+
}
346+
}
347+
}
348+
330349
function processBaseURL({
331350
baseURL,
332351
model,
@@ -340,7 +359,9 @@ function processBaseURL({
340359
azureDeployment?: string;
341360
apiVersion?: string;
342361
}): string {
343-
const url = new URL([baseURL, 'realtime'].join('/'));
362+
// Azure GA (no apiVersion) uses /v1/realtime; legacy preview uses /realtime
363+
const realtimePath = isAzure && !apiVersion ? 'v1/realtime' : 'realtime';
364+
const url = new URL([baseURL, realtimePath].join('/'));
344365

345366
if (url.protocol === 'https:') {
346367
url.protocol = 'wss:';
@@ -354,14 +375,19 @@ function processBaseURL({
354375
}
355376

356377
const queryParams: Record<string, string> = {};
357-
if (isAzure) {
358-
if (apiVersion) {
359-
queryParams['api-version'] = apiVersion;
360-
}
378+
if (isAzure && apiVersion) {
379+
// Legacy Azure preview: /realtime?api-version=<v>&deployment=<d>
380+
queryParams['api-version'] = apiVersion;
361381
if (azureDeployment) {
362382
queryParams['deployment'] = azureDeployment;
363383
}
384+
} else if (isAzure) {
385+
// GA Azure: /v1/realtime?model=<deployment>
386+
if (azureDeployment) {
387+
queryParams['model'] = azureDeployment;
388+
}
364389
} else {
390+
// Standard OpenAI: /realtime?model=<model>
365391
queryParams['model'] = model;
366392
}
367393

@@ -434,37 +460,58 @@ export class RealtimeSession extends llm.RealtimeSession {
434460
}
435461

436462
private createSessionUpdateEvent(): api_proto.SessionUpdateEvent {
437-
const audioFormat: api_proto.AudioFormat = { type: 'audio/pcm', rate: SAMPLE_RATE };
438-
439-
const modality: Modality = this.oaiRealtimeModel._options.modalities.includes('audio')
440-
? 'audio'
441-
: 'text';
463+
const opts = this.oaiRealtimeModel._options;
464+
const maxOutputTokens =
465+
opts.maxResponseOutputTokens === Infinity ? 'inf' : opts.maxResponseOutputTokens;
466+
467+
if (opts.isAzure && opts.apiVersion) {
468+
// Legacy Azure preview API: flat format
469+
const modalities: Modality[] = opts.modalities.includes('audio')
470+
? ['text', 'audio']
471+
: ['text'];
472+
return {
473+
type: 'session.update',
474+
session: {
475+
model: opts.model,
476+
voice: opts.voice,
477+
input_audio_format: 'pcm16',
478+
output_audio_format: 'pcm16',
479+
modalities,
480+
turn_detection: opts.turnDetection,
481+
input_audio_transcription: opts.inputAudioTranscription,
482+
tool_choice: toOaiToolChoice(opts.toolChoice),
483+
max_response_output_tokens: maxOutputTokens,
484+
speed: opts.speed,
485+
instructions: this.instructions,
486+
},
487+
};
488+
}
442489

490+
// GA format (OpenAI or Azure GA)
491+
const audioFormat: api_proto.AudioFormat = { type: 'audio/pcm', rate: SAMPLE_RATE };
492+
const modality: Modality = opts.modalities.includes('audio') ? 'audio' : 'text';
443493
return {
444494
type: 'session.update',
445495
session: {
446496
type: 'realtime',
447-
model: this.oaiRealtimeModel._options.model,
497+
model: opts.model,
448498
output_modalities: [modality],
449499
audio: {
450500
input: {
451501
format: audioFormat,
452-
noise_reduction: this.oaiRealtimeModel._options.inputAudioNoiseReduction,
453-
transcription: this.oaiRealtimeModel._options.inputAudioTranscription,
454-
turn_detection: this.oaiRealtimeModel._options.turnDetection,
502+
noise_reduction: opts.inputAudioNoiseReduction,
503+
transcription: opts.inputAudioTranscription,
504+
turn_detection: opts.turnDetection,
455505
},
456506
output: {
457507
format: audioFormat,
458-
speed: this.oaiRealtimeModel._options.speed,
459-
voice: this.oaiRealtimeModel._options.voice,
508+
speed: opts.speed,
509+
voice: opts.voice,
460510
},
461511
},
462-
max_output_tokens:
463-
this.oaiRealtimeModel._options.maxResponseOutputTokens === Infinity
464-
? 'inf'
465-
: this.oaiRealtimeModel._options.maxResponseOutputTokens,
466-
tool_choice: toOaiToolChoice(this.oaiRealtimeModel._options.toolChoice),
467-
tracing: this.oaiRealtimeModel._options.tracing,
512+
max_output_tokens: maxOutputTokens,
513+
tool_choice: toOaiToolChoice(opts.toolChoice),
514+
tracing: opts.tracing,
468515
instructions: this.instructions,
469516
},
470517
};
@@ -952,6 +999,9 @@ export class RealtimeSession extends llm.RealtimeSession {
952999
try {
9531000
for (const ev of events) {
9541001
this.emit('openai_client_event_queued', ev);
1002+
if (this.oaiRealtimeModel._options.isAzure && this.oaiRealtimeModel._options.apiVersion) {
1003+
normalizeAzureClientEvent(ev as unknown as Record<string, unknown>);
1004+
}
9551005
wsConn!.send(JSON.stringify(ev));
9561006
}
9571007
} catch (error) {
@@ -1042,6 +1092,9 @@ export class RealtimeSession extends llm.RealtimeSession {
10421092
}
10431093

10441094
this.emit('openai_client_event_queued', event);
1095+
if (this.oaiRealtimeModel._options.isAzure && this.oaiRealtimeModel._options.apiVersion) {
1096+
normalizeAzureClientEvent(event as unknown as Record<string, unknown>);
1097+
}
10451098
wsConn.send(JSON.stringify(event));
10461099
} catch (error) {
10471100
break;

0 commit comments

Comments
 (0)