Skip to content

Commit dc76789

Browse files
committed
feat: add HttpClient trait for middleware support
- Add HttpClient trait to abstract HTTP operations - Implement trait for reqwest::Client - Add with_http_client_trait method to Client - Export ClientWithTrait for middleware-enabled clients - Add async-trait dependency This enables using middleware-enabled HTTP clients with async-openai, allowing automatic instrumentation, retry logic, and other middleware features.
1 parent b758b55 commit dc76789

File tree

4 files changed

+103
-1
lines changed

4 files changed

+103
-1
lines changed

async-openai/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ byot = []
2828

2929
[dependencies]
3030
async-openai-macros = { path = "../async-openai-macros", version = "0.1.0" }
31+
async-trait = "0.1"
3132
backoff = { version = "0.4.0", features = ["tokio"] }
3233
base64 = "0.22.1"
3334
futures = "0.3.31"

async-openai/src/client.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
config::{Config, OpenAIConfig},
1111
error::{map_deserialization_error, ApiError, OpenAIError, WrappedError},
1212
file::Files,
13+
http_client::{HttpClient, BoxedHttpClient},
1314
image::Images,
1415
moderation::Moderations,
1516
traits::AsyncTryFrom,
@@ -63,6 +64,16 @@ impl<C: Config> Client<C> {
6364
self.http_client = http_client;
6465
self
6566
}
67+
68+
/// Provide any HTTP client implementing the HttpClient trait
69+
/// This allows using middleware-enabled clients for automatic instrumentation
70+
pub fn with_http_client_trait<H: HttpClient + 'static>(self, http_client: H) -> ClientWithTrait<C> {
71+
ClientWithTrait {
72+
http_client: std::sync::Arc::new(http_client),
73+
config: self.config,
74+
backoff: self.backoff,
75+
}
76+
}
6677

6778
/// Exponential backoff for retrying [rate limited](https://platform.openai.com/docs/guides/rate-limits) requests.
6879
pub fn with_backoff(mut self, backoff: backoff::ExponentialBackoff) -> Self {

async-openai/src/http_client.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/// HTTP client abstraction trait for async-openai
2+
/// This allows using any HTTP client implementation, including those with middleware
3+
use async_trait::async_trait;
4+
use bytes::Bytes;
5+
use reqwest::{Method, StatusCode, Url, header::HeaderMap};
6+
use std::error::Error as StdError;
7+
use std::fmt;
8+
use std::sync::Arc;
9+
10+
/// Error type for HTTP operations
11+
#[derive(Debug)]
12+
pub struct HttpError {
13+
pub message: String,
14+
pub status: Option<StatusCode>,
15+
}
16+
17+
impl fmt::Display for HttpError {
18+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19+
match self.status {
20+
Some(status) => write!(f, "HTTP {}: {}", status, self.message),
21+
None => write!(f, "{}", self.message),
22+
}
23+
}
24+
}
25+
26+
impl StdError for HttpError {}
27+
28+
impl From<reqwest::Error> for HttpError {
29+
fn from(err: reqwest::Error) -> Self {
30+
HttpError {
31+
message: err.to_string(),
32+
status: err.status(),
33+
}
34+
}
35+
}
36+
37+
/// Response from HTTP client
38+
pub struct HttpResponse {
39+
pub status: StatusCode,
40+
pub headers: HeaderMap,
41+
pub body: Bytes,
42+
}
43+
44+
/// Trait for HTTP clients
45+
/// This abstraction allows using reqwest::Client, ClientWithMiddleware, or any custom implementation
46+
#[async_trait]
47+
pub trait HttpClient: Send + Sync {
48+
/// Send an HTTP request
49+
async fn request(
50+
&self,
51+
method: Method,
52+
url: Url,
53+
headers: HeaderMap,
54+
body: Option<Bytes>,
55+
) -> Result<HttpResponse, HttpError>;
56+
}
57+
58+
/// Type alias for boxed HTTP client
59+
pub type BoxedHttpClient = Arc<dyn HttpClient>;
60+
61+
/// Implementation for standard reqwest::Client
62+
#[async_trait]
63+
impl HttpClient for reqwest::Client {
64+
async fn request(
65+
&self,
66+
method: Method,
67+
url: Url,
68+
headers: HeaderMap,
69+
body: Option<Bytes>,
70+
) -> Result<HttpResponse, HttpError> {
71+
let mut request = self.request(method, url).headers(headers);
72+
73+
if let Some(body) = body {
74+
request = request.body(body);
75+
}
76+
77+
let response = request.send().await?;
78+
79+
let status = response.status();
80+
let headers = response.headers().clone();
81+
let body = response.bytes().await?;
82+
83+
Ok(HttpResponse {
84+
status,
85+
headers,
86+
body,
87+
})
88+
}
89+
}

async-openai/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ mod completion;
150150
pub mod config;
151151
mod download;
152152
mod embedding;
153+
pub mod http_client;
153154
pub mod error;
154155
mod file;
155156
mod fine_tuning;
@@ -180,7 +181,7 @@ pub use audio::Audio;
180181
pub use audit_logs::AuditLogs;
181182
pub use batches::Batches;
182183
pub use chat::Chat;
183-
pub use client::Client;
184+
pub use client::{Client, ClientWithTrait};
184185
pub use completion::Completions;
185186
pub use embedding::Embeddings;
186187
pub use file::Files;

0 commit comments

Comments
 (0)