use std::borrow::Cow;
use serde::Deserialize;
use serde::Serialize;
use crate::error::CanddingError;
use crate::error::Result;
use crate::tokenizer::PairTruncationPolicy;
use crate::tokenizer::PromptTemplate;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[non_exhaustive]
pub enum RerankInput {
Pair { truncation: PairTruncationPolicy },
Prompt {
template: PromptTemplate,
default_instruction: Cow<'static, str>,
},
Windowed { query_cap: usize, overlap: usize },
}
impl RerankInput {
pub fn pair_truncation(&self) -> Option<PairTruncationPolicy> {
match self {
RerankInput::Pair { truncation } => Some(*truncation),
RerankInput::Prompt { .. } | RerankInput::Windowed { .. } => None,
}
}
pub fn template(&self) -> Option<PromptTemplate> {
match self {
RerankInput::Prompt { template, .. } => Some(*template),
RerankInput::Pair { .. } | RerankInput::Windowed { .. } => None,
}
}
pub fn default_instruction(&self) -> Option<&str> {
match self {
RerankInput::Prompt {
default_instruction,
..
} => Some(default_instruction),
RerankInput::Pair { .. } | RerankInput::Windowed { .. } => None,
}
}
pub fn windows(&self) -> Option<(usize, usize)> {
match self {
RerankInput::Windowed { query_cap, overlap } => Some((*query_cap, *overlap)),
RerankInput::Pair { .. } | RerankInput::Prompt { .. } => None,
}
}
pub fn check_instruction(&self, model: &str, instruction: Option<&str>) -> Result<()> {
match (self, instruction) {
(RerankInput::Pair { .. } | RerankInput::Windowed { .. }, Some(_)) => {
Err(CanddingError::InstructionOnPairInput {
model: model.to_string(),
})
}
_ => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_pair_input_serializes_with_its_kind_and_truncation() {
let input = RerankInput::Pair {
truncation: PairTruncationPolicy::PreserveQueryToThreeQuarters,
};
let json = serde_json::to_value(&input).unwrap();
assert_eq!(
json,
serde_json::json!({"kind": "pair", "truncation": "preserve_query_to_three_quarters"})
);
assert_eq!(serde_json::from_value::<RerankInput>(json).unwrap(), input);
assert_eq!(
input.pair_truncation(),
Some(PairTruncationPolicy::PreserveQueryToThreeQuarters)
);
assert_eq!(input.template(), None);
assert_eq!(input.default_instruction(), None);
}
#[test]
fn a_prompt_input_serializes_with_its_kind_template_and_default_instruction() {
let input = RerankInput::Prompt {
template: PromptTemplate::Qwen3Reranker,
default_instruction: Cow::Borrowed("Judge it"),
};
let json = serde_json::to_value(&input).unwrap();
assert_eq!(
json,
serde_json::json!({"kind": "prompt", "template": "qwen3_reranker", "default_instruction": "Judge it"})
);
assert_eq!(serde_json::from_value::<RerankInput>(json).unwrap(), input);
assert_eq!(input.pair_truncation(), None);
assert_eq!(input.template(), Some(PromptTemplate::Qwen3Reranker));
assert_eq!(input.default_instruction(), Some("Judge it"));
}
#[test]
fn a_windowed_input_serializes_with_its_kind_query_cap_and_overlap() {
let input = RerankInput::Windowed {
query_cap: 512,
overlap: 80,
};
let json = serde_json::to_value(&input).unwrap();
assert_eq!(
json,
serde_json::json!({"kind": "windowed", "query_cap": 512, "overlap": 80})
);
assert_eq!(serde_json::from_value::<RerankInput>(json).unwrap(), input);
assert_eq!(input.windows(), Some((512, 80)));
assert_eq!(input.pair_truncation(), None);
assert_eq!(input.template(), None);
assert_eq!(input.default_instruction(), None);
}
#[test]
fn a_windowed_input_refuses_an_instruction() {
let input = RerankInput::Windowed {
query_cap: 512,
overlap: 80,
};
for instruction in ["judge it", ""] {
assert!(matches!(
input.check_instruction("m", Some(instruction)),
Err(CanddingError::InstructionOnPairInput { .. })
));
}
assert!(input.check_instruction("m", None).is_ok());
}
#[test]
fn a_template_name_is_its_serde_name() {
let template = PromptTemplate::Qwen3Reranker;
assert_eq!(
serde_json::to_value(template).unwrap(),
serde_json::Value::String(template.name().to_string())
);
}
}