use secrecy::SecretString;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChatRole {
User,
Assistant,
}
#[derive(Debug, Clone)]
pub enum ChatContent {
Text(String),
Parts(Vec<ContentPart>),
}
#[derive(Debug, Clone)]
pub enum ContentPart {
Text { text: String },
ImageUrl { url: String },
ImageBase64 { data: String, media_type: String },
}
#[derive(Clone)]
pub struct ChatRequest {
pub provider: String,
pub model: String,
pub messages: Vec<ChatMessage>,
pub system: Option<String>,
pub max_tokens: Option<u32>,
pub temperature: Option<f64>,
pub top_p: Option<f64>,
pub stop: Option<Vec<String>>,
pub api_key: Option<SecretString>,
pub base_url: Option<String>,
pub timeout_secs: u64,
pub max_response_bytes: u64,
pub extra: Option<serde_json::Value>,
}
impl std::fmt::Debug for ChatRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ChatRequest")
.field("provider", &self.provider)
.field("model", &self.model)
.field("messages", &self.messages)
.field("system", &self.system)
.field("max_tokens", &self.max_tokens)
.field("temperature", &self.temperature)
.field("top_p", &self.top_p)
.field("stop", &self.stop)
.field("api_key", &self.api_key.as_ref().map(|_| "[REDACTED]"))
.field("base_url", &self.base_url)
.field("timeout_secs", &self.timeout_secs)
.field("max_response_bytes", &self.max_response_bytes)
.field("extra", &self.extra)
.finish()
}
}
#[derive(Debug, Clone)]
pub struct ChatMessage {
pub role: ChatRole,
pub content: ChatContent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FinishReason {
Stop,
MaxTokens,
ContentFilter,
Error,
}
#[derive(Debug, Clone, Default)]
pub struct Usage {
pub input_tokens: u32,
pub output_tokens: u32,
}
#[derive(Debug, Clone)]
pub struct ChatResponse {
pub content: String,
pub finish_reason: FinishReason,
pub usage: Usage,
pub model: String,
}
pub trait LlmProvider: Send + Sync + 'static {
fn name(&self) -> &str;
fn default_base_url(&self) -> Option<&str> {
None
}
fn chat(&self, request: &ChatRequest) -> Result<ChatResponse, String>;
}