from __future__ import annotations from typing import Any from fastapi import FastAPI, Response from adapters.postgres import PostgresTraceRepository from bootstrap import build_runtime from config import Settings, get_settings from rag.answer import GroundedAnswerService from routers.rag import router as rag_router def create_app( *, settings: Settings | None = None, answer_service: GroundedAnswerService | None = None, conversational: Any | None = None, trace_writer: PostgresTraceRepository | None = None, metrics: Any | None = None, ) -> FastAPI: configured = settings or get_settings() app = FastAPI(title=configured.app_name, version="0.1.0") app.state.answer_service = answer_service app.state.conversational = conversational app.state.trace_writer = trace_writer app.state.metrics = metrics @app.get("/health") def health() -> dict[str, str]: return {"status": "ok"} @app.get("/metrics") def prometheus_metrics() -> Response: exporter = getattr(app.state, "metrics", None) if exporter is None or not hasattr(exporter, "render"): # 404 rather than an empty 200: a scrape that silently succeeds # with no samples looks identical to a service answering nothing. return Response(status_code=404) body, content_type = exporter.render() return Response(content=body, media_type=content_type) app.include_router(rag_router) return app _settings = get_settings() _answer_service, _conversational, _trace_writer, _metrics = build_runtime(_settings) app = create_app( settings=_settings, answer_service=_answer_service, conversational=_conversational, trace_writer=_trace_writer, metrics=_metrics, )