gglib_core/domain/model.rs
1//! Model domain types.
2//!
3//! These types represent models in the system, independent of any
4//! infrastructure concerns (database, filesystem, etc.).
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::path::PathBuf;
10
11use super::capabilities::ModelCapabilities;
12use super::inference::InferenceConfig;
13
14// ─────────────────────────────────────────────────────────────────────────────
15// Filter/Aggregate Types
16// ─────────────────────────────────────────────────────────────────────────────
17
18/// Filter options for the model library UI.
19///
20/// Contains aggregate data about available models for building
21/// dynamic filter controls.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ModelFilterOptions {
24 /// All distinct quantization types present in the library.
25 pub quantizations: Vec<String>,
26 /// Minimum and maximum parameter counts (in billions).
27 pub param_range: Option<RangeValues>,
28 /// Minimum and maximum context lengths.
29 pub context_range: Option<RangeValues>,
30}
31
32/// A range of numeric values with min and max.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct RangeValues {
35 pub min: f64,
36 pub max: f64,
37}
38
39// ─────────────────────────────────────────────────────────────────────────────
40// Model Types
41// ─────────────────────────────────────────────────────────────────────────────
42
43/// A model that exists in the system with a database ID.
44///
45/// This represents a persisted model with all its metadata.
46/// Use `NewModel` for models that haven't been persisted yet.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct Model {
49 /// Database ID of the model (always present for persisted models).
50 pub id: i64,
51 /// Human-readable name for the model.
52 pub name: String,
53 /// Absolute path to the GGUF file on the filesystem.
54 pub file_path: PathBuf,
55 /// Number of parameters in the model (in billions).
56 pub param_count_b: f64,
57 /// Model architecture (e.g., "llama", "mistral", "falcon").
58 pub architecture: Option<String>,
59 /// Quantization type (e.g., "`Q4_0`", "`Q8_0`", "`F16`", "`F32`").
60 pub quantization: Option<String>,
61 /// Maximum context length the model supports.
62 pub context_length: Option<u64>,
63 /// Additional metadata key-value pairs from the GGUF file.
64 pub metadata: HashMap<String, String>,
65 /// UTC timestamp of when the model was added to the database.
66 pub added_at: DateTime<Utc>,
67 /// `HuggingFace` repository ID (e.g., "`TheBloke/Llama-2-7B-GGUF`").
68 pub hf_repo_id: Option<String>,
69 /// Git commit SHA from `HuggingFace` Hub.
70 pub hf_commit_sha: Option<String>,
71 /// Original filename on `HuggingFace` Hub.
72 pub hf_filename: Option<String>,
73 /// Timestamp of when this model was downloaded from `HuggingFace`.
74 pub download_date: Option<DateTime<Utc>>,
75 /// Last time we checked for updates on `HuggingFace`.
76 pub last_update_check: Option<DateTime<Utc>>,
77 /// User-defined tags for organizing models.
78 pub tags: Vec<String>,
79 /// Model capabilities inferred from chat template analysis.
80 #[serde(default)]
81 pub capabilities: ModelCapabilities,
82 /// Per-model inference parameter defaults.
83 ///
84 /// These are preferred over global settings when making inference requests.
85 /// If not set, falls back to global settings or hardcoded defaults.
86 #[serde(default)]
87 pub inference_defaults: Option<InferenceConfig>,
88}
89
90/// A model to be inserted into the system (no ID yet).
91///
92/// This represents a model that hasn't been persisted to the database.
93/// After insertion, the repository returns a `Model` with the assigned ID.
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct NewModel {
96 /// Human-readable name for the model.
97 pub name: String,
98 /// Absolute path to the GGUF file on the filesystem.
99 pub file_path: PathBuf,
100 /// Number of parameters in the model (in billions).
101 pub param_count_b: f64,
102 /// Model architecture (e.g., "llama", "mistral", "falcon").
103 pub architecture: Option<String>,
104 /// Quantization type (e.g., "`Q4_0`", "`Q8_0`", "`F16`", "`F32`").
105 pub quantization: Option<String>,
106 /// Maximum context length the model supports.
107 pub context_length: Option<u64>,
108 /// Additional metadata key-value pairs from the GGUF file.
109 pub metadata: HashMap<String, String>,
110 /// UTC timestamp of when the model was added to the database.
111 pub added_at: DateTime<Utc>,
112 /// `HuggingFace` repository ID (e.g., "`TheBloke/Llama-2-7B-GGUF`").
113 pub hf_repo_id: Option<String>,
114 /// Git commit SHA from `HuggingFace` Hub.
115 pub hf_commit_sha: Option<String>,
116 /// Original filename on `HuggingFace` Hub.
117 pub hf_filename: Option<String>,
118 /// Timestamp of when this model was downloaded from `HuggingFace`.
119 pub download_date: Option<DateTime<Utc>>,
120 /// Last time we checked for updates on `HuggingFace`.
121 pub last_update_check: Option<DateTime<Utc>>,
122 /// User-defined tags for organizing models.
123 pub tags: Vec<String>,
124 /// Ordered list of all file paths for sharded models (None for single-file models).
125 pub file_paths: Option<Vec<PathBuf>>,
126 /// Model capabilities inferred from chat template analysis.
127 #[serde(default)]
128 pub capabilities: ModelCapabilities,
129 /// Per-model inference parameter defaults.
130 ///
131 /// These are preferred over global settings when making inference requests.
132 /// If not set, falls back to global settings or hardcoded defaults.
133 #[serde(default)]
134 pub inference_defaults: Option<InferenceConfig>,
135}
136
137impl NewModel {
138 /// Create a new model with minimal required fields.
139 ///
140 /// Other fields are set to `None` or empty defaults.
141 #[must_use]
142 pub fn new(
143 name: String,
144 file_path: PathBuf,
145 param_count_b: f64,
146 added_at: DateTime<Utc>,
147 ) -> Self {
148 Self {
149 name,
150 file_path,
151 param_count_b,
152 architecture: None,
153 quantization: None,
154 context_length: None,
155 metadata: HashMap::new(),
156 added_at,
157 hf_repo_id: None,
158 hf_commit_sha: None,
159 hf_filename: None,
160 download_date: None,
161 last_update_check: None,
162 tags: Vec::new(),
163 file_paths: None,
164 capabilities: ModelCapabilities::default(),
165 inference_defaults: None,
166 }
167 }
168}
169
170impl Model {
171 /// Convert this model to a `NewModel` (drops the ID).
172 ///
173 /// Useful when you need to clone a model's data without the ID.
174 #[must_use]
175 pub fn to_new_model(&self) -> NewModel {
176 NewModel {
177 name: self.name.clone(),
178 file_path: self.file_path.clone(),
179 param_count_b: self.param_count_b,
180 architecture: self.architecture.clone(),
181 quantization: self.quantization.clone(),
182 context_length: self.context_length,
183 metadata: self.metadata.clone(),
184 added_at: self.added_at,
185 hf_repo_id: self.hf_repo_id.clone(),
186 hf_commit_sha: self.hf_commit_sha.clone(),
187 hf_filename: self.hf_filename.clone(),
188 download_date: self.download_date,
189 last_update_check: self.last_update_check,
190 tags: self.tags.clone(),
191 file_paths: None, // Not preserved in conversion
192 capabilities: self.capabilities,
193 inference_defaults: self.inference_defaults.clone(),
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use chrono::Utc;
202
203 #[test]
204 fn test_new_model_creation() {
205 let model = NewModel::new(
206 "Test Model".to_string(),
207 PathBuf::from("/path/to/model.gguf"),
208 7.0,
209 Utc::now(),
210 );
211
212 assert_eq!(model.name, "Test Model");
213 assert!((model.param_count_b - 7.0).abs() < f64::EPSILON);
214 assert!(model.architecture.is_none());
215 assert!(model.tags.is_empty());
216 }
217
218 #[test]
219 fn test_model_to_new_model() {
220 let model = Model {
221 id: 42,
222 name: "Persisted Model".to_string(),
223 file_path: PathBuf::from("/path/to/model.gguf"),
224 param_count_b: 13.0,
225 architecture: Some("llama".to_string()),
226 quantization: Some("Q4_0".to_string()),
227 context_length: Some(4096),
228 metadata: HashMap::new(),
229 added_at: Utc::now(),
230 hf_repo_id: Some("TheBloke/Model-GGUF".to_string()),
231 hf_commit_sha: None,
232 hf_filename: None,
233 download_date: None,
234 last_update_check: None,
235 tags: vec!["chat".to_string()],
236 capabilities: ModelCapabilities::default(),
237 inference_defaults: None,
238 };
239
240 let new_model = model.to_new_model();
241 assert_eq!(new_model.name, "Persisted Model");
242 assert_eq!(new_model.architecture, Some("llama".to_string()));
243 assert_eq!(new_model.tags, vec!["chat".to_string()]);
244 }
245}