Skip to main content

mockforge_intelligence/failure_analysis/
narrative_generator.rs

1//! Failure narrative generator
2//!
3//! Uses LLM to generate human-readable narratives explaining why
4//! request failures occurred, with stack traces showing the chain
5//! of events.
6
7use crate::intelligent_behavior::{
8    config::IntelligentBehaviorConfig, llm_client::LlmClient, types::LlmGenerationRequest,
9};
10use mockforge_foundation::Result;
11
12use super::types::*;
13
14/// Generator for failure narratives
15pub struct FailureNarrativeGenerator {
16    /// LLM client for generating narratives
17    llm_client: LlmClient,
18    /// Configuration
19    #[allow(dead_code)]
20    config: IntelligentBehaviorConfig,
21}
22
23impl FailureNarrativeGenerator {
24    /// Create a new failure narrative generator
25    pub fn new(config: IntelligentBehaviorConfig) -> Self {
26        let behavior_model = config.behavior_model.clone();
27        let llm_client = LlmClient::new(behavior_model);
28
29        Self { llm_client, config }
30    }
31
32    /// Generate a narrative explaining a failure
33    pub async fn generate_narrative(&self, context: &FailureContext) -> Result<FailureNarrative> {
34        // Build system prompt for narrative generation
35        let system_prompt = r#"You are an expert at analyzing system failures and explaining them
36in clear, human-readable narratives. Your task is to analyze failure context and generate
37a comprehensive explanation of why a request failed.
38
39Generate a narrative that includes:
401. A concise summary of what failed
412. A detailed explanation of why it failed
423. A stack trace showing the chain of events (which rules/personas/contracts triggered)
434. Contributing factors (what made the failure more likely)
445. Suggested fixes
45
46Focus on identifying which specific rules, personas, contracts, or chaos configurations
47caused or contributed to the failure. Be specific about conditions that were met.
48
49Return your response as a JSON object with this structure:
50{
51  "summary": "Brief one-sentence summary of the failure",
52  "explanation": "Detailed explanation of why the failure occurred",
53  "stack_trace": [
54    {
55      "description": "What happened in this frame",
56      "trigger": "What condition or event triggered this",
57      "source": "Name of the rule/persona/contract/chaos config",
58      "source_type": "rule|persona|contract|chaos|hook|other"
59    }
60  ],
61  "contributing_factors": [
62    {
63      "description": "Description of the contributing factor",
64      "factor_type": "Type of factor (e.g., chaos_config, consistency_rule, etc.)",
65      "impact": "high|medium|low"
66    }
67  ],
68  "suggested_fixes": [
69    "List of suggested fixes or improvements"
70  ],
71  "confidence": 0.0-1.0
72}
73
74Be thorough but concise. Focus on actionable insights."#;
75
76        // Build context summary for the LLM
77        let context_summary = self.build_context_summary(context);
78
79        // Build user prompt
80        let user_prompt = format!(
81            "Analyze this failure context and generate a narrative:\n\n{}",
82            context_summary
83        );
84
85        // Create LLM request
86        let llm_request = LlmGenerationRequest {
87            system_prompt: system_prompt.to_string(),
88            user_prompt,
89            temperature: 0.3, // Lower temperature for more consistent analysis
90            max_tokens: 2000,
91            schema: None,
92        };
93
94        // Generate response from LLM
95        let response = self.llm_client.generate(&llm_request).await?;
96
97        // Parse the response into FailureNarrative
98        let response_str = serde_json::to_string(&response).unwrap_or_default();
99        let narrative: FailureNarrative = serde_json::from_value(response).map_err(|e| {
100            mockforge_foundation::Error::internal(format!(
101                "Failed to parse LLM response as FailureNarrative: {}. Response: {}",
102                e, response_str
103            ))
104        })?;
105
106        Ok(narrative)
107    }
108
109    /// Build a human-readable summary of the failure context
110    fn build_context_summary(&self, context: &FailureContext) -> String {
111        let mut summary = String::new();
112
113        // Request details
114        summary.push_str("## Request Details\n");
115        summary.push_str(&format!("Method: {}\n", context.request.method));
116        summary.push_str(&format!("Path: {}\n", context.request.path));
117        if !context.request.headers.is_empty() {
118            summary.push_str(&format!("Headers: {:?}\n", context.request.headers));
119        }
120        if !context.request.query_params.is_empty() {
121            summary.push_str(&format!("Query Params: {:?}\n", context.request.query_params));
122        }
123        if let Some(ref body) = context.request.body {
124            summary.push_str(&format!("Body: {}\n", body));
125        }
126        summary.push('\n');
127
128        // Response details
129        if let Some(ref response) = context.response {
130            summary.push_str("## Response Details\n");
131            summary.push_str(&format!("Status Code: {}\n", response.status_code));
132            if let Some(duration) = response.duration_ms {
133                summary.push_str(&format!("Duration: {}ms\n", duration));
134            }
135            if let Some(ref body) = response.body {
136                summary.push_str(&format!("Response Body: {}\n", body));
137            }
138            summary.push('\n');
139        }
140
141        // Error message
142        if let Some(ref error) = context.error_message {
143            summary.push_str("## Error\n");
144            summary.push_str(&format!("{}\n", error));
145            summary.push('\n');
146        }
147
148        // Chaos configs
149        if !context.chaos_configs.is_empty() {
150            summary.push_str("## Active Chaos Configurations\n");
151            for config in &context.chaos_configs {
152                summary.push_str(&format!("- {}: enabled={}\n", config.name, config.enabled));
153            }
154            summary.push('\n');
155        }
156
157        // Consistency rules
158        if !context.consistency_rules.is_empty() {
159            summary.push_str("## Consistency Rules\n");
160            for rule in &context.consistency_rules {
161                summary.push_str(&format!(
162                    "- {}: enabled={}, triggered={}\n",
163                    rule.name, rule.enabled, rule.triggered
164                ));
165                if let Some(ref desc) = rule.description {
166                    summary.push_str(&format!("  Description: {}\n", desc));
167                }
168            }
169            summary.push('\n');
170        }
171
172        // Contract validation
173        if let Some(ref validation) = context.contract_validation {
174            summary.push_str("## Contract Validation\n");
175            summary.push_str(&format!("Passed: {}\n", validation.passed));
176            if !validation.errors.is_empty() {
177                summary.push_str("Errors:\n");
178                for error in &validation.errors {
179                    summary.push_str(&format!("  - {}\n", error));
180                }
181            }
182            summary.push('\n');
183        }
184
185        // Behavioral rules
186        if !context.behavioral_rules.is_empty() {
187            summary.push_str("## Behavioral Rules/Personas\n");
188            for rule in &context.behavioral_rules {
189                summary.push_str(&format!("- {}: active={}\n", rule.name, rule.active));
190                if let Some(ref desc) = rule.description {
191                    summary.push_str(&format!("  Description: {}\n", desc));
192                }
193            }
194            summary.push('\n');
195        }
196
197        // Hook results
198        if !context.hook_results.is_empty() {
199            summary.push_str("## Hook Execution Results\n");
200            for hook in &context.hook_results {
201                summary.push_str(&format!(
202                    "- {}: success={}, type={}\n",
203                    hook.name, hook.success, hook.hook_type
204                ));
205                if let Some(ref error) = hook.error {
206                    summary.push_str(&format!("  Error: {}\n", error));
207                }
208            }
209            summary.push('\n');
210        }
211
212        summary
213    }
214}