1use crate::{ArtifactFormat, ModelResourceProfile};
4use serde::{Deserialize, Serialize};
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum InspectionReadiness {
11 Ready,
13 Missing,
15 Unsupported,
17 Invalid,
19 RequestDependent,
21 Unverified,
23 NotApplicable,
25}
26
27#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum InspectionSeverity {
31 Error,
33 Warning,
35 Info,
37}
38
39#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42#[non_exhaustive]
43pub enum InspectionIssueCode {
44 InvalidContainer,
46 InvalidConfiguration,
48 UnsupportedArchitecture,
50 MissingCheckpointShard,
52 UnsupportedTensorEncoding,
54 MissingRequiredTensor,
56 ConflictingTensorLayout,
58 TensorShapeMismatch,
60 QuantizationCompanionMismatch,
62 InvalidLayerOrExpertCount,
64 MissingTokenizer,
66 MissingChatTemplate,
68 MissingMediaProjector,
70 MissingProcessor,
72 UnsupportedQuantizationRequest,
74 UnsupportedResidencyPolicy,
76 UnsupportedParallelTopology,
78 UnsupportedSemanticProtocol,
80 UnsupportedToolProtocol,
82 MissingEosMetadata,
84 ValidationUnavailableUntilLoad,
86 RequestSpecificValidation,
88 Io,
90}
91
92#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
94pub struct InspectionIssue {
95 pub code: InspectionIssueCode,
97 pub severity: InspectionSeverity,
99 pub detail: String,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub path: Option<PathBuf>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub metadata_key: Option<String>,
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub tensor_name: Option<String>,
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub tensor_type_code: Option<u32>,
113}
114
115#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
117pub struct ArtifactTensorEncoding {
118 pub name: String,
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub ggml_type_code: Option<u32>,
123}
124
125#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
127#[serde(rename_all = "snake_case")]
128pub enum ArtifactModality {
129 Text,
131 Image,
133 Video,
135 Audio,
137}
138
139#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
141pub struct InspectionRequirement {
142 pub code: InspectionIssueCode,
144 pub readiness: InspectionReadiness,
146 pub detail: String,
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub path: Option<PathBuf>,
151}
152
153#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
155pub struct ModelInspectionReport {
156 pub path: PathBuf,
158 pub artifact_format: ArtifactFormat,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub model_family: Option<String>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub architecture: Option<String>,
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub gguf_versions: Option<Vec<u32>>,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub checkpoint_shards: Option<usize>,
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub tensor_count: Option<usize>,
175 pub resources: ModelResourceProfile,
177 pub tensor_encodings: Vec<ArtifactTensorEncoding>,
179 pub expected_modalities: Vec<ArtifactModality>,
181 pub container: InspectionReadiness,
183 pub architecture_support: InspectionReadiness,
185 pub structural_binding: InspectionReadiness,
187 pub model_loadability: InspectionReadiness,
189 pub requested_load: InspectionReadiness,
191 pub text_generation: InspectionReadiness,
193 pub tokenizer: InspectionReadiness,
195 pub chat_template: InspectionReadiness,
197 pub semantic_streaming: InspectionReadiness,
199 pub native_tools: InspectionReadiness,
201 pub multimodal: InspectionReadiness,
203 pub requirements: Vec<InspectionRequirement>,
205 pub issues: Vec<InspectionIssue>,
207}
208
209impl ModelInspectionReport {
210 pub fn unverified(path: &Path, artifact_format: ArtifactFormat) -> Self {
212 Self {
213 path: path.to_path_buf(),
214 artifact_format,
215 model_family: None,
216 architecture: None,
217 gguf_versions: None,
218 checkpoint_shards: None,
219 tensor_count: None,
220 resources: ModelResourceProfile::unmeasured(path.to_path_buf(), artifact_format),
221 tensor_encodings: Vec::new(),
222 expected_modalities: Vec::new(),
223 container: InspectionReadiness::Unverified,
224 architecture_support: InspectionReadiness::Unverified,
225 structural_binding: InspectionReadiness::Unverified,
226 model_loadability: InspectionReadiness::Unverified,
227 requested_load: InspectionReadiness::Unverified,
228 text_generation: InspectionReadiness::Unverified,
229 tokenizer: InspectionReadiness::Unverified,
230 chat_template: InspectionReadiness::Unverified,
231 semantic_streaming: InspectionReadiness::Unverified,
232 native_tools: InspectionReadiness::Unverified,
233 multimodal: InspectionReadiness::Unverified,
234 requirements: Vec::new(),
235 issues: Vec::new(),
236 }
237 }
238
239 pub fn is_loadable(&self) -> bool {
241 self.container == InspectionReadiness::Ready
242 && self.architecture_support == InspectionReadiness::Ready
243 && self.structural_binding == InspectionReadiness::Ready
244 && self.model_loadability == InspectionReadiness::Ready
245 && self.requested_load == InspectionReadiness::Ready
246 && !self
247 .issues
248 .iter()
249 .any(|issue| issue.code == InspectionIssueCode::ValidationUnavailableUntilLoad)
250 }
251
252 pub fn issue(
254 &mut self,
255 code: InspectionIssueCode,
256 severity: InspectionSeverity,
257 detail: impl Into<String>,
258 path: Option<PathBuf>,
259 ) {
260 self.issues.push(InspectionIssue {
261 code,
262 severity,
263 detail: detail.into(),
264 path,
265 metadata_key: None,
266 tensor_name: None,
267 tensor_type_code: None,
268 });
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn report_schema_round_trips_without_a_backend() {
278 let report =
279 ModelInspectionReport::unverified(Path::new("model.gguf"), ArtifactFormat::Gguf);
280 let json = serde_json::to_string(&report).unwrap();
281 let decoded: ModelInspectionReport = serde_json::from_str(&json).unwrap();
282 assert_eq!(decoded, report);
283 }
284}