Commit f8852fa
authored
Fix remote eval reruns by preserving upsert IDs (#763)
### Why
Fixes
[COR-84](https://linear.app/braintrustdata/issue/COR-84/remote-eval-playground-re-running-a-row-leaves-a-duplicate-grid-row).
The playground sends a stable `upsert_id` for each row/eval column so
reruns replace the existing result. Python's `EvalCase.from_dict()`
drops this field, and the eval runner creates a fresh root record on
every run. The resulting records appear as duplicate grid rows with
stale outputs. The JS SDK already honors `upsert_id`.
This addresses the SDK cause identified during review of the earlier [UI
workaround,
braintrust#20332](braintrustdata/braintrust#20332).
### Repro
1. Configure a dataset-backed playground with two Python remote evals.
2. Run a row, then rerun it in either eval column.
3. Although the request supplies the same `upsert_id`, Python writes a
different root record ID, leaving both results in the grid.
The regression test reproduces this by running an evaluator twice with
the same `upsert_id` and checking the logged root record IDs. It fails
before the fix.
### Fix
Preserve optional `upsert_id` in `EvalCase` and its input TypedDicts.
The first trial uses that value as its root record ID; additional trials
derive deterministic UUIDs from the upsert ID and trial index. This
keeps the single-trial playground behavior and lets each additional
trial replace its own result on rerun without overwriting other trials.
Both experiment-backed and parent-context evaluations use this logic.
Missing or empty IDs continue to generate fresh record IDs; dataset row
IDs and origins retain their existing meaning.
This prevents future duplicates after SDK upgrade; it does not remove
historical duplicate records. This PR changes Python only; the analogous
JS multi-trial behavior needs a separate fix.
### Test
- All 48 rerun regression cases pass, covering dictionary/dataclass
inputs, experiment/parent-context execution, supplied/missing/empty
upsert IDs, and global/per-row trial counts. The tests verify distinct
stable IDs per trial, updated output, preserved origin, and matching
root/child outputs. The multi-trial cases fail with the bare upsert ID
reused across trials.
- Full core suite: 850 passed, 63 skipped, 12 expected failures.
- All 35 type tests pass, including the optional field on dataclass and
TypedDict inputs; pyright and mypy pass.
- Ruff lint, formatting, and commit hooks pass.
- No manual browser run with the patched SDK yet.1 parent 3f8ba89 commit f8852fa
4 files changed
Lines changed: 79 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
104 | 105 | | |
105 | 106 | | |
106 | 107 | | |
| 108 | + | |
107 | 109 | | |
108 | 110 | | |
109 | 111 | | |
| |||
1648 | 1650 | | |
1649 | 1651 | | |
1650 | 1652 | | |
| 1653 | + | |
| 1654 | + | |
| 1655 | + | |
| 1656 | + | |
| 1657 | + | |
| 1658 | + | |
1651 | 1659 | | |
1652 | 1660 | | |
1653 | 1661 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
72 | 125 | | |
73 | 126 | | |
74 | 127 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
| |||
141 | 142 | | |
142 | 143 | | |
143 | 144 | | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| |||
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
| 61 | + | |
0 commit comments