@hona/openeval 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/app/load-benchmark.ts +9 -0
- package/src/app/plan-benchmark.ts +23 -5
- package/src/app/read-results.ts +94 -50
- package/src/app/recover-run.ts +61 -0
- package/src/app/rejudge.ts +9 -5
- package/src/app/retry-run.ts +9 -5
- package/src/app/run-benchmark.ts +21 -5
- package/src/app/scores.ts +19 -19
- package/src/app/serve-results.ts +26 -9
- package/src/cli.ts +3 -0
- package/src/infra/sqlite/index.ts +7 -0
- package/src/types.ts +7 -0
- package/src/view.ts +7 -0
- package/viewer/assets/{abnfDiagram-VCTEODGH-4dcAM__t.js → abnfDiagram-VCTEODGH-DheibVLk.js} +1 -1
- package/viewer/assets/{angular-html-DCa1K9Q5.js → angular-html-4AdCdqA_.js} +1 -1
- package/viewer/assets/{angular-ts-BwOaP4mi.js → angular-ts-D-G3eLWz.js} +1 -1
- package/viewer/assets/{apl-BDVbqe8d.js → apl-Ckl95Nnu.js} +1 -1
- package/viewer/assets/{arc-vf_TPbdA.js → arc-DBaUWWiS.js} +1 -1
- package/viewer/assets/architecture-7GRP2DOG-DtdqvSTn.js +1 -0
- package/viewer/assets/{architectureDiagram-5GKGNRK7-D9I5g97H.js → architectureDiagram-5GKGNRK7-BSEYyvUG.js} +1 -1
- package/viewer/assets/{astro-C4AVO9I1.js → astro-BpiishHg.js} +1 -1
- package/viewer/assets/{blade-1K2d7b5P.js → blade-DvRbLCOy.js} +1 -1
- package/viewer/assets/{blockDiagram-I7D4REHJ-CnebaxAP.js → blockDiagram-I7D4REHJ-DLxyKv07.js} +1 -1
- package/viewer/assets/{c-CrFxx71c.js → c-BZYE5LIm.js} +1 -1
- package/viewer/assets/{c4Diagram-7LVT6UL2-CZv4bp92.js → c4Diagram-7LVT6UL2--I50ySVl.js} +1 -1
- package/viewer/assets/channel-BksTOWku.js +1 -0
- package/viewer/assets/{chapel-CaV_aD0h.js → chapel-T-o4rT-z.js} +1 -1
- package/viewer/assets/{chunk-4HAMMTFA-1oAN0Hqj.js → chunk-4HAMMTFA-B9xd7TwX.js} +1 -1
- package/viewer/assets/{chunk-75Z2AOVW-DG-4lG9j.js → chunk-75Z2AOVW-B4XIg4PJ.js} +1 -1
- package/viewer/assets/{chunk-DU6HZSFF-K8r4K98Q.js → chunk-DU6HZSFF-Cty-qidK.js} +1 -1
- package/viewer/assets/{chunk-F27PBJKO-DOLcDIe-.js → chunk-F27PBJKO-8WDDKCG7.js} +1 -1
- package/viewer/assets/{chunk-GMAD6QVW-fbOr8e3A.js → chunk-GMAD6QVW-G948aN1X.js} +1 -1
- package/viewer/assets/{chunk-GVQU2GXP-DGskHf86.js → chunk-GVQU2GXP-CVFpDQVF.js} +1 -1
- package/viewer/assets/{chunk-IMKFNOWR-BkiL77Of.js → chunk-IMKFNOWR-BUQviXHJ.js} +1 -1
- package/viewer/assets/{chunk-L3NEJ4N5-rHC84FEy.js → chunk-L3NEJ4N5-BXvzxNPv.js} +1 -1
- package/viewer/assets/{chunk-OSK3NFVY-CDEFeiSo.js → chunk-OSK3NFVY-wLdUx9yD.js} +1 -1
- package/viewer/assets/{chunk-P2QGCYS3-BHNuVs3t.js → chunk-P2QGCYS3-hDwRspSg.js} +1 -1
- package/viewer/assets/{chunk-POPQ4Y6H-8IU6HL0N.js → chunk-POPQ4Y6H-LbeiA8tL.js} +1 -1
- package/viewer/assets/{chunk-PWAF6VOD--FR_J-_V.js → chunk-PWAF6VOD-BcGZUKY4.js} +1 -1
- package/viewer/assets/{chunk-SHT3W25Y-DHfu0YQd.js → chunk-SHT3W25Y-BqKh1v-r.js} +1 -1
- package/viewer/assets/{chunk-SVP7TREG-CZxnTp0N.js → chunk-SVP7TREG-BEMJeRHW.js} +1 -1
- package/viewer/assets/{chunk-TICWLB2K-DeYv6m7z.js → chunk-TICWLB2K-BiHATE6U.js} +1 -1
- package/viewer/assets/{chunk-XXDRQBXY-B-stp2Jg.js → chunk-XXDRQBXY-B0X2aTwI.js} +1 -1
- package/viewer/assets/classDiagram-ZZMXUADV-Cbfu0NLs.js +1 -0
- package/viewer/assets/classDiagram-v2-VYDZK3BY-Cbfu0NLs.js +1 -0
- package/viewer/assets/{cobol-Dfqw6e9L.js → cobol-DS-OUv7W.js} +1 -1
- package/viewer/assets/{coffee-B0VyEiZ_.js → coffee-UJZBXrQS.js} +1 -1
- package/viewer/assets/{cose-bilkent-JH36ORCC-DwO-FoMO.js → cose-bilkent-JH36ORCC-h7Gx4dzS.js} +1 -1
- package/viewer/assets/{cpp-DtAnLTpm.js → cpp-BuO_io74.js} +1 -1
- package/viewer/assets/{crystal-DR276aYT.js → crystal-D_Q6HPhm.js} +1 -1
- package/viewer/assets/{css-jcQdyKCB.js → css-B9bdOUvs.js} +1 -1
- package/viewer/assets/{cynefin-OW5HDTMX-CkCcLfFW.js → cynefin-OW5HDTMX-BONm5P8S.js} +1 -1
- package/viewer/assets/{cynefinDiagram-5FMLGOSQ-Btlq2-ol.js → cynefinDiagram-5FMLGOSQ-CWsyzZBO.js} +1 -1
- package/viewer/assets/{dagre-GXQ25YYZ-BtEJZ3id.js → dagre-GXQ25YYZ-CJG_-oax.js} +1 -1
- package/viewer/assets/{diagram-S7CK7UJ4-BdLily-u.js → diagram-S7CK7UJ4-uOGlO2Fe.js} +1 -1
- package/viewer/assets/{diagram-UQ7AKVKN-DBfD46yQ.js → diagram-UQ7AKVKN-B2mgymOu.js} +1 -1
- package/viewer/assets/{diagram-VSXAHHWV-BJuAmu9T.js → diagram-VSXAHHWV-UkXvypTd.js} +1 -1
- package/viewer/assets/{diagram-VX7I27RA-BVEEYjj-.js → diagram-VX7I27RA-Bzge-YWf.js} +1 -1
- package/viewer/assets/{diagram-Z3DM3KII-DqYpuagj.js → diagram-Z3DM3KII-BAqqEKF_.js} +1 -1
- package/viewer/assets/{dist-B0PK9u1q.js → dist-CACpyRbj.js} +1 -1
- package/viewer/assets/{ebnfDiagram-PWID7BFC-DafUAY97.js → ebnfDiagram-PWID7BFC-dsRp8_0H.js} +1 -1
- package/viewer/assets/{edge-CMwzwqao.js → edge-DUnvwjmM.js} +1 -1
- package/viewer/assets/{elixir-D0m0fb5e.js → elixir-QfE-NI0D.js} +1 -1
- package/viewer/assets/{elm-Dm3Vhwyo.js → elm-D4J4FYyf.js} +1 -1
- package/viewer/assets/{erDiagram-RLTQ6QDP-BjTQFP-n.js → erDiagram-RLTQ6QDP-Dmfq32IQ.js} +1 -1
- package/viewer/assets/{erb-BWrVRqIb.js → erb-DfUVZzqX.js} +1 -1
- package/viewer/assets/eventmodeling-NTZA5JFV-BsZg3yrD.js +1 -0
- package/viewer/assets/flowDiagram-HODETNUW-55jB5pet.js +1 -0
- package/viewer/assets/{ganttDiagram-EL5Y4UJY-cvpUkVwQ.js → ganttDiagram-EL5Y4UJY-DVTv_n_T.js} +1 -1
- package/viewer/assets/{git-rebase-Bx9WSw2T.js → git-rebase-B22-kUR2.js} +1 -1
- package/viewer/assets/{gitGraph-4MIJSDKK-YF9cEiQc.js → gitGraph-4MIJSDKK-C3ZU1Sml.js} +1 -1
- package/viewer/assets/{gitGraphDiagram-WWUBYQGX-Cf7LUlAN.js → gitGraphDiagram-WWUBYQGX-BJqeOFYd.js} +1 -1
- package/viewer/assets/{glimmer-js-jJ5_T2M9.js → glimmer-js-Cm5f8kCy.js} +1 -1
- package/viewer/assets/{glimmer-ts-DqUZ7xnF.js → glimmer-ts-CQ-nNZCl.js} +1 -1
- package/viewer/assets/{glsl-DHzsK5Yf.js → glsl-XxfyrGb3.js} +1 -1
- package/viewer/assets/{graphql-TivQ3Vbm.js → graphql-DBlG1DNQ.js} +1 -1
- package/viewer/assets/{hack-CXzUKlVo.js → hack-Dkc1g494.js} +1 -1
- package/viewer/assets/{haml-DB9hsLVR.js → haml-D9E0-Hw0.js} +1 -1
- package/viewer/assets/{handlebars-Dfxd-Vhz.js → handlebars-Cb17q7C4.js} +1 -1
- package/viewer/assets/{html-CwO_HZk6.js → html-5E3Hc3ha.js} +1 -1
- package/viewer/assets/{html-derivative-Da9QhqUr.js → html-derivative-DOZTqg-2.js} +1 -1
- package/viewer/assets/{http-DaNELgRt.js → http-DJH-LMLV.js} +1 -1
- package/viewer/assets/{hurl-DCibLrD6.js → hurl-CEYg3T6-.js} +1 -1
- package/viewer/assets/{index-DVYWRkK6.css → index-CkY7ZfKs.css} +1 -1
- package/viewer/assets/{index-Cc4pf8mQ.js → index-Dfdu9MKA.js} +76 -76
- package/viewer/assets/{info-A6RAGUB7-B7Ru1Oky.js → info-A6RAGUB7-B-4F1obM.js} +1 -1
- package/viewer/assets/{infoDiagram-27XIBGKW-RlWd2kYA.js → infoDiagram-27XIBGKW-B8RIO086.js} +1 -1
- package/viewer/assets/{ishikawaDiagram-5VMMS53U-CS2-lnGi.js → ishikawaDiagram-5VMMS53U-DIGOldDh.js} +1 -1
- package/viewer/assets/{java-D2z6ozlO.js → java-e3vi9Uzs.js} +1 -1
- package/viewer/assets/{javascript-CxVsIZNi.js → javascript-DXnYMYO7.js} +1 -1
- package/viewer/assets/{jinja-BSUcBlS_.js → jinja-C-Qo2832.js} +1 -1
- package/viewer/assets/{jison-qcnqXcSy.js → jison-CfzTRAm0.js} +1 -1
- package/viewer/assets/{journeyDiagram-3NMN7TZE-DqPSRu4-.js → journeyDiagram-3NMN7TZE-DbQ5PKPQ.js} +1 -1
- package/viewer/assets/{json-eiyev3x0.js → json-BaqYW62L.js} +1 -1
- package/viewer/assets/{jsx-rDHm7tH3.js → jsx-DTafsOf-.js} +1 -1
- package/viewer/assets/{julia-cFfLusXt.js → julia-Djdi6Tch.js} +1 -1
- package/viewer/assets/{just-D6wClmab.js → just-gZxWcw47.js} +1 -1
- package/viewer/assets/{kanban-definition-UXKFOSKX-BI_a5cRc.js → kanban-definition-UXKFOSKX-CwEeXphJ.js} +1 -1
- package/viewer/assets/{latex-BvnFBRMG.js → latex-CIO1OdmU.js} +1 -1
- package/viewer/assets/{line-ChPI9Rz5.js → line-Basn7TUv.js} +1 -1
- package/viewer/assets/{linear-CBr5H_et.js → linear-Bu88wzXh.js} +1 -1
- package/viewer/assets/{liquid-CIOCL1uY.js → liquid-mYuCSXLi.js} +1 -1
- package/viewer/assets/{lua-Dq6WuAHP.js → lua-lXhbu8ma.js} +1 -1
- package/viewer/assets/{marko-Nl8_Nklj.js → marko-BH-anPb4.js} +1 -1
- package/viewer/assets/{mdc-obW39E44.js → mdc-DOuUtwPr.js} +1 -1
- package/viewer/assets/{mermaid-parser.core-BFOx64cQ.js → mermaid-parser.core-CiGT3tRM.js} +3 -3
- package/viewer/assets/{mermaid.core-BDe6Tfbh.js → mermaid.core-BfFqBx2V.js} +4 -4
- package/viewer/assets/{mindmap-definition-YA3MSWOX-CjzA1VTL.js → mindmap-definition-YA3MSWOX-d_TnVAJG.js} +1 -1
- package/viewer/assets/{nginx-Czz1mxQD.js → nginx-BB1dPcHJ.js} +1 -1
- package/viewer/assets/{nim-Boz13cJ3.js → nim-BSJ4UdAS.js} +1 -1
- package/viewer/assets/{org-BOlhXpYw.js → org-JRn8h-K_.js} +1 -1
- package/viewer/assets/{packet-AYTQ26CC-MMkKPhzF.js → packet-AYTQ26CC-BA7VIQ3l.js} +1 -1
- package/viewer/assets/{pegDiagram-XKGWAZYB-q1Rf8_Y9.js → pegDiagram-XKGWAZYB-pfsoYtx9.js} +1 -1
- package/viewer/assets/{perl-BA7VmyiG.js → perl-GzZ7epP9.js} +1 -1
- package/viewer/assets/{php-dYlaksWx.js → php-Crolu7lU.js} +1 -1
- package/viewer/assets/{pie-WAS4IAKB-CLljEA6l.js → pie-WAS4IAKB-B9gCbpVP.js} +1 -1
- package/viewer/assets/{pieDiagram-E7YTZNPT-DqzGIO9H.js → pieDiagram-E7YTZNPT-D578iJLj.js} +1 -1
- package/viewer/assets/{pug-WDsqiRUK.js → pug-BRaBHOlA.js} +1 -1
- package/viewer/assets/{qml-B5P0chYn.js → qml-BBlfnxMr.js} +1 -1
- package/viewer/assets/{quadrantDiagram-AXDQQJYC-JV3sYNXt.js → quadrantDiagram-AXDQQJYC-DoKeLBeZ.js} +1 -1
- package/viewer/assets/{r-DmZfBDub.js → r-DG1YsJtP.js} +1 -1
- package/viewer/assets/{radar-RG4KPBEZ-DH4HiGh1.js → radar-RG4KPBEZ-Dr6Zcwqy.js} +1 -1
- package/viewer/assets/{railroad-74A4TZTK-BwJg41V9.js → railroad-74A4TZTK-CEncWApU.js} +1 -1
- package/viewer/assets/railroad-abnf-HS5TGJTU-BUgwRsng.js +1 -0
- package/viewer/assets/railroad-ebnf-LZEXJU2U-C22SR2MX.js +1 -0
- package/viewer/assets/railroad-peg-WCYAUIDC-DjI1jJwc.js +1 -0
- package/viewer/assets/{railroadDiagram-O6MQD6OU-CrclSzRj.js → railroadDiagram-O6MQD6OU-7j0Th0r6.js} +1 -1
- package/viewer/assets/{razor-BLQfBppW.js → razor-CY_sYdwH.js} +1 -1
- package/viewer/assets/{regexp-CePe_vV5.js → regexp-DZiZuF5z.js} +1 -1
- package/viewer/assets/{requirementDiagram-BXWQKSXE-5QlkAVAN.js → requirementDiagram-BXWQKSXE-Dk1vCdh-.js} +1 -1
- package/viewer/assets/{rst-D9pCulha.js → rst-Dn_AtwYd.js} +1 -1
- package/viewer/assets/{ruby-r3EAH5Rz.js → ruby-BIB6MqpW.js} +1 -1
- package/viewer/assets/{sankeyDiagram-P5KCCOFB-BJzQ5vkn.js → sankeyDiagram-P5KCCOFB-DaB06Zgj.js} +1 -1
- package/viewer/assets/{sas-Dxc2YEvn.js → sas-Ccxd8Air.js} +1 -1
- package/viewer/assets/{scss-rZd3d-ZI.js → scss-Cr7j0d4P.js} +1 -1
- package/viewer/assets/{sequenceDiagram-WJ2MYXX4--tS5gMY4.js → sequenceDiagram-WJ2MYXX4-BGEb3NdT.js} +1 -1
- package/viewer/assets/{shellscript-B-b5xnkP.js → shellscript-CHN-dwU6.js} +1 -1
- package/viewer/assets/{shellsession-DlWXnhiW.js → shellsession-uVGeQNm3.js} +1 -1
- package/viewer/assets/{soy-DXWgq6qD.js → soy-D7NWwJ5L.js} +1 -1
- package/viewer/assets/{sql-5vPpIo1-.js → sql-DRImQ2Oi.js} +1 -1
- package/viewer/assets/{src-eDHFRM9C.js → src-BnSwVg9U.js} +1 -1
- package/viewer/assets/{stata-BPQLlmzg.js → stata-BIe0AUpg.js} +1 -1
- package/viewer/assets/{stateDiagram-D77RDMKH-xapk0kUZ.js → stateDiagram-D77RDMKH-DsRu-B2h.js} +1 -1
- package/viewer/assets/stateDiagram-v2-MP3YSRHH-DtGwflXN.js +1 -0
- package/viewer/assets/{surrealql-CTEIAZfm.js → surrealql-LEEn9cBl.js} +1 -1
- package/viewer/assets/{svelte-B6EVsDV1.js → svelte-CAMB-f9H.js} +1 -1
- package/viewer/assets/{swimlanes-42K2YHIH-BLyG_o3-.js → swimlanes-42K2YHIH-DqXHORT2.js} +1 -1
- package/viewer/assets/swimlanesDiagram-VR7AAH4N-DCpSoqRu.js +8 -0
- package/viewer/assets/{templ-Bu4quHqE.js → templ-zrRSW6Nf.js} +1 -1
- package/viewer/assets/{tex-Dv_54fPG.js → tex-D4XEXDuZ.js} +1 -1
- package/viewer/assets/{timeline-definition-24CTP7MA-DBaZM3qq.js → timeline-definition-24CTP7MA-DSpHKRpt.js} +1 -1
- package/viewer/assets/{treeView-Q6P3EWNA-C3zH0Rwt.js → treeView-Q6P3EWNA-pE7RJ0LU.js} +1 -1
- package/viewer/assets/{treemap-WGGIJYW6-BTho8OjG.js → treemap-WGGIJYW6-CBfU2tIc.js} +1 -1
- package/viewer/assets/{ts-tags-0DHV6Smm.js → ts-tags-DqFdF1-k.js} +1 -1
- package/viewer/assets/{tsx-CmZ6PB1U.js → tsx-Dz5osFq9.js} +1 -1
- package/viewer/assets/{twig-u99yWuG6.js → twig-CQ3wxbq3.js} +1 -1
- package/viewer/assets/{typescript-WF79ZIBQ.js → typescript-Cu1dPDHw.js} +1 -1
- package/viewer/assets/{typst-DQ7XxvRZ.js → typst-DHrnWrov.js} +1 -1
- package/viewer/assets/{vennDiagram-4TSXK5OY-B5Cb69qm.js → vennDiagram-4TSXK5OY-BPPSR6nr.js} +1 -1
- package/viewer/assets/{vue-DXac3mhj.js → vue-BaoJW0em.js} +1 -1
- package/viewer/assets/{vue-html-FSSTYK94.js → vue-html-DB9LdXl9.js} +1 -1
- package/viewer/assets/{vue-vine-BDF8epqI.js → vue-vine-CqtlBPYW.js} +1 -1
- package/viewer/assets/{wardley-WFR3VGLG-DswcXSmz.js → wardley-WFR3VGLG-DfCbsSvk.js} +1 -1
- package/viewer/assets/{wardleyDiagram-VM6X3IG4-BPgNi0Kp.js → wardleyDiagram-VM6X3IG4-DeiKcJbE.js} +1 -1
- package/viewer/assets/{xml-HI856iuk.js → xml-CQ5T6aNZ.js} +1 -1
- package/viewer/assets/{xsl-Ck-aKGwi.js → xsl-DVQ2v5YX.js} +1 -1
- package/viewer/assets/{xychartDiagram-S5SC5T6Z-B8UAJr6c.js → xychartDiagram-S5SC5T6Z-BsvlKd7r.js} +1 -1
- package/viewer/assets/{yaml-DrPyW5uV.js → yaml-DXrThoWS.js} +1 -1
- package/viewer/index.html +2 -2
- package/viewer/assets/architecture-7GRP2DOG-ClUScBK0.js +0 -1
- package/viewer/assets/channel-DOp5Ibzq.js +0 -1
- package/viewer/assets/classDiagram-ZZMXUADV-fi0_kah3.js +0 -1
- package/viewer/assets/classDiagram-v2-VYDZK3BY-fi0_kah3.js +0 -1
- package/viewer/assets/eventmodeling-NTZA5JFV-QswxRol8.js +0 -1
- package/viewer/assets/flowDiagram-HODETNUW-DolIsASs.js +0 -1
- package/viewer/assets/railroad-abnf-HS5TGJTU-Byryy4k9.js +0 -1
- package/viewer/assets/railroad-ebnf-LZEXJU2U-BABBdJeJ.js +0 -1
- package/viewer/assets/railroad-peg-WCYAUIDC-C7YNbOxe.js +0 -1
- package/viewer/assets/stateDiagram-v2-MP3YSRHH-Cb8zYuMi.js +0 -1
- package/viewer/assets/swimlanesDiagram-VR7AAH4N-RLMdNA3S.js +0 -8
package/README.md
CHANGED
|
@@ -143,7 +143,9 @@ bunx --bun @hona/openeval view
|
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
The viewer opens at **http://127.0.0.1:4173**. `run` resumes the same aggregate;
|
|
146
|
-
scope flags select work while retaining existing scores.
|
|
146
|
+
scope flags select work while retaining existing scores. If a runner stops
|
|
147
|
+
mid-run, the next `run` waits until its heartbeat is two minutes old, keeps the
|
|
148
|
+
interrupted records, and collects that work again.
|
|
147
149
|
|
|
148
150
|
To remove a model from an existing aggregate, remove its entry from
|
|
149
151
|
`benchmark.ts`, then retire its active selections:
|
package/package.json
CHANGED
|
@@ -266,6 +266,7 @@ export async function loadBenchmark(
|
|
|
266
266
|
(key) =>
|
|
267
267
|
![
|
|
268
268
|
"name",
|
|
269
|
+
"suite",
|
|
269
270
|
"models",
|
|
270
271
|
"judge",
|
|
271
272
|
"repetitions",
|
|
@@ -277,6 +278,13 @@ export async function loadBenchmark(
|
|
|
277
278
|
)
|
|
278
279
|
)
|
|
279
280
|
throw new Error("benchmark.ts contains unsupported settings");
|
|
281
|
+
if (
|
|
282
|
+
definition.suite !== undefined &&
|
|
283
|
+
(typeof definition.suite !== "string" ||
|
|
284
|
+
!definition.suite.trim() ||
|
|
285
|
+
definition.suite.length > 80)
|
|
286
|
+
)
|
|
287
|
+
throw new Error("benchmark.ts suite must be a label of 1 to 80 characters");
|
|
280
288
|
if (
|
|
281
289
|
definition.categories !== undefined &&
|
|
282
290
|
(!Array.isArray(definition.categories) || !definition.categories.length)
|
|
@@ -352,6 +360,7 @@ export async function loadBenchmark(
|
|
|
352
360
|
throw new Error("Websearch must be exa or false");
|
|
353
361
|
return {
|
|
354
362
|
name: definition.name ?? basename(directory),
|
|
363
|
+
...(definition.suite ? { suite: definition.suite.trim() } : {}),
|
|
355
364
|
directory,
|
|
356
365
|
models,
|
|
357
366
|
evals,
|
|
@@ -22,14 +22,18 @@ export {
|
|
|
22
22
|
export const slotId = (evalId: string, model: ModelRef, repetition: number) =>
|
|
23
23
|
`${evalId}:${model}:${repetition}`;
|
|
24
24
|
|
|
25
|
-
/** Plan only missing or changed work. A failed execution requires an explicit retry
|
|
25
|
+
/** Plan only missing or changed work. A failed execution requires an explicit retry,
|
|
26
|
+
* except work a stopped runner interrupted. `stoppedRunner` plans open executions that way. */
|
|
26
27
|
export function planBenchmark(
|
|
27
28
|
definition: BenchmarkDefinition,
|
|
28
29
|
runtime: BenchmarkRun["runtime"],
|
|
29
30
|
results: Results,
|
|
31
|
+
options: { stoppedRunner?: boolean } = {},
|
|
30
32
|
): PlanItem[] {
|
|
31
33
|
const previous = new Map(results.slots().map((slot) => [slot.id, slot]));
|
|
32
34
|
const judgments = results.judgeRuns();
|
|
35
|
+
const interrupted = (run: { state: string; interrupted?: boolean }) =>
|
|
36
|
+
!!run.interrupted || (!!options.stoppedRunner && run.state === "running");
|
|
33
37
|
return Array.from(
|
|
34
38
|
{ length: definition.repetitions },
|
|
35
39
|
(_, index) => index + 1,
|
|
@@ -70,6 +74,17 @@ export function planBenchmark(
|
|
|
70
74
|
: "New eval/model/repetition",
|
|
71
75
|
};
|
|
72
76
|
const execution = results.evalRun(slot.evalRunId)!;
|
|
77
|
+
if (interrupted(execution))
|
|
78
|
+
return {
|
|
79
|
+
slot: {
|
|
80
|
+
...slot,
|
|
81
|
+
evalRunId: null,
|
|
82
|
+
judgeRunId: null,
|
|
83
|
+
previousEvalRunId: execution.id,
|
|
84
|
+
},
|
|
85
|
+
action: "candidate",
|
|
86
|
+
reason: "The runner stopped during this session; collecting it again",
|
|
87
|
+
};
|
|
73
88
|
if (!canJudgeEval(execution))
|
|
74
89
|
return {
|
|
75
90
|
slot,
|
|
@@ -119,12 +134,13 @@ export function planBenchmark(
|
|
|
119
134
|
action: "reuse",
|
|
120
135
|
reason: "Candidate inputs and judgment unchanged",
|
|
121
136
|
};
|
|
122
|
-
const
|
|
137
|
+
const attempts = judgments.filter(
|
|
123
138
|
(run) =>
|
|
124
139
|
run.input.evalRunId === slot.evalRunId &&
|
|
125
140
|
run.input.judgeHash === slot.judgeHash &&
|
|
126
141
|
run.state !== "completed",
|
|
127
142
|
);
|
|
143
|
+
const failed = attempts.find((run) => !interrupted(run));
|
|
128
144
|
if (failed)
|
|
129
145
|
return {
|
|
130
146
|
slot,
|
|
@@ -134,9 +150,11 @@ export function planBenchmark(
|
|
|
134
150
|
return {
|
|
135
151
|
slot,
|
|
136
152
|
action: "judge",
|
|
137
|
-
reason:
|
|
138
|
-
? "
|
|
139
|
-
:
|
|
153
|
+
reason: attempts.length
|
|
154
|
+
? "The runner stopped during judging; judging again"
|
|
155
|
+
: !sameJudge
|
|
156
|
+
? "Judge inputs changed"
|
|
157
|
+
: "Recorded work is ready for judging",
|
|
140
158
|
};
|
|
141
159
|
}),
|
|
142
160
|
),
|
package/src/app/read-results.ts
CHANGED
|
@@ -58,6 +58,7 @@ const entryOf = (
|
|
|
58
58
|
name: evalId
|
|
59
59
|
? (run.definition.evals.find((item) => item.id === evalId)?.name ?? evalId)
|
|
60
60
|
: run.name,
|
|
61
|
+
...(!evalId && run.definition.suite ? { suite: run.definition.suite } : {}),
|
|
61
62
|
status: run.state,
|
|
62
63
|
startedAt: run.createdAt,
|
|
63
64
|
updatedAt: Date.parse(run.updatedAt),
|
|
@@ -71,13 +72,29 @@ export const evalResultId = (benchmarkId: string, evalId: string) =>
|
|
|
71
72
|
export class ResultReader {
|
|
72
73
|
private paths = new Map<string, string>();
|
|
73
74
|
private runtimes = new Map<string, RuntimeInterval[]>();
|
|
75
|
+
private finishedJudges = new Map<string, Map<string, JudgeRun>>();
|
|
74
76
|
constructor(readonly root: string) {}
|
|
77
|
+
/** Every judge run; parses only running or unseen records, since finalized runs never change. */
|
|
78
|
+
private judgeRunsOf(results: Results) {
|
|
79
|
+
const cache =
|
|
80
|
+
this.finishedJudges.get(results.path) ?? new Map<string, JudgeRun>();
|
|
81
|
+
this.finishedJudges.set(results.path, cache);
|
|
82
|
+
return results.judgeRunStates().flatMap(({ id }) => {
|
|
83
|
+
const cached = cache.get(id);
|
|
84
|
+
if (cached) return [cached];
|
|
85
|
+
const run = results.judgeRun(id);
|
|
86
|
+
if (!run) return [];
|
|
87
|
+
if (run.state !== "running") cache.set(id, run);
|
|
88
|
+
return [run];
|
|
89
|
+
});
|
|
90
|
+
}
|
|
75
91
|
private runtime(
|
|
76
92
|
run: EvalRun | JudgeRun,
|
|
77
93
|
results: Results,
|
|
78
94
|
benchmark: BenchmarkRun,
|
|
79
95
|
now: number,
|
|
80
96
|
candidate?: EvalRun,
|
|
97
|
+
judges?: readonly JudgeRun[],
|
|
81
98
|
) {
|
|
82
99
|
const key = `${results.path}:${run.id}`;
|
|
83
100
|
const cached = this.runtimes.get(key);
|
|
@@ -91,8 +108,7 @@ export class ResultReader {
|
|
|
91
108
|
scheduled(slot, benchmark, now);
|
|
92
109
|
if (current && "evalRunId" in run.input)
|
|
93
110
|
current =
|
|
94
|
-
results
|
|
95
|
-
.judgeRuns()
|
|
111
|
+
(judges ?? this.judgeRunsOf(results))
|
|
96
112
|
.filter(
|
|
97
113
|
(judge) =>
|
|
98
114
|
judge.input.evalRunId === candidate!.id &&
|
|
@@ -271,16 +287,17 @@ export class ResultReader {
|
|
|
271
287
|
(item) => !evalId || item.id === evalId,
|
|
272
288
|
);
|
|
273
289
|
if (!evals.length) throw new Error("Unknown eval");
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
),
|
|
290
|
+
const [slots, allJudges] = results.readSnapshot(
|
|
291
|
+
() => [results.slots(), this.judgeRunsOf(results)] as const,
|
|
292
|
+
);
|
|
293
|
+
const scores = benchmarkScores(run.definition, slots, allJudges, evalId);
|
|
294
|
+
// Runtime and cost describe the work behind the active selections, not retired history.
|
|
295
|
+
const active = new Set(
|
|
296
|
+
slots.filter((slot) => slot.active).map((slot) => slot.id),
|
|
281
297
|
);
|
|
282
|
-
const candidates = results
|
|
283
|
-
|
|
298
|
+
const candidates = results
|
|
299
|
+
.evalRuns()
|
|
300
|
+
.filter((candidate) => active.has(candidate.slotId));
|
|
284
301
|
const now = Date.now(),
|
|
285
302
|
candidateIndex = new Map(candidates.map((run) => [run.id, run]));
|
|
286
303
|
const evalRuntime = new Map<string, RuntimeInterval[]>();
|
|
@@ -291,7 +308,9 @@ export class ResultReader {
|
|
|
291
308
|
: (execution as EvalRun);
|
|
292
309
|
if (!candidate) continue;
|
|
293
310
|
const intervals = evalRuntime.get(candidate.input.evalId) ?? [];
|
|
294
|
-
intervals.push(
|
|
311
|
+
intervals.push(
|
|
312
|
+
...this.runtime(execution, results, run, now, candidate, allJudges),
|
|
313
|
+
);
|
|
295
314
|
evalRuntime.set(candidate.input.evalId, intervals);
|
|
296
315
|
}
|
|
297
316
|
for (const [id, intervals] of evalRuntime)
|
|
@@ -323,6 +342,9 @@ export class ResultReader {
|
|
|
323
342
|
entry: entryOf(run, id, evalId),
|
|
324
343
|
overview: {
|
|
325
344
|
name: evalId ? evals[0].name : run.name,
|
|
345
|
+
...(!evalId && run.definition.suite
|
|
346
|
+
? { suite: run.definition.suite }
|
|
347
|
+
: {}),
|
|
326
348
|
kind: evalId ? "eval" : "benchmark",
|
|
327
349
|
status: run.state,
|
|
328
350
|
startedAt: run.createdAt,
|
|
@@ -353,59 +375,69 @@ export class ResultReader {
|
|
|
353
375
|
if (split < 0) throw new Error("Select an eval");
|
|
354
376
|
const evalId = decodeURIComponent(id.slice(split + 1));
|
|
355
377
|
using results = await this.database(id);
|
|
356
|
-
const benchmark = results.benchmark
|
|
357
|
-
executions = results.evalRuns(),
|
|
358
|
-
judges = results.judgeRuns();
|
|
359
|
-
const runs = results
|
|
360
|
-
.slots()
|
|
361
|
-
.filter((slot) => slot.active && slot.evalId === evalId)
|
|
362
|
-
.map((slot) => {
|
|
363
|
-
const candidate = executions.find((run) => run.id === slot.evalRunId);
|
|
364
|
-
const current = liveRun(
|
|
365
|
-
slot,
|
|
366
|
-
candidate,
|
|
367
|
-
judges,
|
|
368
|
-
benchmark,
|
|
369
|
-
results,
|
|
370
|
-
(run) => this.runtime(run, results, benchmark, Date.now(), candidate),
|
|
371
|
-
);
|
|
372
|
-
const related = executions.filter((run) => run.slotId === slot.id),
|
|
373
|
-
ids = new Set(related.map((run) => run.id));
|
|
374
|
-
return {
|
|
375
|
-
...current,
|
|
376
|
-
cost: runCosts(
|
|
377
|
-
[
|
|
378
|
-
...related,
|
|
379
|
-
...judges.filter((run) => ids.has(run.input.evalRunId)),
|
|
380
|
-
],
|
|
381
|
-
results,
|
|
382
|
-
),
|
|
383
|
-
elapsedMs: runtimeMs(
|
|
384
|
-
[...current.eval.runtime, ...current.judge.runtime],
|
|
385
|
-
Date.now(),
|
|
386
|
-
),
|
|
387
|
-
};
|
|
388
|
-
});
|
|
378
|
+
const benchmark = results.benchmark!;
|
|
389
379
|
return {
|
|
390
380
|
entry: entryOf(benchmark, id, evalId),
|
|
391
381
|
eval: evalId,
|
|
392
382
|
benchmarkId: benchmark.id,
|
|
393
|
-
runs,
|
|
383
|
+
runs: this.liveRuns(results, [evalId]).get(evalId)!,
|
|
394
384
|
};
|
|
395
385
|
}
|
|
386
|
+
/** Active slot rows for the requested evals, from one read of the store. */
|
|
387
|
+
private liveRuns(results: Results, evalIds: readonly string[]) {
|
|
388
|
+
const benchmark = results.benchmark!,
|
|
389
|
+
executions = results.evalRuns(),
|
|
390
|
+
judges = this.judgeRunsOf(results),
|
|
391
|
+
now = Date.now();
|
|
392
|
+
const byId = new Map(executions.map((run) => [run.id, run]));
|
|
393
|
+
const bySlot = Map.groupBy(executions, (run) => run.slotId);
|
|
394
|
+
const judgesFor = Map.groupBy(judges, (run) => run.input.evalRunId);
|
|
395
|
+
const out = new Map<string, EvalRunSummary[]>(
|
|
396
|
+
evalIds.map((evalId) => [evalId, []]),
|
|
397
|
+
);
|
|
398
|
+
for (const slot of results.slots()) {
|
|
399
|
+
const rows = out.get(slot.evalId);
|
|
400
|
+
if (!slot.active || !rows) continue;
|
|
401
|
+
const candidate = slot.evalRunId ? byId.get(slot.evalRunId) : undefined;
|
|
402
|
+
const current = liveRun(
|
|
403
|
+
slot,
|
|
404
|
+
candidate,
|
|
405
|
+
candidate ? (judgesFor.get(candidate.id) ?? []) : [],
|
|
406
|
+
benchmark,
|
|
407
|
+
results,
|
|
408
|
+
(run) => this.runtime(run, results, benchmark, now, candidate, judges),
|
|
409
|
+
);
|
|
410
|
+
const related = bySlot.get(slot.id) ?? [];
|
|
411
|
+
rows.push({
|
|
412
|
+
...current,
|
|
413
|
+
cost: runCosts(
|
|
414
|
+
[...related, ...related.flatMap((run) => judgesFor.get(run.id) ?? [])],
|
|
415
|
+
results,
|
|
416
|
+
),
|
|
417
|
+
elapsedMs: runtimeMs(
|
|
418
|
+
[...current.eval.runtime, ...current.judge.runtime],
|
|
419
|
+
now,
|
|
420
|
+
),
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return out;
|
|
424
|
+
}
|
|
396
425
|
async activity(): Promise<ActivityRun[]> {
|
|
397
426
|
const index = await this.index(),
|
|
398
427
|
out: ActivityRun[] = [];
|
|
399
428
|
for (const entry of index.entries) {
|
|
429
|
+
const summary = await this.summary(entry.id);
|
|
400
430
|
using results = await this.database(entry.id);
|
|
401
431
|
const benchmark = results.benchmark!;
|
|
402
|
-
const
|
|
432
|
+
const live = this.liveRuns(
|
|
433
|
+
results,
|
|
434
|
+
benchmark.definition.evals.map((item) => item.id),
|
|
435
|
+
);
|
|
403
436
|
for (const [
|
|
404
437
|
evalOrder,
|
|
405
438
|
evalDefinition,
|
|
406
439
|
] of benchmark.definition.evals.entries()) {
|
|
407
|
-
const id = evalResultId(benchmark.id, evalDefinition.id)
|
|
408
|
-
runs = await this.evalRuns(id);
|
|
440
|
+
const id = evalResultId(benchmark.id, evalDefinition.id);
|
|
409
441
|
out.push({
|
|
410
442
|
id,
|
|
411
443
|
benchmarkId: benchmark.id,
|
|
@@ -415,15 +447,27 @@ export class ResultReader {
|
|
|
415
447
|
id,
|
|
416
448
|
eval: evalDefinition.id,
|
|
417
449
|
benchmark: benchmark.id,
|
|
450
|
+
name: benchmark.name,
|
|
451
|
+
...(benchmark.definition.suite
|
|
452
|
+
? { suite: benchmark.definition.suite }
|
|
453
|
+
: {}),
|
|
418
454
|
startedAt: benchmark.createdAt,
|
|
419
455
|
heartbeatAt: benchmark.updatedAt,
|
|
420
456
|
status: benchmark.state,
|
|
421
457
|
concurrency: benchmark.definition.concurrency,
|
|
422
458
|
evalOrder,
|
|
423
|
-
runs:
|
|
459
|
+
runs: live.get(evalDefinition.id)!,
|
|
424
460
|
runtime: summary.related.find(
|
|
425
461
|
(item) => item.eval === evalDefinition.id,
|
|
426
462
|
)!.runtime,
|
|
463
|
+
...(benchmark.execution
|
|
464
|
+
? {
|
|
465
|
+
invocation: {
|
|
466
|
+
startedAt: benchmark.execution.startedAt,
|
|
467
|
+
spentUSD: benchmark.execution.spentUSD,
|
|
468
|
+
},
|
|
469
|
+
}
|
|
470
|
+
: {}),
|
|
427
471
|
},
|
|
428
472
|
});
|
|
429
473
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { BenchmarkRun } from "../types";
|
|
2
|
+
import type { Results } from "../infra/sqlite";
|
|
3
|
+
|
|
4
|
+
/** A live runner saves its heartbeat every five seconds. */
|
|
5
|
+
export const STOPPED_RUNNER_MS = 120_000;
|
|
6
|
+
|
|
7
|
+
export const runnerStopped = (
|
|
8
|
+
benchmark: Pick<BenchmarkRun, "state" | "updatedAt">,
|
|
9
|
+
now = Date.now(),
|
|
10
|
+
) =>
|
|
11
|
+
benchmark.state === "running" &&
|
|
12
|
+
now - Date.parse(benchmark.updatedAt) > STOPPED_RUNNER_MS;
|
|
13
|
+
|
|
14
|
+
/** Close the executions a stopped runner left open. Their records stay; the planner repeats the work. */
|
|
15
|
+
export function recoverStoppedRunner(results: Results, now = new Date()) {
|
|
16
|
+
const time = now.toISOString();
|
|
17
|
+
const elapsedMs = (startedAt: string) =>
|
|
18
|
+
Math.max(0, now.getTime() - Date.parse(startedAt));
|
|
19
|
+
const recovered = { evalRuns: 0, judgeRuns: 0 };
|
|
20
|
+
results.transaction(() => {
|
|
21
|
+
for (const run of results.judgeRuns()) {
|
|
22
|
+
if (run.state !== "running") continue;
|
|
23
|
+
for (const check of results.judgeChecks(run.id))
|
|
24
|
+
if (check.state === "running")
|
|
25
|
+
results.finishJudgeCheck({
|
|
26
|
+
...check,
|
|
27
|
+
state: "cancelled",
|
|
28
|
+
completedAt: time,
|
|
29
|
+
});
|
|
30
|
+
results.finishJudge({
|
|
31
|
+
...run,
|
|
32
|
+
state: "failed",
|
|
33
|
+
interrupted: true,
|
|
34
|
+
error: "The runner stopped before judging finished",
|
|
35
|
+
completedAt: time,
|
|
36
|
+
elapsedMs: elapsedMs(run.startedAt),
|
|
37
|
+
});
|
|
38
|
+
recovered.judgeRuns++;
|
|
39
|
+
}
|
|
40
|
+
for (const run of results.evalRuns()) {
|
|
41
|
+
if (run.state !== "running") continue;
|
|
42
|
+
results.finishEval({
|
|
43
|
+
...run,
|
|
44
|
+
state: "failed",
|
|
45
|
+
interrupted: true,
|
|
46
|
+
error: "The runner stopped before this session finished",
|
|
47
|
+
completedAt: time,
|
|
48
|
+
elapsedMs: elapsedMs(run.startedAt),
|
|
49
|
+
...(run.stop ? { stop: { ...run.stop, applied: false } } : {}),
|
|
50
|
+
});
|
|
51
|
+
recovered.evalRuns++;
|
|
52
|
+
}
|
|
53
|
+
results.saveBenchmark({
|
|
54
|
+
...results.benchmark!,
|
|
55
|
+
state: "incomplete",
|
|
56
|
+
scheduledSlotIds: [],
|
|
57
|
+
updatedAt: time,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
return recovered;
|
|
61
|
+
}
|
package/src/app/rejudge.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { executeQueue, finishBenchmark } from "./run-benchmark";
|
|
|
8
8
|
import { judgingFingerprint } from "../infra/judging";
|
|
9
9
|
import { canJudgeEval } from "./eval-state";
|
|
10
10
|
import { verificationRuntime } from "../infra/verification/image";
|
|
11
|
+
import { recoverStoppedRunner, runnerStopped } from "./recover-run";
|
|
11
12
|
|
|
12
13
|
/** Rejudging changes only the selected judgment. The candidate execution is never repeated. */
|
|
13
14
|
export async function judgeRun(
|
|
@@ -26,13 +27,16 @@ export async function judgeRuns(
|
|
|
26
27
|
): Promise<JudgeRun[]> {
|
|
27
28
|
if (!evalRunIds.length) return [];
|
|
28
29
|
using results = new Results(resolve(directory, "runner.db"));
|
|
29
|
-
|
|
30
|
-
if (previous.mergedInto)
|
|
30
|
+
if (results.benchmark!.mergedInto)
|
|
31
31
|
throw new Error(
|
|
32
|
-
`This run was merged into ${
|
|
32
|
+
`This run was merged into ${results.benchmark!.mergedInto}; use the aggregate run`,
|
|
33
33
|
);
|
|
34
|
-
if (
|
|
35
|
-
|
|
34
|
+
if (results.benchmark!.state === "running") {
|
|
35
|
+
if (!runnerStopped(results.benchmark!))
|
|
36
|
+
throw new Error("Wait for the active benchmark run");
|
|
37
|
+
recoverStoppedRunner(results);
|
|
38
|
+
}
|
|
39
|
+
const previous = results.benchmark!;
|
|
36
40
|
const candidates = [...new Set(evalRunIds)].map((evalRunId) => {
|
|
37
41
|
const candidate = results.evalRun(evalRunId);
|
|
38
42
|
if (!canJudgeEval(candidate))
|
package/src/app/retry-run.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { candidateFingerprint } from "./plan-benchmark";
|
|
|
6
6
|
import { runEvalPipeline } from "./run-eval-pipeline";
|
|
7
7
|
import { ExecutionBudget } from "./execution-budget";
|
|
8
8
|
import { finishBenchmark } from "./run-benchmark";
|
|
9
|
+
import { recoverStoppedRunner, runnerStopped } from "./recover-run";
|
|
9
10
|
|
|
10
11
|
export async function retryEvalRun(
|
|
11
12
|
directory: string,
|
|
@@ -13,13 +14,16 @@ export async function retryEvalRun(
|
|
|
13
14
|
onEvent?: ExecutionObserver,
|
|
14
15
|
) {
|
|
15
16
|
using results = new Results(resolve(directory, "runner.db"));
|
|
16
|
-
|
|
17
|
-
if (benchmark.mergedInto)
|
|
17
|
+
if (results.benchmark!.mergedInto)
|
|
18
18
|
throw new Error(
|
|
19
|
-
`This run was merged into ${benchmark
|
|
19
|
+
`This run was merged into ${results.benchmark!.mergedInto}; use the aggregate run`,
|
|
20
20
|
);
|
|
21
|
-
if (benchmark
|
|
22
|
-
|
|
21
|
+
if (results.benchmark!.state === "running") {
|
|
22
|
+
if (!runnerStopped(results.benchmark!))
|
|
23
|
+
throw new Error("Wait for the active benchmark run");
|
|
24
|
+
recoverStoppedRunner(results);
|
|
25
|
+
}
|
|
26
|
+
const benchmark = results.benchmark!;
|
|
23
27
|
const previous = results.evalRun(evalRunId);
|
|
24
28
|
if (!previous || previous.state === "completed")
|
|
25
29
|
throw new Error(
|
package/src/app/run-benchmark.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { canJudgeEval } from "./eval-state";
|
|
|
23
23
|
import { isScored } from "../judgment";
|
|
24
24
|
import { benchmarkScores } from "./scores";
|
|
25
25
|
import { CostBudget, estimateWork } from "./cost-plan";
|
|
26
|
+
import { recoverStoppedRunner, runnerStopped } from "./recover-run";
|
|
26
27
|
import {
|
|
27
28
|
categoryKey,
|
|
28
29
|
categoryList,
|
|
@@ -101,7 +102,8 @@ export function finishBenchmark(context: ExecutionContext) {
|
|
|
101
102
|
(run) =>
|
|
102
103
|
run.input.evalRunId === slot.evalRunId &&
|
|
103
104
|
run.input.judgeHash === slot.judgeHash &&
|
|
104
|
-
["failed", "timed_out"].includes(run.state)
|
|
105
|
+
["failed", "timed_out"].includes(run.state) &&
|
|
106
|
+
!run.interrupted,
|
|
105
107
|
) &&
|
|
106
108
|
!slot.judgeRunId),
|
|
107
109
|
);
|
|
@@ -183,6 +185,7 @@ export async function runBenchmark(
|
|
|
183
185
|
(!options.fresh
|
|
184
186
|
? await currentBenchmarkRun(definition.directory)
|
|
185
187
|
: undefined);
|
|
188
|
+
let stopped = false;
|
|
186
189
|
if (directory) {
|
|
187
190
|
using previous = new Results(resolve(directory, "runner.db"), true);
|
|
188
191
|
const saved = previous.benchmark!;
|
|
@@ -190,8 +193,9 @@ export async function runBenchmark(
|
|
|
190
193
|
throw new Error(
|
|
191
194
|
`This run was merged into ${saved.mergedInto}; use the aggregate run`,
|
|
192
195
|
);
|
|
193
|
-
if (saved.state === "running")
|
|
196
|
+
if (saved.state === "running" && !runnerStopped(saved))
|
|
194
197
|
throw new Error("This benchmark run is already running");
|
|
198
|
+
stopped = saved.state === "running";
|
|
195
199
|
definition.models = [
|
|
196
200
|
...new Set([...saved.definition.models, ...definition.models]),
|
|
197
201
|
];
|
|
@@ -220,10 +224,16 @@ export async function runBenchmark(
|
|
|
220
224
|
? new Results(database, true)
|
|
221
225
|
: new Results(":memory:")
|
|
222
226
|
: new Results(database);
|
|
227
|
+
// A stopped runner leaves sessions open. A dry run plans them as interrupted work.
|
|
228
|
+
const recovered = stopped && !options.dryRun
|
|
229
|
+
? recoverStoppedRunner(results)
|
|
230
|
+
: undefined;
|
|
223
231
|
const previousSelections = new Map(
|
|
224
232
|
results.slots().map((slot) => [slot.id, slot]),
|
|
225
233
|
);
|
|
226
|
-
let plan = planBenchmark(definition, runtime, results
|
|
234
|
+
let plan = planBenchmark(definition, runtime, results, {
|
|
235
|
+
stoppedRunner: stopped,
|
|
236
|
+
});
|
|
227
237
|
if (selected || onlyEvals || options.onlyRepetitions) {
|
|
228
238
|
const retained = new Map(results.slots().map((slot) => [slot.id, slot]));
|
|
229
239
|
plan = plan.map((item) =>
|
|
@@ -260,7 +270,7 @@ export async function runBenchmark(
|
|
|
260
270
|
fingerprint(previous.runtime) === fingerprint(runtime) &&
|
|
261
271
|
fingerprint(previous.modelNames ?? {}) === fingerprint(modelNames)
|
|
262
272
|
)
|
|
263
|
-
return { directory, plan, estimate, benchmark: previous };
|
|
273
|
+
return { directory, plan, estimate, benchmark: previous, recovered };
|
|
264
274
|
const benchmark: BenchmarkRun = {
|
|
265
275
|
id: previous?.id ?? `benchmark_${randomUUID()}`,
|
|
266
276
|
name: definition.name,
|
|
@@ -397,7 +407,13 @@ export async function runBenchmark(
|
|
|
397
407
|
deferred: plan.filter((item) => item.action === "deferred").length,
|
|
398
408
|
},
|
|
399
409
|
});
|
|
400
|
-
return {
|
|
410
|
+
return {
|
|
411
|
+
directory,
|
|
412
|
+
plan,
|
|
413
|
+
estimate,
|
|
414
|
+
benchmark: finishBenchmark(context),
|
|
415
|
+
recovered,
|
|
416
|
+
};
|
|
401
417
|
} catch (error) {
|
|
402
418
|
results.saveBenchmark({
|
|
403
419
|
...results.benchmark!,
|
package/src/app/scores.ts
CHANGED
|
@@ -14,6 +14,15 @@ export function benchmarkScores(
|
|
|
14
14
|
const evals = definition.evals.filter(
|
|
15
15
|
(item) => !evalId || item.id === evalId,
|
|
16
16
|
);
|
|
17
|
+
// A code judgment counts only when it ran the eval's current judge.ts to completion.
|
|
18
|
+
const current = (item: (typeof evals)[number], slot: Slot) => {
|
|
19
|
+
const judge = slot.judgeRunId ? index.get(slot.judgeRunId) : undefined;
|
|
20
|
+
return !item.code ||
|
|
21
|
+
(judge?.code?.state === "completed" &&
|
|
22
|
+
judge.input.code?.hash === item.code.hash)
|
|
23
|
+
? judge
|
|
24
|
+
: undefined;
|
|
25
|
+
};
|
|
17
26
|
const keys = definition.categories?.map(categoryKey);
|
|
18
27
|
const definitions = new Map(
|
|
19
28
|
evals.map((item) => {
|
|
@@ -58,9 +67,7 @@ export function benchmarkScores(
|
|
|
58
67
|
const values = selected
|
|
59
68
|
.map(
|
|
60
69
|
(slot) =>
|
|
61
|
-
(slot.
|
|
62
|
-
? index.get(slot.judgeRunId)?.judgment?.scores
|
|
63
|
-
: undefined)?.[criterion.id]?.value,
|
|
70
|
+
current(item, slot)?.judgment?.scores?.[criterion.id]?.value,
|
|
64
71
|
)
|
|
65
72
|
.filter(isScored);
|
|
66
73
|
const scoredSum = values.reduce<number>(
|
|
@@ -86,26 +93,19 @@ export function benchmarkScores(
|
|
|
86
93
|
});
|
|
87
94
|
}),
|
|
88
95
|
evals.map((item) => item.id),
|
|
96
|
+
// Without a declared `criteria` export, judge.ts reveals its criteria only by running.
|
|
89
97
|
evals
|
|
90
98
|
.filter(
|
|
91
99
|
(item) =>
|
|
92
100
|
item.code &&
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const judge = slot.judgeRunId
|
|
102
|
-
? index.get(slot.judgeRunId)
|
|
103
|
-
: undefined;
|
|
104
|
-
return (
|
|
105
|
-
judge?.code?.state !== "completed" ||
|
|
106
|
-
judge.input.code?.hash !== item.code!.hash
|
|
107
|
-
);
|
|
108
|
-
}),
|
|
101
|
+
!item.codeCriteria?.length &&
|
|
102
|
+
slots.some(
|
|
103
|
+
(slot) =>
|
|
104
|
+
slot.active &&
|
|
105
|
+
slot.evalId === item.id &&
|
|
106
|
+
slot.model === model &&
|
|
107
|
+
!current(item, slot),
|
|
108
|
+
),
|
|
109
109
|
)
|
|
110
110
|
.map((item) => item.id),
|
|
111
111
|
),
|
package/src/app/serve-results.ts
CHANGED
|
@@ -16,6 +16,16 @@ export async function serveResults(options: {
|
|
|
16
16
|
await mkdir(root, { recursive: true });
|
|
17
17
|
const reader = new ResultReader(root),
|
|
18
18
|
encoder = new TextEncoder();
|
|
19
|
+
// Open tabs refresh on every change. Share one recent read so polling cannot saturate the server.
|
|
20
|
+
const recent = new Map<string, { at: number; value: Promise<unknown> }>();
|
|
21
|
+
const shared = <T>(key: string, read: () => Promise<T>): Promise<T> => {
|
|
22
|
+
const hit = recent.get(key);
|
|
23
|
+
if (hit && Date.now() - hit.at < 2000) return hit.value as Promise<T>;
|
|
24
|
+
const value = read();
|
|
25
|
+
recent.set(key, { at: Date.now(), value });
|
|
26
|
+
value.catch(() => recent.delete(key));
|
|
27
|
+
return value;
|
|
28
|
+
};
|
|
19
29
|
const clients = new Set<ReadableStreamDefaultController<Uint8Array>>(),
|
|
20
30
|
streams = new Set<() => void>();
|
|
21
31
|
let changed: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -44,7 +54,7 @@ export async function serveResults(options: {
|
|
|
44
54
|
if (url.pathname === "/api/results")
|
|
45
55
|
return Response.json(await reader.index());
|
|
46
56
|
if (url.pathname === "/api/activity")
|
|
47
|
-
return Response.json(await reader.activity());
|
|
57
|
+
return Response.json(await shared("activity", () => reader.activity()));
|
|
48
58
|
if (url.pathname === "/api/judge-checks")
|
|
49
59
|
return Response.json(
|
|
50
60
|
await reader.judgeAudit(
|
|
@@ -93,12 +103,14 @@ export async function serveResults(options: {
|
|
|
93
103
|
),
|
|
94
104
|
);
|
|
95
105
|
}
|
|
96
|
-
if (url.pathname === "/api/result")
|
|
106
|
+
if (url.pathname === "/api/result") {
|
|
107
|
+
const id = url.searchParams.get("id") ?? "";
|
|
97
108
|
return Response.json(
|
|
98
109
|
url.searchParams.get("format") === "runs"
|
|
99
|
-
? await reader.evalRuns(
|
|
100
|
-
: await reader.summary(
|
|
110
|
+
? await shared(`runs:${id}`, () => reader.evalRuns(id))
|
|
111
|
+
: await shared(`summary:${id}`, () => reader.summary(id)),
|
|
101
112
|
);
|
|
113
|
+
}
|
|
102
114
|
if (url.pathname === "/api/events") {
|
|
103
115
|
let owner: ReadableStreamDefaultController<Uint8Array>;
|
|
104
116
|
return new Response(
|
|
@@ -236,11 +248,16 @@ export async function serveResults(options: {
|
|
|
236
248
|
if (path !== assets && !path.startsWith(assets + sep))
|
|
237
249
|
return new Response("Not found", { status: 404 });
|
|
238
250
|
const file = Bun.file(path);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
251
|
+
const found = url.pathname !== "/" && (await file.exists());
|
|
252
|
+
return new Response(found ? file : Bun.file(resolve(assets, "index.html")), {
|
|
253
|
+
// Built assets have content-hashed names; the page itself must revalidate.
|
|
254
|
+
headers: {
|
|
255
|
+
"Cache-Control":
|
|
256
|
+
found && url.pathname.startsWith("/assets/")
|
|
257
|
+
? "public, max-age=31536000, immutable"
|
|
258
|
+
: "no-cache",
|
|
259
|
+
},
|
|
260
|
+
});
|
|
244
261
|
}
|
|
245
262
|
return new Response("Not found", { status: 404 });
|
|
246
263
|
} catch (error) {
|