use crate::intelligent_behavior::{
config::IntelligentBehaviorConfig,
llm_client::{LlmClient, LlmUsage},
types::LlmGenerationRequest,
};
use crate::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CritiqueRequest {
pub schema: Value,
pub schema_type: String,
#[serde(default)]
pub focus_areas: Vec<String>,
pub workspace_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiCritique {
pub anti_patterns: Vec<AntiPattern>,
pub redundancies: Vec<Redundancy>,
pub naming_issues: Vec<NamingIssue>,
pub tone_analysis: ToneAnalysis,
pub restructuring: RestructuringRecommendations,
pub overall_score: f64,
pub summary: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub tokens_used: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost_usd: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AntiPattern {
pub pattern_type: String,
pub severity: String,
pub location: String,
pub description: String,
pub suggestion: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub example: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Redundancy {
pub redundancy_type: String,
pub severity: String,
pub affected_items: Vec<String>,
pub description: String,
pub suggestion: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NamingIssue {
pub issue_type: String,
pub severity: String,
pub location: String,
pub current_name: String,
pub description: String,
pub suggestion: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToneAnalysis {
pub overall_tone: String,
pub error_message_issues: Vec<ToneIssue>,
pub user_facing_issues: Vec<ToneIssue>,
pub recommendations: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToneIssue {
pub issue_type: String,
pub severity: String,
pub location: String,
pub current_text: String,
pub description: String,
pub suggestion: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RestructuringRecommendations {
pub hierarchy_improvements: Vec<HierarchyImprovement>,
pub consolidation_opportunities: Vec<ConsolidationOpportunity>,
pub resource_modeling: Vec<ResourceModelingSuggestion>,
pub priority: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HierarchyImprovement {
pub current: String,
pub suggested: String,
pub rationale: String,
pub impact: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsolidationOpportunity {
pub items: Vec<String>,
pub description: String,
pub suggestion: String,
pub benefits: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceModelingSuggestion {
pub current: String,
pub suggested: String,
pub rationale: String,
}
pub struct ApiCritiqueEngine {
llm_client: LlmClient,
config: IntelligentBehaviorConfig,
}
impl ApiCritiqueEngine {
pub fn new(config: IntelligentBehaviorConfig) -> Self {
let llm_client = LlmClient::new(config.behavior_model.clone());
Self { llm_client, config }
}
pub async fn analyze(&self, request: &CritiqueRequest) -> Result<ApiCritique> {
let system_prompt = self.build_system_prompt();
let user_prompt = self.build_user_prompt(request)?;
let llm_request = LlmGenerationRequest {
system_prompt,
user_prompt,
temperature: 0.3, max_tokens: 4000,
schema: None,
};
let (response_json, usage) = self.llm_client.generate_with_usage(&llm_request).await?;
let critique = self.parse_critique_response(response_json)?;
let cost_usd = self.estimate_cost(&usage);
Ok(ApiCritique {
tokens_used: Some(usage.total_tokens),
cost_usd: Some(cost_usd),
..critique
})
}
fn build_system_prompt(&self) -> String {
r#"You are an expert API architect and design reviewer. Your task is to analyze API schemas
(OpenAPI, GraphQL, or Protobuf) and provide comprehensive critique covering:
1. **Anti-patterns**: REST violations, inconsistent naming, poor resource modeling
2. **Redundancy**: Duplicate endpoints, overlapping functionality
3. **Naming Quality**: Inconsistent conventions, unclear names, abbreviations
4. **Emotional Tone**: Error messages that are too vague, technical, or unfriendly
5. **Restructuring**: Better resource hierarchy, consolidation opportunities
Return your analysis as a JSON object with the following structure:
{
"anti_patterns": [
{
"pattern_type": "rest_violation|inconsistent_naming|poor_resource_modeling",
"severity": "low|medium|high|critical",
"location": "path/to/endpoint or field name",
"description": "Clear description of the issue",
"suggestion": "How to fix it",
"example": "Optional example of the problem"
}
],
"redundancies": [
{
"redundancy_type": "duplicate_endpoint|overlapping_functionality",
"severity": "low|medium|high",
"affected_items": ["endpoint1", "endpoint2"],
"description": "Description of redundancy",
"suggestion": "How to consolidate"
}
],
"naming_issues": [
{
"issue_type": "inconsistent_convention|unclear_name|abbreviation",
"severity": "low|medium|high",
"location": "field or endpoint name",
"current_name": "actual name",
"description": "What's wrong with it",
"suggestion": "Better name"
}
],
"tone_analysis": {
"overall_tone": "friendly|neutral|technical|unfriendly",
"error_message_issues": [
{
"issue_type": "too_vague|too_technical|unfriendly",
"severity": "low|medium|high",
"location": "error code or endpoint",
"current_text": "actual error message",
"description": "What's wrong",
"suggestion": "Improved message"
}
],
"user_facing_issues": [],
"recommendations": ["list of recommendations"]
},
"restructuring": {
"hierarchy_improvements": [
{
"current": "current structure",
"suggested": "suggested structure",
"rationale": "why this is better",
"impact": "low|medium|high"
}
],
"consolidation_opportunities": [
{
"items": ["item1", "item2"],
"description": "what can be consolidated",
"suggestion": "how to consolidate",
"benefits": ["benefit1", "benefit2"]
}
],
"resource_modeling": [
{
"current": "current approach",
"suggested": "suggested approach",
"rationale": "why this is better"
}
],
"priority": "low|medium|high"
},
"overall_score": 75.5,
"summary": "Overall assessment summary"
}
Be thorough but practical. Focus on actionable recommendations."#
.to_string()
}
fn build_user_prompt(&self, request: &CritiqueRequest) -> Result<String> {
let schema_str = serde_json::to_string_pretty(&request.schema)
.map_err(|e| crate::Error::config(format!("Failed to serialize schema: {}", e)))?;
let focus_areas_text = if request.focus_areas.is_empty() {
"all areas".to_string()
} else {
request.focus_areas.join(", ")
};
Ok(format!(
r#"Analyze this {} API schema and provide critique focusing on: {}
Schema:
{}
Please provide a comprehensive analysis covering all requested areas. Be specific with locations, examples, and actionable suggestions."#,
request.schema_type, focus_areas_text, schema_str
))
}
fn parse_critique_response(&self, response: Value) -> Result<ApiCritique> {
let critique_json = if let Some(critique) = response.get("critique") {
critique.clone()
} else if response.is_object() {
response
} else {
return Err(crate::Error::internal(
"LLM response is not a valid JSON object".to_string(),
));
};
let critique: ApiCritique = serde_json::from_value(critique_json.clone()).map_err(|e| {
crate::Error::internal(format!(
"Failed to parse critique response: {}. Response was: {}",
e,
serde_json::to_string_pretty(&critique_json).unwrap_or_default()
))
})?;
Ok(critique)
}
fn estimate_cost(&self, usage: &LlmUsage) -> f64 {
let cost_per_1k_tokens =
match self.config.behavior_model.llm_provider.to_lowercase().as_str() {
"openai" => match self.config.behavior_model.model.to_lowercase().as_str() {
model if model.contains("gpt-4") => 0.03,
model if model.contains("gpt-3.5") => 0.002,
_ => 0.002,
},
"anthropic" => 0.008,
"ollama" => 0.0, _ => 0.002,
};
(usage.total_tokens as f64 / 1000.0) * cost_per_1k_tokens
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::intelligent_behavior::config::BehaviorModelConfig;
fn create_test_config() -> IntelligentBehaviorConfig {
IntelligentBehaviorConfig {
behavior_model: BehaviorModelConfig {
llm_provider: "ollama".to_string(),
model: "llama2".to_string(),
api_endpoint: Some("http://localhost:11434/api/chat".to_string()),
api_key: None,
temperature: 0.7,
max_tokens: 2000,
rules: crate::intelligent_behavior::types::BehaviorRules::default(),
},
..Default::default()
}
}
#[tokio::test]
#[ignore] async fn test_api_critique_engine_creation() {
let config = create_test_config();
let _engine = ApiCritiqueEngine::new(config);
}
#[test]
fn test_critique_request_serialization() {
let request = CritiqueRequest {
schema: serde_json::json!({"openapi": "3.0.0"}),
schema_type: "openapi".to_string(),
focus_areas: vec!["anti-patterns".to_string()],
workspace_id: None,
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("openapi"));
assert!(json.contains("anti-patterns"));
}
}