11import os
22import time
33import logging
4- from fastapi import FastAPI , HTTPException
4+ import json
5+ import uuid
6+ from fastapi import FastAPI , HTTPException , Request
57from pydantic import BaseModel
68from src .config import settings
79
8- # Setup logging
9- logging .basicConfig (level = logging .INFO )
10- logger = logging .getLogger (__name__ )
10+ # Structured JSON Logging Setup
11+ class JsonFormatter (logging .Formatter ):
12+ def format (self , record ):
13+ log_record = {
14+ "timestamp" : self .formatTime (record , self .datefmt ),
15+ "level" : record .levelname ,
16+ "message" : record .getMessage (),
17+ "module" : record .module ,
18+ "request_id" : getattr (record , "request_id" , "N/A" )
19+ }
20+ return json .dumps (log_record )
21+
22+ logger = logging .getLogger ("api_logger" )
23+ handler = logging .StreamHandler ()
24+ handler .setFormatter (JsonFormatter ())
25+ logger .addHandler (handler )
26+ logger .setLevel (logging .INFO )
1127
1228app = FastAPI (title = "Mistral Domain Support Assistant API" )
1329
@@ -22,9 +38,19 @@ class GenerateRequest(BaseModel):
2238
2339class GenerateResponse (BaseModel ):
2440 response : str
25- latency : float
41+ latency_seconds : float
2642 tokens_generated : int
2743 model_name : str
44+ request_id : str
45+ metadata : dict
46+
47+ @app .middleware ("http" )
48+ async def add_request_id (request : Request , call_next ):
49+ request_id = str (uuid .uuid4 ())
50+ request .state .request_id = request_id
51+ response = await call_next (request )
52+ response .headers ["X-Request-ID" ] = request_id
53+ return response
2854
2955@app .on_event ("startup" )
3056async def startup_event ():
@@ -33,35 +59,61 @@ async def startup_event():
3359 logger .info ("TESTING environment detected. Skipping real model loading." )
3460 return
3561
36- from src .inference .engine import InferenceEngine
37- adapter_path = os .getenv ("ADAPTER_PATH" , None )
38- logger .info (f"Initializing Inference Engine with adapter: { adapter_path } " )
39- engine = InferenceEngine (adapter_path = adapter_path )
62+ try :
63+ from src .inference .engine import InferenceEngine
64+ adapter_path = os .getenv ("ADAPTER_PATH" , None )
65+ logger .info (f"Initializing Inference Engine with adapter: { adapter_path } " )
66+ engine = InferenceEngine (adapter_path = adapter_path )
67+ except Exception as e :
68+ logger .error (f"Failed to load model: { str (e )} " )
4069
4170@app .post ("/generate" , response_model = GenerateResponse )
42- async def generate (request : GenerateRequest ):
71+ async def generate (request : GenerateRequest , fast_request : Request ):
72+ request_id = fast_request .state .request_id
73+
4374 if engine is None :
75+ logger .warning ("Generation attempted but model not loaded" , extra = {"request_id" : request_id })
4476 raise HTTPException (status_code = 503 , detail = "Model not loaded" )
4577
78+ logger .info (f"Processing generation request: { request .instruction [:50 ]} ..." , extra = {"request_id" : request_id })
79+
4680 try :
81+ start_time = time .time ()
4782 result = engine .generate (
4883 instruction = request .instruction ,
4984 max_new_tokens = request .max_new_tokens
5085 )
86+ total_latency = time .time () - start_time
87+
88+ logger .info ("Generation successful" , extra = {
89+ "request_id" : request_id ,
90+ "latency" : total_latency ,
91+ "tokens" : result ['tokens_generated' ]
92+ })
5193
5294 return GenerateResponse (
5395 response = result ['response' ],
54- latency = result [ 'latency' ] ,
96+ latency_seconds = round ( total_latency , 3 ) ,
5597 tokens_generated = result ['tokens_generated' ],
56- model_name = settings .BASE_MODEL_NAME
98+ model_name = settings .BASE_MODEL_NAME ,
99+ request_id = request_id ,
100+ metadata = {
101+ "temperature" : request .temperature ,
102+ "top_p" : request .top_p ,
103+ "timestamp" : time .time ()
104+ }
57105 )
58106 except Exception as e :
59- logger .error (f"Generation error: { str (e )} " )
107+ logger .error (f"Generation error: { str (e )} " , extra = { "request_id" : request_id } )
60108 raise HTTPException (status_code = 500 , detail = str (e ))
61109
62110@app .get ("/health" )
63111async def health ():
64- return {"status" : "ok" , "model_loaded" : engine is not None }
112+ return {
113+ "status" : "ok" ,
114+ "model_loaded" : engine is not None ,
115+ "environment" : "testing" if os .getenv ("TESTING" ) else "production"
116+ }
65117
66118if __name__ == "__main__" :
67119 import uvicorn
0 commit comments