//! Cross-encoder reranking via `score_binary_tokens`: a Qwen3-Reranker (or any //! instruct model) scores a (query, document) pair by the yes-vs-no logit after a //! single forward pass. Ranks relevant documents above irrelevant ones. //! //! Ignored by default (needs a real GGUF). Run with: //! JOULE_RERANK=/path/qwen3-reranker-0.6b-q8_0.gguf \ //! cargo test -p jouleclaw-runtime --test real_reranker -- --ignored --nocapture use jouleclaw_loader_gguf::read_gguf_file; use jouleclaw_loader_gguf::tokenizer::Vocab; use jouleclaw_runtime::chat::encode_with_specials; use jouleclaw_runtime::generate::score_binary_tokens; fn rerank_prompt(instruct: &str, query: &str, doc: &str) -> String { // Qwen3-Reranker format WITHOUT the reasoning `` block — the 0.6B GGUF // predicts yes/no directly; the think block derails it. format!("<|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: {instruct}\n: {query}\n: {doc}<|im_end|>\n<|im_start|>assistant\n") } #[test] #[ignore] fn reranker_ranks_relevant_above_irrelevant() { let path = std::env::var("JOULE_RERANK").expect("set JOULE_RERANK to a reranker gguf"); let model = read_gguf_file(&path).expect("read"); let vocab = Vocab::from_gguf(&model).expect("vocab"); let yes = *vocab.token_to_id.get("yes").expect("yes token"); let no = *vocab.token_to_id.get("no").expect("no token"); let instruct = "Given a code search query, find the relevant source file"; let query = "how to invalidate the cache"; let score = |doc: &str| { let toks = encode_with_specials(&vocab, &rerank_prompt(instruct, query, doc), &["<|im_start|>", "<|im_end|>"], false); score_binary_tokens(&model, &vocab, &toks, yes, no).expect("score") }; let relevant = score("fn cache_invalidate() { self.entries.clear(); }"); let irrelevant = score("struct Color { r: u8, g: u8, b: u8 }"); eprintln!("relevant={relevant:.3} irrelevant={irrelevant:.3}"); assert!(relevant > irrelevant, "reranker must rank the cache fn above the color struct"); }