Spaces:
Running
Running
| """ | |
| API v1 Router - Main entry point for all endpoints | |
| Supports dual-mode LLM (MLX or Docker Model Runner) | |
| """ | |
| from fastapi import APIRouter, File, UploadFile, HTTPException | |
| from typing import Optional | |
| from datetime import datetime | |
| import logging | |
| from ...models.schemas import ( | |
| ChatRequest, ChatResponse, | |
| FileUploadResponse, AnalysisRequest, AnalysisResponse, | |
| SuggestionRequest, SuggestionsResponse, HealthResponse | |
| ) | |
| from ...services.data_processor import DataProcessor | |
| from ...services.analyzer import Analyzer | |
| from ...services.ml_suggester import MLSuggester | |
| from ...config import settings | |
| router = APIRouter(prefix="/api/v1", tags=["v1"]) | |
| logger = logging.getLogger(__name__) | |
| # Global service instances | |
| llm_service = None # Will be set from main.py | |
| data_processor: Optional[DataProcessor] = None | |
| analyzer: Optional[Analyzer] = None | |
| ml_suggester: Optional[MLSuggester] = None | |
| async def init_services(): | |
| """Initialize all services on startup (except LLM - done in main.py)""" | |
| global data_processor, analyzer, ml_suggester | |
| logger.info("π Initializing data services...") | |
| data_processor = DataProcessor() | |
| analyzer = Analyzer() | |
| ml_suggester = MLSuggester() | |
| logger.info("β Data services initialized") | |
| # ============ Chat Endpoint ============ | |
| async def chat_endpoint(request: ChatRequest): | |
| """Chat with LLM about data analysis""" | |
| if not llm_service or not llm_service.is_loaded: | |
| raise HTTPException( | |
| status_code=503, | |
| detail="LLM not ready - still loading or connection failed" | |
| ) | |
| try: | |
| logger.info(f"π¬ Chat request with {len(request.messages)} messages") | |
| # Convert Pydantic ChatMessage objects to dictionaries | |
| messages_dict = [ | |
| {"role": msg.role, "content": msg.content} | |
| for msg in request.messages | |
| ] | |
| response = await llm_service.chat( | |
| messages_dict, | |
| request.system_prompt | |
| ) | |
| # Determine which model name to return based on DEBUG mode | |
| model_name = ( | |
| settings.llm_model_name_mlx if settings.debug | |
| else settings.llm_model_name_docker | |
| ) | |
| return ChatResponse( | |
| response=response, | |
| model=model_name | |
| ) | |
| except Exception as e: | |
| logger.error(f"β Chat error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ============ File Upload Endpoint ============ | |
| async def upload_file(file: UploadFile = File(...)): | |
| """Upload and process data file (CSV or Excel)""" | |
| try: | |
| if not data_processor: | |
| raise HTTPException(status_code=503, detail="Service not ready") | |
| logger.info(f"π Processing file: {file.filename}") | |
| data, file_type = await data_processor.process_file(file) | |
| # Get column names from first row (if data exists) | |
| column_names = list(data[0].keys()) if data else [] | |
| # Get preview (first 5 rows) | |
| preview = data[:5] if data else [] | |
| logger.info(f"β Upload successful: {len(data)} rows, {len(column_names)} columns") | |
| return FileUploadResponse( | |
| filename=file.filename, | |
| size=len(str(data)), | |
| rows=len(data), | |
| columns=len(column_names), | |
| column_names=column_names, | |
| preview=preview, | |
| file_type=file_type | |
| ) | |
| except ValueError as e: | |
| logger.error(f"β Validation error: {e}") | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| except Exception as e: | |
| logger.error(f"β Upload error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ============ Analysis Endpoint ============ | |
| async def analyze_data(request: AnalysisRequest): | |
| """Analyze data - supports multiple analysis types""" | |
| try: | |
| if not analyzer: | |
| raise HTTPException(status_code=503, detail="Service not ready") | |
| logger.info(f"π Analysis: {request.analysis_type} on {len(request.data)} rows") | |
| # Call analyzer with await | |
| results = await analyzer.analyze( | |
| request.data, | |
| request.analysis_type, | |
| request.columns | |
| ) | |
| summary = f"Analysis complete: {request.analysis_type} on {len(request.data)} rows" | |
| import pandas as pd | |
| df = pd.DataFrame(request.data) | |
| data_shape = df.shape | |
| return AnalysisResponse( | |
| analysis_type=request.analysis_type, | |
| results=results, | |
| summary=summary, | |
| data_shape=data_shape, | |
| timestamp=datetime.now() | |
| ) | |
| except ValueError as e: | |
| logger.error(f"Invalid analysis request: {e}") | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| except Exception as e: | |
| logger.error(f"β Analysis error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ============ ML Suggestions Endpoint ============ | |
| async def get_suggestions(request: SuggestionRequest): | |
| """Get ML-based suggestions for data improvement""" | |
| try: | |
| if not ml_suggester: | |
| raise HTTPException(status_code=503, detail="Service not ready") | |
| logger.info(f"π€ Generating suggestions for {len(request.data)} rows") | |
| suggestions_list = ml_suggester.generate( | |
| request.data, | |
| request.analysis_context | |
| ) | |
| return SuggestionsResponse( | |
| suggestions=suggestions_list, | |
| total_suggestions=len(suggestions_list), | |
| timestamp=datetime.now() | |
| ) | |
| except Exception as e: | |
| logger.error(f"β Suggestion error: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ============ Health Check ============ | |
| async def health_check(): | |
| """Health check endpoint - shows current LLM mode""" | |
| # Determine LLM status | |
| llm_status = "unknown" | |
| if llm_service: | |
| if llm_service.is_mock: | |
| llm_status = "mock-mode" | |
| elif llm_service.is_loaded: | |
| llm_status = "loaded" | |
| else: | |
| llm_status = "failed" | |
| # Show which mode is active | |
| mode = "MLX (local)" if settings.debug else "Docker Model Runner" | |
| return HealthResponse( | |
| status="healthy", | |
| environment=settings.fastapi_env, | |
| service="llm-data-analyzer-backend", | |
| llm_loaded=llm_service.is_loaded if llm_service else False, | |
| llm_model=( | |
| settings.llm_model_name_mlx if settings.debug | |
| else settings.llm_model_name_docker | |
| ), | |
| version="0.2.0" | |
| ) | |