| """Traceability endpoint — user-facing per-turn provenance (KM-691). |
| |
| `GET /api/v1/traceability?analysis_id=&message_id=` returns the provenance record for |
| one assistant turn: planning / tool calls (with real inputs + outputs) / data sources |
| (with the executed query). One JSONB row per assistant `message_id`, written by the |
| chat pipeline right before the `done` SSE event; the FE fires this GET on `done`. |
| |
| Renamed from the contracted `/api/v1/observability` (team decision 2026-07-06) so it is |
| never confused with the Langfuse *observability* stack (engineering-only, PII-masked). |
| No auth — Go fronts Python. |
| """ |
|
|
| from fastapi import APIRouter, HTTPException, Query |
|
|
| from src.middlewares.logging import get_logger, log_execution |
| from src.traceability import PostgresTraceabilityStore, TraceabilityPayload |
|
|
| logger = get_logger("traceability_api") |
|
|
| router = APIRouter(prefix="/api/v1", tags=["Traceability"]) |
|
|
| |
| _store = PostgresTraceabilityStore() |
|
|
|
|
| @router.get("/traceability", response_model=TraceabilityPayload) |
| @log_execution(logger) |
| async def get_traceability( |
| analysis_id: str = Query(..., description="Analysis/session id"), |
| message_id: str = Query( |
| ..., description="Assistant turn id, taken from the `done` SSE event" |
| ), |
| ) -> TraceabilityPayload: |
| """Fetch one turn's provenance record. 404 while the turn is still running or if |
| the id is unknown (the FE never gets a `message_id` for error turns, so 404 is |
| the correct answer there).""" |
| payload = await _store.get(analysis_id, message_id) |
| if payload is None: |
| raise HTTPException( |
| status_code=404, |
| detail=f"No traceability for message '{message_id}' yet.", |
| ) |
| return payload |
|
|