youtube-summarizer/backend/api/analysis_templates.py

480 lines
18 KiB
Python

"""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")