Verify exact production traces and record rollout

This commit is contained in:
2026-08-11 11:29:59 +07:00
parent 6b8f7584ed
commit 59e6ad2d0d
46 changed files with 2795 additions and 290 deletions
+64 -1
View File
@@ -1,13 +1,22 @@
from __future__ import annotations
from time import perf_counter
from typing import Any
from fastapi import FastAPI, Response
from fastapi import FastAPI, Request, Response
from adapters.postgres import PostgresTraceRepository
from bootstrap import build_runtime
from config import Settings, get_settings
from rag.answer import GroundedAnswerService
from rag.metrics import REQUEST_DURATION, REQUESTS, NullMetrics
from rag.telemetry import (
annotate_current_span,
configure_telemetry,
correlation_context,
current_trace_id,
request_span,
)
from routers.rag import router as rag_router
@@ -20,16 +29,65 @@ def create_app(
metrics: Any | None = None,
) -> FastAPI:
configured = settings or get_settings()
effective_metrics = metrics or NullMetrics()
configure_telemetry(configured, effective_metrics)
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.middleware("http")
async def correlate_and_trace(request: Request, call_next):
route = _route_label(request.url.path)
method = request.method.upper()
started = perf_counter()
status_code = 500
with correlation_context(request.headers.get("x-correlation-id")) as correlation_id:
with request_span(method, route, request.headers) as span:
try:
response = await call_next(request)
status_code = response.status_code
response.headers["X-Correlation-ID"] = correlation_id
trace_id = current_trace_id()
if trace_id:
response.headers["X-Trace-ID"] = trace_id
return response
finally:
status = f"{status_code // 100}xx"
elapsed = perf_counter() - started
effective_metrics.increment(
REQUESTS, method=method, route=route, status=status
)
effective_metrics.observe(
REQUEST_DURATION,
elapsed,
method=method,
route=route,
status=status,
)
if span is not None:
span.set_attribute("http.response.status_code", status_code)
annotate_current_span(
**{
"duocthu.http.status_class": status,
"duocthu.duration_ms": elapsed * 1000,
}
)
@app.get("/health")
def health() -> dict[str, str]:
return {"status": "ok"}
@app.get("/ready")
def ready() -> Response:
# Runtime construction already verifies the Qdrant corpus manifest and
# fails startup on mismatch. PostgreSQL trace/history are intentionally
# fail-open and therefore must not make readiness flap.
if app.state.answer_service is None and configured.embedding_provider != "disabled":
return Response(status_code=503)
return Response(content='{"status":"ready"}', media_type="application/json")
@app.get("/metrics")
def prometheus_metrics() -> Response:
exporter = getattr(app.state, "metrics", None)
@@ -44,6 +102,11 @@ def create_app(
return app
def _route_label(path: str) -> str:
known = {"/health", "/ready", "/metrics", "/v1/rag/query", "/v1/rag/suggest"}
return path if path in known else "other"
_settings = get_settings()
_answer_service, _conversational, _trace_writer, _metrics = build_runtime(_settings)
app = create_app(