Fastwhisper / app /routers /debug.py
Mbonea's picture
Deploy Habit Journal backend S0-S10 to Hugging Face Space.
990895d
Raw
History Blame Contribute Delete
4.29 kB
"""Authenticated debug routes for coach traces and prompt previews.
Paste bundles are self-contained for an external AI with no user history.
"""
from fastapi import APIRouter, Depends, Query, Request, status
from fastapi.responses import PlainTextResponse
from app.deps import require_login
from app.models import ApiEnvelope, CoachRequest, err, ok
from app.paste_bundle import render_paste_bundle
router = APIRouter(
prefix="/api/debug",
tags=["debug"],
dependencies=[Depends(require_login)],
)
def _settings(request: Request):
return request.app.state.settings
def _paste(request: Request, trace: dict) -> PlainTextResponse:
settings = _settings(request)
body = render_paste_bundle(
trace,
app_name=settings.app_name,
shrink_k=settings.stats_shrink_k,
match_alpha=settings.match_alpha,
min_n=settings.min_stats_n,
)
return PlainTextResponse(content=body, media_type="text/markdown")
@router.get("/traces", response_model=ApiEnvelope)
def list_traces(
request: Request,
limit: int = Query(default=20, ge=1, le=200),
) -> dict[str, object]:
"""Return newest-first trace summaries."""
records = request.app.state.trace_store.list_traces(limit=limit)
summaries = [
{
"trace_id": r.get("trace_id"),
"ts": r.get("ts"),
"source": r.get("source"),
"flags": r.get("flags"),
"model": r.get("model_id"),
}
for r in records
]
return ok({"items": summaries})
@router.get("/traces/{trace_id}", response_model=ApiEnvelope)
def get_trace(request: Request, trace_id: str) -> object:
"""Return one full JSON trace."""
trace = request.app.state.trace_store.get_trace(trace_id)
if trace is None:
return err("not_found", "Trace not found", status.HTTP_404_NOT_FOUND)
return ok(trace)
@router.get("/traces/{trace_id}/paste")
def paste_trace(request: Request, trace_id: str) -> object:
"""Return the context-free markdown paste bundle for one trace."""
trace = request.app.state.trace_store.get_trace(trace_id)
if trace is None:
return err("not_found", "Trace not found", status.HTTP_404_NOT_FOUND)
return _paste(request, trace)
@router.get("/last/paste")
def paste_last(request: Request) -> object:
"""Return the newest trace paste bundle."""
trace = request.app.state.trace_store.latest_trace()
if trace is None:
return err("not_found", "No traces yet", status.HTTP_404_NOT_FOUND)
return _paste(request, trace)
@router.post("/prompt-preview", response_model=ApiEnvelope)
def prompt_preview(request: Request, body: CoachRequest) -> object:
"""Build evidence and messages without calling the model."""
if not body.text and not body.entry_id:
return err(
"validation_error",
"Provide text or entry_id",
status.HTTP_422_UNPROCESSABLE_ENTITY,
)
try:
preview = request.app.state.coach_service.prompt_preview(
text=body.text,
entry_id=body.entry_id,
include_history=body.include_history,
)
except KeyError:
return err("not_found", "Entry not found", status.HTTP_404_NOT_FOUND)
except ValueError as exc:
return err("validation_error", str(exc), status.HTTP_422_UNPROCESSABLE_ENTITY)
return ok(preview)
@router.post("/coach-test", response_model=ApiEnvelope)
def coach_test(request: Request, body: CoachRequest) -> object:
"""Run coach and force a trace; optional force_backup."""
if not body.text and not body.entry_id:
return err(
"validation_error",
"Provide text or entry_id",
status.HTTP_422_UNPROCESSABLE_ENTITY,
)
try:
result = request.app.state.coach_service.coach(
text=body.text,
entry_id=body.entry_id,
include_history=body.include_history,
persist=body.persist,
force_backup=body.force_backup,
)
except KeyError:
return err("not_found", "Entry not found", status.HTTP_404_NOT_FOUND)
except ValueError as exc:
return err("validation_error", str(exc), status.HTTP_422_UNPROCESSABLE_ENTITY)
return ok(result)