alith_interface/llms/api/anthropic/
mod.rs

1pub mod builder;
2pub mod completion;
3
4use super::{
5    client::ApiClient,
6    config::{ApiConfig, ApiConfigTrait},
7};
8use crate::requests::completion::{
9    error::CompletionError, request::CompletionRequest, response::CompletionResponse,
10};
11use alith_devices::logging::LoggingConfig;
12use alith_models::api_model::ApiLLMModel;
13use completion::AnthropicCompletionRequest;
14use reqwest::header::HeaderMap;
15use secrecy::{ExposeSecret, SecretString};
16
17/// Default v1 API base url
18pub const ANTHROPIC_API_HOST: &str = "api.anthropic.com/v1";
19/// Reguired version header
20pub const ANTHROPIC_VERSION_HEADER: &str = "anthropic-version";
21/// Optional beta header
22pub const ANTHROPIC_BETA_HEADER: &str = "anthropic-beta";
23
24pub struct AnthropicBackend {
25    pub(crate) client: ApiClient<AnthropicConfig>,
26    pub model: ApiLLMModel,
27}
28
29impl AnthropicBackend {
30    pub fn new(mut config: AnthropicConfig, model: ApiLLMModel) -> crate::Result<Self> {
31        config.logging_config.load_logger()?;
32        config.api_config.api_key = Some(config.api_config.load_api_key()?);
33        Ok(Self {
34            client: ApiClient::new(config),
35            model,
36        })
37    }
38    pub(crate) async fn completion_request(
39        &self,
40        request: &CompletionRequest,
41    ) -> crate::Result<CompletionResponse, CompletionError> {
42        match self
43            .client
44            .post("/messages", AnthropicCompletionRequest::new(request)?)
45            .await
46        {
47            Err(e) => Err(CompletionError::ClientError(e)),
48            Ok(res) => Ok(CompletionResponse::new_from_anthropic(request, res)?),
49        }
50    }
51}
52
53#[derive(Clone, Debug)]
54pub struct AnthropicConfig {
55    pub api_config: ApiConfig,
56    pub logging_config: LoggingConfig,
57    pub anthropic_version: String,
58    pub anthropic_beta: Option<String>,
59}
60
61impl Default for AnthropicConfig {
62    fn default() -> Self {
63        Self {
64            api_config: ApiConfig {
65                host: ANTHROPIC_API_HOST.to_string(),
66                port: None,
67                api_key: None,
68                api_key_env_var: "ANTHROPIC_API_KEY".to_string(),
69            },
70            logging_config: LoggingConfig {
71                logger_name: "anthropic".to_string(),
72                ..Default::default()
73            },
74            anthropic_version: "2023-06-01".to_string(),
75            anthropic_beta: None,
76        }
77    }
78}
79
80impl AnthropicConfig {
81    pub fn new() -> Self {
82        Default::default()
83    }
84
85    pub fn with_anthropic_version<S: Into<String>>(mut self, version: S) -> Self {
86        self.anthropic_version = version.into();
87        self
88    }
89
90    pub fn with_anthropic_beta<S: Into<String>>(mut self, beta: S) -> Self {
91        self.anthropic_beta = Some(beta.into());
92        self
93    }
94}
95
96impl ApiConfigTrait for AnthropicConfig {
97    fn headers(&self) -> HeaderMap {
98        let mut headers = HeaderMap::new();
99        headers.insert(
100            ANTHROPIC_VERSION_HEADER,
101            self.anthropic_version.as_str().parse().unwrap(),
102        );
103
104        if let Some(anthropic_beta) = &self.anthropic_beta {
105            headers.insert(
106                ANTHROPIC_BETA_HEADER,
107                anthropic_beta.as_str().parse().unwrap(),
108            );
109        }
110
111        if let Some(api_key) = self.api_key() {
112            headers.insert(
113                reqwest::header::HeaderName::from_static("x-api-key"),
114                reqwest::header::HeaderValue::from_str(api_key.expose_secret()).unwrap(),
115            );
116        }
117
118        headers
119    }
120
121    fn url(&self, path: &str) -> String {
122        format!("https://{}{}", self.api_config.host, path)
123    }
124
125    fn api_key(&self) -> &Option<SecretString> {
126        &self.api_config.api_key
127    }
128}