mod base64;
mod provider;
mod schema;
mod validate;
use std::fmt;
use std::rc::Rc;
pub use provider::{parse_model_spec, DEFAULT_TIMEOUT_SECS};
#[derive(Debug, Clone, serde::Serialize)]
pub struct Schema {
pub type_name: String,
pub fields: Vec<SchemaField>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct SchemaField {
pub name: String,
pub field_type: FieldType,
pub description: Option<String>,
pub pattern: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub enum FieldType {
Str,
Int,
Float,
Bool,
ListOfStr,
Object(Rc<Schema>),
ListOfObject(Rc<Schema>),
}
impl FieldType {
pub(crate) fn display_name(&self) -> String {
match self {
FieldType::Str => "string".to_string(),
FieldType::Int => "integer".to_string(),
FieldType::Float => "float".to_string(),
FieldType::Bool => "boolean".to_string(),
FieldType::ListOfStr => "list of strings".to_string(),
FieldType::Object(schema) => format!("object `{}`", schema.type_name),
FieldType::ListOfObject(schema) => format!("list of `{}` objects", schema.type_name),
}
}
}
#[derive(Debug, Clone)]
pub struct ModelConfig {
pub provider: Provider,
pub model: String,
pub endpoint: Option<String>,
pub api_key: Option<String>,
pub max_output_tokens: u32,
pub timeout_secs: u64,
pub max_retries: u32,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Provider {
OpenAI,
Ollama,
}
#[derive(Debug, Clone)]
pub struct AnalyzeRequest {
pub prompt: String,
pub data_json: String,
pub images: Vec<ImagePart>,
pub schema: Schema,
pub tools: Vec<ToolSpec>,
pub tool_history: Vec<ToolExchange>,
}
#[derive(Debug, Clone)]
pub struct ImagePart {
pub mime: String,
pub bytes: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct ToolSpec {
pub name: String,
pub description: String,
pub params: Vec<(String, FieldType)>,
}
#[derive(Debug, Clone)]
pub struct ToolExchange {
pub name: String,
pub arguments_json: String,
pub result_json: String,
}
#[derive(Debug, Clone)]
pub enum Step {
Done(AnalyzeOutcome),
CallTool {
name: String,
arguments_json: String,
tokens_in: u64,
tokens_out: u64,
},
}
#[derive(Debug, Clone)]
pub enum AnalyzeOutcome {
Ok {
fields_json: serde_json::Map<String, serde_json::Value>,
tokens_in: u64,
tokens_out: u64,
},
Uncertain {
reason: String,
tokens_in: u64,
tokens_out: u64,
},
Failed {
reason: String,
tokens_in: u64,
tokens_out: u64,
},
}
#[derive(Debug)]
pub struct ModelError {
pub message: String,
pub retryable: bool,
}
impl ModelError {
pub fn new(message: impl Into<String>) -> Self {
ModelError {
message: message.into(),
retryable: false,
}
}
pub fn retryable(message: impl Into<String>) -> Self {
ModelError {
message: message.into(),
retryable: true,
}
}
}
impl fmt::Display for ModelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for ModelError {}
pub fn analyze(config: &ModelConfig, req: &AnalyzeRequest) -> Result<AnalyzeOutcome, ModelError> {
match provider::step_with(config, req, &*provider::transport_for(config))? {
Step::Done(outcome) => Ok(outcome),
Step::CallTool { name, .. } => Err(ModelError::new(format!(
"model tried to call tool `{name}`, but no tools were provided"
))),
}
}
pub fn step(config: &ModelConfig, req: &AnalyzeRequest) -> Result<Step, ModelError> {
provider::step_with(config, req, &*provider::transport_for(config))
}