Skip to content

Commit 9b3ecda

Browse files
ifsheldon64bit
andauthored
Enable dyn dispatch by dyn Config objects (#383)
* enable dynamic dispatch * update README with dyn dispatch example * add doc for dyn dispatch * Update test Co-authored-by: Himanshu Neema <[email protected]> * Update Config bound Co-authored-by: Himanshu Neema <[email protected]> * remove Rc impl Co-authored-by: Himanshu Neema <[email protected]> * Fix typo Co-authored-by: Himanshu Neema <[email protected]> * Fix typo Co-authored-by: Himanshu Neema <[email protected]> * Update doc Co-authored-by: Himanshu Neema <[email protected]> * Update README Co-authored-by: Himanshu Neema <[email protected]> --------- Co-authored-by: Himanshu Neema <[email protected]>
1 parent 097945b commit 9b3ecda

File tree

3 files changed

+113
-2
lines changed

3 files changed

+113
-2
lines changed

async-openai/README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,24 @@ This can be useful in many scenarios:
141141
- To avoid verbose types.
142142
- To escape deserialization errors.
143143

144-
Visit [examples/bring-your-own-type](https://github.com/64bit/async-openai/tree/main/examples/bring-your-own-type) directory to learn more.
144+
Visit [examples/bring-your-own-type](https://github.com/64bit/async-openai/tree/main/examples/bring-your-own-type)
145+
directory to learn more.
146+
147+
## Dynamic Dispatch for Different Providers
148+
149+
For any struct that implements `Config` trait, you can wrap it in a smart pointer and cast the pointer to `dyn Config`
150+
trait object, then your client can accept any wrapped configuration type.
151+
152+
For example,
153+
154+
```rust
155+
use async_openai::{Client, config::Config, config::OpenAIConfig};
156+
157+
let openai_config = OpenAIConfig::default();
158+
// You can use `std::sync::Arc` to wrap the config as well
159+
let config = Box::new(openai_config) as Box<dyn Config>;
160+
let client: Client<Box<dyn Config> > = Client::with_config(config);
161+
```
145162

146163
## Contributing
147164

async-openai/src/config.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub const OPENAI_BETA_HEADER: &str = "OpenAI-Beta";
1515

1616
/// [crate::Client] relies on this for every API call on OpenAI
1717
/// or Azure OpenAI service
18-
pub trait Config: Clone {
18+
pub trait Config: Send + Sync {
1919
fn headers(&self) -> HeaderMap;
2020
fn url(&self, path: &str) -> String;
2121
fn query(&self) -> Vec<(&str, &str)>;
@@ -25,6 +25,32 @@ pub trait Config: Clone {
2525
fn api_key(&self) -> &SecretString;
2626
}
2727

28+
/// Macro to implement Config trait for pointer types with dyn objects
29+
macro_rules! impl_config_for_ptr {
30+
($t:ty) => {
31+
impl Config for $t {
32+
fn headers(&self) -> HeaderMap {
33+
self.as_ref().headers()
34+
}
35+
fn url(&self, path: &str) -> String {
36+
self.as_ref().url(path)
37+
}
38+
fn query(&self) -> Vec<(&str, &str)> {
39+
self.as_ref().query()
40+
}
41+
fn api_base(&self) -> &str {
42+
self.as_ref().api_base()
43+
}
44+
fn api_key(&self) -> &SecretString {
45+
self.as_ref().api_key()
46+
}
47+
}
48+
};
49+
}
50+
51+
impl_config_for_ptr!(Box<dyn Config>);
52+
impl_config_for_ptr!(std::sync::Arc<dyn Config>);
53+
2854
/// Configuration for OpenAI API
2955
#[derive(Clone, Debug, Deserialize)]
3056
#[serde(default)]
@@ -211,3 +237,55 @@ impl Config for AzureConfig {
211237
vec![("api-version", &self.api_version)]
212238
}
213239
}
240+
241+
#[cfg(test)]
242+
mod test {
243+
use super::*;
244+
use crate::types::{
245+
ChatCompletionRequestMessage, ChatCompletionRequestUserMessage, CreateChatCompletionRequest,
246+
};
247+
use crate::Client;
248+
use std::sync::Arc;
249+
#[test]
250+
fn test_client_creation() {
251+
unsafe { std::env::set_var("OPENAI_API_KEY", "test") }
252+
let openai_config = OpenAIConfig::default();
253+
let config = Box::new(openai_config.clone()) as Box<dyn Config>;
254+
let client = Client::with_config(config);
255+
assert!(client.config().url("").ends_with("/v1"));
256+
257+
let config = Arc::new(openai_config) as Arc<dyn Config>;
258+
let client = Client::with_config(config);
259+
assert!(client.config().url("").ends_with("/v1"));
260+
let cloned_client = client.clone();
261+
assert!(cloned_client.config().url("").ends_with("/v1"));
262+
}
263+
264+
async fn dynamic_dispatch_compiles(client: &Client<Box<dyn Config>>) {
265+
let _ = client.chat().create(CreateChatCompletionRequest {
266+
model: "gpt-4o".to_string(),
267+
messages: vec![ChatCompletionRequestMessage::User(
268+
ChatCompletionRequestUserMessage {
269+
content: "Hello, world!".into(),
270+
..Default::default()
271+
},
272+
)],
273+
..Default::default()
274+
});
275+
}
276+
277+
#[tokio::test]
278+
async fn test_dynamic_dispatch() {
279+
let openai_config = OpenAIConfig::default();
280+
let azure_config = AzureConfig::default();
281+
282+
let azure_client = Client::with_config(Box::new(azure_config.clone()) as Box<dyn Config>);
283+
let oai_client = Client::with_config(Box::new(openai_config.clone()) as Box<dyn Config>);
284+
285+
let _ = dynamic_dispatch_compiles(&azure_client).await;
286+
let _ = dynamic_dispatch_compiles(&oai_client).await;
287+
288+
let _ = tokio::spawn(async move { dynamic_dispatch_compiles(&azure_client).await });
289+
let _ = tokio::spawn(async move { dynamic_dispatch_compiles(&oai_client).await });
290+
}
291+
}

async-openai/src/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,22 @@
9494
//! # });
9595
//!```
9696
//!
97+
//! ## Dynamic Dispatch for Different Providers
98+
//!
99+
//! For any struct that implements `Config` trait, you can wrap it in a smart pointer and cast the pointer to `dyn Config`
100+
//! trait object, then your client can accept any wrapped configuration type.
101+
//!
102+
//! For example,
103+
//! ```
104+
//! use async_openai::{Client, config::Config, config::OpenAIConfig};
105+
//! unsafe { std::env::set_var("OPENAI_API_KEY", "only for doc test") }
106+
//!
107+
//! let openai_config = OpenAIConfig::default();
108+
//! // You can use `std::sync::Arc` to wrap the config as well
109+
//! let config = Box::new(openai_config) as Box<dyn Config>;
110+
//! let client: Client<Box<dyn Config> > = Client::with_config(config);
111+
//! ```
112+
//!
97113
//! ## Microsoft Azure
98114
//!
99115
//! ```

0 commit comments

Comments
 (0)