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
evalkit/sdk/runner.py
ADDED
|
@@ -0,0 +1,957 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from collections.abc import Callable, Sequence
|
|
9
|
+
from datetime import UTC, datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
from opentelemetry import trace
|
|
15
|
+
from opentelemetry.context import Context
|
|
16
|
+
from pydantic import BaseModel
|
|
17
|
+
|
|
18
|
+
from evalkit import version
|
|
19
|
+
from evalkit.client import connection
|
|
20
|
+
from evalkit.client.agents import HttpAgents
|
|
21
|
+
from evalkit.client.api import ApiRequestError, AsyncApi, SyncApi, encode
|
|
22
|
+
from evalkit.client.blob import HttpBlobStore
|
|
23
|
+
from evalkit.client.evaluators import HttpEvaluators
|
|
24
|
+
from evalkit.client.sessions import HttpSessionReader
|
|
25
|
+
from evalkit.client.store import HttpStore, local_holder
|
|
26
|
+
from evalkit.client.summary import summary_lines
|
|
27
|
+
from evalkit.client.sync import git_revision, resolve_dataset, sync_dataset
|
|
28
|
+
from evalkit.config import LocalConfig
|
|
29
|
+
from evalkit.contracts.atif import TrajectoryValidationError, validate_trajectory
|
|
30
|
+
from evalkit.contracts.enums import ExperimentStatus, RunStatus, TrajectoryStatus
|
|
31
|
+
from evalkit.contracts.settings import JudgeSettings
|
|
32
|
+
from evalkit.core.agents import AgentDefinition, explicit
|
|
33
|
+
from evalkit.core.blob import trajectory_key
|
|
34
|
+
from evalkit.core.capabilities import sdk_capabilities
|
|
35
|
+
from evalkit.core.costmap import load_cost_map
|
|
36
|
+
from evalkit.core.python_eval import PythonEvaluator
|
|
37
|
+
from evalkit.core.runner import (
|
|
38
|
+
ExperimentRequest,
|
|
39
|
+
LeaseLost,
|
|
40
|
+
RunnerDeps,
|
|
41
|
+
ScoringFailed,
|
|
42
|
+
case_title,
|
|
43
|
+
dropped_cases_text,
|
|
44
|
+
evaluate_experiment,
|
|
45
|
+
judge_key,
|
|
46
|
+
judge_key_for,
|
|
47
|
+
missing_local_keys,
|
|
48
|
+
rescore_experiment,
|
|
49
|
+
run_leased,
|
|
50
|
+
)
|
|
51
|
+
from evalkit.core.traces.mapping import SDK_ROOT_ATTRIBUTE, TASK_SESSION_ATTRIBUTE
|
|
52
|
+
from evalkit.core.traces.poll import poll_trajectory
|
|
53
|
+
from evalkit.core.traces.sessions import SessionReader
|
|
54
|
+
from evalkit.core.trajectory import TrajectoryView, backfill_step_costs, derive_metrics
|
|
55
|
+
from evalkit.core.workspace import Reference, parse_reference, parse_slug, slug
|
|
56
|
+
from evalkit.credentials import CredentialRegistry
|
|
57
|
+
from evalkit.sdk.binding import (
|
|
58
|
+
Fixture,
|
|
59
|
+
TaskSpec,
|
|
60
|
+
arguments,
|
|
61
|
+
call_task,
|
|
62
|
+
ignores_model,
|
|
63
|
+
inspect_task,
|
|
64
|
+
)
|
|
65
|
+
from evalkit.sdk.tracing import CURRENT_RUN
|
|
66
|
+
from evalkit.store.base import AgentRunRecord, CaseView, Experiment, Selection
|
|
67
|
+
|
|
68
|
+
ROOT_SPAN_NAME = "evalkit.run"
|
|
69
|
+
NO_PROVIDER_ERROR = "no tracer provider configured and the task returned no session_id"
|
|
70
|
+
Task = Callable[..., Any]
|
|
71
|
+
Progress = Callable[[str], None]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class RunResult(BaseModel):
|
|
75
|
+
"""What a task can return instead of a plain answer.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
answer: The agent's answer text.
|
|
79
|
+
output: The agent's structured output.
|
|
80
|
+
model: The model the agent used, recorded on the run.
|
|
81
|
+
error: An error message; the run is recorded as failed.
|
|
82
|
+
session_id: The agent's own session id, when its spans do not continue the
|
|
83
|
+
SDK's trace. The SDK reads the trajectory from that session.
|
|
84
|
+
trajectory: An ATIF document to store as the trajectory, instead of building
|
|
85
|
+
one from spans.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
answer: str | None = None
|
|
89
|
+
output: dict[str, Any] | None = None
|
|
90
|
+
model: str | None = None
|
|
91
|
+
error: str | None = None
|
|
92
|
+
session_id: str | None = None
|
|
93
|
+
trajectory: dict[str, Any] | None = None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class RunSummary(BaseModel):
|
|
97
|
+
"""One run of an experiment.
|
|
98
|
+
|
|
99
|
+
Attributes:
|
|
100
|
+
case_id: The case.
|
|
101
|
+
repetition: 1 for the first repetition, 2 for the second, and so on.
|
|
102
|
+
status: `completed`, `failed`, `cancelled` or `timeout`.
|
|
103
|
+
trajectory_status: `valid`, `partial`, `invalid` or `unavailable`, or `None`
|
|
104
|
+
when no trajectory was read.
|
|
105
|
+
session_id: The session the run's spans belong to.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
case_id: str
|
|
109
|
+
repetition: int
|
|
110
|
+
status: str
|
|
111
|
+
trajectory_status: str | None
|
|
112
|
+
session_id: str | None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ExperimentResult(BaseModel):
|
|
116
|
+
"""The result of `run_experiment` or `evaluate_experiment`.
|
|
117
|
+
|
|
118
|
+
Attributes:
|
|
119
|
+
experiment_id: The experiment, or `None` after a dry run.
|
|
120
|
+
url: The experiment's page in the web UI, or `None` after a dry run.
|
|
121
|
+
runs: One entry per case and repetition.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
experiment_id: int | None
|
|
125
|
+
url: str | None
|
|
126
|
+
runs: list[RunSummary]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Outcome(BaseModel):
|
|
130
|
+
run_id: str
|
|
131
|
+
started: datetime
|
|
132
|
+
answer: str | None = None
|
|
133
|
+
output: dict[str, Any] | None = None
|
|
134
|
+
model: str | None = None
|
|
135
|
+
error: str | None = None
|
|
136
|
+
status: str = "failed"
|
|
137
|
+
session_id: str | None = None
|
|
138
|
+
trajectory: dict[str, Any] | None = None
|
|
139
|
+
wall_ms: int = 0
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _config_search_starts() -> list[Path]:
|
|
143
|
+
script = getattr(sys.modules["__main__"], "__file__", None)
|
|
144
|
+
starts = [Path.cwd()]
|
|
145
|
+
if script is not None and Path(script).parent.resolve() != Path.cwd().resolve():
|
|
146
|
+
starts.append(Path(script).parent)
|
|
147
|
+
return starts
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _local_config() -> LocalConfig:
|
|
151
|
+
starts = _config_search_starts()
|
|
152
|
+
for start in starts:
|
|
153
|
+
local = LocalConfig.find(start)
|
|
154
|
+
if local is not None:
|
|
155
|
+
return local
|
|
156
|
+
raise FileNotFoundError(
|
|
157
|
+
f"evalkit.yaml not found above {' or '.join(str(s) for s in starts)}; "
|
|
158
|
+
"it holds your cases, fixtures and credentials"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def _agent_definition(api: AsyncApi, agent: str, me: str) -> AgentDefinition:
|
|
163
|
+
definition = await HttpAgents(api).get(explicit(parse_reference(agent, me)))
|
|
164
|
+
if definition.harness_url is not None:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
f"{definition.ref} has a harness_url, so evalkit drives it through its Harness API: "
|
|
167
|
+
f"run it with `evalkit run <dataset> --agent {agent}`"
|
|
168
|
+
)
|
|
169
|
+
return definition
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _credentials(local: LocalConfig, me: str) -> CredentialRegistry:
|
|
173
|
+
return CredentialRegistry(local.credentials, local.environ(), home=me)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def _check_evaluator_names(
|
|
177
|
+
evaluators: Sequence[PythonEvaluator], api: AsyncApi, workspace: str
|
|
178
|
+
) -> None:
|
|
179
|
+
remote = await HttpEvaluators(api).in_workspace(workspace)
|
|
180
|
+
clashes = sorted({e.name for e in evaluators} & set(remote))
|
|
181
|
+
if clashes:
|
|
182
|
+
raise ValueError(
|
|
183
|
+
f"python evaluator(s) {', '.join(clashes)} are also remote evaluators in @{workspace}"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _coerce(result: Any) -> RunResult:
|
|
188
|
+
if isinstance(result, RunResult):
|
|
189
|
+
return result
|
|
190
|
+
if isinstance(result, str):
|
|
191
|
+
return RunResult(answer=result)
|
|
192
|
+
if isinstance(result, dict):
|
|
193
|
+
return RunResult(output=result)
|
|
194
|
+
raise TypeError(f"task returned {type(result).__name__}; return str, dict or RunResult")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _root_attributes(
|
|
198
|
+
run_id: str, exp_id: int | None, case: CaseView, repetition: int
|
|
199
|
+
) -> dict[str, Any]:
|
|
200
|
+
attributes: dict[str, Any] = {
|
|
201
|
+
"session.id": run_id,
|
|
202
|
+
"evalkit.case_id": case.case_id,
|
|
203
|
+
"evalkit.repetition": repetition,
|
|
204
|
+
}
|
|
205
|
+
if exp_id is not None:
|
|
206
|
+
attributes["evalkit.experiment_id"] = exp_id
|
|
207
|
+
return attributes
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
async def _fixtures(blob: HttpBlobStore, case: CaseView) -> list[Fixture]:
|
|
211
|
+
return [
|
|
212
|
+
Fixture(name=file.key, path=await blob.fixture_path(file), context=file.context)
|
|
213
|
+
for file in case.files
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
async def _flush(provider: Any) -> None:
|
|
218
|
+
flush = getattr(provider, "force_flush", None)
|
|
219
|
+
if flush is not None:
|
|
220
|
+
await asyncio.to_thread(flush)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
async def call_task_traced(
|
|
224
|
+
task: Task,
|
|
225
|
+
spec: TaskSpec,
|
|
226
|
+
*,
|
|
227
|
+
blob: HttpBlobStore,
|
|
228
|
+
case: CaseView,
|
|
229
|
+
repetition: int,
|
|
230
|
+
exp_id: int | None,
|
|
231
|
+
model: str | None,
|
|
232
|
+
provider: Any,
|
|
233
|
+
) -> Outcome:
|
|
234
|
+
run_id = str(uuid.uuid4())
|
|
235
|
+
clock = time.monotonic()
|
|
236
|
+
metadata = {
|
|
237
|
+
"session_id": run_id,
|
|
238
|
+
"experiment_id": exp_id,
|
|
239
|
+
"case_id": case.case_id,
|
|
240
|
+
"repetition": repetition,
|
|
241
|
+
"tags": case.split_tags,
|
|
242
|
+
}
|
|
243
|
+
files = await _fixtures(blob, case)
|
|
244
|
+
args, kwargs = arguments(spec, input=case.input, files=files, model=model, metadata=metadata)
|
|
245
|
+
attributes = _root_attributes(run_id, exp_id, case, repetition)
|
|
246
|
+
tracer = provider.get_tracer("evalkit.sdk")
|
|
247
|
+
outcome = Outcome(run_id=run_id, started=datetime.now(UTC))
|
|
248
|
+
token = CURRENT_RUN.set(attributes)
|
|
249
|
+
recording = False
|
|
250
|
+
try:
|
|
251
|
+
with tracer.start_as_current_span(ROOT_SPAN_NAME, context=Context()) as span:
|
|
252
|
+
recording = span.is_recording()
|
|
253
|
+
if recording:
|
|
254
|
+
span.set_attributes(
|
|
255
|
+
{
|
|
256
|
+
**attributes,
|
|
257
|
+
SDK_ROOT_ATTRIBUTE: True,
|
|
258
|
+
"openinference.span.kind": "CHAIN",
|
|
259
|
+
"input.value": json.dumps(case.input),
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
try:
|
|
263
|
+
result = _coerce(await call_task(task, args, kwargs))
|
|
264
|
+
except Exception as exc:
|
|
265
|
+
if isinstance(exc, TypeError) and str(exc).startswith("task returned"):
|
|
266
|
+
outcome.error = f"task protocol violation: {exc}"
|
|
267
|
+
else:
|
|
268
|
+
text = str(exc)
|
|
269
|
+
outcome.error = f"{type(exc).__name__}: {text}" if text else type(exc).__name__
|
|
270
|
+
else:
|
|
271
|
+
_apply(outcome, result)
|
|
272
|
+
if recording and outcome.session_id is not None:
|
|
273
|
+
span.set_attributes(
|
|
274
|
+
{"session.id": str(outcome.session_id), TASK_SESSION_ATTRIBUTE: True}
|
|
275
|
+
)
|
|
276
|
+
if recording and (outcome.answer is not None or outcome.output is not None):
|
|
277
|
+
span.set_attribute(
|
|
278
|
+
"output.value",
|
|
279
|
+
outcome.answer
|
|
280
|
+
if outcome.answer is not None
|
|
281
|
+
else json.dumps(outcome.output),
|
|
282
|
+
)
|
|
283
|
+
finally:
|
|
284
|
+
CURRENT_RUN.reset(token)
|
|
285
|
+
outcome.wall_ms = int((time.monotonic() - clock) * 1000)
|
|
286
|
+
await _flush(provider)
|
|
287
|
+
if outcome.session_id is None and recording:
|
|
288
|
+
outcome.session_id = run_id
|
|
289
|
+
if outcome.session_id is None and outcome.trajectory is None and outcome.error is None:
|
|
290
|
+
outcome.error = NO_PROVIDER_ERROR
|
|
291
|
+
return outcome
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _apply(outcome: Outcome, result: RunResult) -> None:
|
|
295
|
+
if result.answer is None and result.output is None and result.error is None:
|
|
296
|
+
outcome.error = (
|
|
297
|
+
"task protocol violation: RunResult carries neither answer, output nor error"
|
|
298
|
+
)
|
|
299
|
+
return
|
|
300
|
+
outcome.answer = result.answer
|
|
301
|
+
outcome.output = result.output
|
|
302
|
+
outcome.model = result.model
|
|
303
|
+
outcome.error = result.error
|
|
304
|
+
has_result = result.answer is not None or result.output is not None
|
|
305
|
+
outcome.status = "completed" if has_result else "failed"
|
|
306
|
+
outcome.session_id = result.session_id
|
|
307
|
+
outcome.trajectory = result.trajectory
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
async def settle_and_record(
|
|
311
|
+
deps: RunnerDeps,
|
|
312
|
+
exp: Experiment,
|
|
313
|
+
case: CaseView,
|
|
314
|
+
repetition: int,
|
|
315
|
+
outcome: Outcome,
|
|
316
|
+
*,
|
|
317
|
+
wait_seconds: int,
|
|
318
|
+
model: str | None,
|
|
319
|
+
poll_interval: float,
|
|
320
|
+
holder: str | None = None,
|
|
321
|
+
used_sessions: set[str] | None = None,
|
|
322
|
+
) -> RunSummary:
|
|
323
|
+
if used_sessions is not None and outcome.session_id is not None:
|
|
324
|
+
if outcome.session_id in used_sessions:
|
|
325
|
+
outcome.error = (
|
|
326
|
+
f"session id {outcome.session_id} was already used in experiment {exp.id}; "
|
|
327
|
+
"each run needs its own session id"
|
|
328
|
+
)
|
|
329
|
+
outcome.status = "failed"
|
|
330
|
+
outcome.session_id = None
|
|
331
|
+
outcome.trajectory = None
|
|
332
|
+
else:
|
|
333
|
+
used_sessions.add(outcome.session_id)
|
|
334
|
+
if outcome.session_id is not None:
|
|
335
|
+
used = await deps.store.harness_run_id_used(exp.id, outcome.session_id)
|
|
336
|
+
if used is not None:
|
|
337
|
+
outcome.error = used
|
|
338
|
+
outcome.status = "failed"
|
|
339
|
+
outcome.session_id = None
|
|
340
|
+
outcome.trajectory = None
|
|
341
|
+
trajectory_doc: dict[str, Any] | None = None
|
|
342
|
+
status: str | None
|
|
343
|
+
if outcome.trajectory is not None:
|
|
344
|
+
try:
|
|
345
|
+
validate_trajectory(outcome.trajectory)
|
|
346
|
+
trajectory_doc, status = outcome.trajectory, "valid"
|
|
347
|
+
except TrajectoryValidationError as exc:
|
|
348
|
+
status = "invalid"
|
|
349
|
+
deps.progress(f"run {case.case_id} #{repetition}: pushed trajectory invalid: {exc}")
|
|
350
|
+
elif outcome.session_id is None:
|
|
351
|
+
status = "unavailable"
|
|
352
|
+
else:
|
|
353
|
+
assert deps.sessions is not None
|
|
354
|
+
trajectory_doc, status = await poll_trajectory(
|
|
355
|
+
deps.sessions,
|
|
356
|
+
exp.agent,
|
|
357
|
+
outcome.session_id,
|
|
358
|
+
wait_seconds,
|
|
359
|
+
poll_interval=poll_interval,
|
|
360
|
+
)
|
|
361
|
+
key: str | None = None
|
|
362
|
+
metrics: dict[str, Any] = {}
|
|
363
|
+
if trajectory_doc is not None:
|
|
364
|
+
trajectory = validate_trajectory(trajectory_doc)
|
|
365
|
+
backfill_step_costs(trajectory, load_cost_map(deps.cfg.cost_map), model or outcome.model)
|
|
366
|
+
if status == "partial":
|
|
367
|
+
trajectory.extra = {**(trajectory.extra or {}), "trajectory_status": status}
|
|
368
|
+
key = trajectory_key(exp.agent_id, outcome.session_id or outcome.run_id)
|
|
369
|
+
await deps.blob_store.put_object(
|
|
370
|
+
key, json.dumps(trajectory.model_dump(exclude_none=True)).encode()
|
|
371
|
+
)
|
|
372
|
+
metrics = derive_metrics(TrajectoryView(trajectory), outcome.wall_ms).model_dump(
|
|
373
|
+
exclude_none=True
|
|
374
|
+
)
|
|
375
|
+
metrics.setdefault("duration_ms", outcome.wall_ms)
|
|
376
|
+
await deps.store.record_run(
|
|
377
|
+
exp.id,
|
|
378
|
+
case.case_id,
|
|
379
|
+
repetition,
|
|
380
|
+
AgentRunRecord(
|
|
381
|
+
harness_run_id=outcome.session_id or outcome.run_id,
|
|
382
|
+
status=RunStatus(outcome.status),
|
|
383
|
+
answer=outcome.answer,
|
|
384
|
+
output=outcome.output,
|
|
385
|
+
error=outcome.error,
|
|
386
|
+
started_at=outcome.started,
|
|
387
|
+
ended_at=datetime.now(UTC),
|
|
388
|
+
trajectory_key=key,
|
|
389
|
+
trajectory_status=TrajectoryStatus(status) if status else None,
|
|
390
|
+
metrics=metrics,
|
|
391
|
+
title=case_title(case.input),
|
|
392
|
+
),
|
|
393
|
+
holder=holder,
|
|
394
|
+
)
|
|
395
|
+
deps.progress(f"run {case.case_id} #{repetition} {outcome.status} (trajectory {status})")
|
|
396
|
+
return RunSummary(
|
|
397
|
+
case_id=case.case_id,
|
|
398
|
+
repetition=repetition,
|
|
399
|
+
status=outcome.status,
|
|
400
|
+
trajectory_status=status,
|
|
401
|
+
session_id=outcome.session_id,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _selection(case_ids: Sequence[str] | None, split: str | None) -> Selection:
|
|
406
|
+
if case_ids:
|
|
407
|
+
return Selection(case_ids=list(case_ids))
|
|
408
|
+
if split:
|
|
409
|
+
return Selection(tag=split)
|
|
410
|
+
return Selection(all=True)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
async def _username(api: AsyncApi, conn: connection.Connection, progress: Progress) -> str:
|
|
414
|
+
me = (await api.get("/api/auth/me")).json()
|
|
415
|
+
warning = connection.token_expiry_warning(conn, me.get("token_expires_at"), datetime.now(UTC))
|
|
416
|
+
if warning is not None:
|
|
417
|
+
progress(f"warning: {warning}")
|
|
418
|
+
return str(me["username"])
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _note_how_to_finish(
|
|
422
|
+
exc: Exception, exp_id: int, python_evaluators: Sequence[str], progress: Progress
|
|
423
|
+
) -> None:
|
|
424
|
+
call = f"evalkit.sdk.evaluate_experiment(experiment_id={exp_id}"
|
|
425
|
+
if python_evaluators:
|
|
426
|
+
how = f"{call}, evaluators=[...]) passing {', '.join(python_evaluators)}"
|
|
427
|
+
else:
|
|
428
|
+
how = f"{call}) or `evalkit evaluate {exp_id}`"
|
|
429
|
+
note = f"experiment {exp_id} is not fully scored; finish it with {how}"
|
|
430
|
+
exc.add_note(note)
|
|
431
|
+
progress(note)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _experiment_url(server_url: str, exp_id: int) -> str:
|
|
435
|
+
return f"{server_url.rstrip('/')}/experiments/{exp_id}"
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
async def _session_summary(api: AsyncApi, path: str, session_id: str) -> str:
|
|
439
|
+
try:
|
|
440
|
+
session = (await api.get(path, params={"esf": "true"})).json()
|
|
441
|
+
except ApiRequestError as exc:
|
|
442
|
+
if exc.status != 413:
|
|
443
|
+
raise
|
|
444
|
+
return f" session {session_id}: {exc.detail}"
|
|
445
|
+
kinds: dict[str, int] = {}
|
|
446
|
+
for esf_span in session.get("esf") or []:
|
|
447
|
+
kinds[esf_span["kind"]] = kinds.get(esf_span["kind"], 0) + 1
|
|
448
|
+
version = session.get("mapping_version")
|
|
449
|
+
return (
|
|
450
|
+
f" session {session_id}: {session['span_count']} spans "
|
|
451
|
+
f"({', '.join(f'{k} {n}' for k, n in sorted(kinds.items())) or 'none'}), "
|
|
452
|
+
f"mapping {f'v{version}' if version else 'built-in'}, "
|
|
453
|
+
f"unmapped {', '.join(session.get('unmapped_attributes') or []) or 'none'}"
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
async def _report_session(
|
|
458
|
+
api: AsyncApi,
|
|
459
|
+
sessions: SessionReader,
|
|
460
|
+
agent_slug: str,
|
|
461
|
+
session_id: str,
|
|
462
|
+
*,
|
|
463
|
+
wait: int,
|
|
464
|
+
progress: Progress,
|
|
465
|
+
poll_interval: float,
|
|
466
|
+
) -> str:
|
|
467
|
+
doc, status = await poll_trajectory(
|
|
468
|
+
sessions, agent_slug, session_id, wait, poll_interval=poll_interval
|
|
469
|
+
)
|
|
470
|
+
session_path = f"/api/agents/{encode(agent_slug)}/sessions/{encode(session_id)}/trajectory"
|
|
471
|
+
progress(await _session_summary(api, session_path, session_id))
|
|
472
|
+
steps = len((doc or {}).get("steps") or [])
|
|
473
|
+
progress(f" trajectory: {status}, {steps} steps")
|
|
474
|
+
return status
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
async def _dry_run(
|
|
478
|
+
api: AsyncApi,
|
|
479
|
+
local: LocalConfig,
|
|
480
|
+
spec: TaskSpec,
|
|
481
|
+
task: Task,
|
|
482
|
+
*,
|
|
483
|
+
dataset_ref: Reference,
|
|
484
|
+
agent_ref: Reference,
|
|
485
|
+
me: str,
|
|
486
|
+
selection: Selection,
|
|
487
|
+
count: int,
|
|
488
|
+
model: str | None,
|
|
489
|
+
wait: int,
|
|
490
|
+
provider: Any,
|
|
491
|
+
progress: Progress,
|
|
492
|
+
poll_interval: float,
|
|
493
|
+
) -> ExperimentResult:
|
|
494
|
+
store = HttpStore(api)
|
|
495
|
+
cases = (await store.cases_for(slug(dataset_ref), selection, agent=slug(agent_ref)))[:count]
|
|
496
|
+
blob = HttpBlobStore(api, None)
|
|
497
|
+
deps = RunnerDeps(
|
|
498
|
+
cfg=local,
|
|
499
|
+
store=store,
|
|
500
|
+
blob_store=blob,
|
|
501
|
+
agents=HttpAgents(api),
|
|
502
|
+
evaluators=HttpEvaluators(api),
|
|
503
|
+
credentials=_credentials(local, me),
|
|
504
|
+
progress=progress,
|
|
505
|
+
sessions=HttpSessionReader(api),
|
|
506
|
+
)
|
|
507
|
+
summaries: list[RunSummary] = []
|
|
508
|
+
for case in cases:
|
|
509
|
+
outcome = await call_task_traced(
|
|
510
|
+
task,
|
|
511
|
+
spec,
|
|
512
|
+
blob=blob,
|
|
513
|
+
case=case,
|
|
514
|
+
repetition=1,
|
|
515
|
+
exp_id=None,
|
|
516
|
+
model=model,
|
|
517
|
+
provider=provider,
|
|
518
|
+
)
|
|
519
|
+
progress(f"dry run {case.case_id}: {outcome.status}")
|
|
520
|
+
progress(f" answer: {outcome.answer if outcome.answer is not None else outcome.output}")
|
|
521
|
+
if outcome.error:
|
|
522
|
+
progress(f" error: {outcome.error}")
|
|
523
|
+
status: str | None = None
|
|
524
|
+
if outcome.trajectory is not None:
|
|
525
|
+
status = "valid"
|
|
526
|
+
progress(f" pushed trajectory: {len(outcome.trajectory.get('steps') or [])} steps")
|
|
527
|
+
elif outcome.session_id is not None:
|
|
528
|
+
assert deps.sessions is not None
|
|
529
|
+
try:
|
|
530
|
+
status = await _report_session(
|
|
531
|
+
api,
|
|
532
|
+
deps.sessions,
|
|
533
|
+
slug(agent_ref),
|
|
534
|
+
outcome.session_id,
|
|
535
|
+
wait=wait,
|
|
536
|
+
progress=progress,
|
|
537
|
+
poll_interval=poll_interval,
|
|
538
|
+
)
|
|
539
|
+
except ApiRequestError as exc:
|
|
540
|
+
status = "unavailable"
|
|
541
|
+
progress(f" session {outcome.session_id}: trajectory read failed: {exc}")
|
|
542
|
+
else:
|
|
543
|
+
status = "unavailable"
|
|
544
|
+
summaries.append(
|
|
545
|
+
RunSummary(
|
|
546
|
+
case_id=case.case_id,
|
|
547
|
+
repetition=1,
|
|
548
|
+
status=outcome.status,
|
|
549
|
+
trajectory_status=status,
|
|
550
|
+
session_id=outcome.session_id,
|
|
551
|
+
)
|
|
552
|
+
)
|
|
553
|
+
return ExperimentResult(experiment_id=None, url=None, runs=summaries)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
async def arun_experiment(
|
|
557
|
+
*,
|
|
558
|
+
dataset: str,
|
|
559
|
+
agent: str,
|
|
560
|
+
task: Task,
|
|
561
|
+
workspace: str | None = None,
|
|
562
|
+
evaluators: Sequence[PythonEvaluator] = (),
|
|
563
|
+
repetitions: int = 1,
|
|
564
|
+
concurrency: int | None = None,
|
|
565
|
+
case_ids: Sequence[str] | None = None,
|
|
566
|
+
split: str | None = None,
|
|
567
|
+
model: str | None = None,
|
|
568
|
+
resume: int | None = None,
|
|
569
|
+
dry_run: bool | int = False,
|
|
570
|
+
trajectory_wait_seconds: int | None = None,
|
|
571
|
+
judge: JudgeSettings | None = None,
|
|
572
|
+
api_transport: httpx.AsyncBaseTransport | None = None,
|
|
573
|
+
custom_eval_transport: httpx.AsyncBaseTransport | None = None,
|
|
574
|
+
progress: Progress = print,
|
|
575
|
+
poll_interval: float = 5.0,
|
|
576
|
+
tracer_provider: Any = None,
|
|
577
|
+
) -> ExperimentResult:
|
|
578
|
+
"""The async form of `run_experiment`, for code already in an event loop (a notebook).
|
|
579
|
+
|
|
580
|
+
Same arguments and result as `run_experiment`. The arguments after `judge` exist
|
|
581
|
+
for tests.
|
|
582
|
+
"""
|
|
583
|
+
local = _local_config().resolve_env(scoring=not dry_run)
|
|
584
|
+
spec = inspect_task(task)
|
|
585
|
+
if model is not None and ignores_model(spec):
|
|
586
|
+
progress(
|
|
587
|
+
f"warning: model={model} is recorded on the experiment, but the task has no `model` "
|
|
588
|
+
"parameter, so the agent never sees it"
|
|
589
|
+
)
|
|
590
|
+
active = connection.active()
|
|
591
|
+
api = AsyncApi(active.url, active.token, transport=api_transport)
|
|
592
|
+
try:
|
|
593
|
+
await api.require_same_version()
|
|
594
|
+
me = await _username(api, active, progress)
|
|
595
|
+
definition = await _agent_definition(api, agent, me)
|
|
596
|
+
await _check_evaluator_names(evaluators, api, definition.workspace)
|
|
597
|
+
dataset_ref = resolve_dataset(dataset, workspace, me)
|
|
598
|
+
agent_ref = definition.reference
|
|
599
|
+
wait = (
|
|
600
|
+
trajectory_wait_seconds
|
|
601
|
+
if trajectory_wait_seconds is not None
|
|
602
|
+
else definition.settings.trajectory_wait_seconds
|
|
603
|
+
)
|
|
604
|
+
provider = tracer_provider if tracer_provider is not None else trace.get_tracer_provider()
|
|
605
|
+
on_disk = local.cases / dataset_ref.name if local.cases is not None else None
|
|
606
|
+
own_dataset = dataset_ref.workspace == me and resume is None
|
|
607
|
+
if own_dataset and on_disk is None:
|
|
608
|
+
progress(
|
|
609
|
+
f"no cases: directory in {local.path}; running {slug(dataset_ref)} as the "
|
|
610
|
+
"server holds it, without syncing"
|
|
611
|
+
)
|
|
612
|
+
if own_dataset and on_disk is not None and on_disk.is_dir():
|
|
613
|
+
await asyncio.to_thread(
|
|
614
|
+
sync_dataset,
|
|
615
|
+
SyncApi(active.url, active.token),
|
|
616
|
+
on_disk,
|
|
617
|
+
name=dataset_ref.name,
|
|
618
|
+
workspace=me,
|
|
619
|
+
revision=git_revision(on_disk),
|
|
620
|
+
fixtures_dir=local.fixtures,
|
|
621
|
+
)
|
|
622
|
+
python_evaluators = {e.name: e for e in evaluators}
|
|
623
|
+
if dry_run:
|
|
624
|
+
return await _dry_run(
|
|
625
|
+
api,
|
|
626
|
+
local,
|
|
627
|
+
spec,
|
|
628
|
+
task,
|
|
629
|
+
dataset_ref=dataset_ref,
|
|
630
|
+
agent_ref=agent_ref,
|
|
631
|
+
me=me,
|
|
632
|
+
selection=_selection(case_ids, split),
|
|
633
|
+
count=int(dry_run),
|
|
634
|
+
model=model,
|
|
635
|
+
wait=wait,
|
|
636
|
+
provider=provider,
|
|
637
|
+
progress=progress,
|
|
638
|
+
poll_interval=poll_interval,
|
|
639
|
+
)
|
|
640
|
+
store = HttpStore(api)
|
|
641
|
+
holder = local_holder(me)
|
|
642
|
+
credentials = _credentials(local, me)
|
|
643
|
+
if resume is None:
|
|
644
|
+
resolved_judge = judge or JudgeSettings.model_validate(
|
|
645
|
+
(await api.get("/api/judge-default")).json()
|
|
646
|
+
)
|
|
647
|
+
selected = await store.cases_for(
|
|
648
|
+
slug(dataset_ref),
|
|
649
|
+
_selection(case_ids, split),
|
|
650
|
+
agent=slug(agent_ref),
|
|
651
|
+
)
|
|
652
|
+
judge_key_for(credentials, resolved_judge, selected)
|
|
653
|
+
revision = git_revision(on_disk) if on_disk is not None and on_disk.is_dir() else None
|
|
654
|
+
exp, dropped = await store.start_experiment(
|
|
655
|
+
ExperimentRequest(
|
|
656
|
+
dataset=slug(dataset_ref),
|
|
657
|
+
agent=slug(agent_ref),
|
|
658
|
+
selection=_selection(case_ids, split),
|
|
659
|
+
repetitions=repetitions,
|
|
660
|
+
model=model,
|
|
661
|
+
capabilities=sdk_capabilities(definition),
|
|
662
|
+
cases=revision,
|
|
663
|
+
evalkit_version=version.current(),
|
|
664
|
+
code_evaluators=sorted(python_evaluators) or None,
|
|
665
|
+
judge=resolved_judge,
|
|
666
|
+
holder=holder,
|
|
667
|
+
trajectory_wait_seconds=trajectory_wait_seconds,
|
|
668
|
+
)
|
|
669
|
+
)
|
|
670
|
+
if dropped:
|
|
671
|
+
progress(dropped_cases_text(dropped))
|
|
672
|
+
else:
|
|
673
|
+
exp = await store.transition(resume, ExperimentStatus.RUNNING, holder)
|
|
674
|
+
progress(f"experiment {exp.id}")
|
|
675
|
+
blob = HttpBlobStore(api, exp.id)
|
|
676
|
+
deps = RunnerDeps(
|
|
677
|
+
cfg=local,
|
|
678
|
+
store=store,
|
|
679
|
+
blob_store=blob,
|
|
680
|
+
agents=HttpAgents(api),
|
|
681
|
+
evaluators=HttpEvaluators(api),
|
|
682
|
+
credentials=credentials,
|
|
683
|
+
custom_eval_transport=custom_eval_transport,
|
|
684
|
+
progress=progress,
|
|
685
|
+
sessions=HttpSessionReader(api),
|
|
686
|
+
python_evaluators=python_evaluators,
|
|
687
|
+
)
|
|
688
|
+
cases = {case.case_id: case for case in await store.pinned_cases(exp.id)}
|
|
689
|
+
for line in await missing_local_keys(deps, definition, cases.values()):
|
|
690
|
+
progress(line)
|
|
691
|
+
judge_key(deps, exp.config.judge, cases.values())
|
|
692
|
+
slots = asyncio.Semaphore(max(concurrency or definition.settings.max_concurrent_runs, 1))
|
|
693
|
+
summaries: list[RunSummary] = []
|
|
694
|
+
used_sessions: set[str] = set()
|
|
695
|
+
|
|
696
|
+
async def one(case: CaseView, repetition: int) -> None:
|
|
697
|
+
async with slots:
|
|
698
|
+
outcome = await call_task_traced(
|
|
699
|
+
task,
|
|
700
|
+
spec,
|
|
701
|
+
blob=blob,
|
|
702
|
+
case=case,
|
|
703
|
+
repetition=repetition,
|
|
704
|
+
exp_id=exp.id,
|
|
705
|
+
model=model,
|
|
706
|
+
provider=provider,
|
|
707
|
+
)
|
|
708
|
+
summaries.append(
|
|
709
|
+
await settle_and_record(
|
|
710
|
+
deps,
|
|
711
|
+
exp,
|
|
712
|
+
case,
|
|
713
|
+
repetition,
|
|
714
|
+
outcome,
|
|
715
|
+
wait_seconds=wait,
|
|
716
|
+
model=model,
|
|
717
|
+
poll_interval=poll_interval,
|
|
718
|
+
holder=holder,
|
|
719
|
+
used_sessions=used_sessions,
|
|
720
|
+
)
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
async def work() -> None:
|
|
724
|
+
await asyncio.gather(
|
|
725
|
+
*(
|
|
726
|
+
one(cases[pending.case_id], pending.repetition)
|
|
727
|
+
for pending in await store.pending_runs(exp.id)
|
|
728
|
+
)
|
|
729
|
+
)
|
|
730
|
+
await store.transition(exp.id, ExperimentStatus.EVALUATING, holder)
|
|
731
|
+
await evaluate_experiment(deps, exp.id, holder)
|
|
732
|
+
|
|
733
|
+
try:
|
|
734
|
+
await run_leased(deps, exp.id, holder, work())
|
|
735
|
+
except LeaseLost as exc:
|
|
736
|
+
note = f"resume it with run_experiment(..., resume={exp.id})"
|
|
737
|
+
exc.add_note(note)
|
|
738
|
+
progress(f"{exc}; {note}")
|
|
739
|
+
raise
|
|
740
|
+
except (ScoringFailed, ApiRequestError, httpx.TransportError) as exc:
|
|
741
|
+
_note_how_to_finish(exc, exp.id, sorted(python_evaluators), progress)
|
|
742
|
+
raise
|
|
743
|
+
report = (await api.get(f"/api/experiments/{exp.id}/scorecard")).json()
|
|
744
|
+
for line in summary_lines(report):
|
|
745
|
+
progress(line)
|
|
746
|
+
return ExperimentResult(
|
|
747
|
+
experiment_id=exp.id,
|
|
748
|
+
url=_experiment_url(active.url, exp.id),
|
|
749
|
+
runs=sorted(summaries, key=lambda s: (s.case_id, s.repetition)),
|
|
750
|
+
)
|
|
751
|
+
finally:
|
|
752
|
+
await api.aclose()
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
async def aevaluate_experiment(
|
|
756
|
+
*,
|
|
757
|
+
experiment_id: int,
|
|
758
|
+
evaluators: Sequence[PythonEvaluator] = (),
|
|
759
|
+
refetch_trajectories: bool = False,
|
|
760
|
+
api_transport: httpx.AsyncBaseTransport | None = None,
|
|
761
|
+
custom_eval_transport: httpx.AsyncBaseTransport | None = None,
|
|
762
|
+
progress: Progress = print,
|
|
763
|
+
) -> ExperimentResult:
|
|
764
|
+
"""The async form of `evaluate_experiment`, for code already in an event loop.
|
|
765
|
+
|
|
766
|
+
Same arguments and result as `evaluate_experiment`. The arguments after
|
|
767
|
+
`refetch_trajectories` exist for tests.
|
|
768
|
+
"""
|
|
769
|
+
local = _local_config().resolve_env(scoring=True)
|
|
770
|
+
active = connection.active()
|
|
771
|
+
api = AsyncApi(active.url, active.token, transport=api_transport)
|
|
772
|
+
try:
|
|
773
|
+
await api.require_same_version()
|
|
774
|
+
store = HttpStore(api)
|
|
775
|
+
exp = await store.get_experiment(experiment_id)
|
|
776
|
+
await _check_evaluator_names(evaluators, api, parse_slug(exp.agent).workspace)
|
|
777
|
+
declared = set(exp.config.snapshot.code_evaluators)
|
|
778
|
+
unknown = sorted({e.name for e in evaluators} - declared)
|
|
779
|
+
if unknown:
|
|
780
|
+
raise ValueError(
|
|
781
|
+
f"python evaluators {', '.join(unknown)} were not part of experiment "
|
|
782
|
+
f"{experiment_id} (declared: {sorted(declared) or 'none'})"
|
|
783
|
+
)
|
|
784
|
+
me = active.username or ""
|
|
785
|
+
holder = local_holder(me)
|
|
786
|
+
deps = RunnerDeps(
|
|
787
|
+
cfg=local,
|
|
788
|
+
store=store,
|
|
789
|
+
blob_store=HttpBlobStore(api, experiment_id),
|
|
790
|
+
agents=HttpAgents(api),
|
|
791
|
+
evaluators=HttpEvaluators(api),
|
|
792
|
+
credentials=_credentials(local, me),
|
|
793
|
+
custom_eval_transport=custom_eval_transport,
|
|
794
|
+
progress=progress,
|
|
795
|
+
sessions=HttpSessionReader(api),
|
|
796
|
+
python_evaluators={e.name: e for e in evaluators},
|
|
797
|
+
)
|
|
798
|
+
await store.transition(experiment_id, ExperimentStatus.EVALUATING, holder)
|
|
799
|
+
try:
|
|
800
|
+
await rescore_experiment(
|
|
801
|
+
deps, experiment_id, holder, refetch_trajectories=refetch_trajectories
|
|
802
|
+
)
|
|
803
|
+
except (ScoringFailed, ApiRequestError, httpx.TransportError) as exc:
|
|
804
|
+
_note_how_to_finish(exc, experiment_id, sorted(declared), progress)
|
|
805
|
+
raise
|
|
806
|
+
runs = [
|
|
807
|
+
RunSummary(
|
|
808
|
+
case_id=r.case_id,
|
|
809
|
+
repetition=r.repetition,
|
|
810
|
+
status=r.agent_run.status.value,
|
|
811
|
+
trajectory_status=r.agent_run.trajectory_status.value
|
|
812
|
+
if r.agent_run.trajectory_status
|
|
813
|
+
else None,
|
|
814
|
+
session_id=r.agent_run.harness_run_id,
|
|
815
|
+
)
|
|
816
|
+
for r in await store.run_details(experiment_id)
|
|
817
|
+
]
|
|
818
|
+
return ExperimentResult(
|
|
819
|
+
experiment_id=experiment_id,
|
|
820
|
+
url=_experiment_url(active.url, experiment_id),
|
|
821
|
+
runs=runs,
|
|
822
|
+
)
|
|
823
|
+
finally:
|
|
824
|
+
await api.aclose()
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def _loop_is_running() -> bool:
|
|
828
|
+
try:
|
|
829
|
+
asyncio.get_running_loop()
|
|
830
|
+
except RuntimeError:
|
|
831
|
+
return False
|
|
832
|
+
return True
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def run_experiment(
|
|
836
|
+
*,
|
|
837
|
+
dataset: str,
|
|
838
|
+
agent: str,
|
|
839
|
+
task: Task,
|
|
840
|
+
workspace: str | None = None,
|
|
841
|
+
evaluators: Sequence[PythonEvaluator] = (),
|
|
842
|
+
repetitions: int = 1,
|
|
843
|
+
concurrency: int | None = None,
|
|
844
|
+
case_ids: Sequence[str] | None = None,
|
|
845
|
+
split: str | None = None,
|
|
846
|
+
model: str | None = None,
|
|
847
|
+
resume: int | None = None,
|
|
848
|
+
dry_run: bool | int = False,
|
|
849
|
+
trajectory_wait_seconds: int | None = None,
|
|
850
|
+
judge: JudgeSettings | None = None,
|
|
851
|
+
) -> ExperimentResult:
|
|
852
|
+
"""Run an experiment: call `task` once per case and repetition, store the runs on
|
|
853
|
+
the server and score them.
|
|
854
|
+
|
|
855
|
+
Scoring uses the judge, the remote evaluators of the agent's workspace and the
|
|
856
|
+
Python `evaluators` you pass. Reads `evalkit.yaml` (cases, fixtures, credentials)
|
|
857
|
+
from the current directory or a parent, and the login from `evalkit login`.
|
|
858
|
+
|
|
859
|
+
Args:
|
|
860
|
+
dataset: The dataset to run. A bare name means your personal workspace; if it
|
|
861
|
+
is also a directory under `cases` in `evalkit.yaml`, it is synced first.
|
|
862
|
+
`name@workspace` runs a shared dataset, every review state included.
|
|
863
|
+
agent: The code-driven agent on the server that the runs belong to, created once
|
|
864
|
+
with `evalkit agents create`. A bare name means your personal workspace.
|
|
865
|
+
task: Your function, called once per case and repetition, sync or async. Its
|
|
866
|
+
parameters bind by name from `input` (the case input), `files`
|
|
867
|
+
(`list[Fixture]`), `model` and `metadata` (`session_id`, `experiment_id`,
|
|
868
|
+
`case_id`, `repetition`, `tags`). A task with one parameter of another name
|
|
869
|
+
receives the input. Return a `str` (the answer), a `dict` (the structured
|
|
870
|
+
output) or a `RunResult`. A task that raises records a failed run.
|
|
871
|
+
workspace: The workspace of `dataset` when it is a bare name.
|
|
872
|
+
evaluators: Python evaluators made with `evaluator`. A case selects one by name
|
|
873
|
+
under `evaluators.custom`, like an HTTP evaluator.
|
|
874
|
+
repetitions: How many times each case runs.
|
|
875
|
+
concurrency: How many tasks run at once. Defaults to the agent's
|
|
876
|
+
`max_concurrent_runs`.
|
|
877
|
+
case_ids: Run only these cases.
|
|
878
|
+
split: Run only the cases of this split (a case tag).
|
|
879
|
+
model: Passed to the task's `model` parameter and recorded on the experiment.
|
|
880
|
+
resume: An experiment id to resume: runs only the cases it has not finished.
|
|
881
|
+
dry_run: `True` runs one case without creating an experiment and prints the
|
|
882
|
+
answer and what the server made of the session; an int runs that many.
|
|
883
|
+
trajectory_wait_seconds: How long to wait for a run's spans to settle into a
|
|
884
|
+
trajectory. Defaults to the agent's setting.
|
|
885
|
+
judge: The judge for LLM-graded evaluators. Defaults to the server's default
|
|
886
|
+
judge. Its API key comes from `credentials.judges` in `evalkit.yaml`, keyed
|
|
887
|
+
by `base_url`.
|
|
888
|
+
|
|
889
|
+
Returns:
|
|
890
|
+
The experiment id, its URL and one summary per run.
|
|
891
|
+
|
|
892
|
+
Raises:
|
|
893
|
+
RuntimeError: When called from a running event loop; await `arun_experiment`
|
|
894
|
+
there instead.
|
|
895
|
+
"""
|
|
896
|
+
if _loop_is_running():
|
|
897
|
+
raise RuntimeError(
|
|
898
|
+
"run_experiment cannot be called from a running event loop; "
|
|
899
|
+
"await arun_experiment(...) instead"
|
|
900
|
+
)
|
|
901
|
+
return asyncio.run(
|
|
902
|
+
arun_experiment(
|
|
903
|
+
dataset=dataset,
|
|
904
|
+
agent=agent,
|
|
905
|
+
task=task,
|
|
906
|
+
workspace=workspace,
|
|
907
|
+
evaluators=evaluators,
|
|
908
|
+
repetitions=repetitions,
|
|
909
|
+
concurrency=concurrency,
|
|
910
|
+
case_ids=case_ids,
|
|
911
|
+
split=split,
|
|
912
|
+
model=model,
|
|
913
|
+
resume=resume,
|
|
914
|
+
dry_run=dry_run,
|
|
915
|
+
trajectory_wait_seconds=trajectory_wait_seconds,
|
|
916
|
+
judge=judge,
|
|
917
|
+
)
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def evaluate_experiment_sync(
|
|
922
|
+
*,
|
|
923
|
+
experiment_id: int,
|
|
924
|
+
evaluators: Sequence[PythonEvaluator] = (),
|
|
925
|
+
refetch_trajectories: bool = False,
|
|
926
|
+
) -> ExperimentResult:
|
|
927
|
+
"""Re-score an experiment that used Python evaluators.
|
|
928
|
+
|
|
929
|
+
`evalkit evaluate` refuses such experiments, because the evaluators live in your
|
|
930
|
+
script; call this from the script instead. Exported as `evaluate_experiment`.
|
|
931
|
+
|
|
932
|
+
Args:
|
|
933
|
+
experiment_id: The experiment to re-score.
|
|
934
|
+
evaluators: The Python evaluators the experiment ran with. Each must have been
|
|
935
|
+
part of the experiment.
|
|
936
|
+
refetch_trajectories: Read the trajectories of runs whose trajectory was
|
|
937
|
+
`partial` or `unavailable` again before scoring.
|
|
938
|
+
|
|
939
|
+
Returns:
|
|
940
|
+
The experiment id, its URL and one summary per run.
|
|
941
|
+
|
|
942
|
+
Raises:
|
|
943
|
+
RuntimeError: When called from a running event loop; await
|
|
944
|
+
`aevaluate_experiment` there instead.
|
|
945
|
+
"""
|
|
946
|
+
if _loop_is_running():
|
|
947
|
+
raise RuntimeError(
|
|
948
|
+
"evaluate_experiment cannot be called from a running event loop; "
|
|
949
|
+
"await aevaluate_experiment(...) instead"
|
|
950
|
+
)
|
|
951
|
+
return asyncio.run(
|
|
952
|
+
aevaluate_experiment(
|
|
953
|
+
experiment_id=experiment_id,
|
|
954
|
+
evaluators=evaluators,
|
|
955
|
+
refetch_trajectories=refetch_trajectories,
|
|
956
|
+
)
|
|
957
|
+
)
|