Skip to main content

ratel_ai_core/
artifact_warm.rs

1//! Registry-level policy and errors for warming dense embeddings from a
2//! build-time artifact — shared by [`crate::ToolRegistry`] and
3//! [`crate::SkillRegistry`].
4
5use std::fmt;
6use std::str::FromStr;
7
8use crate::dense_cache::WarmError;
9use crate::embedding::EmbedderError;
10
11/// What to do when some corpus ids are not covered by the artifact.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum OnArtifactMiss {
14    /// Fail if any corpus id was not reused from the artifact.
15    Error,
16    /// Call [`crate::ToolRegistry::build_embeddings`] /
17    /// [`crate::SkillRegistry::build_embeddings`] to embed only the still-missing ids.
18    Embed,
19}
20
21impl OnArtifactMiss {
22    /// Stable SDK identifier: `"error"` or `"embed"`.
23    #[must_use]
24    pub fn as_str(self) -> &'static str {
25        match self {
26            OnArtifactMiss::Error => "error",
27            OnArtifactMiss::Embed => "embed",
28        }
29    }
30}
31
32/// Rejected [`OnArtifactMiss`] string from the SDK binding.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct ParseOnArtifactMissError(pub String);
35
36impl fmt::Display for ParseOnArtifactMissError {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(
39            f,
40            "unknown on-artifact-miss policy {:?} (expected \"error\" or \"embed\")",
41            self.0
42        )
43    }
44}
45
46impl std::error::Error for ParseOnArtifactMissError {}
47
48impl FromStr for OnArtifactMiss {
49    type Err = ParseOnArtifactMissError;
50
51    /// Parse the SDK identifier: `"error"` or `"embed"`.
52    ///
53    /// # Errors
54    ///
55    /// Any other string is a [`ParseOnArtifactMissError`] naming the rejected
56    /// input.
57    fn from_str(s: &str) -> Result<Self, Self::Err> {
58        match s {
59            "error" => Ok(OnArtifactMiss::Error),
60            "embed" => Ok(OnArtifactMiss::Embed),
61            other => Err(ParseOnArtifactMissError(other.to_string())),
62        }
63    }
64}
65
66/// Failure of [`crate::ToolRegistry::warm_embeddings_from_artifact`] /
67/// [`crate::SkillRegistry::warm_embeddings_from_artifact`].
68#[derive(Debug, Clone)]
69pub enum ArtifactWarmError {
70    /// Parse, RAT1 header mismatch, or wrapped embedder error from warm.
71    Warm(WarmError),
72    /// Policy Error: corpus ids not covered by the artifact.
73    Incomplete {
74        /// Corpus ids that were not reused from the artifact.
75        missing: Vec<String>,
76    },
77    /// Policy Embed: failure from the follow-up [`build_embeddings`](crate::ToolRegistry::build_embeddings).
78    Embedder(EmbedderError),
79}
80
81impl fmt::Display for ArtifactWarmError {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            ArtifactWarmError::Warm(e) => write!(f, "{e}"),
85            ArtifactWarmError::Incomplete { missing } => write!(
86                f,
87                "embedding artifact incomplete for the current corpus: {} id(s) missing ({})",
88                missing.len(),
89                missing.join(", ")
90            ),
91            ArtifactWarmError::Embedder(e) => write!(f, "{e}"),
92        }
93    }
94}
95
96impl std::error::Error for ArtifactWarmError {}
97
98impl From<WarmError> for ArtifactWarmError {
99    fn from(value: WarmError) -> Self {
100        Self::Warm(value)
101    }
102}
103
104impl From<EmbedderError> for ArtifactWarmError {
105    fn from(value: EmbedderError) -> Self {
106        Self::Embedder(value)
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn on_artifact_miss_round_trips_through_str() {
116        for policy in [OnArtifactMiss::Error, OnArtifactMiss::Embed] {
117            assert_eq!(policy.as_str().parse::<OnArtifactMiss>().unwrap(), policy);
118        }
119    }
120
121    #[test]
122    fn on_artifact_miss_rejects_unknown() {
123        assert!("reuse".parse::<OnArtifactMiss>().is_err());
124    }
125
126    #[test]
127    fn incomplete_display_lists_missing_ids() {
128        let incomplete = ArtifactWarmError::Incomplete {
129            missing: vec!["a".into(), "b".into()],
130        };
131        let message = incomplete.to_string();
132        assert!(message.contains("embedding artifact incomplete for the current corpus"));
133        assert!(message.contains("a, b"));
134    }
135}