ciyidogan commited on
Commit
2e36e90
Β·
verified Β·
1 Parent(s): 040f180

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +176 -27
app.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
- Flare – Main Application (Refactored)
3
- =====================================
4
  """
5
  # FastAPI imports
6
  from fastapi import FastAPI, WebSocket, Request, status
@@ -17,15 +17,12 @@ import mimetypes
17
  import uuid
18
  import traceback
19
  from datetime import datetime
 
 
20
  from pydantic import ValidationError
21
  from dotenv import load_dotenv
22
 
23
- # Project imports
24
- from routes.websocket_handler import websocket_endpoint
25
- from routes.admin_routes import router as admin_router, start_cleanup_task
26
- from llm.llm_startup import run_in_thread
27
- from session import session_store, start_session_cleanup
28
- from config.config_provider import ConfigProvider
29
  from event_bus import event_bus
30
  from state_orchestrator import StateOrchestrator
31
  from websocket_manager import WebSocketManager
@@ -35,19 +32,26 @@ from tts_lifecycle_manager import TTSLifecycleManager
35
  from llm_manager import LLMManager
36
  from audio_buffer_manager import AudioBufferManager
37
 
38
- # Logger imports (utils.log yerine)
 
 
 
 
 
 
39
  from utils.logger import log_error, log_info, log_warning
40
 
41
  # Exception imports
42
  from utils.exceptions import (
43
  DuplicateResourceError,
44
  RaceConditionError,
45
- ValidationError,
46
  ResourceNotFoundError,
47
  AuthenticationError,
48
  AuthorizationError,
49
  ConfigurationError,
50
- get_http_status_code
 
51
  )
52
 
53
  # Load .env file if exists
@@ -148,9 +152,71 @@ async def add_request_id(request: Request, call_next):
148
  )
149
  raise
150
 
151
- run_in_thread() # Start LLM startup notifier if needed
152
- start_cleanup_task() # Activity log cleanup
153
- start_session_cleanup() # Session cleanup
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
  # ---------------- Core chat/session routes --------------------------
156
  from routes.chat_handler import router as chat_router
@@ -163,8 +229,59 @@ app.include_router(audio_router, prefix="/api")
163
  # ---------------- Admin API routes ----------------------------------
164
  app.include_router(admin_router, prefix="/api/admin")
165
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  # ---------------- Exception Handlers ----------------------------------
167
- # Add global exception handler
168
  @app.exception_handler(Exception)
169
  async def global_exception_handler(request: Request, exc: Exception):
170
  """Handle all unhandled exceptions"""
@@ -184,7 +301,13 @@ async def global_exception_handler(request: Request, exc: Exception):
184
  # Special handling for FlareExceptions
185
  if isinstance(exc, FlareException):
186
  status_code = get_http_status_code(exc)
187
- response_body = format_error_response(exc, request_id)
 
 
 
 
 
 
188
 
189
  # Special message for race conditions
190
  if isinstance(exc, RaceConditionError):
@@ -228,8 +351,8 @@ async def race_condition_handler(request: Request, exc: RaceConditionError):
228
  content=exc.to_http_detail()
229
  )
230
 
231
- @app.exception_handler(ValidationError)
232
- async def validation_error_handler(request: Request, exc: ValidationError):
233
  """Handle validation errors"""
234
  return JSONResponse(
235
  status_code=422,
@@ -290,7 +413,7 @@ async def configuration_error_handler(request: Request, exc: ConfigurationError)
290
  # ---------------- Metrics endpoint -----------------
291
  @app.get("/metrics")
292
  async def get_metrics():
293
- """Get system metrics"""
294
  import psutil
295
  import gc
296
 
@@ -300,6 +423,23 @@ async def get_metrics():
300
 
301
  # Session stats
302
  session_stats = session_store.get_session_stats()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
  metrics = {
305
  "memory": {
@@ -312,6 +452,7 @@ async def get_metrics():
312
  "num_threads": process.num_threads()
313
  },
314
  "sessions": session_stats,
 
315
  "gc": {
316
  "collections": gc.get_count(),
317
  "objects": len(gc.get_objects())
@@ -325,18 +466,26 @@ async def get_metrics():
325
  @app.get("/api/health")
326
  def health_check():
327
  """Health check endpoint - moved to /api/health"""
 
 
 
328
  return {
329
- "status": "ok",
330
  "version": "2.0.0",
331
  "timestamp": datetime.utcnow().isoformat(),
332
- "environment": os.getenv("ENVIRONMENT", "development")
 
 
 
 
 
 
 
 
 
 
333
  }
334
 
335
- # ---------------- WebSocket route for real-time STT ------------------
336
- @app.websocket("/ws/conversation/{session_id}")
337
- async def conversation_websocket(websocket: WebSocket, session_id: str):
338
- await websocket_endpoint(websocket, session_id)
339
-
340
  # ---------------- Serve static files ------------------------------------
341
  # UI static files (production build)
342
  static_path = Path(__file__).parent / "static"
@@ -393,4 +542,4 @@ else:
393
 
394
  if __name__ == "__main__":
395
  log_info("🌐 Starting Flare backend on port 7860...")
396
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  """
2
+ Flare – Main Application (Refactored with Event-Driven Architecture)
3
+ ====================================================================
4
  """
5
  # FastAPI imports
6
  from fastapi import FastAPI, WebSocket, Request, status
 
17
  import uuid
18
  import traceback
19
  from datetime import datetime
20
+ import asyncio
21
+ import time
22
  from pydantic import ValidationError
23
  from dotenv import load_dotenv
24
 
25
+ # Event-driven architecture imports
 
 
 
 
 
26
  from event_bus import event_bus
27
  from state_orchestrator import StateOrchestrator
28
  from websocket_manager import WebSocketManager
 
32
  from llm_manager import LLMManager
33
  from audio_buffer_manager import AudioBufferManager
34
 
35
+ # Project imports
36
+ from routes.admin_routes import router as admin_router, start_cleanup_task
37
+ from llm.llm_startup import run_in_thread
38
+ from session import session_store, start_session_cleanup
39
+ from config.config_provider import ConfigProvider
40
+
41
+ # Logger imports
42
  from utils.logger import log_error, log_info, log_warning
43
 
44
  # Exception imports
45
  from utils.exceptions import (
46
  DuplicateResourceError,
47
  RaceConditionError,
48
+ ValidationError as FlareValidationError,
49
  ResourceNotFoundError,
50
  AuthenticationError,
51
  AuthorizationError,
52
  ConfigurationError,
53
+ get_http_status_code,
54
+ FlareException
55
  )
56
 
57
  # Load .env file if exists
 
152
  )
153
  raise
154
 
155
+ # ===================== Event-Driven Architecture Initialization =====================
156
+ @app.on_event("startup")
157
+ async def startup_event():
158
+ """Initialize event-driven components on startup"""
159
+ try:
160
+ # Initialize event bus
161
+ await event_bus.start()
162
+ log_info("βœ… Event bus started")
163
+
164
+ # Initialize resource manager
165
+ resource_manager = ResourceManager(event_bus)
166
+ await resource_manager.start()
167
+ log_info("βœ… Resource manager started")
168
+
169
+ # Initialize managers
170
+ state_orchestrator = StateOrchestrator(event_bus)
171
+ websocket_manager = WebSocketManager(event_bus)
172
+ audio_buffer_manager = AudioBufferManager(event_bus)
173
+ stt_manager = STTLifecycleManager(event_bus, resource_manager)
174
+ tts_manager = TTSLifecycleManager(event_bus, resource_manager)
175
+ llm_manager = LLMManager(event_bus, resource_manager)
176
+
177
+ # Store in app state for access in routes
178
+ app.state.event_bus = event_bus
179
+ app.state.resource_manager = resource_manager
180
+ app.state.state_orchestrator = state_orchestrator
181
+ app.state.websocket_manager = websocket_manager
182
+ app.state.audio_buffer_manager = audio_buffer_manager
183
+ app.state.stt_manager = stt_manager
184
+ app.state.tts_manager = tts_manager
185
+ app.state.llm_manager = llm_manager
186
+
187
+ log_info("βœ… All managers initialized")
188
+
189
+ # Start existing background tasks
190
+ run_in_thread() # Start LLM startup notifier if needed
191
+ start_cleanup_task() # Activity log cleanup
192
+ start_session_cleanup() # Session cleanup
193
+
194
+ log_info("βœ… Background tasks started")
195
+
196
+ except Exception as e:
197
+ log_error("❌ Failed to start event-driven components", error=str(e), traceback=traceback.format_exc())
198
+ raise
199
+
200
+ @app.on_event("shutdown")
201
+ async def shutdown_event():
202
+ """Cleanup event-driven components on shutdown"""
203
+ try:
204
+ # Stop event bus
205
+ await event_bus.stop()
206
+ log_info("βœ… Event bus stopped")
207
+
208
+ # Stop resource manager
209
+ if hasattr(app.state, 'resource_manager'):
210
+ await app.state.resource_manager.stop()
211
+ log_info("βœ… Resource manager stopped")
212
+
213
+ # Close all WebSocket connections
214
+ if hasattr(app.state, 'websocket_manager'):
215
+ await app.state.websocket_manager.close_all_connections()
216
+ log_info("βœ… All WebSocket connections closed")
217
+
218
+ except Exception as e:
219
+ log_error("❌ Error during shutdown", error=str(e))
220
 
221
  # ---------------- Core chat/session routes --------------------------
222
  from routes.chat_handler import router as chat_router
 
229
  # ---------------- Admin API routes ----------------------------------
230
  app.include_router(admin_router, prefix="/api/admin")
231
 
232
+ # ---------------- WebSocket route for real-time chat ------------------
233
+ @app.websocket("/ws/conversation/{session_id}")
234
+ async def websocket_route(websocket: WebSocket, session_id: str):
235
+ """Handle WebSocket connections using the new WebSocketManager"""
236
+ if hasattr(app.state, 'websocket_manager'):
237
+ await app.state.websocket_manager.handle_connection(websocket, session_id)
238
+ else:
239
+ log_error("WebSocketManager not initialized")
240
+ await websocket.close(code=1011, reason="Server not ready")
241
+
242
+ # ---------------- Test endpoint for event-driven flow ------------------
243
+ @app.post("/api/test/realtime")
244
+ async def test_realtime():
245
+ """Test endpoint for event-driven realtime flow"""
246
+ from event_bus import Event, EventType
247
+
248
+ try:
249
+ # Create a test session
250
+ session = session_store.create_session(
251
+ project_name="kronos_jet",
252
+ version_no=1,
253
+ is_realtime=True
254
+ )
255
+
256
+ # Get version config
257
+ cfg = ConfigProvider.get()
258
+ project = next((p for p in cfg.projects if p.name == "kronos_jet"), None)
259
+ if project:
260
+ version = next((v for v in project.versions if v.no == 1), None)
261
+ if version:
262
+ session.set_version_config(version)
263
+
264
+ # Publish session started event
265
+ await app.state.event_bus.publish(Event(
266
+ type=EventType.SESSION_STARTED,
267
+ session_id=session.session_id,
268
+ data={
269
+ "session": session,
270
+ "has_welcome": bool(version and version.welcome_prompt),
271
+ "welcome_text": version.welcome_prompt if version and version.welcome_prompt else "Hoş geldiniz!"
272
+ }
273
+ ))
274
+
275
+ return {
276
+ "session_id": session.session_id,
277
+ "message": "Test session created. Connect via WebSocket to continue."
278
+ }
279
+
280
+ except Exception as e:
281
+ log_error("Test endpoint error", error=str(e))
282
+ raise HTTPException(500, f"Test failed: {str(e)}")
283
+
284
  # ---------------- Exception Handlers ----------------------------------
 
285
  @app.exception_handler(Exception)
286
  async def global_exception_handler(request: Request, exc: Exception):
287
  """Handle all unhandled exceptions"""
 
301
  # Special handling for FlareExceptions
302
  if isinstance(exc, FlareException):
303
  status_code = get_http_status_code(exc)
304
+ response_body = {
305
+ "error": type(exc).__name__,
306
+ "message": str(exc),
307
+ "request_id": request_id,
308
+ "timestamp": datetime.utcnow().isoformat(),
309
+ "details": getattr(exc, 'details', {})
310
+ }
311
 
312
  # Special message for race conditions
313
  if isinstance(exc, RaceConditionError):
 
351
  content=exc.to_http_detail()
352
  )
353
 
354
+ @app.exception_handler(FlareValidationError)
355
+ async def validation_error_handler(request: Request, exc: FlareValidationError):
356
  """Handle validation errors"""
357
  return JSONResponse(
358
  status_code=422,
 
413
  # ---------------- Metrics endpoint -----------------
414
  @app.get("/metrics")
415
  async def get_metrics():
416
+ """Get system metrics including event-driven components"""
417
  import psutil
418
  import gc
419
 
 
423
 
424
  # Session stats
425
  session_stats = session_store.get_session_stats()
426
+
427
+ # Event-driven component stats
428
+ event_stats = {}
429
+ if hasattr(app.state, 'stt_manager'):
430
+ event_stats['stt'] = app.state.stt_manager.get_stats()
431
+ if hasattr(app.state, 'tts_manager'):
432
+ event_stats['tts'] = app.state.tts_manager.get_stats()
433
+ if hasattr(app.state, 'llm_manager'):
434
+ event_stats['llm'] = app.state.llm_manager.get_stats()
435
+ if hasattr(app.state, 'websocket_manager'):
436
+ event_stats['websocket'] = {
437
+ 'active_connections': app.state.websocket_manager.get_connection_count()
438
+ }
439
+ if hasattr(app.state, 'resource_manager'):
440
+ event_stats['resources'] = app.state.resource_manager.get_stats()
441
+ if hasattr(app.state, 'audio_buffer_manager'):
442
+ event_stats['audio_buffers'] = app.state.audio_buffer_manager.get_all_stats()
443
 
444
  metrics = {
445
  "memory": {
 
452
  "num_threads": process.num_threads()
453
  },
454
  "sessions": session_stats,
455
+ "event_driven_components": event_stats,
456
  "gc": {
457
  "collections": gc.get_count(),
458
  "objects": len(gc.get_objects())
 
466
  @app.get("/api/health")
467
  def health_check():
468
  """Health check endpoint - moved to /api/health"""
469
+ # Check if event-driven components are healthy
470
+ event_bus_healthy = hasattr(app.state, 'event_bus') and app.state.event_bus._running
471
+
472
  return {
473
+ "status": "ok" if event_bus_healthy else "degraded",
474
  "version": "2.0.0",
475
  "timestamp": datetime.utcnow().isoformat(),
476
+ "environment": os.getenv("ENVIRONMENT", "development"),
477
+ "event_driven": {
478
+ "event_bus": "running" if event_bus_healthy else "not_running",
479
+ "managers": {
480
+ "state_orchestrator": "initialized" if hasattr(app.state, 'state_orchestrator') else "not_initialized",
481
+ "websocket_manager": "initialized" if hasattr(app.state, 'websocket_manager') else "not_initialized",
482
+ "stt_manager": "initialized" if hasattr(app.state, 'stt_manager') else "not_initialized",
483
+ "tts_manager": "initialized" if hasattr(app.state, 'tts_manager') else "not_initialized",
484
+ "llm_manager": "initialized" if hasattr(app.state, 'llm_manager') else "not_initialized"
485
+ }
486
+ }
487
  }
488
 
 
 
 
 
 
489
  # ---------------- Serve static files ------------------------------------
490
  # UI static files (production build)
491
  static_path = Path(__file__).parent / "static"
 
542
 
543
  if __name__ == "__main__":
544
  log_info("🌐 Starting Flare backend on port 7860...")
545
+ uvicorn.run(app, host="0.0.0.0", port=7860)