use std::sync::Arc;
use offgrid::*;
fn env_model(var: &str) -> Option<Arc<Model>> {
let Ok(path) = std::env::var(var) else {
eprintln!("skip: {var} not set");
return None;
};
Some(Model::load(path, &ModelParams::default()).expect("model loads"))
}
fn greedy(messages: Vec<ChatMessage>, max_tokens: u32) -> ChatRequest {
ChatRequest {
max_tokens: Some(max_tokens),
sampling: SamplingParams { temperature: 0.0, ..Default::default() },
..ChatRequest::new(messages)
}
}
#[test]
fn lists_devices() {
let devs = devices();
println!("{devs:#?}");
assert!(!devs.is_empty());
}
#[test]
fn chat_streams_and_reuses_prefix() {
let Some(model) = env_model("OFFGRID_TEST_MODEL") else { return };
println!("{:#?}", model.info());
let mut chat = ChatSession::new(model, &SessionParams::default()).unwrap();
let mut msgs =
vec![ChatMessage::system("You are terse."), ChatMessage::user("What is the capital of France? One word.")];
let mut streamed = String::new();
let r1 = chat
.complete(&greedy(msgs.clone(), 16), |p| {
streamed.push_str(p);
true
})
.unwrap();
println!("r1: {r1:?}");
assert_eq!(streamed, r1.content);
assert!(r1.content.contains("Paris"));
assert_eq!(r1.finish_reason, FinishReason::Stop);
msgs.push(ChatMessage::assistant(r1.content.clone()));
msgs.push(ChatMessage::user("And of Germany?"));
let r2 = chat.complete(&greedy(msgs, 16), |_| true).unwrap();
println!("r2: {r2:?}");
assert!(r2.content.contains("Berlin"));
assert!(r2.usage.cached_tokens > 10, "second turn should reuse the KV prefix");
}
#[test]
fn chat_limits_stop_and_cancel() {
let Some(model) = env_model("OFFGRID_TEST_MODEL") else { return };
let mut chat = ChatSession::new(model, &SessionParams::default()).unwrap();
let count = || vec![ChatMessage::user("Count from 1 to 30, comma separated.")];
let r = chat.complete(&greedy(count(), 5), |_| true).unwrap();
assert_eq!(r.finish_reason, FinishReason::Length);
assert_eq!(r.usage.completion_tokens, 5);
let mut req = greedy(count(), 200);
req.stop = vec!["5".into()];
let r = chat.complete(&req, |_| true).unwrap();
assert_eq!(r.finish_reason, FinishReason::Stop);
assert!(!r.content.contains('5'), "{:?}", r.content);
let mut n = 0;
let r = chat
.complete(&greedy(count(), 200), |_| {
n += 1;
n < 3
})
.unwrap();
assert_eq!(r.finish_reason, FinishReason::Cancelled);
let token = chat.cancel_token();
token.cancel();
let r = chat.complete(&greedy(count(), 5), |_| true).unwrap();
assert_eq!(r.finish_reason, FinishReason::Length, "stale cancel leaked into the next request");
let next = token.next_request();
next.cancel();
let r = chat.complete(&greedy(count(), 200), |_| true).unwrap();
assert_eq!(r.finish_reason, FinishReason::Cancelled);
next.cancel();
let r = chat.complete(&greedy(count(), 5), |_| true).unwrap();
assert_eq!(r.finish_reason, FinishReason::Length);
let t = token.clone();
let canceller = std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(150));
t.cancel();
});
let r = chat.complete(&greedy(count(), 2000), |_| true).unwrap();
canceller.join().unwrap();
assert_eq!(r.finish_reason, FinishReason::Cancelled);
}
#[test]
fn chat_grammar() {
let Some(model) = env_model("OFFGRID_TEST_MODEL") else { return };
let mut chat = ChatSession::new(model, &SessionParams::default()).unwrap();
let mut req = greedy(vec![ChatMessage::user("Is the sky blue? Answer yes or no.")], 8);
req.sampling.grammar = Some(r#"root ::= "YES" | "NO""#.into());
let r = chat.complete(&req, |_| true).unwrap();
assert!(r.content == "YES" || r.content == "NO", "{:?}", r.content);
}
#[test]
fn embeddings_rank_similarity() {
let Some(model) = env_model("OFFGRID_TEST_EMBED_MODEL") else { return };
let mut emb = Embedder::new(model, &EmbedParams::default()).unwrap();
let v = emb
.embed(&["The cat sits on the mat.", "A kitten is resting on a rug.", "Quarterly tax filing deadlines"])
.unwrap();
assert_eq!(v.len(), 3);
assert_eq!(v[0].len(), emb.dim());
let dot = |a: &[f32], b: &[f32]| a.iter().zip(b).map(|(x, y)| x * y).sum::<f32>();
let (sim, diff) = (dot(&v[0], &v[1]), dot(&v[0], &v[2]));
println!("cat~kitten {sim:.3} cat~tax {diff:.3}");
assert!(sim > diff + 0.2);
let single = emb.embed_one("The cat sits on the mat.").unwrap();
assert!(dot(&single, &v[0]) > 0.999);
let long_doc = "The quick brown fox jumps over the lazy dog. ".repeat(150); let long = emb.embed(&[long_doc.as_str(), "short"]).expect("long input embeds");
assert_eq!(long[0].len(), emb.dim());
}
#[test]
fn hybrid_model_restores_checkpoints() {
let Some(model) = env_model("OFFGRID_TEST_HYBRID_MODEL") else { return };
let params = SessionParams { n_ctx: 16384, ..Default::default() };
let system: String = (0..700).map(|i| format!("Rule {i}: item {i} costs {} coins.\n", i * 7 % 101)).collect();
let ask = |q: &str| {
let mut req = greedy(vec![ChatMessage::system(system.clone()), ChatMessage::user(q)], 24);
req.chat_template_kwargs = Some(serde_json::from_str(r#"{"enable_thinking": false}"#).unwrap());
req
};
let mut chat = ChatSession::new(model.clone(), ¶ms).unwrap();
let first = chat.complete(&ask("What does item 12 cost? Answer with the number."), |_| true).unwrap();
assert_eq!(first.usage.cached_tokens, 0);
let second = chat.complete(&ask("Copy the rules for items 340 to 343, one per line."), |_| true).unwrap();
println!("first: {:?}\nsecond: {:?} ({:?})", first.content, second.content, second.usage);
assert!(second.usage.cached_tokens >= 8192, "restored a checkpoint: {:?}", second.usage);
let mut fresh = ChatSession::new(model, ¶ms).unwrap();
let again = fresh.complete(&ask("Copy the rules for items 340 to 343, one per line."), |_| true).unwrap();
assert_eq!(second.content, again.content, "a restored checkpoint answers like a fresh session");
}