Skip to main content

Crate typesafe_ai_rs

Crate typesafe_ai_rs 

Source
Expand description

§TypeSafe Rust SDK

An independent Rust client for the TypeSafe AI System One API, maintained at gilljon/typesafe-ai-rs.

Async and blocking clients, typed Noul/Choice/Score questions and answers, model discovery, configurable retries, cancellation, and complete HTTP response metadata. The implementation targets the official Python and JavaScript SDKs at version 0.6.0. See the parity notes for exact source revisions and deliberate Rust differences.

§Install

cargo add typesafe-ai-rs
cargo add tokio --features macros,rt-multi-thread
cargo add serde_json

The package is named typesafe-ai-rs, imported as typesafe_ai_rs.

Alternatively, install directly from the GitHub release:

cargo add typesafe-ai-rs --git https://github.com/gilljon/typesafe-ai-rs --tag v0.1.0

Rust 1.88 or newer is required. The default TLS backend is Rustls. To use native TLS, disable default features and enable native-tls. Enable blocking for the synchronous client.

§Quick start

Set TYPESAFE_API_KEY to an API key from the TypeSafe console.

use serde_json::json;
use typesafe_ai_rs::{Client, Choice, Noul, Question, Score, SystemOneRequest};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new()?;
    let response = client.system_one(SystemOneRequest::new(
        json!({"document": "I was charged twice. Please fix this ASAP."}),
        [
            ("billing", Question::from(Noul::new("Is this about billing?"))),
            ("tone", Choice::new([("calm", json!(null)), ("frustrated", json!(null)), ("angry", json!(null))])
                .instructions("What is the customer's tone?").into()),
            ("urgency", Score::new(["can wait", "this week", "today"])
                .instructions("How urgent is this?").into()),
        ],
    )).await?;

    println!("billing: {}", response.nouls()["billing"].noul);
    println!("tone: {}", response.choices()["tone"].choice);
    println!("urgency: {}", response.scores()["urgency"].score);
    println!("request ID: {:?}", response.request_id());
    Ok(())
}

Questions accept JSON instructions and descriptions. Omitted instructions and explicit JSON null remain distinct. Score criteria are an ordered, nonempty sequence, sent as an array on the wire. Score answers expose integer keys for their probabilities and legend. A one-level score rubric follows Python’s validation; JavaScript requires two levels.

§Blocking client

cargo add typesafe-ai-rs --features blocking
use typesafe_ai_rs::{blocking::Client, Noul, SystemOneRequest};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = Client::new()?.system_one(SystemOneRequest::new(
        "I was charged twice.",
        [("billing", Noul::new("Is this about billing?"))],
    ))?;
    println!("{}", response.nouls()["billing"].noul);
    Ok(())
}

Use the blocking client outside async runtimes, or in tokio::task::spawn_blocking. Clones share connection pools; dropping the last client releases its resources. AsyncTypeSafeClient and, with the feature enabled, TypeSafeClient are aliases matching Python’s naming.

§Configuration and per-call options

Explicit configuration takes precedence over environment variables. Empty environment values are ignored.

Environment variableDefault
TYPESAFE_API_KEYRequired
TYPESAFE_BASE_URLhttps://api.typesafe.ai
TYPESAFE_DEFAULT_MODELjev-latest
TYPESAFE_LOG_LEVELwarn
use std::time::Duration;
use typesafe_ai_rs::{Client, Noul, RequestOptions, RetryPolicy, SystemOneRequest};

let client = Client::builder()
    .api_key("your-api-key")
    .model("jev-latest")
    .timeout(Duration::from_secs(10))
    .retry(RetryPolicy { max_retries: 3, ..Default::default() })
    .build()?;

let result = client.system_one_with_options(
    SystemOneRequest::new("A support ticket", [("billing", Noul::new("About billing?"))])
        .model("jev-latest"),
    RequestOptions {
        timeout: Some(Duration::from_secs(5)),
        retry: Some(RetryPolicy::no_retries()),
        ..Default::default()
    },
).await?;

default_headers and per-call headers use HeaderMap. Per-call headers override defaults case-insensitively, except protected authentication and SDK protocol headers. A custom reqwest::Client (or reqwest::blocking::Client) can be supplied through .http_client(...) for proxy, TLS, and pool configuration. SDK request timeouts still apply. The default HTTP client does not follow redirects; custom clients control their own redirect behavior.

The default policy makes up to two retries for HTTP 408, 429, 5xx, connection errors, and timeouts. Exponential backoff starts at 500ms, caps at 5s, and subtracts up to 25% jitter. Server retry headers accept milliseconds, seconds, and HTTP dates. A 30s retry budget follows Python; the 60s retry-header cap follows JavaScript. The budget prevents scheduling another retry but does not interrupt an already-running attempt. Set timeout: None in RetryPolicy for JavaScript’s unlimited total budget, or max_retry_after: None for Python’s uncapped server delays. A per-call retry policy replaces the client’s policy. Use struct update syntax to inherit selected settings. Custom HTTP statuses and an additional retry predicate are supported.

§Models and metadata

let response = client.models().list().await?;
for model in &response.models {
    println!("{}: {}", model.name, model.description);
}
println!("request ID: {:?}", response.request_id());
println!("HTTP status: {}", response.raw_http_response.status);

Responses retain status, headers, and body bytes in raw_http_response; .json() and .text() inspect the full body. Required response fields are validated, unknown fields are ignored, and unknown answer kinds are skipped from typed answers while remaining available in the raw body.

§Errors and cancellation

Errors distinguish invalid configuration/requests, transport failures, timeout, cancellation, server errors, and malformed successful responses. API errors expose an ApiErrorKind, status, request ID, headers, body, and extracted server message. Display and Debug omit response bodies and secret headers; call .message() explicitly for server details.

use typesafe_ai_rs::{ApiErrorKind, Error};
match client.models().list().await {
    Ok(response) => println!("{} models", response.models.len()),
    Err(Error::Api(error)) if error.kind() == ApiErrorKind::RateLimit => {
        eprintln!("retry after {:?}; request {:?}", error.retry_after(), error.request_id());
    }
    Err(error) => eprintln!("{error}"),
}

Pass a cloned CancellationToken in RequestOptions::cancellation_token and call token.cancel() from another task to cancel an async request or retry wait. Dropping an in-progress request future also cancels it. Cancellation is never retried. Blocking requests use timeouts and reject cancellation tokens.

§Forward compatibility and logging

Use Question::Raw(json!(...)) for future question types/fields, and .extra_body([("beam_width", json!(4))]) on a request for additional top-level fields. Extra body fields merge last and can override state, model, or questions, matching Python.

The SDK uses the log facade with target typesafe_ai_rs; install an application logger such as env_logger to receive output. TYPESAFE_LOG_LEVEL or .log_level(...) filters transport messages. info emits request summaries; debug adds bodies and redacted headers. Authorization, cookies, API keys, and headers containing token or secret are redacted. Bodies can contain application data and are deliberately not redacted at debug level.

§Development

cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo test --no-default-features
cargo doc --all-features --no-deps
cargo package

Tests use local HTTP servers and need no TypeSafe API key. Runnable examples live in examples/. Live examples require your key and may consume API credits. See release instructions for registry publication.

MIT licensed. This project is independently maintained and is not an official TypeSafe SDK.

Modules§

blocking
Synchronous client, enabled by the blocking Cargo feature.

Structs§

ApiError
An unsuccessful HTTP response with its body and request metadata.
CancellationToken
A token which can be used to signal a cancellation request to one or more tasks.
Choice
A question that selects among named alternatives.
ChoiceAnswer
The selected label, its confidence, and the probability of each label.
Client
Asynchronous TypeSafe client. Clone it to share a pooled HTTP connection manager.
ClientBuilder
Construct an asynchronous client with environment fallbacks.
HeaderMap
A specialized multimap for header names and values.
HeaderName
Represents an HTTP header field name
HeaderValue
Represents an HTTP header field value.
ListModelsResponse
Models available to the account, with raw HTTP metadata.
ModelCard
Metadata for an available model.
Models
Models resource associated with an asynchronous client.
Noul
A yes/no question. Its answer is a probability between zero and one.
NoulAnswer
A yes/no answer, expressed as the probability of yes.
RawResponse
The complete response before typed parsing, including unrecognized API fields.
RequestOptions
Per-call overrides. An omitted field inherits the client’s setting.
RetryPolicy
Configuration shared by synchronous and asynchronous request retries.
Score
A question that assigns an expected score using an ordered rubric.
ScoreAnswer
An expected score, which may fall between integer rubric levels.
StatusCode
An HTTP status code (status-code in RFC 9110 et al.).
SystemOneRequest
State and named questions to evaluate with System One.
SystemOneResponse
Answers keyed by question name, together with model and token usage metadata.
Usage
Token counts, when reported by the API.

Enums§

Answer
An answer identified by its type discriminator in the API response.
ApiErrorKind
Classification of an unsuccessful HTTP response.
Error
An error returned by the TypeSafe SDK.
Question
A typed question, or a raw JSON object for additional and future API fields.

Constants§

DEFAULT_BASE_URL
Default API root (without the versioned endpoint path).
DEFAULT_MODEL
Default model alias.
DEFAULT_TIMEOUT
Timeout for each complete HTTP attempt, including its response body.
VERSION
This crate’s version.

Functions§

parse_retry_after
Parse a server retry delay, preferring retry-after-ms over Retry-After.

Type Aliases§

AsyncTypeSafeClient
Python-compatible name for the asynchronous client.
ChoiceCriteria
Labels mapped to descriptions; use Value::Null for an undescribed label.
Entry
JSON content accepted by the API, including text, objects, arrays, and null.
LogLevel
Per-client log filtering. Messages use the typesafe_ai_rs target of the log facade. Applications install their own logger; this library never installs a global logger.
ModelMetadata
The Python SDK calls model cards ModelMetadata.
NoulCriteria
Descriptions for the true and false outcomes of a noul.
Questions
Questions keyed by the names used to identify their answers.
RetryPredicate
An additional rule that can opt an error into retries.
ScoreCriteria
An ordered rubric whose positions are the integer score levels.
TypeSafeClient
Python-compatible name for the synchronous client.