use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ModelError {
#[error("provider API error ({status}): {body}")]
Api { status: u16, body: String },
#[error("rate limited — retry after {retry_after_secs}s")]
RateLimited { retry_after_secs: u64 },
#[error("context window exceeded: {input_tokens} tokens > {limit} limit")]
ContextWindowExceeded { input_tokens: u64, limit: u64 },
#[error("network error: {0}")]
Network(String),
#[error("serialization error: {0}")]
Serialization(String),
#[error("timeout")]
Timeout,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ChatRole {
System,
User,
Assistant,
Tool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: ChatRole,
pub content: String,
}
impl ChatMessage {
pub fn system(content: impl Into<String>) -> Self {
Self {
role: ChatRole::System,
content: content.into(),
}
}
pub fn user(content: impl Into<String>) -> Self {
Self {
role: ChatRole::User,
content: content.into(),
}
}
pub fn assistant(content: impl Into<String>) -> Self {
Self {
role: ChatRole::Assistant,
content: content.into(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ModelConfig {
pub model: Option<String>,
pub max_tokens: Option<u32>,
pub temperature: Option<f32>,
pub system_prompt: Option<String>,
pub stop_sequences: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct ModelRequest {
pub messages: Vec<ChatMessage>,
pub config: ModelConfig,
}
impl ModelRequest {
pub fn new(messages: Vec<ChatMessage>) -> Self {
Self {
messages,
config: ModelConfig::default(),
}
}
pub fn with_config(mut self, config: ModelConfig) -> Self {
self.config = config;
self
}
}
#[derive(Debug, Clone)]
pub struct StructuredRequest {
pub messages: Vec<ChatMessage>,
pub config: ModelConfig,
pub output_schema: serde_json::Value,
}
#[derive(Debug, Clone)]
pub struct ModelResponse {
pub content: String,
pub model: String,
pub finish_reason: String,
pub input_tokens: u64,
pub output_tokens: u64,
pub structured: Option<serde_json::Value>,
}
#[async_trait]
pub trait ModelAdapter: Send + Sync {
fn system_name(&self) -> &'static str;
fn default_model(&self) -> &str;
async fn chat(&self, request: ModelRequest) -> Result<ModelResponse, ModelError>;
async fn structured_output(
&self,
request: StructuredRequest,
) -> Result<ModelResponse, ModelError>;
}