use crate::RedOxideResult;
use async_openai::{
config::OpenAIConfig,
types::{
ChatCompletionRequestMessage, ChatCompletionRequestUserMessageArgs,
CreateChatCompletionRequestArgs,
},
Client,
};
use async_trait::async_trait;
use reqwest::Client as HttpClient;
use serde_json::json;
#[async_trait]
pub trait Target: Send + Sync {
async fn send_prompt(&self, prompt: &str) -> RedOxideResult<String>;
}
pub struct OpenAITarget {
client: Client<OpenAIConfig>,
model: String,
}
impl OpenAITarget {
pub fn new(api_key: String, model: String) -> Self {
let config = OpenAIConfig::new().with_api_key(api_key);
let client = Client::with_config(config);
Self { client, model }
}
}
#[async_trait]
impl Target for OpenAITarget {
async fn send_prompt(&self, prompt: &str) -> RedOxideResult<String> {
let user_msg_struct = ChatCompletionRequestUserMessageArgs::default()
.content(prompt)
.build()?;
let message = ChatCompletionRequestMessage::User(user_msg_struct);
let request = CreateChatCompletionRequestArgs::default()
.model(&self.model)
.messages(vec![message])
.build()?;
let response = self.client.chat().create(request).await?;
Ok(response
.choices
.first()
.and_then(|c| c.message.content.clone())
.unwrap_or_default())
}
}
pub struct AnthropicTarget {
client: HttpClient,
api_key: String,
model: String,
}
impl AnthropicTarget {
pub fn new(api_key: String, model: String) -> Self {
Self {
client: HttpClient::new(),
api_key,
model,
}
}
}
#[async_trait]
impl Target for AnthropicTarget {
async fn send_prompt(&self, prompt: &str) -> RedOxideResult<String> {
let url = "https://api.anthropic.com/v1/messages";
let body = json!({
"model": self.model,
"max_tokens": 1024,
"messages": [{ "role": "user", "content": prompt }]
});
let res = self
.client
.post(url)
.header("x-api-key", &self.api_key)
.header("anthropic-version", "2023-06-01")
.header("content-type", "application/json")
.json(&body)
.send()
.await?;
let json_resp: serde_json::Value = res.json().await?;
let text = json_resp["content"][0]["text"]
.as_str()
.unwrap_or("")
.to_string();
Ok(text)
}
}
pub struct OllamaTarget {
client: HttpClient,
endpoint: String,
model: String,
}
impl OllamaTarget {
pub fn new(endpoint: String, model: String) -> Self {
Self {
client: HttpClient::new(),
endpoint,
model,
}
}
}
#[async_trait]
impl Target for OllamaTarget {
async fn send_prompt(&self, prompt: &str) -> RedOxideResult<String> {
let url = format!("{}/api/chat", self.endpoint);
let body = json!({
"model": self.model,
"messages": [{ "role": "user", "content": prompt }],
"stream": false
});
let res = self.client.post(&url).json(&body).send().await?;
let json_resp: serde_json::Value = res.json().await?;
let text = json_resp["message"]["content"]
.as_str()
.unwrap_or("")
.to_string();
Ok(text)
}
}
pub struct GeminiTarget {
client: HttpClient,
api_key: String,
model: String,
}
impl GeminiTarget {
pub fn new(api_key: String, model: String) -> Self {
Self {
client: HttpClient::new(),
api_key,
model,
}
}
}
#[async_trait]
impl Target for GeminiTarget {
async fn send_prompt(&self, prompt: &str) -> RedOxideResult<String> {
let url = format!(
"https://generativelanguage.googleapis.com/v1beta/models/{}:generateContent?key={}",
self.model, self.api_key
);
let body = json!({
"contents": [{
"parts": [{
"text": prompt
}]
}]
});
let res = self
.client
.post(&url)
.header("content-type", "application/json")
.json(&body)
.send()
.await?;
if !res.status().is_success() {
let error_text = res.text().await?;
return Err(anyhow::anyhow!("Gemini API Error: {}", error_text));
}
let json_resp: serde_json::Value = res.json().await?;
let text = json_resp["candidates"][0]["content"]["parts"][0]["text"]
.as_str()
.unwrap_or("")
.to_string();
Ok(text)
}
}