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}