Skip to main content

remem/retrieval/rerank/
types.rs

1use serde::Serialize;
2
3use crate::perf::PhaseTiming;
4
5/// Closed set of reasons why the rerank stage did not publish a reranked
6/// order. Every reason maps to a stable machine-readable token that is shared
7/// by search explain, SessionStart diagnostics, logs, and doctor output.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum RerankDisabledReason {
10    /// Rerank is explicitly disabled by configuration.
11    Off,
12    /// The current path has no usable normalized query.
13    EmptyQuery,
14    /// Rerank is enabled but the model manifest is not installed.
15    ModelMissing,
16    /// Manifest, file bytes, or hashes failed verification.
17    ModelCorrupt,
18    /// Local runtime failed to load the verified model.
19    ModelLoadFailed,
20    /// Scoring failed (including non-finite scores).
21    InferenceFailed,
22    /// The approved per-request deadline elapsed before completion.
23    DeadlineExceeded,
24    /// The caller cancelled the request before scores were published.
25    Cancelled,
26}
27
28impl RerankDisabledReason {
29    pub fn as_str(self) -> &'static str {
30        match self {
31            Self::Off => "off",
32            Self::EmptyQuery => "empty_query",
33            Self::ModelMissing => "model_missing",
34            Self::ModelCorrupt => "model_corrupt",
35            Self::ModelLoadFailed => "model_load_failed",
36            Self::InferenceFailed => "inference_failed",
37            Self::DeadlineExceeded => "deadline_exceeded",
38            Self::Cancelled => "cancelled",
39        }
40    }
41
42    /// Reasons that represent a fail-visible error state (as opposed to an
43    /// intentional off/empty-query state).
44    pub fn is_error(self) -> bool {
45        !matches!(self, Self::Off | Self::EmptyQuery)
46    }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum RerankStatus {
51    Applied,
52    NotApplied {
53        disabled_reason: RerankDisabledReason,
54    },
55}
56
57/// One eligible candidate entering the shared rerank stage. Candidates must
58/// already have passed every project/owner/suppression/staleness rule of the
59/// calling path; the stage never recalls or re-admits memories.
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct RerankCandidate {
62    pub id: i64,
63    /// Stable 0-based rank in the caller's pre-rerank baseline order.
64    pub baseline_rank: usize,
65    /// Whether the memory carries the `verify-before-trust` source anchor.
66    /// After a successful rerank these candidates are hard-partitioned behind
67    /// all normal candidates.
68    pub verify_before_trust: bool,
69    /// Canonical bounded query-document projection text.
70    pub document: String,
71}
72
73/// Result of one rerank stage invocation. `ordered_ids` is only meaningful
74/// when `status == Applied`; on any failure the caller must keep the complete
75/// pre-rerank baseline order.
76#[derive(Debug, Clone, PartialEq)]
77pub struct RerankOutcome {
78    pub status: RerankStatus,
79    pub ordered_ids: Vec<i64>,
80    pub preset: Option<String>,
81    pub model_manifest_sha256: Option<String>,
82    pub input_count: usize,
83    pub output_count: usize,
84    pub top_n: usize,
85    pub top_k: usize,
86    pub timings: Vec<PhaseTiming>,
87}
88
89impl RerankOutcome {
90    pub fn not_applied(reason: RerankDisabledReason) -> Self {
91        Self {
92            status: RerankStatus::NotApplied {
93                disabled_reason: reason,
94            },
95            ordered_ids: vec![],
96            preset: None,
97            model_manifest_sha256: None,
98            input_count: 0,
99            output_count: 0,
100            top_n: 0,
101            top_k: 0,
102            timings: vec![],
103        }
104    }
105
106    pub fn applied(&self) -> bool {
107        matches!(self.status, RerankStatus::Applied)
108    }
109
110    pub fn disabled_reason(&self) -> Option<RerankDisabledReason> {
111        match self.status {
112            RerankStatus::Applied => None,
113            RerankStatus::NotApplied { disabled_reason } => Some(disabled_reason),
114        }
115    }
116
117    pub fn to_explain(&self, requested: bool) -> RerankExplain {
118        RerankExplain {
119            requested,
120            applied: self.applied(),
121            preset: self.preset.clone(),
122            model_manifest_sha256: self.model_manifest_sha256.clone(),
123            top_n: self.top_n,
124            top_k: self.top_k,
125            input_count: self.input_count,
126            output_count: self.output_count,
127            disabled_reason: self.disabled_reason().map(|reason| reason.as_str().into()),
128            timings: self.timings.clone(),
129        }
130    }
131}
132
133/// Serializable rerank diagnostics shared by search explain, service
134/// responses, and SessionStart evidence. Contains no query or memory content.
135#[derive(Debug, Clone, PartialEq, Serialize)]
136pub struct RerankExplain {
137    pub requested: bool,
138    pub applied: bool,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub preset: Option<String>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub model_manifest_sha256: Option<String>,
143    pub top_n: usize,
144    pub top_k: usize,
145    pub input_count: usize,
146    pub output_count: usize,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub disabled_reason: Option<String>,
149    pub timings: Vec<PhaseTiming>,
150}