rig/providers/anthropic/
client.rs

1//! Anthropic client api implementation
2use super::completion::{ANTHROPIC_VERSION_LATEST, CompletionModel};
3use crate::client::{
4    ClientBuilderError, CompletionClient, ProviderClient, ProviderValue, impl_conversion_traits,
5};
6
7// ================================================================
8// Main Anthropic Client
9// ================================================================
10const ANTHROPIC_API_BASE_URL: &str = "https://api.anthropic.com";
11
12pub struct ClientBuilder<'a> {
13    api_key: &'a str,
14    base_url: &'a str,
15    anthropic_version: &'a str,
16    anthropic_betas: Option<Vec<&'a str>>,
17    http_client: Option<reqwest::Client>,
18}
19
20/// Create a new anthropic client using the builder
21///
22/// # Example
23/// ```
24/// use rig::providers::anthropic::{ClientBuilder, self};
25///
26/// // Initialize the Anthropic client
27/// let anthropic_client = ClientBuilder::new("your-claude-api-key")
28///    .anthropic_version(ANTHROPIC_VERSION_LATEST)
29///    .anthropic_beta("prompt-caching-2024-07-31")
30///    .build()
31/// ```
32impl<'a> ClientBuilder<'a> {
33    pub fn new(api_key: &'a str) -> Self {
34        Self {
35            api_key,
36            base_url: ANTHROPIC_API_BASE_URL,
37            anthropic_version: ANTHROPIC_VERSION_LATEST,
38            anthropic_betas: None,
39            http_client: None,
40        }
41    }
42
43    pub fn base_url(mut self, base_url: &'a str) -> Self {
44        self.base_url = base_url;
45        self
46    }
47
48    pub fn anthropic_version(mut self, anthropic_version: &'a str) -> Self {
49        self.anthropic_version = anthropic_version;
50        self
51    }
52
53    pub fn anthropic_beta(mut self, anthropic_beta: &'a str) -> Self {
54        if let Some(mut betas) = self.anthropic_betas {
55            betas.push(anthropic_beta);
56            self.anthropic_betas = Some(betas);
57        } else {
58            self.anthropic_betas = Some(vec![anthropic_beta]);
59        }
60        self
61    }
62
63    pub fn custom_client(mut self, client: reqwest::Client) -> Self {
64        self.http_client = Some(client);
65        self
66    }
67
68    pub fn build(self) -> Result<Client, ClientBuilderError> {
69        let mut default_headers = reqwest::header::HeaderMap::new();
70        default_headers.insert(
71            "anthropic-version",
72            self.anthropic_version
73                .parse()
74                .map_err(|_| ClientBuilderError::InvalidProperty("anthropic-version"))?,
75        );
76        if let Some(betas) = self.anthropic_betas {
77            default_headers.insert(
78                "anthropic-beta",
79                betas
80                    .join(",")
81                    .parse()
82                    .map_err(|_| ClientBuilderError::InvalidProperty("anthropic-beta"))?,
83            );
84        };
85
86        let http_client = if let Some(http_client) = self.http_client {
87            http_client
88        } else {
89            reqwest::Client::builder().build()?
90        };
91
92        Ok(Client {
93            base_url: self.base_url.to_string(),
94            api_key: self.api_key.to_string(),
95            default_headers,
96            http_client,
97        })
98    }
99}
100
101#[derive(Clone)]
102pub struct Client {
103    /// The base URL
104    base_url: String,
105    /// The API key
106    api_key: String,
107    /// The underlying HTTP client
108    http_client: reqwest::Client,
109    /// Default headers that will be automatically added to any given request with this client (API key, Anthropic Version and any betas that have been added)
110    default_headers: reqwest::header::HeaderMap,
111}
112
113impl std::fmt::Debug for Client {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        f.debug_struct("Client")
116            .field("base_url", &self.base_url)
117            .field("http_client", &self.http_client)
118            .field("api_key", &"<REDACTED>")
119            .field("default_headers", &self.default_headers)
120            .finish()
121    }
122}
123
124impl Client {
125    /// Create a new Anthropic client builder.
126    ///
127    /// # Example
128    /// ```
129    /// use rig::providers::anthropic::{ClientBuilder, self};
130    ///
131    /// // Initialize the Anthropic client
132    /// let anthropic_client = Client::builder("your-claude-api-key")
133    ///    .anthropic_version(ANTHROPIC_VERSION_LATEST)
134    ///    .anthropic_beta("prompt-caching-2024-07-31")
135    ///    .build()
136    /// ```
137    pub fn builder(api_key: &str) -> ClientBuilder<'_> {
138        ClientBuilder::new(api_key)
139    }
140
141    /// Create a new Anthropic client. For more control, use the `builder` method.
142    ///
143    /// # Panics
144    /// - If the API key or version cannot be parsed as a Json value from a String.
145    /// - If the reqwest client cannot be built (if the TLS backend cannot be initialized).
146    pub fn new(api_key: &str) -> Self {
147        Self::builder(api_key)
148            .build()
149            .expect("Anthropic client should build")
150    }
151
152    pub(crate) fn post(&self, path: &str) -> reqwest::RequestBuilder {
153        let url = format!("{}/{}", self.base_url, path).replace("//", "/");
154        self.http_client
155            .post(url)
156            .header("X-Api-Key", &self.api_key)
157            .headers(self.default_headers.clone())
158    }
159}
160
161impl ProviderClient for Client {
162    /// Create a new Anthropic client from the `ANTHROPIC_API_KEY` environment variable.
163    /// Panics if the environment variable is not set.
164    fn from_env() -> Self {
165        let api_key = std::env::var("ANTHROPIC_API_KEY").expect("ANTHROPIC_API_KEY not set");
166        Client::new(&api_key)
167    }
168
169    fn from_val(input: crate::client::ProviderValue) -> Self {
170        let ProviderValue::Simple(api_key) = input else {
171            panic!("Incorrect provider value type")
172        };
173        Client::new(&api_key)
174    }
175}
176
177impl CompletionClient for Client {
178    type CompletionModel = CompletionModel;
179    fn completion_model(&self, model: &str) -> CompletionModel {
180        CompletionModel::new(self.clone(), model)
181    }
182}
183
184impl_conversion_traits!(
185    AsTranscription,
186    AsEmbeddings,
187    AsImageGeneration,
188    AsAudioGeneration for Client
189);