youtube-summarizer/backend/api/summarization.py

192 lines
6.5 KiB
Python

"""API endpoints for AI summarization."""
import uuid
import os
from fastapi import APIRouter, HTTPException, BackgroundTasks, Depends
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from ..services.ai_service import SummaryRequest, SummaryLength
from ..services.deepseek_summarizer import DeepSeekSummarizer
from ..core.exceptions import AIServiceError, CostLimitExceededError
router = APIRouter(prefix="/api", tags=["summarization"])
# In-memory storage for async job results (replace with Redis/DB in production)
job_results: Dict[str, Any] = {}
class SummarizeRequest(BaseModel):
"""Request model for summarization endpoint."""
transcript: str = Field(..., description="Video transcript to summarize")
length: SummaryLength = Field(SummaryLength.STANDARD, description="Summary length preference")
focus_areas: Optional[List[str]] = Field(None, description="Areas to focus on")
language: str = Field("en", description="Content language")
async_processing: bool = Field(False, description="Process asynchronously")
class SummarizeResponse(BaseModel):
"""Response model for summarization endpoint."""
summary_id: Optional[str] = None # For async processing
summary: Optional[str] = None # For sync processing
key_points: Optional[List[str]] = None
main_themes: Optional[List[str]] = None
actionable_insights: Optional[List[str]] = None
confidence_score: Optional[float] = None
processing_metadata: Optional[dict] = None
cost_data: Optional[dict] = None
status: str = "completed" # "processing", "completed", "failed"
async def get_ai_service() -> DeepSeekSummarizer:
"""Dependency to get AI service instance."""
api_key = os.getenv("DEEPSEEK_API_KEY")
if not api_key:
raise HTTPException(
status_code=500,
detail="DeepSeek API key not configured"
)
# Create and initialize service using BaseAIService pattern
service = DeepSeekSummarizer(api_key=api_key)
if not service.is_initialized:
await service.initialize()
return service
@router.post("/summarize", response_model=SummarizeResponse)
async def summarize_transcript(
request: SummarizeRequest,
background_tasks: BackgroundTasks,
ai_service: DeepSeekSummarizer = Depends(get_ai_service)
):
"""Generate AI summary from transcript."""
# Validate transcript length
if len(request.transcript.strip()) < 50:
raise HTTPException(
status_code=400,
detail="Transcript too short for meaningful summarization"
)
if len(request.transcript) > 100000: # ~100k characters
request.async_processing = True # Force async for very long transcripts
try:
# Estimate cost before processing
estimated_cost = ai_service.estimate_cost(request.transcript, request.length)
if estimated_cost > 1.00: # Cost limit check
raise CostLimitExceededError(estimated_cost, 1.00)
summary_request = SummaryRequest(
transcript=request.transcript,
length=request.length,
focus_areas=request.focus_areas,
language=request.language
)
if request.async_processing:
# Process asynchronously
summary_id = str(uuid.uuid4())
background_tasks.add_task(
process_summary_async,
summary_id=summary_id,
request=summary_request,
ai_service=ai_service
)
# Store initial status
job_results[summary_id] = {
"status": "processing",
"summary_id": summary_id
}
return SummarizeResponse(
summary_id=summary_id,
status="processing"
)
else:
# Process synchronously
result = await ai_service.generate_summary(summary_request)
return SummarizeResponse(
summary=result.summary,
key_points=result.key_points,
main_themes=result.main_themes,
actionable_insights=result.actionable_insights,
confidence_score=result.confidence_score,
processing_metadata=result.processing_metadata,
cost_data=result.cost_data,
status="completed"
)
except CostLimitExceededError as e:
raise HTTPException(
status_code=e.status_code,
detail={
"error": "Cost limit exceeded",
"message": e.message,
"details": e.details
}
)
except AIServiceError as e:
raise HTTPException(
status_code=500,
detail={
"error": "AI service error",
"message": e.message,
"code": e.error_code,
"details": e.details
}
)
except Exception as e:
raise HTTPException(
status_code=500,
detail={
"error": "Internal server error",
"message": str(e)
}
)
async def process_summary_async(
summary_id: str,
request: SummaryRequest,
ai_service: DeepSeekSummarizer
):
"""Background task for async summary processing."""
try:
result = await ai_service.generate_summary(request)
# Store result in memory (replace with proper storage)
job_results[summary_id] = {
"status": "completed",
"summary": result.summary,
"key_points": result.key_points,
"main_themes": result.main_themes,
"actionable_insights": result.actionable_insights,
"confidence_score": result.confidence_score,
"processing_metadata": result.processing_metadata,
"cost_data": result.cost_data
}
except Exception as e:
job_results[summary_id] = {
"status": "failed",
"error": str(e)
}
@router.get("/summaries/{summary_id}", response_model=SummarizeResponse)
async def get_summary(summary_id: str):
"""Get async summary result by ID."""
# Retrieve from memory (replace with proper storage)
result = job_results.get(summary_id)
if not result:
raise HTTPException(status_code=404, detail="Summary not found")
return SummarizeResponse(**result)