Files
NSCT---Neutral-Search-Crawl…/src/nsct/api/main.py
NSCT Agent 4d5f6747f7 feat(stage14): implement REST API for research lifecycle
- POST /v1/research — start research (non-blocking, background pipeline)
- GET  /v1/research/{id} — research metadata
- GET  /v1/research/{id}/status — detailed state machine status
- GET  /v1/research/{id}/sources — sources list
- GET  /v1/research/{id}/claims — claims list
- GET  /v1/research/{id}/evidence — evidence scores
- GET  /v1/research/{id}/report — research report
- DELETE /v1/research/{id} — delete research (non-completed)
- GET  /v1/research — paginated list of all research runs

Depth budgets (quick/normal/deep) control only resource limits.
In-memory store for now, to be replaced with PostgreSQL later.
Router mounted in main.py as tag "research-api".
2026-08-27 16:07:47 +00:00

121 lines
3.9 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"])
# Mount audio router (Stage 11)
from nsct.api.audio import router as audio_router
app.include_router(audio_router, tags=["audio"])
# Mount REST API router (Stage 14)
from nsct.api.rest_research import router as rest_research_router
app.include_router(rest_research_router, tags=["research"])
return app
app = create_app()