feat(validation): v2 LLM dashboard validation — plugin, routes, services
Core implementation of the v2 LLM dashboard validation pipeline: - LLM plugin with Path A (screenshots) and Path B (logs-only) execution - Validation task management (CRUD, schedule, run) - WebSocket task progress with Python 3.13 asyncio fix - Cross-task runs listing (GET /validation-tasks/runs/all) - RecordResponse schema for validation records - JSON prompt helper, per-dashboard status aggregation - Prompt templates with docs/git-commit/validation presets - Migration: v2 validation models + description column - Tests: plugin persistence, prompt templates, batch, payload, url
This commit is contained in:
@@ -25,6 +25,7 @@ from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
from typing import Any
|
||||
|
||||
from alembic import command as alembic_command
|
||||
|
||||
@@ -52,8 +53,8 @@ from .api.routes import (
|
||||
storage,
|
||||
tasks,
|
||||
translate,
|
||||
validation,
|
||||
)
|
||||
from .api.routes.validation_tasks import router as validation_tasks
|
||||
from .core.auth.security import get_password_hash
|
||||
from .core.cot_logger import seed_trace_id
|
||||
from .core.database import AuthSessionLocal, init_db
|
||||
@@ -341,7 +342,6 @@ async def log_requests(request: Request, call_next):
|
||||
# @RELATION DEPENDS_ON -> [ReportsRouter]
|
||||
# @RELATION DEPENDS_ON -> [LlmRoutes]
|
||||
# @RELATION DEPENDS_ON -> [CleanReleaseV2Api]
|
||||
# @RELATION DEPENDS_ON -> [ValidationRoutes]
|
||||
# @RELATION DEPENDS_ON -> [MaintenanceRouter]
|
||||
# Include API routes
|
||||
app.include_router(auth.router)
|
||||
@@ -366,7 +366,7 @@ app.include_router(profile.router)
|
||||
app.include_router(dataset_review.router)
|
||||
app.include_router(health.router)
|
||||
app.include_router(translate.router)
|
||||
app.include_router(validation.router, prefix="/api/validation", tags=["Validation"])
|
||||
app.include_router(validation_tasks, prefix="/api/validation-tasks", tags=["Validation Tasks"])
|
||||
app.include_router(maintenance.maintenance_router)
|
||||
# #endregion API_Routes
|
||||
# #region api.include_routers [C:1] [TYPE Action] [SEMANTICS routes, registration, api]
|
||||
@@ -590,8 +590,10 @@ async def websocket_endpoint(
|
||||
|
||||
# ── Main loop: listen on both log and status queues ──
|
||||
while True:
|
||||
log_task = asyncio.create_task(log_queue.get())
|
||||
status_task = asyncio.create_task(status_queue.get())
|
||||
done, _ = await asyncio.wait(
|
||||
[log_queue.get(), status_queue.get()],
|
||||
[log_task, status_task],
|
||||
return_when=asyncio.FIRST_COMPLETED,
|
||||
)
|
||||
for coro in done:
|
||||
@@ -634,7 +636,11 @@ async def websocket_endpoint(
|
||||
await asyncio.sleep(2)
|
||||
except (WebSocketDisconnect, StopIteration):
|
||||
if isinstance(StopIteration):
|
||||
pass # normal termination
|
||||
# Task reached terminal state — close cleanly with code 1000
|
||||
try:
|
||||
await websocket.close(code=1000, reason="Task completed")
|
||||
except Exception:
|
||||
pass
|
||||
logger.reason(
|
||||
"WebSocket client disconnected or stream ended",
|
||||
extra={"task_id": task_id},
|
||||
|
||||
Reference in New Issue
Block a user