auto_sigma_rule_generator/backend/routers/llm_operations.py
bpmcdevitt a6fb367ed4 refactor: modularize backend architecture for improved maintainability
- Extract database models from monolithic main.py (2,373 lines) into organized modules
- Implement service layer pattern with dedicated business logic classes
- Split API endpoints into modular FastAPI routers by functionality
- Add centralized configuration management with environment variable handling
- Create proper separation of concerns across data, service, and presentation layers

**Architecture Changes:**
- models/: SQLAlchemy database models (CVE, SigmaRule, RuleTemplate, BulkProcessingJob)
- config/: Centralized settings and database configuration
- services/: Business logic (CVEService, SigmaRuleService, GitHubExploitAnalyzer)
- routers/: Modular API endpoints (cves, sigma_rules, bulk_operations, llm_operations)
- schemas/: Pydantic request/response models

**Key Improvements:**
- 95% reduction in main.py size (2,373 → 120 lines)
- Updated 15+ backend files with proper import structure
- Eliminated circular dependencies and tight coupling
- Enhanced testability with isolated service components
- Better code organization for team collaboration

**Backward Compatibility:**
- All API endpoints maintain same URLs and behavior
- Zero breaking changes to existing functionality
- Database schema unchanged
- Environment variables preserved

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-14 17:51:23 -05:00

211 lines
No EOL
7.6 KiB
Python

from typing import Dict, Any
from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session
from pydantic import BaseModel
from config.database import get_db
from models import CVE, SigmaRule
router = APIRouter(prefix="/api", tags=["llm-operations"])
class LLMRuleRequest(BaseModel):
cve_id: str
poc_content: str = ""
class LLMSwitchRequest(BaseModel):
provider: str
model: str = ""
@router.post("/llm-enhanced-rules")
async def generate_llm_enhanced_rules(request: LLMRuleRequest, db: Session = Depends(get_db)):
"""Generate SIGMA rules using LLM AI analysis"""
try:
from enhanced_sigma_generator import EnhancedSigmaGenerator
# Get CVE
cve = db.query(CVE).filter(CVE.cve_id == request.cve_id).first()
if not cve:
raise HTTPException(status_code=404, detail="CVE not found")
# Generate enhanced rule using LLM
generator = EnhancedSigmaGenerator(db)
result = await generator.generate_enhanced_rule(cve, use_llm=True)
if result.get('success'):
return {
"success": True,
"message": f"Generated LLM-enhanced rule for {request.cve_id}",
"rule_id": result.get('rule_id'),
"generation_method": "llm_enhanced"
}
else:
return {
"success": False,
"error": result.get('error', 'Unknown error'),
"cve_id": request.cve_id
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error generating LLM-enhanced rule: {str(e)}")
@router.get("/llm-status")
async def get_llm_status():
"""Check LLM API availability and configuration for all providers"""
try:
from llm_client import LLMClient
# Test all providers
providers_status = {}
# Test Ollama
try:
ollama_client = LLMClient(provider="ollama")
ollama_status = await ollama_client.test_connection()
providers_status["ollama"] = {
"available": ollama_status.get("available", False),
"models": ollama_status.get("models", []),
"current_model": ollama_status.get("current_model"),
"base_url": ollama_status.get("base_url")
}
except Exception as e:
providers_status["ollama"] = {"available": False, "error": str(e)}
# Test OpenAI
try:
openai_client = LLMClient(provider="openai")
openai_status = await openai_client.test_connection()
providers_status["openai"] = {
"available": openai_status.get("available", False),
"models": openai_status.get("models", []),
"current_model": openai_status.get("current_model"),
"has_api_key": openai_status.get("has_api_key", False)
}
except Exception as e:
providers_status["openai"] = {"available": False, "error": str(e)}
# Test Anthropic
try:
anthropic_client = LLMClient(provider="anthropic")
anthropic_status = await anthropic_client.test_connection()
providers_status["anthropic"] = {
"available": anthropic_status.get("available", False),
"models": anthropic_status.get("models", []),
"current_model": anthropic_status.get("current_model"),
"has_api_key": anthropic_status.get("has_api_key", False)
}
except Exception as e:
providers_status["anthropic"] = {"available": False, "error": str(e)}
# Get current configuration
current_client = LLMClient()
current_config = {
"current_provider": current_client.provider,
"current_model": current_client.model,
"default_provider": "ollama"
}
return {
"providers": providers_status,
"configuration": current_config,
"status": "operational" if any(p.get("available") for p in providers_status.values()) else "no_providers_available"
}
except Exception as e:
return {
"status": "error",
"error": str(e),
"providers": {},
"configuration": {}
}
@router.post("/llm-switch")
async def switch_llm_provider(request: LLMSwitchRequest):
"""Switch between LLM providers and models"""
try:
from llm_client import LLMClient
# Test the new provider/model
test_client = LLMClient(provider=request.provider, model=request.model)
connection_test = await test_client.test_connection()
if not connection_test.get("available"):
raise HTTPException(
status_code=400,
detail=f"Provider {request.provider} with model {request.model} is not available"
)
# Switch to new configuration (this would typically involve updating environment variables
# or configuration files, but for now we'll just confirm the switch)
return {
"success": True,
"message": f"Switched to {request.provider}" + (f" with model {request.model}" if request.model else ""),
"provider": request.provider,
"model": request.model or connection_test.get("current_model"),
"available": True
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error switching LLM provider: {str(e)}")
@router.post("/ollama-pull-model")
async def pull_ollama_model(model: str = "llama3.2"):
"""Pull a model in Ollama"""
try:
import aiohttp
import os
ollama_url = os.getenv("OLLAMA_BASE_URL", "http://ollama:11434")
async with aiohttp.ClientSession() as session:
async with session.post(f"{ollama_url}/api/pull", json={"name": model}) as response:
if response.status == 200:
return {
"success": True,
"message": f"Successfully pulled model {model}",
"model": model
}
else:
raise HTTPException(status_code=500, detail=f"Failed to pull model: {response.status}")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error pulling Ollama model: {str(e)}")
@router.get("/ollama-models")
async def get_ollama_models():
"""Get available Ollama models"""
try:
import aiohttp
import os
ollama_url = os.getenv("OLLAMA_BASE_URL", "http://ollama:11434")
async with aiohttp.ClientSession() as session:
async with session.get(f"{ollama_url}/api/tags") as response:
if response.status == 200:
data = await response.json()
models = [model["name"] for model in data.get("models", [])]
return {
"models": models,
"total_models": len(models),
"ollama_url": ollama_url
}
else:
return {
"models": [],
"total_models": 0,
"error": f"Ollama not available (status: {response.status})"
}
except Exception as e:
return {
"models": [],
"total_models": 0,
"error": f"Error connecting to Ollama: {str(e)}"
}