feat: add failure cohort analysis for evaluation reports - #360
feat: add failure cohort analysis for evaluation reports#360max-rattray-aws wants to merge 2 commits into
Conversation
Add strands_evals.analysis module with analyze_failure_cohorts() that groups failed cases by evaluator name, sorted largest-first. This helps identify systemic problems (e.g. 14 Faithfulness failures out of 20 total) vs scattered one-off edge cases. New types: - FailureCohort: a group of cases that failed the same evaluator - CohortAnalysis: sorted list of cohorts with summary counts Also includes a print_cohort_summary() Rich display helper.
| """ | ||
| failures_by_evaluator: dict[str, list[tuple[int, str]]] = {} | ||
|
|
||
| for i, (case, passed) in enumerate(zip(report.cases, report.test_passes, strict=False)): |
There was a problem hiding this comment.
Issue: zip(..., strict=False) silently stops at the shorter of cases/test_passes. Since total_cases and total_failures are computed independently from report.test_passes (lines below), a length mismatch between the two lists would produce a silently inconsistent CohortAnalysis (e.g. total_failures counting rows that were never bucketed) rather than an error.
Suggestion: Use strict=True so a corrupted/mismatched report surfaces loudly, or if lenient behavior is intentional (e.g. for from_file on hand-edited JSON), add a one-line comment documenting the invariant so the choice is clear.
There was a problem hiding this comment.
✅ Addressed in 69bea41 — now zip(..., strict=True), so a cases/test_passes length mismatch surfaces loudly instead of silently truncating.
Minor follow-up (non-blocking): consider adding a test that asserts the mismatch case raises (e.g. with pytest.raises(ValueError): analyze_failure_cohorts(report_with_mismatched_lengths)), so this new contract is locked in against regressions.
| Args: | ||
| analysis: A CohortAnalysis returned by analyze_failure_cohorts. | ||
| """ | ||
| from rich.console import Console |
There was a problem hiding this comment.
Issue: rich is imported lazily inside print_cohort_summary, but rich is a hard runtime dependency (rich>=14.0.0,<15.0.0 in pyproject.toml). AGENTS.md is explicit that hard deps must be imported at module top and that lazy imports of them are not allowed (the allowed exceptions are optional extras, genuinely expensive rare-path imports, or breaking real circular imports — none apply here).
Suggestion: Move from rich.console import Console and from rich.table import Table to the top of the module alongside the other imports. (The linked issue #348 sketched the lazy-import version, but the repo convention takes precedence.)
There was a problem hiding this comment.
✅ Resolved in 69bea41 — from rich.console import Console and from rich.table import Table are now at module top (lines 11–12). Thanks for the quick fix.
|
Issue (API review): This PR introduces a new public surface — a new Suggestion: Add the
|
|
Assessment: Request Changes Clean, well-scoped, well-tested feature that faithfully implements #348. The main blocker is a repo-convention violation on imports; the rest are design/process items to settle before this public API lands. Review Categories
Nice work — the pure-function design, docstrings, and graceful defaulting for missing keys are all solid. |
|
Assessment: Approve (pending API review) Re-reviewed after Verification of fixes
Two open items, neither blocking a code approval:
Nice, responsive iteration — the module is clean and well-tested. |
Summary
Adds a new
strands_evals.analysismodule withanalyze_failure_cohorts()that groups failed evaluation cases by evaluator name, sorted largest-first. This surfaces systemic problems (e.g. 14 Faithfulness failures out of 20 total) vs scattered one-off edge cases, without requiring users to write their own grouping code.Usage
What's included
FailureCohortPydantic model withis_systemicpropertyCohortAnalysismodel withsystemic_cohortsandone_off_failurespropertiesanalyze_failure_cohorts(report)pure function (no side effects, no model calls)print_cohort_summary(analysis)Rich table display helper (no new deps)EvaluationReportincluding fromEvaluationReport.from_file()andEvaluationReport.flatten()What's tested
EvaluationReport.from_file()andEvaluationReport.flatten()Related to #348