224 lines
6.5 KiB
Python
224 lines
6.5 KiB
Python
"""
|
|
Progress and Step Tracking utilities for AI framework
|
|
"""
|
|
import time
|
|
import logging
|
|
from typing import List, Dict, Any, Optional, Callable
|
|
from igny8_core.ai.types import StepLog, ProgressState
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class StepTracker:
|
|
"""Tracks detailed request and response steps for debugging"""
|
|
|
|
def __init__(self, function_name: str):
|
|
self.function_name = function_name
|
|
self.request_steps: List[Dict] = []
|
|
self.response_steps: List[Dict] = []
|
|
self.step_counter = 0
|
|
|
|
def add_request_step(
|
|
self,
|
|
step_name: str,
|
|
status: str = 'success',
|
|
message: str = '',
|
|
error: str = None,
|
|
duration: int = None
|
|
) -> Dict:
|
|
"""Add a request step with automatic timing"""
|
|
self.step_counter += 1
|
|
step = {
|
|
'stepNumber': self.step_counter,
|
|
'stepName': step_name,
|
|
'functionName': self.function_name,
|
|
'status': status,
|
|
'message': message,
|
|
'duration': duration
|
|
}
|
|
if error:
|
|
step['error'] = error
|
|
|
|
self.request_steps.append(step)
|
|
return step
|
|
|
|
def add_response_step(
|
|
self,
|
|
step_name: str,
|
|
status: str = 'success',
|
|
message: str = '',
|
|
error: str = None,
|
|
duration: int = None
|
|
) -> Dict:
|
|
"""Add a response step with automatic timing"""
|
|
self.step_counter += 1
|
|
step = {
|
|
'stepNumber': self.step_counter,
|
|
'stepName': step_name,
|
|
'functionName': self.function_name,
|
|
'status': status,
|
|
'message': message,
|
|
'duration': duration
|
|
}
|
|
if error:
|
|
step['error'] = error
|
|
|
|
self.response_steps.append(step)
|
|
return step
|
|
|
|
def get_meta(self) -> Dict:
|
|
"""Get metadata for progress callback"""
|
|
return {
|
|
'request_steps': self.request_steps,
|
|
'response_steps': self.response_steps
|
|
}
|
|
|
|
|
|
class ProgressTracker:
|
|
"""Tracks progress updates for AI tasks"""
|
|
|
|
def __init__(self, celery_task=None):
|
|
self.task = celery_task
|
|
self.current_phase = 'INIT'
|
|
self.current_message = 'Initializing...'
|
|
self.current_percentage = 0
|
|
self.start_time = time.time()
|
|
self.current = 0
|
|
self.total = 0
|
|
|
|
def update(
|
|
self,
|
|
phase: str,
|
|
percentage: int,
|
|
message: str,
|
|
current: int = None,
|
|
total: int = None,
|
|
current_item: str = None,
|
|
meta: Dict = None
|
|
):
|
|
"""Update progress with consistent format"""
|
|
self.current_phase = phase
|
|
self.current_message = message
|
|
self.current_percentage = percentage
|
|
|
|
if current is not None:
|
|
self.current = current
|
|
if total is not None:
|
|
self.total = total
|
|
|
|
progress_meta = {
|
|
'phase': phase,
|
|
'percentage': percentage,
|
|
'message': message,
|
|
'current': self.current,
|
|
'total': self.total,
|
|
}
|
|
|
|
if current_item:
|
|
progress_meta['current_item'] = current_item
|
|
|
|
if meta:
|
|
progress_meta.update(meta)
|
|
|
|
# Update Celery task state if available
|
|
if self.task:
|
|
try:
|
|
self.task.update_state(
|
|
state='PROGRESS',
|
|
meta=progress_meta
|
|
)
|
|
except Exception as e:
|
|
logger.warning(f"Failed to update Celery task state: {e}")
|
|
|
|
logger.info(f"[{phase}] {percentage}%: {message}")
|
|
|
|
def set_phase(self, phase: str, percentage: int, message: str, meta: Dict = None):
|
|
"""Set progress phase"""
|
|
self.update(phase, percentage, message, meta=meta)
|
|
|
|
def complete(self, message: str = "Task complete!", meta: Dict = None):
|
|
"""Mark task as complete"""
|
|
final_meta = {
|
|
'phase': 'DONE',
|
|
'percentage': 100,
|
|
'message': message,
|
|
'status': 'success'
|
|
}
|
|
if meta:
|
|
final_meta.update(meta)
|
|
|
|
if self.task:
|
|
try:
|
|
self.task.update_state(
|
|
state='SUCCESS',
|
|
meta=final_meta
|
|
)
|
|
except Exception as e:
|
|
logger.warning(f"Failed to update Celery task state: {e}")
|
|
|
|
def error(self, error_message: str, meta: Dict = None):
|
|
"""Mark task as failed"""
|
|
error_meta = {
|
|
'phase': 'ERROR',
|
|
'percentage': 0,
|
|
'message': f'Error: {error_message}',
|
|
'status': 'error',
|
|
'error': error_message
|
|
}
|
|
if meta:
|
|
error_meta.update(meta)
|
|
|
|
if self.task:
|
|
try:
|
|
self.task.update_state(
|
|
state='FAILURE',
|
|
meta=error_meta
|
|
)
|
|
except Exception as e:
|
|
logger.warning(f"Failed to update Celery task state: {e}")
|
|
|
|
def get_duration(self) -> int:
|
|
"""Get elapsed time in milliseconds"""
|
|
return int((time.time() - self.start_time) * 1000)
|
|
|
|
def update_ai_progress(self, state: str, meta: Dict):
|
|
"""Callback for AI processor progress updates"""
|
|
if isinstance(meta, dict):
|
|
percentage = meta.get('percentage', self.current_percentage)
|
|
message = meta.get('message', self.current_message)
|
|
phase = meta.get('phase', self.current_phase)
|
|
self.update(phase, percentage, message, meta=meta)
|
|
|
|
|
|
class CostTracker:
|
|
"""Tracks API costs and token usage"""
|
|
|
|
def __init__(self):
|
|
self.total_cost = 0.0
|
|
self.total_tokens = 0
|
|
self.operations = []
|
|
|
|
def record(self, function_name: str, cost: float, tokens: int, model: str = None):
|
|
"""Record an API call cost"""
|
|
self.total_cost += cost
|
|
self.total_tokens += tokens
|
|
self.operations.append({
|
|
'function': function_name,
|
|
'cost': cost,
|
|
'tokens': tokens,
|
|
'model': model
|
|
})
|
|
|
|
def get_total(self) -> float:
|
|
"""Get total cost"""
|
|
return self.total_cost
|
|
|
|
def get_total_tokens(self) -> int:
|
|
"""Get total tokens"""
|
|
return self.total_tokens
|
|
|
|
def get_operations(self) -> List[Dict]:
|
|
"""Get all operations"""
|
|
return self.operations
|
|
|