-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat(azure): Realtime API support #1287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { OpenAIRealtimeWebSocket } from 'openai/beta/realtime/websocket'; | ||
| import { AzureOpenAI } from 'openai'; | ||
| import { DefaultAzureCredential, getBearerTokenProvider } from '@azure/identity'; | ||
| import 'dotenv/config'; | ||
|
|
||
| async function main() { | ||
| const cred = new DefaultAzureCredential(); | ||
| const scope = 'https://cognitiveservices.azure.com/.default'; | ||
| const deploymentName = 'gpt-4o-realtime-preview-1001'; | ||
| const azureADTokenProvider = getBearerTokenProvider(cred, scope); | ||
| const client = new AzureOpenAI({ | ||
| azureADTokenProvider, | ||
| apiVersion: '2024-10-01-preview', | ||
| deployment: deploymentName, | ||
| }); | ||
| const rt = await OpenAIRealtimeWebSocket.azure(client); | ||
|
|
||
| // access the underlying `ws.WebSocket` instance | ||
| rt.socket.addEventListener('open', () => { | ||
| console.log('Connection opened!'); | ||
| rt.send({ | ||
| type: 'session.update', | ||
| session: { | ||
| modalities: ['text'], | ||
| model: 'gpt-4o-realtime-preview', | ||
| }, | ||
| }); | ||
|
|
||
| rt.send({ | ||
| type: 'conversation.item.create', | ||
| item: { | ||
| type: 'message', | ||
| role: 'user', | ||
| content: [{ type: 'input_text', text: 'Say a couple paragraphs!' }], | ||
| }, | ||
| }); | ||
|
|
||
| rt.send({ type: 'response.create' }); | ||
| }); | ||
|
|
||
| rt.on('error', (err) => { | ||
| // in a real world scenario this should be logged somewhere as you | ||
| // likely want to continue procesing events regardless of any errors | ||
| throw err; | ||
| }); | ||
|
|
||
| rt.on('session.created', (event) => { | ||
| console.log('session created!', event.session); | ||
| console.log(); | ||
| }); | ||
|
|
||
| rt.on('response.text.delta', (event) => process.stdout.write(event.delta)); | ||
| rt.on('response.text.done', () => console.log()); | ||
|
|
||
| rt.on('response.done', () => rt.close()); | ||
|
|
||
| rt.socket.addEventListener('close', () => console.log('\nConnection closed!')); | ||
| } | ||
|
|
||
| main(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { DefaultAzureCredential, getBearerTokenProvider } from '@azure/identity'; | ||
| import { OpenAIRealtimeWS } from 'openai/beta/realtime/ws'; | ||
| import { AzureOpenAI } from 'openai'; | ||
| import 'dotenv/config'; | ||
|
|
||
| async function main() { | ||
| const cred = new DefaultAzureCredential(); | ||
| const scope = 'https://cognitiveservices.azure.com/.default'; | ||
| const deploymentName = 'gpt-4o-realtime-preview-1001'; | ||
| const azureADTokenProvider = getBearerTokenProvider(cred, scope); | ||
| const client = new AzureOpenAI({ | ||
| azureADTokenProvider, | ||
| apiVersion: '2024-10-01-preview', | ||
| deployment: deploymentName, | ||
| }); | ||
| const rt = await OpenAIRealtimeWS.azure(client); | ||
|
|
||
| // access the underlying `ws.WebSocket` instance | ||
| rt.socket.on('open', () => { | ||
| console.log('Connection opened!'); | ||
| rt.send({ | ||
| type: 'session.update', | ||
| session: { | ||
| modalities: ['text'], | ||
| model: 'gpt-4o-realtime-preview', | ||
| }, | ||
| }); | ||
| rt.send({ | ||
| type: 'session.update', | ||
| session: { | ||
| modalities: ['text'], | ||
| model: 'gpt-4o-realtime-preview', | ||
| }, | ||
| }); | ||
|
|
||
| rt.send({ | ||
| type: 'conversation.item.create', | ||
| item: { | ||
| type: 'message', | ||
| role: 'user', | ||
| content: [{ type: 'input_text', text: 'Say a couple paragraphs!' }], | ||
| }, | ||
| }); | ||
|
|
||
| rt.send({ type: 'response.create' }); | ||
| }); | ||
|
|
||
| rt.on('error', (err) => { | ||
| // in a real world scenario this should be logged somewhere as you | ||
| // likely want to continue procesing events regardless of any errors | ||
| throw err; | ||
| }); | ||
|
|
||
| rt.on('session.created', (event) => { | ||
| console.log('session created!', event.session); | ||
| console.log(); | ||
| }); | ||
|
|
||
| rt.on('response.text.delta', (event) => process.stdout.write(event.delta)); | ||
| rt.on('response.text.done', () => console.log()); | ||
|
|
||
| rt.on('response.done', () => rt.close()); | ||
|
|
||
| rt.socket.on('close', () => console.log('\nConnection closed!')); | ||
| } | ||
|
|
||
| main(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ async function main() { | |
| rt.send({ | ||
| type: 'session.update', | ||
| session: { | ||
| modalities: ['foo'] as any, | ||
| modalities: ['text'], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oops thanks for fixing! |
||
| model: 'gpt-4o-realtime-preview', | ||
| }, | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||
| import { RealtimeClientEvent, RealtimeServerEvent, ErrorEvent } from '../../resources/beta/realtime/realtime'; | ||||||
| import { EventEmitter } from '../../lib/EventEmitter'; | ||||||
| import { OpenAIError } from '../../error'; | ||||||
| import OpenAI, { AzureOpenAI } from 'openai'; | ||||||
|
||||||
| import OpenAI, { AzureOpenAI } from 'openai'; | |
| import OpenAI, { AzureOpenAI } from '../../index'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 9a73f6f
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,8 @@ | ||||||||||||||||||
| import { OpenAI } from '../../index'; | ||||||||||||||||||
| import { AzureOpenAI, OpenAI } from '../../index'; | ||||||||||||||||||
| import { OpenAIError } from '../../error'; | ||||||||||||||||||
| import * as Core from '../../core'; | ||||||||||||||||||
| import type { RealtimeClientEvent, RealtimeServerEvent } from '../../resources/beta/realtime/realtime'; | ||||||||||||||||||
| import { OpenAIRealtimeEmitter, buildRealtimeURL } from './internal-base'; | ||||||||||||||||||
| import { OpenAIRealtimeEmitter, buildRealtimeURL, isAzure } from './internal-base'; | ||||||||||||||||||
|
|
||||||||||||||||||
| interface MessageEvent { | ||||||||||||||||||
| data: string; | ||||||||||||||||||
|
|
@@ -26,6 +26,7 @@ export class OpenAIRealtimeWebSocket extends OpenAIRealtimeEmitter { | |||||||||||||||||
| props: { | ||||||||||||||||||
| model: string; | ||||||||||||||||||
| dangerouslyAllowBrowser?: boolean; | ||||||||||||||||||
| onUrl?: (url: URL) => void; | ||||||||||||||||||
|
||||||||||||||||||
| onUrl?: (url: URL) => void; | |
| onURL?: (url: URL) => void; |
also would be nice to mention / mark this as internal-only
| onUrl?: (url: URL) => void; | |
| /** | |
| * Callback to mutate the URL, needed for Azure. | |
| * @internal | |
| */ | |
| onURL?: (url: URL) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 9a73f6f
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional suggestion: I would personally find this easier to read if isAzure(client) was just inlined instead of extracted to a variable like this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 9a73f6f
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: why do we need to do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We try not to expose secrets and this.url is exposed on the client. Follows the same spirit of #1218
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @kwhinnery-openai I suspect you'll want to do some wordsmithing here