alith_interface/llms/api/generic_openai/
mod.rs

1use super::{
2    client::ApiClient,
3    config::{ApiConfig, ApiConfigTrait},
4    openai::completion::OpenAICompletionRequest,
5};
6use crate::requests::{
7    completion::{
8        error::CompletionError, request::CompletionRequest, response::CompletionResponse,
9    },
10    embeddings::{EmbeddingsError, EmbeddingsRequest, EmbeddingsResponse},
11};
12use alith_devices::logging::LoggingConfig;
13use alith_models::api_model::ApiLLMModel;
14use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue};
15use secrecy::{ExposeSecret, SecretString};
16use serde_json::json;
17
18pub struct GenericApiBackend {
19    pub(crate) client: ApiClient<GenericApiConfig>,
20    pub model: ApiLLMModel,
21}
22
23impl GenericApiBackend {
24    pub fn new(mut config: GenericApiConfig, model: ApiLLMModel) -> crate::Result<Self> {
25        config.logging_config.load_logger()?;
26        if let Ok(api_key) = config.api_config.load_api_key() {
27            config.api_config.api_key = Some(api_key);
28        }
29        Ok(Self {
30            client: ApiClient::new(config),
31            model,
32        })
33    }
34
35    pub(crate) async fn completion_request(
36        &self,
37        request: &CompletionRequest,
38    ) -> crate::Result<CompletionResponse, CompletionError> {
39        match self
40            .client
41            .post(
42                &self.client.config.completion_path,
43                OpenAICompletionRequest::new(request)?,
44            )
45            .await
46        {
47            Err(e) => Err(CompletionError::ClientError(e)),
48            Ok(res) => Ok(CompletionResponse::new_from_openai(request, res)?),
49        }
50    }
51
52    pub(crate) async fn embeddings_request(
53        &self,
54        request: &EmbeddingsRequest,
55    ) -> crate::Result<EmbeddingsResponse, EmbeddingsError> {
56        match self
57            .client
58            .post(
59                "/embeddings",
60                json!({
61                    "input": request.input,
62                    "model": request.model,
63                }),
64            )
65            .await
66        {
67            Ok(res) => Ok(res),
68            Err(e) => Err(EmbeddingsError::ClientError(e)),
69        }
70    }
71}
72
73#[derive(Clone, Debug)]
74pub struct GenericApiConfig {
75    pub api_config: ApiConfig,
76    pub logging_config: LoggingConfig,
77    pub completion_path: String,
78}
79
80impl Default for GenericApiConfig {
81    fn default() -> Self {
82        Self {
83            api_config: ApiConfig {
84                host: Default::default(),
85                port: None,
86                api_key: None,
87                api_key_env_var: Default::default(),
88            },
89            logging_config: LoggingConfig {
90                logger_name: "generic".to_string(),
91                ..Default::default()
92            },
93            completion_path: "/chat/completions".to_string(),
94        }
95    }
96}
97
98impl GenericApiConfig {
99    pub fn new() -> Self {
100        Default::default()
101    }
102
103    pub fn completion_path<S: Into<String>>(mut self, path: S) -> Self {
104        self.completion_path = path.into();
105        self
106    }
107}
108
109impl ApiConfigTrait for GenericApiConfig {
110    fn headers(&self) -> HeaderMap {
111        let mut headers = HeaderMap::new();
112        if let Some(api_key) = self.api_key() {
113            if let Ok(header_value) =
114                HeaderValue::from_str(&format!("Bearer {}", api_key.expose_secret()))
115            {
116                headers.insert(AUTHORIZATION, header_value);
117            } else {
118                crate::error!("Failed to create header value from authorization value");
119            }
120        }
121
122        headers
123    }
124
125    fn url(&self, path: &str) -> String {
126        if let Some(port) = &self.api_config.port {
127            format!("https://{}:{}{}", self.api_config.host, port, path)
128        } else {
129            format!("https://{}:{}", self.api_config.host, path)
130        }
131    }
132
133    fn api_key(&self) -> &Option<SecretString> {
134        &self.api_config.api_key
135    }
136}