pub use aria_ffi::{
aria_complete, aria_complete_stream, aria_embed, aria_last_error, aria_model_destroy,
aria_model_init, aria_transcribe, AriaModelHandle,
};
pub use aria_inference::{EngineError, GenerateOpts, Generation, Session, SessionBuilder};
mod download;
mod setup;
pub use download::{download_model, download_model_setup, ensure_ffi_lib, DownloadError};
pub use setup::{
apply_setup, fill_setup_urls, SetupConfig, SetupError, SetupUpdates, CN_SITE, CN_UPGRADE,
};
#[derive(Default, Clone)]
pub struct OpenOptions {
pub token: Option<String>,
pub hf_token: Option<String>,
pub modelscope_api_token: Option<String>,
pub site: Option<String>,
}
pub struct Engine {
session: Option<Session>,
cfg: SetupConfig,
generic_token: Option<String>,
}
impl Engine {
pub fn new() -> Self {
Self {
session: None,
cfg: SetupConfig::default(),
generic_token: None,
}
}
pub fn setup(&mut self, updates: &SetupUpdates) -> Result<&mut Self, SetupError> {
self.cfg = apply_setup(&self.cfg, updates)?;
Ok(self)
}
pub fn setup_status(&self) -> &SetupConfig {
&self.cfg
}
pub fn setup_clear(&mut self) -> &mut Self {
self.cfg = SetupConfig::default();
self
}
pub fn from_bundle(bundle_path: impl AsRef<std::path::Path>) -> Result<Self, EngineError> {
let session = SessionBuilder::new().model(bundle_path).build()?;
Ok(Self {
session: Some(session),
cfg: SetupConfig::default(),
generic_token: None,
})
}
pub fn open(bundle_path: impl AsRef<std::path::Path>) -> Result<Self, EngineError> {
Self::from_bundle(bundle_path)
}
fn load_ref(&mut self, model_ref: &str, opts: &OpenOptions) -> Result<(), OpenError> {
let _ = ensure_ffi_lib(opts.site.as_deref())?;
let path = if model_ref.contains('/')
|| model_ref.contains('\\')
|| std::path::Path::new(model_ref).exists()
{
std::path::PathBuf::from(model_ref)
} else {
download_model_setup(
model_ref,
opts.token.as_deref().unwrap_or(""),
opts.site.as_deref(),
opts.hf_token.as_deref(),
opts.modelscope_api_token.as_deref(),
)
.map_err(OpenError::Download)?
};
let session = SessionBuilder::new()
.model(&path)
.build()
.map_err(OpenError::Engine)?;
self.session = Some(session);
Ok(())
}
pub fn open_named(&mut self, model_ref: &str) -> Result<&mut Self, OpenError> {
let opts = OpenOptions {
token: self.generic_token.clone(),
hf_token: if self.cfg.hf_token.is_empty() {
None
} else {
Some(self.cfg.hf_token.clone())
},
modelscope_api_token: if self.cfg.modelscope_api_token.is_empty() {
None
} else {
Some(self.cfg.modelscope_api_token.clone())
},
site: if self.cfg.site_url.is_empty() {
None
} else {
Some(self.cfg.site_url.clone())
},
};
self.load_ref(model_ref, &opts)?;
Ok(self)
}
pub fn open_model(model_ref: &str, opts: &OpenOptions) -> Result<Self, OpenError> {
let mut eng = Self::new();
let updates = SetupUpdates {
site_url: opts.site.clone(),
hf_token: opts.hf_token.clone(),
modelscope_api_token: opts.modelscope_api_token.clone(),
..Default::default()
};
eng.generic_token = opts.token.clone();
let _ = eng.setup(&updates);
eng.load_ref(model_ref, opts)?;
Ok(eng)
}
fn session_mut(&mut self) -> Result<&mut Session, EngineError> {
self.session
.as_mut()
.ok_or_else(|| EngineError::InvalidParam("engine not opened".into()))
}
fn session_ref(&self) -> Result<&Session, EngineError> {
self.session
.as_ref()
.ok_or_else(|| EngineError::InvalidParam("engine not opened".into()))
}
pub fn complete(&mut self, prompt: &str, opts: &GenerateOpts) -> Result<Generation, EngineError> {
let turns = [aria_inference::ChatTurn::new("user", prompt)];
let session = self.session_mut()?;
let tokens = session.encode_chat(&turns);
session.generate(&tokens, opts)
}
pub fn embed(&self, text: &str) -> Result<Vec<f32>, EngineError> {
self.session_ref()?.embed_text(text)
}
pub fn transcribe(&self, pcm: &[u8]) -> Result<String, EngineError> {
self.session_ref()?.transcribe_pcm16le(pcm)
}
}
impl Default for Engine {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, thiserror::Error)]
pub enum OpenError {
#[error("download failed: {0}")]
Download(#[from] DownloadError),
#[error("engine load failed: {0}")]
Engine(#[from] EngineError),
}
#[cfg(test)]
mod tests {
use super::*;
use aria_inference::fixture::write_tiny_q4_bundle;
#[test]
fn engine_complete_ok() {
let dir = tempfile::tempdir().unwrap();
write_tiny_q4_bundle(dir.path()).unwrap();
let mut eng = Engine::open(dir.path()).unwrap();
let g = eng
.complete("hi", &GenerateOpts { max_tokens: 2, temperature: 0.0 })
.unwrap();
assert!(!g.text.is_empty());
assert!(!eng.embed("x").unwrap().is_empty());
assert!(!eng.transcribe(&[0, 1, 2, 3]).unwrap().is_empty());
}
#[test]
fn open_model_local_path_no_token() {
let _guard = crate::download::ENV_LOCK.lock().unwrap();
let home = tempfile::tempdir().unwrap();
std::env::set_var("ARIA_COMPUTE_HOME", home.path());
let libdir = home.path().join("lib");
std::fs::create_dir_all(&libdir).unwrap();
let name = if cfg!(windows) {
"aria-engine_ffi.dll"
} else if cfg!(target_os = "macos") {
"libaria-engine_ffi.dylib"
} else {
"libaria-engine_ffi.so"
};
std::fs::write(libdir.join(name), b"x").unwrap();
let dir = tempfile::tempdir().unwrap();
write_tiny_q4_bundle(dir.path()).unwrap();
let mut eng = Engine::open_model(dir.path().to_str().unwrap(), &OpenOptions::default()).unwrap();
let g = eng
.complete("hi", &GenerateOpts { max_tokens: 2, temperature: 0.0 })
.unwrap();
assert!(!g.text.is_empty());
std::env::remove_var("ARIA_COMPUTE_HOME");
}
#[test]
fn setup_instance_all_fields() {
let mut eng = Engine::new();
eng.setup(&SetupUpdates {
router: Some("http://127.0.0.1:8080".into()),
router_api_key: Some("sk-aria_test".into()),
site_url: Some(crate::setup::CN_SITE.into()),
upgrade_url: Some(crate::setup::CN_UPGRADE.into()),
compute: Some("cpu".into()),
hf_token: Some("hf_abc".into()),
modelscope_api_token: Some("ms_xyz".into()),
})
.unwrap();
let st = eng.setup_status();
assert_eq!(st.router, "http://127.0.0.1:8080");
assert_eq!(st.router_api_key, "sk-aria_test");
assert_eq!(st.compute, "cpu");
assert_eq!(st.hf_token, "hf_abc");
assert_eq!(st.modelscope_api_token, "ms_xyz");
assert_eq!(st.site_url, crate::setup::CN_SITE);
}
#[test]
fn setup_accepts_bfvk_router_api_key() {
let mut eng = Engine::new();
eng.setup(&SetupUpdates {
router_api_key: Some("bfvk-cloud".into()),
..Default::default()
})
.unwrap();
assert_eq!(eng.setup_status().router_api_key, "bfvk-cloud");
assert!(eng
.setup(&SetupUpdates {
router_api_key: Some("bad-key".into()),
..Default::default()
})
.is_err());
}
#[test]
fn setup_partial_merge() {
let mut eng = Engine::new();
eng.setup(&SetupUpdates {
hf_token: Some("hf_one".into()),
router: Some("http://127.0.0.1:1".into()),
..Default::default()
})
.unwrap();
eng.setup(&SetupUpdates {
compute: Some("cuda".into()),
..Default::default()
})
.unwrap();
let st = eng.setup_status();
assert_eq!(st.hf_token, "hf_one");
assert_eq!(st.router, "http://127.0.0.1:1");
assert_eq!(st.compute, "cuda");
}
#[test]
fn setup_invalid_enum_leaves_state() {
let mut eng = Engine::new();
eng.setup(&SetupUpdates {
compute: Some("cpu".into()),
..Default::default()
})
.unwrap();
assert!(eng
.setup(&SetupUpdates {
compute: Some("gpu".into()),
..Default::default()
})
.is_err());
assert_eq!(eng.setup_status().compute, "cpu");
}
#[test]
fn setup_clear_resets_instance() {
let mut eng = Engine::new();
eng.setup(&SetupUpdates {
hf_token: Some("hf_x".into()),
compute: Some("cpu".into()),
..Default::default()
})
.unwrap();
eng.setup_clear();
let st = eng.setup_status();
assert_eq!(st.hf_token, "");
assert_eq!(st.compute, "auto");
}
#[test]
fn setup_fills_urls_from_site_tld() {
let mut eng = Engine::new();
eng.setup(&SetupUpdates {
site_url: Some("https://ariacompute.cn".into()),
..Default::default()
})
.unwrap();
let st = eng.setup_status();
assert_eq!(st.upgrade_url, crate::setup::CN_UPGRADE);
}
#[test]
fn setup_does_not_write_engine_yml() {
let _guard = crate::download::ENV_LOCK.lock().unwrap();
let home = tempfile::tempdir().unwrap();
std::env::set_var("ARIA_COMPUTE_HOME", home.path());
let mut eng = Engine::new();
eng.setup(&SetupUpdates {
router: Some("http://127.0.0.1:8080".into()),
site_url: Some("https://ariacompute.com".into()),
hf_token: Some("hf_x".into()),
..Default::default()
})
.unwrap();
assert!(!home.path().join("engine.yml").is_file());
assert!(!home.path().join("config.yml").is_file());
std::env::remove_var("ARIA_COMPUTE_HOME");
}
}