Spaces:
Sleeping
Sleeping
Delete app.py
Browse files
app.py
DELETED
@@ -1,202 +0,0 @@
|
|
1 |
-
import logging
|
2 |
-
import asyncio
|
3 |
-
import uuid
|
4 |
-
from datetime import datetime
|
5 |
-
from pathlib import Path
|
6 |
-
from typing import Dict, Optional
|
7 |
-
|
8 |
-
from fastapi import FastAPI, HTTPException, Request
|
9 |
-
from fastapi.middleware.cors import CORSMiddleware
|
10 |
-
from fastapi.responses import JSONResponse
|
11 |
-
from pydantic import BaseModel, constr, Field, validator, constr
|
12 |
-
from rag import generate_answer # Importing the function to generate answers
|
13 |
-
|
14 |
-
# Create logs directory if it doesn't exist
|
15 |
-
Path("logs").mkdir(exist_ok=True) # Ensure logs directory is available for logging
|
16 |
-
|
17 |
-
# Initialize FastAPI app with metadata
|
18 |
-
app = FastAPI(
|
19 |
-
title="Question Answering API", # Title of the API
|
20 |
-
description="API for generating answers using RAG", # Description of the API
|
21 |
-
version="1.0.0", # Version of the API
|
22 |
-
docs_url="/docs", # URL for API documentation
|
23 |
-
redoc_url="/redoc" # URL for ReDoc documentation
|
24 |
-
)
|
25 |
-
|
26 |
-
# Configure CORS
|
27 |
-
app.add_middleware(
|
28 |
-
CORSMiddleware,
|
29 |
-
allow_origins=["*"], # Allow all origins; modify in production for security
|
30 |
-
allow_credentials=True, # Allow credentials to be included in requests
|
31 |
-
allow_methods=["*"], # Allow all HTTP methods
|
32 |
-
allow_headers=["*"], # Allow all headers
|
33 |
-
)
|
34 |
-
|
35 |
-
# Configure detailed logging
|
36 |
-
logging.basicConfig(
|
37 |
-
level=logging.INFO, # Set logging level to INFO
|
38 |
-
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # Log format
|
39 |
-
handlers=[ # Handlers for logging output
|
40 |
-
logging.StreamHandler(), # Log to console
|
41 |
-
logging.FileHandler(f'logs/app_{datetime.now().strftime("%Y%m%d")}.log') # Log to file
|
42 |
-
]
|
43 |
-
)
|
44 |
-
logger = logging.getLogger(__name__) # Create a logger for this module
|
45 |
-
|
46 |
-
def generate_request_id() -> str:
|
47 |
-
"""Generate a unique request ID.""" # Docstring for function
|
48 |
-
return str(uuid.uuid4()) # Return a new UUID as a string
|
49 |
-
|
50 |
-
class QuestionRequest(BaseModel):
|
51 |
-
"""Request model for question answering endpoint.""" # Docstring for model
|
52 |
-
question: constr(min_length=1) = Field( # Field for the question with validation
|
53 |
-
...,
|
54 |
-
description="The question to be answered", # Description of the field
|
55 |
-
example="What is the capital of France?" # Example question
|
56 |
-
)
|
57 |
-
|
58 |
-
@validator('question') # Validator for the question field
|
59 |
-
def validate_question(cls, v):
|
60 |
-
if v.strip() == "": # Check if the question is empty
|
61 |
-
raise HTTPException(
|
62 |
-
status_code=500, # Raise HTTP 500 if empty
|
63 |
-
detail="Error processing request" # Error message
|
64 |
-
)
|
65 |
-
return v.strip() # Return the trimmed question
|
66 |
-
|
67 |
-
class ErrorResponse(BaseModel):
|
68 |
-
"""Standard error response model.""" # Docstring for error response model
|
69 |
-
detail: str # Detail of the error
|
70 |
-
request_id: Optional[str] = None # Optional request ID for tracking
|
71 |
-
|
72 |
-
@app.middleware("http")
|
73 |
-
async def add_request_id(request: Request, call_next):
|
74 |
-
"""Middleware to add request ID to all requests.""" # Docstring for middleware
|
75 |
-
request_id = generate_request_id() # Generate a new request ID
|
76 |
-
request.state.request_id = request_id # Store request ID in request state
|
77 |
-
response = await call_next(request) # Process the request
|
78 |
-
response.headers["X-Request-ID"] = request_id # Add request ID to response headers
|
79 |
-
return response # Return the response
|
80 |
-
|
81 |
-
@app.exception_handler(HTTPException)
|
82 |
-
async def http_exception_handler(request: Request, exc: HTTPException):
|
83 |
-
"""Custom exception handler to include request ID.""" # Docstring for exception handler
|
84 |
-
return JSONResponse(
|
85 |
-
status_code=exc.status_code, # Return the status code from the exception
|
86 |
-
content={"detail": exc.detail, "request_id": request.state.request_id} # Include error details and request ID
|
87 |
-
)
|
88 |
-
|
89 |
-
@app.post("/answer",
|
90 |
-
response_model=Dict[str, str], # Response model for the endpoint
|
91 |
-
summary="Generate an answer for the given question", # Summary of the endpoint
|
92 |
-
response_description="Returns the generated answer", # Description of the response
|
93 |
-
responses={ # Possible responses
|
94 |
-
400: {"model": ErrorResponse}, # Bad request response
|
95 |
-
500: {"model": ErrorResponse}, # Internal server error response
|
96 |
-
504: {"model": ErrorResponse} # Gateway timeout response
|
97 |
-
}
|
98 |
-
)
|
99 |
-
async def get_answer(request: Request, question_request: QuestionRequest) -> Dict[str, str]:
|
100 |
-
"""
|
101 |
-
Generate an answer for the given question.
|
102 |
-
|
103 |
-
Args:
|
104 |
-
request: FastAPI request object # Description of request argument
|
105 |
-
question_request: The question request model # Description of question_request argument
|
106 |
-
|
107 |
-
Returns:
|
108 |
-
Dict containing the generated answer # Description of return value
|
109 |
-
|
110 |
-
Raises:
|
111 |
-
HTTPException: For various error conditions # Description of possible exceptions
|
112 |
-
"""
|
113 |
-
request_id = request.state.request_id # Retrieve request ID from state
|
114 |
-
logger.info("Processing request %s - Question: %s", request_id, question_request.question) # Log the processing request
|
115 |
-
|
116 |
-
try:
|
117 |
-
# Additional validation
|
118 |
-
if len(question_request.question) > 1000: # Check if question exceeds max length
|
119 |
-
logger.warning("Request %s: Question exceeds maximum length", request_id) # Log warning
|
120 |
-
raise HTTPException(
|
121 |
-
status_code=400, # Raise HTTP 400 for bad request
|
122 |
-
detail="Question length exceeds maximum allowed characters (1000)" # Error message
|
123 |
-
)
|
124 |
-
|
125 |
-
# Convert generate_answer to async if it's not already
|
126 |
-
async def async_generate_answer(question: str):
|
127 |
-
loop = asyncio.get_event_loop() # Get the current event loop
|
128 |
-
return await loop.run_in_executor(None, generate_answer, question) # Run the generate_answer function in executor
|
129 |
-
|
130 |
-
# Generate answer with timeout handling
|
131 |
-
answer = await asyncio.wait_for(
|
132 |
-
async_generate_answer(question_request.question), # Await the answer generation
|
133 |
-
timeout=120.0 # 120 second timeout
|
134 |
-
)
|
135 |
-
|
136 |
-
logger.info("Request %s: Successfully generated answer", request_id) # Log successful answer generation
|
137 |
-
return {"answer": answer} # Return the generated answer
|
138 |
-
|
139 |
-
except asyncio.TimeoutError: # Handle timeout errors
|
140 |
-
logger.error("Request %s: Generation timeout", request_id) # Log timeout error
|
141 |
-
raise HTTPException(
|
142 |
-
status_code=504, # Raise HTTP 504 for timeout
|
143 |
-
detail="Answer generation timed out" # Error message
|
144 |
-
)
|
145 |
-
|
146 |
-
except ValueError as ve: # Handle value errors
|
147 |
-
logger.error("Request %s: Validation error - %s", request_id, str(ve)) # Log validation error
|
148 |
-
raise HTTPException(
|
149 |
-
status_code=400, # Raise HTTP 400 for bad request
|
150 |
-
detail=str(ve) # Return the validation error message
|
151 |
-
)
|
152 |
-
|
153 |
-
except Exception as e: # Handle all other exceptions
|
154 |
-
logger.error(
|
155 |
-
"Request %s: Unexpected error - %s",
|
156 |
-
request_id,
|
157 |
-
str(e),
|
158 |
-
exc_info=True # Include stack trace in logs
|
159 |
-
)
|
160 |
-
raise HTTPException(
|
161 |
-
status_code=500, # Raise HTTP 500 for internal server error
|
162 |
-
detail="Internal server error occurred. Please try again later." # Error message
|
163 |
-
)
|
164 |
-
|
165 |
-
@app.get("/health",
|
166 |
-
summary="Health check endpoint", # Summary of the health check endpoint
|
167 |
-
response_description="Returns the API health status" # Description of the response
|
168 |
-
)
|
169 |
-
async def health_check():
|
170 |
-
"""Health check endpoint to verify API is running.""" # Docstring for health check
|
171 |
-
return {"status": "healthy"} # Return health status
|
172 |
-
|
173 |
-
@app.on_event("startup")
|
174 |
-
async def startup_event():
|
175 |
-
"""Initialize any resources on startup.""" # Docstring for startup event
|
176 |
-
logger.info("Starting up Question Answering API") # Log startup event
|
177 |
-
# Add any initialization code here (e.g., loading models)
|
178 |
-
|
179 |
-
@app.on_event("shutdown")
|
180 |
-
async def shutdown_event():
|
181 |
-
"""Cleanup any resources on shutdown.""" # Docstring for shutdown event
|
182 |
-
logger.info("Shutting down Question Answering API") # Log shutdown event
|
183 |
-
# Add cleanup code here
|
184 |
-
|
185 |
-
if __name__ == "__main__": # Entry point for the application
|
186 |
-
import uvicorn # Import Uvicorn for running the app
|
187 |
-
|
188 |
-
# Load configuration from environment variables or use defaults
|
189 |
-
host = "0.0.0.0" # Host address
|
190 |
-
port = 8000 # Port number
|
191 |
-
|
192 |
-
logger.info(f"Starting server on {host}:{port}") # Log server start
|
193 |
-
|
194 |
-
uvicorn.run(
|
195 |
-
"app:app", # Application to run
|
196 |
-
host=host, # Host address
|
197 |
-
port=port, # Port number
|
198 |
-
reload=False, # Disable auto-reload in production
|
199 |
-
log_level="info", # Set log level
|
200 |
-
access_log=True, # Enable access logging
|
201 |
-
workers=1 # Number of worker processes
|
202 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|