192 lines
6.5 KiB
Python
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) |