Skip to main content

agentshield/adapter/
semantic_kernel.rs

1use std::path::Path;
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5
6use crate::config::ScanPathFilter;
7use crate::error::Result;
8use crate::ir::*;
9
10static SK_KERNEL_FN_RE: Lazy<Regex> = Lazy::new(|| {
11    Regex::new(r#"(?s)@kernel_function\s*(?:\(\s*(?:[^\)]*?name\s*=\s*["'`]([^"'`]+)["'`])?(?:[^\)]*?description\s*=\s*["'`]([^"'`]+)["'`])?[^\)]*\))?\s*\n\s*(?:async\s+)?def\s+(\w+)\s*\([^)]*\)\s*(?:->[^:]+)?:\s*(?:\n\s*["'`]([\s\S]*?)["'`])?"#)
12        .expect("static regex pattern is valid")
13});
14
15/// Microsoft Semantic Kernel framework adapter (`semantic-kernel`).
16///
17/// Detects Semantic Kernel projects by looking for:
18/// - `pyproject.toml` / `requirements.txt` with `semantic-kernel`
19/// - Python files importing `from semantic_kernel` or `import semantic_kernel`
20pub struct SemanticKernelAdapter;
21
22impl super::Adapter for SemanticKernelAdapter {
23    fn framework(&self) -> Framework {
24        Framework::SemanticKernel
25    }
26
27    fn detect(&self, root: &Path) -> bool {
28        super::pipeline::detect_python_framework(
29            root,
30            &["semantic-kernel", "semantic_kernel"],
31            |trimmed| {
32                trimmed.starts_with("semantic-kernel") || trimmed.starts_with("semantic_kernel")
33            },
34            &[
35                "from semantic_kernel",
36                "import semantic_kernel",
37                "@kernel_function",
38            ],
39        )
40    }
41
42    fn load(&self, root: &Path, ignore_tests: bool) -> Result<Vec<ScanTarget>> {
43        let filter = ScanPathFilter::for_ignore_tests(ignore_tests);
44        self.load_with_filter(root, &filter)
45    }
46
47    fn load_with_filter(&self, root: &Path, filter: &ScanPathFilter) -> Result<Vec<ScanTarget>> {
48        super::pipeline::load_framework_target(
49            root,
50            filter,
51            Framework::SemanticKernel,
52            "semantic-kernel-project",
53            |lang| lang == Language::Python,
54            Some(extract_semantic_kernel_tools_from_source),
55        )
56    }
57}
58
59pub(crate) fn extract_semantic_kernel_tools_from_source(
60    path: &Path,
61    content: &str,
62) -> Vec<ToolSurface> {
63    let mut tools = Vec::new();
64
65    for cap in SK_KERNEL_FN_RE.captures_iter(content) {
66        let explicit_name = cap.get(1).map(|m| m.as_str().to_string());
67        let explicit_desc = cap.get(2).map(|m| m.as_str().to_string());
68        let def_name = cap[3].to_string();
69        let docstring = cap.get(4).map(|m| m.as_str().trim().to_string());
70
71        let tool_name = explicit_name.unwrap_or(def_name);
72        let description = explicit_desc.or(docstring);
73
74        let match_start = cap.get(0).map(|m| m.start()).unwrap_or(0);
75        let line = content[..match_start].lines().count() + 1;
76
77        tools.push(super::pipeline::build_extracted_tool(
78            tool_name,
79            description,
80            path,
81            line,
82        ));
83    }
84
85    tools
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::adapter::Adapter;
92
93    #[test]
94    fn detects_semantic_kernel_requirements() {
95        let dir = tempfile::tempdir().unwrap();
96        let req = dir.path().join("requirements.txt");
97        std::fs::write(&req, "semantic-kernel>=1.0.0\n").unwrap();
98
99        let adapter = SemanticKernelAdapter;
100        assert!(adapter.detect(dir.path()));
101    }
102
103    #[test]
104    fn detects_semantic_kernel_import() {
105        let dir = tempfile::tempdir().unwrap();
106        let src = dir.path().join("plugin.py");
107        std::fs::write(
108            &src,
109            "from semantic_kernel.functions import kernel_function\n",
110        )
111        .unwrap();
112
113        let adapter = SemanticKernelAdapter;
114        assert!(adapter.detect(dir.path()));
115    }
116
117    #[test]
118    fn extracts_semantic_kernel_tools() {
119        let content = r#"
120from semantic_kernel.functions import kernel_function
121
122class DatabasePlugin:
123    @kernel_function(name="query_db", description="Execute a database query")
124    def execute_query(self, sql: str) -> str:
125        """Run SQL query against data warehouse."""
126        return db.query(sql)
127"#;
128        let tools = extract_semantic_kernel_tools_from_source(Path::new("plugin.py"), content);
129        assert_eq!(tools.len(), 1);
130        assert_eq!(tools[0].name, "query_db");
131        assert_eq!(
132            tools[0].description.as_deref(),
133            Some("Execute a database query")
134        );
135    }
136}