Skip to main content

adaptive_card_core/knowledge/
mod.rs

1//! Knowledge base — curated sample library for LLM inspiration.
2
3pub mod format;
4pub mod loader;
5pub mod selector;
6
7pub use format::{Complexity, KnowledgeEntry, KnowledgeManifest, ManifestEntry};
8pub use selector::SuggestResult;
9
10use crate::error::{Error, Result};
11use std::path::Path;
12use std::sync::LazyLock;
13
14/// In-memory collection of knowledge base entries.
15#[derive(Debug, Clone, Default)]
16pub struct KnowledgeBase {
17    entries: Vec<KnowledgeEntry>,
18}
19
20/// Macro to embed all KB entry JSON files at compile time.
21/// Each entry is included as a static &str and parsed lazily.
22///
23/// To add a new entry: add the `include_str!` line below and update
24/// `data/knowledge-base/_manifest.json` (manifest is for `from_dir` only;
25/// embedded entries are listed here explicitly).
26const EMBEDDED_ENTRIES_JSON: &[&str] = &[
27    // Domain examples (18 entries)
28    include_str!("../../data/knowledge-base/examples/commerce/order-tracker.json"),
29    include_str!("../../data/knowledge-base/examples/communication/feedback-survey.json"),
30    include_str!("../../data/knowledge-base/examples/data-viz/kpi-dashboard.json"),
31    include_str!("../../data/knowledge-base/examples/data-viz/weather-forecast.json"),
32    include_str!("../../data/knowledge-base/examples/finance/expense-report.json"),
33    include_str!("../../data/knowledge-base/examples/finance/invoice-processing.json"),
34    include_str!("../../data/knowledge-base/examples/helpdesk/it-helpdesk.json"),
35    include_str!("../../data/knowledge-base/examples/hr/employee-onboarding.json"),
36    include_str!("../../data/knowledge-base/examples/hr/leave-management.json"),
37    include_str!("../../data/knowledge-base/examples/hr/performance-review.json"),
38    include_str!("../../data/knowledge-base/examples/hr/recruitment.json"),
39    include_str!("../../data/knowledge-base/examples/hr/timesheet.json"),
40    include_str!("../../data/knowledge-base/examples/hr/training-lms.json"),
41    include_str!("../../data/knowledge-base/examples/it/asset-management.json"),
42    include_str!("../../data/knowledge-base/examples/productivity/meeting-scheduler.json"),
43    include_str!("../../data/knowledge-base/examples/reference/element-showcase.json"),
44    include_str!("../../data/knowledge-base/examples/travel/digital-travel-agent.json"),
45    include_str!("../../data/knowledge-base/examples/workflow/document-approval.json"),
46    // Design pattern examples (15 curated entries)
47    include_str!("../../data/knowledge-base/examples/patterns/approval-split-layout.json"),
48    include_str!("../../data/knowledge-base/examples/patterns/calendar-ooo.json"),
49    include_str!("../../data/knowledge-base/examples/patterns/employee-onboarding.json"),
50    include_str!("../../data/knowledge-base/examples/patterns/events-timeline.json"),
51    include_str!("../../data/knowledge-base/examples/patterns/expense-report-approval.json"),
52    include_str!("../../data/knowledge-base/examples/patterns/kpi-counter.json"),
53    include_str!("../../data/knowledge-base/examples/patterns/meeting-room-booking.json"),
54    include_str!("../../data/knowledge-base/examples/patterns/payslip-viewer.json"),
55    include_str!("../../data/knowledge-base/examples/patterns/product-showcase.json"),
56    include_str!("../../data/knowledge-base/examples/patterns/pto-balance-request.json"),
57    include_str!("../../data/knowledge-base/examples/patterns/safety-alert.json"),
58    include_str!("../../data/knowledge-base/examples/patterns/social-news-feed.json"),
59    include_str!("../../data/knowledge-base/examples/patterns/stock-price-widget.json"),
60    include_str!("../../data/knowledge-base/examples/patterns/warehouse-inventory.json"),
61    include_str!("../../data/knowledge-base/examples/patterns/work-anniversary.json"),
62    // Add new entries here:
63    // include_str!("../../data/knowledge-base/examples/<category>/<name>.json"),
64];
65
66impl KnowledgeBase {
67    /// Return the embedded, compile-time knowledge base.
68    ///
69    /// Entries are included via `include_str!` and parsed on first access.
70    /// To add a new entry, add the file to `data/knowledge-base/examples/`
71    /// and add an `include_str!` line to `EMBEDDED_ENTRIES_JSON` above.
72    #[must_use]
73    pub fn embedded() -> &'static KnowledgeBase {
74        static KB: LazyLock<KnowledgeBase> = LazyLock::new(|| {
75            let entries: Vec<KnowledgeEntry> = EMBEDDED_ENTRIES_JSON
76                .iter()
77                .filter_map(|json_str| serde_json::from_str(json_str).ok())
78                .collect();
79            KnowledgeBase { entries }
80        });
81        &KB
82    }
83
84    /// Load a knowledge base from a directory on disk.
85    pub fn from_dir(dir: &Path) -> Result<Self> {
86        let entries = loader::from_dir(dir)?;
87        Ok(Self { entries })
88    }
89
90    /// Construct from an in-memory list (for tests).
91    #[must_use]
92    pub fn from_entries(entries: Vec<KnowledgeEntry>) -> Self {
93        Self { entries }
94    }
95
96    #[must_use]
97    pub fn all(&self) -> &[KnowledgeEntry] {
98        &self.entries
99    }
100
101    #[must_use]
102    pub fn by_id(&self, id: &str) -> Option<&KnowledgeEntry> {
103        self.entries.iter().find(|e| e.id == id)
104    }
105
106    #[must_use]
107    pub fn by_category(&self, category: &str) -> Vec<&KnowledgeEntry> {
108        self.entries
109            .iter()
110            .filter(|e| e.category == category)
111            .collect()
112    }
113
114    /// Get-or-error variant useful in MCP tool handlers.
115    pub fn require(&self, id: &str) -> Result<&KnowledgeEntry> {
116        self.by_id(id)
117            .ok_or(Error::KnowledgeEntryNotFound { id: id.to_string() })
118    }
119
120    #[must_use]
121    pub fn suggest(&self, query: &str, limit: usize) -> Vec<SuggestResult> {
122        selector::suggest(&self.entries, query, limit)
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use crate::types::Host;
130    use serde_json::json;
131
132    fn make(id: &str, category: &str) -> KnowledgeEntry {
133        KnowledgeEntry {
134            id: id.to_string(),
135            title: id.to_string(),
136            description: String::new(),
137            category: category.to_string(),
138            tags: vec![],
139            use_cases: vec![],
140            host_targets: vec![Host::Teams],
141            complexity: Complexity::Basic,
142            card: json!({ "type": "AdaptiveCard" }),
143            notes: String::new(),
144        }
145    }
146
147    #[test]
148    fn embedded_loads_seeded_entries() {
149        let kb = KnowledgeBase::embedded();
150        assert!(
151            !kb.all().is_empty(),
152            "embedded KB should have seeded entries"
153        );
154        // Verify the travel agent entry is present
155        assert!(kb.by_id("travel/digital-travel-agent").is_some());
156    }
157
158    #[test]
159    fn by_id_returns_entry() {
160        let kb = KnowledgeBase::from_entries(vec![make("a", "finance"), make("b", "hr")]);
161        assert!(kb.by_id("a").is_some());
162        assert!(kb.by_id("c").is_none());
163    }
164
165    #[test]
166    fn by_category_filters() {
167        let kb = KnowledgeBase::from_entries(vec![make("a", "finance"), make("b", "hr")]);
168        assert_eq!(kb.by_category("finance").len(), 1);
169    }
170
171    #[test]
172    fn require_errors_on_missing() {
173        let kb = KnowledgeBase::default();
174        assert!(matches!(
175            kb.require("x"),
176            Err(Error::KnowledgeEntryNotFound { .. })
177        ));
178    }
179}