1mod base64;
7mod provider;
8mod schema;
9mod validate;
10
11use std::fmt;
12
13pub use provider::{parse_model_spec, DEFAULT_TIMEOUT_SECS};
14
15#[derive(Debug, Clone, serde::Serialize)]
18pub struct Schema {
19 pub type_name: String,
20 pub fields: Vec<SchemaField>,
22}
23
24#[derive(Debug, Clone, serde::Serialize)]
26pub struct SchemaField {
27 pub name: String,
28 pub field_type: FieldType,
29 pub description: Option<String>,
30 pub pattern: Option<String>,
31}
32
33#[derive(Debug, Clone, serde::Serialize)]
34pub enum FieldType {
35 Str,
36 Int,
37 Float,
38 Bool,
39 ListOfStr,
40}
41
42impl FieldType {
43 pub(crate) fn display_name(&self) -> &'static str {
45 match self {
46 FieldType::Str => "string",
47 FieldType::Int => "integer",
48 FieldType::Float => "float",
49 FieldType::Bool => "boolean",
50 FieldType::ListOfStr => "list of strings",
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
56pub struct ModelConfig {
57 pub provider: Provider,
58 pub model: String,
60 pub endpoint: Option<String>,
62 pub api_key: Option<String>,
64 pub max_output_tokens: u32,
66 pub timeout_secs: u64,
69}
70
71#[derive(Debug, Clone, PartialEq)]
72pub enum Provider {
73 OpenAI,
74 Ollama,
75}
76
77#[derive(Debug, Clone)]
78pub struct AnalyzeRequest {
79 pub prompt: String,
81 pub data_json: String,
83 pub images: Vec<ImagePart>,
89 pub schema: Schema,
90 pub tools: Vec<ToolSpec>,
92 pub tool_history: Vec<ToolExchange>,
95}
96
97#[derive(Debug, Clone)]
103pub struct ImagePart {
104 pub mime: String,
106 pub bytes: Vec<u8>,
107}
108
109#[derive(Debug, Clone)]
112pub struct ToolSpec {
113 pub name: String,
114 pub description: String,
115 pub params: Vec<(String, FieldType)>,
116}
117
118#[derive(Debug, Clone)]
120pub struct ToolExchange {
121 pub name: String,
122 pub arguments_json: String,
123 pub result_json: String,
124}
125
126#[derive(Debug, Clone)]
128pub enum Step {
129 Done(AnalyzeOutcome),
131 CallTool {
133 name: String,
134 arguments_json: String,
135 tokens_in: u64,
136 tokens_out: u64,
137 },
138}
139
140#[derive(Debug, Clone)]
141pub enum AnalyzeOutcome {
142 Ok {
144 fields_json: serde_json::Map<String, serde_json::Value>,
145 tokens_in: u64,
146 tokens_out: u64,
147 },
148 Uncertain {
150 reason: String,
151 tokens_in: u64,
152 tokens_out: u64,
153 },
154}
155
156#[derive(Debug)]
157pub struct ModelError {
158 pub message: String,
159}
160
161impl ModelError {
162 pub fn new(message: impl Into<String>) -> Self {
163 ModelError {
164 message: message.into(),
165 }
166 }
167}
168
169impl fmt::Display for ModelError {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 write!(f, "{}", self.message)
172 }
173}
174
175impl std::error::Error for ModelError {}
176
177pub fn analyze(config: &ModelConfig, req: &AnalyzeRequest) -> Result<AnalyzeOutcome, ModelError> {
179 match provider::step_with(config, req, &*provider::transport_for(config))? {
180 Step::Done(outcome) => Ok(outcome),
181 Step::CallTool { name, .. } => Err(ModelError::new(format!(
184 "model tried to call tool `{name}`, but no tools were provided"
185 ))),
186 }
187}
188
189pub fn step(config: &ModelConfig, req: &AnalyzeRequest) -> Result<Step, ModelError> {
191 provider::step_with(config, req, &*provider::transport_for(config))
192}