feat(benchmarking): add document filename and delete experiment endpoint

Why:
- Experiment list and detail responses showed raw IDs instead of filenames
- No way to delete experiments from the UI

Changes:
- Add document_filename field to ExperimentDetailResponse
- Enrich list endpoint with filenames and calculated best_strategy from aggregate_metrics
- Add DELETE /experiments/{id} endpoint

Impact:
- API responses now include document_filename for all experiment endpoints
- Frontend can display filenames instead of IDs
This commit is contained in:
2026-07-29 17:54:07 +03:30
parent a309e64841
commit a277672444
2 changed files with 36 additions and 1 deletions

View File

@@ -119,6 +119,7 @@ class ExperimentDetailResponse(BaseModel):
"""Returned when retrieving an experiment."""
id: str
document_id: str
document_filename: str = ""
benchmark_config: dict
questions: list[dict]
per_question: list[dict]

View File

@@ -154,9 +154,15 @@ async def get_benchmark(experiment_id: str):
if result is None:
raise BenchmarkError(f"Experiment not found: {experiment_id}")
# Resolve document filename
from src.storage import sqlite as db
doc = db.get_document(result.get("document_id", ""))
doc_filename = doc.get("filename", "Unknown") if doc else "Deleted"
return ExperimentDetailResponse(
id=result["id"],
document_id=result["document_id"],
document_filename=doc_filename,
benchmark_config=result.get("benchmark_config", {}),
questions=result.get("questions", []),
per_question=result.get("per_question", []),
@@ -171,7 +177,35 @@ async def get_benchmark(experiment_id: str):
@router.get("/experiments")
async def list_experiments(document_id: str | None = None):
"""List all experiments, optionally filtered by document."""
return benchmark_service.list_experiments(document_id=document_id)
result = benchmark_service.list_experiments(document_id=document_id)
# Enrich with document filenames and best_strategy
from src.storage import sqlite as db
for item in result.get("items", []):
doc = db.get_document(item.get("document_id", ""))
item["document_filename"] = doc.get("filename", "Unknown") if doc else "Deleted"
# Calculate best_strategy from aggregate_metrics
aggs = item.get("aggregate_metrics", {})
best_strat, best_score = "N/A", -1
for strat, m in aggs.items():
score = (m.get("avg_context_relevance", 0) * 0.3 +
m.get("avg_answer_similarity", 0) * 0.4 +
m.get("avg_faithfulness", 0) * 0.3)
adjusted = score * (1 - m.get("hallucination_rate", 0))
if adjusted > best_score:
best_score = adjusted
best_strat = strat
item["best_strategy"] = best_strat
return result
@router.delete("/experiments/{experiment_id}")
async def delete_experiment(experiment_id: str):
"""Delete an experiment by ID."""
from src.storage import sqlite as db
deleted = db.delete_experiment(experiment_id)
if not deleted:
raise BenchmarkError(f"Experiment not found: {experiment_id}")
return {"deleted": True, "experiment_id": experiment_id}
@router.get("/benchmarks/{experiment_id}/report", response_class=HTMLResponse)