Skip to content

Enable dyn dispatch by dyn Config objects #383

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion async-openai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,24 @@ This can be useful in many scenarios:
- To avoid verbose types.
- To escape deserialization errors.

Visit [examples/bring-your-own-type](https://github.com/64bit/async-openai/tree/main/examples/bring-your-own-type) directory to learn more.
Visit [examples/bring-your-own-type](https://github.com/64bit/async-openai/tree/main/examples/bring-your-own-type)
directory to learn more.

## Dynamic Dispatch for Different Providers

For any struct that implements `Config` trait, you can wrap it in a smart pointer as cast the pointer to `dyn Config`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For any struct that implements `Config` trait, you can wrap it in a smart pointer as cast the pointer to `dyn Config`
For any struct that implements `Config` trait, you can wrap it in a smart pointer and cast the pointer to `dyn Config`

trait object, then your client can accept any wrapped configuration type.

For example,

```rust
use async_openai::{Client, config::Config, config::OpenAIConfig};

let openai_config = OpenAIConfig::default();
// You can use `std::rc::Rc` or `std::sync::Arc` to wrap the config as well
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// You can use `std::rc::Rc` or `std::sync::Arc` to wrap the config as well
// You can use `std::sync::Arc` to wrap the config as well

let config = Box::new(openai_config) as Box<dyn Config>;
let client: Client<Box<dyn Config> > = Client::with_config(config);
```

## Contributing

Expand Down
55 changes: 54 additions & 1 deletion async-openai/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub const OPENAI_BETA_HEADER: &str = "OpenAI-Beta";

/// [crate::Client] relies on this for every API call on OpenAI
/// or Azure OpenAI service
pub trait Config: Clone {
pub trait Config {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub trait Config {
pub trait Config: Send + Sync {

fn headers(&self) -> HeaderMap;
fn url(&self, path: &str) -> String;
fn query(&self) -> Vec<(&str, &str)>;
Expand All @@ -25,6 +25,33 @@ pub trait Config: Clone {
fn api_key(&self) -> &SecretString;
}

/// Macro to implement Config trait for pointer types with dyn objects
macro_rules! impl_config_for_ptr {
($t:ty) => {
impl Config for $t {
fn headers(&self) -> HeaderMap {
self.as_ref().headers()
}
fn url(&self, path: &str) -> String {
self.as_ref().url(path)
}
fn query(&self) -> Vec<(&str, &str)> {
self.as_ref().query()
}
fn api_base(&self) -> &str {
self.as_ref().api_base()
}
fn api_key(&self) -> &SecretString {
self.as_ref().api_key()
}
}
};
}

impl_config_for_ptr!(Box<dyn Config>);
impl_config_for_ptr!(std::rc::Rc<dyn Config>);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
impl_config_for_ptr!(std::rc::Rc<dyn Config>);

impl_config_for_ptr!(std::sync::Arc<dyn Config>);

/// Configuration for OpenAI API
#[derive(Clone, Debug, Deserialize)]
#[serde(default)]
Expand Down Expand Up @@ -211,3 +238,29 @@ impl Config for AzureConfig {
vec![("api-version", &self.api_version)]
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::Client;
use std::rc::Rc;
use std::sync::Arc;
#[test]
fn test_client_creation() {
unsafe { std::env::set_var("OPENAI_API_KEY", "test") }
let openai_config = OpenAIConfig::default();
let config = Box::new(openai_config.clone()) as Box<dyn Config>;
let client = Client::with_config(config);
assert!(client.config().url("").ends_with("/v1"));
let config = Rc::new(openai_config.clone()) as Rc<dyn Config>;
let client = Client::with_config(config);
assert!(client.config().url("").ends_with("/v1"));
let cloned_client = client.clone();
assert!(cloned_client.config().url("").ends_with("/v1"));
let config = Arc::new(openai_config) as Arc<dyn Config>;
let client = Client::with_config(config);
assert!(client.config().url("").ends_with("/v1"));
let cloned_client = client.clone();
assert!(cloned_client.config().url("").ends_with("/v1"));
}
}
Comment on lines +242 to +266
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[cfg(test)]
mod test {
use super::*;
use crate::Client;
use std::rc::Rc;
use std::sync::Arc;
#[test]
fn test_client_creation() {
unsafe { std::env::set_var("OPENAI_API_KEY", "test") }
let openai_config = OpenAIConfig::default();
let config = Box::new(openai_config.clone()) as Box<dyn Config>;
let client = Client::with_config(config);
assert!(client.config().url("").ends_with("/v1"));
let config = Rc::new(openai_config.clone()) as Rc<dyn Config>;
let client = Client::with_config(config);
assert!(client.config().url("").ends_with("/v1"));
let cloned_client = client.clone();
assert!(cloned_client.config().url("").ends_with("/v1"));
let config = Arc::new(openai_config) as Arc<dyn Config>;
let client = Client::with_config(config);
assert!(client.config().url("").ends_with("/v1"));
let cloned_client = client.clone();
assert!(cloned_client.config().url("").ends_with("/v1"));
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::types::{
ChatCompletionRequestMessage, ChatCompletionRequestUserMessage, CreateChatCompletionRequest,
};
use crate::Client;
use std::sync::Arc;
#[test]
fn test_client_creation() {
unsafe { std::env::set_var("OPENAI_API_KEY", "test") }
let openai_config = OpenAIConfig::default();
let config = Box::new(openai_config.clone()) as Box<dyn Config>;
let client = Client::with_config(config);
assert!(client.config().url("").ends_with("/v1"));
let config = Arc::new(openai_config) as Arc<dyn Config>;
let client = Client::with_config(config);
assert!(client.config().url("").ends_with("/v1"));
let cloned_client = client.clone();
assert!(cloned_client.config().url("").ends_with("/v1"));
}
async fn dynamic_dispatch_compiles(client: &Client<Box<dyn Config>>) {
let _ = client.chat().create(CreateChatCompletionRequest {
model: "gpt-4o".to_string(),
messages: vec![ChatCompletionRequestMessage::User(
ChatCompletionRequestUserMessage {
content: "Hello, world!".into(),
..Default::default()
},
)],
..Default::default()
});
}
#[tokio::test]
async fn test_dynamic_dispatch() {
let openai_config = OpenAIConfig::default();
let azure_config = AzureConfig::default();
let azure_client = Client::with_config(Box::new(azure_config.clone()) as Box<dyn Config>);
let oai_client = Client::with_config(Box::new(openai_config.clone()) as Box<dyn Config>);
let _ = dynamic_dispatch_compiles(&azure_client).await;
let _ = dynamic_dispatch_compiles(&oai_client).await;
let _ = tokio::spawn(async move { dynamic_dispatch_compiles(&azure_client).await });
let _ = tokio::spawn(async move { dynamic_dispatch_compiles(&oai_client).await });
}
}

16 changes: 16 additions & 0 deletions async-openai/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@
//! # });
//!```
//!
//! ## Dynamic Dispatch for Different Providers
//!
//! For any struct that implements `Config` trait, you can wrap it in a smart pointer as cast the pointer to `dyn Config`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! For any struct that implements `Config` trait, you can wrap it in a smart pointer as cast the pointer to `dyn Config`
//! For any struct that implements `Config` trait, you can wrap it in a smart pointer and cast the pointer to `dyn Config`

//! trait object, then your client can accept any wrapped configuration type.
//!
//! For example,
//! ```
//! use async_openai::{Client, config::Config, config::OpenAIConfig};
//! unsafe { std::env::set_var("OPENAI_API_KEY", "only for doc test") }
//!
//! let openai_config = OpenAIConfig::default();
//! // You can use `std::rc::Rc` or `std::sync::Arc` to wrap the config as well
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! // You can use `std::rc::Rc` or `std::sync::Arc` to wrap the config as well
//! // You can use `std::sync::Arc` to wrap the config as well

//! let config = Box::new(openai_config) as Box<dyn Config>;
//! let client: Client<Box<dyn Config> > = Client::with_config(config);
//! ```
//!
//! ## Microsoft Azure
//!
//! ```
Expand Down