use crate::openai; use crate::server::AppState; use axum::Json; use axum::extract::State; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::response::Response; use axum_extra::routing::TypedPath; use serde::Deserialize; use serde_with::DeserializeFromStr; use serde_with::SerializeDisplay; use std::collections::HashMap; use std::hash::RandomState; use strum::EnumString; use strum::VariantArray; #[derive(Deserialize, TypedPath)] #[typed_path("/v1/decisions")] pub struct V1DecisionsPath; #[derive( Clone, Debug, EnumString, DeserializeFromStr, strum::Display, SerializeDisplay, VariantArray, )] #[strum(serialize_all = "lowercase")] pub enum Shortcut { S, M, } pub struct DecisionsResponse(openai::decisions::DecisionResponse); impl IntoResponse for DecisionsResponse { fn into_response(self) -> Response { let Self(embeddings) = self; Json(embeddings).into_response() } } pub enum DecisionsError { NoneSucceeded, } impl IntoResponse for DecisionsError { fn into_response(self) -> Response { match self { DecisionsError::NoneSucceeded => StatusCode::SERVICE_UNAVAILABLE.into_response(), } } } #[tracing::instrument(skip(app_state, request))] pub async fn create_decision( _: V1DecisionsPath, State(app_state): State, Json(request): Json>, ) -> Result { use super::system_one; let openai::decisions::DecisionRequest { model, input, questions, } = request; let model = match model { Shortcut::S => system_one::Shortcut::S, Shortcut::M => system_one::Shortcut::M, }; let (state, images, files) = match input { openai::decisions::DecisionInput::Messages(messages) => { let mut images = vec![]; let mut texts = vec![]; for openai::decisions::DecisionInputMessage { role, content } in messages { match content { openai::decisions::DecisionInputContent::Text(text) => texts.push(text), openai::decisions::DecisionInputContent::Parts(decision_input_parts) => { for decision_input_part in decision_input_parts { match decision_input_part { openai::decisions::DecisionInputPart::Image { image_url, detail } => images.push(image_url), openai::decisions::DecisionInputPart::Text { text } => texts.push(text), } } }, } }; let text = texts.join("\n\n"); let files = images.clone(); (openai::system_one::State::Text(text), Some(images), Some(files)) } openai::decisions::DecisionInput::Text(text) => (openai::system_one::State::Text(text), None, None), }; let questions = questions.into_iter().map(|question| match question { openai::decisions::DecisionQuestion::Predicate(predicate_question) => todo!(), openai::decisions::DecisionQuestion::Choice(choice_question) => todo!(), openai::decisions::DecisionQuestion::Score(score_question) => todo!(), } ).collect(); let request = openai::system_one::SystemOneRequest { model, state, files, images, questions, }; system_one::create_system_one(system_one::V1SystemOnePath, State(app_state), Json(request)) .await .map( |system_one::SystemOneResponse(openai::system_one::SystemOneResponse { model, answers, usage, })| { let answers = answers .into_iter() .map(|(id, answer)| match answer { openai::system_one::Answer::Noul(openai::system_one::NoulAnswer { noul, }) => openai::decisions::DecisionAnswer::Predicate( openai::decisions::PredicateAnswer { name: Some(id), probability: noul, }, ), openai::system_one::Answer::Choice(openai::system_one::ChoiceAnswer { choice, probabilities, confidence, }) => openai::decisions::DecisionAnswer::Choice( openai::decisions::ChoiceAnswer { name: Some(id), choice: openai::decisions::Choice::String(choice), confidence, probabilities: probabilities .into_iter() .map(|(choice_id, probability)| { openai::decisions::ChoiceProbability { value: openai::decisions::Choice::String(choice_id), probability, } }) .collect(), }, ), openai::system_one::Answer::Score(openai::system_one::ScoreAnswer { score, legend, probabilities, confidence, }) => openai::decisions::DecisionAnswer::Score( openai::decisions::ScoreAnswer { name: Some(id), score, confidence, probabilities: { let reverse_legend = HashMap::<_, _, RandomState>::from_iter( legend.into_iter().map(|(index, value)| { ( value, index .parse::() .expect("this should be an integer, I believe"), ) }), ); probabilities .into_iter() .map(|(choice_id, probability)| { openai::decisions::ScoreProbability { value: reverse_legend .get(&choice_id) .expect("it should exist, I think") .clone(), label: choice_id, probability, } }) .collect() }, }, ), }) .collect(); let openai::system_one::Usage { input_tokens, output_tokens, } = usage; let input_tokens = input_tokens as i32; let output_tokens = output_tokens as i32; let total_tokens = input_tokens + output_tokens; let input_tokens_details = openai::decisions::TokenUsageDetails { cache_write_tokens: 0, cached_tokens: 0, }; let output_tokens_details = openai::decisions::OutputTokenDetails { reasoning_tokens: 0, }; let usage = openai::decisions::TokenUsage { input_tokens, input_tokens_details, output_tokens, output_tokens_details, total_tokens, }; DecisionsResponse(openai::decisions::DecisionResponse { model, answers, usage, }) }, ) .map_err(|_| DecisionsError::NoneSucceeded) }