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 };
80
81 let (response_json, usage) = self.llm_client.generate_with_usage(&llm_request).await?;
82
83 let critique = self.parse_critique_response(response_json)?;
85
86 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 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 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 fn parse_critique_response(&self, response: Value) -> Result<ApiCritique> {
212 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 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 fn estimate_cost(&self, usage: &LlmUsage) -> f64 {
237 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, _ => 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] async fn test_api_critique_engine_creation() {
278 let config = create_test_config();
279 let _engine = ApiCritiqueEngine::new(config);
280 }
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}