khive_runtime/
embedding_reranker.rs1use std::collections::{BTreeMap, BTreeSet};
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use khive_retrieval::error::RetrievalError;
8use khive_retrieval::hybrid::Reranker;
9use khive_score::{cmp_desc_then_id, DeterministicScore};
10use uuid::Uuid;
11
12use crate::{KhiveRuntime, NamespaceToken, RuntimeError, RuntimeResult};
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct EmbeddingRerankHit {
17 pub id: Uuid,
18 pub score: DeterministicScore,
19 pub missing_vector: bool,
22}
23
24#[derive(Clone)]
35pub struct EmbeddingCosineReranker {
36 runtime: Arc<KhiveRuntime>,
37 token: NamespaceToken,
38 engine: String,
39}
40
41impl EmbeddingCosineReranker {
42 pub fn new(
45 runtime: Arc<KhiveRuntime>,
46 token: NamespaceToken,
47 engine: impl Into<String>,
48 ) -> RuntimeResult<Self> {
49 let engine = engine.into();
50 let (engine, _) = runtime.vector_model_metadata(&engine)?;
51 Ok(Self {
52 runtime,
53 token,
54 engine,
55 })
56 }
57
58 pub async fn rerank_detailed(
70 &self,
71 query: &str,
72 results: Vec<(Uuid, DeterministicScore)>,
73 top_k: usize,
74 ) -> RuntimeResult<Vec<EmbeddingRerankHit>> {
75 let ids: Vec<_> = results
76 .iter()
77 .map(|(id, _)| *id)
78 .collect::<BTreeSet<_>>()
79 .into_iter()
80 .collect();
81 let count = u32::try_from(ids.len()).map_err(|_| {
82 RuntimeError::InvalidInput("rerank candidate count exceeds u32::MAX".into())
83 })?;
84 let query_vector = self
85 .runtime
86 .embed_query_with_model_for_token(&self.token, &self.engine, query)
87 .await?;
88 let scores: BTreeMap<_, _> = self
89 .runtime
90 .rerank_in(&self.token, &self.engine, &query_vector, &ids, count)
91 .await?
92 .into_iter()
93 .map(|hit| (hit.subject_id, hit.score))
94 .collect();
95 let mut hits: Vec<_> = results
96 .into_iter()
97 .enumerate()
98 .map(|(position, (id, incoming_score))| {
99 let score = scores.get(&id).copied();
100 (
101 position,
102 EmbeddingRerankHit {
103 id,
104 score: score.unwrap_or(incoming_score),
105 missing_vector: score.is_none(),
106 },
107 )
108 })
109 .collect();
110 hits.sort_by(|(a_position, a), (b_position, b)| {
111 cmp_desc_then_id(a.score, &a.id, b.score, &b.id)
112 .then_with(|| a_position.cmp(b_position))
113 });
114 Ok(hits.into_iter().take(top_k).map(|(_, hit)| hit).collect())
115 }
116}
117
118#[async_trait]
119impl Reranker<Uuid> for EmbeddingCosineReranker {
120 async fn rerank(
121 &self,
122 query: &str,
123 results: Vec<(Uuid, DeterministicScore)>,
124 top_k: usize,
125 ) -> khive_retrieval::error::Result<Vec<(Uuid, DeterministicScore)>> {
126 self.rerank_detailed(query, results, top_k)
127 .await
128 .map(|hits| hits.into_iter().map(|hit| (hit.id, hit.score)).collect())
129 .map_err(|error| RetrievalError::Rerank(error.to_string()))
130 }
131}