evalkit-py 0.2.0.dev12__py3-none-any.whl
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.
- evalkit/__init__.py +0 -0
- evalkit/api/__init__.py +0 -0
- evalkit/api/access.py +74 -0
- evalkit/api/agents.py +159 -0
- evalkit/api/app.py +360 -0
- evalkit/api/auth.py +225 -0
- evalkit/api/cli_dist.py +10 -0
- evalkit/api/datasets.py +169 -0
- evalkit/api/deps.py +79 -0
- evalkit/api/download.py +15 -0
- evalkit/api/evaluators.py +121 -0
- evalkit/api/experiments.py +616 -0
- evalkit/api/fixtures.py +95 -0
- evalkit/api/harness_check.py +48 -0
- evalkit/api/imports.py +180 -0
- evalkit/api/limits.py +84 -0
- evalkit/api/llm_providers.py +120 -0
- evalkit/api/mcp/__init__.py +0 -0
- evalkit/api/mcp/auth.py +100 -0
- evalkit/api/mcp/descriptions.py +336 -0
- evalkit/api/mcp/models.py +259 -0
- evalkit/api/mcp/server.py +101 -0
- evalkit/api/mcp/tools.py +844 -0
- evalkit/api/meta.py +19 -0
- evalkit/api/otel.py +350 -0
- evalkit/api/pull.py +197 -0
- evalkit/api/review.py +271 -0
- evalkit/api/runs.py +555 -0
- evalkit/api/server_keys.py +44 -0
- evalkit/api/tokens.py +51 -0
- evalkit/api/trace_imports.py +173 -0
- evalkit/api/trace_sources.py +49 -0
- evalkit/api/traces.py +196 -0
- evalkit/api/users.py +72 -0
- evalkit/api/workspaces.py +131 -0
- evalkit/bundled/contracts/atif-accepted.json +5 -0
- evalkit/bundled/contracts/custom-evaluator-api-v1.json +243 -0
- evalkit/bundled/contracts/harness-api-v1.json +938 -0
- evalkit/bundled/contracts/span-mapping-v1.json +314 -0
- evalkit/bundled/docs/concepts.md +26 -0
- evalkit/bundled/docs/config.md +38 -0
- evalkit/bundled/docs/harness-api.md +44 -0
- evalkit/bundled/docs/install.md +60 -0
- evalkit/bundled/docs/login.md +36 -0
- evalkit/bundled/docs/mappings.md +136 -0
- evalkit/bundled/docs/mcp.md +44 -0
- evalkit/bundled/docs/quickstart.md +142 -0
- evalkit/bundled/docs/sdk-runs.md +171 -0
- evalkit/bundled/docs/tracing.md +171 -0
- evalkit/bundled/docs/try-locally.md +288 -0
- evalkit/bundled/docs/workspaces.md +45 -0
- evalkit/bundled/skills/authoring-eval-cases/SKILL.md +387 -0
- evalkit/bundled/skills/authoring-eval-cases/references/overlays.md +83 -0
- evalkit/bundled/skills/authoring-eval-cases/references/tool-checks.md +71 -0
- evalkit/bundled/skills/connect-traces/SKILL.md +296 -0
- evalkit/bundled/skills/connect-traces/references/gen_ai.json +42 -0
- evalkit/bundled/skills/connect-traces/references/openinference.json +35 -0
- evalkit/bundled/skills/connect-traces/references/span-mapping-schema.md +88 -0
- evalkit/bundled/skills/eval-driven-development/SKILL.md +458 -0
- evalkit/bundled/skills/eval-driven-development/references/analyze-run.md +97 -0
- evalkit/bundled/skills/eval-driven-development/references/diagnose.md +130 -0
- evalkit/bundled/skills/eval-driven-development/references/synthesize-cases.md +87 -0
- evalkit/bundled/skills/eval-driven-development/references/synthesize.md +165 -0
- evalkit/bundled/skills/evalkit-deployment/SKILL.md +332 -0
- evalkit/bundled/skills/evalkit-deployment/config.example.yaml +49 -0
- evalkit/bundled/skills/evalkit-deployment/docker-compose.yml +51 -0
- evalkit/bundled/skills/evalkit-deployment/references/config-and-deploy.md +333 -0
- evalkit/bundled/skills/evalkit-deployment/references/harness-api.md +139 -0
- evalkit/bundled/skills/evalkit-deployment/try/.env.example +11 -0
- evalkit/bundled/skills/evalkit-deployment/try/agent.py +131 -0
- evalkit/bundled/skills/evalkit-deployment/try/cases/acme/balance-sheet/cases.yaml +25 -0
- evalkit/bundled/skills/evalkit-deployment/try/cases/acme/income-statement/cases.yaml +43 -0
- evalkit/bundled/skills/evalkit-deployment/try/config.yaml +17 -0
- evalkit/bundled/skills/evalkit-deployment/try/docker-compose.yml +82 -0
- evalkit/bundled/skills/evalkit-deployment/try/evalkit.yaml +4 -0
- evalkit/bundled/skills/evalkit-deployment/try/mapping.json +42 -0
- evalkit/bundled/skills/evalkit-deployment/try/run.py +33 -0
- evalkit/bundled/skills/evalkit-usage/SKILL.md +509 -0
- evalkit/bundled/skills/evalkit-usage/references/suite-fixtures.md +42 -0
- evalkit/bundled/ui/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- evalkit/bundled/ui/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- evalkit/bundled/ui/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- evalkit/bundled/ui/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- evalkit/bundled/ui/assets/abap-08VXUWAP.js +1 -0
- evalkit/bundled/ui/assets/apex-BWPQTe0t.js +1 -0
- evalkit/bundled/ui/assets/azcli-Bc_sGQ0U.js +1 -0
- evalkit/bundled/ui/assets/bat-i0X4ZdIN.js +1 -0
- evalkit/bundled/ui/assets/bicep-B5-_aFwp.js +2 -0
- evalkit/bundled/ui/assets/cameligo-DMUM7wLl.js +1 -0
- evalkit/bundled/ui/assets/clojure-Cm7r79vr.js +1 -0
- evalkit/bundled/ui/assets/codicon-DF1abBS2.ttf +0 -0
- evalkit/bundled/ui/assets/coffee-Ba7i2nA0.js +1 -0
- evalkit/bundled/ui/assets/cpp-C7h46wYY.js +1 -0
- evalkit/bundled/ui/assets/csharp-BKxtCVv1.js +1 -0
- evalkit/bundled/ui/assets/csp-bTuwJoIa.js +1 -0
- evalkit/bundled/ui/assets/css-DIMkf-bt.js +3 -0
- evalkit/bundled/ui/assets/css.worker-DwfUZ3fB.js +96 -0
- evalkit/bundled/ui/assets/cssMode-C97l1pbB.js +1 -0
- evalkit/bundled/ui/assets/cypher-CVaqCwHa.js +1 -0
- evalkit/bundled/ui/assets/dart-onAF5SnQ.js +1 -0
- evalkit/bundled/ui/assets/dockerfile-DZFCIeNp.js +1 -0
- evalkit/bundled/ui/assets/ecl-94qehhXe.js +1 -0
- evalkit/bundled/ui/assets/editor-DjpsE8to.css +1 -0
- evalkit/bundled/ui/assets/editor.api-NTesIxw5.js +908 -0
- evalkit/bundled/ui/assets/editor.worker-CRz__AKW.js +33 -0
- evalkit/bundled/ui/assets/elixir-6RTg0lbw.js +1 -0
- evalkit/bundled/ui/assets/flow9-C5_-GSwl.js +1 -0
- evalkit/bundled/ui/assets/freemarker2-BWqEFagp.js +3 -0
- evalkit/bundled/ui/assets/fsharp-C8Ef5oNN.js +1 -0
- evalkit/bundled/ui/assets/go-C-y9NEjX.js +1 -0
- evalkit/bundled/ui/assets/graphql-fmXr3nnJ.js +1 -0
- evalkit/bundled/ui/assets/handlebars-DkIF-whN.js +1 -0
- evalkit/bundled/ui/assets/hcl-CpzslTdj.js +1 -0
- evalkit/bundled/ui/assets/html-C_rpeQXJ.js +1 -0
- evalkit/bundled/ui/assets/html.worker-D9ugpX4y.js +509 -0
- evalkit/bundled/ui/assets/htmlMode-B0YIq3DF.js +1 -0
- evalkit/bundled/ui/assets/index-Cw-jBH6M.js +736 -0
- evalkit/bundled/ui/assets/index-DKtoQZtm.css +1 -0
- evalkit/bundled/ui/assets/ini-sBoK_t0W.js +1 -0
- evalkit/bundled/ui/assets/java-BEtHBSE6.js +1 -0
- evalkit/bundled/ui/assets/javascript-D4EGhYA6.js +1 -0
- evalkit/bundled/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- evalkit/bundled/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- evalkit/bundled/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- evalkit/bundled/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- evalkit/bundled/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- evalkit/bundled/ui/assets/json.worker-CV9fqkUp.js +65 -0
- evalkit/bundled/ui/assets/jsonMode-pQjSfIH6.js +7 -0
- evalkit/bundled/ui/assets/julia-Bri6UV-V.js +1 -0
- evalkit/bundled/ui/assets/kotlin-BOotOW0E.js +1 -0
- evalkit/bundled/ui/assets/less-B9JPFI3C.js +2 -0
- evalkit/bundled/ui/assets/lexon-CfSJPG6W.js +1 -0
- evalkit/bundled/ui/assets/liquid-DbrvoYjD.js +1 -0
- evalkit/bundled/ui/assets/lspLanguageFeatures-D0B7cH6y.js +4 -0
- evalkit/bundled/ui/assets/lua-CsQS60Ue.js +1 -0
- evalkit/bundled/ui/assets/m3-D-oSqn_W.js +1 -0
- evalkit/bundled/ui/assets/markdown-Cimd5fb3.js +1 -0
- evalkit/bundled/ui/assets/mdx-B0HrujLR.js +1 -0
- evalkit/bundled/ui/assets/mips-CIPQ_RoX.js +1 -0
- evalkit/bundled/ui/assets/monacoEditor-DsoPncxW.js +68 -0
- evalkit/bundled/ui/assets/monacoEditor-n7clI0si.css +1 -0
- evalkit/bundled/ui/assets/msdax-DauUninz.js +1 -0
- evalkit/bundled/ui/assets/mysql-SOo6toE5.js +1 -0
- evalkit/bundled/ui/assets/objective-c-FvmIjYaQ.js +1 -0
- evalkit/bundled/ui/assets/pascal-DrH0SRf2.js +1 -0
- evalkit/bundled/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
- evalkit/bundled/ui/assets/perl-oz_6vUea.js +1 -0
- evalkit/bundled/ui/assets/pgsql-DTj74zXo.js +1 -0
- evalkit/bundled/ui/assets/php-nr791fC2.js +1 -0
- evalkit/bundled/ui/assets/pla-CopQ2nXW.js +1 -0
- evalkit/bundled/ui/assets/postiats-43DmfD33.js +1 -0
- evalkit/bundled/ui/assets/powerquery-D3hlyOfw.js +1 -0
- evalkit/bundled/ui/assets/powershell-DmHpPYUd.js +1 -0
- evalkit/bundled/ui/assets/protobuf-C531GsRP.js +2 -0
- evalkit/bundled/ui/assets/pug-Z5eAx3Zn.js +1 -0
- evalkit/bundled/ui/assets/python-CieHVM9n.js +1 -0
- evalkit/bundled/ui/assets/qsharp-DkqhCAOL.js +1 -0
- evalkit/bundled/ui/assets/r-BwWrilGY.js +1 -0
- evalkit/bundled/ui/assets/razor-sLewSMyp.js +1 -0
- evalkit/bundled/ui/assets/redis-ClamHrr6.js +1 -0
- evalkit/bundled/ui/assets/redshift-DT7zqm-g.js +1 -0
- evalkit/bundled/ui/assets/restructuredtext-BYgofb2h.js +1 -0
- evalkit/bundled/ui/assets/ruby-DezsRK8O.js +1 -0
- evalkit/bundled/ui/assets/rust-DdL9SqIa.js +1 -0
- evalkit/bundled/ui/assets/sb-CcwsVR0C.js +1 -0
- evalkit/bundled/ui/assets/scala-DHpiXF5c.js +1 -0
- evalkit/bundled/ui/assets/scheme-BeGwcela.js +1 -0
- evalkit/bundled/ui/assets/scss-gp-XZpBa.js +3 -0
- evalkit/bundled/ui/assets/shell-CC2rA5mh.js +1 -0
- evalkit/bundled/ui/assets/solidity-BEEn4gHE.js +1 -0
- evalkit/bundled/ui/assets/sophia-CRfGWb83.js +1 -0
- evalkit/bundled/ui/assets/sparql-D_Lu-MrJ.js +1 -0
- evalkit/bundled/ui/assets/sql-NEE52Syq.js +1 -0
- evalkit/bundled/ui/assets/st-DbInun42.js +1 -0
- evalkit/bundled/ui/assets/swift-Bxkupp3x.js +1 -0
- evalkit/bundled/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
- evalkit/bundled/ui/assets/tcl-DISqw1ZD.js +1 -0
- evalkit/bundled/ui/assets/ts.worker-DIAQkoB3.js +67741 -0
- evalkit/bundled/ui/assets/tsMode-BcVQ1nu0.js +11 -0
- evalkit/bundled/ui/assets/twig-De2hgUGE.js +1 -0
- evalkit/bundled/ui/assets/typescript-CUvNWd9p.js +1 -0
- evalkit/bundled/ui/assets/typespec-B8J7ngcE.js +1 -0
- evalkit/bundled/ui/assets/vb-DV3o63ZY.js +1 -0
- evalkit/bundled/ui/assets/wgsl-DpFanUEy.js +298 -0
- evalkit/bundled/ui/assets/workers-CwpYQtzp.js +1 -0
- evalkit/bundled/ui/assets/xml-C1HnXMdF.js +1 -0
- evalkit/bundled/ui/assets/yaml-D7Ey3HQu.js +1 -0
- evalkit/bundled/ui/favicon.svg +6 -0
- evalkit/bundled/ui/index.html +14 -0
- evalkit/cli/__init__.py +0 -0
- evalkit/cli/admin_commands.py +590 -0
- evalkit/cli/client.py +67 -0
- evalkit/cli/init_server.py +165 -0
- evalkit/cli/local_run.py +195 -0
- evalkit/cli/main.py +1365 -0
- evalkit/cli/runs_commands.py +539 -0
- evalkit/cli/server_commands.py +130 -0
- evalkit/cli/skill_targets.py +43 -0
- evalkit/cli/tokens_commands.py +52 -0
- evalkit/cli/traces_commands.py +332 -0
- evalkit/cli/update.py +85 -0
- evalkit/client/__init__.py +0 -0
- evalkit/client/agents.py +25 -0
- evalkit/client/api.py +193 -0
- evalkit/client/blob.py +104 -0
- evalkit/client/connection.py +88 -0
- evalkit/client/evaluators.py +11 -0
- evalkit/client/sessions.py +17 -0
- evalkit/client/store.py +139 -0
- evalkit/client/summary.py +46 -0
- evalkit/client/sync.py +198 -0
- evalkit/config.py +227 -0
- evalkit/contracts/__init__.py +0 -0
- evalkit/contracts/atif.py +115 -0
- evalkit/contracts/cases.py +456 -0
- evalkit/contracts/custom_evaluator.py +58 -0
- evalkit/contracts/enums.py +86 -0
- evalkit/contracts/esf.py +120 -0
- evalkit/contracts/harness.py +281 -0
- evalkit/contracts/notes.json +4 -0
- evalkit/contracts/notes.py +7 -0
- evalkit/contracts/settings.py +41 -0
- evalkit/core/__init__.py +0 -0
- evalkit/core/access.py +75 -0
- evalkit/core/agents.py +65 -0
- evalkit/core/blob.py +46 -0
- evalkit/core/capabilities.py +36 -0
- evalkit/core/costmap.py +90 -0
- evalkit/core/custom_eval.py +50 -0
- evalkit/core/data/cost_map.json +81546 -0
- evalkit/core/endpoints.py +31 -0
- evalkit/core/evaluators/__init__.py +29 -0
- evalkit/core/evaluators/base.py +121 -0
- evalkit/core/evaluators/gauges.py +84 -0
- evalkit/core/evaluators/presentation.py +166 -0
- evalkit/core/evaluators/response_time.py +21 -0
- evalkit/core/evaluators/rubrics.py +494 -0
- evalkit/core/evaluators/tool_trajectory.py +187 -0
- evalkit/core/evaluators/trajectory_rubrics.py +198 -0
- evalkit/core/fixtures.py +57 -0
- evalkit/core/harness.py +227 -0
- evalkit/core/harness_check.py +191 -0
- evalkit/core/importer.py +257 -0
- evalkit/core/judge.py +78 -0
- evalkit/core/lint.py +265 -0
- evalkit/core/overlays.py +76 -0
- evalkit/core/paths.py +25 -0
- evalkit/core/promotion.py +94 -0
- evalkit/core/pull.py +198 -0
- evalkit/core/python_eval.py +59 -0
- evalkit/core/remote_evaluators.py +32 -0
- evalkit/core/review.py +220 -0
- evalkit/core/run_digest.py +421 -0
- evalkit/core/runner.py +995 -0
- evalkit/core/scorecard.py +255 -0
- evalkit/core/traces/__init__.py +0 -0
- evalkit/core/traces/convert.py +336 -0
- evalkit/core/traces/importer.py +113 -0
- evalkit/core/traces/mapping.py +385 -0
- evalkit/core/traces/poll.py +38 -0
- evalkit/core/traces/raw.py +21 -0
- evalkit/core/traces/sessions.py +172 -0
- evalkit/core/traces/settle.py +14 -0
- evalkit/core/traces/sources/__init__.py +19 -0
- evalkit/core/traces/sources/base.py +25 -0
- evalkit/core/traces/sources/phoenix.py +121 -0
- evalkit/core/trajectory.py +248 -0
- evalkit/core/workspace.py +30 -0
- evalkit/credentials.py +119 -0
- evalkit/db/__init__.py +0 -0
- evalkit/db/agents.py +289 -0
- evalkit/db/alembic/env.py +90 -0
- evalkit/db/alembic/script.py.mako +28 -0
- evalkit/db/alembic/versions/0011_baseline.py +1002 -0
- evalkit/db/alembic/versions/0012_ticket_token_kind.py +27 -0
- evalkit/db/alembic/versions/0013_keys_and_llm_providers.py +67 -0
- evalkit/db/cases.py +608 -0
- evalkit/db/crypto.py +12 -0
- evalkit/db/evaluators.py +156 -0
- evalkit/db/experiments.py +363 -0
- evalkit/db/fixture_blobs.py +91 -0
- evalkit/db/keys.py +78 -0
- evalkit/db/llm_providers.py +176 -0
- evalkit/db/locks.py +6 -0
- evalkit/db/mappings.py +168 -0
- evalkit/db/models.py +567 -0
- evalkit/db/promotion.py +271 -0
- evalkit/db/rekey.py +46 -0
- evalkit/db/review.py +250 -0
- evalkit/db/review_state.py +50 -0
- evalkit/db/runs.py +533 -0
- evalkit/db/scorecard.py +300 -0
- evalkit/db/session.py +71 -0
- evalkit/db/sessions.py +90 -0
- evalkit/db/spans.py +360 -0
- evalkit/db/store.py +209 -0
- evalkit/db/tokens.py +100 -0
- evalkit/db/trace_imports.py +308 -0
- evalkit/db/trace_sources.py +131 -0
- evalkit/db/users.py +186 -0
- evalkit/db/workspaces.py +232 -0
- evalkit/paths.py +33 -0
- evalkit/sdk/__init__.py +29 -0
- evalkit/sdk/binding.py +69 -0
- evalkit/sdk/evaluators.py +56 -0
- evalkit/sdk/runner.py +957 -0
- evalkit/sdk/tracing.py +166 -0
- evalkit/store/__init__.py +0 -0
- evalkit/store/base.py +354 -0
- evalkit/store/blob.py +69 -0
- evalkit/version.py +44 -0
- evalkit_py-0.2.0.dev12.dist-info/METADATA +205 -0
- evalkit_py-0.2.0.dev12.dist-info/RECORD +371 -0
- evalkit_py-0.2.0.dev12.dist-info/WHEEL +4 -0
- evalkit_py-0.2.0.dev12.dist-info/entry_points.txt +2 -0
- evalkit_py-0.2.0.dev12.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import uuid
|
|
4
|
+
from collections.abc import Coroutine
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from fastapi import APIRouter, HTTPException, Query, Request
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from sqlalchemy import select
|
|
11
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
12
|
+
from starlette.responses import Response, StreamingResponse
|
|
13
|
+
|
|
14
|
+
from evalkit.api.access import ActorDep, ensure_active, require, workspace_ref
|
|
15
|
+
from evalkit.api.deps import (
|
|
16
|
+
AgentStoreDep,
|
|
17
|
+
HolderDep,
|
|
18
|
+
PgStoreDep,
|
|
19
|
+
RunnerDepsDep,
|
|
20
|
+
SessionDep,
|
|
21
|
+
)
|
|
22
|
+
from evalkit.api.llm_providers import NO_DEFAULT_JUDGE, require_registered_judge
|
|
23
|
+
from evalkit.api.server_keys import server_keys, with_keys
|
|
24
|
+
from evalkit.contracts.atif import validate_trajectory
|
|
25
|
+
from evalkit.contracts.enums import ExperimentStatus, SyncSource
|
|
26
|
+
from evalkit.core.access import (
|
|
27
|
+
Actor,
|
|
28
|
+
can_change_experiment,
|
|
29
|
+
can_create_experiment,
|
|
30
|
+
can_use_agent,
|
|
31
|
+
)
|
|
32
|
+
from evalkit.core.agents import AgentDefinition, explicit, server_may_drive
|
|
33
|
+
from evalkit.core.blob import trajectory_key
|
|
34
|
+
from evalkit.core.costmap import load_cost_map
|
|
35
|
+
from evalkit.core.runner import (
|
|
36
|
+
ACTIVE,
|
|
37
|
+
REFETCHABLE,
|
|
38
|
+
ExperimentRequest,
|
|
39
|
+
InvalidCaseInputs,
|
|
40
|
+
NothingRunnable,
|
|
41
|
+
RunnerDeps,
|
|
42
|
+
error_text,
|
|
43
|
+
harness_capabilities,
|
|
44
|
+
prepare_experiment,
|
|
45
|
+
rescore_experiment,
|
|
46
|
+
run_experiment,
|
|
47
|
+
stored_trajectory_doc,
|
|
48
|
+
)
|
|
49
|
+
from evalkit.core.scorecard import assemble_report
|
|
50
|
+
from evalkit.core.trajectory import backfill_step_costs
|
|
51
|
+
from evalkit.core.workspace import Reference, parse_slug
|
|
52
|
+
from evalkit.db import experiments as db_experiments
|
|
53
|
+
from evalkit.db import scorecard as db_scorecard
|
|
54
|
+
from evalkit.db.cases import dataset_by_ref
|
|
55
|
+
from evalkit.db.models import AgentRun as AgentRunRow
|
|
56
|
+
from evalkit.db.models import ExperimentRun as ExperimentRunRow
|
|
57
|
+
from evalkit.db.models import User
|
|
58
|
+
from evalkit.db.runs import record_run as db_record_run
|
|
59
|
+
from evalkit.store.base import (
|
|
60
|
+
AgentRunRecord,
|
|
61
|
+
EvaluationRow,
|
|
62
|
+
Experiment,
|
|
63
|
+
GitRevision,
|
|
64
|
+
InvalidRun,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
LOGGER = logging.getLogger(__name__)
|
|
68
|
+
|
|
69
|
+
router = APIRouter(prefix="/api/experiments", tags=["experiments"])
|
|
70
|
+
runs_router = APIRouter(prefix="/api/experiment-runs", tags=["experiments"])
|
|
71
|
+
|
|
72
|
+
INTERRUPTED_ERROR = "interrupted: evalkit server shut down"
|
|
73
|
+
|
|
74
|
+
TaskRegistry = dict[str, tuple[int, str, asyncio.Task]]
|
|
75
|
+
LIST_LIMIT = 200
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ExperimentPatch(BaseModel):
|
|
79
|
+
status: ExperimentStatus | None = None
|
|
80
|
+
error: str | None = None
|
|
81
|
+
holder: str | None = None
|
|
82
|
+
model: str | None = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class LeaseRequest(BaseModel):
|
|
86
|
+
holder: str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class RunPost(BaseModel):
|
|
90
|
+
case_id: str
|
|
91
|
+
repetition: int
|
|
92
|
+
run: AgentRunRecord
|
|
93
|
+
holder: str
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class EvaluationsPost(BaseModel):
|
|
97
|
+
rows: list[EvaluationRow]
|
|
98
|
+
force: bool = False
|
|
99
|
+
holder: str
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _consume_exception(task: asyncio.Task) -> None:
|
|
103
|
+
def _cb(t: asyncio.Task) -> None:
|
|
104
|
+
if t.cancelled():
|
|
105
|
+
return
|
|
106
|
+
exc = t.exception()
|
|
107
|
+
if exc is not None:
|
|
108
|
+
LOGGER.error(
|
|
109
|
+
"experiment task %s failed: %s", t.get_name(), error_text(exc), exc_info=exc
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
task.add_done_callback(_cb)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def task_holder(pod: str) -> str:
|
|
116
|
+
return f"{pod}:{uuid.uuid4().hex[:12]}"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _track(
|
|
120
|
+
request: Request, exp_id: int, holder: str, name: str, coro: Coroutine[Any, Any, None]
|
|
121
|
+
) -> None:
|
|
122
|
+
task = asyncio.create_task(coro, name=name)
|
|
123
|
+
_consume_exception(task)
|
|
124
|
+
registry: TaskRegistry = request.app.state.tasks
|
|
125
|
+
registry[name] = (exp_id, holder, task)
|
|
126
|
+
task.add_done_callback(lambda t: registry.pop(name, None))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def interrupt_tasks(store, registry: TaskRegistry) -> None:
|
|
130
|
+
tracked = list(registry.values())
|
|
131
|
+
for _, _, task in tracked:
|
|
132
|
+
task.cancel()
|
|
133
|
+
await asyncio.gather(*(task for _, _, task in tracked), return_exceptions=True)
|
|
134
|
+
results = await asyncio.gather(
|
|
135
|
+
*(
|
|
136
|
+
store.transition(exp_id, ExperimentStatus.FAILED, holder, error=INTERRUPTED_ERROR)
|
|
137
|
+
for exp_id, holder, task in tracked
|
|
138
|
+
if task.cancelled()
|
|
139
|
+
),
|
|
140
|
+
return_exceptions=True,
|
|
141
|
+
)
|
|
142
|
+
for result in results:
|
|
143
|
+
if isinstance(result, BaseException):
|
|
144
|
+
LOGGER.warning("could not mark an interrupted experiment failed: %s", result)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def slug_or_400(value: str) -> Reference:
|
|
148
|
+
try:
|
|
149
|
+
return parse_slug(value)
|
|
150
|
+
except ValueError as exc:
|
|
151
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async def _creator_id(session: AsyncSession, exp: Experiment) -> int | None:
|
|
155
|
+
return await session.scalar(select(User.id).where(User.username == exp.created_by))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def may_change(session: AsyncSession, actor: Actor, exp: Experiment) -> bool:
|
|
159
|
+
ws = await workspace_ref(session, exp.workspace)
|
|
160
|
+
return can_change_experiment(actor, ws, await _creator_id(session, exp))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def _changeable(session: AsyncSession, actor: Actor, exp_id: int) -> Experiment:
|
|
164
|
+
exp = await db_experiments.get_experiment(session, exp_id)
|
|
165
|
+
require(await may_change(session, actor, exp), "you cannot change this experiment")
|
|
166
|
+
return exp
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def _agent(deps: RunnerDeps, exp: Experiment) -> AgentDefinition:
|
|
170
|
+
return await deps.agents.get(explicit(parse_slug(exp.agent)))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _local_hint(agent: AgentDefinition, exp: Experiment, command: str) -> str:
|
|
174
|
+
if command == "evaluate":
|
|
175
|
+
return f"evalkit evaluate {exp.id}"
|
|
176
|
+
if agent.harness_url is None:
|
|
177
|
+
return f"run_experiment(..., resume={exp.id}) from your script"
|
|
178
|
+
return f"evalkit run {exp.dataset} --agent {exp.agent} --resume {exp.id}"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def _server_driven(deps: RunnerDeps, exp: Experiment, command: str) -> AgentDefinition:
|
|
182
|
+
agent = await _agent(deps, exp)
|
|
183
|
+
if not server_may_drive(agent):
|
|
184
|
+
raise HTTPException(
|
|
185
|
+
status_code=409, detail=f"{command} it with {_local_hint(agent, exp, command)}"
|
|
186
|
+
)
|
|
187
|
+
if agent.archived:
|
|
188
|
+
raise HTTPException(status_code=409, detail=f"agent {agent.ref} is archived")
|
|
189
|
+
return agent
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
async def _runner_written(deps: RunnerDeps, exp: Experiment) -> AgentDefinition:
|
|
193
|
+
agent = await _agent(deps, exp)
|
|
194
|
+
if server_may_drive(agent):
|
|
195
|
+
raise HTTPException(
|
|
196
|
+
status_code=409,
|
|
197
|
+
detail=f"experiment {exp.id} is driven by the server; only its runner writes to it",
|
|
198
|
+
)
|
|
199
|
+
return agent
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def _last_git_revision(session: AsyncSession, dataset: str) -> GitRevision | None:
|
|
203
|
+
row = await dataset_by_ref(session, dataset)
|
|
204
|
+
sync = row.last_sync
|
|
205
|
+
if sync is None or sync.source != SyncSource.GIT.value:
|
|
206
|
+
return None
|
|
207
|
+
return GitRevision(
|
|
208
|
+
sha=sync.detail.get("sha"), branch=sync.detail.get("branch"), dirty=sync.detail.get("dirty")
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@router.post("")
|
|
213
|
+
async def create_experiment(
|
|
214
|
+
body: ExperimentRequest,
|
|
215
|
+
request: Request,
|
|
216
|
+
deps: RunnerDepsDep,
|
|
217
|
+
store: PgStoreDep,
|
|
218
|
+
agents: AgentStoreDep,
|
|
219
|
+
session: SessionDep,
|
|
220
|
+
actor: ActorDep,
|
|
221
|
+
pod: HolderDep,
|
|
222
|
+
) -> dict:
|
|
223
|
+
agent = await agents.get(explicit(slug_or_400(body.agent)))
|
|
224
|
+
if agent.archived:
|
|
225
|
+
raise HTTPException(status_code=409, detail=f"agent {agent.ref} is archived")
|
|
226
|
+
agent_ws = await workspace_ref(session, agent.workspace)
|
|
227
|
+
require(
|
|
228
|
+
can_use_agent(actor, agent_ws),
|
|
229
|
+
f"only the owner of @{agent.workspace} can run {agent.ref}",
|
|
230
|
+
)
|
|
231
|
+
dataset_ws = await workspace_ref(
|
|
232
|
+
session, (await dataset_by_ref(session, body.dataset)).workspace
|
|
233
|
+
)
|
|
234
|
+
ensure_active(agent_ws)
|
|
235
|
+
ensure_active(dataset_ws)
|
|
236
|
+
if dataset_ws.is_shared and agent_ws.is_shared:
|
|
237
|
+
require(
|
|
238
|
+
can_create_experiment(actor, dataset_ws),
|
|
239
|
+
f"experiments of {body.dataset} with {agent.ref} belong to @{dataset_ws.name}; "
|
|
240
|
+
"only its writers, reviewers and admins may start them",
|
|
241
|
+
)
|
|
242
|
+
server_driven = server_may_drive(agent)
|
|
243
|
+
if server_driven and body.capabilities is not None:
|
|
244
|
+
raise HTTPException(
|
|
245
|
+
status_code=400, detail="capabilities are fetched by the server for shared agents"
|
|
246
|
+
)
|
|
247
|
+
if not server_driven and (body.capabilities is None or body.holder is None):
|
|
248
|
+
raise HTTPException(
|
|
249
|
+
status_code=400,
|
|
250
|
+
detail="capabilities and holder are required for agents the server does not drive",
|
|
251
|
+
)
|
|
252
|
+
providers = request.app.state.llm_providers
|
|
253
|
+
judge = body.judge or await providers.default_judge()
|
|
254
|
+
if judge is None:
|
|
255
|
+
raise HTTPException(status_code=400, detail=f"{NO_DEFAULT_JUDGE}, or pass a judge")
|
|
256
|
+
api_key: str | None = None
|
|
257
|
+
warnings: list[str] = []
|
|
258
|
+
run_deps = deps
|
|
259
|
+
if server_driven:
|
|
260
|
+
await require_registered_judge(providers, judge.base_url)
|
|
261
|
+
keys, warnings = await server_keys(request, agent, judge.base_url)
|
|
262
|
+
api_key = keys.key_for("agent", agent.ref)
|
|
263
|
+
run_deps = with_keys(deps, keys)
|
|
264
|
+
holder = task_holder(pod) if server_driven else str(body.holder)
|
|
265
|
+
try:
|
|
266
|
+
caps = body.capabilities or await harness_capabilities(
|
|
267
|
+
agent, api_key, deps.harness_transport
|
|
268
|
+
)
|
|
269
|
+
exp, dropped = await prepare_experiment(
|
|
270
|
+
store,
|
|
271
|
+
body,
|
|
272
|
+
agent=agent,
|
|
273
|
+
caps=caps,
|
|
274
|
+
remote_evaluators=await deps.evaluators.in_workspace(agent.workspace),
|
|
275
|
+
judge=judge,
|
|
276
|
+
runner=actor.username,
|
|
277
|
+
holder=holder,
|
|
278
|
+
revision=None
|
|
279
|
+
if body.cases is not None
|
|
280
|
+
else await _last_git_revision(session, body.dataset),
|
|
281
|
+
)
|
|
282
|
+
except (InvalidCaseInputs, NothingRunnable) as exc:
|
|
283
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
284
|
+
except httpx.HTTPError as exc:
|
|
285
|
+
raise HTTPException(status_code=502, detail=f"could not start experiment: {exc}") from exc
|
|
286
|
+
if server_driven:
|
|
287
|
+
_track(request, exp.id, holder, f"run:{exp.id}", run_experiment(run_deps, exp.id, holder))
|
|
288
|
+
return {**exp.model_dump(mode="json"), "dropped_cases": dropped, "warnings": warnings}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
async def with_progress(session: AsyncSession, experiments: list[Experiment]) -> list[dict]:
|
|
292
|
+
ids = [e.id for e in experiments]
|
|
293
|
+
scores = await db_scorecard.list_scores(session, ids)
|
|
294
|
+
costs = await db_scorecard.list_costs(session, ids)
|
|
295
|
+
progress = await db_scorecard.progress_many(session, ids)
|
|
296
|
+
return [
|
|
297
|
+
{
|
|
298
|
+
**exp.model_dump(mode="json"),
|
|
299
|
+
"progress": progress[exp.id].model_dump(),
|
|
300
|
+
"scores": scores.get(exp.id, {}),
|
|
301
|
+
**costs.get(exp.id, {}),
|
|
302
|
+
}
|
|
303
|
+
for exp in experiments
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@router.get("")
|
|
308
|
+
async def list_experiments(
|
|
309
|
+
session: SessionDep,
|
|
310
|
+
workspace: str | None = None,
|
|
311
|
+
status: ExperimentStatus | None = None,
|
|
312
|
+
limit: int = Query(50, ge=1),
|
|
313
|
+
offset: int = Query(0, ge=0),
|
|
314
|
+
brief: bool = False,
|
|
315
|
+
) -> list[dict]:
|
|
316
|
+
workspace_id = (await workspace_ref(session, workspace)).id if workspace else None
|
|
317
|
+
found = await db_experiments.list_experiments(
|
|
318
|
+
session,
|
|
319
|
+
workspace_id=workspace_id,
|
|
320
|
+
status=status,
|
|
321
|
+
limit=min(limit, LIST_LIMIT),
|
|
322
|
+
offset=offset,
|
|
323
|
+
)
|
|
324
|
+
if brief:
|
|
325
|
+
return [exp.model_dump(mode="json", exclude={"config"}) for exp in found]
|
|
326
|
+
return await with_progress(session, found)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@router.get("/compare")
|
|
330
|
+
async def compare(base: int, compare: int, store: PgStoreDep) -> dict:
|
|
331
|
+
base_rows = await store.aggregate(base)
|
|
332
|
+
compare_rows = await store.aggregate(compare)
|
|
333
|
+
return {
|
|
334
|
+
"base": assemble_report(base_rows).model_dump(),
|
|
335
|
+
"compare": assemble_report(compare_rows, baseline=base_rows).model_dump(),
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@router.get("/{exp_id}")
|
|
340
|
+
async def get_experiment(
|
|
341
|
+
exp_id: int, deps: RunnerDepsDep, session: SessionDep, actor: ActorDep
|
|
342
|
+
) -> dict:
|
|
343
|
+
exp = await db_experiments.get_experiment(session, exp_id)
|
|
344
|
+
pending_runs = await db_experiments.has_pending_runs(session, exp_id)
|
|
345
|
+
return {
|
|
346
|
+
**exp.model_dump(mode="json"),
|
|
347
|
+
"progress": (await db_scorecard.progress(session, exp_id)).model_dump(),
|
|
348
|
+
"case_states": await db_scorecard.case_states(session, exp_id),
|
|
349
|
+
"can_change": await may_change(session, actor, exp),
|
|
350
|
+
"server_may_drive": server_may_drive(await _agent(deps, exp)),
|
|
351
|
+
"pending_runs": pending_runs,
|
|
352
|
+
"resumable": db_experiments.resumable(exp, pending_runs),
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@router.get("/{exp_id}/cases")
|
|
357
|
+
async def pinned_cases(exp_id: int, store: PgStoreDep) -> list[dict]:
|
|
358
|
+
return [case.model_dump(mode="json") for case in await store.pinned_cases(exp_id)]
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@router.get("/{exp_id}/runs")
|
|
362
|
+
async def list_runs(exp_id: int, deps: RunnerDepsDep, store: PgStoreDep) -> list[dict]:
|
|
363
|
+
exp = await store.get_experiment(exp_id)
|
|
364
|
+
rows = []
|
|
365
|
+
for run in await store.run_details(exp_id):
|
|
366
|
+
if run.agent_run.trajectory_status in REFETCHABLE:
|
|
367
|
+
_, status = await stored_trajectory_doc(deps.blob_store, exp.agent_id, run.agent_run)
|
|
368
|
+
run.agent_run.trajectory_status = status
|
|
369
|
+
rows.append(run.model_dump(mode="json"))
|
|
370
|
+
return rows
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
@router.post("/{exp_id}/runs")
|
|
374
|
+
async def record_run(
|
|
375
|
+
exp_id: int, body: RunPost, deps: RunnerDepsDep, session: SessionDep, actor: ActorDep
|
|
376
|
+
) -> dict:
|
|
377
|
+
exp = await _changeable(session, actor, exp_id)
|
|
378
|
+
await _runner_written(deps, exp)
|
|
379
|
+
expected = trajectory_key(exp.agent_id, body.run.harness_run_id)
|
|
380
|
+
if body.run.trajectory_key is not None and body.run.trajectory_key != expected:
|
|
381
|
+
raise HTTPException(status_code=400, detail=f"trajectory_key must be {expected}")
|
|
382
|
+
try:
|
|
383
|
+
ref = await db_record_run(
|
|
384
|
+
session,
|
|
385
|
+
exp_id,
|
|
386
|
+
body.case_id,
|
|
387
|
+
body.repetition,
|
|
388
|
+
body.run,
|
|
389
|
+
actor.id,
|
|
390
|
+
holder=body.holder,
|
|
391
|
+
)
|
|
392
|
+
except InvalidRun as exc:
|
|
393
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
394
|
+
await session.commit()
|
|
395
|
+
return ref.model_dump()
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
@router.get("/{exp_id}/pending")
|
|
399
|
+
async def pending_runs(exp_id: int, store: PgStoreDep) -> list[dict]:
|
|
400
|
+
return [rep.model_dump() for rep in await store.pending_runs(exp_id)]
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
@router.get("/{exp_id}/harness-run-id")
|
|
404
|
+
async def harness_run_id_used(exp_id: int, value: str, store: PgStoreDep) -> dict:
|
|
405
|
+
return {"used": await store.harness_run_id_used(exp_id, value)}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@router.get("/{exp_id}/runs-to-score")
|
|
409
|
+
async def runs_to_score(exp_id: int, store: PgStoreDep, force: bool = False) -> list[dict]:
|
|
410
|
+
return [run.model_dump(mode="json") for run in await store.runs_to_score(exp_id, force=force)]
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@runs_router.post("/{run_id}/evaluations")
|
|
414
|
+
async def publish_evaluations(
|
|
415
|
+
run_id: int,
|
|
416
|
+
body: EvaluationsPost,
|
|
417
|
+
deps: RunnerDepsDep,
|
|
418
|
+
store: PgStoreDep,
|
|
419
|
+
session: SessionDep,
|
|
420
|
+
actor: ActorDep,
|
|
421
|
+
) -> dict:
|
|
422
|
+
exp_id = await db_experiments.experiment_of_run(session, run_id)
|
|
423
|
+
if exp_id is None:
|
|
424
|
+
raise HTTPException(status_code=404, detail=f"unknown experiment run: {run_id}")
|
|
425
|
+
await _runner_written(deps, await _changeable(session, actor, exp_id))
|
|
426
|
+
await store.publish_evaluations(run_id, body.rows, force=body.force, holder=body.holder)
|
|
427
|
+
return {"ok": True}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@router.patch("/{exp_id}")
|
|
431
|
+
async def patch_experiment(
|
|
432
|
+
exp_id: int,
|
|
433
|
+
body: ExperimentPatch,
|
|
434
|
+
deps: RunnerDepsDep,
|
|
435
|
+
store: PgStoreDep,
|
|
436
|
+
session: SessionDep,
|
|
437
|
+
actor: ActorDep,
|
|
438
|
+
) -> dict:
|
|
439
|
+
agent = await _runner_written(deps, await _changeable(session, actor, exp_id))
|
|
440
|
+
if body.status in ACTIVE and agent.archived:
|
|
441
|
+
raise HTTPException(status_code=409, detail=f"agent {agent.ref} is archived")
|
|
442
|
+
if body.model is not None:
|
|
443
|
+
await store.set_model(exp_id, body.model)
|
|
444
|
+
if body.status is not None:
|
|
445
|
+
await store.transition(exp_id, body.status, body.holder, error=body.error)
|
|
446
|
+
return (await store.get_experiment(exp_id)).model_dump(mode="json")
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@router.post("/{exp_id}/lease")
|
|
450
|
+
async def renew_lease(
|
|
451
|
+
exp_id: int, body: LeaseRequest, store: PgStoreDep, session: SessionDep, actor: ActorDep
|
|
452
|
+
) -> dict:
|
|
453
|
+
await _changeable(session, actor, exp_id)
|
|
454
|
+
return {"renewed": await store.renew_lease(exp_id, body.holder)}
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@router.get("/{exp_id}/scorecard")
|
|
458
|
+
async def scorecard(exp_id: int, store: PgStoreDep, baseline_id: int | None = None) -> dict:
|
|
459
|
+
baseline = await store.aggregate(baseline_id) if baseline_id is not None else None
|
|
460
|
+
return assemble_report(await store.aggregate(exp_id), baseline=baseline).model_dump()
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@router.post("/{exp_id}/evaluate")
|
|
464
|
+
async def evaluate(
|
|
465
|
+
exp_id: int,
|
|
466
|
+
request: Request,
|
|
467
|
+
deps: RunnerDepsDep,
|
|
468
|
+
store: PgStoreDep,
|
|
469
|
+
session: SessionDep,
|
|
470
|
+
actor: ActorDep,
|
|
471
|
+
pod: HolderDep,
|
|
472
|
+
) -> dict:
|
|
473
|
+
exp = await _changeable(session, actor, exp_id)
|
|
474
|
+
agent = await _server_driven(deps, exp, "evaluate")
|
|
475
|
+
await require_registered_judge(request.app.state.llm_providers, exp.config.judge.base_url)
|
|
476
|
+
keys, warnings = await server_keys(request, agent, exp.config.judge.base_url)
|
|
477
|
+
holder = task_holder(pod)
|
|
478
|
+
exp = await store.transition(exp_id, ExperimentStatus.EVALUATING, holder)
|
|
479
|
+
_track(
|
|
480
|
+
request,
|
|
481
|
+
exp_id,
|
|
482
|
+
holder,
|
|
483
|
+
f"evaluate:{exp_id}",
|
|
484
|
+
rescore_experiment(with_keys(deps, keys), exp_id, holder),
|
|
485
|
+
)
|
|
486
|
+
return {**exp.model_dump(mode="json"), "warnings": warnings}
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
@router.post("/{exp_id}/resume")
|
|
490
|
+
async def resume(
|
|
491
|
+
exp_id: int,
|
|
492
|
+
request: Request,
|
|
493
|
+
deps: RunnerDepsDep,
|
|
494
|
+
store: PgStoreDep,
|
|
495
|
+
session: SessionDep,
|
|
496
|
+
actor: ActorDep,
|
|
497
|
+
pod: HolderDep,
|
|
498
|
+
) -> dict:
|
|
499
|
+
exp = await _changeable(session, actor, exp_id)
|
|
500
|
+
agent = await _server_driven(deps, exp, "resume")
|
|
501
|
+
await require_registered_judge(request.app.state.llm_providers, exp.config.judge.base_url)
|
|
502
|
+
keys, warnings = await server_keys(request, agent, exp.config.judge.base_url)
|
|
503
|
+
holder = task_holder(pod)
|
|
504
|
+
exp = await store.transition(exp_id, ExperimentStatus.RUNNING, holder)
|
|
505
|
+
_track(
|
|
506
|
+
request,
|
|
507
|
+
exp_id,
|
|
508
|
+
holder,
|
|
509
|
+
f"run:{exp_id}",
|
|
510
|
+
run_experiment(with_keys(deps, keys), exp_id, holder),
|
|
511
|
+
)
|
|
512
|
+
return {**exp.model_dump(mode="json"), "warnings": warnings}
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@router.get("/{exp_id}/runs/{run_id}/trajectory")
|
|
516
|
+
async def stored_trajectory(
|
|
517
|
+
exp_id: int, run_id: int, deps: RunnerDepsDep, store: PgStoreDep
|
|
518
|
+
) -> dict:
|
|
519
|
+
exp = await store.get_experiment(exp_id)
|
|
520
|
+
run = next((r for r in await store.run_details(exp_id) if r.experiment_run_id == run_id), None)
|
|
521
|
+
if run is None:
|
|
522
|
+
raise HTTPException(status_code=404, detail="unknown run")
|
|
523
|
+
doc, _ = await stored_trajectory_doc(deps.blob_store, exp.agent_id, run.agent_run)
|
|
524
|
+
if doc is None:
|
|
525
|
+
raise HTTPException(status_code=404, detail="run has no stored trajectory")
|
|
526
|
+
trajectory = validate_trajectory(doc)
|
|
527
|
+
# No-op for runs stored after per-step pricing landed; prices older
|
|
528
|
+
# trajectories so the cost view matches the scorecard gauge.
|
|
529
|
+
backfill_step_costs(trajectory, load_cost_map(deps.cfg.cost_map))
|
|
530
|
+
return trajectory.model_dump(exclude_none=True)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _harness_run_id(exp: Experiment, key: str) -> str | None:
|
|
534
|
+
for folder in ("trajectories", "workspaces"):
|
|
535
|
+
prefix = f"{folder}/{exp.agent_id}/"
|
|
536
|
+
if key.startswith(prefix) and key.endswith(".json"):
|
|
537
|
+
return key.removeprefix(prefix).removesuffix(".json") or None
|
|
538
|
+
run_id, _, path = key.removeprefix(f"artifacts/{exp.agent_id}/").partition("/")
|
|
539
|
+
return run_id if run_id and path else None
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _check_blob_key(exp: Experiment, key: str) -> None:
|
|
543
|
+
folders = ("trajectories", "artifacts", "workspaces")
|
|
544
|
+
if not key.startswith(tuple(f"{folder}/{exp.agent_id}/" for folder in folders)):
|
|
545
|
+
raise HTTPException(
|
|
546
|
+
status_code=400,
|
|
547
|
+
detail="key must be under this experiment's agent's "
|
|
548
|
+
"trajectories/, artifacts/ or workspaces/",
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
@router.get("/{exp_id}/blob")
|
|
553
|
+
async def list_blob(exp_id: int, prefix: str, deps: RunnerDepsDep, store: PgStoreDep) -> dict:
|
|
554
|
+
_check_blob_key(await store.get_experiment(exp_id), prefix)
|
|
555
|
+
return {"objects": await deps.blob_store.list_objects(prefix)}
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
@router.put("/{exp_id}/blob/{key:path}")
|
|
559
|
+
async def put_blob(
|
|
560
|
+
exp_id: int,
|
|
561
|
+
key: str,
|
|
562
|
+
request: Request,
|
|
563
|
+
deps: RunnerDepsDep,
|
|
564
|
+
session: SessionDep,
|
|
565
|
+
actor: ActorDep,
|
|
566
|
+
) -> dict:
|
|
567
|
+
exp = await _changeable(session, actor, exp_id)
|
|
568
|
+
await _runner_written(deps, exp)
|
|
569
|
+
_check_blob_key(exp, key)
|
|
570
|
+
harness_run_id = _harness_run_id(exp, key)
|
|
571
|
+
if harness_run_id is None:
|
|
572
|
+
raise HTTPException(status_code=400, detail=f"{key} names no run")
|
|
573
|
+
recorded = (
|
|
574
|
+
await session.execute(
|
|
575
|
+
select(AgentRunRow.id, ExperimentRunRow.experiment_id, AgentRunRow.trajectory_key)
|
|
576
|
+
.outerjoin(ExperimentRunRow, ExperimentRunRow.agent_run_id == AgentRunRow.id)
|
|
577
|
+
.where(
|
|
578
|
+
AgentRunRow.agent_id == exp.agent_id, AgentRunRow.harness_run_id == harness_run_id
|
|
579
|
+
)
|
|
580
|
+
)
|
|
581
|
+
).one_or_none()
|
|
582
|
+
if recorded is not None and recorded[1] != exp_id:
|
|
583
|
+
raise HTTPException(
|
|
584
|
+
status_code=403, detail=f"run {harness_run_id} does not belong to experiment {exp_id}"
|
|
585
|
+
)
|
|
586
|
+
if recorded is not None and recorded[2] == key and exp.status is ExperimentStatus.RUNNING:
|
|
587
|
+
raise HTTPException(
|
|
588
|
+
status_code=409,
|
|
589
|
+
detail=f"session id {harness_run_id} was already used in experiment {exp_id}; "
|
|
590
|
+
"each run needs its own session id",
|
|
591
|
+
)
|
|
592
|
+
await deps.blob_store.put_stream(key, request.stream())
|
|
593
|
+
return {"ok": True}
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
async def _existing_blob_key(exp_id: int, key: str, deps: RunnerDeps, store) -> None:
|
|
597
|
+
_check_blob_key(await store.get_experiment(exp_id), key)
|
|
598
|
+
if not await deps.blob_store.object_exists(key):
|
|
599
|
+
raise HTTPException(status_code=404, detail="no such object")
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
@router.head("/{exp_id}/blob/{key:path}")
|
|
603
|
+
async def head_blob(exp_id: int, key: str, deps: RunnerDepsDep, store: PgStoreDep) -> Response:
|
|
604
|
+
await _existing_blob_key(exp_id, key, deps, store)
|
|
605
|
+
return Response(status_code=200)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@router.get("/{exp_id}/blob/{key:path}")
|
|
609
|
+
async def get_blob(exp_id: int, key: str, deps: RunnerDepsDep, store: PgStoreDep) -> Response:
|
|
610
|
+
await _existing_blob_key(exp_id, key, deps, store)
|
|
611
|
+
blob = await deps.blob_store.get_stream(key)
|
|
612
|
+
return StreamingResponse(
|
|
613
|
+
blob.chunks,
|
|
614
|
+
media_type="application/octet-stream",
|
|
615
|
+
headers={"Content-Length": str(blob.size)},
|
|
616
|
+
)
|