bep/providers/anthropic/
client.rs

1//! Anthropic client api implementation
2
3use crate::{agent::AgentBuilder, extractor::ExtractorBuilder};
4
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use super::completion::{CompletionModel, ANTHROPIC_VERSION_LATEST};
9
10// ================================================================
11// Main Anthropic Client
12// ================================================================
13const ANTHROPIC_API_BASE_URL: &str = "https://api.anthropic.com";
14
15#[derive(Clone)]
16pub struct ClientBuilder<'a> {
17    api_key: &'a str,
18    base_url: &'a str,
19    anthropic_version: &'a str,
20    anthropic_betas: Option<Vec<&'a str>>,
21}
22
23/// Create a new anthropic client using the builder
24///
25/// # Example
26/// ```
27/// use bep::providers::anthropic::{ClientBuilder, self};
28///
29/// // Initialize the Anthropic client
30/// let anthropic_client = ClientBuilder::new("your-claude-api-key")
31///    .anthropic_version(ANTHROPIC_VERSION_LATEST)
32///    .anthropic_beta("prompt-caching-2024-07-31")
33///    .build()
34/// ```
35impl<'a> ClientBuilder<'a> {
36    pub fn new(api_key: &'a str) -> Self {
37        Self {
38            api_key,
39            base_url: ANTHROPIC_API_BASE_URL,
40            anthropic_version: ANTHROPIC_VERSION_LATEST,
41            anthropic_betas: None,
42        }
43    }
44
45    pub fn base_url(mut self, base_url: &'a str) -> Self {
46        self.base_url = base_url;
47        self
48    }
49
50    pub fn anthropic_version(mut self, anthropic_version: &'a str) -> Self {
51        self.anthropic_version = anthropic_version;
52        self
53    }
54
55    pub fn anthropic_beta(mut self, anthropic_beta: &'a str) -> Self {
56        if let Some(mut betas) = self.anthropic_betas {
57            betas.push(anthropic_beta);
58            self.anthropic_betas = Some(betas);
59        } else {
60            self.anthropic_betas = Some(vec![anthropic_beta]);
61        }
62        self
63    }
64
65    pub fn build(self) -> Client {
66        Client::new(
67            self.api_key,
68            self.base_url,
69            self.anthropic_betas,
70            self.anthropic_version,
71        )
72    }
73}
74
75#[derive(Clone)]
76pub struct Client {
77    base_url: String,
78    http_client: reqwest::Client,
79}
80
81impl Client {
82    /// Create a new Anthropic client with the given API key, base URL, betas, and version.
83    /// Note, you proably want to use the `ClientBuilder` instead.
84    ///
85    /// Panics:
86    /// - If the API key or version cannot be parsed as a Json value from a String.
87    ///   - This should really never happen.
88    /// - If the reqwest client cannot be built (if the TLS backend cannot be initialized).
89    pub fn new(api_key: &str, base_url: &str, betas: Option<Vec<&str>>, version: &str) -> Self {
90        Self {
91            base_url: base_url.to_string(),
92            http_client: reqwest::Client::builder()
93                .default_headers({
94                    let mut headers = reqwest::header::HeaderMap::new();
95                    headers.insert("x-api-key", api_key.parse().expect("API key should parse"));
96                    headers.insert(
97                        "anthropic-version",
98                        version.parse().expect("Anthropic version should parse"),
99                    );
100                    if let Some(betas) = betas {
101                        headers.insert(
102                            "anthropic-beta",
103                            betas
104                                .join(",")
105                                .parse()
106                                .expect("Anthropic betas should parse"),
107                        );
108                    }
109                    headers
110                })
111                .build()
112                .expect("Anthropic reqwest client should build"),
113        }
114    }
115
116    /// Create a new Anthropic client from the `ANTHROPIC_API_KEY` environment variable.
117    /// Panics if the environment variable is not set.
118    pub fn from_env() -> Self {
119        let api_key = std::env::var("ANTHROPIC_API_KEY").expect("ANTHROPIC_API_KEY not set");
120        ClientBuilder::new(&api_key).build()
121    }
122
123    pub fn post(&self, path: &str) -> reqwest::RequestBuilder {
124        let url = format!("{}/{}", self.base_url, path).replace("//", "/");
125        self.http_client.post(url)
126    }
127
128    pub fn completion_model(&self, model: &str) -> CompletionModel {
129        CompletionModel::new(self.clone(), model)
130    }
131
132    /// Create an agent builder with the given completion model.
133    ///
134    /// # Example
135    /// ```
136    /// use bep::providers::anthropic::{ClientBuilder, self};
137    ///
138    /// // Initialize the Anthropic client
139    /// let anthropic = ClientBuilder::new("your-claude-api-key").build();
140    ///
141    /// let agent = anthropic.agent(anthropic::CLAUDE_3_5_SONNET)
142    ///    .preamble("You are comedian AI with a mission to make people laugh.")
143    ///    .temperature(0.0)
144    ///    .build();
145    /// ```
146    pub fn agent(&self, model: &str) -> AgentBuilder<CompletionModel> {
147        AgentBuilder::new(self.completion_model(model))
148    }
149
150    pub fn extractor<T: JsonSchema + for<'a> Deserialize<'a> + Serialize + Send + Sync>(
151        &self,
152        model: &str,
153    ) -> ExtractorBuilder<T, CompletionModel> {
154        ExtractorBuilder::new(self.completion_model(model))
155    }
156}