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            seed: None,
93        };
94
95        // Generate response from LLM
96        let response = self.llm_client.generate(&llm_request).await?;
97
98        // Parse the response into FailureNarrative
99        let response_str = serde_json::to_string(&response).unwrap_or_default();
100        let narrative: FailureNarrative = serde_json::from_value(response).map_err(|e| {
101            mockforge_foundation::Error::internal(format!(
102                "Failed to parse LLM response as FailureNarrative: {}. Response: {}",
103                e, response_str
104            ))
105        })?;
106
107        Ok(narrative)
108    }
109
110    /// Build a human-readable summary of the failure context
111    fn build_context_summary(&self, context: &FailureContext) -> String {
112        let mut summary = String::new();
113
114        // Request details
115        summary.push_str("## Request Details\n");
116        summary.push_str(&format!("Method: {}\n", context.request.method));
117        summary.push_str(&format!("Path: {}\n", context.request.path));
118        if !context.request.headers.is_empty() {
119            summary.push_str(&format!("Headers: {:?}\n", context.request.headers));
120        }
121        if !context.request.query_params.is_empty() {
122            summary.push_str(&format!("Query Params: {:?}\n", context.request.query_params));
123        }
124        if let Some(ref body) = context.request.body {
125            summary.push_str(&format!("Body: {}\n", body));
126        }
127        summary.push('\n');
128
129        // Response details
130        if let Some(ref response) = context.response {
131            summary.push_str("## Response Details\n");
132            summary.push_str(&format!("Status Code: {}\n", response.status_code));
133            if let Some(duration) = response.duration_ms {
134                summary.push_str(&format!("Duration: {}ms\n", duration));
135            }
136            if let Some(ref body) = response.body {
137                summary.push_str(&format!("Response Body: {}\n", body));
138            }
139            summary.push('\n');
140        }
141
142        // Error message
143        if let Some(ref error) = context.error_message {
144            summary.push_str("## Error\n");
145            summary.push_str(&format!("{}\n", error));
146            summary.push('\n');
147        }
148
149        // Chaos configs
150        if !context.chaos_configs.is_empty() {
151            summary.push_str("## Active Chaos Configurations\n");
152            for config in &context.chaos_configs {
153                summary.push_str(&format!("- {}: enabled={}\n", config.name, config.enabled));
154            }
155            summary.push('\n');
156        }
157
158        // Consistency rules
159        if !context.consistency_rules.is_empty() {
160            summary.push_str("## Consistency Rules\n");
161            for rule in &context.consistency_rules {
162                summary.push_str(&format!(
163                    "- {}: enabled={}, triggered={}\n",
164                    rule.name, rule.enabled, rule.triggered
165                ));
166                if let Some(ref desc) = rule.description {
167                    summary.push_str(&format!("  Description: {}\n", desc));
168                }
169            }
170            summary.push('\n');
171        }
172
173        // Contract validation
174        if let Some(ref validation) = context.contract_validation {
175            summary.push_str("## Contract Validation\n");
176            summary.push_str(&format!("Passed: {}\n", validation.passed));
177            if !validation.errors.is_empty() {
178                summary.push_str("Errors:\n");
179                for error in &validation.errors {
180                    summary.push_str(&format!("  - {}\n", error));
181                }
182            }
183            summary.push('\n');
184        }
185
186        // Behavioral rules
187        if !context.behavioral_rules.is_empty() {
188            summary.push_str("## Behavioral Rules/Personas\n");
189            for rule in &context.behavioral_rules {
190                summary.push_str(&format!("- {}: active={}\n", rule.name, rule.active));
191                if let Some(ref desc) = rule.description {
192                    summary.push_str(&format!("  Description: {}\n", desc));
193                }
194            }
195            summary.push('\n');
196        }
197
198        // Hook results
199        if !context.hook_results.is_empty() {
200            summary.push_str("## Hook Execution Results\n");
201            for hook in &context.hook_results {
202                summary.push_str(&format!(
203                    "- {}: success={}, type={}\n",
204                    hook.name, hook.success, hook.hook_type
205                ));
206                if let Some(ref error) = hook.error {
207                    summary.push_str(&format!("  Error: {}\n", error));
208                }
209            }
210            summary.push('\n');
211        }
212
213        summary
214    }
215}