Skip to main content

agentic_workflow/template/
natural.rs

1use crate::types::{
2    Clarification, NaturalLanguageRequest, WorkflowResult,
3};
4
5/// Natural language workflow creation engine.
6/// Note: actual NL parsing delegates to LLM (per CLAUDE.md no-hardcoded-intelligence rule).
7pub struct NaturalLanguageEngine {
8    requests: Vec<NaturalLanguageRequest>,
9}
10
11impl NaturalLanguageEngine {
12    pub fn new() -> Self {
13        Self {
14            requests: Vec::new(),
15        }
16    }
17
18    /// Start a natural language workflow request.
19    pub fn create_request(&mut self, description: &str) -> usize {
20        let request = NaturalLanguageRequest {
21            description: description.to_string(),
22            clarifications: Vec::new(),
23            synthesized_workflow: None,
24        };
25
26        self.requests.push(request);
27        self.requests.len() - 1
28    }
29
30    /// Add a clarification question.
31    pub fn add_clarification(
32        &mut self,
33        request_idx: usize,
34        question: &str,
35        options: Option<Vec<String>>,
36    ) -> WorkflowResult<()> {
37        let request = self.requests.get_mut(request_idx).ok_or_else(|| {
38            crate::types::WorkflowError::Internal("Request not found".to_string())
39        })?;
40
41        request.clarifications.push(Clarification {
42            question: question.to_string(),
43            answer: None,
44            options,
45        });
46
47        Ok(())
48    }
49
50    /// Answer a clarification question.
51    pub fn answer_clarification(
52        &mut self,
53        request_idx: usize,
54        clarification_idx: usize,
55        answer: &str,
56    ) -> WorkflowResult<()> {
57        let request = self.requests.get_mut(request_idx).ok_or_else(|| {
58            crate::types::WorkflowError::Internal("Request not found".to_string())
59        })?;
60
61        let clarification = request
62            .clarifications
63            .get_mut(clarification_idx)
64            .ok_or_else(|| {
65                crate::types::WorkflowError::Internal("Clarification not found".to_string())
66            })?;
67
68        clarification.answer = Some(answer.to_string());
69        Ok(())
70    }
71
72    /// Set the synthesized workflow (produced by LLM).
73    pub fn set_synthesized(
74        &mut self,
75        request_idx: usize,
76        workflow: serde_json::Value,
77    ) -> WorkflowResult<()> {
78        let request = self.requests.get_mut(request_idx).ok_or_else(|| {
79            crate::types::WorkflowError::Internal("Request not found".to_string())
80        })?;
81
82        request.synthesized_workflow = Some(workflow);
83        Ok(())
84    }
85
86    /// Get a request.
87    pub fn get_request(&self, request_idx: usize) -> Option<&NaturalLanguageRequest> {
88        self.requests.get(request_idx)
89    }
90
91    /// Get all requests.
92    pub fn list_requests(&self) -> &[NaturalLanguageRequest] {
93        &self.requests
94    }
95}
96
97impl Default for NaturalLanguageEngine {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_nl_request_flow() {
109        let mut engine = NaturalLanguageEngine::new();
110        let idx = engine.create_request(
111            "Every morning, check inventory and reorder items below threshold",
112        );
113
114        engine
115            .add_clarification(idx, "What threshold should trigger reorder?", None)
116            .unwrap();
117
118        engine
119            .answer_clarification(idx, 0, "When stock is below 50 units")
120            .unwrap();
121
122        let req = engine.get_request(idx).unwrap();
123        assert_eq!(req.clarifications.len(), 1);
124        assert_eq!(
125            req.clarifications[0].answer.as_deref(),
126            Some("When stock is below 50 units")
127        );
128    }
129}