pub mod config;
pub mod config_file;
#[cfg(all(feature = "native-http", feature = "tower"))]
pub mod managed;
use std::future::Future;
use std::pin::Pin;
#[cfg(feature = "native-http")]
use std::sync::Arc;
use futures_core::Stream;
use crate::error::Result;
use crate::types::audio::{CreateSpeechRequest, CreateTranscriptionRequest, TranscriptionResponse};
use crate::types::batch::{BatchListQuery, BatchListResponse, BatchObject, CreateBatchRequest};
use crate::types::files::{CreateFileRequest, DeleteResponse, FileListQuery, FileListResponse, FileObject};
use crate::types::image::{CreateImageRequest, ImagesResponse};
use crate::types::moderation::{ModerationRequest, ModerationResponse};
use crate::types::ocr::{OcrRequest, OcrResponse};
use crate::types::rerank::{RerankRequest, RerankResponse};
use crate::types::responses::{CreateResponseRequest, ResponseObject};
use crate::types::search::{SearchRequest, SearchResponse};
use crate::types::{
ChatCompletionChunk, ChatCompletionRequest, ChatCompletionResponse, EmbeddingRequest, EmbeddingResponse,
ModelsListResponse,
};
#[cfg(feature = "native-http")]
use crate::auth::Credential;
#[cfg(feature = "native-http")]
use crate::error::LiterLlmError;
#[cfg(feature = "native-http")]
use crate::http;
#[cfg(feature = "native-http")]
use crate::provider::{self, OpenAiCompatibleProvider, OpenAiProvider, Provider};
#[cfg(feature = "native-http")]
use secrecy::ExposeSecret;
pub use config::{ClientConfig, ClientConfigBuilder};
pub use config_file::FileConfig;
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'a>>;
pub type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = Result<T>> + Send + 'a>>;
#[cfg(feature = "native-http")]
struct PreparedRequest {
url: String,
provider: Arc<dyn Provider>,
body_json: serde_json::Value,
body_bytes: bytes::Bytes,
}
#[cfg(feature = "native-http")]
fn str_pair(pair: &(String, String)) -> (&str, &str) {
(pair.0.as_str(), pair.1.as_str())
}
pub trait LlmClient: Send + Sync {
fn chat(&self, req: ChatCompletionRequest) -> BoxFuture<'_, ChatCompletionResponse>;
fn chat_stream(&self, req: ChatCompletionRequest) -> BoxFuture<'_, BoxStream<'_, ChatCompletionChunk>>;
fn embed(&self, req: EmbeddingRequest) -> BoxFuture<'_, EmbeddingResponse>;
fn list_models(&self) -> BoxFuture<'_, ModelsListResponse>;
fn image_generate(&self, req: CreateImageRequest) -> BoxFuture<'_, ImagesResponse>;
fn speech(&self, req: CreateSpeechRequest) -> BoxFuture<'_, bytes::Bytes>;
fn transcribe(&self, req: CreateTranscriptionRequest) -> BoxFuture<'_, TranscriptionResponse>;
fn moderate(&self, req: ModerationRequest) -> BoxFuture<'_, ModerationResponse>;
fn rerank(&self, req: RerankRequest) -> BoxFuture<'_, RerankResponse>;
fn search(&self, req: SearchRequest) -> BoxFuture<'_, SearchResponse>;
fn ocr(&self, req: OcrRequest) -> BoxFuture<'_, OcrResponse>;
}
pub trait FileClient: Send + Sync {
fn create_file(&self, req: CreateFileRequest) -> BoxFuture<'_, FileObject>;
fn retrieve_file(&self, file_id: &str) -> BoxFuture<'_, FileObject>;
fn delete_file(&self, file_id: &str) -> BoxFuture<'_, DeleteResponse>;
fn list_files(&self, query: Option<FileListQuery>) -> BoxFuture<'_, FileListResponse>;
fn file_content(&self, file_id: &str) -> BoxFuture<'_, bytes::Bytes>;
}
pub trait BatchClient: Send + Sync {
fn create_batch(&self, req: CreateBatchRequest) -> BoxFuture<'_, BatchObject>;
fn retrieve_batch(&self, batch_id: &str) -> BoxFuture<'_, BatchObject>;
fn list_batches(&self, query: Option<BatchListQuery>) -> BoxFuture<'_, BatchListResponse>;
fn cancel_batch(&self, batch_id: &str) -> BoxFuture<'_, BatchObject>;
}
pub trait ResponseClient: Send + Sync {
fn create_response(&self, req: CreateResponseRequest) -> BoxFuture<'_, ResponseObject>;
fn retrieve_response(&self, id: &str) -> BoxFuture<'_, ResponseObject>;
fn cancel_response(&self, id: &str) -> BoxFuture<'_, ResponseObject>;
}
#[cfg(feature = "native-http")]
pub struct DefaultClient {
config: ClientConfig,
http: reqwest::Client,
provider: Arc<dyn Provider>,
cached_auth_header: Option<(String, String)>,
cached_extra_headers: Vec<(String, String)>,
}
#[cfg(feature = "native-http")]
impl DefaultClient {
pub fn new(config: ClientConfig, model_hint: Option<&str>) -> Result<Self> {
let provider = build_provider(&config, model_hint);
provider.validate()?;
let mut header_map = reqwest::header::HeaderMap::new();
for (k, v) in config.headers() {
let name =
reqwest::header::HeaderName::from_bytes(k.as_bytes()).map_err(|_| LiterLlmError::InvalidHeader {
name: k.clone(),
reason: "pre-validated header name became invalid".into(),
})?;
let val = reqwest::header::HeaderValue::from_str(v).map_err(|_| LiterLlmError::InvalidHeader {
name: k.clone(),
reason: "pre-validated header value became invalid".into(),
})?;
header_map.insert(name, val);
}
let http = reqwest::Client::builder()
.timeout(config.timeout)
.default_headers(header_map)
.build()
.map_err(LiterLlmError::from)?;
let cached_auth_header = provider
.auth_header(config.api_key.expose_secret())
.map(|(name, value)| (name.into_owned(), value.into_owned()));
let cached_extra_headers = provider
.extra_headers()
.iter()
.map(|&(name, value)| (name.to_owned(), value.to_owned()))
.collect();
Ok(Self {
config,
http,
provider,
cached_auth_header,
cached_extra_headers,
})
}
fn resolve_provider_for_model(&self, model: &str) -> Arc<dyn Provider> {
if self.config.base_url.is_some() {
return Arc::clone(&self.provider);
}
if self.provider.matches_model(model) {
return Arc::clone(&self.provider);
}
if let Some(detected) = provider::detect_provider(model) {
return Arc::from(detected);
}
Arc::clone(&self.provider)
}
async fn resolve_auth_header_for_provider(&self, prov: &dyn Provider) -> Result<Option<(String, String)>> {
if let Some(ref cp) = self.config.credential_provider {
let credential = cp.resolve().await?;
match credential {
Credential::BearerToken(token) => Ok(Some((
"Authorization".to_owned(),
format!("Bearer {}", token.expose_secret()),
))),
Credential::AwsCredentials { .. } => Ok(None),
}
} else {
Ok(prov
.auth_header(self.config.api_key.expose_secret())
.map(|(name, value)| (name.into_owned(), value.into_owned())))
}
}
fn all_headers_for_provider(
&self,
prov: &dyn Provider,
method: &str,
url: &str,
body_json: &serde_json::Value,
body_bytes: &[u8],
) -> Vec<(String, String)> {
let mut headers = prov.signing_headers(method, url, body_bytes);
headers.extend(
prov.extra_headers()
.iter()
.map(|&(name, value)| (name.to_owned(), value.to_owned())),
);
headers.extend(prov.dynamic_headers(body_json));
headers
}
fn prepare_request(
&self,
serializable: &impl serde::Serialize,
endpoint_fn: impl FnOnce(&dyn Provider) -> &str,
model: &str,
stream: Option<bool>,
) -> Result<PreparedRequest> {
if model.is_empty() {
return Err(LiterLlmError::BadRequest {
message: "model must not be empty".into(),
});
}
let prov = self.resolve_provider_for_model(model);
let bare_model = prov.strip_model_prefix(model).to_owned();
let endpoint_path = endpoint_fn(prov.as_ref());
let url = prov.build_url(endpoint_path, &bare_model);
let mut body = serde_json::to_value(serializable)?;
if let Some(obj) = body.as_object_mut() {
obj.insert("model".into(), serde_json::Value::String(bare_model));
if let Some(s) = stream {
obj.insert("stream".into(), serde_json::Value::Bool(s));
}
}
prov.transform_request(&mut body)?;
let body_bytes = bytes::Bytes::from(serde_json::to_vec(&body)?);
Ok(PreparedRequest {
url,
provider: prov,
body_json: body,
body_bytes,
})
}
async fn resolve_auth_header(&self) -> Result<Option<(String, String)>> {
if let Some(ref cp) = self.config.credential_provider {
let credential = cp.resolve().await?;
match credential {
Credential::BearerToken(token) => Ok(Some((
"Authorization".to_owned(),
format!("Bearer {}", token.expose_secret()),
))),
Credential::AwsCredentials { .. } => Ok(None),
}
} else {
Ok(self.cached_auth_header.clone())
}
}
fn all_headers(
&self,
method: &str,
url: &str,
body_json: &serde_json::Value,
body_bytes: &[u8],
) -> Vec<(String, String)> {
let mut headers = self.provider.signing_headers(method, url, body_bytes);
headers.extend(self.cached_extra_headers.iter().cloned());
headers.extend(self.provider.dynamic_headers(body_json));
headers
}
}
#[cfg(feature = "native-http")]
fn build_provider(config: &ClientConfig, model_hint: Option<&str>) -> Arc<dyn Provider> {
if let Some(ref base_url) = config.base_url {
return Arc::new(OpenAiCompatibleProvider {
name: "custom".into(),
base_url: base_url.clone(),
env_var: None,
model_prefixes: vec![],
});
}
if let Some(model) = model_hint
&& let Some(p) = provider::detect_provider(model)
{
return Arc::from(p);
}
Arc::new(OpenAiProvider)
}
#[cfg(feature = "native-http")]
impl LlmClient for DefaultClient {
fn chat(&self, req: ChatCompletionRequest) -> BoxFuture<'_, ChatCompletionResponse> {
Box::pin(async move {
let prepared = self.prepare_request(&req, |p| p.chat_completions_path(), &req.model, Some(false))?;
let auth_header = self
.resolve_auth_header_for_provider(prepared.provider.as_ref())
.await?;
let all_headers = self.all_headers_for_provider(
prepared.provider.as_ref(),
"POST",
&prepared.url,
&prepared.body_json,
&prepared.body_bytes,
);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let mut raw = http::request::post_json_raw(
&self.http,
&prepared.url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
)
.await?;
prepared.provider.transform_response(&mut raw)?;
serde_json::from_value::<ChatCompletionResponse>(raw).map_err(LiterLlmError::from)
})
}
fn chat_stream(&self, req: ChatCompletionRequest) -> BoxFuture<'_, BoxStream<'_, ChatCompletionChunk>> {
Box::pin(async move {
let prepared = self.prepare_request(&req, |p| p.chat_completions_path(), &req.model, Some(true))?;
let bare_model = prepared.provider.strip_model_prefix(&req.model);
let url = prepared
.provider
.build_stream_url(prepared.provider.chat_completions_path(), bare_model);
let auth_header = self
.resolve_auth_header_for_provider(prepared.provider.as_ref())
.await?;
let all_headers = self.all_headers_for_provider(
prepared.provider.as_ref(),
"POST",
&url,
&prepared.body_json,
&prepared.body_bytes,
);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
match prepared.provider.stream_format() {
provider::StreamFormat::Sse => {
let provider = Arc::clone(&prepared.provider);
let parse_event = move |data: &str| provider.parse_stream_event(data);
let stream = http::streaming::post_stream(
&self.http,
&url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
parse_event,
)
.await?;
Ok(stream)
}
provider::StreamFormat::AwsEventStream => {
let stream = http::eventstream::post_eventstream(
&self.http,
&url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
provider::bedrock::parse_bedrock_stream_event,
)
.await?;
Ok(stream)
}
}
})
}
fn embed(&self, req: EmbeddingRequest) -> BoxFuture<'_, EmbeddingResponse> {
Box::pin(async move {
let prepared = self.prepare_request(&req, |p| p.embeddings_path(), &req.model, None)?;
let auth_header = self
.resolve_auth_header_for_provider(prepared.provider.as_ref())
.await?;
let all_headers = self.all_headers_for_provider(
prepared.provider.as_ref(),
"POST",
&prepared.url,
&prepared.body_json,
&prepared.body_bytes,
);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let mut raw = http::request::post_json_raw(
&self.http,
&prepared.url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
)
.await?;
prepared.provider.transform_response(&mut raw)?;
serde_json::from_value::<EmbeddingResponse>(raw).map_err(LiterLlmError::from)
})
}
fn list_models(&self) -> BoxFuture<'_, ModelsListResponse> {
Box::pin(async move {
let url = self.provider.build_url(self.provider.models_path(), "");
let auth_header = self.resolve_auth_header().await?;
let auth = auth_header.as_ref().map(str_pair);
let all_headers = self.all_headers("GET", &url, &serde_json::Value::Null, &[]);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let mut raw = http::request::get_json_raw(&self.http, &url, auth, &extra, self.config.max_retries).await?;
self.provider.transform_response(&mut raw)?;
serde_json::from_value::<ModelsListResponse>(raw).map_err(LiterLlmError::from)
})
}
fn image_generate(&self, req: CreateImageRequest) -> BoxFuture<'_, ImagesResponse> {
Box::pin(async move {
let model = req.model.as_deref().unwrap_or_default();
let prepared = self.prepare_request(&req, |p| p.image_generations_path(), model, None)?;
let auth_header = self
.resolve_auth_header_for_provider(prepared.provider.as_ref())
.await?;
let all_headers = self.all_headers_for_provider(
prepared.provider.as_ref(),
"POST",
&prepared.url,
&prepared.body_json,
&prepared.body_bytes,
);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let mut raw = http::request::post_json_raw(
&self.http,
&prepared.url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
)
.await?;
prepared.provider.transform_response(&mut raw)?;
serde_json::from_value::<ImagesResponse>(raw).map_err(LiterLlmError::from)
})
}
fn speech(&self, req: CreateSpeechRequest) -> BoxFuture<'_, bytes::Bytes> {
Box::pin(async move {
let prepared = self.prepare_request(&req, |p| p.audio_speech_path(), &req.model, None)?;
let auth_header = self
.resolve_auth_header_for_provider(prepared.provider.as_ref())
.await?;
let all_headers = self.all_headers_for_provider(
prepared.provider.as_ref(),
"POST",
&prepared.url,
&prepared.body_json,
&prepared.body_bytes,
);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
http::request::post_binary(
&self.http,
&prepared.url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
)
.await
})
}
fn transcribe(&self, req: CreateTranscriptionRequest) -> BoxFuture<'_, TranscriptionResponse> {
Box::pin(async move {
let prepared = self.prepare_request(&req, |p| p.audio_transcriptions_path(), &req.model, None)?;
let auth_header = self
.resolve_auth_header_for_provider(prepared.provider.as_ref())
.await?;
let all_headers = self.all_headers_for_provider(
prepared.provider.as_ref(),
"POST",
&prepared.url,
&prepared.body_json,
&prepared.body_bytes,
);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let mut raw = http::request::post_json_raw(
&self.http,
&prepared.url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
)
.await?;
prepared.provider.transform_response(&mut raw)?;
serde_json::from_value::<TranscriptionResponse>(raw).map_err(LiterLlmError::from)
})
}
fn moderate(&self, req: ModerationRequest) -> BoxFuture<'_, ModerationResponse> {
Box::pin(async move {
let model = req.model.as_deref().unwrap_or_default();
let prepared = self.prepare_request(&req, |p| p.moderations_path(), model, None)?;
let auth_header = self
.resolve_auth_header_for_provider(prepared.provider.as_ref())
.await?;
let all_headers = self.all_headers_for_provider(
prepared.provider.as_ref(),
"POST",
&prepared.url,
&prepared.body_json,
&prepared.body_bytes,
);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let mut raw = http::request::post_json_raw(
&self.http,
&prepared.url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
)
.await?;
prepared.provider.transform_response(&mut raw)?;
serde_json::from_value::<ModerationResponse>(raw).map_err(LiterLlmError::from)
})
}
fn rerank(&self, req: RerankRequest) -> BoxFuture<'_, RerankResponse> {
Box::pin(async move {
let prepared = self.prepare_request(&req, |p| p.rerank_path(), &req.model, None)?;
let auth_header = self
.resolve_auth_header_for_provider(prepared.provider.as_ref())
.await?;
let all_headers = self.all_headers_for_provider(
prepared.provider.as_ref(),
"POST",
&prepared.url,
&prepared.body_json,
&prepared.body_bytes,
);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let mut raw = http::request::post_json_raw(
&self.http,
&prepared.url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
)
.await?;
prepared.provider.transform_response(&mut raw)?;
serde_json::from_value::<RerankResponse>(raw).map_err(LiterLlmError::from)
})
}
fn search(&self, req: SearchRequest) -> BoxFuture<'_, SearchResponse> {
Box::pin(async move {
let prepared = self.prepare_request(&req, |p| p.search_path(), &req.model, None)?;
let auth_header = self
.resolve_auth_header_for_provider(prepared.provider.as_ref())
.await?;
let all_headers = self.all_headers_for_provider(
prepared.provider.as_ref(),
"POST",
&prepared.url,
&prepared.body_json,
&prepared.body_bytes,
);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let mut raw = http::request::post_json_raw(
&self.http,
&prepared.url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
)
.await?;
prepared.provider.transform_response(&mut raw)?;
serde_json::from_value::<SearchResponse>(raw).map_err(LiterLlmError::from)
})
}
fn ocr(&self, req: OcrRequest) -> BoxFuture<'_, OcrResponse> {
Box::pin(async move {
let prepared = self.prepare_request(&req, |p| p.ocr_path(), &req.model, None)?;
let auth_header = self
.resolve_auth_header_for_provider(prepared.provider.as_ref())
.await?;
let all_headers = self.all_headers_for_provider(
prepared.provider.as_ref(),
"POST",
&prepared.url,
&prepared.body_json,
&prepared.body_bytes,
);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let mut raw = http::request::post_json_raw(
&self.http,
&prepared.url,
auth,
&extra,
prepared.body_bytes,
self.config.max_retries,
)
.await?;
prepared.provider.transform_response(&mut raw)?;
serde_json::from_value::<OcrResponse>(raw).map_err(LiterLlmError::from)
})
}
}
#[cfg(feature = "native-http")]
impl FileClient for DefaultClient {
fn create_file(&self, req: CreateFileRequest) -> BoxFuture<'_, FileObject> {
Box::pin(async move {
let url = self.provider.build_url(self.provider.files_path(), "");
let auth_header = self.resolve_auth_header().await?;
let auth = auth_header.as_ref().map(str_pair);
let all_headers = self.all_headers("POST", &url, &serde_json::Value::Null, &[]);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
use base64::Engine;
let file_bytes = base64::engine::general_purpose::STANDARD
.decode(&req.file)
.map_err(|e| LiterLlmError::BadRequest {
message: format!("invalid base64 file data: {e}"),
})?;
let filename = req.filename.unwrap_or_else(|| "upload".to_owned());
let file_part = reqwest::multipart::Part::bytes(file_bytes).file_name(filename);
let purpose_str = serde_json::to_value(&req.purpose)?
.as_str()
.unwrap_or_default()
.to_owned();
let form = reqwest::multipart::Form::new()
.part("file", file_part)
.text("purpose", purpose_str);
let raw = http::request::post_multipart(&self.http, &url, auth, &extra, form).await?;
serde_json::from_value::<FileObject>(raw).map_err(LiterLlmError::from)
})
}
fn retrieve_file(&self, file_id: &str) -> BoxFuture<'_, FileObject> {
let file_id = file_id.to_owned();
Box::pin(async move {
let url = format!(
"{}/{}",
self.provider.build_url(self.provider.files_path(), ""),
file_id
);
let auth_header = self.resolve_auth_header().await?;
let auth = auth_header.as_ref().map(str_pair);
let all_headers = self.all_headers("GET", &url, &serde_json::Value::Null, &[]);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let raw = http::request::get_json_raw(&self.http, &url, auth, &extra, self.config.max_retries).await?;
serde_json::from_value::<FileObject>(raw).map_err(LiterLlmError::from)
})
}
fn delete_file(&self, file_id: &str) -> BoxFuture<'_, DeleteResponse> {
let file_id = file_id.to_owned();
Box::pin(async move {
let url = format!(
"{}/{}",
self.provider.build_url(self.provider.files_path(), ""),
file_id
);
let auth_header = self.resolve_auth_header().await?;
let auth = auth_header.as_ref().map(str_pair);
let all_headers = self.all_headers("DELETE", &url, &serde_json::Value::Null, &[]);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let raw = http::request::delete_json(&self.http, &url, auth, &extra, self.config.max_retries).await?;
serde_json::from_value::<DeleteResponse>(raw).map_err(LiterLlmError::from)
})
}
fn list_files(&self, query: Option<FileListQuery>) -> BoxFuture<'_, FileListResponse> {
Box::pin(async move {
let base_url = self.provider.build_url(self.provider.files_path(), "");
let url = if let Some(ref q) = query {
let mut params = Vec::new();
if let Some(ref purpose) = q.purpose {
params.push(format!("purpose={purpose}"));
}
if let Some(limit) = q.limit {
params.push(format!("limit={limit}"));
}
if let Some(ref after) = q.after {
params.push(format!("after={after}"));
}
if params.is_empty() {
base_url
} else {
format!("{base_url}?{}", params.join("&"))
}
} else {
base_url
};
let auth_header = self.resolve_auth_header().await?;
let auth = auth_header.as_ref().map(str_pair);
let all_headers = self.all_headers("GET", &url, &serde_json::Value::Null, &[]);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let raw = http::request::get_json_raw(&self.http, &url, auth, &extra, self.config.max_retries).await?;
serde_json::from_value::<FileListResponse>(raw).map_err(LiterLlmError::from)
})
}
fn file_content(&self, file_id: &str) -> BoxFuture<'_, bytes::Bytes> {
let file_id = file_id.to_owned();
Box::pin(async move {
let url = format!(
"{}/{}/content",
self.provider.build_url(self.provider.files_path(), ""),
file_id
);
let auth_header = self.resolve_auth_header().await?;
let auth = auth_header.as_ref().map(str_pair);
let all_headers = self.all_headers("GET", &url, &serde_json::Value::Null, &[]);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
http::request::get_binary(&self.http, &url, auth, &extra, self.config.max_retries).await
})
}
}
#[cfg(feature = "native-http")]
impl BatchClient for DefaultClient {
fn create_batch(&self, req: CreateBatchRequest) -> BoxFuture<'_, BatchObject> {
Box::pin(async move {
let url = self.provider.build_url(self.provider.batches_path(), "");
let body_bytes = bytes::Bytes::from(serde_json::to_vec(&req)?);
let body_json = serde_json::to_value(&req)?;
let auth_header = self.resolve_auth_header().await?;
let all_headers = self.all_headers("POST", &url, &body_json, &body_bytes);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let raw = http::request::post_json_raw(&self.http, &url, auth, &extra, body_bytes, self.config.max_retries)
.await?;
serde_json::from_value::<BatchObject>(raw).map_err(LiterLlmError::from)
})
}
fn retrieve_batch(&self, batch_id: &str) -> BoxFuture<'_, BatchObject> {
let batch_id = batch_id.to_owned();
Box::pin(async move {
let url = format!(
"{}/{}",
self.provider.build_url(self.provider.batches_path(), ""),
batch_id
);
let auth_header = self.resolve_auth_header().await?;
let auth = auth_header.as_ref().map(str_pair);
let all_headers = self.all_headers("GET", &url, &serde_json::Value::Null, &[]);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let raw = http::request::get_json_raw(&self.http, &url, auth, &extra, self.config.max_retries).await?;
serde_json::from_value::<BatchObject>(raw).map_err(LiterLlmError::from)
})
}
fn list_batches(&self, query: Option<BatchListQuery>) -> BoxFuture<'_, BatchListResponse> {
Box::pin(async move {
let base_url = self.provider.build_url(self.provider.batches_path(), "");
let url = if let Some(ref q) = query {
let mut params = Vec::new();
if let Some(limit) = q.limit {
params.push(format!("limit={limit}"));
}
if let Some(ref after) = q.after {
params.push(format!("after={after}"));
}
if params.is_empty() {
base_url
} else {
format!("{base_url}?{}", params.join("&"))
}
} else {
base_url
};
let auth_header = self.resolve_auth_header().await?;
let auth = auth_header.as_ref().map(str_pair);
let all_headers = self.all_headers("GET", &url, &serde_json::Value::Null, &[]);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let raw = http::request::get_json_raw(&self.http, &url, auth, &extra, self.config.max_retries).await?;
serde_json::from_value::<BatchListResponse>(raw).map_err(LiterLlmError::from)
})
}
fn cancel_batch(&self, batch_id: &str) -> BoxFuture<'_, BatchObject> {
let batch_id = batch_id.to_owned();
Box::pin(async move {
let url = format!(
"{}/{}/cancel",
self.provider.build_url(self.provider.batches_path(), ""),
batch_id
);
let auth_header = self.resolve_auth_header().await?;
let body_json = serde_json::Value::Null;
let body_bytes = bytes::Bytes::new();
let all_headers = self.all_headers("POST", &url, &body_json, &body_bytes);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let raw = http::request::post_json_raw(&self.http, &url, auth, &extra, body_bytes, self.config.max_retries)
.await?;
serde_json::from_value::<BatchObject>(raw).map_err(LiterLlmError::from)
})
}
}
#[cfg(feature = "native-http")]
impl ResponseClient for DefaultClient {
fn create_response(&self, req: CreateResponseRequest) -> BoxFuture<'_, ResponseObject> {
Box::pin(async move {
let url = self.provider.build_url(self.provider.responses_path(), "");
let body_bytes = bytes::Bytes::from(serde_json::to_vec(&req)?);
let body_json = serde_json::to_value(&req)?;
let auth_header = self.resolve_auth_header().await?;
let all_headers = self.all_headers("POST", &url, &body_json, &body_bytes);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let raw = http::request::post_json_raw(&self.http, &url, auth, &extra, body_bytes, self.config.max_retries)
.await?;
serde_json::from_value::<ResponseObject>(raw).map_err(LiterLlmError::from)
})
}
fn retrieve_response(&self, id: &str) -> BoxFuture<'_, ResponseObject> {
let id = id.to_owned();
Box::pin(async move {
let url = format!("{}/{}", self.provider.build_url(self.provider.responses_path(), ""), id);
let auth_header = self.resolve_auth_header().await?;
let auth = auth_header.as_ref().map(str_pair);
let all_headers = self.all_headers("GET", &url, &serde_json::Value::Null, &[]);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let raw = http::request::get_json_raw(&self.http, &url, auth, &extra, self.config.max_retries).await?;
serde_json::from_value::<ResponseObject>(raw).map_err(LiterLlmError::from)
})
}
fn cancel_response(&self, id: &str) -> BoxFuture<'_, ResponseObject> {
let id = id.to_owned();
Box::pin(async move {
let url = format!(
"{}/{}/cancel",
self.provider.build_url(self.provider.responses_path(), ""),
id
);
let auth_header = self.resolve_auth_header().await?;
let body_json = serde_json::Value::Null;
let body_bytes = bytes::Bytes::new();
let all_headers = self.all_headers("POST", &url, &body_json, &body_bytes);
let extra: Vec<(&str, &str)> = all_headers.iter().map(|(n, v)| (n.as_str(), v.as_str())).collect();
let auth = auth_header.as_ref().map(str_pair);
let raw = http::request::post_json_raw(&self.http, &url, auth, &extra, body_bytes, self.config.max_retries)
.await?;
serde_json::from_value::<ResponseObject>(raw).map_err(LiterLlmError::from)
})
}
}