Skip to main content

mockforge_intelligence/voice/
hook_transpiler.rs

1//! Natural language to hook transpiler
2//!
3//! This module converts natural language descriptions of hook logic into
4//! structured Hook definitions that can be used in chaos orchestration scenarios.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use mockforge_core::voice::HookTranspiler;
10//! use mockforge_core::intelligent_behavior::IntelligentBehaviorConfig;
11//!
12//! # async fn example() -> mockforge_core::Result<()> {
13//! let config = IntelligentBehaviorConfig::default();
14//! let transpiler = HookTranspiler::new(config);
15//!
16//! let description = "For users flagged as VIP, webhooks should fire instantly but payments fail 5% of the time";
17//! let hook = transpiler.transpile(description).await?;
18//! # Ok(())
19//! # }
20//! ```
21
22use crate::intelligent_behavior::{
23    config::IntelligentBehaviorConfig, llm_client::LlmClient, types::LlmGenerationRequest,
24};
25use mockforge_foundation::Result;
26// Hook types are defined in mockforge-chaos, but we use serde_json::Value to avoid circular dependency
27// When used, they should be deserialized from JSON
28type Hook = serde_json::Value;
29
30/// Transpiler that converts natural language hook descriptions to Hook structs
31pub struct HookTranspiler {
32    /// LLM client for parsing descriptions
33    llm_client: LlmClient,
34    /// Configuration
35    #[allow(dead_code)]
36    config: IntelligentBehaviorConfig,
37}
38
39impl HookTranspiler {
40    /// Create a new hook transpiler
41    pub fn new(config: IntelligentBehaviorConfig) -> Self {
42        let behavior_model = config.behavior_model.clone();
43        let llm_client = LlmClient::new(behavior_model);
44
45        Self { llm_client, config }
46    }
47
48    /// Transpile a natural language hook description to a Hook struct
49    ///
50    /// # Arguments
51    ///
52    /// * `description` - Natural language description of the hook logic
53    ///
54    /// # Example Description
55    ///
56    /// "For users flagged as VIP, webhooks should fire instantly but payments fail 5% of the time"
57    pub async fn transpile(&self, description: &str) -> Result<Hook> {
58        // Build system prompt for hook parsing
59        let system_prompt = r#"You are an expert at parsing natural language descriptions of hook logic
60and converting them to structured hook configurations.
61
62A hook consists of:
631. **Name**: A descriptive name for the hook
642. **Hook Type**: When the hook executes (pre_step, post_step, pre_orchestration, post_orchestration)
653. **Condition**: Optional condition that must be met for the hook to execute
664. **Actions**: List of actions to perform when the hook executes
67
68Available hook types:
69- pre_step: Execute before a step
70- post_step: Execute after a step
71- pre_orchestration: Execute before orchestration starts
72- post_orchestration: Execute after orchestration completes
73
74Available conditions:
75- equals: Variable equals value
76- not_equals: Variable not equals value
77- greater_than: Variable greater than numeric value
78- less_than: Variable less than numeric value
79- exists: Variable exists
80- and: All conditions must be true
81- or: At least one condition must be true
82- not: Condition must be false
83
84Available actions:
85- set_variable: Set a variable to a value
86- log: Log a message at a level (trace, debug, info, warn, error)
87- http_request: Make an HTTP request (webhook)
88- command: Execute a command
89- record_metric: Record a metric value
90
91For probability-based failures (e.g., "fail 5% of the time"), you should:
921. Use a condition that checks a random variable or metric
932. Use set_variable to set a failure flag
943. The actual failure injection should be handled by the chaos configuration
95
96For timing constraints (e.g., "instantly", "with delay"), use appropriate hook types or add delay actions.
97
98Return your response as a JSON object with this structure:
99{
100  "name": "string (descriptive hook name)",
101  "hook_type": "pre_step | post_step | pre_orchestration | post_orchestration",
102  "condition": {
103    "type": "condition type",
104    ...condition-specific fields
105  } or null,
106  "actions": [
107    {
108      "type": "action type",
109      ...action-specific fields
110    }
111  ]
112}
113
114Be specific and extract all details from the description. If timing is mentioned (instantly, with delay),
115choose the appropriate hook_type. If conditions are mentioned (for users flagged as VIP), create
116appropriate condition structures."#;
117
118        // Build user prompt with the description
119        let user_prompt = format!(
120            "Parse this hook description and convert it to a hook configuration:\n\n{}",
121            description
122        );
123
124        // Create LLM request
125        let llm_request = LlmGenerationRequest {
126            system_prompt: system_prompt.to_string(),
127            user_prompt,
128            temperature: 0.2, // Lower temperature for more consistent parsing
129            max_tokens: 2000,
130            schema: None,
131        };
132
133        // Generate response from LLM
134        let response = self.llm_client.generate(&llm_request).await?;
135
136        // Since Hook is now serde_json::Value, we can return the response directly
137        // Just validate it's a valid JSON object
138        if !response.is_object() {
139            return Err(mockforge_foundation::Error::internal(format!(
140                "LLM response is not a JSON object. Response: {}",
141                serde_json::to_string(&response).unwrap_or_default()
142            )));
143        }
144
145        Ok(response)
146    }
147
148    // Note: convert_to_hook and related functions removed since Hook is now serde_json::Value
149    // The LLM response is returned directly as JSON
150}