| |
|
|
| import os |
| import logging |
| import asyncio |
| from pathlib import Path |
| from contextlib import asynccontextmanager |
|
|
| import uvicorn |
| from fastapi import FastAPI |
| from fastapi.staticfiles import StaticFiles |
| from fastapi.responses import FileResponse, RedirectResponse |
|
|
| from core.config import ConfigManager |
| from core.token_manager import TokenManager |
| from core.log_store import LogStore |
| from core.conversation_cache import ConversationCache |
| from core.ratelimit import RateLimiter |
| from routes import openai_compat, admin_api, claude_api, system_api, mcp_api |
|
|
| logging.basicConfig( |
| level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s" |
| ) |
| logger = logging.getLogger("noke") |
|
|
| cfg = ConfigManager() |
| token_manager = TokenManager(cfg) |
| log_store = LogStore(max_entries=cfg.get("logging", "max_entries", default=500)) |
|
|
| |
| conv_cache = ConversationCache(max_size=2000, ttl=1800) |
| limiter = RateLimiter( |
| qps=cfg.get("proxy", "rate_limit_qps", default=0), |
| burst=cfg.get("proxy", "rate_limit_burst", default=0), |
| ) |
|
|
| |
| openai_compat.init(token_manager, cfg, log_store, conv_cache, limiter) |
| admin_api.init(cfg, token_manager, log_store) |
| claude_api.init(token_manager, cfg, log_store, conv_cache, limiter) |
| system_api.init(token_manager, cfg, conv_cache, limiter) |
| mcp_api.init(token_manager, cfg) |
|
|
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| logger.info( |
| "Noke started — tokens: %d, port: %d", |
| len(cfg.get("tokens", default=[])), |
| int(os.environ.get("PORT", cfg.get("server", "port", default=7860))), |
| ) |
|
|
| |
| async def _periodic_flush(): |
| while True: |
| await asyncio.sleep(60) |
| token_manager.flush() |
| flush_task = asyncio.create_task(_periodic_flush()) |
|
|
| yield |
|
|
| flush_task.cancel() |
| try: |
| await flush_task |
| except asyncio.CancelledError: |
| pass |
| await token_manager.close_all() |
|
|
|
|
| app = FastAPI(lifespan=lifespan) |
|
|
| |
| @app.exception_handler(Exception) |
| async def global_exception_handler(request, exc): |
| import traceback |
| error_detail = f"{type(exc).__name__}: {str(exc)}\n{traceback.format_exc()}" |
| logger.error("Unhandled exception: %s", error_detail) |
| from fastapi.responses import JSONResponse |
| return JSONResponse( |
| status_code=500, |
| content={"detail": "internal server error"} |
| ) |
|
|
| app.include_router(claude_api.router) |
| app.include_router(openai_compat.router) |
| app.include_router(admin_api.router) |
| app.include_router(system_api.router) |
| app.include_router(mcp_api.router) |
|
|
| static_dir = Path(__file__).parent / "static" |
| app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") |
|
|
|
|
| @app.get("/") |
| async def root(): |
| return RedirectResponse(url="/admin") |
|
|
|
|
| |
| @app.get("/health") |
| async def health_legacy(): |
| return {"status": "ok"} |
|
|
|
|
| @app.get("/admin") |
| async def admin_page(): |
| return FileResponse(str(static_dir / "index.html")) |
|
|
|
|
| if __name__ == "__main__": |
| import urllib3 |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) |
| port = int(os.environ.get("PORT", 7860)) |
| uvicorn.run( |
| app, |
| host=cfg.get("server", "host", default="0.0.0.0"), |
| port=port, |
| ) |
|
|