use jev_sdk::{Choice, Error, Noul, NoulCriteria, Question, Score, TypeSafeClient};
fn client() -> Option<TypeSafeClient> {
TypeSafeClient::from_env().ok()
}
#[tokio::test]
async fn evaluates_mixed_questions() {
let Some(client) = client() else {
eprintln!("TYPESAFE_API_KEY not set; skipping live test");
return;
};
let ticket = "Hi, I've been trying to connect my Stripe account for 3 days \
and it keeps failing. I'm losing sales. Please help ASAP.";
let response = client
.system_one(
ticket,
[
(
"department",
Question::from(Choice::new(
"Which team should handle this",
[
("billing", Some("Payment or subscription issues")),
("technical", Some("Bugs or integration problems")),
("sales", Some("Pricing or account questions")),
],
)),
),
(
"frustration",
Question::from(Score::new(
"How frustrated the customer appears",
[
"Calm, just stating facts",
"Frustrated but civil",
"Very angry",
],
)),
),
(
"is_urgent",
Question::from(
Noul::new("The message conveys urgency or time-sensitivity").criteria(
NoulCriteria::new("Explicitly time-sensitive", "No urgency expressed"),
),
),
),
],
)
.await
.expect("system_one call failed");
assert!(!response.model.is_empty());
assert!(response.usage.input_tokens > 0);
let department = response
.choice("department")
.expect("missing choice answer");
assert!(
["billing", "technical", "sales"].contains(&department.choice.as_str()),
"unexpected choice: {}",
department.choice
);
assert_eq!(department.probabilities.len(), 3);
assert!((0.0..=1.0).contains(&department.confidence));
let frustration = response.score("frustration").expect("missing score answer");
assert!((0.0..=2.0).contains(&frustration.score));
assert_eq!(frustration.legend.len(), 3);
let urgency = response.noul("is_urgent").expect("missing noul answer");
assert!(
urgency.noul > 0.5,
"ticket is clearly urgent, got {}",
urgency.noul
);
}
#[tokio::test]
async fn structured_state_is_accepted() {
let Some(client) = client() else {
eprintln!("TYPESAFE_API_KEY not set; skipping live test");
return;
};
let state = serde_json::json!({
"messages": [
{"role": "user", "content": "Do you have the X200 in stock?"},
{"role": "assistant", "content": "Let me check our inventory for you."},
]
});
let response = client
.system_one(
state,
[(
"inventory_question",
Question::from(Noul::new("The user is asking about product availability")),
)],
)
.await
.expect("system_one call failed");
let noul = response.noul("inventory_question").unwrap().noul;
assert!((0.0..=1.0).contains(&noul));
}
#[tokio::test]
async fn lists_models() {
let Some(client) = client() else {
eprintln!("TYPESAFE_API_KEY not set; skipping live test");
return;
};
let response = client.list_models().await.expect("list_models failed");
assert!(!response.models.is_empty());
assert!(
response.models.iter().any(|m| m.name == "jev-latest"),
"expected jev-latest in {:?}",
response.models.iter().map(|m| &m.name).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn bad_key_is_an_authentication_error() {
let client = TypeSafeClient::new("definitely-not-a-real-key").unwrap();
let err = client
.system_one(
"hello",
[("q", Question::from(Noul::new("Is this a test?")))],
)
.await
.expect_err("expected authentication failure");
assert!(err.is_authentication(), "unexpected error: {err}");
match &err {
Error::Api(api) => assert!(!api.message.is_empty()),
other => panic!("expected Error::Api, got {other:?}"),
}
}