Skip to main content

batuta/oracle/cookbook/
mod.rs

1//! Cookbook - Practical recipes for common Sovereign AI Stack patterns
2//!
3//! Each recipe includes:
4//! - Problem description
5//! - Components involved
6//! - Code example
7//! - Related recipes
8
9mod recipes;
10mod recipes_more;
11mod recipes_rlhf_alignment;
12mod recipes_rlhf_efficiency;
13mod recipes_rlhf_training;
14
15use serde::{Deserialize, Serialize};
16
17/// A cookbook recipe for a common pattern
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct Recipe {
20    /// Unique recipe ID
21    pub id: String,
22    /// Recipe title
23    pub title: String,
24    /// Problem this recipe solves
25    pub problem: String,
26    /// Components used
27    pub components: Vec<String>,
28    /// Tags for discovery
29    pub tags: Vec<String>,
30    /// Complete code example
31    pub code: String,
32    /// TDD test companion for the code example
33    pub test_code: String,
34    /// Related recipe IDs
35    pub related: Vec<String>,
36}
37
38impl Recipe {
39    pub fn new(id: impl Into<String>, title: impl Into<String>) -> Self {
40        Self {
41            id: id.into(),
42            title: title.into(),
43            problem: String::new(),
44            components: Vec::new(),
45            tags: Vec::new(),
46            code: String::new(),
47            test_code: String::new(),
48            related: Vec::new(),
49        }
50    }
51
52    pub fn with_problem(mut self, problem: impl Into<String>) -> Self {
53        self.problem = problem.into();
54        self
55    }
56
57    pub fn with_components(mut self, components: Vec<&str>) -> Self {
58        self.components = components.into_iter().map(String::from).collect();
59        self
60    }
61
62    pub fn with_tags(mut self, tags: Vec<&str>) -> Self {
63        self.tags = tags.into_iter().map(String::from).collect();
64        self
65    }
66
67    pub fn with_code(mut self, code: impl Into<String>) -> Self {
68        self.code = code.into();
69        self
70    }
71
72    pub fn with_test_code(mut self, test_code: impl Into<String>) -> Self {
73        self.test_code = test_code.into();
74        self
75    }
76
77    pub fn with_related(mut self, related: Vec<&str>) -> Self {
78        self.related = related.into_iter().map(String::from).collect();
79        self
80    }
81}
82
83/// Cookbook containing all recipes
84#[derive(Debug, Clone, Default)]
85pub struct Cookbook {
86    pub(crate) recipes: Vec<Recipe>,
87}
88
89impl Cookbook {
90    /// Create a new empty cookbook
91    pub fn new() -> Self {
92        Self::default()
93    }
94
95    /// Create cookbook with all standard recipes
96    pub fn standard() -> Self {
97        let mut cookbook = Self::new();
98        recipes::register_all(&mut cookbook);
99        cookbook
100    }
101
102    /// Get all recipes
103    pub fn recipes(&self) -> &[Recipe] {
104        &self.recipes
105    }
106
107    /// Find recipes by tag
108    pub fn find_by_tag(&self, tag: &str) -> Vec<&Recipe> {
109        self.recipes.iter().filter(|r| r.tags.iter().any(|t| t.eq_ignore_ascii_case(tag))).collect()
110    }
111
112    /// Find recipes by component
113    pub fn find_by_component(&self, component: &str) -> Vec<&Recipe> {
114        self.recipes
115            .iter()
116            .filter(|r| r.components.iter().any(|c| c.eq_ignore_ascii_case(component)))
117            .collect()
118    }
119
120    /// Get recipe by ID
121    pub fn get(&self, id: &str) -> Option<&Recipe> {
122        self.recipes.iter().find(|r| r.id == id)
123    }
124
125    /// Search recipes by keyword
126    pub fn search(&self, query: &str) -> Vec<&Recipe> {
127        let query_lower = query.to_lowercase();
128        self.recipes
129            .iter()
130            .filter(|r| {
131                r.title.to_lowercase().contains(&query_lower)
132                    || r.problem.to_lowercase().contains(&query_lower)
133                    || r.tags.iter().any(|t| t.to_lowercase().contains(&query_lower))
134            })
135            .collect()
136    }
137
138    /// Add a recipe to the cookbook (used by recipes module)
139    pub(crate) fn add(&mut self, recipe: Recipe) {
140        self.recipes.push(recipe);
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_cookbook_standard() {
150        let cookbook = Cookbook::standard();
151        assert!(!cookbook.recipes().is_empty());
152    }
153
154    #[test]
155    fn test_find_by_tag() {
156        let cookbook = Cookbook::standard();
157        let wasm_recipes = cookbook.find_by_tag("wasm");
158        assert!(!wasm_recipes.is_empty());
159    }
160
161    #[test]
162    fn test_find_by_component() {
163        let cookbook = Cookbook::standard();
164        let simular_recipes = cookbook.find_by_component("simular");
165        assert!(!simular_recipes.is_empty());
166    }
167
168    #[test]
169    fn test_get_by_id() {
170        let cookbook = Cookbook::standard();
171        let recipe = cookbook.get("wasm-zero-js");
172        assert!(recipe.is_some());
173        assert_eq!(recipe.expect("unexpected failure").title, "Zero-JS WASM Application");
174    }
175
176    #[test]
177    fn test_search() {
178        let cookbook = Cookbook::standard();
179        let results = cookbook.search("random forest");
180        assert!(!results.is_empty());
181    }
182
183    #[test]
184    fn test_all_recipes_have_code() {
185        let cookbook = Cookbook::standard();
186        for recipe in cookbook.recipes() {
187            assert!(!recipe.code.is_empty(), "Recipe '{}' has empty code field", recipe.id);
188        }
189    }
190
191    #[test]
192    fn test_recipe_code_contains_rust() {
193        let cookbook = Cookbook::standard();
194        let recipe = cookbook.get("ml-random-forest").expect("ml-random-forest must exist");
195        assert!(recipe.code.contains("use "), "ml-random-forest code should contain 'use ' import");
196        assert!(
197            recipe.code.contains("aprender"),
198            "ml-random-forest code should reference aprender"
199        );
200    }
201
202    #[test]
203    fn test_all_recipes_have_test_code() {
204        let cookbook = Cookbook::standard();
205        for recipe in cookbook.recipes() {
206            assert!(
207                !recipe.test_code.is_empty(),
208                "Recipe '{}' has empty test_code field",
209                recipe.id
210            );
211        }
212    }
213
214    #[test]
215    fn test_test_code_has_cfg_test() {
216        let cookbook = Cookbook::standard();
217        for recipe in cookbook.recipes() {
218            assert!(
219                recipe.test_code.contains("#[cfg("),
220                "Recipe '{}' test_code should contain #[cfg(test)] or #[cfg(all(test, ...))]",
221                recipe.id
222            );
223        }
224    }
225
226    #[test]
227    fn test_test_code_has_test_attr() {
228        let cookbook = Cookbook::standard();
229        for recipe in cookbook.recipes() {
230            assert!(
231                recipe.test_code.contains("#[test]"),
232                "Recipe '{}' test_code should contain #[test] attribute",
233                recipe.id
234            );
235        }
236    }
237}