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