use std::path::Path;
use candle_core::Device;
use candle_core::Tensor;
use serde::Deserialize;
use serde::Serialize;
use tokenizers::Tokenizer;
use super::EncodedBatch;
use super::PadToken;
use super::pad_id;
use super::tokenizer_error;
use crate::error::CanddingError;
use crate::error::LengthKind;
use crate::error::Result;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum PromptTemplate {
Qwen3Reranker,
}
const QWEN3_RERANKER_PREFIX: &str = "<|im_start|>system\nJudge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be \"yes\" or \"no\".<|im_end|>\n<|im_start|>user\n";
const QWEN3_RERANKER_SUFFIX: &str = "<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n";
impl PromptTemplate {
pub fn name(self) -> &'static str {
match self {
PromptTemplate::Qwen3Reranker => "qwen3_reranker",
}
}
pub fn prefix(self) -> &'static str {
match self {
PromptTemplate::Qwen3Reranker => QWEN3_RERANKER_PREFIX,
}
}
pub fn suffix(self) -> &'static str {
match self {
PromptTemplate::Qwen3Reranker => QWEN3_RERANKER_SUFFIX,
}
}
pub fn content(self, instruction: &str, query: &str, document: &str) -> String {
match self {
PromptTemplate::Qwen3Reranker => {
format!("<Instruct>: {instruction}\n<Query>: {query}\n<Document>: {document}")
}
}
}
pub fn render(self, instruction: &str, query: &str, document: &str) -> String {
[
self.prefix(),
&self.content(instruction, query, document),
self.suffix(),
]
.concat()
}
}
pub struct PromptTokenizer {
inner: Tokenizer,
template: PromptTemplate,
max_length: usize,
pad_id: u32,
prefix_len: usize,
suffix_ids: Vec<u32>,
}
impl PromptTokenizer {
pub fn from_file(
path: &Path,
max_length: usize,
pad: PadToken,
template: PromptTemplate,
) -> Result<Self> {
let mut inner = Tokenizer::from_file(path).map_err(tokenizer_error)?;
inner.with_padding(None);
inner.with_truncation(None).map_err(tokenizer_error)?;
let prefix_len = encode(&inner, template.prefix())?.len();
let suffix_ids = encode(&inner, template.suffix())?;
let minimum = prefix_len + suffix_ids.len() + 1;
if max_length < minimum {
return Err(CanddingError::LengthTooSmall {
requested: max_length,
minimum,
kind: LengthKind::Prompt {
template,
prefix: prefix_len,
suffix: suffix_ids.len(),
},
});
}
let pad_id = pad_id(&inner, pad);
Ok(Self {
inner,
template,
max_length,
pad_id,
prefix_len,
suffix_ids,
})
}
pub fn max_length(&self) -> usize {
self.max_length
}
pub fn template(&self) -> PromptTemplate {
self.template
}
pub fn prefix_len(&self) -> usize {
self.prefix_len
}
pub fn suffix_ids(&self) -> &[u32] {
&self.suffix_ids
}
pub fn prompt_ids(&self, instruction: &str, query: &str, document: &str) -> Result<Vec<u32>> {
self.rendered_ids(&self.template.render(instruction, query, document))
}
pub(super) fn rendered_ids(&self, rendered: &str) -> Result<Vec<u32>> {
let ids = encode(&self.inner, rendered)?;
if !ids.ends_with(&self.suffix_ids) {
let tail = ids.len().saturating_sub(self.suffix_ids.len());
return Err(CanddingError::PromptSuffixMismatch {
template: self.template.name().to_string(),
expected: self.suffix_ids.clone(),
found: ids[tail..].to_vec(),
});
}
Ok(cut(ids, self.max_length, &self.suffix_ids))
}
pub fn encode(
&self,
instruction: &str,
pairs: &[(&str, &str)],
device: &Device,
) -> Result<EncodedBatch> {
let rows = pairs
.iter()
.map(|&(query, document)| self.prompt_ids(instruction, query, document))
.collect::<Result<Vec<_>>>()?;
rows_to_batch(&rows, self.pad_id, device)
}
pub fn count(&self, instruction: &str, pairs: &[(&str, &str)]) -> Result<Vec<usize>> {
pairs
.iter()
.map(|&(query, document)| Ok(self.prompt_ids(instruction, query, document)?.len()))
.collect()
}
}
fn encode(tokenizer: &Tokenizer, text: &str) -> Result<Vec<u32>> {
let encoding = tokenizer.encode(text, false).map_err(tokenizer_error)?;
Ok(encoding.get_ids().to_vec())
}
fn cut(mut ids: Vec<u32>, max_length: usize, suffix: &[u32]) -> Vec<u32> {
if ids.len() > max_length {
ids.truncate(max_length - suffix.len());
ids.extend_from_slice(suffix);
}
ids
}
fn rows_to_batch(rows: &[Vec<u32>], pad_id: u32, device: &Device) -> Result<EncodedBatch> {
let batch = rows.len();
let seq = rows.iter().map(Vec::len).max().unwrap_or(0);
let mut ids = Vec::with_capacity(batch * seq);
let mut mask = Vec::with_capacity(batch * seq);
for row in rows {
let padding = seq - row.len();
ids.extend_from_slice(row);
ids.extend(std::iter::repeat_n(pad_id, padding));
mask.extend(std::iter::repeat_n(1u32, row.len()));
mask.extend(std::iter::repeat_n(0u32, padding));
}
let input_ids = Tensor::from_vec(ids, (batch, seq), device)?;
Ok(EncodedBatch {
token_type_ids: input_ids.zeros_like()?,
attention_mask: Tensor::from_vec(mask, (batch, seq), device)?,
input_ids,
lengths: rows.iter().map(Vec::len).collect(),
})
}