"""API endpoints for template-driven analysis system.""" import logging from typing import Dict, List, Optional, Any from fastapi import APIRouter, HTTPException, Depends, BackgroundTasks, Query from pydantic import BaseModel, Field from ..core.dependencies import get_current_user from ..models.user import User from ..models.analysis_templates import ( AnalysisTemplate, TemplateSet, TemplateRegistry, TemplateType, ComplexityLevel ) from ..services.template_driven_agent import ( TemplateDrivenAgent, TemplateAnalysisRequest, TemplateAnalysisResult ) from ..services.template_defaults import DEFAULT_REGISTRY from ..services.enhanced_orchestrator import ( EnhancedMultiAgentOrchestrator, OrchestrationConfig, OrchestrationResult ) from ..services.template_agent_factory import get_template_agent_factory logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/templates", tags=["Analysis Templates"]) # Request/Response Models class AnalyzeWithTemplateRequest(BaseModel): """Request to analyze content with a specific template.""" content: str = Field(..., description="Content to analyze", min_length=10) template_id: str = Field(..., description="Template ID to use for analysis") context: Dict[str, Any] = Field(default_factory=dict, description="Additional context variables") video_id: Optional[str] = Field(None, description="Video ID if analyzing video content") class AnalyzeWithTemplateSetRequest(BaseModel): """Request to analyze content with a template set.""" content: str = Field(..., description="Content to analyze", min_length=10) template_set_id: str = Field(..., description="Template set ID to use") context: Dict[str, Any] = Field(default_factory=dict, description="Additional context variables") include_synthesis: bool = Field(default=True, description="Whether to include synthesis of results") video_id: Optional[str] = Field(None, description="Video ID if analyzing video content") class MultiTemplateAnalysisResult(BaseModel): """Result from analyzing content with multiple templates.""" template_set_id: str template_set_name: str results: Dict[str, TemplateAnalysisResult] synthesis_result: Optional[TemplateAnalysisResult] = None total_processing_time_seconds: float class CreateTemplateRequest(BaseModel): """Request to create a custom template.""" name: str = Field(..., min_length=1, max_length=100) description: str = Field(..., min_length=10, max_length=500) template_type: TemplateType system_prompt: str = Field(..., min_length=50) analysis_focus: List[str] = Field(..., min_items=1, max_items=10) output_format: str = Field(..., min_length=20) complexity_level: Optional[ComplexityLevel] = None target_audience: str = Field(default="general") tone: str = Field(default="professional") depth: str = Field(default="standard") variables: Dict[str, Any] = Field(default_factory=dict) tags: List[str] = Field(default_factory=list, max_items=10) class UpdateTemplateRequest(BaseModel): """Request to update an existing template.""" name: Optional[str] = Field(None, min_length=1, max_length=100) description: Optional[str] = Field(None, min_length=10, max_length=500) system_prompt: Optional[str] = Field(None, min_length=50) analysis_focus: Optional[List[str]] = Field(None, min_items=1, max_items=10) output_format: Optional[str] = Field(None, min_length=20) target_audience: Optional[str] = None tone: Optional[str] = None depth: Optional[str] = None variables: Optional[Dict[str, Any]] = None tags: Optional[List[str]] = Field(None, max_items=10) is_active: Optional[bool] = None # Enhanced Unified System Request/Response Models class UnifiedAnalysisRequest(BaseModel): """Request for unified multi-agent analysis.""" content: str = Field(..., description="Content to analyze", min_length=10) template_set_id: str = Field(..., description="Template set ID for orchestrated analysis") context: Dict[str, Any] = Field(default_factory=dict, description="Additional context variables") video_id: Optional[str] = Field(None, description="Video ID if analyzing video content") enable_synthesis: bool = Field(default=True, description="Whether to synthesize results") parallel_execution: bool = Field(default=True, description="Execute agents in parallel") save_to_database: bool = Field(default=True, description="Save results to database") class MixedPerspectiveRequest(BaseModel): """Request for mixed perspective analysis (Educational + Domain).""" content: str = Field(..., description="Content to analyze", min_length=10) template_ids: List[str] = Field(..., description="List of template IDs to use", min_items=1, max_items=10) context: Dict[str, Any] = Field(default_factory=dict, description="Additional context variables") video_id: Optional[str] = Field(None, description="Video ID if analyzing video content") enable_synthesis: bool = Field(default=True, description="Whether to synthesize mixed results") class OrchestrationResultResponse(BaseModel): """Response from unified orchestration.""" job_id: str template_set_id: str results: Dict[str, TemplateAnalysisResult] synthesis_result: Optional[TemplateAnalysisResult] = None processing_time_seconds: float success: bool error: Optional[str] = None metadata: Dict[str, Any] timestamp: str # Dependencies async def get_template_agent() -> TemplateDrivenAgent: """Get template-driven agent instance.""" return TemplateDrivenAgent(template_registry=DEFAULT_REGISTRY) async def get_enhanced_orchestrator() -> EnhancedMultiAgentOrchestrator: """Get enhanced multi-agent orchestrator instance.""" agent_factory = get_template_agent_factory(template_registry=DEFAULT_REGISTRY) config = OrchestrationConfig( parallel_execution=True, synthesis_enabled=True, max_concurrent_agents=4, timeout_seconds=300, enable_database_persistence=True ) return EnhancedMultiAgentOrchestrator( template_registry=DEFAULT_REGISTRY, agent_factory=agent_factory, config=config ) # Analysis Endpoints @router.post("/analyze", response_model=TemplateAnalysisResult) async def analyze_with_template( request: AnalyzeWithTemplateRequest, agent: TemplateDrivenAgent = Depends(get_template_agent), current_user: User = Depends(get_current_user) ): """Analyze content using a specific template.""" try: analysis_request = TemplateAnalysisRequest( content=request.content, template_id=request.template_id, context=request.context, video_id=request.video_id ) result = await agent.analyze_with_template(analysis_request) logger.info(f"Template analysis completed: {request.template_id} for user {current_user.id}") return result except Exception as e: logger.error(f"Template analysis failed: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/analyze-set", response_model=MultiTemplateAnalysisResult) async def analyze_with_template_set( request: AnalyzeWithTemplateSetRequest, background_tasks: BackgroundTasks, agent: TemplateDrivenAgent = Depends(get_template_agent), current_user: User = Depends(get_current_user) ): """Analyze content using all templates in a template set.""" try: import time start_time = time.time() # Analyze with template set results = await agent.analyze_with_template_set( content=request.content, template_set_id=request.template_set_id, context=request.context, video_id=request.video_id ) synthesis_result = None if request.include_synthesis: synthesis_result = await agent.synthesize_results( results=results, template_set_id=request.template_set_id, context=request.context ) total_processing_time = time.time() - start_time # Get template set info template_set = DEFAULT_REGISTRY.get_template_set(request.template_set_id) template_set_name = template_set.name if template_set else "Unknown" result = MultiTemplateAnalysisResult( template_set_id=request.template_set_id, template_set_name=template_set_name, results=results, synthesis_result=synthesis_result, total_processing_time_seconds=total_processing_time ) logger.info(f"Template set analysis completed: {request.template_set_id} for user {current_user.id}") return result except Exception as e: logger.error(f"Template set analysis failed: {e}") raise HTTPException(status_code=500, detail=str(e)) # Template Management Endpoints @router.get("/list", response_model=List[AnalysisTemplate]) async def list_templates( template_type: Optional[TemplateType] = Query(None, description="Filter by template type"), active_only: bool = Query(True, description="Only return active templates"), agent: TemplateDrivenAgent = Depends(get_template_agent) ): """List all available templates.""" try: templates = agent.template_registry.list_templates(template_type) if active_only: templates = [t for t in templates if t.is_active] return templates except Exception as e: logger.error(f"Failed to list templates: {e}") raise HTTPException(status_code=500, detail="Failed to list templates") @router.get("/sets", response_model=List[TemplateSet]) async def list_template_sets( template_type: Optional[TemplateType] = Query(None, description="Filter by template type"), active_only: bool = Query(True, description="Only return active template sets"), agent: TemplateDrivenAgent = Depends(get_template_agent) ): """List all available template sets.""" try: template_sets = agent.template_registry.list_template_sets(template_type) if active_only: template_sets = [ts for ts in template_sets if ts.is_active] return template_sets except Exception as e: logger.error(f"Failed to list template sets: {e}") raise HTTPException(status_code=500, detail="Failed to list template sets") @router.get("/template/{template_id}", response_model=AnalysisTemplate) async def get_template( template_id: str, agent: TemplateDrivenAgent = Depends(get_template_agent) ): """Get a specific template by ID.""" template = agent.template_registry.get_template(template_id) if not template: raise HTTPException(status_code=404, detail="Template not found") return template @router.get("/set/{set_id}", response_model=TemplateSet) async def get_template_set( set_id: str, agent: TemplateDrivenAgent = Depends(get_template_agent) ): """Get a specific template set by ID.""" template_set = agent.template_registry.get_template_set(set_id) if not template_set: raise HTTPException(status_code=404, detail="Template set not found") return template_set # Custom Template Creation (Future Enhancement) @router.post("/create", response_model=AnalysisTemplate) async def create_custom_template( request: CreateTemplateRequest, agent: TemplateDrivenAgent = Depends(get_template_agent), current_user: User = Depends(get_current_user) ): """Create a custom template (placeholder for future implementation).""" # This is a placeholder for custom template creation # In a full implementation, this would: # 1. Validate the template configuration # 2. Save to database # 3. Register with template registry # 4. Handle template versioning and permissions raise HTTPException( status_code=501, detail="Custom template creation not yet implemented. Use default templates." ) # Unified Multi-Agent Analysis Endpoints @router.post("/unified-analyze", response_model=OrchestrationResultResponse) async def unified_analysis( request: UnifiedAnalysisRequest, background_tasks: BackgroundTasks, orchestrator: EnhancedMultiAgentOrchestrator = Depends(get_enhanced_orchestrator), current_user: User = Depends(get_current_user) ): """Perform unified multi-agent analysis using a template set.""" import uuid try: job_id = str(uuid.uuid4()) # Perform orchestrated analysis result = await orchestrator.orchestrate_template_set( job_id=job_id, template_set_id=request.template_set_id, content=request.content, context=request.context, video_id=request.video_id ) # Convert OrchestrationResult to response format response = OrchestrationResultResponse( job_id=result.job_id, template_set_id=result.template_set_id, results=result.results, synthesis_result=result.synthesis_result, processing_time_seconds=result.processing_time_seconds, success=result.success, error=result.error, metadata=result.metadata, timestamp=result.timestamp.isoformat() ) logger.info(f"Unified analysis completed: {job_id} for user {current_user.id}") return response except Exception as e: logger.error(f"Unified analysis failed: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/mixed-perspective", response_model=OrchestrationResultResponse) async def mixed_perspective_analysis( request: MixedPerspectiveRequest, background_tasks: BackgroundTasks, orchestrator: EnhancedMultiAgentOrchestrator = Depends(get_enhanced_orchestrator), current_user: User = Depends(get_current_user) ): """Perform analysis using mixed perspectives (Educational + Domain).""" import uuid try: job_id = str(uuid.uuid4()) # Perform mixed perspective analysis result = await orchestrator.orchestrate_mixed_perspectives( job_id=job_id, template_ids=request.template_ids, content=request.content, context=request.context, video_id=request.video_id, enable_synthesis=request.enable_synthesis ) # Convert OrchestrationResult to response format response = OrchestrationResultResponse( job_id=result.job_id, template_set_id=result.template_set_id, results=result.results, synthesis_result=result.synthesis_result, processing_time_seconds=result.processing_time_seconds, success=result.success, error=result.error, metadata=result.metadata, timestamp=result.timestamp.isoformat() ) logger.info(f"Mixed perspective analysis completed: {job_id} for user {current_user.id}") return response except Exception as e: logger.error(f"Mixed perspective analysis failed: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/orchestrator/stats") async def get_orchestrator_statistics( orchestrator: EnhancedMultiAgentOrchestrator = Depends(get_enhanced_orchestrator), current_user: User = Depends(get_current_user) ): """Get comprehensive orchestrator and factory statistics.""" try: stats = orchestrator.get_orchestration_statistics() active_jobs = orchestrator.get_active_orchestrations() return { "orchestrator_stats": stats, "active_orchestrations": active_jobs, "system_status": "operational" } except Exception as e: logger.error(f"Failed to get orchestrator statistics: {e}") raise HTTPException(status_code=500, detail="Failed to get orchestrator statistics") # Statistics and Information Endpoints @router.get("/stats") async def get_template_statistics( agent: TemplateDrivenAgent = Depends(get_template_agent), current_user: User = Depends(get_current_user) ): """Get template usage statistics.""" try: usage_stats = agent.get_usage_stats() available_templates = len(agent.get_available_templates()) available_sets = len(agent.get_available_template_sets()) return { "available_templates": available_templates, "available_template_sets": available_sets, "usage_statistics": usage_stats, "total_uses": sum(usage_stats.values()) } except Exception as e: logger.error(f"Failed to get template statistics: {e}") raise HTTPException(status_code=500, detail="Failed to get statistics") @router.get("/types", response_model=List[str]) async def get_template_types(): """Get list of available template types.""" return [template_type.value for template_type in TemplateType] @router.get("/complexity-levels", response_model=List[str]) async def get_complexity_levels(): """Get list of available complexity levels.""" return [level.value for level in ComplexityLevel] # Health check @router.get("/health") async def template_service_health(): """Health check for template service.""" try: agent = await get_template_agent() template_count = len(agent.get_available_templates()) set_count = len(agent.get_available_template_sets()) return { "status": "healthy", "available_templates": template_count, "available_template_sets": set_count, "timestamp": "2024-01-01T00:00:00Z" # Would use actual timestamp } except Exception as e: logger.error(f"Template service health check failed: {e}") raise HTTPException(status_code=503, detail="Template service unhealthy")