Call System One from Rust
September 17, 2026 ยท View on GitHub
Dependencies
Add these crates to Cargo.toml.
[dependencies]
typesafe-sdk = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
serde_json = "1"
Write serde_json = "1" yourself. Do not run cargo add serde_json if that selects a newer patch. This crate pins serde_json = "=1.0.134".
API key
Export TYPESAFE_API_KEY before you run the program.
Client::from_env returns Error::Sdk when the variable is unset or blank.
Client
Use Client::from_env when the key is in the environment.
let client = Client::from_env()?;
To pass the key in code, call Client::builder().api_key(...).build().
Client::builder().build() does not compile. The builder is typestated. build exists only after api_key.
Ask questions
Copy this program. Change the state and the question names for your task.
use typesafe_sdk::{Client, Question};
#[tokio::main]
async fn main() -> Result<(), typesafe_sdk::Error> {
let client = Client::from_env()?;
let response = client
.system_one(
serde_json::json!({"document": "I was charged twice. Please fix this ASAP."}),
[
("billing", Question::noul("Is this ticket about billing?")),
(
"tone",
Question::choice(
"What is the customer's tone?",
[("calm", None), ("frustrated", None), ("angry", None)],
),
),
(
"urgency",
Question::score(
"How urgent is this ticket?",
["can wait", "this week", "today"],
),
),
],
)
.await?;
println!("billing={}", response.noul("billing")?.noul);
println!("tone={}", response.choice("tone")?.choice);
println!("urgency={}", response.score("urgency")?.score);
Ok(())
}
Check the same program with cargo build --example system_one --features mock.
Cookbook examples
See examples/README.md for the full list (fan-out, confidence routing, guardrails, and others).
Mock run (no API key):
cargo run --example fan_out --features mock
Live run against your account:
export TYPESAFE_API_KEY=...
export TYPESAFE_LIVE=1
cargo run --example fan_out
TYPESAFE_LIVE=1 selects the real API. Without it, examples need --features mock and use wiremock fixtures in examples/support/fixtures.rs.
Read answers
Use the helper that matches the question type.
response.noul("billing")?.noulis anf64yes probability from 0.0 to 1.0. It is not abool.response.choice("tone")?.choiceis the selected label as aString.response.score("urgency")?.scoreis anf64rubric value. It is not a legend index.
The answer name must match the question name.
There is no nouls or choices map. Call response.noul("name").
Scan mixed types through response.answers.
Choice descriptions are Option<JsonContent>. Write Some("Calm".into()), not Some("Calm").
Blocking client
If the program cannot be async, enable blocking and use typesafe_sdk::blocking::Client. Do not .await.
typesafe-sdk = { version = "0.1", features = ["blocking"] }
use typesafe_sdk::blocking::Client;
use typesafe_sdk::Question;
fn main() -> Result<(), typesafe_sdk::Error> {
let client = Client::from_env()?;
let response = client.system_one("some text", [("q", Question::noul("Yes?"))])?;
println!("{}", response.noul("q")?.noul);
Ok(())
}
Common failures
- There is no
noulsorchoicesdict. Useresponse.noul("name"),response.choice("name"), orresponse.score("name"). - Async code needs Tokio with
macrosandrt-multi-thread, plus#[tokio::main]. - Set
TYPESAFE_API_KEYor passapi_keyon the builder. - Answer names must match question names.
- Call
api_keybeforebuild.Client::builder().build()does not compile. - Enable
features = ["blocking"]before you importtypesafe_sdk::blocking::Client. - Do not call
blocking::Clientinside an existing Tokio runtime. It panics. - Treat
noulas a probability (f64), not a boolean. - Pin consumer
serde_jsonas"1". A newer exact version conflicts with the crate pin.
More reading
README.mdfor install, env defaults, retries, and Python parity- https://docs.rs/typesafe-sdk
examples/system_one.rsfor the compile-checked programsrc/*.rsfor the public API