use crate::{DEFAULT_MODEL, ValidationError};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Content {
Text(String),
Object(Map<String, Value>),
Array(Vec<Value>),
}
impl From<&str> for Content {
fn from(value: &str) -> Self {
Self::Text(value.into())
}
}
impl From<String> for Content {
fn from(value: String) -> Self {
Self::Text(value)
}
}
impl From<Map<String, Value>> for Content {
fn from(value: Map<String, Value>) -> Self {
Self::Object(value)
}
}
impl From<Vec<Value>> for Content {
fn from(value: Vec<Value>) -> Self {
Self::Array(value)
}
}
impl TryFrom<Value> for Content {
type Error = ValidationError;
fn try_from(value: Value) -> Result<Self, Self::Error> {
match value {
Value::String(value) => Ok(Self::Text(value)),
Value::Object(value) => Ok(Self::Object(value)),
Value::Array(value) => Ok(Self::Array(value)),
_ => Err(ValidationError::new(
"content",
"expected a string, object, or array",
)),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct NoulCriteria {
#[serde(rename = "true", skip_serializing_if = "Option::is_none")]
pub yes: Option<Content>,
#[serde(rename = "false", skip_serializing_if = "Option::is_none")]
pub no: Option<Content>,
}
impl NoulCriteria {
#[must_use]
pub fn yes(mut self, description: impl Into<Content>) -> Self {
self.yes = Some(description.into());
self
}
#[must_use]
pub fn no(mut self, description: impl Into<Content>) -> Self {
self.no = Some(description.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Question {
Noul {
instructions: Content,
#[serde(skip_serializing_if = "Option::is_none")]
criteria: Option<NoulCriteria>,
},
Choice {
instructions: Content,
criteria: BTreeMap<String, Option<Content>>,
},
Score {
instructions: Content,
criteria: Vec<Content>,
},
}
impl Question {
pub fn noul(instructions: impl Into<Content>) -> Self {
Self::Noul {
instructions: instructions.into(),
criteria: None,
}
}
pub fn noul_with_criteria(instructions: impl Into<Content>, criteria: NoulCriteria) -> Self {
Self::Noul {
instructions: instructions.into(),
criteria: Some(criteria),
}
}
pub fn choice<K: Into<String>>(
instructions: impl Into<Content>,
criteria: impl IntoIterator<Item = (K, Option<Content>)>,
) -> Self {
Self::Choice {
instructions: instructions.into(),
criteria: criteria
.into_iter()
.map(|(key, value)| (key.into(), value))
.collect(),
}
}
pub fn score<C: Into<Content>>(
instructions: impl Into<Content>,
criteria: impl IntoIterator<Item = C>,
) -> Self {
Self::Score {
instructions: instructions.into(),
criteria: criteria.into_iter().map(Into::into).collect(),
}
}
pub fn validate(&self) -> Result<(), ValidationError> {
match self {
Self::Choice { criteria, .. } if !(1..=255).contains(&criteria.len()) => Err(
ValidationError::new("criteria", "choice requires between 1 and 255 options"),
),
Self::Score { criteria, .. } if !(2..=10).contains(&criteria.len()) => Err(
ValidationError::new("criteria", "score requires between 2 and 10 levels"),
),
_ => Ok(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EvaluationRequest {
pub state: Content,
pub model: String,
pub questions: BTreeMap<String, Question>,
}
impl EvaluationRequest {
pub fn new(state: impl Into<Content>) -> Self {
Self {
state: state.into(),
model: DEFAULT_MODEL.into(),
questions: BTreeMap::new(),
}
}
#[must_use]
pub fn model(mut self, model: impl Into<String>) -> Self {
self.model = model.into();
self
}
#[must_use]
pub fn question(mut self, id: impl Into<String>, question: Question) -> Self {
self.questions.insert(id.into(), question);
self
}
pub fn validate(&self) -> Result<(), ValidationError> {
if self.model.trim().is_empty() {
return Err(ValidationError::new("model", "must not be empty"));
}
if self.questions.is_empty() {
return Err(ValidationError::new(
"questions",
"at least one question is required",
));
}
for (id, question) in &self.questions {
question.validate().map_err(|mut error| {
error.path = format!("questions[{id:?}].{}", error.path);
error
})?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Answer {
Noul {
noul: f64,
},
Choice {
choice: String,
probabilities: BTreeMap<String, f64>,
confidence: f64,
},
Score {
score: f64,
legend: BTreeMap<String, String>,
probabilities: BTreeMap<String, f64>,
confidence: f64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Usage {
pub input_tokens: u64,
pub output_tokens: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EvaluationResponse {
pub model: String,
pub answers: BTreeMap<String, Answer>,
pub usage: Usage,
}