mockforge_intelligence/ai_studio/
api_critique.rs1use crate::intelligent_behavior::{
37 config::IntelligentBehaviorConfig,
38 llm_client::{LlmClient, LlmUsage},
39 types::LlmGenerationRequest,
40};
41use mockforge_foundation::Result;
42pub 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
50pub struct ApiCritiqueEngine {
52 llm_client: LlmClient,
54
55 config: IntelligentBehaviorConfig,
57}
58
59impl ApiCritiqueEngine {
60 pub fn new(config: IntelligentBehaviorConfig) -> Self {
62 let llm_client = LlmClient::new(config.behavior_model.clone());
63 Self { llm_client, config }
64 }
65
66 pub async fn analyze(&self, request: &CritiqueRequest) -> Result<ApiCritique> {
68 let system_prompt = self.build_system_prompt();
70 let user_prompt = self.build_user_prompt(request)?;
71
72 let llm_request = LlmGenerationRequest {
74 system_prompt,
75 user_prompt,
76 temperature: 0.3, 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 let critique = self.parse_critique_response(response_json)?;
86
87 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 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 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 fn parse_critique_response(&self, response: Value) -> Result<ApiCritique> {
213 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 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 fn estimate_cost(&self, usage: &LlmUsage) -> f64 {
238 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, _ => 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] async fn test_api_critique_engine_creation() {
280 let config = create_test_config();
281 let _engine = ApiCritiqueEngine::new(config);
282 }
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}