Files
RAG/backend/app/main.py
YoVinchen 75592af33a
Some checks failed
verify / verify (push) Has been cancelled
Isolate cloud model access before enabling product RAG workflows
The API and ingestion tools now use a fixed internal model gateway while
governed profiles, embedding cache assignments, traceable citations, and
stable API errors establish the boundaries required by later workflows.

Constraint: The current Alibaba Cloud workspace rejects all three live model calls with authentication failures
Rejected: Give the API or seed tools the Bailian key and direct egress | combines database access, cloud credentials, and public network access
Rejected: Mix offline and Bailian vectors in one demo namespace | makes profile activation and retrieval ambiguous
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep Bailian credentials and egress exclusive to model-gateway and create a new immutable profile hash for any embedding identity change
Tested: make verify; 121 backend tests; 14 frontend tests; fresh and populated Alembic upgrade-downgrade-upgrade; two idempotent offline seeds; Docker health and HTTP retrieval; isolated provider smoke
Not-tested: Successful live Bailian responses because the supplied workspace credential currently fails authentication
2026-07-13 04:09:06 +08:00

117 lines
3.6 KiB
Python

"""FastAPI application factory and production entrypoint."""
from typing import Any
import psycopg
import uvicorn
from fastapi import FastAPI, Response, status
from app import __version__
from app.api.v1 import demo_router
from app.core.config import get_settings
from app.core.problems import ApiProblem, api_problem_handler
from app.core.request_context import trace_request
from app.core.secrets import SecretFileError
type HealthPayload = dict[str, str | dict[str, str]]
def live() -> dict[str, str]:
return {"status": "ok", "version": __version__}
def ready(response: Response) -> HealthPayload:
settings = get_settings()
try:
dsn = settings.database_url().set(drivername="postgresql")
with psycopg.connect(
dsn.render_as_string(hide_password=False),
connect_timeout=2,
autocommit=True,
) as connection:
result = connection.execute("SELECT 1").fetchone()
if result != (1,):
raise psycopg.DatabaseError("readiness query returned an unexpected result")
except (OSError, SecretFileError, psycopg.Error):
# Do not expose connection strings, secret paths, hostnames, or driver errors.
response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
return {"status": "not_ready", "checks": {"database": "unavailable"}}
return {"status": "ready", "checks": {"database": "ok"}}
def meta() -> dict[str, Any]:
settings = get_settings()
return {
"name": settings.app_name,
"environment": settings.app_env,
"version": __version__,
"models": {
"embedding": settings.embedding_model,
"rerank": settings.rerank_model,
"generation": settings.llm_model,
},
}
def create_app() -> FastAPI:
"""Create the API without opening a database or loading model credentials."""
api = FastAPI(
title="Geological RAG API",
version=__version__,
openapi_tags=[
{"name": "health", "description": "Process and database health probes."},
{"name": "meta", "description": "Safe runtime capability metadata."},
{"name": "offline-demo", "description": "Synthetic offline validation only."},
],
)
api.middleware("http")(trace_request)
api.add_exception_handler(ApiProblem, api_problem_handler) # type: ignore[arg-type]
api.include_router(demo_router)
api.add_api_route(
"/health/live",
live,
methods=["GET"],
tags=["health"],
include_in_schema=False,
)
api.add_api_route(
"/api/v1/health/live",
live,
methods=["GET"],
tags=["health"],
operation_id="getLiveness",
)
api.add_api_route(
"/health/ready",
ready,
methods=["GET"],
tags=["health"],
include_in_schema=False,
responses={status.HTTP_503_SERVICE_UNAVAILABLE: {"description": "Database unavailable"}},
)
api.add_api_route(
"/api/v1/health/ready",
ready,
methods=["GET"],
tags=["health"],
operation_id="getReadiness",
responses={status.HTTP_503_SERVICE_UNAVAILABLE: {"description": "Database unavailable"}},
)
api.add_api_route(
"/api/v1/meta",
meta,
methods=["GET"],
tags=["meta"],
operation_id="getRuntimeMetadata",
)
return api
app = create_app()
if __name__ == "__main__":
# Compose publishes this listener only on the host loopback interface.
uvicorn.run("app.main:app", host="0.0.0.0", port=8000) # noqa: S104