480 lines
18 KiB
Python
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") |