use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::ids::ArtifactId;
use crate::limits::BACKEND_OUTPUT_V1;
use crate::schema;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct BackendOutputDoc {
pub schema: String,
pub step: String,
pub instance: Vec<String>,
pub artifact: ArtifactId,
pub output: RawOutput,
}
crate::document::document!(BackendOutputDoc, schema::BACKEND_OUTPUT, BACKEND_OUTPUT_V1);
#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum RawOutput {
Ranking {
#[schemars(length(max = 1024))]
ids: Vec<String>,
scores: RankingScores,
},
Label(String),
Distribution(BTreeMap<String, f64>),
Logits(BTreeMap<String, f64>),
Scores(BTreeMap<String, f64>),
Screen {
blocked: bool,
categories: Vec<String>,
units: ScreenUnits,
},
TextRecord(BTreeMap<String, TextValue>),
TypedRecord {
fields: BTreeMap<String, serde_json::Value>,
logprobs: Vec<FieldLogprobs>,
#[serde(deserialize_with = "provider_reported")]
provider_reported: BTreeMap<String, String>,
},
}
#[derive(Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
enum RawOutputWire {
Ranking {
ids: Vec<String>,
scores: RankingScores,
},
Label(String),
Distribution(BTreeMap<String, f64>),
Logits(BTreeMap<String, f64>),
Scores(BTreeMap<String, f64>),
Screen {
blocked: bool,
categories: Vec<String>,
units: ScreenUnits,
},
TextRecord(BTreeMap<String, TextValue>),
TypedRecord {
fields: BTreeMap<String, serde_json::Value>,
logprobs: Vec<FieldLogprobs>,
#[serde(deserialize_with = "provider_reported")]
provider_reported: BTreeMap<String, String>,
},
}
impl<'de> Deserialize<'de> for RawOutput {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
Ok(match RawOutputWire::deserialize(d)? {
RawOutputWire::Ranking { ids, scores } => {
if ids.len() > 1024 || matches!(&scores, RankingScores::List(s) if s.len() > 1024) {
return Err(serde::de::Error::custom("ranking output bound exceeded"));
}
if let RankingScores::List(s) = &scores
&& (s.is_empty() || s.len() != ids.len() || s.iter().any(|n| !n.is_finite()))
{
return Err(serde::de::Error::custom(
"ranking scores must be nonempty, finite and match ids length",
));
}
Self::Ranking { ids, scores }
}
RawOutputWire::Label(v) => Self::Label(v),
RawOutputWire::Distribution(v) => Self::Distribution(v),
RawOutputWire::Logits(v) => Self::Logits(v),
RawOutputWire::Scores(v) => Self::Scores(v),
RawOutputWire::Screen {
blocked,
categories,
units,
} => Self::Screen {
blocked,
categories,
units,
},
RawOutputWire::TextRecord(v) => Self::TextRecord(v),
RawOutputWire::TypedRecord {
fields,
logprobs,
provider_reported,
} => Self::TypedRecord {
fields,
logprobs,
provider_reported,
},
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum RankingScores {
None,
#[serde(untagged)]
List(#[schemars(length(min = 1, max = 1024))] Vec<f64>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum ScreenUnits {
Reported { n: u64 },
NotReported,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum TextValue {
Text(String),
List(Vec<String>),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct FieldLogprobs {
pub field: String,
pub item: LogprobItem,
#[serde(deserialize_with = "finite_logprobs")]
pub values: BTreeMap<String, f64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum LogprobItem {
None,
#[serde(untagged)]
Index(u64),
}
fn finite_logprobs<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<BTreeMap<String, f64>, D::Error> {
let v = BTreeMap::<String, f64>::deserialize(d)?;
if v.values().any(|n| !n.is_finite()) {
return Err(serde::de::Error::custom("non-finite extraction logprob"));
}
Ok(v)
}
fn provider_reported<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<BTreeMap<String, String>, D::Error> {
let v = BTreeMap::<String, String>::deserialize(d)?;
if v.values().any(|s| s.len() > 256) {
return Err(serde::de::Error::custom(
"provider_reported exceeds 256 bytes",
));
}
Ok(v)
}