Skip to main content

Client

Struct Client 

Source
pub struct Client { /* private fields */ }
Expand description

A connection to the System One API. One shared pool; call ask from as many threads as Config::pool_size allows.

Implementations§

Source§

impl Client

Source

pub fn new(api_key: &str, cfg: Config) -> Result<Self, Error>

A client that sends api_key as a bearer token to Config::base_url over HTTPS. Whitespace around the key, such as the newline at the end of a key file, is dropped.

Nothing is sent until the first ask: a key the API rejects is reported then, as Error::Auth.

§Errors

What no request could be sent with is reported here rather than retried later: Error::Auth for a blank key or one that cannot go in a header, and Error::InvalidConfig for a Config::base_url that is not an absolute http or https URL or a Config::user_agent that is not a valid header value.

Source

pub fn from_env(cfg: Config) -> Result<Self, Error>

A client that reads its API key from the API_KEY_VAR environment variable.

§Errors

Error::Auth if the variable is unset or blank; otherwise as Client::new.

Examples found in repository?
examples/ask.rs (line 21)
20fn main() -> Result<(), Error> {
21    let client = Client::from_env(Config::default())?;
22
23    let ticket = Ticket {
24        customer_plan: "business",
25        message: "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. \
26                  I'm losing sales. Please help ASAP.",
27    };
28    let questions = Questions::new()
29        .with(
30            "department",
31            Choice::new(
32                "Which team should handle the `message`?",
33                [
34                    ("billing", "Payment or subscription issues"),
35                    ("technical", "Bugs or integration problems"),
36                    ("sales", "Pricing or account questions"),
37                ],
38            ),
39        )
40        .with(
41            "frustration",
42            Score::new(
43                "How frustrated the customer appears",
44                ["Calm, just stating facts", "Frustrated but civil", "Very angry, strong language"],
45            ),
46        )
47        .with("is_urgent", Noul::new("The message conveys urgency or time-sensitivity"));
48
49    let response = match client.ask(&ticket, &questions) {
50        Ok(response) => response,
51        // Too much state for one request: a real caller would split it and ask again.
52        Err(Error::TokenLimit(message)) => {
53            eprintln!("request too large: {message}");
54            return Ok(());
55        }
56        Err(other) => return Err(other),
57    };
58
59    if let Some(department) = response.choice("department") {
60        println!("department: {} (confidence {:.2})", department.choice, department.confidence);
61        for (option, probability) in &department.probabilities {
62            println!("  {option}: {probability:.2}");
63        }
64    }
65    if let Some(frustration) = response.score("frustration") {
66        println!("frustration: {:.2} of {} (confidence {:.2})", frustration.score, frustration.legend.len() - 1, frustration.confidence);
67    }
68    if let Some(urgent) = response.noul("is_urgent") {
69        println!("urgent: p={:.2}", urgent.noul);
70    }
71
72    let usage = client.usage();
73    println!(
74        "{}: {} request(s), {} retries, {} input tokens, ~${:.6}",
75        response.model,
76        usage.requests(),
77        usage.retries(),
78        usage.input_tokens(),
79        usage.cost_usd()
80    );
81    Ok(())
82}
Source

pub fn with_transport(transport: impl Transport + 'static, cfg: Config) -> Self

A client over any Transport, for tests and local fakes. Config::base_url, Config::user_agent and the timeouts are the transport’s business and are ignored.

Source

pub fn set_debug_reporter( &mut self, reporter: impl Fn(&str) + Send + Sync + 'static, )

Receives one line per failed attempt (the status or connection error, and any Retry-After) before the client retries. Nothing is reported without a reporter, and the reporter changes neither the retry policy nor the request.

Source

pub fn model(&self) -> &str

The model name sent in every request.

Source

pub fn usage(&self) -> &Usage

Requests, retries and tokens so far.

Examples found in repository?
examples/ask.rs (line 72)
20fn main() -> Result<(), Error> {
21    let client = Client::from_env(Config::default())?;
22
23    let ticket = Ticket {
24        customer_plan: "business",
25        message: "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. \
26                  I'm losing sales. Please help ASAP.",
27    };
28    let questions = Questions::new()
29        .with(
30            "department",
31            Choice::new(
32                "Which team should handle the `message`?",
33                [
34                    ("billing", "Payment or subscription issues"),
35                    ("technical", "Bugs or integration problems"),
36                    ("sales", "Pricing or account questions"),
37                ],
38            ),
39        )
40        .with(
41            "frustration",
42            Score::new(
43                "How frustrated the customer appears",
44                ["Calm, just stating facts", "Frustrated but civil", "Very angry, strong language"],
45            ),
46        )
47        .with("is_urgent", Noul::new("The message conveys urgency or time-sensitivity"));
48
49    let response = match client.ask(&ticket, &questions) {
50        Ok(response) => response,
51        // Too much state for one request: a real caller would split it and ask again.
52        Err(Error::TokenLimit(message)) => {
53            eprintln!("request too large: {message}");
54            return Ok(());
55        }
56        Err(other) => return Err(other),
57    };
58
59    if let Some(department) = response.choice("department") {
60        println!("department: {} (confidence {:.2})", department.choice, department.confidence);
61        for (option, probability) in &department.probabilities {
62            println!("  {option}: {probability:.2}");
63        }
64    }
65    if let Some(frustration) = response.score("frustration") {
66        println!("frustration: {:.2} of {} (confidence {:.2})", frustration.score, frustration.legend.len() - 1, frustration.confidence);
67    }
68    if let Some(urgent) = response.noul("is_urgent") {
69        println!("urgent: p={:.2}", urgent.noul);
70    }
71
72    let usage = client.usage();
73    println!(
74        "{}: {} request(s), {} retries, {} input tokens, ~${:.6}",
75        response.model,
76        usage.requests(),
77        usage.retries(),
78        usage.input_tokens(),
79        usage.cost_usd()
80    );
81    Ok(())
82}
Source

pub fn limiter(&self) -> &AdaptiveLimiter

The concurrency gate, for inspection.

Source

pub fn ask<S: Serialize + ?Sized>( &self, state: &S, questions: &Questions, ) -> Result<Response, Error>

Evaluates questions against state and returns the Response: one Answer per question, under the id the question had.

state is what the questions are about, and anything that serializes to a JSON string, object or array: a &str, a serde_json::Value, or a type of your own that derives Serialize. Jev reads text only. A question can name a field of the state in backticks.

Transient failures are retried with exponential backoff up to Config::max_retries times, honouring Retry-After (capped at 30 seconds) and the shared AdaptiveLimiter. A Retry-After given as a date rather than as seconds is ignored in favour of the backoff. The call blocks the calling thread; run it from a thread pool for concurrency.

§Errors

Error::Auth for HTTP 401 and 403, Error::TokenLimit for a request larger than the model’s context, and Error::InvalidRequest for a state that does not serialize or a body the API refuses as malformed (HTTP 422), all without a retry. Error::Api for any other status that is not worth retrying, for a reply that is not the documented JSON, and once the retries are spent; the message then ends with the last failure.

Examples found in repository?
examples/ask.rs (line 49)
20fn main() -> Result<(), Error> {
21    let client = Client::from_env(Config::default())?;
22
23    let ticket = Ticket {
24        customer_plan: "business",
25        message: "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. \
26                  I'm losing sales. Please help ASAP.",
27    };
28    let questions = Questions::new()
29        .with(
30            "department",
31            Choice::new(
32                "Which team should handle the `message`?",
33                [
34                    ("billing", "Payment or subscription issues"),
35                    ("technical", "Bugs or integration problems"),
36                    ("sales", "Pricing or account questions"),
37                ],
38            ),
39        )
40        .with(
41            "frustration",
42            Score::new(
43                "How frustrated the customer appears",
44                ["Calm, just stating facts", "Frustrated but civil", "Very angry, strong language"],
45            ),
46        )
47        .with("is_urgent", Noul::new("The message conveys urgency or time-sensitivity"));
48
49    let response = match client.ask(&ticket, &questions) {
50        Ok(response) => response,
51        // Too much state for one request: a real caller would split it and ask again.
52        Err(Error::TokenLimit(message)) => {
53            eprintln!("request too large: {message}");
54            return Ok(());
55        }
56        Err(other) => return Err(other),
57    };
58
59    if let Some(department) = response.choice("department") {
60        println!("department: {} (confidence {:.2})", department.choice, department.confidence);
61        for (option, probability) in &department.probabilities {
62            println!("  {option}: {probability:.2}");
63        }
64    }
65    if let Some(frustration) = response.score("frustration") {
66        println!("frustration: {:.2} of {} (confidence {:.2})", frustration.score, frustration.legend.len() - 1, frustration.confidence);
67    }
68    if let Some(urgent) = response.noul("is_urgent") {
69        println!("urgent: p={:.2}", urgent.noul);
70    }
71
72    let usage = client.usage();
73    println!(
74        "{}: {} request(s), {} retries, {} input tokens, ~${:.6}",
75        response.model,
76        usage.requests(),
77        usage.retries(),
78        usage.input_tokens(),
79        usage.cost_usd()
80    );
81    Ok(())
82}

Trait Implementations§

Source§

impl Debug for Client

The model, the retry policy and the counters. The transport is left out: it holds the key.

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !Freeze for Client

§

impl !RefUnwindSafe for Client

§

impl !UnwindSafe for Client

§

impl Send for Client

§

impl Sync for Client

§

impl Unpin for Client

§

impl UnsafeUnpin for Client

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.