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
220#[derive(Serialize, Deserialize)]
223#[serde(tag = "type", rename_all = "lowercase")]
224enum Known<'a> {
225 Choice(Cow<'a, ChoiceAnswer>),
226 Score(Cow<'a, ScoreAnswer>),
227 Noul(Cow<'a, NoulAnswer>),
228}
229
230impl Serialize for Answer {
231 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
232 match self {
233 Answer::Choice(answer) => Known::Choice(Cow::Borrowed(answer)).serialize(serializer),
234 Answer::Score(answer) => Known::Score(Cow::Borrowed(answer)).serialize(serializer),
235 Answer::Noul(answer) => Known::Noul(Cow::Borrowed(answer)).serialize(serializer),
236 Answer::Other(answer) => answer.serialize(serializer),
237 }
238 }
239}
240
241impl<'de> Deserialize<'de> for Answer {
242 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Answer, D::Error> {
243 let answer = Value::deserialize(deserializer)?;
244 let known = match answer.get("type").and_then(Value::as_str) {
245 Some("choice" | "score" | "noul") => serde_json::from_value::<Known>(answer.clone()).map_err(D::Error::custom)?,
246 _ => return Ok(Answer::Other(answer)),
247 };
248 Ok(match known {
249 Known::Choice(answer) => Answer::Choice(answer.into_owned()),
250 Known::Score(answer) => Answer::Score(answer.into_owned()),
251 Known::Noul(answer) => Answer::Noul(answer.into_owned()),
252 })
253 }
254}
255
256#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
257pub struct ChoiceAnswer {
258 pub choice: String,
260 pub confidence: f64,
262 pub probabilities: BTreeMap<String, f64>,
264}
265
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
267pub struct ScoreAnswer {
268 pub score: f64,
270 pub confidence: f64,
272 #[serde(deserialize_with = "by_level")]
274 pub probabilities: BTreeMap<u8, f64>,
275 #[serde(default, deserialize_with = "by_level")]
277 pub legend: BTreeMap<u8, Value>,
278}
279
280fn by_level<'de, D: Deserializer<'de>, T: Deserialize<'de>>(deserializer: D) -> std::result::Result<BTreeMap<u8, T>, D::Error> {
283 BTreeMap::<String, T>::deserialize(deserializer)?
284 .into_iter()
285 .map(|(level, value)| {
286 level.parse().map(|level| (level, value)).map_err(|_| D::Error::custom(format!("level {level:?} is not a number")))
287 })
288 .collect()
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292pub struct NoulAnswer {
293 pub noul: f64,
295}
296
297#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
298pub struct Usage {
299 pub input_tokens: u64,
300 pub output_tokens: u64,
301 #[serde(default, skip_serializing_if = "Option::is_none")]
303 pub cost: Option<f64>,
304}