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/client/api.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from urllib.parse import quote
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from httpx_retries import Retry, RetryTransport
|
|
9
|
+
|
|
10
|
+
from evalkit import version
|
|
11
|
+
|
|
12
|
+
RETRY_STATUSES = (502, 503, 504)
|
|
13
|
+
RETRY_ERRORS = (httpx.NetworkError, httpx.ConnectTimeout, httpx.RemoteProtocolError)
|
|
14
|
+
RETRY_ATTEMPTS = 4
|
|
15
|
+
RETRY_BACKOFF_FACTOR = 0.5
|
|
16
|
+
RETRY_MAX_WAIT_SECONDS = 8.0
|
|
17
|
+
READ_METHODS = ("GET", "HEAD")
|
|
18
|
+
WRITE_METHODS = ("GET", "HEAD", "POST", "PUT", "PATCH")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ApiRequestError(Exception):
|
|
22
|
+
def __init__(self, status: int, detail: str, method: str, path: str):
|
|
23
|
+
self.status = status
|
|
24
|
+
self.detail = detail
|
|
25
|
+
super().__init__(f"{method} {path} → {status}: {detail}")
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_response(cls, resp: httpx.Response) -> ApiRequestError:
|
|
29
|
+
return cls(
|
|
30
|
+
resp.status_code, response_detail(resp), resp.request.method, resp.request.url.path
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def response_detail(resp: httpx.Response) -> str:
|
|
35
|
+
if resp.content and resp.headers.get("content-type", "").startswith("application/json"):
|
|
36
|
+
body = resp.json()
|
|
37
|
+
if isinstance(body, dict) and body.get("detail") is not None:
|
|
38
|
+
return str(body["detail"])
|
|
39
|
+
return resp.reason_phrase
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def retry_policy(methods: tuple[str, ...]) -> Retry:
|
|
43
|
+
return Retry(
|
|
44
|
+
total=RETRY_ATTEMPTS,
|
|
45
|
+
allowed_methods=methods,
|
|
46
|
+
status_forcelist=RETRY_STATUSES,
|
|
47
|
+
retry_on_exceptions=RETRY_ERRORS,
|
|
48
|
+
backoff_factor=RETRY_BACKOFF_FACTOR,
|
|
49
|
+
max_backoff_wait=RETRY_MAX_WAIT_SECONDS,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def retry_extensions(retry: bool | None) -> dict[str, Retry]:
|
|
54
|
+
if retry is None:
|
|
55
|
+
return {}
|
|
56
|
+
return {"retry": retry_policy(WRITE_METHODS) if retry else Retry(total=0)}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class VersionMismatch(Exception):
|
|
60
|
+
def __init__(self, local: str, remote: str):
|
|
61
|
+
self.local = local
|
|
62
|
+
self.remote = remote
|
|
63
|
+
super().__init__(
|
|
64
|
+
f"evalkit {local} does not match server {remote}; local runs need the same "
|
|
65
|
+
"version. Run `evalkit update` from the project directory: it reinstalls the CLI "
|
|
66
|
+
"and moves the evalkit-py pin in ./pyproject.toml"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Shorter than uvicorn's 5s idle keep-alive, so a poll never reuses a
|
|
71
|
+
# connection the server is closing at the same moment.
|
|
72
|
+
KEEPALIVE_EXPIRY_SECONDS = 2
|
|
73
|
+
DEFAULT_TIMEOUT_SECONDS = 120
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def timeout_seconds() -> float:
|
|
77
|
+
return float(os.environ.get("EVALKIT_TIMEOUT", DEFAULT_TIMEOUT_SECONDS))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def encode(segment: str) -> str:
|
|
81
|
+
return quote(segment, safe="")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AsyncApi:
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
server_url: str,
|
|
88
|
+
token: str | None,
|
|
89
|
+
*,
|
|
90
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
91
|
+
):
|
|
92
|
+
self._client = httpx.AsyncClient(
|
|
93
|
+
base_url=server_url.rstrip("/"),
|
|
94
|
+
headers={"Authorization": f"Bearer {token}"} if token else {},
|
|
95
|
+
timeout=timeout_seconds(),
|
|
96
|
+
transport=RetryTransport(
|
|
97
|
+
transport=transport
|
|
98
|
+
or httpx.AsyncHTTPTransport(
|
|
99
|
+
limits=httpx.Limits(keepalive_expiry=KEEPALIVE_EXPIRY_SECONDS)
|
|
100
|
+
),
|
|
101
|
+
retry=retry_policy(READ_METHODS),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
self.remote_version: str | None = None
|
|
105
|
+
|
|
106
|
+
async def aclose(self) -> None:
|
|
107
|
+
await self._client.aclose()
|
|
108
|
+
|
|
109
|
+
async def request(
|
|
110
|
+
self, method: str, path: str, *, retry: bool | None = None, **kwargs
|
|
111
|
+
) -> httpx.Response:
|
|
112
|
+
resp = await self._client.request(
|
|
113
|
+
method, path, extensions=retry_extensions(retry), **kwargs
|
|
114
|
+
)
|
|
115
|
+
self.remote_version = resp.headers.get("X-Evalkit-Version", self.remote_version)
|
|
116
|
+
if resp.status_code >= 400:
|
|
117
|
+
raise ApiRequestError.from_response(resp)
|
|
118
|
+
return resp
|
|
119
|
+
|
|
120
|
+
async def get(self, path: str, **kwargs) -> httpx.Response:
|
|
121
|
+
return await self.request("GET", path, **kwargs)
|
|
122
|
+
|
|
123
|
+
async def post(self, path: str, **kwargs) -> httpx.Response:
|
|
124
|
+
return await self.request("POST", path, **kwargs)
|
|
125
|
+
|
|
126
|
+
async def put(self, path: str, **kwargs) -> httpx.Response:
|
|
127
|
+
return await self.request("PUT", path, **kwargs)
|
|
128
|
+
|
|
129
|
+
async def patch(self, path: str, **kwargs) -> httpx.Response:
|
|
130
|
+
return await self.request("PATCH", path, **kwargs)
|
|
131
|
+
|
|
132
|
+
async def head(self, path: str, **kwargs) -> httpx.Response:
|
|
133
|
+
resp = await self._client.request("HEAD", path, **kwargs)
|
|
134
|
+
self.remote_version = resp.headers.get("X-Evalkit-Version", self.remote_version)
|
|
135
|
+
return resp
|
|
136
|
+
|
|
137
|
+
async def require_same_version(self) -> None:
|
|
138
|
+
await self.get("/api/health")
|
|
139
|
+
local = version.current()
|
|
140
|
+
if self.remote_version != local:
|
|
141
|
+
raise VersionMismatch(local, self.remote_version or "unknown")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class SyncApi:
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
server_url: str,
|
|
148
|
+
token: str | None = None,
|
|
149
|
+
*,
|
|
150
|
+
transport: httpx.BaseTransport | None = None,
|
|
151
|
+
):
|
|
152
|
+
self._client = httpx.Client(
|
|
153
|
+
base_url=server_url.rstrip("/"),
|
|
154
|
+
headers={"Authorization": f"Bearer {token}"} if token else {},
|
|
155
|
+
timeout=timeout_seconds(),
|
|
156
|
+
transport=RetryTransport(
|
|
157
|
+
transport=transport or httpx.HTTPTransport(), retry=retry_policy(READ_METHODS)
|
|
158
|
+
),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def _handle(self, resp: httpx.Response) -> httpx.Response:
|
|
162
|
+
if resp.status_code >= 400:
|
|
163
|
+
raise ApiRequestError.from_response(resp)
|
|
164
|
+
return resp
|
|
165
|
+
|
|
166
|
+
def request(
|
|
167
|
+
self, method: str, path: str, *, retry: bool | None = None, **kwargs
|
|
168
|
+
) -> httpx.Response:
|
|
169
|
+
return self._handle(
|
|
170
|
+
self._client.request(method, path, extensions=retry_extensions(retry), **kwargs)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def get(self, path: str, **kwargs) -> httpx.Response:
|
|
174
|
+
return self.request("GET", path, **kwargs)
|
|
175
|
+
|
|
176
|
+
def post(self, path: str, **kwargs) -> httpx.Response:
|
|
177
|
+
return self.request("POST", path, **kwargs)
|
|
178
|
+
|
|
179
|
+
def delete(self, path: str, **kwargs) -> httpx.Response:
|
|
180
|
+
return self.request("DELETE", path, **kwargs)
|
|
181
|
+
|
|
182
|
+
def post_files(self, path: str, files: list[tuple[str, Path]], **kwargs) -> httpx.Response:
|
|
183
|
+
parts = [("files", (name, f.read_bytes(), "application/octet-stream")) for name, f in files]
|
|
184
|
+
return self.request("POST", path, files=parts, **kwargs)
|
|
185
|
+
|
|
186
|
+
def patch(self, path: str, **kwargs) -> httpx.Response:
|
|
187
|
+
return self.request("PATCH", path, **kwargs)
|
|
188
|
+
|
|
189
|
+
def put(self, path: str, **kwargs) -> httpx.Response:
|
|
190
|
+
return self.request("PUT", path, **kwargs)
|
|
191
|
+
|
|
192
|
+
def head(self, path: str, **kwargs) -> httpx.Response:
|
|
193
|
+
return self._client.request("HEAD", path, **kwargs)
|
evalkit/client/blob.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from tempfile import mkdtemp
|
|
6
|
+
|
|
7
|
+
from evalkit.client.api import AsyncApi
|
|
8
|
+
from evalkit.store.base import BlobStream, CaseFile
|
|
9
|
+
|
|
10
|
+
FIXTURE_PREFIX = "fixtures/sha256/"
|
|
11
|
+
CHECK_PREFIX = "checks/"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HttpBlobStore:
|
|
15
|
+
def __init__(self, api: AsyncApi, experiment_id: int | None, cache_dir: Path | None = None):
|
|
16
|
+
self._api = api
|
|
17
|
+
self._experiment_id = experiment_id
|
|
18
|
+
self._configured_dir = cache_dir
|
|
19
|
+
self._scratch_dir: Path | None = None
|
|
20
|
+
|
|
21
|
+
def _cache_dir(self) -> Path:
|
|
22
|
+
if self._configured_dir is not None:
|
|
23
|
+
return self._configured_dir
|
|
24
|
+
if self._scratch_dir is None:
|
|
25
|
+
self._scratch_dir = Path(mkdtemp(prefix="evalkit-fixtures-"))
|
|
26
|
+
return self._scratch_dir
|
|
27
|
+
|
|
28
|
+
def _local_path(self, key: str) -> Path | None:
|
|
29
|
+
if key.startswith(FIXTURE_PREFIX):
|
|
30
|
+
return self._cache_dir() / ".sha256" / key.removeprefix(FIXTURE_PREFIX)
|
|
31
|
+
if key.startswith(CHECK_PREFIX):
|
|
32
|
+
return self._cache_dir() / ".checks" / key.removeprefix(CHECK_PREFIX)
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
def _experiment_path(self) -> str:
|
|
36
|
+
if self._experiment_id is None:
|
|
37
|
+
raise ValueError("no experiment bound; only fixture and check keys are available")
|
|
38
|
+
return f"/api/experiments/{self._experiment_id}/blob"
|
|
39
|
+
|
|
40
|
+
def _remote(self, key: str) -> str:
|
|
41
|
+
return f"{self._experiment_path()}/{key}"
|
|
42
|
+
|
|
43
|
+
async def put_object(self, key: str, data: bytes) -> None:
|
|
44
|
+
local = self._local_path(key)
|
|
45
|
+
if local is not None:
|
|
46
|
+
local.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
local.write_bytes(data)
|
|
48
|
+
return
|
|
49
|
+
await self._api.put(self._remote(key), content=data, retry=True)
|
|
50
|
+
|
|
51
|
+
async def put_stream(self, key: str, chunks: AsyncIterator[bytes]) -> None:
|
|
52
|
+
await self.put_object(key, b"".join([chunk async for chunk in chunks]))
|
|
53
|
+
|
|
54
|
+
async def _cached(self, key: str, local: Path) -> Path:
|
|
55
|
+
if not local.is_file() and key.startswith(FIXTURE_PREFIX):
|
|
56
|
+
resp = await self._api.get(f"/api/fixtures/sha256/{key.removeprefix(FIXTURE_PREFIX)}")
|
|
57
|
+
local.parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
local.write_bytes(resp.content)
|
|
59
|
+
return local
|
|
60
|
+
|
|
61
|
+
async def get_object(self, key: str) -> bytes:
|
|
62
|
+
local = self._local_path(key)
|
|
63
|
+
if local is None:
|
|
64
|
+
return (await self._api.get(self._remote(key))).content
|
|
65
|
+
return (await self._cached(key, local)).read_bytes()
|
|
66
|
+
|
|
67
|
+
async def get_stream(self, key: str) -> BlobStream:
|
|
68
|
+
data = await self.get_object(key)
|
|
69
|
+
return BlobStream(len(data), _once(data))
|
|
70
|
+
|
|
71
|
+
async def fixture_path(self, file: CaseFile) -> Path:
|
|
72
|
+
named = self._cache_dir() / file.sha256 / file.key
|
|
73
|
+
if not named.is_file():
|
|
74
|
+
named.parent.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
named.write_bytes(await self.get_object(f"{FIXTURE_PREFIX}{file.sha256}"))
|
|
76
|
+
return named
|
|
77
|
+
|
|
78
|
+
async def object_exists(self, key: str) -> bool:
|
|
79
|
+
local = self._local_path(key)
|
|
80
|
+
if local is not None:
|
|
81
|
+
return local.is_file()
|
|
82
|
+
return (await self._api.head(self._remote(key))).status_code == 200
|
|
83
|
+
|
|
84
|
+
async def list_objects(self, prefix: str) -> list[tuple[str, int]]:
|
|
85
|
+
if self._local_path(prefix) is not None:
|
|
86
|
+
raise ValueError("listing local keys is not supported")
|
|
87
|
+
body = (await self._api.get(self._experiment_path(), params={"prefix": prefix})).json()
|
|
88
|
+
return [(str(key), int(size)) for key, size in body["objects"]]
|
|
89
|
+
|
|
90
|
+
async def delete_object(self, key: str) -> None:
|
|
91
|
+
raise NotImplementedError
|
|
92
|
+
|
|
93
|
+
async def copy_object(self, source: str, destination: str) -> None:
|
|
94
|
+
raise NotImplementedError
|
|
95
|
+
|
|
96
|
+
async def object_url(self, key: str) -> str:
|
|
97
|
+
local = self._local_path(key)
|
|
98
|
+
if local is None:
|
|
99
|
+
raise ValueError(f"{key} has no URL a harness can fetch")
|
|
100
|
+
return (await self._cached(key, local)).resolve().as_uri()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def _once(data: bytes) -> AsyncIterator[bytes]:
|
|
104
|
+
yield data
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
import tomllib
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import NamedTuple
|
|
9
|
+
|
|
10
|
+
CONFIG_PATH = Path.home() / ".evalkit" / "config.toml"
|
|
11
|
+
|
|
12
|
+
TOKEN_WARNING_WINDOW = timedelta(days=1)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Connection(NamedTuple):
|
|
16
|
+
url: str
|
|
17
|
+
token: str | None
|
|
18
|
+
username: str | None
|
|
19
|
+
from_env: bool = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _toml_string(value: str) -> str:
|
|
23
|
+
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
|
|
24
|
+
return f'"{escaped}"'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load(path: Path | None = None) -> Connection | None:
|
|
28
|
+
target = path or CONFIG_PATH
|
|
29
|
+
if not target.is_file():
|
|
30
|
+
return None
|
|
31
|
+
data = tomllib.loads(target.read_text())
|
|
32
|
+
if "url" not in data:
|
|
33
|
+
if "profiles" in data:
|
|
34
|
+
raise LookupError(
|
|
35
|
+
f"{target} holds server profiles, which evalkit no longer supports; "
|
|
36
|
+
"run `evalkit login --url <url>` to replace it"
|
|
37
|
+
)
|
|
38
|
+
return None
|
|
39
|
+
return Connection(str(data["url"]), data.get("token"), data.get("username"))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def save(connection: Connection, path: Path | None = None) -> Path:
|
|
43
|
+
target = path or CONFIG_PATH
|
|
44
|
+
lines = [f"url = {_toml_string(connection.url)}"]
|
|
45
|
+
if connection.token is not None:
|
|
46
|
+
lines.append(f"token = {_toml_string(connection.token)}")
|
|
47
|
+
if connection.username is not None:
|
|
48
|
+
lines.append(f"username = {_toml_string(connection.username)}")
|
|
49
|
+
target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
50
|
+
target.parent.chmod(0o700)
|
|
51
|
+
fd, temp = tempfile.mkstemp(dir=target.parent, prefix=f".{target.name}.")
|
|
52
|
+
with os.fdopen(fd, "w") as file:
|
|
53
|
+
file.write("\n".join(lines) + "\n")
|
|
54
|
+
os.replace(temp, target)
|
|
55
|
+
return target
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def active(path: Path | None = None) -> Connection:
|
|
59
|
+
env_url = os.environ.get("EVALKIT_SERVER_URL")
|
|
60
|
+
if env_url:
|
|
61
|
+
return Connection(env_url, os.environ.get("EVALKIT_TOKEN"), None, from_env=True)
|
|
62
|
+
stored = load(path or CONFIG_PATH)
|
|
63
|
+
if stored is None:
|
|
64
|
+
raise LookupError(
|
|
65
|
+
"no server configured — run `evalkit login --url <url>` or set EVALKIT_SERVER_URL"
|
|
66
|
+
)
|
|
67
|
+
return stored
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def renew_hint(connection: Connection) -> str:
|
|
71
|
+
if connection.from_env:
|
|
72
|
+
return "set a fresh EVALKIT_TOKEN"
|
|
73
|
+
return f"run `evalkit login --url {connection.url}`"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def token_expiry_warning(
|
|
77
|
+
connection: Connection, expires_at: str | None, now: datetime
|
|
78
|
+
) -> str | None:
|
|
79
|
+
if expires_at is None:
|
|
80
|
+
return None
|
|
81
|
+
left = datetime.fromisoformat(expires_at) - now
|
|
82
|
+
if left > TOKEN_WARNING_WINDOW:
|
|
83
|
+
return None
|
|
84
|
+
hours = max(left.total_seconds(), 0) / 3600
|
|
85
|
+
return (
|
|
86
|
+
f"the token for {connection.url} expires in {hours:.1f}h; "
|
|
87
|
+
f"{renew_hint(connection)} before a long run"
|
|
88
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from evalkit.client.api import AsyncApi
|
|
2
|
+
from evalkit.core.remote_evaluators import RemoteEvaluator
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HttpEvaluators:
|
|
6
|
+
def __init__(self, api: AsyncApi):
|
|
7
|
+
self._api = api
|
|
8
|
+
|
|
9
|
+
async def in_workspace(self, workspace: str) -> dict[str, RemoteEvaluator]:
|
|
10
|
+
rows = (await self._api.get("/api/evaluators", params={"workspace": workspace})).json()
|
|
11
|
+
return {row["name"]: RemoteEvaluator.model_validate(row) for row in rows}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from evalkit.client.api import ApiRequestError, AsyncApi, encode
|
|
2
|
+
from evalkit.core.traces.sessions import SessionTooLarge, SessionTrajectory
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HttpSessionReader:
|
|
6
|
+
def __init__(self, api: AsyncApi):
|
|
7
|
+
self._api = api
|
|
8
|
+
|
|
9
|
+
async def trajectory(self, agent: str, session_id: str) -> SessionTrajectory:
|
|
10
|
+
path = f"/api/agents/{encode(agent)}/sessions/{encode(session_id)}/trajectory"
|
|
11
|
+
try:
|
|
12
|
+
resp = await self._api.get(path)
|
|
13
|
+
except ApiRequestError as exc:
|
|
14
|
+
if exc.status == 413:
|
|
15
|
+
raise SessionTooLarge(exc.detail) from exc
|
|
16
|
+
raise
|
|
17
|
+
return SessionTrajectory.model_validate(resp.json())
|
evalkit/client/store.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import socket
|
|
5
|
+
|
|
6
|
+
from evalkit.client.api import AsyncApi, encode
|
|
7
|
+
from evalkit.contracts.enums import ExperimentStatus
|
|
8
|
+
from evalkit.core.runner import LEASE_SECONDS, ExperimentRequest
|
|
9
|
+
from evalkit.store.base import (
|
|
10
|
+
AgentRunRecord,
|
|
11
|
+
CaseRep,
|
|
12
|
+
CaseView,
|
|
13
|
+
EvaluationRow,
|
|
14
|
+
Experiment,
|
|
15
|
+
RunDetail,
|
|
16
|
+
RunForScoring,
|
|
17
|
+
RunRef,
|
|
18
|
+
Selection,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
RENEW_TIMEOUT_SECONDS = LEASE_SECONDS / 6
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def local_holder(username: str) -> str:
|
|
25
|
+
return f"local:{username}@{socket.gethostname()}:{os.getpid()}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HttpStore:
|
|
29
|
+
def __init__(self, api: AsyncApi):
|
|
30
|
+
self._api = api
|
|
31
|
+
|
|
32
|
+
async def aclose(self) -> None:
|
|
33
|
+
await self._api.aclose()
|
|
34
|
+
|
|
35
|
+
async def cases_for(
|
|
36
|
+
self, dataset: str, selection: Selection, *, agent: str | None
|
|
37
|
+
) -> list[CaseView]:
|
|
38
|
+
rows = (
|
|
39
|
+
await self._api.post(
|
|
40
|
+
f"/api/datasets/{encode(dataset)}/cases/select",
|
|
41
|
+
json={
|
|
42
|
+
"selection": selection.model_dump(exclude_none=True),
|
|
43
|
+
"agent": agent,
|
|
44
|
+
},
|
|
45
|
+
retry=True,
|
|
46
|
+
)
|
|
47
|
+
).json()
|
|
48
|
+
return [CaseView.model_validate(r) for r in rows]
|
|
49
|
+
|
|
50
|
+
async def start_experiment(self, req: ExperimentRequest) -> tuple[Experiment, list[str]]:
|
|
51
|
+
body = req.model_dump(mode="json", exclude_none=True)
|
|
52
|
+
created = (await self._api.post("/api/experiments", json=body)).json()
|
|
53
|
+
return Experiment.model_validate(created), list(created.get("dropped_cases") or [])
|
|
54
|
+
|
|
55
|
+
async def get_experiment(self, exp_id: int) -> Experiment:
|
|
56
|
+
return Experiment.model_validate((await self._api.get(f"/api/experiments/{exp_id}")).json())
|
|
57
|
+
|
|
58
|
+
async def pinned_cases(self, exp_id: int) -> list[CaseView]:
|
|
59
|
+
rows = (await self._api.get(f"/api/experiments/{exp_id}/cases")).json()
|
|
60
|
+
return [CaseView.model_validate(r) for r in rows]
|
|
61
|
+
|
|
62
|
+
async def transition(
|
|
63
|
+
self, exp_id: int, to: ExperimentStatus, holder: str | None, error: str | None = None
|
|
64
|
+
) -> Experiment:
|
|
65
|
+
body = {"status": to.value, "holder": holder, "error": error}
|
|
66
|
+
resp = await self._api.patch(f"/api/experiments/{exp_id}", json=body, retry=True)
|
|
67
|
+
return Experiment.model_validate(resp.json())
|
|
68
|
+
|
|
69
|
+
async def renew_lease(self, exp_id: int, holder: str) -> bool:
|
|
70
|
+
resp = await self._api.post(
|
|
71
|
+
f"/api/experiments/{exp_id}/lease",
|
|
72
|
+
json={"holder": holder},
|
|
73
|
+
retry=True,
|
|
74
|
+
timeout=RENEW_TIMEOUT_SECONDS,
|
|
75
|
+
)
|
|
76
|
+
return bool(resp.json()["renewed"])
|
|
77
|
+
|
|
78
|
+
async def set_model(self, exp_id: int, model: str) -> None:
|
|
79
|
+
await self._api.patch(f"/api/experiments/{exp_id}", json={"model": model}, retry=True)
|
|
80
|
+
|
|
81
|
+
async def pending_runs(self, exp_id: int) -> list[CaseRep]:
|
|
82
|
+
rows = (await self._api.get(f"/api/experiments/{exp_id}/pending")).json()
|
|
83
|
+
return [CaseRep.model_validate(r) for r in rows]
|
|
84
|
+
|
|
85
|
+
async def harness_run_id_used(self, exp_id: int, harness_run_id: str) -> str | None:
|
|
86
|
+
resp = await self._api.get(
|
|
87
|
+
f"/api/experiments/{exp_id}/harness-run-id", params={"value": harness_run_id}
|
|
88
|
+
)
|
|
89
|
+
return resp.json()["used"]
|
|
90
|
+
|
|
91
|
+
async def record_run(
|
|
92
|
+
self,
|
|
93
|
+
exp_id: int,
|
|
94
|
+
case_id: str,
|
|
95
|
+
repetition: int,
|
|
96
|
+
run: AgentRunRecord,
|
|
97
|
+
*,
|
|
98
|
+
holder: str | None = None,
|
|
99
|
+
) -> RunRef:
|
|
100
|
+
resp = await self._api.post(
|
|
101
|
+
f"/api/experiments/{exp_id}/runs",
|
|
102
|
+
json={
|
|
103
|
+
"case_id": case_id,
|
|
104
|
+
"repetition": repetition,
|
|
105
|
+
"run": run.model_dump(mode="json"),
|
|
106
|
+
"holder": holder,
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
return RunRef.model_validate(resp.json())
|
|
110
|
+
|
|
111
|
+
async def runs_to_score(self, exp_id: int, *, force: bool) -> list[RunForScoring]:
|
|
112
|
+
rows = (
|
|
113
|
+
await self._api.get(
|
|
114
|
+
f"/api/experiments/{exp_id}/runs-to-score", params={"force": str(force).lower()}
|
|
115
|
+
)
|
|
116
|
+
).json()
|
|
117
|
+
return [RunForScoring.model_validate(r) for r in rows]
|
|
118
|
+
|
|
119
|
+
async def publish_evaluations(
|
|
120
|
+
self,
|
|
121
|
+
experiment_run_id: int,
|
|
122
|
+
rows: list[EvaluationRow],
|
|
123
|
+
*,
|
|
124
|
+
force: bool,
|
|
125
|
+
holder: str | None = None,
|
|
126
|
+
) -> None:
|
|
127
|
+
await self._api.post(
|
|
128
|
+
f"/api/experiment-runs/{experiment_run_id}/evaluations",
|
|
129
|
+
json={
|
|
130
|
+
"rows": [r.model_dump(mode="json") for r in rows],
|
|
131
|
+
"force": force,
|
|
132
|
+
"holder": holder,
|
|
133
|
+
},
|
|
134
|
+
retry=True,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
async def run_details(self, exp_id: int) -> list[RunDetail]:
|
|
138
|
+
rows = (await self._api.get(f"/api/experiments/{exp_id}/runs")).json()
|
|
139
|
+
return [RunDetail.model_validate(r) for r in rows]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
def _fmt(value: float | None) -> str:
|
|
2
|
+
return f"{value:.2f}" if value is not None else " - "
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _num(value: float | None) -> str:
|
|
6
|
+
if value is None:
|
|
7
|
+
return "-"
|
|
8
|
+
if value and abs(value) < 1:
|
|
9
|
+
return f"{value:.3g}"
|
|
10
|
+
return f"{value:.2f}".rstrip("0").rstrip(".")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def summary_lines(report: dict) -> list[str]:
|
|
14
|
+
lines = [f"\nexperiment {report['experiment_id']} {report['name']} of {report['dataset']}"]
|
|
15
|
+
scored = {bucket: value for bucket, value in report["buckets"].items() if value is not None}
|
|
16
|
+
if scored:
|
|
17
|
+
lines.append("buckets:")
|
|
18
|
+
lines += [f" {bucket:<14} {_fmt(value)}" for bucket, value in scored.items()]
|
|
19
|
+
lines.append("evaluators:")
|
|
20
|
+
for stat in report["evaluators"]:
|
|
21
|
+
errored = f", {stat['errors']} errored" if stat.get("errors") else ""
|
|
22
|
+
noun = "case" if stat["cases"] == 1 else "cases"
|
|
23
|
+
counts = f"({stat['cases']} {noun}{errored})"
|
|
24
|
+
lines.append(f" {stat['name']:<40} {_fmt(stat['score'])} {counts}")
|
|
25
|
+
failing = [c for c in report["cases"] if c["overall"] is not None and c["overall"] < 0.8]
|
|
26
|
+
if failing:
|
|
27
|
+
lines.append("failing cases (< 0.80):")
|
|
28
|
+
for case in failing:
|
|
29
|
+
delta = f" Δ{case['delta']:+.2f}" if case.get("delta") is not None else ""
|
|
30
|
+
lines.append(f" {case['case_id']:<48} {_fmt(case['overall'])}{delta}")
|
|
31
|
+
errored_cases = [c for c in report["cases"] if c["errors"] or c["run_errors"]]
|
|
32
|
+
if errored_cases:
|
|
33
|
+
lines.append("errored cases:")
|
|
34
|
+
for case in errored_cases:
|
|
35
|
+
problems = [("run", error) for error in case["run_errors"]]
|
|
36
|
+
problems += sorted(case["errors"].items())
|
|
37
|
+
for name, error in problems:
|
|
38
|
+
first_line = error.splitlines()[0][:120] if error else ""
|
|
39
|
+
lines.append(f" {case['case_id']:<48} {name}: {first_line}")
|
|
40
|
+
if report["gauges"]:
|
|
41
|
+
lines.append("gauges:")
|
|
42
|
+
lines += [
|
|
43
|
+
f" {gauge['name']:<32} mean={_num(gauge['mean'])} median={_num(gauge['median'])}"
|
|
44
|
+
for gauge in report["gauges"]
|
|
45
|
+
]
|
|
46
|
+
return lines
|