use std::path::PathBuf;
use std::sync::Arc;
use nedb_cast_slm::Cast as CastModel;
#[derive(Clone)]
pub struct Caster {
inner: Arc<CastModel>,
source: String,
}
impl Caster {
pub fn load(data_dir: &std::path::Path) -> Result<Self, String> {
for cand in Self::candidates(data_dir) {
if !cand.exists() {
continue;
}
return match CastModel::load(&cand) {
Ok(m) => {
if !m.checksum_verified {
eprintln!(
" cast: WARNING {} has no checksum; bytes unverified",
cand.display()
);
}
Ok(Caster {
inner: Arc::new(m),
source: cand.display().to_string(),
})
}
Err(e) => Err(format!("failed to load {}: {}", cand.display(), e)),
};
}
Err(format!(
"no model.cast found. Looked in:\n {}\n\
Download it from \
https://github.com/aiassistsecure/nedb-cast-slm/releases \
or set NEDBD_CAST_MODEL to its path.",
Self::candidates(data_dir)
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join("\n ")
))
}
fn candidates(data_dir: &std::path::Path) -> Vec<PathBuf> {
let mut v = Vec::new();
if let Ok(p) = std::env::var("NEDBD_CAST_MODEL") {
v.push(PathBuf::from(p));
}
v.push(data_dir.join("model.cast"));
if let Ok(h) = std::env::var("CAST_HOME") {
v.push(PathBuf::from(&h).join("model.cast"));
v.push(PathBuf::from(&h).join("v10.30.90").join("model.cast"));
}
if let Ok(home) = std::env::var("HOME") {
let base = PathBuf::from(home).join(".cache").join("nedb-cast-slm");
v.push(base.join("v10.30.90").join("model.cast"));
v.push(base.join("model.cast"));
}
v
}
pub fn source(&self) -> &str {
&self.source
}
pub fn n_params(&self) -> usize {
self.inner.n_params()
}
pub fn vocab_size(&self) -> usize {
self.inner.vocab_size()
}
pub fn cast(&self, prompt: &str) -> String {
self.inner.cast(prompt)
}
pub fn cast_checked(&self, prompt: &str, collections: &[String]) -> CastResult {
let nql = self.cast(prompt);
let coll = extract_collection(&nql);
let known = match &coll {
Some(c) => collections.iter().any(|k| k == c),
None => false,
};
CastResult { nql, collection: coll, collection_known: known }
}
}
#[derive(Debug, Clone)]
pub struct CastResult {
pub nql: String,
pub collection: Option<String>,
pub collection_known: bool,
}
fn extract_collection(nql: &str) -> Option<String> {
let mut it = nql.split_whitespace();
while let Some(tok) = it.next() {
if tok.eq_ignore_ascii_case("FROM") {
return it.next().map(|s| s.trim_matches('"').to_string());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extracts_collection_after_from() {
assert_eq!(
extract_collection("FROM orders WHERE total > 99"),
Some("orders".to_string())
);
assert_eq!(
extract_collection("from stylists limit 5"),
Some("stylists".to_string())
);
assert_eq!(extract_collection("WHERE total > 99"), None);
assert_eq!(extract_collection(""), None);
}
#[test]
fn strips_quotes_from_collection() {
assert_eq!(
extract_collection("FROM \"my coll\" LIMIT 1"),
Some("my".to_string()),
"quoted multiword collections are not a thing in NQL; \
first token is the right answer"
);
}
}