jev-sdk 0.1.0

Unofficial Rust client for the TypeSafe AI System One API (Jev): typed decisions with calibrated probabilities
Documentation
//! Live integration tests against the real TypeSafe API.
//!
//! These run automatically when `TYPESAFE_API_KEY` is set, and are skipped
//! otherwise.

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:?}"),
    }
}