mockforge_intelligence/failure_analysis/
narrative_generator.rs1use crate::intelligent_behavior::{
8 config::IntelligentBehaviorConfig, llm_client::LlmClient, types::LlmGenerationRequest,
9};
10use mockforge_foundation::Result;
11
12use super::types::*;
13
14pub struct FailureNarrativeGenerator {
16 llm_client: LlmClient,
18 #[allow(dead_code)]
20 config: IntelligentBehaviorConfig,
21}
22
23impl FailureNarrativeGenerator {
24 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 pub async fn generate_narrative(&self, context: &FailureContext) -> Result<FailureNarrative> {
34 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 let context_summary = self.build_context_summary(context);
78
79 let user_prompt = format!(
81 "Analyze this failure context and generate a narrative:\n\n{}",
82 context_summary
83 );
84
85 let llm_request = LlmGenerationRequest {
87 system_prompt: system_prompt.to_string(),
88 user_prompt,
89 temperature: 0.3, max_tokens: 2000,
91 schema: None,
92 };
93
94 let response = self.llm_client.generate(&llm_request).await?;
96
97 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 fn build_context_summary(&self, context: &FailureContext) -> String {
111 let mut summary = String::new();
112
113 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 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 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 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 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 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 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 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}