113 lines
3.6 KiB
Python
113 lines
3.6 KiB
Python
"""FastAPI application — entry point for the API server."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from contextlib import asynccontextmanager
|
|
|
|
import httpx
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from nsct.config import AppSettings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lifespan
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Application lifespan — startup and shutdown hooks."""
|
|
config: AppSettings = app.state.config
|
|
|
|
logger.info("NSCT API starting up — version %s", app.state.version)
|
|
|
|
# Pre-flight: validate LLM connectivity
|
|
llm_base = config.llm.base_url if config and config.llm and config.llm.base_url else ""
|
|
if llm_base:
|
|
try:
|
|
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
resp = await client.get(f"{llm_base}/models")
|
|
if resp.status_code == 200:
|
|
logger.info("LLM provider reachable — model endpoint returned 200")
|
|
else:
|
|
logger.warning("LLM provider returned %s", resp.status_code)
|
|
except Exception as exc:
|
|
logger.warning("LLM connectivity check failed: %s", exc)
|
|
|
|
yield
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# App factory
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
"""Create the FastAPI application with all routes and middleware."""
|
|
config = AppSettings.from_env()
|
|
|
|
app = FastAPI(
|
|
title="NSCT API",
|
|
description="Neutral Search Crawler Tool — API server",
|
|
version="0.1.0",
|
|
docs_url="/docs" if config.debug else None,
|
|
redoc_url="/redoc" if config.debug else None,
|
|
lifespan=lifespan,
|
|
)
|
|
app.state.config = config
|
|
app.state.version = "0.1.0"
|
|
|
|
# CORS — allow local development
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["http://localhost:3000", "http://localhost:5173", "http://127.0.0.1:3000"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Mount health router
|
|
from nsct.api.health import router as health_router
|
|
app.include_router(health_router, tags=["system"])
|
|
|
|
# Mount search router
|
|
from nsct.api.search import router as search_router
|
|
app.include_router(search_router, tags=["search"])
|
|
|
|
# Mount crawler router
|
|
from nsct.api.crawler import router as crawler_router
|
|
app.include_router(crawler_router, tags=["crawler"])
|
|
|
|
# Mount planner router
|
|
from nsct.api.planner import router as planner_router
|
|
app.include_router(planner_router, tags=["planner"])
|
|
|
|
# Mount claim extraction router (Stage 5)
|
|
from nsct.api.claims import router as claims_router
|
|
app.include_router(claims_router, tags=["research"])
|
|
|
|
# Mount claim clustering router (Stage 7)
|
|
from nsct.api.stage7 import router as stage7_router
|
|
app.include_router(stage7_router, tags=["research"])
|
|
|
|
# Mount evidence scoring router (Stage 8)
|
|
from nsct.api.stage8 import router as stage8_router
|
|
app.include_router(stage8_router, tags=["research"])
|
|
|
|
# Mount synthesis router (Stage 9)
|
|
from nsct.api.synthesis import router as synthesis_router
|
|
app.include_router(synthesis_router, tags=["research"])
|
|
|
|
# Mount vision router (Stage 10)
|
|
from nsct.api.vision import router as vision_router
|
|
app.include_router(vision_router, tags=["vision"])
|
|
|
|
return app
|
|
|
|
|
|
app = create_app() |