@hona/openeval 0.2.2 → 0.3.0
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/JUDGING.md +134 -23
- package/README.md +67 -9
- package/package.json +3 -2
- package/src/app/cost-plan.ts +20 -16
- package/src/app/eval-state.ts +2 -1
- package/src/app/grade-recording.ts +72 -0
- package/src/app/input-fingerprints.ts +22 -8
- package/src/app/judge-evidence.ts +26 -11
- package/src/app/judge-run.ts +36 -19
- package/src/app/load-benchmark.ts +46 -15
- package/src/app/read-results.ts +35 -2
- package/src/app/read-run.ts +23 -2
- package/src/app/rejudge.ts +2 -1
- package/src/app/run-benchmark.ts +4 -0
- package/src/app/run-eval-pipeline.ts +2 -1
- package/src/app/run-eval.ts +13 -0
- package/src/app/scores.ts +76 -34
- package/src/app/serve-results.ts +2 -0
- package/src/evidence.ts +4 -1
- package/src/index.ts +15 -3
- package/src/infra/evidence/index.ts +39 -2
- package/src/infra/evidence/tool-calls.ts +5 -2
- package/src/infra/judging/agent.ts +3 -2
- package/src/infra/judging/code-result.ts +102 -0
- package/src/infra/judging/code-source.ts +96 -0
- package/src/infra/judging/code-worker.ts +26 -0
- package/src/infra/judging/code.ts +97 -0
- package/src/infra/judging/contract.ts +82 -64
- package/src/infra/judging/evidence-tool.ts +3 -1
- package/src/infra/judging/index.ts +14 -0
- package/src/infra/judging/judge-agent.md +24 -18
- package/src/infra/judging/observer.ts +8 -7
- package/src/infra/judging/submission.ts +8 -8
- package/src/infra/judging/tools.ts +9 -9
- package/src/infra/opencode/host.ts +1 -1
- package/src/infra/opencode/read-recording.ts +6 -1
- package/src/infra/opencode/version.ts +1 -0
- package/src/infra/recording/index.ts +244 -0
- package/src/infra/recording/metrics.ts +193 -0
- package/src/infra/sqlite/index.ts +29 -19
- package/src/judge-context.ts +141 -0
- package/src/judgment.ts +26 -17
- package/src/types.ts +36 -17
- package/src/view.ts +40 -14
- package/viewer/assets/{abnfDiagram-VCTEODGH-B1kcYgQG.js → abnfDiagram-VCTEODGH-rgmalTBh.js} +1 -1
- package/viewer/assets/{angular-html-DrYCUiZv.js → angular-html-CcaajvLj.js} +1 -1
- package/viewer/assets/{angular-ts-BgjwQn-Z.js → angular-ts-COx6AtG2.js} +1 -1
- package/viewer/assets/{apl-DlHqe8o4.js → apl-lBEnC_vz.js} +1 -1
- package/viewer/assets/{arc-C4nf7ZoY.js → arc-BQ0cQOwK.js} +1 -1
- package/viewer/assets/architecture-7GRP2DOG-DcUKSnYa.js +1 -0
- package/viewer/assets/{architectureDiagram-5GKGNRK7-BIWoe0fO.js → architectureDiagram-5GKGNRK7-_XIJP-LV.js} +1 -1
- package/viewer/assets/{astro-BrlHnIh9.js → astro-Ca8FrXna.js} +1 -1
- package/viewer/assets/{blade-DOcerOiW.js → blade-B07ZVjUX.js} +1 -1
- package/viewer/assets/{blockDiagram-I7D4REHJ-C3x13a9m.js → blockDiagram-I7D4REHJ-BdcDRYB-.js} +1 -1
- package/viewer/assets/{c-BMbThxEo.js → c-4MsqXzJK.js} +1 -1
- package/viewer/assets/{c4Diagram-7LVT6UL2-BeWktchx.js → c4Diagram-7LVT6UL2-E3nvwqBd.js} +1 -1
- package/viewer/assets/channel-CViXUg9J.js +1 -0
- package/viewer/assets/{chapel-k2cjkSmc.js → chapel-BlwR_8zu.js} +1 -1
- package/viewer/assets/{chunk-4HAMMTFA-o6YtJsqE.js → chunk-4HAMMTFA-DN_vtpqx.js} +1 -1
- package/viewer/assets/{chunk-75Z2AOVW-KohQa9Nw.js → chunk-75Z2AOVW-DWMGmicp.js} +1 -1
- package/viewer/assets/{chunk-DU6HZSFF-C88wxc6m.js → chunk-DU6HZSFF-D2RGHn75.js} +1 -1
- package/viewer/assets/{chunk-F27PBJKO-mO4AP2as.js → chunk-F27PBJKO-wfQZ5Ilx.js} +1 -1
- package/viewer/assets/{chunk-GMAD6QVW-DzQX2Cld.js → chunk-GMAD6QVW-DrlfUg7k.js} +1 -1
- package/viewer/assets/{chunk-GVQU2GXP-CS78RfJD.js → chunk-GVQU2GXP-BHoRLKIU.js} +1 -1
- package/viewer/assets/{chunk-IMKFNOWR-DxS42GUo.js → chunk-IMKFNOWR-mxLyQhYU.js} +1 -1
- package/viewer/assets/{chunk-L3NEJ4N5-DGo-uYLy.js → chunk-L3NEJ4N5-IpGl-pvp.js} +1 -1
- package/viewer/assets/{chunk-OSK3NFVY-BOcXQG16.js → chunk-OSK3NFVY-t0yN50X6.js} +1 -1
- package/viewer/assets/{chunk-P2QGCYS3-B7GnZdIQ.js → chunk-P2QGCYS3-BPBYYOcK.js} +1 -1
- package/viewer/assets/{chunk-POPQ4Y6H-DgBcYHog.js → chunk-POPQ4Y6H-DIOcv86B.js} +1 -1
- package/viewer/assets/{chunk-PWAF6VOD-DpEq6qHA.js → chunk-PWAF6VOD-CqVB-mzq.js} +1 -1
- package/viewer/assets/{chunk-SHT3W25Y-CrOGxKM2.js → chunk-SHT3W25Y-DwOEgFIr.js} +1 -1
- package/viewer/assets/{chunk-SVP7TREG-4HY7Fljj.js → chunk-SVP7TREG-Dfg1jHc3.js} +1 -1
- package/viewer/assets/{chunk-TICWLB2K-B1Rl31EC.js → chunk-TICWLB2K-DE6tQNRu.js} +1 -1
- package/viewer/assets/{chunk-XXDRQBXY-CK8giEW-.js → chunk-XXDRQBXY-Bu8dW06M.js} +1 -1
- package/viewer/assets/classDiagram-ZZMXUADV-O1Zm0_p8.js +1 -0
- package/viewer/assets/classDiagram-v2-VYDZK3BY-O1Zm0_p8.js +1 -0
- package/viewer/assets/{cobol-CupUwvw9.js → cobol-BS_DrT-b.js} +1 -1
- package/viewer/assets/{coffee-C17pkozF.js → coffee-Clk97ecB.js} +1 -1
- package/viewer/assets/{cose-bilkent-JH36ORCC-V1OmrmCv.js → cose-bilkent-JH36ORCC-B3xA8n2e.js} +1 -1
- package/viewer/assets/{cpp-BJwVQVXg.js → cpp-aunMdKLY.js} +1 -1
- package/viewer/assets/{crystal-BF-oCN-L.js → crystal-B1oihgG5.js} +1 -1
- package/viewer/assets/{css-BMgkVI3c.js → css-ClOubSVo.js} +1 -1
- package/viewer/assets/{cynefin-OW5HDTMX-BEjY_gsc.js → cynefin-OW5HDTMX-DdEeq9jN.js} +1 -1
- package/viewer/assets/{cynefinDiagram-5FMLGOSQ-bfSYgr6z.js → cynefinDiagram-5FMLGOSQ-BwHCV_5V.js} +1 -1
- package/viewer/assets/{dagre-GXQ25YYZ-VtwTgGfa.js → dagre-GXQ25YYZ-BkuWRmq-.js} +1 -1
- package/viewer/assets/{diagram-S7CK7UJ4-CHaAAyW7.js → diagram-S7CK7UJ4-C5WGyCny.js} +1 -1
- package/viewer/assets/{diagram-UQ7AKVKN-CjL65-As.js → diagram-UQ7AKVKN-mdununKH.js} +1 -1
- package/viewer/assets/{diagram-VSXAHHWV-Cnxrk1TJ.js → diagram-VSXAHHWV-CpuvU9Ij.js} +1 -1
- package/viewer/assets/{diagram-VX7I27RA-D78jPXr7.js → diagram-VX7I27RA-CcLmTPwv.js} +1 -1
- package/viewer/assets/{diagram-Z3DM3KII-DKjnOaF2.js → diagram-Z3DM3KII-DWCnKyKY.js} +1 -1
- package/viewer/assets/{dist-BTYPW0dz.js → dist-1DE7d697.js} +1 -1
- package/viewer/assets/{ebnfDiagram-PWID7BFC-7XiXh5Lx.js → ebnfDiagram-PWID7BFC-9M_-_M-B.js} +1 -1
- package/viewer/assets/{edge-B34N_HjN.js → edge-NsPpWeFX.js} +1 -1
- package/viewer/assets/{elixir-CQzWMyKD.js → elixir-CXZ4zQTn.js} +1 -1
- package/viewer/assets/{elm-BNJiG-wg.js → elm-DVXugwZs.js} +1 -1
- package/viewer/assets/{erDiagram-RLTQ6QDP-YPFEgZxU.js → erDiagram-RLTQ6QDP-B2j6iCkZ.js} +1 -1
- package/viewer/assets/{erb-D5Rm3UNa.js → erb-h8ybsnSQ.js} +1 -1
- package/viewer/assets/eventmodeling-NTZA5JFV-CpMf6avu.js +1 -0
- package/viewer/assets/flowDiagram-HODETNUW-DIKEcF1k.js +1 -0
- package/viewer/assets/{ganttDiagram-EL5Y4UJY-ergSGtTP.js → ganttDiagram-EL5Y4UJY-CR2QLJ-I.js} +1 -1
- package/viewer/assets/{git-rebase-BpIMZIRG.js → git-rebase-CT9f4kn8.js} +1 -1
- package/viewer/assets/{gitGraph-4MIJSDKK-CZt4REVj.js → gitGraph-4MIJSDKK-DGw-0dqe.js} +1 -1
- package/viewer/assets/{gitGraphDiagram-WWUBYQGX-h9QMzC2r.js → gitGraphDiagram-WWUBYQGX-BW112jVV.js} +1 -1
- package/viewer/assets/{glimmer-js-SeSwns5_.js → glimmer-js-B17pi1gL.js} +1 -1
- package/viewer/assets/{glimmer-ts-BExI5tTz.js → glimmer-ts-CNmwRbOn.js} +1 -1
- package/viewer/assets/{glsl-CXikKw2b.js → glsl-By4kbzYa.js} +1 -1
- package/viewer/assets/{graphql-6GpQIInt.js → graphql-BrDJs0bG.js} +1 -1
- package/viewer/assets/{hack-i7rizvpB.js → hack-NosEIqRi.js} +1 -1
- package/viewer/assets/{haml-C68gmkTf.js → haml-B-GQD8Zj.js} +1 -1
- package/viewer/assets/{handlebars-BBn_c0Xt.js → handlebars-1vSTccT5.js} +1 -1
- package/viewer/assets/{html-BObmAZQA.js → html-BBEodwWE.js} +1 -1
- package/viewer/assets/{html-derivative-BjMuR1LM.js → html-derivative-ClDEAvlc.js} +1 -1
- package/viewer/assets/{http-BJBk8gt0.js → http-I0uH89QQ.js} +1 -1
- package/viewer/assets/{hurl-D7obCYfg.js → hurl-JU97iWqA.js} +1 -1
- package/viewer/assets/{index-FMtYXcX7.js → index-CXBRQlCH.js} +5 -5
- package/viewer/assets/{index-Wz0qQyIk.css → index-D6HZB4Yz.css} +1 -1
- package/viewer/assets/{info-A6RAGUB7-CrzitQIU.js → info-A6RAGUB7-aRrZ0_i2.js} +1 -1
- package/viewer/assets/{infoDiagram-27XIBGKW-D2CgRDxu.js → infoDiagram-27XIBGKW-BnD3mUdL.js} +1 -1
- package/viewer/assets/{ishikawaDiagram-5VMMS53U-BhnQMHOo.js → ishikawaDiagram-5VMMS53U-u-wcafFg.js} +1 -1
- package/viewer/assets/{java-5VJU_EW8.js → java-BxAxXJLN.js} +1 -1
- package/viewer/assets/{javascript-r8UgndxQ.js → javascript-DNepMMwm.js} +1 -1
- package/viewer/assets/{jinja-BY2ibHG5.js → jinja-CfKdKg9T.js} +1 -1
- package/viewer/assets/{jison-C_7YxymZ.js → jison-C62ElGA9.js} +1 -1
- package/viewer/assets/{journeyDiagram-3NMN7TZE-DSBzKQEa.js → journeyDiagram-3NMN7TZE-E6uhr_fk.js} +1 -1
- package/viewer/assets/{json-8e1WlJYD.js → json-DSQqeQzH.js} +1 -1
- package/viewer/assets/{jsx-BYp5GD4Y.js → jsx-CpZi9Ceb.js} +1 -1
- package/viewer/assets/{julia-Bkob1uVX.js → julia-CypyJgFE.js} +1 -1
- package/viewer/assets/{just-CUnSjty-.js → just-_p8XAy70.js} +1 -1
- package/viewer/assets/{kanban-definition-UXKFOSKX-CotbrQue.js → kanban-definition-UXKFOSKX-Blo6hDj7.js} +1 -1
- package/viewer/assets/{latex-UdE3ivqk.js → latex-Blby6_jP.js} +1 -1
- package/viewer/assets/{line-DDh6a6hz.js → line-CQc5Trjj.js} +1 -1
- package/viewer/assets/{linear-BxMsCbKb.js → linear-BjWjnaEC.js} +1 -1
- package/viewer/assets/{liquid-WL4MvEIT.js → liquid-x3m9Bx2x.js} +1 -1
- package/viewer/assets/{lua-CtOubZta.js → lua-Da9XAgwV.js} +1 -1
- package/viewer/assets/{marko-C0GSQJi3.js → marko-Gu1d_h3t.js} +1 -1
- package/viewer/assets/{mdc-DUivddFc.js → mdc-CM0pUbEe.js} +1 -1
- package/viewer/assets/{mermaid-parser.core-FhKE_uv2.js → mermaid-parser.core-DR191xUW.js} +3 -3
- package/viewer/assets/{mermaid.core-BSOWwyvd.js → mermaid.core-DnwMH2U1.js} +4 -4
- package/viewer/assets/{mindmap-definition-YA3MSWOX-tsLZ_Opn.js → mindmap-definition-YA3MSWOX-KrnPcZeq.js} +1 -1
- package/viewer/assets/{nginx-B__INAtC.js → nginx-DAKCD0HY.js} +1 -1
- package/viewer/assets/{nim-UEY12GJF.js → nim-BvNTuhqF.js} +1 -1
- package/viewer/assets/{org-DDIZsz95.js → org-B-tOc6Cc.js} +1 -1
- package/viewer/assets/{packet-AYTQ26CC-CqdJnqfo.js → packet-AYTQ26CC-Cl2KBEou.js} +1 -1
- package/viewer/assets/{pegDiagram-XKGWAZYB-BJs7qu7m.js → pegDiagram-XKGWAZYB--GvmNzTl.js} +1 -1
- package/viewer/assets/{perl-D5KwNZt7.js → perl-RyNpCOE-.js} +1 -1
- package/viewer/assets/{php-BEs7d_Dk.js → php-D5gke6Nh.js} +1 -1
- package/viewer/assets/{pie-WAS4IAKB-lgzqBWqy.js → pie-WAS4IAKB-RfZPDbVI.js} +1 -1
- package/viewer/assets/{pieDiagram-E7YTZNPT-Cxe9LnnE.js → pieDiagram-E7YTZNPT-BbcD9IEY.js} +1 -1
- package/viewer/assets/{pug-B0dsfQOV.js → pug-CSZdPWzF.js} +1 -1
- package/viewer/assets/{qml-XPIhrIzf.js → qml-CCUSNo18.js} +1 -1
- package/viewer/assets/{quadrantDiagram-AXDQQJYC-D5JGwC11.js → quadrantDiagram-AXDQQJYC-DHP-nbSh.js} +1 -1
- package/viewer/assets/{r-C6hQdpIM.js → r-Cl6F5Lf8.js} +1 -1
- package/viewer/assets/{radar-RG4KPBEZ-B7JRLhUA.js → radar-RG4KPBEZ-DdfC1yOQ.js} +1 -1
- package/viewer/assets/{railroad-74A4TZTK-Df7ZQBLn.js → railroad-74A4TZTK-MHxcKgVS.js} +1 -1
- package/viewer/assets/railroad-abnf-HS5TGJTU-yEJXYTCu.js +1 -0
- package/viewer/assets/railroad-ebnf-LZEXJU2U-CEc058YK.js +1 -0
- package/viewer/assets/railroad-peg-WCYAUIDC-0iFSHGbV.js +1 -0
- package/viewer/assets/{railroadDiagram-O6MQD6OU-DZJzpmP8.js → railroadDiagram-O6MQD6OU-sxdxPqy-.js} +1 -1
- package/viewer/assets/{razor-C3KSUuOw.js → razor-hm0vLE-h.js} +1 -1
- package/viewer/assets/{regexp-n-T936Rk.js → regexp-BhIot3VY.js} +1 -1
- package/viewer/assets/{requirementDiagram-BXWQKSXE-Be9lEFrk.js → requirementDiagram-BXWQKSXE-CrZXt7Yu.js} +1 -1
- package/viewer/assets/{rst-fVnHDyXD.js → rst-C3giCu1C.js} +1 -1
- package/viewer/assets/{ruby-DuRgxlXF.js → ruby-B6fOYc5V.js} +1 -1
- package/viewer/assets/{sankeyDiagram-P5KCCOFB-BsgKAEBN.js → sankeyDiagram-P5KCCOFB-BVK_LjzN.js} +1 -1
- package/viewer/assets/{sas-C0WA2OFa.js → sas-a9NUQlsy.js} +1 -1
- package/viewer/assets/{scss-CQDZ4smR.js → scss-ChVMSp7G.js} +1 -1
- package/viewer/assets/{sequenceDiagram-WJ2MYXX4-BkUDWL0D.js → sequenceDiagram-WJ2MYXX4-B4E1wY9a.js} +1 -1
- package/viewer/assets/{shellscript-CE84G0GP.js → shellscript-Dxbnxou2.js} +1 -1
- package/viewer/assets/{shellsession-BdJvdaqL.js → shellsession-DWcRFAj_.js} +1 -1
- package/viewer/assets/{soy-BSxOREC0.js → soy-C-D-pvKx.js} +1 -1
- package/viewer/assets/{sql-D4MN9uRI.js → sql-W7nl9Rxy.js} +1 -1
- package/viewer/assets/{src-BT_kESTA.js → src-C_D1vmKd.js} +1 -1
- package/viewer/assets/{stata-B6nXAaGV.js → stata-BWq3CAeQ.js} +1 -1
- package/viewer/assets/{stateDiagram-D77RDMKH-C80wp8xG.js → stateDiagram-D77RDMKH-Dc8nXUDp.js} +1 -1
- package/viewer/assets/stateDiagram-v2-MP3YSRHH-SMxxOURe.js +1 -0
- package/viewer/assets/{surrealql-fVjZmhV3.js → surrealql-BkXUcx4E.js} +1 -1
- package/viewer/assets/{svelte-DnBQ5p7d.js → svelte-CowGUt9l.js} +1 -1
- package/viewer/assets/{swimlanes-42K2YHIH-Cff6yT8e.js → swimlanes-42K2YHIH-BtZAMLQ7.js} +1 -1
- package/viewer/assets/swimlanesDiagram-VR7AAH4N-C_XphOba.js +8 -0
- package/viewer/assets/{templ-TCx51Uts.js → templ-ejLYfFxc.js} +1 -1
- package/viewer/assets/{tex-CF6SMAQ_.js → tex-McefVK6r.js} +1 -1
- package/viewer/assets/{timeline-definition-24CTP7MA-Cje7BvaN.js → timeline-definition-24CTP7MA-TvSh-paH.js} +1 -1
- package/viewer/assets/{treeView-Q6P3EWNA-D-QLY3IO.js → treeView-Q6P3EWNA-BRIxIFdd.js} +1 -1
- package/viewer/assets/{treemap-WGGIJYW6-BCHcTa4N.js → treemap-WGGIJYW6-CeY__ApH.js} +1 -1
- package/viewer/assets/{ts-tags-BqLRnADX.js → ts-tags-D8LHJIWj.js} +1 -1
- package/viewer/assets/{tsx-D7KpDX0b.js → tsx-BkPshBHh.js} +1 -1
- package/viewer/assets/{twig-CEZLObpN.js → twig-CmfWCxbk.js} +1 -1
- package/viewer/assets/{typescript-DAbIDLbf.js → typescript-DnVmzADb.js} +1 -1
- package/viewer/assets/{typst-CqN7-whv.js → typst-BpEOIbiv.js} +1 -1
- package/viewer/assets/{vennDiagram-4TSXK5OY-Bz20rx02.js → vennDiagram-4TSXK5OY-Ba-jimVX.js} +1 -1
- package/viewer/assets/{vue-html-DQmvvKbd.js → vue-html-C-XYg42q.js} +1 -1
- package/viewer/assets/{vue-vine-Did3qTVW.js → vue-vine-DK68dTen.js} +1 -1
- package/viewer/assets/{vue-kTxN6wdc.js → vue-zII4ln6o.js} +1 -1
- package/viewer/assets/{wardley-WFR3VGLG-CpXaqOit.js → wardley-WFR3VGLG-e2A2dYzM.js} +1 -1
- package/viewer/assets/{wardleyDiagram-VM6X3IG4-Cz06jHpA.js → wardleyDiagram-VM6X3IG4-D2Rq1FsU.js} +1 -1
- package/viewer/assets/{xml-te2hFcAA.js → xml-Cc-dIbaq.js} +1 -1
- package/viewer/assets/{xsl-X4fuKSVl.js → xsl-C7A6UsbO.js} +1 -1
- package/viewer/assets/{xychartDiagram-S5SC5T6Z-BXCw0sfu.js → xychartDiagram-S5SC5T6Z-De55Qsdg.js} +1 -1
- package/viewer/assets/{yaml-NHtDSZab.js → yaml-BJLNLIRM.js} +1 -1
- package/viewer/index.html +2 -2
- package/viewer/assets/architecture-7GRP2DOG-Be250VKZ.js +0 -1
- package/viewer/assets/channel-DtULXRee.js +0 -1
- package/viewer/assets/classDiagram-ZZMXUADV-Bmqu01Xf.js +0 -1
- package/viewer/assets/classDiagram-v2-VYDZK3BY-Bmqu01Xf.js +0 -1
- package/viewer/assets/eventmodeling-NTZA5JFV-CJCg5f6p.js +0 -1
- package/viewer/assets/flowDiagram-HODETNUW-BFzq4Zvs.js +0 -1
- package/viewer/assets/railroad-abnf-HS5TGJTU-Z8q3QVTf.js +0 -1
- package/viewer/assets/railroad-ebnf-LZEXJU2U-BWoRkG3W.js +0 -1
- package/viewer/assets/railroad-peg-WCYAUIDC-BzoV1cN7.js +0 -1
- package/viewer/assets/stateDiagram-v2-MP3YSRHH-CYdsKKlz.js +0 -1
- package/viewer/assets/swimlanesDiagram-VR7AAH4N-niTUGjMT.js +0 -8
|
@@ -11,6 +11,7 @@ import type { EvidenceFeed, EvidenceView, EvidenceQuery } from "../../evidence";
|
|
|
11
11
|
export type { EvidenceQuery } from "../../evidence";
|
|
12
12
|
import { trackTool } from "./tool-calls";
|
|
13
13
|
import { hash as sha256, writeJson } from "../files";
|
|
14
|
+
import { measureRecording } from "../recording/metrics";
|
|
14
15
|
type InferenceResponse = { text: string; textBlocks?: string[] };
|
|
15
16
|
type ArtifactPolicy = { excludeDirectories: string[] };
|
|
16
17
|
|
|
@@ -25,7 +26,7 @@ export type EvidenceQueryAudit = {
|
|
|
25
26
|
checkId?: string;
|
|
26
27
|
checkpoint?: EvidenceCheckpoint;
|
|
27
28
|
};
|
|
28
|
-
type FileEntry = {
|
|
29
|
+
export type FileEntry = {
|
|
29
30
|
path: string;
|
|
30
31
|
bytes: number;
|
|
31
32
|
sha256?: string;
|
|
@@ -37,7 +38,7 @@ export type EvidenceEvent = {
|
|
|
37
38
|
time: string;
|
|
38
39
|
event: OpenCodeStreamEvent;
|
|
39
40
|
};
|
|
40
|
-
type EvidenceManifest = {
|
|
41
|
+
export type EvidenceManifest = {
|
|
41
42
|
version: 1;
|
|
42
43
|
handle: "candidate";
|
|
43
44
|
coverage: "recorded-session" | "recorded-prefix";
|
|
@@ -446,6 +447,28 @@ export class CandidateEvidence {
|
|
|
446
447
|
readonly hash: string,
|
|
447
448
|
) {}
|
|
448
449
|
|
|
450
|
+
/** Native records for trusted code judges. LLM queries use the separate blinded view. */
|
|
451
|
+
rawEvents(): EvidenceEvent[] {
|
|
452
|
+
return structuredClone(this.records);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
toolCalls(): ToolCall[] {
|
|
456
|
+
return structuredClone(this.manifest.tools);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
files(revision: "initial" | "final" = "final"): FileEntry[] {
|
|
460
|
+
const snapshot = this.manifest[revision];
|
|
461
|
+
if (!snapshot)
|
|
462
|
+
throw new Error(`${revision} workspace snapshot is unavailable`);
|
|
463
|
+
return structuredClone(snapshot.files);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async readFile(path: string, revision: "initial" | "final" = "final") {
|
|
467
|
+
const entry = this.entry(path, revision);
|
|
468
|
+
if (!entry) throw new Error(`Artifact not found: ${path}`);
|
|
469
|
+
return this.bytes(entry);
|
|
470
|
+
}
|
|
471
|
+
|
|
449
472
|
view(reference: EvidenceRef): EvidenceView {
|
|
450
473
|
if (reference.hash !== this.hash)
|
|
451
474
|
throw new Error("Evidence view reference does not match its archive");
|
|
@@ -629,6 +652,20 @@ export class CandidateEvidence {
|
|
|
629
652
|
|
|
630
653
|
async query(input: EvidenceQuery): Promise<Record<string, unknown>> {
|
|
631
654
|
if (input.action === "summary") return this.summary();
|
|
655
|
+
if (input.action === "metrics") {
|
|
656
|
+
const metrics = measureRecording(this.records, this.manifest.tools, {
|
|
657
|
+
directory: this.directory,
|
|
658
|
+
hash: this.hash,
|
|
659
|
+
});
|
|
660
|
+
if (!input.metric) return { ...metrics };
|
|
661
|
+
let value: unknown = metrics;
|
|
662
|
+
for (const key of input.metric.split(".")) {
|
|
663
|
+
if (!value || typeof value !== "object" || !Object.hasOwn(value, key))
|
|
664
|
+
throw new Error(`Unknown recorded metric: ${input.metric}`);
|
|
665
|
+
value = (value as Record<string, unknown>)[key];
|
|
666
|
+
}
|
|
667
|
+
return { metric: input.metric, value, evidence: metrics.evidence };
|
|
668
|
+
}
|
|
632
669
|
if (input.action === "response")
|
|
633
670
|
return textPage(this.manifest.response.text, input);
|
|
634
671
|
if (input.action === "events")
|
|
@@ -8,6 +8,7 @@ export function trackTool(
|
|
|
8
8
|
if (!("data" in event)) return;
|
|
9
9
|
const data = event.data as {
|
|
10
10
|
id?: string;
|
|
11
|
+
sessionID?: string;
|
|
11
12
|
name?: string;
|
|
12
13
|
assistantMessageID?: string;
|
|
13
14
|
input?: Record<string, unknown>;
|
|
@@ -17,8 +18,10 @@ export function trackTool(
|
|
|
17
18
|
executed?: boolean;
|
|
18
19
|
};
|
|
19
20
|
if (!data.id) return;
|
|
20
|
-
const
|
|
21
|
+
const key = `${data.sessionID ?? ""}:${data.assistantMessageID ?? ""}:${data.id}`;
|
|
22
|
+
const value = calls.get(key) ?? {
|
|
21
23
|
id: data.id,
|
|
24
|
+
sessionID: data.sessionID,
|
|
22
25
|
name: data.name ?? "unknown",
|
|
23
26
|
assistantMessageId: data.assistantMessageID ?? "",
|
|
24
27
|
status: "preparing" as const,
|
|
@@ -45,5 +48,5 @@ export function trackTool(
|
|
|
45
48
|
if (value.startedAt !== undefined)
|
|
46
49
|
value.durationMs = event.created - value.startedAt;
|
|
47
50
|
}
|
|
48
|
-
calls.set(
|
|
51
|
+
calls.set(key, value);
|
|
49
52
|
}
|
|
@@ -18,7 +18,7 @@ export const JUDGE_TOOLS = [
|
|
|
18
18
|
*/
|
|
19
19
|
export const JUDGE_AGENT = {
|
|
20
20
|
id: "openeval-judge",
|
|
21
|
-
description: "Grades immutable recorded work against eval
|
|
21
|
+
description: "Grades immutable recorded work against eval criteria",
|
|
22
22
|
mode: "primary",
|
|
23
23
|
system: readFileSync(new URL("./judge-agent.md", import.meta.url), "utf8"),
|
|
24
24
|
permissions: [
|
|
@@ -35,6 +35,7 @@ export async function judgeConfiguration(
|
|
|
35
35
|
input: Pick<JudgeRunInput, "agent" | "rubric" | "websearch">,
|
|
36
36
|
directory: string,
|
|
37
37
|
) {
|
|
38
|
+
if (!input.agent) throw new Error("An LLM judge requires an agent profile");
|
|
38
39
|
const { id, ...agent } = input.agent;
|
|
39
40
|
const configuration = resolve(directory, "configuration");
|
|
40
41
|
const config = {
|
|
@@ -45,7 +46,7 @@ export async function judgeConfiguration(
|
|
|
45
46
|
...agent,
|
|
46
47
|
// Keep the rubric in the native system prompt so compaction cannot lose
|
|
47
48
|
// it. Request context and result data move through registered tools.
|
|
48
|
-
system: `${agent.system.trim()}\n\n# Eval
|
|
49
|
+
system: `${agent.system.trim()}\n\n# Eval rubric\n\n${input.rubric.trim()}`,
|
|
49
50
|
},
|
|
50
51
|
},
|
|
51
52
|
websearch: input.websearch ? { provider: input.websearch } : false,
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { JsonValue } from "../../judge-context";
|
|
2
|
+
import type { CriterionScore, Judgment } from "../../types";
|
|
3
|
+
import { criterionMean, isScored } from "../../judgment";
|
|
4
|
+
|
|
5
|
+
/** Preserve author JSON exactly; reject values JSON would silently discard or coerce. */
|
|
6
|
+
export function jsonOutput(
|
|
7
|
+
value: unknown,
|
|
8
|
+
path = "output",
|
|
9
|
+
seen = new Set<object>(),
|
|
10
|
+
): JsonValue {
|
|
11
|
+
if (value === null || typeof value === "boolean" || typeof value === "string")
|
|
12
|
+
return value;
|
|
13
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
14
|
+
if (typeof value !== "object" || !value || seen.has(value))
|
|
15
|
+
throw new Error(
|
|
16
|
+
`${path} must be JSON-compatible (no undefined, non-finite numbers, or cycles)`,
|
|
17
|
+
);
|
|
18
|
+
if (
|
|
19
|
+
!Array.isArray(value) &&
|
|
20
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
21
|
+
)
|
|
22
|
+
throw new Error(`${path} must be a plain JSON object`);
|
|
23
|
+
if (Object.getOwnPropertySymbols(value).length)
|
|
24
|
+
throw new Error(`${path} contains a symbol key`);
|
|
25
|
+
seen.add(value);
|
|
26
|
+
try {
|
|
27
|
+
if (Array.isArray(value))
|
|
28
|
+
return Array.from(value, (item, index) =>
|
|
29
|
+
jsonOutput(item, `${path}[${index}]`, seen),
|
|
30
|
+
);
|
|
31
|
+
return Object.fromEntries(
|
|
32
|
+
Object.entries(value).map(([key, item]) => [
|
|
33
|
+
key,
|
|
34
|
+
jsonOutput(item, `${path}.${key}`, seen),
|
|
35
|
+
]),
|
|
36
|
+
);
|
|
37
|
+
} finally {
|
|
38
|
+
seen.delete(value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function codeJudgment(output: JsonValue): Judgment {
|
|
43
|
+
const values =
|
|
44
|
+
output &&
|
|
45
|
+
typeof output === "object" &&
|
|
46
|
+
!Array.isArray(output) &&
|
|
47
|
+
"scores" in output
|
|
48
|
+
? output.scores
|
|
49
|
+
: {};
|
|
50
|
+
if (!values || typeof values !== "object" || Array.isArray(values))
|
|
51
|
+
throw new Error("output.scores must be an object keyed by criterion ID");
|
|
52
|
+
const scores = Object.fromEntries(
|
|
53
|
+
Object.entries(values).map(([id, supplied]): [string, CriterionScore] => {
|
|
54
|
+
if (!/^[a-z][a-z0-9_]*$/.test(id))
|
|
55
|
+
throw new Error(`Invalid criterion ID: ${id}`);
|
|
56
|
+
const value = typeof supplied === "boolean" ? Number(supplied) : supplied;
|
|
57
|
+
if (value !== null && !isScored(value))
|
|
58
|
+
throw new Error(
|
|
59
|
+
`scores.${id} must be a boolean, a finite number from 0 to 1, or null`,
|
|
60
|
+
);
|
|
61
|
+
return [
|
|
62
|
+
id,
|
|
63
|
+
{
|
|
64
|
+
value: value as number | null,
|
|
65
|
+
reason:
|
|
66
|
+
value === null
|
|
67
|
+
? "judge.ts returned an unresolved score."
|
|
68
|
+
: `judge.ts returned ${String(supplied)}.`,
|
|
69
|
+
evidence: value === null ? [] : [{ kind: "recording" }],
|
|
70
|
+
source: "judge.ts",
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
return {
|
|
76
|
+
scores,
|
|
77
|
+
value: criterionMean(scores),
|
|
78
|
+
reason: Object.keys(scores).length
|
|
79
|
+
? "Criterion scores computed by judge.ts."
|
|
80
|
+
: "The code judge returned data without criterion scores.",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function combineJudgments(...judgments: Judgment[]): Judgment {
|
|
85
|
+
const scores: Record<string, CriterionScore> = {};
|
|
86
|
+
for (const judgment of judgments)
|
|
87
|
+
for (const [id, score] of Object.entries(judgment.scores)) {
|
|
88
|
+
if (Object.hasOwn(scores, id))
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Duplicate criterion ID from judge.md and judge.ts: ${id}`,
|
|
91
|
+
);
|
|
92
|
+
scores[id] = score;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
scores,
|
|
96
|
+
value: criterionMean(scores),
|
|
97
|
+
reason: judgments
|
|
98
|
+
.map((judgment) => judgment.reason)
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.join("\n"),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import type { CodeJudgeDefinition } from "../../judge-context";
|
|
4
|
+
import { fingerprint } from "../files";
|
|
5
|
+
|
|
6
|
+
/** Bundle local imports without executing author code during planning. */
|
|
7
|
+
export async function compileCodeJudge(
|
|
8
|
+
file: string,
|
|
9
|
+
): Promise<CodeJudgeDefinition> {
|
|
10
|
+
file = resolve(file);
|
|
11
|
+
const dependencies: Record<string, string> = {};
|
|
12
|
+
const sdkManifest = fileURLToPath(
|
|
13
|
+
new URL("../../../package.json", import.meta.url),
|
|
14
|
+
);
|
|
15
|
+
dependencies[sdkManifest] = await Bun.file(sdkManifest).text();
|
|
16
|
+
const external = new Set<string>();
|
|
17
|
+
const built = await Bun.build({
|
|
18
|
+
entrypoints: [file],
|
|
19
|
+
root: dirname(file),
|
|
20
|
+
target: "bun",
|
|
21
|
+
format: "esm",
|
|
22
|
+
packages: "external",
|
|
23
|
+
sourcemap: "external",
|
|
24
|
+
plugins: [
|
|
25
|
+
{
|
|
26
|
+
name: "judge-dependencies",
|
|
27
|
+
setup(build) {
|
|
28
|
+
build.onResolve({ filter: /^[^./]/ }, (args) => {
|
|
29
|
+
if (
|
|
30
|
+
args.path.startsWith("node:") ||
|
|
31
|
+
args.path.startsWith("bun:") ||
|
|
32
|
+
isAbsolute(args.path)
|
|
33
|
+
)
|
|
34
|
+
return;
|
|
35
|
+
const path = Bun.resolveSync(
|
|
36
|
+
args.path,
|
|
37
|
+
args.resolveDir || dirname(file),
|
|
38
|
+
);
|
|
39
|
+
external.add(path);
|
|
40
|
+
return { path, external: true };
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
if (!built.success)
|
|
47
|
+
throw new Error(`Cannot compile judge.ts: ${built.logs.join("\n")}`);
|
|
48
|
+
const source = await built.outputs
|
|
49
|
+
.find((output) => output.kind === "entry-point")!
|
|
50
|
+
.text();
|
|
51
|
+
if (
|
|
52
|
+
!new Bun.Transpiler({ loader: "js" })
|
|
53
|
+
.scan(source)
|
|
54
|
+
.exports.includes("default")
|
|
55
|
+
)
|
|
56
|
+
throw new Error("judge.ts must have a default function export");
|
|
57
|
+
const sourceMap =
|
|
58
|
+
(await built.outputs
|
|
59
|
+
.find((output) => output.kind === "sourcemap")
|
|
60
|
+
?.text()) ?? "";
|
|
61
|
+
const collect = async (directory: string, stopAtPackage: boolean) => {
|
|
62
|
+
for (;;) {
|
|
63
|
+
let found = false;
|
|
64
|
+
for (const name of [
|
|
65
|
+
"package.json",
|
|
66
|
+
"bun.lock",
|
|
67
|
+
"bun.lockb",
|
|
68
|
+
"package-lock.json",
|
|
69
|
+
"pnpm-lock.yaml",
|
|
70
|
+
"yarn.lock",
|
|
71
|
+
]) {
|
|
72
|
+
const path = resolve(directory, name);
|
|
73
|
+
if (await Bun.file(path).exists()) {
|
|
74
|
+
dependencies[path] =
|
|
75
|
+
name === "bun.lockb"
|
|
76
|
+
? Buffer.from(await Bun.file(path).bytes()).toString("base64")
|
|
77
|
+
: await Bun.file(path).text();
|
|
78
|
+
found ||= name === "package.json";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (found && stopAtPackage) return;
|
|
82
|
+
const parent = dirname(directory);
|
|
83
|
+
if (parent === directory) return;
|
|
84
|
+
directory = parent;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
await collect(dirname(file), false);
|
|
88
|
+
for (const path of external) await collect(dirname(path), true);
|
|
89
|
+
return {
|
|
90
|
+
file,
|
|
91
|
+
source,
|
|
92
|
+
sourceMap,
|
|
93
|
+
dependencies,
|
|
94
|
+
hash: fingerprint({ source, sourceMap, dependencies }),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
import { openRecording, type RecordingInput } from "../recording";
|
|
3
|
+
import { jsonOutput } from "./code-result";
|
|
4
|
+
import { errorMessage, writeJson } from "../files";
|
|
5
|
+
import type { JudgeFunction, RunMetrics } from "../../judge-context";
|
|
6
|
+
|
|
7
|
+
type Job = RecordingInput & { module: string; output: string; scratch: string };
|
|
8
|
+
const job: Job = JSON.parse(await Bun.stdin.text());
|
|
9
|
+
let metrics: RunMetrics | undefined;
|
|
10
|
+
async function grade() {
|
|
11
|
+
await using context = await openRecording(job, job.scratch);
|
|
12
|
+
metrics = context.metrics;
|
|
13
|
+
const module = await import(pathToFileURL(job.module).href);
|
|
14
|
+
if (typeof module.default !== "function")
|
|
15
|
+
throw new Error("judge.ts must default-export a function");
|
|
16
|
+
const output = jsonOutput(await (module.default as JudgeFunction)(context));
|
|
17
|
+
return { output, metrics: context.metrics };
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
await writeJson(job.output, await grade());
|
|
21
|
+
process.exit(0);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
await writeJson(job.output, { error: errorMessage(error), metrics });
|
|
24
|
+
console.error(error);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type {
|
|
5
|
+
CodeJudgeDefinition,
|
|
6
|
+
CodeJudgeExecution,
|
|
7
|
+
JsonValue,
|
|
8
|
+
RunMetrics,
|
|
9
|
+
} from "../../judge-context";
|
|
10
|
+
import type { RecordingInput } from "../recording";
|
|
11
|
+
import { codeJudgment, jsonOutput } from "./code-result";
|
|
12
|
+
import { errorMessage, writeJson } from "../files";
|
|
13
|
+
|
|
14
|
+
/** A separate process bounds synchronous loops as well as asynchronous judges. */
|
|
15
|
+
export async function executeCodeJudge(
|
|
16
|
+
code: CodeJudgeDefinition,
|
|
17
|
+
recording: RecordingInput,
|
|
18
|
+
directory: string,
|
|
19
|
+
timeoutMs: number,
|
|
20
|
+
): Promise<CodeJudgeExecution> {
|
|
21
|
+
directory = resolve(directory);
|
|
22
|
+
await mkdir(directory, { recursive: true });
|
|
23
|
+
const module = resolve(directory, "judge.js"),
|
|
24
|
+
output = resolve(directory, "output.json");
|
|
25
|
+
const scratch = resolve(directory, "scratch");
|
|
26
|
+
const stdout = resolve(directory, "stdout.log"),
|
|
27
|
+
stderr = resolve(directory, "stderr.log");
|
|
28
|
+
await Bun.write(module, code.source);
|
|
29
|
+
await Bun.write(resolve(directory, "judge.js.map"), code.sourceMap);
|
|
30
|
+
await writeJson(resolve(directory, "dependencies.json"), code.dependencies);
|
|
31
|
+
const startedAt = new Date().toISOString();
|
|
32
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
33
|
+
let timedOut = false;
|
|
34
|
+
let raw: JsonValue | undefined, metrics: RunMetrics | undefined;
|
|
35
|
+
const base = { startedAt, sourceHash: code.hash, stdout, stderr };
|
|
36
|
+
try {
|
|
37
|
+
const child = Bun.spawn(
|
|
38
|
+
[
|
|
39
|
+
process.execPath,
|
|
40
|
+
fileURLToPath(new URL("./code-worker.ts", import.meta.url)),
|
|
41
|
+
],
|
|
42
|
+
{
|
|
43
|
+
cwd: directory,
|
|
44
|
+
stdin: "pipe",
|
|
45
|
+
stdout: Bun.file(stdout),
|
|
46
|
+
stderr: Bun.file(stderr),
|
|
47
|
+
windowsHide: true,
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
timer = setTimeout(() => {
|
|
51
|
+
timedOut = true;
|
|
52
|
+
child.kill();
|
|
53
|
+
}, timeoutMs);
|
|
54
|
+
await child.stdin.write(
|
|
55
|
+
JSON.stringify({ ...recording, module, output, scratch }),
|
|
56
|
+
);
|
|
57
|
+
child.stdin.end();
|
|
58
|
+
const exit = await child.exited;
|
|
59
|
+
if (timedOut)
|
|
60
|
+
throw new Error(`judge.ts exceeded its ${timeoutMs}ms time limit`);
|
|
61
|
+
const result = await Bun.file(output).json();
|
|
62
|
+
metrics = result.metrics;
|
|
63
|
+
if (exit !== 0 || result.error)
|
|
64
|
+
throw new Error(result.error ?? `judge.ts exited with code ${exit}`);
|
|
65
|
+
raw = jsonOutput(result.output);
|
|
66
|
+
const judgment = codeJudgment(raw);
|
|
67
|
+
return {
|
|
68
|
+
...base,
|
|
69
|
+
state: "completed",
|
|
70
|
+
completedAt: new Date().toISOString(),
|
|
71
|
+
elapsedMs: Date.now() - Date.parse(startedAt),
|
|
72
|
+
output: raw,
|
|
73
|
+
metrics,
|
|
74
|
+
scores: Object.fromEntries(
|
|
75
|
+
Object.entries(judgment.scores).map(([id, score]) => [id, score.value]),
|
|
76
|
+
),
|
|
77
|
+
};
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
...base,
|
|
81
|
+
state: timedOut ? "timed_out" : "failed",
|
|
82
|
+
completedAt: new Date().toISOString(),
|
|
83
|
+
elapsedMs: Date.now() - Date.parse(startedAt),
|
|
84
|
+
error: errorMessage(error),
|
|
85
|
+
output: raw,
|
|
86
|
+
metrics,
|
|
87
|
+
};
|
|
88
|
+
} finally {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
await rm(scratch, {
|
|
91
|
+
recursive: true,
|
|
92
|
+
force: true,
|
|
93
|
+
maxRetries: 5,
|
|
94
|
+
retryDelay: 100,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
EvidenceCitation,
|
|
3
3
|
Judgment,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
CriterionDefinition,
|
|
5
|
+
CriterionScore,
|
|
6
6
|
} from "../../types";
|
|
7
7
|
import type { EvidenceReader, EvidenceQuery } from "../../evidence";
|
|
8
|
-
import {
|
|
8
|
+
import { criterionMean, isScored } from "../../judgment";
|
|
9
9
|
import { Schema } from "effect";
|
|
10
10
|
|
|
11
11
|
export const STRICT_OBJECT = {
|
|
@@ -14,68 +14,77 @@ export const STRICT_OBJECT = {
|
|
|
14
14
|
|
|
15
15
|
const object = (value: unknown): value is Record<string, unknown> =>
|
|
16
16
|
!!value && typeof value === "object" && !Array.isArray(value);
|
|
17
|
-
const
|
|
17
|
+
const validScore = (value: unknown) =>
|
|
18
|
+
value === null || typeof value === "boolean" || isScored(value);
|
|
18
19
|
const reason = (value: unknown): value is string =>
|
|
19
20
|
typeof value === "string" && !!value.trim();
|
|
20
21
|
|
|
21
|
-
/** Validate structured tool arguments;
|
|
22
|
-
export function
|
|
22
|
+
/** Validate structured tool arguments; criterion meaning remains the judge's job. */
|
|
23
|
+
export function validateCriteria(
|
|
23
24
|
value: unknown,
|
|
24
|
-
expected: readonly
|
|
25
|
+
expected: readonly CriterionDefinition[],
|
|
25
26
|
): Judgment {
|
|
26
27
|
if (!object(value))
|
|
27
|
-
throw new Error("
|
|
28
|
+
throw new Error("scores must be an object keyed by criterion ID");
|
|
28
29
|
const ids = Object.keys(value);
|
|
29
30
|
if (
|
|
30
31
|
ids.length !== expected.length ||
|
|
31
|
-
ids.some((id) => !expected.some((
|
|
32
|
+
ids.some((id) => !expected.some((criterion) => criterion.id === id))
|
|
32
33
|
)
|
|
33
34
|
throw new Error(
|
|
34
|
-
`Submit exactly these
|
|
35
|
+
`Submit exactly these criterion IDs: ${expected.map((criterion) => criterion.id).join(", ")}`,
|
|
35
36
|
);
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
!item.sources
|
|
54
|
-
(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
37
|
+
const scores = Object.fromEntries(
|
|
38
|
+
expected.map(({ id }): [string, CriterionScore] => {
|
|
39
|
+
const item = value[id];
|
|
40
|
+
if (
|
|
41
|
+
!object(item) ||
|
|
42
|
+
!validScore(item.value) ||
|
|
43
|
+
!reason(item.reason) ||
|
|
44
|
+
!Array.isArray(item.evidence) ||
|
|
45
|
+
item.evidence.length > 32 ||
|
|
46
|
+
(item.value !== null && !item.evidence.length)
|
|
47
|
+
)
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Criterion ${id} requires a boolean, a value from 0 to 1, or null, a non-empty reason, and 1–32 citations when decided`,
|
|
50
|
+
);
|
|
51
|
+
const evidence = item.evidence.map(parseCitation);
|
|
52
|
+
if (
|
|
53
|
+
item.sources !== undefined &&
|
|
54
|
+
(!Array.isArray(item.sources) ||
|
|
55
|
+
!item.sources.every(
|
|
56
|
+
(url) => typeof url === "string" && /^https?:\/\//.test(url),
|
|
57
|
+
))
|
|
58
|
+
)
|
|
59
|
+
throw new Error("Criterion sources must be HTTP(S) URLs");
|
|
60
|
+
return [
|
|
61
|
+
id,
|
|
62
|
+
{
|
|
63
|
+
value:
|
|
64
|
+
typeof item.value === "boolean"
|
|
65
|
+
? Number(item.value)
|
|
66
|
+
: (item.value as number | null),
|
|
67
|
+
reason: item.reason,
|
|
68
|
+
evidence,
|
|
69
|
+
source: "judge.md",
|
|
70
|
+
...(item.sources ? { sources: [...(item.sources as string[])] } : {}),
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
66
75
|
return {
|
|
67
|
-
value:
|
|
68
|
-
reason:
|
|
69
|
-
.map((
|
|
76
|
+
value: criterionMean(scores),
|
|
77
|
+
reason: Object.entries(scores)
|
|
78
|
+
.map(([id, score]) => `${id}: ${score.reason}`)
|
|
70
79
|
.join("\n"),
|
|
71
|
-
|
|
80
|
+
scores,
|
|
72
81
|
};
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
function parseCitation(value: unknown): EvidenceCitation {
|
|
76
85
|
if (
|
|
77
86
|
!object(value) ||
|
|
78
|
-
!["response", "tool", "message", "event", "artifact"].includes(
|
|
87
|
+
!["response", "tool", "message", "event", "artifact", "metric"].includes(
|
|
79
88
|
String(value.kind),
|
|
80
89
|
)
|
|
81
90
|
)
|
|
@@ -110,7 +119,7 @@ function parseCitation(value: unknown): EvidenceCitation {
|
|
|
110
119
|
}
|
|
111
120
|
if (!reason(value.id)) throw new Error("Citation requires a recorded ID");
|
|
112
121
|
return {
|
|
113
|
-
kind: value.kind as "tool" | "message" | "event",
|
|
122
|
+
kind: value.kind as "tool" | "message" | "event" | "metric",
|
|
114
123
|
id: value.id,
|
|
115
124
|
...extra,
|
|
116
125
|
};
|
|
@@ -123,18 +132,22 @@ export async function validateCitations(
|
|
|
123
132
|
judgment: Judgment,
|
|
124
133
|
reader: EvidenceReader,
|
|
125
134
|
) {
|
|
126
|
-
for (const
|
|
127
|
-
for (const citation of
|
|
135
|
+
for (const [id, score] of Object.entries(judgment.scores))
|
|
136
|
+
for (const citation of score.evidence) {
|
|
128
137
|
const query: EvidenceQuery =
|
|
129
|
-
citation.kind === "
|
|
130
|
-
? {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
citation.kind === "recording"
|
|
139
|
+
? { action: "summary" }
|
|
140
|
+
: citation.kind === "metric"
|
|
141
|
+
? { action: "metrics", metric: citation.id }
|
|
142
|
+
: citation.kind === "artifact"
|
|
143
|
+
? {
|
|
144
|
+
action: "artifact",
|
|
145
|
+
path: citation.path,
|
|
146
|
+
revision: citation.revision,
|
|
147
|
+
}
|
|
148
|
+
: citation.kind === "response"
|
|
149
|
+
? { action: "response" }
|
|
150
|
+
: { action: citation.kind, id: citation.id };
|
|
138
151
|
let offset = citation.offset ?? 0,
|
|
139
152
|
found = false;
|
|
140
153
|
for (let page = 0; page < 100; page++) {
|
|
@@ -154,12 +167,12 @@ export async function validateCitations(
|
|
|
154
167
|
);
|
|
155
168
|
}
|
|
156
169
|
if (!found)
|
|
157
|
-
throw new Error(`Citation quote was not found for
|
|
170
|
+
throw new Error(`Citation quote was not found for criterion ${id}`);
|
|
158
171
|
}
|
|
159
172
|
}
|
|
160
173
|
|
|
161
174
|
/** The native tool validator exposes these shapes directly in Code Mode. */
|
|
162
|
-
export function
|
|
175
|
+
export function criteriaSchema(criteria: readonly CriterionDefinition[]) {
|
|
163
176
|
const citationFields = {
|
|
164
177
|
quote: Schema.optional(Schema.String.check(Schema.isMaxLength(2000))),
|
|
165
178
|
offset: Schema.optional(
|
|
@@ -175,7 +188,7 @@ export function metricsSchema(metrics: readonly MetricDefinition[]) {
|
|
|
175
188
|
}).annotate(STRICT_OBJECT),
|
|
176
189
|
Schema.Struct({
|
|
177
190
|
...citationFields,
|
|
178
|
-
kind: Schema.Literals(["tool", "message", "event"]),
|
|
191
|
+
kind: Schema.Literals(["tool", "message", "event", "metric"]),
|
|
179
192
|
id: Schema.NonEmptyString,
|
|
180
193
|
}).annotate(STRICT_OBJECT),
|
|
181
194
|
Schema.Struct({
|
|
@@ -186,7 +199,12 @@ export function metricsSchema(metrics: readonly MetricDefinition[]) {
|
|
|
186
199
|
}).annotate(STRICT_OBJECT),
|
|
187
200
|
]);
|
|
188
201
|
const decision = Schema.Struct({
|
|
189
|
-
value: Schema.NullOr(
|
|
202
|
+
value: Schema.NullOr(
|
|
203
|
+
Schema.Union([
|
|
204
|
+
Schema.Boolean,
|
|
205
|
+
Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 1 })),
|
|
206
|
+
]),
|
|
207
|
+
),
|
|
190
208
|
reason: Schema.NonEmptyString,
|
|
191
209
|
evidence: Schema.Array(citation).check(
|
|
192
210
|
Schema.isMinLength(0),
|
|
@@ -198,9 +216,9 @@ export function metricsSchema(metrics: readonly MetricDefinition[]) {
|
|
|
198
216
|
}).annotate(STRICT_OBJECT);
|
|
199
217
|
return Schema.Struct(
|
|
200
218
|
Object.fromEntries(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
decision.annotate({ description:
|
|
219
|
+
criteria.map((criterion) => [
|
|
220
|
+
criterion.id,
|
|
221
|
+
decision.annotate({ description: criterion.name }),
|
|
204
222
|
]),
|
|
205
223
|
),
|
|
206
224
|
).annotate(STRICT_OBJECT);
|