use std::collections::BTreeMap;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use crate::Content;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Response {
pub model: String,
pub answers: IndexMap<String, Answer>,
#[serde(default)]
pub usage: TokenUsage,
}
impl Response {
pub fn noul(&self, id: &str) -> Option<&NoulAnswer> {
self.answers.get(id).and_then(Answer::as_noul)
}
pub fn choice(&self, id: &str) -> Option<&ChoiceAnswer> {
self.answers.get(id).and_then(Answer::as_choice)
}
pub fn score(&self, id: &str) -> Option<&ScoreAnswer> {
self.answers.get(id).and_then(Answer::as_score)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TokenUsage {
#[serde(default)]
pub input_tokens: Option<u64>,
#[serde(default)]
pub output_tokens: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase", try_from = "RawAnswer")]
#[non_exhaustive]
pub enum Answer {
Noul(NoulAnswer),
Choice(ChoiceAnswer),
Score(ScoreAnswer),
}
impl Answer {
pub fn as_noul(&self) -> Option<&NoulAnswer> {
match self {
Answer::Noul(answer) => Some(answer),
_ => None,
}
}
pub fn as_choice(&self) -> Option<&ChoiceAnswer> {
match self {
Answer::Choice(answer) => Some(answer),
_ => None,
}
}
pub fn as_score(&self) -> Option<&ScoreAnswer> {
match self {
Answer::Score(answer) => Some(answer),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
#[non_exhaustive]
pub struct NoulAnswer {
pub noul: f64,
}
impl NoulAnswer {
pub fn new(noul: f64) -> Self {
NoulAnswer { noul }
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct ChoiceAnswer {
pub choice: String,
pub confidence: f64,
pub probabilities: IndexMap<String, f64>,
}
impl ChoiceAnswer {
pub fn new(choice: impl Into<String>, confidence: f64, probabilities: IndexMap<String, f64>) -> Self {
ChoiceAnswer { choice: choice.into(), confidence, probabilities }
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct ScoreAnswer {
pub score: f64,
pub confidence: f64,
pub legend: BTreeMap<u32, Content>,
pub probabilities: BTreeMap<u32, f64>,
}
impl ScoreAnswer {
pub fn new(score: f64, confidence: f64, legend: BTreeMap<u32, Content>, probabilities: BTreeMap<u32, f64>) -> Self {
ScoreAnswer { score, confidence, legend, probabilities }
}
}
#[derive(Deserialize)]
struct RawAnswer {
r#type: String,
noul: Option<f64>,
choice: Option<String>,
score: Option<f64>,
confidence: Option<f64>,
probabilities: Option<IndexMap<String, f64>>,
legend: Option<IndexMap<String, Content>>,
}
impl TryFrom<RawAnswer> for Answer {
type Error = String;
fn try_from(raw: RawAnswer) -> Result<Self, String> {
fn required<T>(field: Option<T>, name: &str, kind: &str) -> Result<T, String> {
field.ok_or_else(|| format!("a `{kind}` answer has no `{name}`"))
}
fn by_level<T>(map: IndexMap<String, T>, name: &str) -> Result<BTreeMap<u32, T>, String> {
map.into_iter()
.map(|(level, value)| match level.parse() {
Ok(level) => Ok((level, value)),
Err(_) => Err(format!("a `score` answer's `{name}` has the key `{level}`, which is not a level number")),
})
.collect()
}
let kind = raw.r#type.as_str();
match kind {
"noul" => Ok(Answer::Noul(NoulAnswer { noul: required(raw.noul, "noul", kind)? })),
"choice" => Ok(Answer::Choice(ChoiceAnswer {
choice: required(raw.choice, "choice", kind)?,
confidence: required(raw.confidence, "confidence", kind)?,
probabilities: required(raw.probabilities, "probabilities", kind)?,
})),
"score" => Ok(Answer::Score(ScoreAnswer {
score: required(raw.score, "score", kind)?,
confidence: required(raw.confidence, "confidence", kind)?,
legend: by_level(required(raw.legend, "legend", kind)?, "legend")?,
probabilities: by_level(required(raw.probabilities, "probabilities", kind)?, "probabilities")?,
})),
other => Err(format!("unknown answer type `{other}`; this client reads `noul`, `choice` and `score`")),
}
}
}