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