ishaq101's picture
/feat traceability (#11)
f873f92
Raw
History Blame Contribute Delete
1.8 kB
"""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"])
# Warm, process-shared store (mirrors the chat endpoints' module-level instances).
_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