use std::fmt;
use std::future::IntoFuture;
use std::marker::PhantomData;
use std::ops::Deref;
use std::time::Duration;
use http::header::{HeaderName, HeaderValue};
use serde::Serialize;
use serde_json::Value;
use crate::client::{BoxFuture, Client, SystemOneRequest};
use crate::error::{Error, ResponseValidationError, Result};
use crate::question::{Choice, Questions};
use crate::response::{ChoiceAnswer, NoulAnswer, ScoreAnswer, SystemOneResponse};
use crate::retry::RetryPolicy;
#[cfg(doc)]
use crate::question::{Noul, Score};
pub trait Rubric: Sized {
fn questions() -> Questions;
fn from_response(response: &SystemOneResponse) -> Result<Self>;
}
pub trait RubricChoice: Sized {
const OPTIONS: &'static [(&'static str, Option<&'static str>)];
fn from_label(label: &str) -> Option<Self>;
fn label(&self) -> &'static str;
fn choice(instructions: impl Into<Value>) -> Choice {
Self::OPTIONS
.iter()
.fold(
Choice::new(instructions),
|c, (label, description)| match description {
Some(d) => c.option(*label, *d),
None => c.label(*label),
},
)
}
fn parse_label(label: &str) -> std::result::Result<Self, UnknownLabel> {
Self::from_label(label).ok_or_else(|| UnknownLabel {
label: label.to_owned(),
expected: Self::OPTIONS.iter().map(|(l, _)| *l).collect(),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct UnknownLabel {
pub label: String,
pub expected: Vec<&'static str>,
}
impl fmt::Display for UnknownLabel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unknown label {:?}; expected one of ", self.label)?;
for (i, l) in self.expected.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{l:?}")?;
}
Ok(())
}
}
impl std::error::Error for UnknownLabel {}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct ChoiceOf<T> {
pub value: T,
pub answer: ChoiceAnswer,
}
impl<T> ChoiceOf<T> {
pub fn confidence(&self) -> f64 {
self.answer.confidence
}
pub fn into_inner(self) -> T {
self.value
}
}
impl<T: RubricChoice> ChoiceOf<T> {
pub fn probability(&self, option: &T) -> Option<f64> {
self.answer.probability(option.label())
}
}
impl<T> Deref for ChoiceOf<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
pub trait NoulField {
fn from_noul(answer: &NoulAnswer) -> Self;
}
impl NoulField for NoulAnswer {
fn from_noul(answer: &NoulAnswer) -> Self {
answer.clone()
}
}
impl NoulField for f64 {
fn from_noul(answer: &NoulAnswer) -> Self {
answer.noul
}
}
pub trait ScoreField {
fn from_score(answer: &ScoreAnswer) -> Self;
}
impl ScoreField for ScoreAnswer {
fn from_score(answer: &ScoreAnswer) -> Self {
answer.clone()
}
}
impl ScoreField for f64 {
fn from_score(answer: &ScoreAnswer) -> Self {
answer.score
}
}
pub trait ChoiceField: Sized {
fn question(instructions: Value) -> Choice;
fn from_choice(answer: &ChoiceAnswer) -> std::result::Result<Self, UnknownLabel>;
}
impl<T: RubricChoice> ChoiceField for ChoiceOf<T> {
fn question(instructions: Value) -> Choice {
T::choice(instructions)
}
fn from_choice(answer: &ChoiceAnswer) -> std::result::Result<Self, UnknownLabel> {
Ok(ChoiceOf {
value: T::parse_label(&answer.choice)?,
answer: answer.clone(),
})
}
}
impl ChoiceField for ChoiceAnswer {
fn question(instructions: Value) -> Choice {
Choice::new(instructions)
}
fn from_choice(answer: &ChoiceAnswer) -> std::result::Result<Self, UnknownLabel> {
Ok(answer.clone())
}
}
impl ChoiceField for String {
fn question(instructions: Value) -> Choice {
Choice::new(instructions)
}
fn from_choice(answer: &ChoiceAnswer) -> std::result::Result<Self, UnknownLabel> {
Ok(answer.choice.clone())
}
}
#[doc(hidden)]
pub mod __private {
use super::*;
use crate::response::Answer;
fn mismatch(response: &SystemOneResponse, field_path: String, detail: String) -> Error {
Error::ResponseValidation(Box::new(ResponseValidationError {
status: response.meta.status,
field_path,
detail,
body: Some(response.raw.clone()),
headers: response.meta.headers.clone(),
endpoint: None,
}))
}
fn answer<'r>(response: &'r SystemOneResponse, name: &str, kind: &str) -> Result<&'r Answer> {
let answer = response.answers.get(name).ok_or_else(|| {
let detail = if response.raw["answers"].get(name).is_some() {
format!("the answer is of a type this SDK does not know; expected a {kind}")
} else {
format!("no answer; expected a {kind}")
};
mismatch(response, format!("answers.{name}"), detail)
})?;
if answer.kind() != kind {
return Err(mismatch(
response,
format!("answers.{name}"),
format!("expected a {kind} answer, got a {}", answer.kind()),
));
}
Ok(answer)
}
pub fn noul<'r>(response: &'r SystemOneResponse, name: &str) -> Result<&'r NoulAnswer> {
match answer(response, name, "noul")? {
Answer::Noul(a) => Ok(a),
_ => unreachable!("kind checked"),
}
}
pub fn score<'r>(response: &'r SystemOneResponse, name: &str) -> Result<&'r ScoreAnswer> {
match answer(response, name, "score")? {
Answer::Score(a) => Ok(a),
_ => unreachable!("kind checked"),
}
}
pub fn choice<T: ChoiceField>(response: &SystemOneResponse, name: &str) -> Result<T> {
let Answer::Choice(a) = answer(response, name, "choice")? else {
unreachable!("kind checked")
};
T::from_choice(a)
.map_err(|e| mismatch(response, format!("answers.{name}.choice"), e.to_string()))
}
}
impl Client {
pub fn ask<R: Rubric>(&self, state: impl Serialize) -> AskRequest<R> {
AskRequest {
req: self.system_one(state, R::questions()),
rubric: PhantomData,
}
}
}
#[must_use = "requests do nothing until awaited"]
#[derive(Debug)]
pub struct AskRequest<R> {
req: SystemOneRequest,
rubric: PhantomData<fn() -> R>,
}
impl<R: Rubric> AskRequest<R> {
pub fn retry(mut self, policy: RetryPolicy) -> Self {
self.req = self.req.retry(policy);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.req = self.req.timeout(timeout);
self
}
pub fn header(mut self, name: HeaderName, value: HeaderValue) -> Self {
self.req = self.req.header(name, value);
self
}
pub fn model(mut self, model: impl Into<String>) -> Self {
self.req = self.req.model(model);
self
}
pub fn extra_body(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
self.req = self.req.extra_body(key, value);
self
}
pub async fn send(self) -> Result<R> {
R::from_response(&self.req.send().await?)
}
}
impl<R: Rubric + 'static> IntoFuture for AskRequest<R> {
type Output = Result<R>;
type IntoFuture = BoxFuture<'static, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(self.send())
}
}
#[cfg(all(doctest, feature = "derive"))]
pub struct DeriveCompileFail;