use oxillama_arch::lora::LoadedLora;
use crate::engine::InferenceEngine;
use crate::error::{RuntimeError, RuntimeResult};
pub fn apply_lora(engine: &mut InferenceEngine, lora_path: &str) -> RuntimeResult<()> {
let lora = LoadedLora::load(lora_path).map_err(|e| RuntimeError::ModelLoadError {
message: format!("LoRA load failed: {e}"),
})?;
let rank = lora.rank;
let alpha = lora.alpha;
let n_adapters = lora.num_adapters();
engine.apply_lora_adapters(&lora)?;
tracing::info!(
path = %lora_path,
rank = rank,
alpha = alpha,
adapters = n_adapters,
"LoRA adapter applied"
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{EngineConfig, InferenceEngine};
#[test]
fn test_apply_lora_missing_file() {
let mut engine = InferenceEngine::new(EngineConfig::default());
let path = std::env::temp_dir().join("nonexistent_adapter_xyz.gguf");
let path_str = path.to_string_lossy();
let result = apply_lora(&mut engine, &path_str);
assert!(
result.is_err(),
"apply_lora with missing file should return Err"
);
assert!(
matches!(result, Err(RuntimeError::ModelLoadError { .. })),
"expected ModelLoadError for missing adapter file, got {:?}",
result
);
}
#[test]
fn test_apply_lora_garbage_bytes_errors() {
let mut tmp = std::env::temp_dir();
tmp.push("oxillama_lora_bad_magic_test.gguf");
std::fs::write(
&tmp,
b"GARBAGE BYTES - DEFINITELY NOT A GGUF FILE 9876543210",
)
.expect("write temp file");
let path = tmp
.to_str()
.expect("temp path must be valid UTF-8")
.to_string();
let mut engine = InferenceEngine::new(EngineConfig::default());
let result = apply_lora(&mut engine, &path);
let _ = std::fs::remove_file(&tmp);
assert!(
result.is_err(),
"apply_lora with garbage GGUF content should return Err"
);
assert!(
matches!(result, Err(RuntimeError::ModelLoadError { .. })),
"expected ModelLoadError for invalid GGUF content, got {:?}",
result
);
}
#[test]
fn test_apply_lora_empty_file_errors() {
let mut tmp = std::env::temp_dir();
tmp.push("oxillama_lora_empty_test.gguf");
std::fs::write(&tmp, b"").expect("write empty temp file");
let path = tmp
.to_str()
.expect("temp path must be valid UTF-8")
.to_string();
let mut engine = InferenceEngine::new(EngineConfig::default());
let result = apply_lora(&mut engine, &path);
let _ = std::fs::remove_file(&tmp);
assert!(
result.is_err(),
"apply_lora with empty file should return Err"
);
assert!(
matches!(result, Err(RuntimeError::ModelLoadError { .. })),
"expected ModelLoadError for empty file, got {:?}",
result
);
}
}