llm_api_access/
anthropic.rs

1// src/anthropic.rs
2use serde::{Deserialize, Serialize};
3use reqwest::header::{HeaderMap, HeaderValue};
4use reqwest::Client;
5use std::env;
6use crate::errors::GeneralError;
7use dotenv::dotenv;
8
9use crate::structs::general::Message;
10
11// https://docs.anthropic.com/claude/reference/getting-started-with-the-api
12
13#[derive(Debug, Serialize, Clone)]
14pub struct AnthropicRequest {
15    pub model: String,
16    pub max_tokens: usize,
17    pub messages: Vec<Message>,
18}
19
20#[derive(Debug, Deserialize)]
21pub struct AnthropicResult {
22    pub response: AnthropicMessage,
23}
24
25#[derive(Debug, Deserialize)]
26pub struct AnthropicMessage {
27    pub content: String,
28}
29
30const MODEL: &str = "claude-3-7-sonnet-20250219";
31const MAX_TOKENS: usize = 4096;
32
33use std::str;
34
35#[derive(Debug, Deserialize)]
36pub struct AnthropicResponse {
37    pub id: String,
38    pub role: String,
39    pub content: Vec<Content>,
40    // Other fields omitted for brevity
41}
42
43#[derive(Debug, Deserialize)]
44pub struct Content {
45    pub text: String,
46    // Other fields omitted for brevity
47}
48
49pub async fn call_anthropic(
50    messages: Vec<Message>,
51) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
52    dotenv().ok();
53
54    let api_key: String = env::var("ANTHROPIC_API_KEY").map_err(|_| GeneralError {
55        message: "ANTHROPIC API KEY not found in environment variables".to_string(),
56    })?;
57
58    let url: &str = "https://api.anthropic.com/v1/messages";
59
60    let mut headers: HeaderMap = HeaderMap::new();
61
62    headers.insert(
63        "x-api-key",
64        HeaderValue::from_str(&api_key)
65            .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
66                Box::new(GeneralError {
67                    message: format!("Invalid Anthropic API key: {}", e.to_string()),
68                })
69            })?,
70    );
71
72    headers.insert(
73        "anthropic-version",
74        HeaderValue::from_str("2023-06-01")
75            .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
76                Box::new(GeneralError {
77                    message: format!("Failed to set Anthropic version header: {}", e.to_string()),
78                })
79            })?,
80    );
81
82    headers.insert(
83        "content-type",
84        HeaderValue::from_str("application/json")
85            .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
86                Box::new(GeneralError {
87                    message: format!("Failed to set content-type header: {}", e.to_string()),
88                })
89            })?,
90    );
91
92    let client = Client::builder()
93        .default_headers(headers)
94        .build()
95        .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
96            Box::new(GeneralError {
97                message: format!("Failed to create HTTP client: {}", e.to_string()),
98            })
99        })?;
100
101    // Convert "model" roles to "assistant" roles
102    let processed_messages = messages.into_iter().map(|mut message| {
103        if message.role == "model" {
104            message.role = "assistant".to_string();
105        }
106        message
107    }).collect::<Vec<Message>>();
108
109    let request: AnthropicRequest = AnthropicRequest {
110        model: MODEL.to_string(),
111        max_tokens: MAX_TOKENS,
112        messages: processed_messages,
113    };
114
115    let res = client
116        .post(url)
117        .json(&request)
118        .send()
119        .await
120        .map_err(|e| {
121            println!("{:?}", e);
122            Box::new(GeneralError {
123                message: format!("Failed to send request to Anthropic API: {}", e.to_string()),
124            }) as Box<dyn std::error::Error + Send + Sync>
125        })?;
126
127    // Rest of the function remains the same
128    let rspns_strng = res.text().await.map_err(|e: reqwest::Error| {
129        Box::new(GeneralError {
130            message: format!("Failed to read response from Anthropic API: {}", e.to_string()),
131        }) as Box<dyn std::error::Error + Send + Sync>
132    })?;
133
134    // println!("Raw response: {}", rspns_strng);
135
136    if rspns_strng.contains("invalid x-api-key") {
137        return Err(Box::new(GeneralError {
138            message: "Invalid Anthropic API key. Please check your API key and try again."
139                .to_string(),
140        }));
141    }
142
143    let res: AnthropicResponse = serde_json::from_str(&rspns_strng).map_err(|e| {
144        println!("AnthropicResponse res: {:?}", e);
145        Box::new(GeneralError {
146            message: format!(
147                "Failed to parse response from Anthropic API: {}",
148                e.to_string()
149            ),
150        }) as Box<dyn std::error::Error + Send + Sync>
151    })?;
152
153    Ok(res.content[0].text.clone())
154}