rig/providers/anthropic/
client.rs

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