ciyidogan commited on
Commit
f8b2916
Β·
verified Β·
1 Parent(s): 9b8b857

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +206 -29
app.py CHANGED
@@ -2,50 +2,68 @@
2
  Flare – Main Application (Refactored)
3
  =====================================
4
  """
5
-
6
- from fastapi import FastAPI, WebSocket
7
  from fastapi.staticfiles import StaticFiles
8
- from fastapi.responses import FileResponse
9
  from fastapi.middleware.cors import CORSMiddleware
 
 
10
  import uvicorn
11
  import os
12
  from pathlib import Path
13
  import mimetypes
14
- from websocket_handler import websocket_endpoint
 
 
15
 
16
- from utils import log
17
- from chat_handler import router as chat_router # ← start_session & chat
 
 
 
 
18
  from admin_routes import router as admin_router, start_cleanup_task
19
- from llm_startup import run_in_thread # Changed from spark_startup
20
  from session import session_store, start_session_cleanup
21
-
22
  from config_provider import ConfigProvider
23
 
 
 
 
 
 
 
 
 
 
 
 
24
  # ===================== Environment Setup =====================
25
  def setup_environment():
26
  """Setup environment based on deployment mode"""
27
  cfg = ConfigProvider.get()
28
 
29
- log("=" * 60)
30
- log(f"πŸš€ Flare Starting")
31
- log(f"πŸ”Œ LLM Provider: {cfg.global_config.llm_provider.name}")
32
- log(f"🎀 TTS Provider: {cfg.global_config.tts_provider.name}")
33
- log(f"🎧 STT Provider: {cfg.global_config.stt_provider.name}")
34
- log("=" * 60)
35
 
36
  if cfg.global_config.is_cloud_mode():
37
- log("☁️ Cloud Mode: Using HuggingFace Secrets")
38
- log("πŸ“Œ Required secrets: JWT_SECRET, FLARE_TOKEN_KEY")
39
 
40
  # Check for provider-specific tokens
41
  llm_config = cfg.global_config.get_provider_config("llm", cfg.global_config.llm_provider.name)
42
  if llm_config and llm_config.requires_repo_info:
43
- log("πŸ“Œ LLM requires SPARK_TOKEN for repository operations")
44
  else:
45
- log("🏒 On-Premise Mode: Using .env file")
46
  if not Path(".env").exists():
47
- log("⚠️ WARNING: .env file not found!")
48
- log("πŸ“Œ Copy .env.example to .env and configure it")
49
 
50
  # Run setup
51
  setup_environment()
@@ -61,31 +79,190 @@ app = FastAPI(
61
  )
62
 
63
  # CORS for development
64
- if os.getenv("ENVIRONMENT") == "development":
65
  app.add_middleware(
66
  CORSMiddleware,
67
- allow_origins=["http://localhost:4200"], # Angular dev server
68
  allow_credentials=True,
69
- allow_methods=["*"],
70
  allow_headers=["*"],
 
 
71
  )
72
- log("πŸ”§ CORS enabled for development")
 
 
 
 
 
 
 
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  run_in_thread() # Start LLM startup notifier if needed
75
  start_cleanup_task() # Activity log cleanup
76
  start_session_cleanup() # Session cleanup
77
 
78
- # ---------------- Health probe (HF Spaces watchdog) -----------------
79
- @app.get("/")
80
- def health_check():
81
- return {"status": "ok", "version": "2.0.0"}
82
-
83
  # ---------------- Core chat/session routes --------------------------
84
  app.include_router(chat_router, prefix="/api")
85
 
86
  # ---------------- Admin API routes ----------------------------------
87
  app.include_router(admin_router)
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  # ---------------- WebSocket route for real-time STT ------------------
90
  @app.websocket("/ws/conversation/{session_id}")
91
  async def conversation_websocket(websocket: WebSocket, session_id: str):
 
2
  Flare – Main Application (Refactored)
3
  =====================================
4
  """
5
+ # FastAPI imports
6
+ from fastapi import FastAPI, WebSocket, Request, status
7
  from fastapi.staticfiles import StaticFiles
8
+ from fastapi.responses import FileResponse, JSONResponse
9
  from fastapi.middleware.cors import CORSMiddleware
10
+
11
+ # Standard library
12
  import uvicorn
13
  import os
14
  from pathlib import Path
15
  import mimetypes
16
+ import uuid
17
+ import traceback
18
+ from datetime import datetime
19
 
20
+ # Pydantic
21
+ from pydantic import ValidationError
22
+
23
+ # Project imports
24
+ from websocket_handler import websocket_endpoint
25
+ from chat_handler import router as chat_router
26
  from admin_routes import router as admin_router, start_cleanup_task
27
+ from llm_startup import run_in_thread
28
  from session import session_store, start_session_cleanup
 
29
  from config_provider import ConfigProvider
30
 
31
+ # Logger imports (utils.log yerine)
32
+ from logger import log_error, log_info, log_warning
33
+
34
+ # Exception imports
35
+ from exceptions import (
36
+ FlareException, RaceConditionError, format_error_response,
37
+ get_http_status_code
38
+ )
39
+
40
+ ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "http://localhost:4200").split(",")
41
+
42
  # ===================== Environment Setup =====================
43
  def setup_environment():
44
  """Setup environment based on deployment mode"""
45
  cfg = ConfigProvider.get()
46
 
47
+ log_info("=" * 60)
48
+ log_info("πŸš€ Flare Starting", version="2.0.0")
49
+ log_info(f"πŸ”Œ LLM Provider: {cfg.global_config.llm_provider.name}")
50
+ log_info(f"🎀 TTS Provider: {cfg.global_config.tts_provider.name}")
51
+ log_info(f"🎧 STT Provider: {cfg.global_config.stt_provider.name}")
52
+ log_info("=" * 60)
53
 
54
  if cfg.global_config.is_cloud_mode():
55
+ log_info("☁️ Cloud Mode: Using HuggingFace Secrets")
56
+ log_info("πŸ“Œ Required secrets: JWT_SECRET, FLARE_TOKEN_KEY")
57
 
58
  # Check for provider-specific tokens
59
  llm_config = cfg.global_config.get_provider_config("llm", cfg.global_config.llm_provider.name)
60
  if llm_config and llm_config.requires_repo_info:
61
+ log_info("πŸ“Œ LLM requires SPARK_TOKEN for repository operations")
62
  else:
63
+ log_info("🏒 On-Premise Mode: Using .env file")
64
  if not Path(".env").exists():
65
+ log_warning("⚠️ WARNING: .env file not found!")
66
+ log_info("πŸ“Œ Copy .env.example to .env and configure it")
67
 
68
  # Run setup
69
  setup_environment()
 
79
  )
80
 
81
  # CORS for development
82
+ if os.getenv("ENVIRONMENT", "development") == "development":
83
  app.add_middleware(
84
  CORSMiddleware,
85
+ allow_origins=ALLOWED_ORIGINS,
86
  allow_credentials=True,
87
+ allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
88
  allow_headers=["*"],
89
+ max_age=3600,
90
+ expose_headers=["X-Request-ID"]
91
  )
92
+ log_info(f"πŸ”§ CORS enabled for origins: {ALLOWED_ORIGINS}")
93
+
94
+ # Request ID middleware
95
+ @app.middleware("http")
96
+ async def add_request_id(request: Request, call_next):
97
+ """Add request ID for tracking"""
98
+ request_id = str(uuid.uuid4())
99
+ request.state.request_id = request_id
100
 
101
+ # Log request start
102
+ log_info(
103
+ "Request started",
104
+ request_id=request_id,
105
+ method=request.method,
106
+ path=request.url.path,
107
+ client=request.client.host if request.client else "unknown"
108
+ )
109
+
110
+ try:
111
+ response = await call_next(request)
112
+
113
+ # Add request ID to response headers
114
+ response.headers["X-Request-ID"] = request_id
115
+
116
+ # Log request completion
117
+ log_info(
118
+ "Request completed",
119
+ request_id=request_id,
120
+ status_code=response.status_code,
121
+ method=request.method,
122
+ path=request.url.path
123
+ )
124
+
125
+ return response
126
+ except Exception as e:
127
+ log_error(
128
+ "Request failed",
129
+ request_id=request_id,
130
+ error=str(e),
131
+ traceback=traceback.format_exc()
132
+ )
133
+ raise
134
+
135
  run_in_thread() # Start LLM startup notifier if needed
136
  start_cleanup_task() # Activity log cleanup
137
  start_session_cleanup() # Session cleanup
138
 
 
 
 
 
 
139
  # ---------------- Core chat/session routes --------------------------
140
  app.include_router(chat_router, prefix="/api")
141
 
142
  # ---------------- Admin API routes ----------------------------------
143
  app.include_router(admin_router)
144
 
145
+ # Global exception handler
146
+ @app.exception_handler(Exception)
147
+ async def global_exception_handler(request: Request, exc: Exception):
148
+ """Handle all unhandled exceptions"""
149
+ request_id = getattr(request.state, 'request_id', 'unknown')
150
+
151
+ # Log the full exception
152
+ log_error(
153
+ "Unhandled exception",
154
+ request_id=request_id,
155
+ endpoint=str(request.url),
156
+ method=request.method,
157
+ error=str(exc),
158
+ error_type=type(exc).__name__,
159
+ traceback=traceback.format_exc()
160
+ )
161
+
162
+ # Special handling for FlareExceptions
163
+ if isinstance(exc, FlareException):
164
+ status_code = get_http_status_code(exc)
165
+ response_body = format_error_response(exc, request_id)
166
+
167
+ # Special message for race conditions
168
+ if isinstance(exc, RaceConditionError):
169
+ response_body["user_action"] = "Please reload the data and try again"
170
+
171
+ return JSONResponse(
172
+ status_code=status_code,
173
+ content=response_body
174
+ )
175
+
176
+ # Generic error response
177
+ return JSONResponse(
178
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
179
+ content={
180
+ "error": "InternalServerError",
181
+ "message": "An unexpected error occurred. Please try again later.",
182
+ "request_id": request_id,
183
+ "timestamp": datetime.utcnow().isoformat()
184
+ }
185
+ )
186
+
187
+ # Validation error handler
188
+
189
+ @app.exception_handler(ValidationError)
190
+ async def validation_exception_handler(request: Request, exc: ValidationError):
191
+ """Handle Pydantic validation errors"""
192
+ request_id = getattr(request.state, 'request_id', 'unknown')
193
+
194
+ errors = []
195
+ for error in exc.errors():
196
+ field = " -> ".join(str(x) for x in error['loc'])
197
+ errors.append({
198
+ 'field': field,
199
+ 'message': error['msg'],
200
+ 'type': error['type'],
201
+ 'input': error.get('input')
202
+ })
203
+
204
+ log_warning(
205
+ "Validation error",
206
+ request_id=request_id,
207
+ errors=errors
208
+ )
209
+
210
+ return JSONResponse(
211
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
212
+ content={
213
+ "error": "ValidationError",
214
+ "message": "Invalid request data. Please check the fields and try again.",
215
+ "details": errors,
216
+ "request_id": request_id,
217
+ "timestamp": datetime.utcnow().isoformat()
218
+ }
219
+ )
220
+
221
+ # ---------------- Metrics endpoint -----------------
222
+ @app.get("/metrics")
223
+ async def get_metrics():
224
+ """Get system metrics"""
225
+ import psutil
226
+ import gc
227
+
228
+ # Memory info
229
+ process = psutil.Process()
230
+ memory_info = process.memory_info()
231
+
232
+ # Session stats
233
+ session_stats = session_store.get_session_stats()
234
+
235
+ metrics = {
236
+ "memory": {
237
+ "rss_mb": memory_info.rss / 1024 / 1024,
238
+ "vms_mb": memory_info.vms / 1024 / 1024,
239
+ "percent": process.memory_percent()
240
+ },
241
+ "cpu": {
242
+ "percent": process.cpu_percent(interval=0.1),
243
+ "num_threads": process.num_threads()
244
+ },
245
+ "sessions": session_stats,
246
+ "gc": {
247
+ "collections": gc.get_count(),
248
+ "objects": len(gc.get_objects())
249
+ },
250
+ "uptime_seconds": time.time() - process.create_time()
251
+ }
252
+
253
+ return metrics
254
+
255
+ # ---------------- Health probe (HF Spaces watchdog) -----------------
256
+ @app.get("/")
257
+ def health_check():
258
+ """Health check with detailed status"""
259
+ return {
260
+ "status": "ok",
261
+ "version": "2.0.0",
262
+ "timestamp": datetime.utcnow().isoformat(),
263
+ "environment": os.getenv("ENVIRONMENT", "development")
264
+ }
265
+
266
  # ---------------- WebSocket route for real-time STT ------------------
267
  @app.websocket("/ws/conversation/{session_id}")
268
  async def conversation_websocket(websocket: WebSocket, session_id: str):