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 seed: None,
93 };
94
95 let response = self.llm_client.generate(&llm_request).await?;
97
98 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 fn build_context_summary(&self, context: &FailureContext) -> String {
112 let mut summary = String::new();
113
114 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 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 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 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 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 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 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 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}