memvid_core/enrich/
engine.rs

1//! Enrichment engine trait and context types.
2//!
3//! The `EnrichmentEngine` trait defines the interface that all enrichment
4//! engines must implement. Each engine processes frames and produces
5//! structured memory cards.
6
7use crate::error::Result;
8use crate::types::{FrameId, MemoryCard};
9
10/// Context provided to enrichment engines during processing.
11#[derive(Debug, Clone)]
12pub struct EnrichmentContext {
13    /// The frame ID being processed.
14    pub frame_id: FrameId,
15    /// The frame's URI (e.g., "mv2://session-1/msg-5").
16    pub uri: String,
17    /// The frame's text content.
18    pub text: String,
19    /// The frame's title (if any).
20    pub title: Option<String>,
21    /// The frame's timestamp (Unix seconds).
22    pub timestamp: i64,
23    /// Optional metadata from the frame.
24    pub metadata: Option<String>,
25}
26
27impl EnrichmentContext {
28    /// Create a new enrichment context.
29    #[must_use]
30    pub fn new(
31        frame_id: FrameId,
32        uri: String,
33        text: String,
34        title: Option<String>,
35        timestamp: i64,
36        metadata: Option<String>,
37    ) -> Self {
38        Self {
39            frame_id,
40            uri,
41            text,
42            title,
43            timestamp,
44            metadata,
45        }
46    }
47}
48
49/// Result of running an enrichment engine on a frame.
50#[derive(Debug, Clone, Default)]
51pub struct EnrichmentResult {
52    /// Memory cards extracted from the frame.
53    pub cards: Vec<MemoryCard>,
54    /// Whether the engine successfully processed the frame.
55    /// Even if no cards were extracted, this can be true.
56    pub success: bool,
57    /// Optional error message if processing failed.
58    pub error: Option<String>,
59}
60
61impl EnrichmentResult {
62    /// Create a successful result with cards.
63    #[must_use]
64    pub fn success(cards: Vec<MemoryCard>) -> Self {
65        Self {
66            cards,
67            success: true,
68            error: None,
69        }
70    }
71
72    /// Create an empty successful result (no cards extracted).
73    #[must_use]
74    pub fn empty() -> Self {
75        Self {
76            cards: Vec::new(),
77            success: true,
78            error: None,
79        }
80    }
81
82    /// Create a failed result.
83    #[must_use]
84    pub fn failed(error: impl Into<String>) -> Self {
85        Self {
86            cards: Vec::new(),
87            success: false,
88            error: Some(error.into()),
89        }
90    }
91}
92
93/// Trait for enrichment engines that process frames and extract memory cards.
94///
95/// Engines are identified by a kind (e.g., "rules", "llm:phi-3.5-mini") and
96/// a version string. The combination allows tracking which frames have been
97/// processed by which engine versions.
98///
99/// # Example
100///
101/// ```ignore
102/// use memvid_core::enrich::{EnrichmentEngine, EnrichmentContext, EnrichmentResult};
103///
104/// struct MyEngine;
105///
106/// impl EnrichmentEngine for MyEngine {
107///     fn kind(&self) -> &str { "my-engine" }
108///     fn version(&self) -> &str { "1.0.0" }
109///
110///     fn enrich(&self, ctx: &EnrichmentContext) -> EnrichmentResult {
111///         // Extract memory cards from ctx.text
112///         EnrichmentResult::empty()
113///     }
114/// }
115/// ```
116pub trait EnrichmentEngine: Send + Sync {
117    /// Return the engine kind identifier (e.g., "rules", "llm:phi-3.5-mini").
118    fn kind(&self) -> &str;
119
120    /// Return the engine version string (e.g., "1.0.0").
121    fn version(&self) -> &str;
122
123    /// Process a frame and extract memory cards.
124    ///
125    /// The engine receives the frame's text content and metadata via the
126    /// `EnrichmentContext` and should return any extracted memory cards.
127    fn enrich(&self, ctx: &EnrichmentContext) -> EnrichmentResult;
128
129    /// Initialize the engine (e.g., load models).
130    ///
131    /// This is called once before processing begins. Engines that need
132    /// to load models or other resources should do so here.
133    fn init(&mut self) -> Result<()> {
134        Ok(())
135    }
136
137    /// Check if the engine is ready for processing.
138    ///
139    /// Returns true if init() has been called and the engine is ready.
140    fn is_ready(&self) -> bool {
141        true
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    struct TestEngine;
150
151    impl EnrichmentEngine for TestEngine {
152        fn kind(&self) -> &str {
153            "test"
154        }
155        fn version(&self) -> &str {
156            "1.0.0"
157        }
158        fn enrich(&self, _ctx: &EnrichmentContext) -> EnrichmentResult {
159            EnrichmentResult::empty()
160        }
161    }
162
163    #[test]
164    fn test_enrichment_context() {
165        let ctx = EnrichmentContext::new(
166            42,
167            "mv2://test/msg-1".to_string(),
168            "Hello, I work at Anthropic.".to_string(),
169            Some("Test".to_string()),
170            1700000000,
171            None,
172        );
173        assert_eq!(ctx.frame_id, 42);
174        assert_eq!(ctx.uri, "mv2://test/msg-1");
175    }
176
177    #[test]
178    fn test_enrichment_result() {
179        let success = EnrichmentResult::success(vec![]);
180        assert!(success.success);
181        assert!(success.error.is_none());
182
183        let empty = EnrichmentResult::empty();
184        assert!(empty.success);
185        assert!(empty.cards.is_empty());
186
187        let failed = EnrichmentResult::failed("test error");
188        assert!(!failed.success);
189        assert_eq!(failed.error, Some("test error".to_string()));
190    }
191
192    #[test]
193    fn test_engine_trait() {
194        let engine = TestEngine;
195        assert_eq!(engine.kind(), "test");
196        assert_eq!(engine.version(), "1.0.0");
197        assert!(engine.is_ready());
198    }
199}