Skip to main content

neurondump_core/
model.rs

1//! An opened model: weights GGUF plus Ollama side layers.
2
3use std::sync::Arc;
4
5use anyhow::Result;
6use serde::Serialize;
7
8use neurondump_gguf::GgufFile;
9
10use crate::discovery::ModelEntry;
11
12#[derive(Debug, Clone, Default, Serialize)]
13pub struct OllamaExtras {
14    pub template: Option<String>,
15    pub system: Option<String>,
16    pub params: Option<serde_json::Value>,
17    pub licenses: Vec<String>,
18    pub messages: Option<serde_json::Value>,
19    pub adapters: Vec<String>,
20}
21
22pub struct ModelHandle {
23    pub entry: ModelEntry,
24    pub gguf: Arc<GgufFile>,
25    pub ollama: OllamaExtras,
26}
27
28#[cfg(feature = "native")]
29fn read_small(path: &std::path::Path, limit: u64) -> Option<String> {
30    let md = std::fs::metadata(path).ok()?;
31    if md.len() > limit {
32        return None;
33    }
34    let bytes = std::fs::read(path).ok()?;
35    Some(String::from_utf8_lossy(&bytes).into_owned())
36}
37
38impl ModelHandle {
39    /// A handle over an already parsed file (e.g. read lazily in the browser).
40    pub fn from_gguf(entry: ModelEntry, gguf: GgufFile) -> Self {
41        Self { entry, gguf: Arc::new(gguf), ollama: OllamaExtras::default() }
42    }
43
44    #[cfg(feature = "native")]
45    pub fn open(entry: ModelEntry) -> Result<Self> {
46        use anyhow::Context;
47        let gguf = GgufFile::open(&entry.path).with_context(|| format!("parsing {}", entry.path.display()))?;
48        let mut ollama = OllamaExtras::default();
49        if let Some(info) = &entry.ollama {
50            for l in &info.layers {
51                let Some(p) = &l.path else { continue };
52                match l.kind.as_str() {
53                    "template" => ollama.template = read_small(p, 1 << 20),
54                    "system" => ollama.system = read_small(p, 1 << 20),
55                    "params" => ollama.params = read_small(p, 1 << 20).and_then(|s| serde_json::from_str(&s).ok()),
56                    "messages" => ollama.messages = read_small(p, 4 << 20).and_then(|s| serde_json::from_str(&s).ok()),
57                    "license" => ollama.licenses.extend(read_small(p, 4 << 20)),
58                    "adapter" => ollama.adapters.push(p.to_string_lossy().to_string()),
59                    _ => {}
60                }
61            }
62        }
63        Ok(Self { entry, gguf: Arc::new(gguf), ollama })
64    }
65
66    pub fn architecture(&self) -> &str {
67        self.gguf.architecture().unwrap_or("unknown")
68    }
69
70    pub fn open_projector(&self) -> Option<Result<GgufFile>> {
71        #[cfg(feature = "native")]
72        return self.entry.projector.as_ref().map(|p| GgufFile::open(p).map_err(Into::into));
73        #[cfg(not(feature = "native"))]
74        None
75    }
76}