foundry_local_sdk/detail/
model.rs1use std::fmt;
9use std::path::PathBuf;
10use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed};
11use std::sync::Arc;
12
13use super::api::Api;
14use super::info::build_model_info;
15use super::native::NativeModel;
16use super::task::spawn_blocking;
17use crate::error::{FoundryLocalError, Result};
18use crate::types::ModelInfo;
19
20#[derive(Clone)]
22pub(crate) struct VariantData {
23 native: NativeModel,
24 info: ModelInfo,
25}
26
27pub struct Model {
34 inner: ModelKind,
35}
36
37type DownloadProgressCallback = Box<dyn FnMut(f64) + Send + 'static>;
38
39pub struct DownloadBuilder<'a> {
43 model: &'a Model,
44 progress: Option<DownloadProgressCallback>,
45 cancel_flag: Option<Arc<AtomicBool>>,
46}
47
48impl<'a> DownloadBuilder<'a> {
49 fn new(model: &'a Model) -> Self {
50 Self {
51 model,
52 progress: None,
53 cancel_flag: None,
54 }
55 }
56
57 pub fn progress<F>(mut self, callback: F) -> Self
59 where
60 F: FnMut(f64) + Send + 'static,
61 {
62 self.progress = Some(Box::new(callback));
63 self
64 }
65
66 pub fn cancel(mut self, cancel_flag: Arc<AtomicBool>) -> Self {
68 self.cancel_flag = Some(cancel_flag);
69 self
70 }
71
72 pub async fn run(self) -> Result<()> {
74 let native = self.model.selected_variant().native.clone();
75 let progress = self.progress;
76 let cancel_flag = self.cancel_flag;
77 spawn_blocking(move || native.download(progress, cancel_flag)).await
78 }
79}
80enum ModelKind {
81 Variant(Arc<VariantData>),
83 Group {
85 alias: String,
86 variants: Vec<Arc<VariantData>>,
87 selected: AtomicUsize,
88 },
89}
90
91impl Clone for Model {
92 fn clone(&self) -> Self {
93 Self {
94 inner: match &self.inner {
95 ModelKind::Variant(v) => ModelKind::Variant(v.clone()),
96 ModelKind::Group {
97 alias,
98 variants,
99 selected,
100 } => ModelKind::Group {
101 alias: alias.clone(),
102 variants: variants.clone(),
103 selected: AtomicUsize::new(selected.load(Relaxed)),
104 },
105 },
106 }
107 }
108}
109
110impl fmt::Debug for Model {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match &self.inner {
113 ModelKind::Variant(v) => f
114 .debug_struct("Model::ModelVariant")
115 .field("id", &v.info.id)
116 .field("alias", &v.info.alias)
117 .finish(),
118 ModelKind::Group {
119 alias,
120 variants,
121 selected,
122 } => f
123 .debug_struct("Model::Model")
124 .field("alias", alias)
125 .field("id", &variants[selected.load(Relaxed)].info.id)
126 .field("variants_count", &variants.len())
127 .field("selected_index", &selected.load(Relaxed))
128 .finish(),
129 }
130 }
131}
132
133impl Model {
136 pub(crate) fn from_variant(api: &Arc<Api>, native: NativeModel) -> Result<Self> {
138 let info = build_model_info(api, &native)?;
139 Ok(Self {
140 inner: ModelKind::Variant(Arc::new(VariantData { native, info })),
141 })
142 }
143
144 pub(crate) fn from_group(api: &Arc<Api>, native: NativeModel) -> Result<Self> {
146 let group_info = build_model_info(api, &native)?;
147 let alias = group_info.alias.clone();
148
149 let mut variants = Vec::new();
150 for variant_native in native.get_variants()? {
151 let info = build_model_info(api, &variant_native)?;
152 variants.push(Arc::new(VariantData {
153 native: variant_native,
154 info,
155 }));
156 }
157
158 if variants.is_empty() {
160 return Ok(Self {
161 inner: ModelKind::Variant(Arc::new(VariantData {
162 native,
163 info: group_info,
164 })),
165 });
166 }
167
168 let selected = variants.iter().position(|v| v.info.cached).unwrap_or(0);
170
171 Ok(Self {
172 inner: ModelKind::Group {
173 alias,
174 variants,
175 selected: AtomicUsize::new(selected),
176 },
177 })
178 }
179}
180
181impl Model {
184 fn selected_variant(&self) -> &VariantData {
185 match &self.inner {
186 ModelKind::Variant(v) => v.as_ref(),
187 ModelKind::Group {
188 variants, selected, ..
189 } => variants[selected.load(Relaxed)].as_ref(),
190 }
191 }
192
193 pub(crate) fn selected_native(&self) -> &NativeModel {
194 &self.selected_variant().native
195 }
196}
197
198impl Model {
201 pub fn id(&self) -> &str {
203 &self.selected_variant().info.id
204 }
205
206 pub fn alias(&self) -> &str {
208 match &self.inner {
209 ModelKind::Variant(v) => &v.info.alias,
210 ModelKind::Group { alias, .. } => alias,
211 }
212 }
213
214 pub fn info(&self) -> Result<ModelInfo> {
220 let variant = self.selected_variant();
221 build_model_info(&variant.native.api, &variant.native)
222 }
223
224 pub fn context_length(&self) -> Option<u64> {
226 self.selected_variant().info.context_length
227 }
228
229 pub fn input_modalities(&self) -> Option<&str> {
231 self.selected_variant().info.input_modalities.as_deref()
232 }
233
234 pub fn output_modalities(&self) -> Option<&str> {
236 self.selected_variant().info.output_modalities.as_deref()
237 }
238
239 pub fn capabilities(&self) -> Option<&str> {
241 self.selected_variant().info.capabilities.as_deref()
242 }
243
244 pub fn supports_tool_calling(&self) -> Option<bool> {
246 self.selected_variant().info.supports_tool_calling
247 }
248
249 pub async fn is_cached(&self) -> Result<bool> {
251 let native = self.selected_native().clone();
252 spawn_blocking(move || native.is_cached()).await
253 }
254
255 pub async fn is_loaded(&self) -> Result<bool> {
257 let native = self.selected_native().clone();
258 spawn_blocking(move || native.is_loaded()).await
259 }
260
261 pub async fn download<F>(&self, progress: Option<F>) -> Result<()>
264 where
265 F: FnMut(f64) + Send + 'static,
266 {
267 let native = self.selected_native().clone();
268 let progress: Option<DownloadProgressCallback> =
269 progress.map(|f| Box::new(f) as DownloadProgressCallback);
270 spawn_blocking(move || native.download(progress, None)).await
271 }
272
273 pub fn download_builder(&self) -> DownloadBuilder<'_> {
278 DownloadBuilder::new(self)
279 }
280
281 pub async fn path(&self) -> Result<PathBuf> {
283 let native = self.selected_native().clone();
284 let id = self.id().to_owned();
285 let path = spawn_blocking(move || native.path()).await?;
286 match path {
287 Some(p) => Ok(PathBuf::from(p)),
288 None => Err(FoundryLocalError::ModelOperation {
289 reason: format!("Error getting path for model {id}. Has it been downloaded?"),
290 }),
291 }
292 }
293
294 pub async fn load(&self) -> Result<()> {
296 let native = self.selected_native().clone();
297 spawn_blocking(move || native.load()).await
298 }
299
300 pub async fn unload(&self) -> Result<()> {
302 let native = self.selected_native().clone();
303 spawn_blocking(move || native.unload()).await
304 }
305
306 pub async fn remove_from_cache(&self) -> Result<()> {
308 let native = self.selected_native().clone();
309 spawn_blocking(move || native.remove_from_cache()).await
310 }
311
312 #[deprecated(
314 since = "2.0.0",
315 note = "The OpenAI direct clients are deprecated; use `ChatSession::new(&model)` instead."
316 )]
317 #[allow(deprecated)]
318 pub fn create_chat_client(&self) -> crate::openai::ChatClient {
319 let v = self.selected_variant();
320 crate::openai::ChatClient::new(&v.info.id, v.native.clone())
321 }
322
323 #[deprecated(
325 since = "2.0.0",
326 note = "The OpenAI direct clients are deprecated; use `AudioSession::new(&model)` instead."
327 )]
328 #[allow(deprecated)]
329 pub fn create_audio_client(&self) -> crate::openai::AudioClient {
330 let v = self.selected_variant();
331 crate::openai::AudioClient::new(&v.info.id, v.native.clone())
332 }
333
334 #[deprecated(
336 since = "2.0.0",
337 note = "The OpenAI direct clients are deprecated; use `EmbeddingsSession::new(&model)` \
338 instead."
339 )]
340 #[allow(deprecated)]
341 pub fn create_embedding_client(&self) -> crate::openai::EmbeddingClient {
342 let v = self.selected_variant();
343 crate::openai::EmbeddingClient::new(&v.info.id, v.native.clone())
344 }
345
346 pub fn variants(&self) -> Vec<Arc<Model>> {
352 match &self.inner {
353 ModelKind::Variant(v) => {
354 vec![Arc::new(Model {
355 inner: ModelKind::Variant(v.clone()),
356 })]
357 }
358 ModelKind::Group { variants, .. } => variants
359 .iter()
360 .map(|v| {
361 Arc::new(Model {
362 inner: ModelKind::Variant(v.clone()),
363 })
364 })
365 .collect(),
366 }
367 }
368
369 pub fn select_variant(&self, variant: &Model) -> Result<()> {
380 self.select_variant_by_id(variant.id())
381 }
382
383 pub fn select_variant_by_id(&self, id: &str) -> Result<()> {
396 match &self.inner {
397 ModelKind::Variant(v) => Err(FoundryLocalError::ModelOperation {
398 reason: format!(
399 "Selecting a variant is not supported on a single-variant model. \
400 Call Catalog::get_model(\"{}\") to get a model with all variants available.",
401 v.info.alias
402 ),
403 }),
404 ModelKind::Group {
405 variants,
406 selected,
407 alias,
408 } => match variants.iter().position(|v| v.info.id == id) {
409 Some(pos) => {
410 selected.store(pos, Relaxed);
411 Ok(())
412 }
413 None => {
414 let available: Vec<&str> =
415 variants.iter().map(|v| v.info.id.as_str()).collect();
416 Err(FoundryLocalError::ModelOperation {
417 reason: format!(
418 "Variant '{id}' not found for model '{alias}'. Available: {available:?}",
419 ),
420 })
421 }
422 },
423 }
424 }
425}