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/core/importer.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""One importer code path for `sync`: loads and
|
|
2
|
+
validates YAML, lints against the other datasets in the workspace, checks that
|
|
3
|
+
every fixture's bytes were uploaded, then hands the cases and overlays to the
|
|
4
|
+
store, which diffs by content hash.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Protocol
|
|
11
|
+
|
|
12
|
+
import yaml
|
|
13
|
+
from pydantic import BaseModel, Field, ValidationError
|
|
14
|
+
|
|
15
|
+
from evalkit.contracts.cases import (
|
|
16
|
+
REGISTRY_FILENAME,
|
|
17
|
+
Case,
|
|
18
|
+
FixtureEntry,
|
|
19
|
+
Overlay,
|
|
20
|
+
content_hash,
|
|
21
|
+
is_case_yaml,
|
|
22
|
+
load_cases_from_yaml,
|
|
23
|
+
)
|
|
24
|
+
from evalkit.contracts.enums import DatasetKind, SyncSource
|
|
25
|
+
from evalkit.core.access import WorkspaceRef
|
|
26
|
+
from evalkit.core.fixtures import load_registries
|
|
27
|
+
from evalkit.core.lint import Finding, fixture_findings, has_errors
|
|
28
|
+
from evalkit.core.lint import lint as lint_cases
|
|
29
|
+
from evalkit.core.overlays import OverlayBlock, incoming_overlay, overlay_clashes
|
|
30
|
+
from evalkit.store.base import (
|
|
31
|
+
CarriedReview,
|
|
32
|
+
CaseFile,
|
|
33
|
+
IncomingCase,
|
|
34
|
+
IncomingOverlay,
|
|
35
|
+
SyncResult,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CaseCatalog(Protocol):
|
|
40
|
+
async def workspace_cases(
|
|
41
|
+
self, workspace: WorkspaceRef, *, exclude_dataset: str | None
|
|
42
|
+
) -> dict[str, str]: ...
|
|
43
|
+
|
|
44
|
+
async def overlays_on(
|
|
45
|
+
self, workspace: WorkspaceRef, case_ids: list[str], *, exclude_dataset: str
|
|
46
|
+
) -> dict[str, list[OverlayBlock]]: ...
|
|
47
|
+
|
|
48
|
+
async def known_blobs(self, shas: list[str]) -> set[str]: ...
|
|
49
|
+
|
|
50
|
+
async def has_dataset(self, workspace: WorkspaceRef, name: str) -> bool: ...
|
|
51
|
+
|
|
52
|
+
async def sync(
|
|
53
|
+
self,
|
|
54
|
+
*,
|
|
55
|
+
workspace: WorkspaceRef,
|
|
56
|
+
dataset: str,
|
|
57
|
+
kind: DatasetKind,
|
|
58
|
+
incoming: list[IncomingCase],
|
|
59
|
+
overlays: list[IncomingOverlay],
|
|
60
|
+
full: bool,
|
|
61
|
+
actor_id: int,
|
|
62
|
+
source: SyncSource,
|
|
63
|
+
detail: dict,
|
|
64
|
+
) -> SyncResult: ...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ReviewReport(BaseModel):
|
|
68
|
+
now_unreviewed: list[str] = Field(default_factory=list)
|
|
69
|
+
carried: list[CarriedReview] = Field(default_factory=list)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ImportReport(BaseModel):
|
|
73
|
+
created: int = 0
|
|
74
|
+
patched: int = 0
|
|
75
|
+
deleted: int = 0
|
|
76
|
+
overlays_applied: int = 0
|
|
77
|
+
findings: list[Finding] = Field(default_factory=list)
|
|
78
|
+
aborted: bool = False
|
|
79
|
+
review: ReviewReport = Field(default_factory=ReviewReport)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def load_files(
|
|
83
|
+
files: list[Path],
|
|
84
|
+
) -> tuple[list[tuple[Case, Path]], list[tuple[Overlay, str]], list[Finding]]:
|
|
85
|
+
cases: list[tuple[Case, Path]] = []
|
|
86
|
+
overlays: list[tuple[Overlay, str]] = []
|
|
87
|
+
findings: list[Finding] = []
|
|
88
|
+
for path in files:
|
|
89
|
+
if not is_case_yaml(path):
|
|
90
|
+
continue
|
|
91
|
+
try:
|
|
92
|
+
file_cases, file_overlays = load_cases_from_yaml(path)
|
|
93
|
+
except (ValidationError, ValueError, yaml.YAMLError) as exc:
|
|
94
|
+
findings.append(
|
|
95
|
+
Finding(
|
|
96
|
+
severity="error",
|
|
97
|
+
code="E-SCHEMA",
|
|
98
|
+
case_id=path.name,
|
|
99
|
+
message=str(exc),
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
continue
|
|
103
|
+
cases.extend((case, path) for case in file_cases)
|
|
104
|
+
overlays.extend((ov, path.name) for ov in file_overlays)
|
|
105
|
+
return cases, overlays, findings
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def case_files(case: Case, entries: dict[str, FixtureEntry]) -> list[CaseFile]:
|
|
109
|
+
return [
|
|
110
|
+
CaseFile(key=key, sha256=entries[key].sha256, context=entries[key].context)
|
|
111
|
+
for key in case.fixtures
|
|
112
|
+
if key in entries
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def missing_blobs(
|
|
117
|
+
catalog: CaseCatalog, cases: list[Case], entries: dict[str, FixtureEntry]
|
|
118
|
+
) -> list[Finding]:
|
|
119
|
+
wanted = [(case.id, file) for case in cases for file in case_files(case, entries)]
|
|
120
|
+
known = await catalog.known_blobs(sorted({file.sha256 for _, file in wanted}))
|
|
121
|
+
return [
|
|
122
|
+
Finding(
|
|
123
|
+
severity="error",
|
|
124
|
+
code="E-FIXTURE-MISSING",
|
|
125
|
+
case_id=case_id,
|
|
126
|
+
message=(
|
|
127
|
+
f"fixture {file.key!r} (sha256 {file.sha256}) was never uploaded; the evalkit "
|
|
128
|
+
"CLI uploads it from the `fixtures:` directory in evalkit.yaml when you sync"
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
for case_id, file in wanted
|
|
132
|
+
if file.sha256 not in known
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _path_tags(source_path: Path | None, root: Path | None) -> list[str]:
|
|
137
|
+
if source_path is None or root is None:
|
|
138
|
+
return []
|
|
139
|
+
return list(source_path.resolve().relative_to(root.resolve()).parent.parts)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def incoming_case(
|
|
143
|
+
case: Case, source: str, files: list[CaseFile], extra_tags: list[str]
|
|
144
|
+
) -> IncomingCase:
|
|
145
|
+
return IncomingCase(
|
|
146
|
+
case_id=case.id,
|
|
147
|
+
content_hash=content_hash(case, [f.model_dump() for f in files]),
|
|
148
|
+
input=dict(case.input),
|
|
149
|
+
expected=case.expected,
|
|
150
|
+
evaluators=case.evaluators.as_metadata(),
|
|
151
|
+
tags=list(case.tags),
|
|
152
|
+
path_tags=[tag for tag in dict.fromkeys(extra_tags) if tag not in case.tags],
|
|
153
|
+
source=source,
|
|
154
|
+
files=files,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def import_cases(
|
|
159
|
+
catalog: CaseCatalog,
|
|
160
|
+
*,
|
|
161
|
+
workspace: WorkspaceRef,
|
|
162
|
+
dataset: str,
|
|
163
|
+
kind: DatasetKind,
|
|
164
|
+
sourced_cases: list[tuple[Case, Path | None]],
|
|
165
|
+
overlays: list[tuple[Overlay, str]],
|
|
166
|
+
findings: list[Finding],
|
|
167
|
+
full: bool,
|
|
168
|
+
known_custom_evaluators: set[str],
|
|
169
|
+
actor_id: int,
|
|
170
|
+
source: SyncSource,
|
|
171
|
+
detail: dict,
|
|
172
|
+
entries: dict[str, FixtureEntry] | None = None,
|
|
173
|
+
root: Path | None = None,
|
|
174
|
+
) -> ImportReport:
|
|
175
|
+
cases = [case for case, _ in sourced_cases]
|
|
176
|
+
entries = entries or {}
|
|
177
|
+
findings = list(findings)
|
|
178
|
+
findings.extend(
|
|
179
|
+
lint_cases(
|
|
180
|
+
cases,
|
|
181
|
+
[overlay for overlay, _ in overlays],
|
|
182
|
+
known_custom_evaluators=known_custom_evaluators,
|
|
183
|
+
other_cases=await catalog.workspace_cases(workspace, exclude_dataset=dataset),
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
existing = await catalog.overlays_on(
|
|
187
|
+
workspace, [overlay.extends for overlay, _ in overlays], exclude_dataset=dataset
|
|
188
|
+
)
|
|
189
|
+
findings.extend(overlay_clashes([overlay for overlay, _ in overlays], existing))
|
|
190
|
+
findings.extend(fixture_findings(cases, entries, warn_unused=False))
|
|
191
|
+
if has_errors(findings):
|
|
192
|
+
return ImportReport(findings=findings, aborted=True)
|
|
193
|
+
findings.extend(await missing_blobs(catalog, cases, entries))
|
|
194
|
+
if has_errors(findings):
|
|
195
|
+
return ImportReport(findings=findings, aborted=True)
|
|
196
|
+
if not sourced_cases and not overlays and not await catalog.has_dataset(workspace, dataset):
|
|
197
|
+
return ImportReport(findings=findings)
|
|
198
|
+
result = await catalog.sync(
|
|
199
|
+
workspace=workspace,
|
|
200
|
+
dataset=dataset,
|
|
201
|
+
kind=kind,
|
|
202
|
+
incoming=[
|
|
203
|
+
incoming_case(
|
|
204
|
+
case,
|
|
205
|
+
source=dataset if source_path is None else source_path.name,
|
|
206
|
+
files=case_files(case, entries),
|
|
207
|
+
extra_tags=_path_tags(source_path, root),
|
|
208
|
+
)
|
|
209
|
+
for case, source_path in sourced_cases
|
|
210
|
+
],
|
|
211
|
+
overlays=[incoming_overlay(overlay, file) for overlay, file in overlays],
|
|
212
|
+
full=full,
|
|
213
|
+
actor_id=actor_id,
|
|
214
|
+
source=source,
|
|
215
|
+
detail=detail,
|
|
216
|
+
)
|
|
217
|
+
return ImportReport(
|
|
218
|
+
created=len(result.created),
|
|
219
|
+
patched=len(result.patched),
|
|
220
|
+
deleted=len(result.removed),
|
|
221
|
+
overlays_applied=result.overlays,
|
|
222
|
+
findings=findings,
|
|
223
|
+
review=ReviewReport(now_unreviewed=result.now_unreviewed, carried=result.carried),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
async def import_dataset(
|
|
228
|
+
catalog: CaseCatalog,
|
|
229
|
+
*,
|
|
230
|
+
workspace: WorkspaceRef,
|
|
231
|
+
dataset: str,
|
|
232
|
+
files: list[Path],
|
|
233
|
+
full: bool,
|
|
234
|
+
known_custom_evaluators: set[str],
|
|
235
|
+
actor_id: int,
|
|
236
|
+
detail: dict,
|
|
237
|
+
root: Path | None = None,
|
|
238
|
+
) -> ImportReport:
|
|
239
|
+
sourced_cases, overlays, findings = load_files(files)
|
|
240
|
+
entries, registry_findings = load_registries([p for p in files if p.name == REGISTRY_FILENAME])
|
|
241
|
+
sourced: list[tuple[Case, Path | None]] = list(sourced_cases)
|
|
242
|
+
return await import_cases(
|
|
243
|
+
catalog,
|
|
244
|
+
workspace=workspace,
|
|
245
|
+
dataset=dataset,
|
|
246
|
+
kind=DatasetKind.SUITE,
|
|
247
|
+
sourced_cases=sourced,
|
|
248
|
+
overlays=overlays,
|
|
249
|
+
findings=findings + registry_findings,
|
|
250
|
+
full=full,
|
|
251
|
+
known_custom_evaluators=known_custom_evaluators,
|
|
252
|
+
actor_id=actor_id,
|
|
253
|
+
source=SyncSource.GIT,
|
|
254
|
+
detail=detail,
|
|
255
|
+
entries=entries,
|
|
256
|
+
root=root,
|
|
257
|
+
)
|
evalkit/core/judge.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from pydantic import BaseModel, ValidationError
|
|
3
|
+
|
|
4
|
+
from evalkit.contracts.settings import JudgeSettings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class JudgeUsage(BaseModel):
|
|
8
|
+
input_tokens: int = 0
|
|
9
|
+
output_tokens: int = 0
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def judge_headers(session_id: str | None, evaluator_name: str) -> dict[str, str]:
|
|
13
|
+
headers = {"x-litellm-tags": f"eval-judge,evaluator:{evaluator_name}"}
|
|
14
|
+
if session_id:
|
|
15
|
+
headers["x-litellm-session-id"] = session_id
|
|
16
|
+
return headers
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def call_judge[T: BaseModel](
|
|
20
|
+
cfg: JudgeSettings,
|
|
21
|
+
*,
|
|
22
|
+
api_key: str | None,
|
|
23
|
+
system_prompt: str,
|
|
24
|
+
user_prompt: str,
|
|
25
|
+
output_type: type[T],
|
|
26
|
+
session_id: str | None = None,
|
|
27
|
+
evaluator_name: str,
|
|
28
|
+
model: str | None = None,
|
|
29
|
+
) -> tuple[T, JudgeUsage]:
|
|
30
|
+
usage = JudgeUsage()
|
|
31
|
+
messages = [
|
|
32
|
+
{"role": "system", "content": system_prompt},
|
|
33
|
+
{"role": "user", "content": user_prompt},
|
|
34
|
+
]
|
|
35
|
+
async with httpx.AsyncClient(
|
|
36
|
+
base_url=cfg.base_url,
|
|
37
|
+
headers={"Authorization": f"Bearer {api_key}"} if api_key else {},
|
|
38
|
+
timeout=300,
|
|
39
|
+
) as client:
|
|
40
|
+
for attempt in range(2):
|
|
41
|
+
response = await client.post(
|
|
42
|
+
"/chat/completions",
|
|
43
|
+
json={
|
|
44
|
+
"model": model or cfg.model,
|
|
45
|
+
"messages": messages,
|
|
46
|
+
"response_format": {
|
|
47
|
+
"type": "json_schema",
|
|
48
|
+
"json_schema": {
|
|
49
|
+
"name": output_type.__name__,
|
|
50
|
+
"schema": output_type.model_json_schema(),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
"max_tokens": cfg.max_tokens,
|
|
54
|
+
"reasoning_effort": cfg.reasoning_effort,
|
|
55
|
+
},
|
|
56
|
+
headers=judge_headers(session_id, evaluator_name),
|
|
57
|
+
)
|
|
58
|
+
response.raise_for_status()
|
|
59
|
+
body = response.json()
|
|
60
|
+
call_usage = body.get("usage") or {}
|
|
61
|
+
usage.input_tokens += call_usage.get("prompt_tokens") or 0
|
|
62
|
+
usage.output_tokens += call_usage.get("completion_tokens") or 0
|
|
63
|
+
content = body["choices"][0]["message"]["content"] or ""
|
|
64
|
+
try:
|
|
65
|
+
return output_type.model_validate_json(content), usage
|
|
66
|
+
except ValidationError as exc:
|
|
67
|
+
if attempt == 1:
|
|
68
|
+
raise
|
|
69
|
+
messages = messages + [
|
|
70
|
+
{"role": "assistant", "content": content},
|
|
71
|
+
{
|
|
72
|
+
"role": "user",
|
|
73
|
+
"content": "Your previous response was not valid JSON for the "
|
|
74
|
+
f"required schema: {exc.errors()[0]['msg']}. "
|
|
75
|
+
"Respond again with only the corrected JSON object.",
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
raise AssertionError("unreachable")
|
evalkit/core/lint.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Deterministic quality lint over validated eval cases — no LLM.
|
|
2
|
+
|
|
3
|
+
Severity is calibrated to confidence and blast radius: errors block sync and
|
|
4
|
+
fire only where the case is genuinely broken; warnings are heuristics that
|
|
5
|
+
never block. evalkit knows no client tools, so the jargon check looks for
|
|
6
|
+
tool-ish vocabulary patterns (mcp__ prefixes, double-underscore tokens,
|
|
7
|
+
snake_case call names) rather than a known-tool list.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Literal
|
|
16
|
+
|
|
17
|
+
from evalkit.contracts.cases import Case, FixtureEntry, Overlay
|
|
18
|
+
from evalkit.contracts.harness import input_errors
|
|
19
|
+
|
|
20
|
+
_DATE_ANCHOR = re.compile(
|
|
21
|
+
r"\d{4}-\d{2}-\d{2}"
|
|
22
|
+
r"|\d{1,2}[-/ ](?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*[-/ ]?\d{2,4}"
|
|
23
|
+
r"|\b(?:january|february|march|april|may|june|july|august|september|october"
|
|
24
|
+
r"|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+\d{4}"
|
|
25
|
+
r"|\b20\d{2}\b"
|
|
26
|
+
r"|\bFY\s?\d{2,4}\b"
|
|
27
|
+
r"|\bQ[1-4]\b"
|
|
28
|
+
r"|\bas of\b"
|
|
29
|
+
r"|\bas on\b"
|
|
30
|
+
r"|\bbetween\b.{1,40}\band\b",
|
|
31
|
+
re.IGNORECASE,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
_NUMERIC_VALUE = re.compile(
|
|
35
|
+
r"\d+\.\d+|\d+\s*%|[₹$€£]\s*\d|\b(?:million|billion|crore|crores|lakh|lakhs|bps)\b",
|
|
36
|
+
re.IGNORECASE,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
_DOUBLE_UNDERSCORE_TOKEN = re.compile(r"\b\w+__\w+\b")
|
|
40
|
+
_SNAKE_CALL = re.compile(r"\b[a-z][a-z0-9]+(?:_[a-z0-9]+)+\s*\(")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Finding:
|
|
45
|
+
severity: Literal["error", "warning"]
|
|
46
|
+
code: str
|
|
47
|
+
case_id: str
|
|
48
|
+
message: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _blackbox_texts(case: Case) -> list[tuple[str, str]]:
|
|
52
|
+
texts: list[tuple[str, str]] = [("input.question", str(case.input.get("question", "")))]
|
|
53
|
+
rubrics = case.evaluators.output.answer_rubrics
|
|
54
|
+
if rubrics is not None:
|
|
55
|
+
for r in rubrics.rubrics:
|
|
56
|
+
texts.append(("answer_rubrics.rubric", r.rubric))
|
|
57
|
+
tr = case.evaluators.trajectory.trajectory_rubrics
|
|
58
|
+
if tr is not None:
|
|
59
|
+
for rubric in tr.rubrics:
|
|
60
|
+
texts.append(("trajectory_rubrics.rubric", rubric))
|
|
61
|
+
pj = case.evaluators.output.presentation_judge
|
|
62
|
+
if pj is not None:
|
|
63
|
+
for pr in pj.rubrics:
|
|
64
|
+
texts.append(("presentation_judge.rubric", pr.rubric or pr.forbid or ""))
|
|
65
|
+
return texts
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _jargon_hits(text: str) -> set[str]:
|
|
69
|
+
hits: set[str] = set()
|
|
70
|
+
if "mcp__" in text.lower():
|
|
71
|
+
hits.add("mcp__ prefix")
|
|
72
|
+
hits.update(_DOUBLE_UNDERSCORE_TOKEN.findall(text))
|
|
73
|
+
hits.update(m.rstrip("( \t") for m in _SNAKE_CALL.findall(text))
|
|
74
|
+
return hits
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _e_jargon(case: Case) -> list[Finding]:
|
|
78
|
+
findings = []
|
|
79
|
+
for surface, text in _blackbox_texts(case):
|
|
80
|
+
hits = _jargon_hits(text)
|
|
81
|
+
if hits:
|
|
82
|
+
findings.append(
|
|
83
|
+
Finding(
|
|
84
|
+
severity="error",
|
|
85
|
+
code="E-JARGON",
|
|
86
|
+
case_id=case.id,
|
|
87
|
+
message=(
|
|
88
|
+
f"{surface} leaks agent internals ({', '.join(sorted(hits))}) "
|
|
89
|
+
"— black-box surfaces must stay free of tool/skill names; "
|
|
90
|
+
"move tool checks into tool_trajectory_match / required_tools_called "
|
|
91
|
+
"or a custom evaluator"
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
return findings
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _e_reserved_tags(case_id: str, tags: list[str]) -> list[Finding]:
|
|
99
|
+
return [
|
|
100
|
+
Finding(
|
|
101
|
+
severity="error",
|
|
102
|
+
code="E-RESERVED-TAG",
|
|
103
|
+
case_id=case_id,
|
|
104
|
+
message=(
|
|
105
|
+
f"tag '{t}' is reserved for run-time case selection "
|
|
106
|
+
"(the working split) — pick another tag name"
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
for t in tags
|
|
110
|
+
if t.startswith("working:")
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _w_custom_unknown(
|
|
115
|
+
case_id: str, custom: dict, known_custom_evaluators: set[str]
|
|
116
|
+
) -> list[Finding]:
|
|
117
|
+
return [
|
|
118
|
+
Finding(
|
|
119
|
+
severity="warning",
|
|
120
|
+
code="W-CUSTOM-UNKNOWN",
|
|
121
|
+
case_id=case_id,
|
|
122
|
+
message=(
|
|
123
|
+
f"custom evaluator '{name}' is not a remote evaluator here: create it with "
|
|
124
|
+
"evalkit evaluators create, or pass a Python evaluator with this name to "
|
|
125
|
+
"run_experiment"
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
for name in sorted(set(custom) - known_custom_evaluators)
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _w_undated(case: Case) -> list[Finding]:
|
|
133
|
+
rubrics = case.evaluators.output.answer_rubrics
|
|
134
|
+
if rubrics is None:
|
|
135
|
+
return []
|
|
136
|
+
rubric_text = " ".join(r.rubric for r in rubrics.rubrics)
|
|
137
|
+
if not _NUMERIC_VALUE.search(rubric_text):
|
|
138
|
+
return []
|
|
139
|
+
if _DATE_ANCHOR.search(str(case.input.get("question", "")) + " " + rubric_text):
|
|
140
|
+
return []
|
|
141
|
+
return [
|
|
142
|
+
Finding(
|
|
143
|
+
severity="warning",
|
|
144
|
+
code="W-UNDATED",
|
|
145
|
+
case_id=case.id,
|
|
146
|
+
message=(
|
|
147
|
+
"rubrics pin numeric values but neither the input nor the rubrics "
|
|
148
|
+
"carry a date anchor — pinned ground truth will drift"
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _w_thin_rubrics(case: Case) -> list[Finding]:
|
|
155
|
+
rubrics = case.evaluators.output.answer_rubrics
|
|
156
|
+
if rubrics is None or len(rubrics.rubrics) >= 2:
|
|
157
|
+
return []
|
|
158
|
+
return [
|
|
159
|
+
Finding(
|
|
160
|
+
severity="warning",
|
|
161
|
+
code="W-THIN-RUBRICS",
|
|
162
|
+
case_id=case.id,
|
|
163
|
+
message="answer_rubrics carries a single rubric — thin coverage for a whole case",
|
|
164
|
+
)
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def fixture_findings(
|
|
169
|
+
cases: list[Case], entries: dict[str, FixtureEntry], *, warn_unused: bool = True
|
|
170
|
+
) -> list[Finding]:
|
|
171
|
+
findings: list[Finding] = []
|
|
172
|
+
used: set[str] = set()
|
|
173
|
+
for case in cases:
|
|
174
|
+
for key in case.fixtures:
|
|
175
|
+
used.add(key)
|
|
176
|
+
if key not in entries:
|
|
177
|
+
findings.append(
|
|
178
|
+
Finding(
|
|
179
|
+
severity="error",
|
|
180
|
+
code="E-FIXTURE-UNKNOWN-KEY",
|
|
181
|
+
case_id=case.id,
|
|
182
|
+
message=f"fixture key {key!r} is not declared in fixtures.yaml",
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
for key in entries:
|
|
186
|
+
if warn_unused and key not in used:
|
|
187
|
+
findings.append(
|
|
188
|
+
Finding(
|
|
189
|
+
severity="warning",
|
|
190
|
+
code="W-FIXTURE-UNUSED",
|
|
191
|
+
case_id=key,
|
|
192
|
+
message=f"fixture {key!r} is declared but no case references it",
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
return findings
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def lint(
|
|
199
|
+
cases: list[Case],
|
|
200
|
+
overlays: list[Overlay],
|
|
201
|
+
*,
|
|
202
|
+
known_custom_evaluators: set[str],
|
|
203
|
+
other_cases: Mapping[str, str] | None = None,
|
|
204
|
+
input_schema: dict | None = None,
|
|
205
|
+
) -> list[Finding]:
|
|
206
|
+
findings: list[Finding] = []
|
|
207
|
+
|
|
208
|
+
seen: set[str] = set()
|
|
209
|
+
for case in cases:
|
|
210
|
+
if case.id in seen:
|
|
211
|
+
findings.append(
|
|
212
|
+
Finding(
|
|
213
|
+
severity="error",
|
|
214
|
+
code="E-DUP-ID",
|
|
215
|
+
case_id=case.id,
|
|
216
|
+
message="duplicate case id in this import",
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
seen.add(case.id)
|
|
220
|
+
seen_targets: set[str] = set()
|
|
221
|
+
for overlay in overlays:
|
|
222
|
+
if overlay.extends in seen_targets:
|
|
223
|
+
findings.append(
|
|
224
|
+
Finding(
|
|
225
|
+
severity="error",
|
|
226
|
+
code="E-DUP-ID",
|
|
227
|
+
case_id=overlay.extends,
|
|
228
|
+
message="multiple overlays extend the same case in this import",
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
seen_targets.add(overlay.extends)
|
|
232
|
+
|
|
233
|
+
for case in cases:
|
|
234
|
+
findings.extend(_e_jargon(case))
|
|
235
|
+
findings.extend(_e_reserved_tags(case.id, case.tags))
|
|
236
|
+
findings.extend(_w_custom_unknown(case.id, case.evaluators.custom, known_custom_evaluators))
|
|
237
|
+
findings.extend(_w_undated(case))
|
|
238
|
+
findings.extend(_w_thin_rubrics(case))
|
|
239
|
+
|
|
240
|
+
if input_schema is not None:
|
|
241
|
+
for case in cases:
|
|
242
|
+
for error in input_errors(input_schema, case.input):
|
|
243
|
+
findings.append(
|
|
244
|
+
Finding(severity="error", code="E-INPUT-SCHEMA", case_id=case.id, message=error)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
for overlay in overlays:
|
|
248
|
+
findings.extend(_e_reserved_tags(overlay.extends, overlay.tags))
|
|
249
|
+
findings.extend(
|
|
250
|
+
_w_custom_unknown(overlay.extends, overlay.evaluators.custom, known_custom_evaluators)
|
|
251
|
+
)
|
|
252
|
+
if other_cases is not None and overlay.extends not in other_cases:
|
|
253
|
+
findings.append(
|
|
254
|
+
Finding(
|
|
255
|
+
severity="error",
|
|
256
|
+
code="E-OVERLAY-TARGET",
|
|
257
|
+
case_id=overlay.extends,
|
|
258
|
+
message="overlay extends a case id no other dataset in this workspace holds",
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
return findings
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def has_errors(findings: list[Finding]) -> bool:
|
|
265
|
+
return any(f.severity == "error" for f in findings)
|
evalkit/core/overlays.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from evalkit.contracts.cases import Overlay
|
|
8
|
+
from evalkit.core.lint import Finding
|
|
9
|
+
from evalkit.store.base import IncomingOverlay
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OverlayBlock(BaseModel):
|
|
13
|
+
source: str
|
|
14
|
+
agents: list[str]
|
|
15
|
+
tags: list[str] = Field(default_factory=list)
|
|
16
|
+
evaluators: dict = Field(default_factory=dict)
|
|
17
|
+
source_file: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def overlay_evaluators(overlay: Overlay) -> dict:
|
|
21
|
+
families = {
|
|
22
|
+
"custom": overlay.evaluators.custom,
|
|
23
|
+
"trajectory": overlay.evaluators.trajectory.as_metadata(),
|
|
24
|
+
}
|
|
25
|
+
return {family: block for family, block in families.items() if block}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def incoming_overlay(overlay: Overlay, source_file: str | None) -> IncomingOverlay:
|
|
29
|
+
return IncomingOverlay(
|
|
30
|
+
target_case_id=overlay.extends,
|
|
31
|
+
agents=list(overlay.agents),
|
|
32
|
+
tags=list(overlay.tags),
|
|
33
|
+
evaluators=overlay_evaluators(overlay),
|
|
34
|
+
source_file=source_file,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def apply_overlay_blocks(evaluators: dict, blocks: list[OverlayBlock], agent: str | None) -> dict:
|
|
39
|
+
out = {family: dict(block) for family, block in evaluators.items()}
|
|
40
|
+
if not agent:
|
|
41
|
+
return out
|
|
42
|
+
for block in sorted(blocks, key=lambda b: b.source):
|
|
43
|
+
if agent.partition("@")[0] not in block.agents:
|
|
44
|
+
continue
|
|
45
|
+
for family, named in block.evaluators.items():
|
|
46
|
+
out[family] = {**out.get(family, {}), **named}
|
|
47
|
+
return out
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _names(evaluators: dict) -> set[tuple[str, str]]:
|
|
51
|
+
return {(family, name) for family, block in evaluators.items() for name in block}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def overlay_clashes(
|
|
55
|
+
overlays: list[Overlay], existing: Mapping[str, list[OverlayBlock]]
|
|
56
|
+
) -> list[Finding]:
|
|
57
|
+
findings: list[Finding] = []
|
|
58
|
+
for overlay in overlays:
|
|
59
|
+
ours = _names(overlay_evaluators(overlay))
|
|
60
|
+
for block in sorted(existing.get(overlay.extends, []), key=lambda b: b.source):
|
|
61
|
+
agents = sorted(set(overlay.agents) & set(block.agents))
|
|
62
|
+
names = sorted(ours & _names(block.evaluators))
|
|
63
|
+
if agents and names:
|
|
64
|
+
findings.append(
|
|
65
|
+
Finding(
|
|
66
|
+
severity="error",
|
|
67
|
+
code="E-OVERLAY-CLASH",
|
|
68
|
+
case_id=overlay.extends,
|
|
69
|
+
message=(
|
|
70
|
+
f"{block.source} already sets "
|
|
71
|
+
f"{', '.join(f'{family}.{name}' for family, name in names)} "
|
|
72
|
+
f"for agents {', '.join(agents)}"
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
return findings
|
evalkit/core/paths.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UnsafePathError(ValueError):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def is_safe_path(path: str) -> bool:
|
|
9
|
+
return all(segment not in ("", ".", "..") for segment in path.lstrip("/").split("/"))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def require_safe_path(path: str) -> str:
|
|
13
|
+
if not is_safe_path(path):
|
|
14
|
+
raise UnsafePathError(f"unsafe path {path!r}")
|
|
15
|
+
return path.lstrip("/")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def is_safe_run_id(run_id: str) -> bool:
|
|
19
|
+
return "/" not in run_id and is_safe_path(run_id)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def require_safe_run_id(run_id: str) -> str:
|
|
23
|
+
if not is_safe_run_id(run_id):
|
|
24
|
+
raise UnsafePathError(f"unsafe run id {run_id!r}")
|
|
25
|
+
return run_id
|