|
|
"""Biomedical RAG service main program entry point.""" |
|
|
|
|
|
import importlib |
|
|
import os |
|
|
import pkgutil |
|
|
import time |
|
|
|
|
|
import uvicorn |
|
|
from dotenv import load_dotenv |
|
|
|
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
from asgi_correlation_id import CorrelationIdMiddleware, correlation_id |
|
|
from fastapi import FastAPI, Request |
|
|
from fastapi_mcp import FastApiMCP |
|
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
from slowapi import Limiter, _rate_limit_exceeded_handler |
|
|
from slowapi.util import get_remote_address |
|
|
from slowapi.errors import RateLimitExceeded |
|
|
|
|
|
from routers import sensor, mcp_sensor |
|
|
from utils.bio_logger import bio_logger as logger |
|
|
|
|
|
|
|
|
logger.info(f"SERPER_API_KEY loaded: {'Yes' if os.getenv('SERPER_API_KEY') else 'No'}") |
|
|
|
|
|
|
|
|
app = FastAPI( |
|
|
docs_url=None, |
|
|
redoc_url=None, |
|
|
openapi_url=None, |
|
|
debug=False, |
|
|
) |
|
|
|
|
|
|
|
|
limiter = Limiter(key_func=get_remote_address) |
|
|
app.state.limiter = limiter |
|
|
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) |
|
|
|
|
|
|
|
|
app.add_middleware(CorrelationIdMiddleware) |
|
|
|
|
|
app.add_middleware( |
|
|
CORSMiddleware, |
|
|
allow_origins=["*"], |
|
|
allow_credentials=True, |
|
|
allow_methods=["*"], |
|
|
allow_headers=["*"], |
|
|
) |
|
|
|
|
|
|
|
|
app.include_router(sensor.router) |
|
|
app.include_router(mcp_sensor.router) |
|
|
|
|
|
@app.get("/health") |
|
|
async def health_check(): |
|
|
"""健康检查端点""" |
|
|
return {"status": "healthy", "service": "bio-rag-mcp"} |
|
|
|
|
|
|
|
|
@app.middleware("http") |
|
|
async def add_process_time_header(request: Request, call_next): |
|
|
"""HTTP中间件,记录请求处理时间和状态。完全兼容SSE流式响应。""" |
|
|
start_time = time.time() |
|
|
|
|
|
|
|
|
is_sse_endpoint = request.url.path.startswith("/sse") |
|
|
|
|
|
logger.info(f"Request started | URL: {request.url}") |
|
|
|
|
|
try: |
|
|
response = await call_next(request) |
|
|
process_time = time.time() - start_time |
|
|
|
|
|
|
|
|
if is_sse_endpoint: |
|
|
logger.info( |
|
|
f"SSE connection established | " |
|
|
f"Time: {process_time:.2f}s" |
|
|
) |
|
|
else: |
|
|
|
|
|
try: |
|
|
status_code = getattr(response, 'status_code', 'UNKNOWN') |
|
|
logger.info( |
|
|
f"Request completed | " |
|
|
f"Status: {status_code} | " |
|
|
f"Time: {process_time:.2f}s" |
|
|
) |
|
|
except Exception as e: |
|
|
logger.warning(f"Could not get status code: {e}") |
|
|
logger.info( |
|
|
f"Request completed | " |
|
|
f"Status: UNKNOWN | " |
|
|
f"Time: {process_time:.2f}s" |
|
|
) |
|
|
|
|
|
return response |
|
|
|
|
|
except Exception as e: |
|
|
process_time = time.time() - start_time |
|
|
logger.error( |
|
|
f"Request failed | " |
|
|
f"Error: {str(e)} | " |
|
|
f"Time: {process_time:.2f}s" |
|
|
) |
|
|
raise |
|
|
|
|
|
|
|
|
def dynamic_import_subclasses(parent_dir: str) -> None: |
|
|
"""动态导入指定目录下的所有Python模块。 |
|
|
|
|
|
Args: |
|
|
parent_dir: 要导入的目录路径 |
|
|
""" |
|
|
for _, module_name, _ in pkgutil.iter_modules([parent_dir]): |
|
|
module = importlib.import_module(f"{parent_dir}.{module_name}") |
|
|
logger.info(f"Imported: {module.__name__}") |
|
|
|
|
|
|
|
|
|
|
|
mcp = FastApiMCP(app, name="bio qa mcp", include_operations=["bio_qa_stream_chat"]) |
|
|
|
|
|
|
|
|
mcp.mount_sse() |
|
|
|
|
|
if __name__ == "__main__": |
|
|
logger.info("Starting Bio RAG Server...") |
|
|
dynamic_import_subclasses("search_service") |
|
|
uvicorn.run(app, host="0.0.0.0", port=9487) |
|
|
|