Skip to main content

mockforge_intelligence/ai_studio/
api_critique.rs

1//! API Architecture Critique Engine
2//!
3//! This module provides LLM-powered analysis of API schemas (OpenAPI, GraphQL, Protobuf)
4//! to detect anti-patterns, redundancies, naming issues, emotional tone problems,
5//! and provide restructuring recommendations.
6//!
7//! # Features
8//!
9//! - **Anti-pattern Detection**: REST violations, inconsistent naming, poor resource modeling
10//! - **Redundancy Detection**: Duplicate endpoints, overlapping functionality
11//! - **Naming Quality Assessment**: Inconsistent conventions, unclear names
12//! - **Emotional Tone Analysis**: Error messages, user-facing text quality
13//! - **Restructuring Recommendations**: Better resource hierarchy, consolidation opportunities
14//!
15//! # Example Usage
16//!
17//! ```rust,ignore
18//! use mockforge_core::ai_studio::api_critique::{ApiCritique, ApiCritiqueEngine, CritiqueRequest};
19//! use mockforge_core::intelligent_behavior::IntelligentBehaviorConfig;
20//!
21//! async fn example() -> mockforge_core::Result<()> {
22//!     let config = IntelligentBehaviorConfig::default();
23//!     let engine = ApiCritiqueEngine::new(config);
24//!
25//!     let request = CritiqueRequest {
26//!         schema: serde_json::json!({"openapi": "3.0.0"}),
27//!         schema_type: "openapi".to_string(),
28//!         focus_areas: vec!["anti-patterns".to_string(), "naming".to_string()],
29//!     };
30//!
31//!     let critique = engine.analyze(&request).await?;
32//!     Ok(())
33//! }
34//! ```
35
36use crate::intelligent_behavior::{
37    config::IntelligentBehaviorConfig,
38    llm_client::{LlmClient, LlmUsage},
39    types::LlmGenerationRequest,
40};
41use mockforge_foundation::Result;
42// Data types re-exported from foundation.
43pub use mockforge_foundation::ai_studio_types::{
44    AntiPattern, ApiCritique, ConsolidationOpportunity, CritiqueRequest, HierarchyImprovement,
45    NamingIssue, Redundancy, ResourceModelingSuggestion, RestructuringRecommendations,
46    ToneAnalysis, ToneIssue,
47};
48use serde_json::Value;
49
50/// API Critique Engine
51pub struct ApiCritiqueEngine {
52    /// LLM client for analysis
53    llm_client: LlmClient,
54
55    /// Configuration
56    config: IntelligentBehaviorConfig,
57}
58
59impl ApiCritiqueEngine {
60    /// Create a new API critique engine
61    pub fn new(config: IntelligentBehaviorConfig) -> Self {
62        let llm_client = LlmClient::new(config.behavior_model.clone());
63        Self { llm_client, config }
64    }
65
66    /// Analyze an API schema and generate critique
67    pub async fn analyze(&self, request: &CritiqueRequest) -> Result<ApiCritique> {
68        // Build the analysis prompt
69        let system_prompt = self.build_system_prompt();
70        let user_prompt = self.build_user_prompt(request)?;
71
72        // Generate critique using LLM
73        let llm_request = LlmGenerationRequest {
74            system_prompt,
75            user_prompt,
76            temperature: 0.3, // Lower temperature for more consistent analysis
77            max_tokens: 4000,
78            schema: None,
79            seed: None,
80        };
81
82        let (response_json, usage) = self.llm_client.generate_with_usage(&llm_request).await?;
83
84        // Parse the response
85        let critique = self.parse_critique_response(response_json)?;
86
87        // Calculate cost
88        let cost_usd = self.estimate_cost(&usage);
89
90        Ok(ApiCritique {
91            tokens_used: Some(usage.total_tokens),
92            cost_usd: Some(cost_usd),
93            ..critique
94        })
95    }
96
97    /// Build system prompt for API critique
98    fn build_system_prompt(&self) -> String {
99        r#"You are an expert API architect and design reviewer. Your task is to analyze API schemas
100(OpenAPI, GraphQL, or Protobuf) and provide comprehensive critique covering:
101
1021. **Anti-patterns**: REST violations, inconsistent naming, poor resource modeling
1032. **Redundancy**: Duplicate endpoints, overlapping functionality
1043. **Naming Quality**: Inconsistent conventions, unclear names, abbreviations
1054. **Emotional Tone**: Error messages that are too vague, technical, or unfriendly
1065. **Restructuring**: Better resource hierarchy, consolidation opportunities
107
108Return your analysis as a JSON object with the following structure:
109{
110  "anti_patterns": [
111    {
112      "pattern_type": "rest_violation|inconsistent_naming|poor_resource_modeling",
113      "severity": "low|medium|high|critical",
114      "location": "path/to/endpoint or field name",
115      "description": "Clear description of the issue",
116      "suggestion": "How to fix it",
117      "example": "Optional example of the problem"
118    }
119  ],
120  "redundancies": [
121    {
122      "redundancy_type": "duplicate_endpoint|overlapping_functionality",
123      "severity": "low|medium|high",
124      "affected_items": ["endpoint1", "endpoint2"],
125      "description": "Description of redundancy",
126      "suggestion": "How to consolidate"
127    }
128  ],
129  "naming_issues": [
130    {
131      "issue_type": "inconsistent_convention|unclear_name|abbreviation",
132      "severity": "low|medium|high",
133      "location": "field or endpoint name",
134      "current_name": "actual name",
135      "description": "What's wrong with it",
136      "suggestion": "Better name"
137    }
138  ],
139  "tone_analysis": {
140    "overall_tone": "friendly|neutral|technical|unfriendly",
141    "error_message_issues": [
142      {
143        "issue_type": "too_vague|too_technical|unfriendly",
144        "severity": "low|medium|high",
145        "location": "error code or endpoint",
146        "current_text": "actual error message",
147        "description": "What's wrong",
148        "suggestion": "Improved message"
149      }
150    ],
151    "user_facing_issues": [],
152    "recommendations": ["list of recommendations"]
153  },
154  "restructuring": {
155    "hierarchy_improvements": [
156      {
157        "current": "current structure",
158        "suggested": "suggested structure",
159        "rationale": "why this is better",
160        "impact": "low|medium|high"
161      }
162    ],
163    "consolidation_opportunities": [
164      {
165        "items": ["item1", "item2"],
166        "description": "what can be consolidated",
167        "suggestion": "how to consolidate",
168        "benefits": ["benefit1", "benefit2"]
169      }
170    ],
171    "resource_modeling": [
172      {
173        "current": "current approach",
174        "suggested": "suggested approach",
175        "rationale": "why this is better"
176      }
177    ],
178    "priority": "low|medium|high"
179  },
180  "overall_score": 75.5,
181  "summary": "Overall assessment summary"
182}
183
184Be thorough but practical. Focus on actionable recommendations."#
185            .to_string()
186    }
187
188    /// Build user prompt with schema and focus areas
189    fn build_user_prompt(&self, request: &CritiqueRequest) -> Result<String> {
190        let schema_str = serde_json::to_string_pretty(&request.schema).map_err(|e| {
191            mockforge_foundation::Error::config(format!("Failed to serialize schema: {}", e))
192        })?;
193
194        let focus_areas_text = if request.focus_areas.is_empty() {
195            "all areas".to_string()
196        } else {
197            request.focus_areas.join(", ")
198        };
199
200        Ok(format!(
201            r#"Analyze this {} API schema and provide critique focusing on: {}
202
203Schema:
204{}
205
206Please provide a comprehensive analysis covering all requested areas. Be specific with locations, examples, and actionable suggestions."#,
207            request.schema_type, focus_areas_text, schema_str
208        ))
209    }
210
211    /// Parse LLM response into ApiCritique struct
212    fn parse_critique_response(&self, response: Value) -> Result<ApiCritique> {
213        // Try to extract the critique from the response
214        let critique_json = if let Some(critique) = response.get("critique") {
215            critique.clone()
216        } else if response.is_object() {
217            response
218        } else {
219            return Err(mockforge_foundation::Error::internal(
220                "LLM response is not a valid JSON object".to_string(),
221            ));
222        };
223
224        // Parse into ApiCritique struct
225        let critique: ApiCritique = serde_json::from_value(critique_json.clone()).map_err(|e| {
226            mockforge_foundation::Error::internal(format!(
227                "Failed to parse critique response: {}. Response was: {}",
228                e,
229                serde_json::to_string_pretty(&critique_json).unwrap_or_default()
230            ))
231        })?;
232
233        Ok(critique)
234    }
235
236    /// Estimate cost in USD based on token usage
237    fn estimate_cost(&self, usage: &LlmUsage) -> f64 {
238        // Rough cost estimates per 1K tokens
239        // These are approximate and should be updated based on actual provider pricing
240        let cost_per_1k_tokens =
241            match self.config.behavior_model.llm_provider.to_lowercase().as_str() {
242                "openai" => match self.config.behavior_model.model.to_lowercase().as_str() {
243                    model if model.contains("gpt-4") => 0.03,
244                    model if model.contains("gpt-3.5") => 0.002,
245                    _ => 0.002,
246                },
247                "anthropic" => 0.008,
248                "ollama" => 0.0, // Local models are free
249                _ => 0.002,
250            };
251
252        (usage.total_tokens as f64 / 1000.0) * cost_per_1k_tokens
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use crate::intelligent_behavior::config::BehaviorModelConfig;
260
261    fn create_test_config() -> IntelligentBehaviorConfig {
262        IntelligentBehaviorConfig {
263            behavior_model: BehaviorModelConfig {
264                llm_provider: "ollama".to_string(),
265                model: "llama2".to_string(),
266                api_endpoint: Some("http://localhost:11434/api/chat".to_string()),
267                api_key: None,
268                temperature: 0.7,
269                max_tokens: 2000,
270                rules: crate::intelligent_behavior::types::BehaviorRules::default(),
271                seed: None,
272            },
273            ..Default::default()
274        }
275    }
276
277    #[tokio::test]
278    #[ignore] // Requires LLM service
279    async fn test_api_critique_engine_creation() {
280        let config = create_test_config();
281        let _engine = ApiCritiqueEngine::new(config);
282        // Engine was successfully created with test config
283    }
284
285    #[test]
286    fn test_critique_request_serialization() {
287        let request = CritiqueRequest {
288            schema: serde_json::json!({"openapi": "3.0.0"}),
289            schema_type: "openapi".to_string(),
290            focus_areas: vec!["anti-patterns".to_string()],
291            workspace_id: None,
292        };
293
294        let json = serde_json::to_string(&request).unwrap();
295        assert!(json.contains("openapi"));
296        assert!(json.contains("anti-patterns"));
297    }
298}