Isolate cloud model access before enabling product RAG workflows
Some checks failed
verify / verify (push) Has been cancelled

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
This commit is contained in:
2026-07-13 04:09:06 +08:00
parent 99b7df64ea
commit 75592af33a
28 changed files with 3932 additions and 254 deletions

View File

@@ -1,4 +1,4 @@
"""FastAPI entrypoint with dependency-free liveness and database readiness probes."""
"""FastAPI application factory and production entrypoint."""
from typing import Any
@@ -9,26 +9,17 @@ 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
app = FastAPI(title="Geological RAG API", version=__version__)
app.include_router(demo_router)
type HealthPayload = dict[str, str | dict[str, str]]
@app.get("/health/live", tags=["health"])
@app.get("/api/v1/health/live", tags=["health"])
def live() -> dict[str, str]:
return {"status": "ok", "version": __version__}
@app.get(
"/health/ready",
tags=["health"],
responses={status.HTTP_503_SERVICE_UNAVAILABLE: {"description": "Database unavailable"}},
)
@app.get("/api/v1/health/ready", tags=["health"])
def ready(response: Response) -> HealthPayload:
settings = get_settings()
try:
@@ -48,7 +39,6 @@ def ready(response: Response) -> HealthPayload:
return {"status": "ready", "checks": {"database": "ok"}}
@app.get("/api/v1/meta", tags=["meta"])
def meta() -> dict[str, Any]:
settings = get_settings()
return {
@@ -63,6 +53,64 @@ def meta() -> dict[str, Any]:
}
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