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        };
80
81        let (response_json, usage) = self.llm_client.generate_with_usage(&llm_request).await?;
82
83        // Parse the response
84        let critique = self.parse_critique_response(response_json)?;
85
86        // Calculate cost
87        let cost_usd = self.estimate_cost(&usage);
88
89        Ok(ApiCritique {
90            tokens_used: Some(usage.total_tokens),
91            cost_usd: Some(cost_usd),
92            ..critique
93        })
94    }
95
96    /// Build system prompt for API critique
97    fn build_system_prompt(&self) -> String {
98        r#"You are an expert API architect and design reviewer. Your task is to analyze API schemas
99(OpenAPI, GraphQL, or Protobuf) and provide comprehensive critique covering:
100
1011. **Anti-patterns**: REST violations, inconsistent naming, poor resource modeling
1022. **Redundancy**: Duplicate endpoints, overlapping functionality
1033. **Naming Quality**: Inconsistent conventions, unclear names, abbreviations
1044. **Emotional Tone**: Error messages that are too vague, technical, or unfriendly
1055. **Restructuring**: Better resource hierarchy, consolidation opportunities
106
107Return your analysis as a JSON object with the following structure:
108{
109  "anti_patterns": [
110    {
111      "pattern_type": "rest_violation|inconsistent_naming|poor_resource_modeling",
112      "severity": "low|medium|high|critical",
113      "location": "path/to/endpoint or field name",
114      "description": "Clear description of the issue",
115      "suggestion": "How to fix it",
116      "example": "Optional example of the problem"
117    }
118  ],
119  "redundancies": [
120    {
121      "redundancy_type": "duplicate_endpoint|overlapping_functionality",
122      "severity": "low|medium|high",
123      "affected_items": ["endpoint1", "endpoint2"],
124      "description": "Description of redundancy",
125      "suggestion": "How to consolidate"
126    }
127  ],
128  "naming_issues": [
129    {
130      "issue_type": "inconsistent_convention|unclear_name|abbreviation",
131      "severity": "low|medium|high",
132      "location": "field or endpoint name",
133      "current_name": "actual name",
134      "description": "What's wrong with it",
135      "suggestion": "Better name"
136    }
137  ],
138  "tone_analysis": {
139    "overall_tone": "friendly|neutral|technical|unfriendly",
140    "error_message_issues": [
141      {
142        "issue_type": "too_vague|too_technical|unfriendly",
143        "severity": "low|medium|high",
144        "location": "error code or endpoint",
145        "current_text": "actual error message",
146        "description": "What's wrong",
147        "suggestion": "Improved message"
148      }
149    ],
150    "user_facing_issues": [],
151    "recommendations": ["list of recommendations"]
152  },
153  "restructuring": {
154    "hierarchy_improvements": [
155      {
156        "current": "current structure",
157        "suggested": "suggested structure",
158        "rationale": "why this is better",
159        "impact": "low|medium|high"
160      }
161    ],
162    "consolidation_opportunities": [
163      {
164        "items": ["item1", "item2"],
165        "description": "what can be consolidated",
166        "suggestion": "how to consolidate",
167        "benefits": ["benefit1", "benefit2"]
168      }
169    ],
170    "resource_modeling": [
171      {
172        "current": "current approach",
173        "suggested": "suggested approach",
174        "rationale": "why this is better"
175      }
176    ],
177    "priority": "low|medium|high"
178  },
179  "overall_score": 75.5,
180  "summary": "Overall assessment summary"
181}
182
183Be thorough but practical. Focus on actionable recommendations."#
184            .to_string()
185    }
186
187    /// Build user prompt with schema and focus areas
188    fn build_user_prompt(&self, request: &CritiqueRequest) -> Result<String> {
189        let schema_str = serde_json::to_string_pretty(&request.schema).map_err(|e| {
190            mockforge_foundation::Error::config(format!("Failed to serialize schema: {}", e))
191        })?;
192
193        let focus_areas_text = if request.focus_areas.is_empty() {
194            "all areas".to_string()
195        } else {
196            request.focus_areas.join(", ")
197        };
198
199        Ok(format!(
200            r#"Analyze this {} API schema and provide critique focusing on: {}
201
202Schema:
203{}
204
205Please provide a comprehensive analysis covering all requested areas. Be specific with locations, examples, and actionable suggestions."#,
206            request.schema_type, focus_areas_text, schema_str
207        ))
208    }
209
210    /// Parse LLM response into ApiCritique struct
211    fn parse_critique_response(&self, response: Value) -> Result<ApiCritique> {
212        // Try to extract the critique from the response
213        let critique_json = if let Some(critique) = response.get("critique") {
214            critique.clone()
215        } else if response.is_object() {
216            response
217        } else {
218            return Err(mockforge_foundation::Error::internal(
219                "LLM response is not a valid JSON object".to_string(),
220            ));
221        };
222
223        // Parse into ApiCritique struct
224        let critique: ApiCritique = serde_json::from_value(critique_json.clone()).map_err(|e| {
225            mockforge_foundation::Error::internal(format!(
226                "Failed to parse critique response: {}. Response was: {}",
227                e,
228                serde_json::to_string_pretty(&critique_json).unwrap_or_default()
229            ))
230        })?;
231
232        Ok(critique)
233    }
234
235    /// Estimate cost in USD based on token usage
236    fn estimate_cost(&self, usage: &LlmUsage) -> f64 {
237        // Rough cost estimates per 1K tokens
238        // These are approximate and should be updated based on actual provider pricing
239        let cost_per_1k_tokens =
240            match self.config.behavior_model.llm_provider.to_lowercase().as_str() {
241                "openai" => match self.config.behavior_model.model.to_lowercase().as_str() {
242                    model if model.contains("gpt-4") => 0.03,
243                    model if model.contains("gpt-3.5") => 0.002,
244                    _ => 0.002,
245                },
246                "anthropic" => 0.008,
247                "ollama" => 0.0, // Local models are free
248                _ => 0.002,
249            };
250
251        (usage.total_tokens as f64 / 1000.0) * cost_per_1k_tokens
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258    use crate::intelligent_behavior::config::BehaviorModelConfig;
259
260    fn create_test_config() -> IntelligentBehaviorConfig {
261        IntelligentBehaviorConfig {
262            behavior_model: BehaviorModelConfig {
263                llm_provider: "ollama".to_string(),
264                model: "llama2".to_string(),
265                api_endpoint: Some("http://localhost:11434/api/chat".to_string()),
266                api_key: None,
267                temperature: 0.7,
268                max_tokens: 2000,
269                rules: crate::intelligent_behavior::types::BehaviorRules::default(),
270            },
271            ..Default::default()
272        }
273    }
274
275    #[tokio::test]
276    #[ignore] // Requires LLM service
277    async fn test_api_critique_engine_creation() {
278        let config = create_test_config();
279        let _engine = ApiCritiqueEngine::new(config);
280        // Engine was successfully created with test config
281    }
282
283    #[test]
284    fn test_critique_request_serialization() {
285        let request = CritiqueRequest {
286            schema: serde_json::json!({"openapi": "3.0.0"}),
287            schema_type: "openapi".to_string(),
288            focus_areas: vec!["anti-patterns".to_string()],
289            workspace_id: None,
290        };
291
292        let json = serde_json::to_string(&request).unwrap();
293        assert!(json.contains("openapi"));
294        assert!(json.contains("anti-patterns"));
295    }
296}