""" Token Usage Service Handles token tracking, cost calculation, and usage analytics. Usage: from apps.chatbot.services import TokenUsageService # Track token usage usage = TokenUsageService.track_usage( user=user, session=session, model_name="gpt-4o", prompt_tokens=150, completion_tokens=75 ) """ from typing import Dict, Any, Optional from uuid import UUID from decimal import Decimal from django.db.models import Sum, Avg, Count, Q from django.utils import timezone from datetime import datetime, timedelta from apps.chatbot.models import TokenUsage, ChatSession from apps.accounts.models import CustomUser class TokenUsageService: """Service for tracking and analyzing token usage.""" # Pricing per 1M tokens (October 2025) MODEL_PRICING = { 'gpt-4o': { 'prompt': Decimal('2.50'), # $2.50 per 1M prompt tokens 'completion': Decimal('10.00') # $10.00 per 1M completion tokens }, 'gpt-4o-mini': { 'prompt': Decimal('0.15'), # $0.15 per 1M prompt tokens 'completion': Decimal('0.60') # $0.60 per 1M completion tokens }, 'gpt-3.5-turbo': { 'prompt': Decimal('0.50'), 'completion': Decimal('1.50') } } @staticmethod def calculate_cost( model_name: str, prompt_tokens: int, completion_tokens: int ) -> Dict[str, Decimal]: """ Calculate cost for token usage. Args: model_name: AI model name prompt_tokens: Input tokens completion_tokens: Output tokens Returns: Dict with prompt_cost, completion_cost, total_cost Example: costs = TokenUsageService.calculate_cost( model_name="gpt-4o", prompt_tokens=150, completion_tokens=75 ) """ pricing = TokenUsageService.MODEL_PRICING.get( model_name, TokenUsageService.MODEL_PRICING['gpt-4o'] # Default ) # Calculate costs (pricing is per 1M tokens) prompt_cost = (Decimal(prompt_tokens) / Decimal(1000000)) * pricing['prompt'] completion_cost = (Decimal(completion_tokens) / Decimal(1000000)) * pricing['completion'] return { 'prompt_cost': prompt_cost, 'completion_cost': completion_cost, 'total_cost': prompt_cost + completion_cost } @staticmethod def track_usage( user: CustomUser, session: ChatSession, model_name: str, prompt_tokens: int, completion_tokens: int, request_type: str = 'chat', response_time_ms: Optional[int] = None, metadata: Optional[Dict[str, Any]] = None ) -> TokenUsage: """ Track token usage for a request. Args: user: User making the request session: Chat session model_name: AI model used prompt_tokens: Input tokens completion_tokens: Output tokens request_type: Type of request (chat, summarization, etc.) response_time_ms: Response time in milliseconds metadata: Additional metadata Returns: Created TokenUsage instance Example: usage = TokenUsageService.track_usage( user=request.user, session=session, model_name="gpt-4o", prompt_tokens=150, completion_tokens=75, response_time_ms=1200 ) """ # Calculate costs costs = TokenUsageService.calculate_cost( model_name=model_name, prompt_tokens=prompt_tokens, completion_tokens=completion_tokens ) # Create usage record usage = TokenUsage.objects.create( user=user, chat_session=session, model_name=model_name, prompt_tokens=prompt_tokens, completion_tokens=completion_tokens, total_tokens=prompt_tokens + completion_tokens, prompt_cost=costs['prompt_cost'], completion_cost=costs['completion_cost'], total_cost=costs['total_cost'], request_type=request_type, response_time_ms=response_time_ms, metadata=metadata or {} ) # Update session analytics session.total_tokens_used += usage.total_tokens session.total_cost += usage.total_cost session.save(update_fields=['total_tokens_used', 'total_cost', 'updated_at']) return usage @staticmethod def check_user_limits( user: CustomUser, additional_tokens: int = 0 ) -> Dict[str, Any]: """ Check if user has exceeded usage limits. Args: user: The user additional_tokens: Tokens about to be used Returns: Dict with allowed status and reason Example: limit_check = TokenUsageService.check_user_limits( user=request.user, additional_tokens=200 ) if not limit_check['allowed']: return Response({'error': limit_check['reason']}, status=429) """ prefs = user.ai_preferences # Get today's usage today_start = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) today_usage = TokenUsage.objects.filter( user=user, created_at__gte=today_start ).aggregate( total_tokens=Sum('total_tokens'), total_cost=Sum('total_cost') ) current_tokens = today_usage['total_tokens'] or 0 current_cost = float(today_usage['total_cost'] or 0) # Check token limit if prefs.daily_token_limit: if current_tokens + additional_tokens > prefs.daily_token_limit: return { 'allowed': False, 'reason': f'Daily token limit exceeded ({prefs.daily_token_limit})', 'current_usage': current_tokens, 'limit': prefs.daily_token_limit } # Check cost limit if prefs.daily_cost_limit: # Estimate cost of additional tokens (use gpt-4o pricing as worst case) estimated_cost = TokenUsageService.calculate_cost( 'gpt-4o', additional_tokens, 0 )['prompt_cost'] if current_cost + float(estimated_cost) > prefs.daily_cost_limit: return { 'allowed': False, 'reason': f'Daily cost limit exceeded (${prefs.daily_cost_limit})', 'current_cost': current_cost, 'limit': float(prefs.daily_cost_limit) } return { 'allowed': True, 'current_tokens': current_tokens, 'current_cost': current_cost } @staticmethod def get_user_usage_stats( user: CustomUser, days: int = 30 ) -> Dict[str, Any]: """ Get usage statistics for a user. Args: user: The user days: Number of days to analyze Returns: Dict with usage statistics Example: stats = TokenUsageService.get_user_usage_stats( user=request.user, days=30 ) """ cutoff_date = timezone.now() - timedelta(days=days) usage = TokenUsage.objects.filter( user=user, created_at__gte=cutoff_date ) stats = usage.aggregate( total_requests=Count('id'), total_tokens=Sum('total_tokens'), total_cost=Sum('total_cost'), avg_tokens_per_request=Avg('total_tokens'), avg_response_time=Avg('response_time_ms') ) # Group by model by_model = usage.values('model_name').annotate( requests=Count('id'), tokens=Sum('total_tokens'), cost=Sum('total_cost') ).order_by('-cost') return { 'period_days': days, 'total_requests': stats['total_requests'] or 0, 'total_tokens': stats['total_tokens'] or 0, 'total_cost': float(stats['total_cost'] or 0), 'avg_tokens_per_request': float(stats['avg_tokens_per_request'] or 0), 'avg_response_time_ms': float(stats['avg_response_time'] or 0), 'usage_by_model': list(by_model) } @staticmethod def get_daily_usage( user: CustomUser, date: Optional[datetime] = None ) -> Dict[str, Any]: """ Get usage for a specific day. Args: user: The user date: Specific date (defaults to today) Returns: Dict with daily usage """ target_date = date or timezone.now() day_start = target_date.replace(hour=0, minute=0, second=0, microsecond=0) day_end = day_start + timedelta(days=1) usage = TokenUsage.objects.filter( user=user, created_at__gte=day_start, created_at__lt=day_end ) stats = usage.aggregate( total_requests=Count('id'), total_tokens=Sum('total_tokens'), total_cost=Sum('total_cost') ) return { 'date': day_start.date(), 'total_requests': stats['total_requests'] or 0, 'total_tokens': stats['total_tokens'] or 0, 'total_cost': float(stats['total_cost'] or 0) }