Skip to content

feat: Add pass@k with per-case CSV loading for NxM consolidation - #316

Draft
asamal4 wants to merge 1 commit into
lightspeed-core:mainfrom
asamal4:nxm-pass-at-k
Draft

feat: Add pass@k with per-case CSV loading for NxM consolidation#316
asamal4 wants to merge 1 commit into
lightspeed-core:mainfrom
asamal4:nxm-pass-at-k

Conversation

@asamal4

@asamal4 asamal4 commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Description

  1. statistics.py — pass_at_k with per-case signature: takes lists of pass counts and totals per case, computes unbiased estimator averaged across cases
  2. loader.py — added _load_case_results() to read per-case pass/fail from *_detailed.csv; RunData now includes case_results; fixed log message
  3. consolidation.py — _compute_pass_at_k groups cases by (conv_id, turn_id, metric) across runs, wired into overall["pass_at_k"] for multi-run only

Type of change

  • Refactor
  • New feature
  • Bug fix
  • CVE fix
  • Optimization
  • Documentation Update
  • Configuration Update
  • Bump-up service version
  • Bump-up dependent library
  • Bump-up library or tool used for development (does not change the final image)
  • CI configuration change
  • Unit tests improvement

Tools used to create PR

Identify any AI code assistants used in this PR (for transparency and review context)

  • Assisted-by: Claude

Related Tickets & Documents

  • Related Issue #
  • Closes #

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

Testing

  • Please provide detailed steps to perform tests related to this code change.
  • How were the fix/results from this change verified? Please provide relevant screenshots or results.

Summary by CodeRabbit

  • New Features

    • Added pass@k evaluation metrics for multi-run behavioral results.
    • Per-case results can now be loaded from detailed CSV files.
    • Reports omit pass@k when only one run or no case-level data is available.
  • Bug Fixes

    • Improved handling and reporting of missing usable summary files.
  • Tests

    • Added coverage for pass@k calculations and detailed per-case result loading.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The behavioral pipeline now loads per-case results from detailed CSV files and computes an unbiased pass_at_k statistic across multiple runs. RunData exposes optional case results, and consolidation omits pass_at_k when only one run or no case results exist.

Changes

Behavioral pass@k aggregation

Layer / File(s) Summary
pass_at_k statistic
src/lightspeed_evaluation/pipeline/behavioral/statistics.py, tests/unit/pipeline/behavioral/test_statistics.py
The new pass_at_k function computes unbiased per-case probabilities and averages them. Tests cover boundary conditions, partial success, averaging, and insufficient samples.
Detailed case-result loading
src/lightspeed_evaluation/pipeline/behavioral/loader.py, tests/unit/pipeline/behavioral/test_loader.py
RunData stores optional case results. The loader reads the first matching *_detailed.csv, extracts case identifiers and results, and returns None when no usable file exists.
Multi-run pass@k consolidation
src/lightspeed_evaluation/pipeline/behavioral/consolidation.py, tests/unit/pipeline/behavioral/test_consolidation.py
Consolidation groups results by conversation, turn, and metric, then computes pass_at_k across multiple runs. Tests cover mixed results and single-run omission.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DetailedCSV
  participant load_run_data
  participant RunData
  participant consolidate
  participant pass_at_k
  DetailedCSV->>load_run_data: read *_detailed.csv case rows
  load_run_data->>RunData: store optional case_results
  RunData->>consolidate: provide case results from multiple runs
  consolidate->>pass_at_k: pass counts, totals, and k
  pass_at_k-->>consolidate: return pass@k estimate
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: adding pass@k support and loading per-case CSV results for NxM consolidation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lightspeed_evaluation/pipeline/behavioral/consolidation.py`:
- Around line 280-286: In the case-counting logic, replace the
colon-concatenated string assigned to key with the tuple of
case["conversation_group_id"], case["turn_id"], and case["metric_identifier"]
directly. Keep the existing case_total increment and pass/fail aggregation
unchanged so distinct identifiers cannot collide and corrupt pass_at_k.

In `@src/lightspeed_evaluation/pipeline/behavioral/loader.py`:
- Around line 92-95: Validate that DictReader.fieldnames contains
conversation_group_id, turn_id, and metric_identifier before iterating over rows
in the loader function containing this result mapping. If any required
identifier column is missing, log the invalid schema and return None; otherwise
preserve the existing row parsing behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 19e85eea-0156-4151-a309-0c7cc9470c5b

📥 Commits

Reviewing files that changed from the base of the PR and between 27adf51 and a4a5a23.

📒 Files selected for processing (6)
  • src/lightspeed_evaluation/pipeline/behavioral/consolidation.py
  • src/lightspeed_evaluation/pipeline/behavioral/loader.py
  • src/lightspeed_evaluation/pipeline/behavioral/statistics.py
  • tests/unit/pipeline/behavioral/test_consolidation.py
  • tests/unit/pipeline/behavioral/test_loader.py
  • tests/unit/pipeline/behavioral/test_statistics.py

Comment on lines +280 to +286
key = (
f"{case['conversation_group_id']}:"
f"{case['turn_id']}:"
f"{case['metric_identifier']}"
)
case_total[key] += 1
if case["result"] == "PASS":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use a tuple for the case key.

If an identifier contains :, the string key can collide. For example, ("a:b", "c", "d") and ("a", "b:c", "d") both produce "a:b:c:d". Line 285 then combines unrelated case counts and corrupts pass_at_k.

Use the required identifier tuple directly as the dictionary key.

Proposed fix
-def _compute_pass_at_k(runs: list[RunData], k: int) -> Optional[float]:
+def _compute_pass_at_k(runs: list[RunData], k: int) -> Optional[float]:
     """Compute pass@k from per-case results across runs."""
-    case_pass: dict[str, int] = defaultdict(int)
-    case_total: dict[str, int] = defaultdict(int)
+    case_pass: dict[tuple[str, str, str], int] = defaultdict(int)
+    case_total: dict[tuple[str, str, str], int] = defaultdict(int)
...
-            key = (
-                f"{case['conversation_group_id']}:"
-                f"{case['turn_id']}:"
-                f"{case['metric_identifier']}"
+            key = (
+                case["conversation_group_id"],
+                case["turn_id"],
+                case["metric_identifier"],
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
key = (
f"{case['conversation_group_id']}:"
f"{case['turn_id']}:"
f"{case['metric_identifier']}"
)
case_total[key] += 1
if case["result"] == "PASS":
key = (
case["conversation_group_id"],
case["turn_id"],
case["metric_identifier"],
)
case_total[key] += 1
if case["result"] == "PASS":
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lightspeed_evaluation/pipeline/behavioral/consolidation.py` around lines
280 - 286, In the case-counting logic, replace the colon-concatenated string
assigned to key with the tuple of case["conversation_group_id"],
case["turn_id"], and case["metric_identifier"] directly. Keep the existing
case_total increment and pass/fail aggregation unchanged so distinct identifiers
cannot collide and corrupt pass_at_k.

Comment on lines +92 to +95
"conversation_group_id": row.get("conversation_group_id", ""),
"turn_id": row.get("turn_id", ""),
"metric_identifier": row.get("metric_identifier", ""),
"result": row.get("result", "ERROR"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate required case identifier columns.

If a detailed CSV lacks an identifier column, Lines 92-94 silently replace it with "". Consolidation then groups these rows as a real case and reports a distorted pass_at_k value.

Validate conversation_group_id, turn_id, and metric_identifier from DictReader.fieldnames. Log the invalid schema and return None before reading rows.

Proposed fix
         cases: list[dict[str, str]] = []
         with open(matches[0], encoding="utf-8") as f:
-            for row in csv.DictReader(f):
+            reader = csv.DictReader(f)
+            required_columns = {
+                "conversation_group_id",
+                "turn_id",
+                "metric_identifier",
+            }
+            if not reader.fieldnames or not required_columns.issubset(reader.fieldnames):
+                logger.warning("Invalid detailed CSV schema in %s", matches[0])
+                return None
+            for row in reader:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"conversation_group_id": row.get("conversation_group_id", ""),
"turn_id": row.get("turn_id", ""),
"metric_identifier": row.get("metric_identifier", ""),
"result": row.get("result", "ERROR"),
cases: list[dict[str, str]] = []
with open(matches[0], encoding="utf-8") as f:
reader = csv.DictReader(f)
required_columns = {
"conversation_group_id",
"turn_id",
"metric_identifier",
}
if not reader.fieldnames or not required_columns.issubset(reader.fieldnames):
logger.warning("Invalid detailed CSV schema in %s", matches[0])
return None
for row in reader:
"conversation_group_id": row.get("conversation_group_id", ""),
"turn_id": row.get("turn_id", ""),
"metric_identifier": row.get("metric_identifier", ""),
"result": row.get("result", "ERROR"),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lightspeed_evaluation/pipeline/behavioral/loader.py` around lines 92 -
95, Validate that DictReader.fieldnames contains conversation_group_id, turn_id,
and metric_identifier before iterating over rows in the loader function
containing this result mapping. If any required identifier column is missing,
log the invalid schema and return None; otherwise preserve the existing row
parsing behavior.

@asamal4
asamal4 marked this pull request as draft August 12, 2026 06:23
@asamal4 asamal4 added the hold label Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant