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;
77#[cfg(test)]
78mod rendering_tests;
79pub mod request;
80pub mod response;
81pub mod retry;
82mod telemetry;
83mod text;
84pub mod transport;
85
86#[cfg(feature = "internals")]
87#[doc(hidden)]
88pub mod __internals;
89
90pub use crate::{
91 client::{Client, ClientBuilder},
92 codec::{DecodeError, DecodeErrorKind, EncodeError, RawJson},
93 content::{Content, ContentError},
94 de::{AnswerContext, AnswerSet},
95 error::{ApiError, ApiErrorKind, Error, ErrorKind, ResponseValidationError},
96 models::{ListModels, ListModelsResponse, ModelMetadata, Models},
97 question::{Choice, Noul, PreparedQuestions, Question, Questions, RawQuestion, Score},
98 request::SystemOne,
99 response::{
100 Answer, Answers, ChoiceAnswer, NoulAnswer, ResponseMeta, ScoreAnswer, SystemOneResponse,
101 Usage,
102 },
103 retry::{RetryPolicy, StatusSet},
104 transport::{
105 Body, BoxError, HttpService, HttpVersion, HyperResponseFuture, HyperTransport, ResponseBody,
106 },
107};
108
109pub use crate::question::QuestionSet;
110
111/// Implements [`QuestionSet`](trait@QuestionSet) and [`AnswerSet`] for a struct with one field
112/// per question.
113///
114/// Available with the `macros` feature, which is on by default.
115///
116/// ```
117/// use typesafe_sdk::{ChoiceAnswer, NoulAnswer, QuestionSet, ScoreAnswer};
118///
119/// #[derive(QuestionSet)]
120/// struct Ticket {
121/// #[noul(instructions = "Is this about billing?", yes = "payments or invoices")]
122/// billing: NoulAnswer,
123/// #[choice(instructions = "What is the tone?", options("calm" = "neutral or polite", "angry"))]
124/// tone: ChoiceAnswer,
125/// #[score(instructions = "How urgent?", levels("can wait", "this week", "today"))]
126/// urgency: ScoreAnswer,
127/// }
128///
129/// assert_eq!(Ticket::prepared().names().collect::<Vec<_>>(), ["billing", "tone", "urgency"]);
130/// ```
131#[cfg(feature = "macros")]
132#[doc(inline)]
133pub use typesafe_sdk_rust_macros::QuestionSet;
134
135#[doc(hidden)]
136pub mod __private;
137
138/// Compiles every Rust block of `README.md` as a doctest, so the front page
139/// cannot drift from the API. The typed-answers block needs the derive, hence
140/// the feature.
141#[cfg(all(doctest, feature = "macros"))]
142#[doc = include_str!("../README.md")]
143struct ReadmeDoctests;