typesafe_sdk/lib.rs
1//! Async Rust SDK for the [TypeSafe AI](https://typesafe.ai) API.
2//!
3//! The crate is published as `typesafe-sdk-rust` because `typesafe-sdk` is
4//! already taken on crates.io; the library it builds is `typesafe_sdk`, so
5//! callers write `use typesafe_sdk::...`.
6//!
7//! # Asking questions
8//!
9//! A call asks a set of named questions about a state. The set is built once,
10//! validated and serialized by [`Questions::prepare`], and the resulting
11//! [`PreparedQuestions`] is reused by every call that asks it. A [`Client`]
12//! sends it: [`Client::system_one`] makes a request, whose methods set the
13//! model, the deadline, extra headers and extra body members, and
14//! [`send`](SystemOne::send) sends it and decodes the answers.
15//!
16//! ```
17//! use std::time::Duration;
18//!
19//! use typesafe_sdk::{Choice, Client, Noul, Questions, Score};
20//!
21//! let questions = Questions::new()
22//! .noul("billing", Noul::new().instructions("Is this about billing?"))
23//! .choice("tone", Choice::new(["calm", "angry"]).instructions("What is the tone?"))
24//! .score("urgency", Score::new(["can wait", "this week", "today"]))
25//! .prepare()?;
26//! assert_eq!(questions.names().collect::<Vec<_>>(), ["billing", "tone", "urgency"]);
27//!
28//! // `Client::from_env()` reads the same settings from TYPESAFE_API_KEY and
29//! // friends. Building connects to nothing.
30//! let client = Client::builder().api_key("your-api-key").build()?;
31//!
32//! let state = "I was charged twice for one order.";
33//! let request = client
34//! .system_one(state, &questions)
35//! .model("jev-latest")
36//! .timeout(Duration::from_secs(2))
37//! .header("x-team", "billing");
38//!
39//! // Sending needs a Tokio runtime; this example stops before it.
40//! async fn ask(request: typesafe_sdk::SystemOne<'_, typesafe_sdk::HyperTransport, str>)
41//! -> Result<f64, typesafe_sdk::Error> {
42//! let response = request.send().await?;
43//! Ok(response.answers().noul("billing").map_or(0.0, |answer| answer.noul()))
44//! }
45//! drop(ask(request));
46//! # Ok::<(), typesafe_sdk::Error>(())
47//! ```
48//!
49//! # Runtime requirements
50//!
51//! Every network operation is `async` and expects a [Tokio] runtime whose
52//! **time driver is enabled** (`#[tokio::main]`, or a `Builder` with
53//! `enable_time()` / `enable_all()`). Per-attempt deadlines and HTTP/2
54//! keep-alive both arm timers, and Tokio panics when a timer is created on a
55//! runtime without that driver.
56//!
57//! # Safety
58//!
59//! The crate is `#![forbid(unsafe_code)]`. Dependencies that use `unsafe`
60//! internally are confined to single modules so that swapping one out is a
61//! local change.
62//!
63//! [Tokio]: https://docs.rs/tokio
64
65#![forbid(unsafe_code)]
66
67pub mod client;
68mod codec;
69mod config;
70pub mod constants;
71pub mod content;
72pub mod de;
73pub mod error;
74pub mod models;
75mod name;
76pub mod question;
77mod redact;
78#[cfg(test)]
79mod rendering_tests;
80pub mod request;
81pub mod response;
82pub mod retry;
83mod telemetry;
84mod text;
85pub mod transport;
86
87#[cfg(feature = "internals")]
88#[doc(hidden)]
89pub mod __internals;
90
91pub use crate::{
92 client::{Client, ClientBuilder},
93 codec::{DecodeError, DecodeErrorKind, EncodeError, RawJson},
94 content::{Content, ContentError},
95 de::{AnswerContext, AnswerSet},
96 error::{ApiError, ApiErrorKind, Error, ErrorKind, ResponseValidationError},
97 models::{ListModels, ListModelsResponse, ModelMetadata, Models},
98 question::{Choice, Noul, PreparedQuestions, Question, Questions, RawQuestion, Score},
99 request::SystemOne,
100 response::{
101 Answer, Answers, ChoiceAnswer, NoulAnswer, ResponseMeta, ScoreAnswer, SystemOneResponse,
102 Usage,
103 },
104 retry::{RetryPolicy, StatusSet},
105 transport::{
106 Body, BoxError, HttpService, HttpVersion, HyperResponseFuture, HyperTransport, ResponseBody,
107 },
108};
109
110pub use crate::question::QuestionSet;
111
112/// Implements [`QuestionSet`](trait@QuestionSet) and [`AnswerSet`] for a struct with one field
113/// per question.
114///
115/// Available with the `macros` feature, which is on by default.
116///
117/// ```
118/// use typesafe_sdk::{ChoiceAnswer, NoulAnswer, QuestionSet, ScoreAnswer};
119///
120/// #[derive(QuestionSet)]
121/// struct Ticket {
122/// #[noul(instructions = "Is this about billing?", yes = "payments or invoices")]
123/// billing: NoulAnswer,
124/// #[choice(instructions = "What is the tone?", options("calm" = "neutral or polite", "angry"))]
125/// tone: ChoiceAnswer,
126/// #[score(instructions = "How urgent?", levels("can wait", "this week", "today"))]
127/// urgency: ScoreAnswer,
128/// }
129///
130/// assert_eq!(Ticket::prepared().names().collect::<Vec<_>>(), ["billing", "tone", "urgency"]);
131/// ```
132#[cfg(feature = "macros")]
133#[doc(inline)]
134pub use typesafe_sdk_rust_macros::QuestionSet;
135
136#[doc(hidden)]
137pub mod __private;
138
139/// Compiles every Rust block of `README.md` as a doctest, so the front page
140/// cannot drift from the API. The typed-answers block needs the derive, hence
141/// the feature.
142#[cfg(all(doctest, feature = "macros"))]
143#[doc = include_str!("../README.md")]
144struct ReadmeDoctests;