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 13)
12fn main() -> Result<(), Error> {
13    let client = Client::from_env(Config::default())?;
14
15    let state = json!({
16        "code": "1| import os\n2| def cwd():\n3|     return os.getcwd()",
17        "query": "where is the working directory read",
18    });
19    let mut questions = Map::new();
20    questions.insert(
21        "line3".into(),
22        json!({
23            "type": "noul",
24            "instructions": "Does line 3 of the code directly answer the query?",
25        }),
26    );
27    questions.insert(
28        "relevance".into(),
29        json!({
30            "type": "score",
31            "instructions": "How relevant is the code to the query?",
32            "criteria": ["unrelated", "tangential", "relevant", "exactly what was asked"],
33        }),
34    );
35
36    match client.ask(&state, &questions) {
37        Ok(answers) => {
38            for (id, answer) in &answers {
39                println!("{id}: {answer}");
40            }
41        }
42        // Too much state for one request: a real caller would split it and ask again.
43        Err(Error::TokenLimit(message)) => eprintln!("request too large: {message}"),
44        Err(other) => return Err(other),
45    }
46
47    let usage = client.usage();
48    println!(
49        "{} request(s), {} retries, {} input tokens, ~${:.6}",
50        usage.requests(),
51        usage.retries(),
52        usage.input_tokens(),
53        usage.cost_usd()
54    );
55    Ok(())
56}
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 47)
12fn main() -> Result<(), Error> {
13    let client = Client::from_env(Config::default())?;
14
15    let state = json!({
16        "code": "1| import os\n2| def cwd():\n3|     return os.getcwd()",
17        "query": "where is the working directory read",
18    });
19    let mut questions = Map::new();
20    questions.insert(
21        "line3".into(),
22        json!({
23            "type": "noul",
24            "instructions": "Does line 3 of the code directly answer the query?",
25        }),
26    );
27    questions.insert(
28        "relevance".into(),
29        json!({
30            "type": "score",
31            "instructions": "How relevant is the code to the query?",
32            "criteria": ["unrelated", "tangential", "relevant", "exactly what was asked"],
33        }),
34    );
35
36    match client.ask(&state, &questions) {
37        Ok(answers) => {
38            for (id, answer) in &answers {
39                println!("{id}: {answer}");
40            }
41        }
42        // Too much state for one request: a real caller would split it and ask again.
43        Err(Error::TokenLimit(message)) => eprintln!("request too large: {message}"),
44        Err(other) => return Err(other),
45    }
46
47    let usage = client.usage();
48    println!(
49        "{} request(s), {} retries, {} input tokens, ~${:.6}",
50        usage.requests(),
51        usage.retries(),
52        usage.input_tokens(),
53        usage.cost_usd()
54    );
55    Ok(())
56}
Source

pub fn limiter(&self) -> &AdaptiveLimiter

The concurrency gate, for inspection.

Source

pub fn ask( &self, state: &Value, questions: &Map<String, Value>, ) -> Result<Map<String, Value>, Error>

Evaluates questions against state and returns the answers map, keyed like the questions.

state is any JSON the questions refer to. Each question is an object with a type (noul or score) and instructions; a score question also lists its criteria. Each answer is an object with the same type and the model’s judgment: a noul probability in [0, 1], or a score with its confidence and probabilities. The crate documentation shows both shapes.

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, and Error::TokenLimit for a request larger than the model’s context, both 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 36)
12fn main() -> Result<(), Error> {
13    let client = Client::from_env(Config::default())?;
14
15    let state = json!({
16        "code": "1| import os\n2| def cwd():\n3|     return os.getcwd()",
17        "query": "where is the working directory read",
18    });
19    let mut questions = Map::new();
20    questions.insert(
21        "line3".into(),
22        json!({
23            "type": "noul",
24            "instructions": "Does line 3 of the code directly answer the query?",
25        }),
26    );
27    questions.insert(
28        "relevance".into(),
29        json!({
30            "type": "score",
31            "instructions": "How relevant is the code to the query?",
32            "criteria": ["unrelated", "tangential", "relevant", "exactly what was asked"],
33        }),
34    );
35
36    match client.ask(&state, &questions) {
37        Ok(answers) => {
38            for (id, answer) in &answers {
39                println!("{id}: {answer}");
40            }
41        }
42        // Too much state for one request: a real caller would split it and ask again.
43        Err(Error::TokenLimit(message)) => eprintln!("request too large: {message}"),
44        Err(other) => return Err(other),
45    }
46
47    let usage = client.usage();
48    println!(
49        "{} request(s), {} retries, {} input tokens, ~${:.6}",
50        usage.requests(),
51        usage.retries(),
52        usage.input_tokens(),
53        usage.cost_usd()
54    );
55    Ok(())
56}

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.