1use std::borrow::Cow;
6use std::collections::BTreeMap;
7
8use serde::de::Error as _;
9use serde::ser::SerializeMap;
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use serde_json::Value;
12
13use crate::error::{Error, Result};
14
15#[derive(Debug, Clone, PartialEq, Serialize)]
18pub struct DecisionRequest {
19 pub model: String,
20 pub state: Value,
21 pub questions: BTreeMap<String, Question>,
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27#[serde(tag = "type", rename_all = "lowercase")]
28pub enum Question {
29 Choice { instructions: Value, criteria: Options },
31 Score { instructions: Value, criteria: Vec<Value> },
33 Noul {
35 instructions: Value,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 criteria: Option<NoulCriteria>,
38 },
39}
40
41impl Question {
42 pub fn choice<K: Into<String>, V: Into<Value>>(instructions: impl Into<Value>, options: impl IntoIterator<Item = (K, V)>) -> Question {
44 let options = options.into_iter().map(|(name, description)| (name.into(), description.into())).collect();
45 Question::Choice { instructions: instructions.into(), criteria: Options(options) }
46 }
47
48 pub fn score<V: Into<Value>>(instructions: impl Into<Value>, levels: impl IntoIterator<Item = V>) -> Question {
50 Question::Score { instructions: instructions.into(), criteria: levels.into_iter().map(Into::into).collect() }
51 }
52
53 pub fn noul(instructions: impl Into<Value>) -> Question {
55 Question::Noul { instructions: instructions.into(), criteria: None }
56 }
57
58 pub fn noul_with_criteria(instructions: impl Into<Value>, yes: impl Into<Value>, no: impl Into<Value>) -> Question {
60 Question::Noul { instructions: instructions.into(), criteria: Some(NoulCriteria { yes: yes.into(), no: no.into() }) }
61 }
62}
63
64impl Question {
65 pub fn check(&self) -> std::result::Result<(), String> {
68 match self {
69 Question::Choice { criteria: Options(options), .. } => {
70 if options.is_empty() {
71 return Err("a choice needs options to pick from".to_owned());
72 }
73 for (at, (name, _)) in options.iter().enumerate() {
75 if options[..at].iter().any(|(earlier, _)| earlier == name) {
76 return Err(format!("option `{name}` is there twice"));
77 }
78 }
79 if options.len() > CHOICE_OPTIONS {
80 return Err(format!("a choice takes up to {CHOICE_OPTIONS} options, not {}", options.len()));
81 }
82 }
83 Question::Score { criteria: levels, .. } => {
84 if levels.is_empty() {
85 return Err("a score needs levels to place the state on".to_owned());
86 }
87 if levels.len() > SCORE_LEVELS {
88 return Err(format!("a score takes up to {SCORE_LEVELS} levels, not {}", levels.len()));
89 }
90 }
91 Question::Noul { .. } => {}
92 }
93 Ok(())
94 }
95}
96
97const CHOICE_OPTIONS: usize = 255;
100const SCORE_LEVELS: usize = 10;
101
102#[derive(Debug, Clone, PartialEq, Default)]
104pub struct Options(pub Vec<(String, Value)>);
105
106impl Serialize for Options {
107 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
108 let mut map = serializer.serialize_map(Some(self.0.len()))?;
109 for (name, description) in &self.0 {
110 map.serialize_entry(name, description)?;
111 }
112 map.end()
113 }
114}
115
116impl<'de> Deserialize<'de> for Options {
117 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Options, D::Error> {
119 struct Visitor;
120 impl<'de> serde::de::Visitor<'de> for Visitor {
121 type Value = Options;
122
123 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
124 f.write_str("an object of options and their descriptions")
125 }
126
127 fn visit_map<A: serde::de::MapAccess<'de>>(self, mut map: A) -> std::result::Result<Options, A::Error> {
128 let mut options = Vec::new();
129 while let Some(option) = map.next_entry()? {
130 options.push(option);
131 }
132 Ok(Options(options))
133 }
134 }
135 deserializer.deserialize_map(Visitor)
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
141pub struct NoulCriteria {
142 #[serde(rename = "true")]
143 pub yes: Value,
144 #[serde(rename = "false")]
145 pub no: Value,
146}
147
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
150pub struct DecisionResponse {
151 pub model: String,
153 pub answers: BTreeMap<String, Answer>,
154 pub usage: Usage,
155 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pub id: Option<String>,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub provider: Option<String>,
160}
161
162impl DecisionResponse {
163 pub fn answer(&self, id: &str) -> Result<&Answer> {
165 self.answers.get(id).ok_or_else(|| Error::MissingAnswer(id.to_owned()))
166 }
167
168 pub fn choice(&self, id: &str) -> Result<&ChoiceAnswer> {
170 match self.answer(id)? {
171 Answer::Choice(answer) => Ok(answer),
172 other => Err(wrong_type(id, "choice", other)),
173 }
174 }
175
176 pub fn score(&self, id: &str) -> Result<&ScoreAnswer> {
178 match self.answer(id)? {
179 Answer::Score(answer) => Ok(answer),
180 other => Err(wrong_type(id, "score", other)),
181 }
182 }
183
184 pub fn noul(&self, id: &str) -> Result<f64> {
186 match self.answer(id)? {
187 Answer::Noul(answer) => Ok(answer.noul),
188 other => Err(wrong_type(id, "noul", other)),
189 }
190 }
191}
192
193fn wrong_type(id: &str, expected: &'static str, found: &Answer) -> Error {
194 Error::WrongType { id: id.to_owned(), expected, found: found.kind().to_owned() }
195}
196
197#[derive(Debug, Clone, PartialEq)]
199pub enum Answer {
200 Choice(ChoiceAnswer),
201 Score(ScoreAnswer),
202 Noul(NoulAnswer),
203 Other(Value),
206}
207
208impl Answer {
209 pub fn kind(&self) -> &str {
211 match self {
212 Answer::Choice(_) => "choice",
213 Answer::Score(_) => "score",
214 Answer::Noul(_) => "noul",
215 Answer::Other(answer) => answer.get("type").and_then(Value::as_str).unwrap_or("an answer of another type"),
216 }
217 }
218
219 pub fn check(&self) -> std::result::Result<(), String> {
223 let probability = |what: String, value: f64| match value.is_finite() && (0.0..=1.0).contains(&value) {
224 true => Ok(()),
225 false => Err(format!("{what} is {value}, which isn't a probability from 0 to 1")),
226 };
227 let distribution = |values: Vec<(String, f64)>| {
228 for (what, value) in &values {
229 probability(what.clone(), *value)?;
230 }
231 let sum: f64 = values.iter().map(|(_, value)| value).sum();
232 let slack = (0.005 * values.len() as f64).max(0.01) + 1e-9;
234 match (sum - 1.0).abs() <= slack {
235 true => Ok(()),
236 false => Err(format!("its probabilities add up to {sum:.3}, not 1")),
237 }
238 };
239 match self {
240 Answer::Noul(answer) => probability("the probability of yes".to_owned(), answer.noul),
241 Answer::Choice(answer) => {
242 distribution(answer.probabilities.iter().map(|(option, value)| (format!("`{option}`'s probability"), *value)).collect())
243 }
244 Answer::Score(answer) => {
245 distribution(answer.probabilities.iter().map(|(level, value)| (format!("level {level}'s probability"), *value)).collect())
246 }
247 Answer::Other(_) => Ok(()),
248 }
249 }
250}
251
252#[derive(Serialize, Deserialize)]
255#[serde(tag = "type", rename_all = "lowercase")]
256enum Known<'a> {
257 Choice(Cow<'a, ChoiceAnswer>),
258 Score(Cow<'a, ScoreAnswer>),
259 Noul(Cow<'a, NoulAnswer>),
260}
261
262impl Serialize for Answer {
263 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
264 match self {
265 Answer::Choice(answer) => Known::Choice(Cow::Borrowed(answer)).serialize(serializer),
266 Answer::Score(answer) => Known::Score(Cow::Borrowed(answer)).serialize(serializer),
267 Answer::Noul(answer) => Known::Noul(Cow::Borrowed(answer)).serialize(serializer),
268 Answer::Other(answer) => answer.serialize(serializer),
269 }
270 }
271}
272
273impl<'de> Deserialize<'de> for Answer {
274 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Answer, D::Error> {
275 let answer = Value::deserialize(deserializer)?;
276 let known = match answer.get("type").and_then(Value::as_str) {
277 Some("choice" | "score" | "noul") => serde_json::from_value::<Known>(answer.clone()).map_err(D::Error::custom)?,
278 _ => return Ok(Answer::Other(answer)),
279 };
280 Ok(match known {
281 Known::Choice(answer) => Answer::Choice(answer.into_owned()),
282 Known::Score(answer) => Answer::Score(answer.into_owned()),
283 Known::Noul(answer) => Answer::Noul(answer.into_owned()),
284 })
285 }
286}
287
288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289pub struct ChoiceAnswer {
290 pub choice: String,
292 pub confidence: f64,
294 pub probabilities: BTreeMap<String, f64>,
296}
297
298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
299pub struct ScoreAnswer {
300 pub score: f64,
302 pub confidence: f64,
304 #[serde(deserialize_with = "by_level")]
306 pub probabilities: BTreeMap<u8, f64>,
307 #[serde(default, deserialize_with = "by_level")]
309 pub legend: BTreeMap<u8, Value>,
310}
311
312fn by_level<'de, D: Deserializer<'de>, T: Deserialize<'de>>(deserializer: D) -> std::result::Result<BTreeMap<u8, T>, D::Error> {
315 BTreeMap::<String, T>::deserialize(deserializer)?
316 .into_iter()
317 .map(|(level, value)| {
318 level.parse().map(|level| (level, value)).map_err(|_| D::Error::custom(format!("level {level:?} is not a number")))
319 })
320 .collect()
321}
322
323#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
324pub struct NoulAnswer {
325 pub noul: f64,
327}
328
329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
330pub struct Usage {
331 pub input_tokens: u64,
332 pub output_tokens: u64,
333 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub cost: Option<f64>,
336}