jackkuo's picture
reinit repo
82bf89e
"""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, # 关闭 Swagger UI 文档
redoc_url=None, # 关闭 ReDoc 文档
openapi_url=None, # 关闭 OpenAPI 规范文件
debug=False, # 关闭调试模式
)
# 全局限流配置(按客户端IP)
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# 第一个添加的中间件
app.add_middleware(CorrelationIdMiddleware)
# 配置CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 路由
app.include_router(sensor.router)
app.include_router(mcp_sensor.router) # 包含 MCP 路由
@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()
# 检查是否为SSE端点
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
# 对于SSE端点,只记录请求开始时间,不尝试访问响应属性
if is_sse_endpoint:
logger.info(
f"SSE connection established | "
f"Time: {process_time:.2f}s"
)
else:
# 对于普通HTTP请求,安全地获取状态码
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__}")
# Add MCP server to the FastAPI app
mcp = FastApiMCP(app, name="bio qa mcp", include_operations=["bio_qa_stream_chat"])
# Mount the MCP server to the FastAPI app
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)