Skip to content

Commit 9761085

Browse files
authored
Merge pull request #59 from pragmatrix/azure-openai
Basic auth and connection support for OpenAI via Azure
2 parents c1c6824 + 602b435 commit 9761085

7 files changed

Lines changed: 261 additions & 60 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# OpenAI Configuration
22
OPENAI_API_KEY=your_openai_key
33
OPENAI_REALTIME_API_MODEL=gpt-4o-mini-realtime-preview
4+
OPENAI_REALTIME_ENDPOINT=
45

56
# Aristech
67
ARISTECH_ENDPOINT=

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ tokio = { workspace = true, features = ["rt-multi-thread"] }
8080
openai-api-rs = { workspace = true }
8181
serde_json = { workspace = true }
8282
chrono-tz = { version = "0.10.3" }
83+
url = { workspace = true }
84+
strum = { version = "0.28" }
8385

8486

8587
# For recognizing audio files in azure-transcribe.

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Configure the services by setting the appropriate environment variables in your
9393
# OpenAI Configuration
9494
OPENAI_API_KEY=your_openai_key
9595
OPENAI_REALTIME_API_MODEL=gpt-4o-mini-realtime-preview
96+
OPENAI_REALTIME_ENDPOINT=
9697
9798
# Azure Configuration
9899
AZURE_SUBSCRIPTION_KEY=your_azure_key
@@ -105,6 +106,13 @@ ELEVENLABS_API_KEY=your_elevenlabs_key
105106
AUDIO_KNIFE_ADDRESS=127.0.0.1:8123
106107
```
107108

109+
For Azure OpenAI realtime endpoints (`*.openai.azure.com`), the realtime client automatically appends
110+
`api-key` as a query parameter to the websocket URL. For other hosts, it uses the standard
111+
`Authorization: Bearer ...` header.
112+
113+
The websocket client does not follow redirects. If the endpoint responds with `3xx` (for example
114+
`302 Found`), update the configured endpoint URL to the final websocket target.
115+
108116
## License
109117

110118
[MIT License](LICENSE)

examples/openai-dialog.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ use std::{
1010

1111
use anyhow::{Context, Result, bail};
1212
use chrono::Utc;
13+
use clap::builder::{PossibleValuesParser, TypedValueParser};
14+
use clap::{Parser, ValueEnum};
1315
use context_switch::{InputModality, OutputModality};
1416
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
1517
use openai_api_rs::realtime::types;
16-
use openai_dialog::{OpenAIDialog, ServiceInputEvent, ServiceOutputEvent};
18+
use openai_dialog::{OpenAIDialog, Protocol, ServiceInputEvent, ServiceOutputEvent};
1719
use rodio::{DeviceSinkBuilder, Player, Source};
1820
use serde_json::json;
1921
use tokio::{
@@ -27,8 +29,48 @@ use context_switch_core::{
2729
conversation::{Conversation, Input, Output},
2830
};
2931

32+
#[derive(Debug, Parser)]
33+
struct Cli {
34+
#[arg(long, value_enum)]
35+
protocol: Option<CliProtocol>,
36+
#[arg(long)]
37+
endpoint: Option<String>,
38+
#[arg(long, value_parser = realtime_voice_value_parser())]
39+
voice: Option<types::RealtimeVoice>,
40+
}
41+
42+
#[derive(Debug, Clone, Copy, ValueEnum)]
43+
enum CliProtocol {
44+
#[value(name = "openai")]
45+
OpenAI,
46+
Azure,
47+
}
48+
49+
fn realtime_voice_value_parser() -> impl TypedValueParser<Value = types::RealtimeVoice> {
50+
PossibleValuesParser::new(<types::RealtimeVoice as strum::VariantNames>::VARIANTS).try_map(
51+
|value| {
52+
parse_realtime_voice_value(&value)
53+
.map_err(|e| format!("Invalid voice value `{value}`: {e}"))
54+
},
55+
)
56+
}
57+
58+
fn parse_realtime_voice_value(value: &str) -> Result<types::RealtimeVoice, strum::ParseError> {
59+
types::RealtimeVoice::from_str(value)
60+
}
61+
62+
impl From<CliProtocol> for Protocol {
63+
fn from(value: CliProtocol) -> Self {
64+
match value {
65+
CliProtocol::OpenAI => Protocol::OpenAI,
66+
CliProtocol::Azure => Protocol::Azure,
67+
}
68+
}
69+
}
70+
3071
#[tokio::main]
3172
async fn main() -> Result<()> {
73+
let cli = Cli::parse();
3274
dotenvy::dotenv_override().context("Reading .env file")?;
3375
tracing_subscriber::fmt::init();
3476

@@ -76,6 +118,12 @@ async fn main() -> Result<()> {
76118

77119
let openai = OpenAIDialog;
78120
let mut params = openai_dialog::Params::new(key, model);
121+
params.host = cli
122+
.endpoint
123+
.or_else(|| env::var("OPENAI_REALTIME_ENDPOINT").ok())
124+
.filter(|endpoint| !endpoint.trim().is_empty());
125+
params.protocol = cli.protocol.map(Into::into);
126+
params.voice = cli.voice;
79127
params.tools.push(get_time_function_definition());
80128

81129
let (output_sender, output_receiver) = unbounded_channel();

services/openai-dialog/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ base64 = { workspace = true }
2222
serde = { workspace = true }
2323
async-trait = { workspace = true }
2424
uuid = { workspace = true }
25+
url = { workspace = true }

0 commit comments

Comments
 (0)