use crate::{AIConsensus, AIExplanation, Finding, Result, Severity, ShieldContractError};
use async_trait::async_trait;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[async_trait]
pub trait LLMProvider: Send + Sync {
async fn analyze_code(
&self,
code: &str,
context: &AnalysisContext,
) -> Result<LLMAnalysisResult>;
async fn suggest_fix(&self, finding: &Finding, code: &str) -> Result<String>;
async fn validate_ai_generated(&self, code: &str) -> Result<AIGeneratedValidation>;
async fn test_connection(&self) -> Result<()>;
fn name(&self) -> &str;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalysisContext {
pub file_path: String,
pub language: String,
pub platform: String,
pub focus_areas: Vec<String>,
pub custom_rules: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LLMAnalysisResult {
pub findings: Vec<Finding>,
pub suggestions: Vec<CodeSuggestion>,
pub confidence: f32,
pub model_name: String,
pub reasoning: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeSuggestion {
pub title: String,
pub description: String,
pub original_code: String,
pub suggested_code: String,
pub performance_gain: f32,
pub security_improvement: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AIGeneratedValidation {
pub is_ai_generated: bool,
pub confidence: f32,
pub patterns_detected: Vec<String>,
pub hallucination_risks: Vec<HallucinationRisk>,
pub determinism_issues: Vec<DeterminismIssue>,
pub quality_score: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HallucinationRisk {
pub risk_type: String,
pub description: String,
pub severity: Severity,
pub location: CodeLocation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeterminismIssue {
pub issue_type: String,
pub description: String,
pub impact: String,
pub location: CodeLocation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeLocation {
pub line: usize,
pub column: usize,
pub snippet: String,
}
pub struct LLMManager {
providers: Arc<RwLock<HashMap<String, Arc<dyn LLMProvider>>>>,
client: Client,
config: LLMConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LLMConfig {
pub timeout_seconds: u64,
pub max_retries: usize,
pub consensus_threshold: f32,
pub cache_responses: bool,
pub prompt_injection_protection: bool,
}
impl Default for LLMConfig {
fn default() -> Self {
Self {
timeout_seconds: 30,
max_retries: 3,
consensus_threshold: 0.7,
cache_responses: true,
prompt_injection_protection: true,
}
}
}
impl LLMManager {
pub async fn new() -> Result<Self> {
let client = Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()
.map_err(|e| ShieldContractError::Network(e))?;
Ok(Self {
providers: Arc::new(RwLock::new(HashMap::new())),
client,
config: LLMConfig::default(),
})
}
pub async fn with_config(config: LLMConfig) -> Result<Self> {
let client = Client::builder()
.timeout(std::time::Duration::from_secs(config.timeout_seconds))
.build()
.map_err(|e| ShieldContractError::Network(e))?;
Ok(Self {
providers: Arc::new(RwLock::new(HashMap::new())),
client,
config,
})
}
pub async fn add_provider(
&mut self,
name: String,
provider: Box<dyn LLMProvider>,
) -> Result<()> {
provider.test_connection().await?;
let provider_arc = Arc::from(provider);
let mut providers = self.providers.write().await;
providers.insert(name, provider_arc);
Ok(())
}
pub async fn analyze_with_consensus(
&self,
code: &str,
context: &AnalysisContext,
) -> Result<ConsensusAnalysisResult> {
let mut results = Vec::new();
let mut tasks = Vec::new();
{
let providers = self.providers.read().await;
if providers.is_empty() {
return Err(ShieldContractError::LLM(
"No LLM providers configured".to_string(),
));
}
let provider_list: Vec<Arc<dyn LLMProvider>> = providers.values().cloned().collect();
drop(providers);
for provider in provider_list {
let code = code.to_string();
let context = context.clone();
let task = tokio::spawn(async move {
(
provider.name().to_string(),
provider.analyze_code(&code, &context).await,
)
});
tasks.push(task);
}
}
for task in tasks {
match task.await {
Ok((name, result)) => match result {
Ok(analysis) => results.push((name, analysis)),
Err(e) => tracing::warn!("Provider {} failed: {}", name, e),
},
Err(e) => tracing::warn!("Task failed: {}", e),
}
}
if results.is_empty() {
return Err(ShieldContractError::LLM("All providers failed".to_string()));
}
self.build_consensus(results).await
}
async fn build_consensus(
&self,
results: Vec<(String, LLMAnalysisResult)>,
) -> Result<ConsensusAnalysisResult> {
let mut consensus_findings: HashMap<String, ConsensusFindings> = HashMap::new();
for (provider_name, result) in &results {
for finding in &result.findings {
let key = format!("{}:{}:{}", finding.category, finding.file, finding.line);
let consensus =
consensus_findings
.entry(key)
.or_insert_with(|| ConsensusFindings {
finding: finding.clone(),
providers_agreed: vec![],
providers_disagreed: vec![],
explanations: vec![],
});
consensus.providers_agreed.push(provider_name.clone());
consensus.explanations.push(AIExplanation {
model: provider_name.clone(),
explanation: result.reasoning.clone(),
confidence: result.confidence,
});
}
}
let mut final_findings = Vec::new();
let total_providers = results.len() as f32;
for (_, mut consensus) in consensus_findings {
let consensus_level = consensus.providers_agreed.len() as f32 / total_providers;
if consensus_level >= self.config.consensus_threshold {
consensus.finding.ai_consensus = Some(AIConsensus {
models_agreed: consensus.providers_agreed.clone(),
models_disagreed: consensus.providers_disagreed.clone(),
consensus_level,
explanations: consensus.explanations.clone(),
});
final_findings.push(consensus.finding);
}
}
let mut all_suggestions = Vec::new();
for (_, result) in &results {
all_suggestions.extend(result.suggestions.clone());
}
Ok(ConsensusAnalysisResult {
findings: final_findings,
suggestions: all_suggestions,
providers_used: results.len(),
consensus_achieved: true,
})
}
pub async fn validate_ai_generated_with_consensus(
&self,
code: &str,
) -> Result<AIGeneratedValidation> {
let mut validations = Vec::new();
let mut tasks = Vec::new();
{
let providers = self.providers.read().await;
if providers.is_empty() {
return Err(ShieldContractError::LLM(
"No LLM providers configured".to_string(),
));
}
let provider_list: Vec<Arc<dyn LLMProvider>> = providers.values().cloned().collect();
drop(providers);
for provider in provider_list {
let code = code.to_string();
let task = tokio::spawn(async move { provider.validate_ai_generated(&code).await });
tasks.push(task);
}
}
for task in tasks {
if let Ok(Ok(validation)) = task.await {
validations.push(validation);
}
}
if validations.is_empty() {
return Err(ShieldContractError::LLM(
"All validation attempts failed".to_string(),
));
}
self.aggregate_validations(validations)
}
fn aggregate_validations(
&self,
validations: Vec<AIGeneratedValidation>,
) -> Result<AIGeneratedValidation> {
let count = validations.len() as f32;
let avg_confidence = validations.iter().map(|v| v.confidence).sum::<f32>() / count;
let is_ai_generated =
validations.iter().filter(|v| v.is_ai_generated).count() as f32 / count >= 0.5;
let mut all_patterns = Vec::new();
let mut all_hallucination_risks = Vec::new();
let mut all_determinism_issues = Vec::new();
let avg_quality = validations.iter().map(|v| v.quality_score).sum::<f32>() / count;
for validation in validations {
all_patterns.extend(validation.patterns_detected);
all_hallucination_risks.extend(validation.hallucination_risks);
all_determinism_issues.extend(validation.determinism_issues);
}
all_patterns.sort();
all_patterns.dedup();
Ok(AIGeneratedValidation {
is_ai_generated,
confidence: avg_confidence,
patterns_detected: all_patterns,
hallucination_risks: all_hallucination_risks,
determinism_issues: all_determinism_issues,
quality_score: avg_quality,
})
}
}
#[derive(Debug, Clone)]
struct ConsensusFindings {
finding: Finding,
providers_agreed: Vec<String>,
providers_disagreed: Vec<String>,
explanations: Vec<AIExplanation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsensusAnalysisResult {
pub findings: Vec<Finding>,
pub suggestions: Vec<CodeSuggestion>,
pub providers_used: usize,
pub consensus_achieved: bool,
}