typesafe-systemone 0.1.0

Async Rust client for the TypeSafe System One API (Jev): typed Noul, Choice and Score judgments
Documentation

typesafe-systemone

Async Rust client for the TypeSafe System One API. Jev, TypeSafe's System One model, evaluates a state against typed questions and returns calibrated probabilities rather than generated text.

This is an unofficial client. It follows the public API contract at https://docs.typesafe.ai/api and mirrors the shape of TypeSafe's Python and JavaScript SDKs.

[dependencies]
typesafe-systemone = "0.1"

Usage

use typesafe_systemone::Client;

let client = Client::from_env()?; // TYPESAFE_API_KEY

let response = client
    .system_one()
    .field("message", "Help! My payouts have been failing for 3 days.")
    .noul("is_urgent", "Does `message` convey urgency?")
    .choice("department", "Which team should handle `message`?", |c| {
        c.option("billing", "Payments, invoicing, refunds")
            .option("technical", "Bugs, outages, integrations")
            .none_of_the_above("No listed team fits")
    })
    .score("frustration", "How frustrated is the customer?", ["Calm", "Frustrated", "Very angry"])
    .send()
    .await?;

let urgent: f64 = response.noul("is_urgent")?;
let team = response.choice("department")?;
if team.confidence >= 0.9 {
    route(&team.choice);
} else {
    ask_a_human(team.ranked());
}

state is whatever the questions refer to. Build it with .field(name, value) calls, or pass one Serialize value with .state(my_struct); .model(..) overrides the client's model for one call. Input mistakes (no state, no questions, a Choice without options, a duplicate id) come back from .send() as Error::InvalidRequest, so the chain stays clean.

Already holding a question map? client.evaluate(state, questions) takes (id, Question) pairs directly.

Primitives

Builder Read the answer Use for
.noul(id, q) / .noul_with_criteria(..) response.noul(id)? (probability of yes) "does this condition hold?"
.choice(id, q, closure) with .option(..), .options_plain(..), .none_of_the_above(..) response.choice(id)? (choice, probabilities, confidence, ranked()) one of up to 255 options
.score(id, q, levels) response.score(id)? (score, legend, probabilities, confidence) position on an ordered rubric

Question::noul / choice / score constructors exist too, for .question(id, q) and client.evaluate(..).

Ask independent questions about the same state in one call. Add .none_of_the_above(..) to a Choice when the list may not cover every input; a Choice always picks something.

Configuration

Builder Env var Default
.api_key(..) TYPESAFE_API_KEY required
.model(..) TYPESAFE_DEFAULT_MODEL jev-latest
.base_url(..) TYPESAFE_BASE_URL https://api.typesafe.ai
.timeout(..) 30 s per request
.retry(RetryPolicy) 2 retries, 0.5 s → 5 s backoff, 25 % jitter, honours retry-after
.http_client(reqwest::Client) build our own

Explicit builder values win over environment variables. Empty or whitespace-only env values are ignored.

Pin a versioned model id (jev-1.13.0) once you have tuned confidence thresholds; the jev-latest alias moves when a new release ships.

Errors

Error mirrors the official SDKs: Authentication (401), PermissionDenied (403), NotFound (404), BadRequest (400), UnprocessableEntity (422), RateLimit (429), Overloaded (529), Server (other 5xx), Connection, Timeout, ResponseValidation. 408, 429 and 5xx are retried per RetryPolicy; other 4xx are not.

TLS

rustls is the default feature. Use native-tls instead, or disable default features and pass your own reqwest::Client via .http_client(..) to reuse the TLS backend your application already ships.

Limits worth knowing

From the models page: 64k tokens per request for state plus all questions, 32k for state plus the longest question, 255 options per Choice. Accuracy degrades as unrelated material in state grows, so keep each request's state to what its questions need.

License

MIT OR Apache-2.0.