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/db/promotion.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import delete, func, select
|
|
7
|
+
from sqlalchemy.dialects.postgresql import insert
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
|
+
|
|
10
|
+
from evalkit.contracts.enums import SyncSource
|
|
11
|
+
from evalkit.core.access import WorkspaceRef
|
|
12
|
+
from evalkit.core.promotion import (
|
|
13
|
+
MissingTargetDataset,
|
|
14
|
+
OverlayCopy,
|
|
15
|
+
PromotionPlan,
|
|
16
|
+
twin_summary,
|
|
17
|
+
)
|
|
18
|
+
from evalkit.core.review import CaseEvent, CaseEventKind, CaseReview
|
|
19
|
+
from evalkit.db import models
|
|
20
|
+
from evalkit.db.cases import find_dataset_row
|
|
21
|
+
from evalkit.db.review_state import latest_events
|
|
22
|
+
from evalkit.db.workspaces import load_ref
|
|
23
|
+
from evalkit.store.base import CaseView, DatasetRow
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def overlays_extending(
|
|
27
|
+
session: AsyncSession, workspace_id: int, case_refs: list[int]
|
|
28
|
+
) -> dict[int, list[OverlayCopy]]:
|
|
29
|
+
if not case_refs:
|
|
30
|
+
return {}
|
|
31
|
+
rows = (
|
|
32
|
+
await session.execute(
|
|
33
|
+
select(models.Overlay, models.Dataset.name)
|
|
34
|
+
.join(models.Dataset, models.Dataset.id == models.Overlay.source_dataset_id)
|
|
35
|
+
.where(
|
|
36
|
+
models.Dataset.workspace_id == workspace_id,
|
|
37
|
+
models.Overlay.target_case.in_(case_refs),
|
|
38
|
+
)
|
|
39
|
+
.order_by(models.Dataset.name)
|
|
40
|
+
)
|
|
41
|
+
).all()
|
|
42
|
+
out: dict[int, list[OverlayCopy]] = {}
|
|
43
|
+
for overlay, source in rows:
|
|
44
|
+
out.setdefault(overlay.target_case, []).append(
|
|
45
|
+
OverlayCopy(
|
|
46
|
+
source_dataset=source,
|
|
47
|
+
agents=tuple(overlay.agents),
|
|
48
|
+
tags=tuple(overlay.tags),
|
|
49
|
+
evaluators=overlay.evaluators,
|
|
50
|
+
source_file=overlay.source_file,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
return out
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def _workspace(session: AsyncSession, workspace_id: int) -> WorkspaceRef:
|
|
57
|
+
return await load_ref(session, await session.get_one(models.Workspace, workspace_id))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def _username(session: AsyncSession, user_id: int | None) -> str | None:
|
|
61
|
+
if user_id is None:
|
|
62
|
+
return None
|
|
63
|
+
return await session.scalar(select(models.User.username).where(models.User.id == user_id))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def _placement(
|
|
67
|
+
session: AsyncSession, dataset_id: int, case_ref: int
|
|
68
|
+
) -> tuple[list[str], str | None]:
|
|
69
|
+
row = (
|
|
70
|
+
await session.execute(
|
|
71
|
+
select(models.DatasetCase.path_tags, models.DatasetCase.source).where(
|
|
72
|
+
models.DatasetCase.dataset_id == dataset_id,
|
|
73
|
+
models.DatasetCase.case_ref == case_ref,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
).one()
|
|
77
|
+
return list(row[0]), row[1]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def plan_promotion(
|
|
81
|
+
session: AsyncSession, source_ws: WorkspaceRef, dataset: DatasetRow, case: CaseView
|
|
82
|
+
) -> PromotionPlan:
|
|
83
|
+
assert source_ws.promotes_to is not None
|
|
84
|
+
target_ws = await load_ref(
|
|
85
|
+
session, await session.get_one(models.Workspace, source_ws.promotes_to)
|
|
86
|
+
)
|
|
87
|
+
target = await find_dataset_row(session, target_ws.name, dataset.name)
|
|
88
|
+
if target is None or target.kind != "suite":
|
|
89
|
+
raise MissingTargetDataset(f"{dataset.name}@{target_ws.name}")
|
|
90
|
+
overlays = (await overlays_extending(session, source_ws.id, [case.case_ref])).get(
|
|
91
|
+
case.case_ref, []
|
|
92
|
+
)
|
|
93
|
+
overlay_targets: dict[str, int] = {}
|
|
94
|
+
for name in sorted({o.source_dataset for o in overlays}):
|
|
95
|
+
found = await find_dataset_row(session, target_ws.name, name)
|
|
96
|
+
if found is None:
|
|
97
|
+
raise MissingTargetDataset(f"{name}@{target_ws.name}")
|
|
98
|
+
overlay_targets[name] = found.id
|
|
99
|
+
path_tags, source = await _placement(session, dataset.id, case.case_ref)
|
|
100
|
+
return PromotionPlan(
|
|
101
|
+
source_ref=dataset.ref,
|
|
102
|
+
target_ref=target.ref,
|
|
103
|
+
target_workspace_id=target_ws.id,
|
|
104
|
+
target_dataset_id=target.id,
|
|
105
|
+
case_ref=case.case_ref,
|
|
106
|
+
case_id=case.case_id,
|
|
107
|
+
content_hash=case.content_hash,
|
|
108
|
+
path_tags=path_tags,
|
|
109
|
+
source=source,
|
|
110
|
+
overlays=overlays,
|
|
111
|
+
overlay_targets=overlay_targets,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def execute_promotion(
|
|
116
|
+
session: AsyncSession, plan: PromotionPlan, *, actor_id: int, event_id: int
|
|
117
|
+
) -> int:
|
|
118
|
+
sync = models.DatasetSync(
|
|
119
|
+
dataset_id=plan.target_dataset_id,
|
|
120
|
+
actor_id=actor_id,
|
|
121
|
+
source=SyncSource.PROMOTION.value,
|
|
122
|
+
detail={
|
|
123
|
+
"from": plan.source_ref,
|
|
124
|
+
"case_id": plan.case_id,
|
|
125
|
+
"content_hash": plan.content_hash,
|
|
126
|
+
"event_id": event_id,
|
|
127
|
+
"overlays": len(plan.overlays),
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
session.add(sync)
|
|
131
|
+
await session.flush()
|
|
132
|
+
current = (
|
|
133
|
+
await session.execute(
|
|
134
|
+
select(models.DatasetCase.content_hash, models.DatasetCase.removed_at).where(
|
|
135
|
+
models.DatasetCase.dataset_id == plan.target_dataset_id,
|
|
136
|
+
models.DatasetCase.case_ref == plan.case_ref,
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
).one_or_none()
|
|
140
|
+
if current is None or current[0] != plan.content_hash or current[1] is not None:
|
|
141
|
+
statement = insert(models.DatasetCase).values(
|
|
142
|
+
dataset_id=plan.target_dataset_id,
|
|
143
|
+
case_ref=plan.case_ref,
|
|
144
|
+
content_hash=plan.content_hash,
|
|
145
|
+
path_tags=plan.path_tags,
|
|
146
|
+
source=plan.source,
|
|
147
|
+
removed_at=None,
|
|
148
|
+
)
|
|
149
|
+
await session.execute(
|
|
150
|
+
statement.on_conflict_do_update(
|
|
151
|
+
index_elements=["dataset_id", "case_ref"],
|
|
152
|
+
set_={
|
|
153
|
+
"content_hash": statement.excluded.content_hash,
|
|
154
|
+
"path_tags": statement.excluded.path_tags,
|
|
155
|
+
"source": statement.excluded.source,
|
|
156
|
+
"removed_at": None,
|
|
157
|
+
"updated_at": func.now(),
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
for kind, sync_id in (
|
|
162
|
+
(CaseEventKind.SYNCED, sync.id),
|
|
163
|
+
(CaseEventKind.APPROVED, None),
|
|
164
|
+
):
|
|
165
|
+
session.add(
|
|
166
|
+
models.CaseEvent(
|
|
167
|
+
dataset_id=plan.target_dataset_id,
|
|
168
|
+
case_ref=plan.case_ref,
|
|
169
|
+
content_hash=plan.content_hash,
|
|
170
|
+
kind=kind.value,
|
|
171
|
+
sync_id=sync_id,
|
|
172
|
+
actor_id=actor_id,
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
await session.flush()
|
|
176
|
+
await session.execute(
|
|
177
|
+
delete(models.Overlay).where(
|
|
178
|
+
models.Overlay.target_case == plan.case_ref,
|
|
179
|
+
models.Overlay.source_dataset_id.in_(
|
|
180
|
+
select(models.Dataset.id).where(
|
|
181
|
+
models.Dataset.workspace_id == plan.target_workspace_id
|
|
182
|
+
)
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
for overlay in plan.overlays:
|
|
187
|
+
session.add(
|
|
188
|
+
models.Overlay(
|
|
189
|
+
source_dataset_id=plan.overlay_targets[overlay.source_dataset],
|
|
190
|
+
target_case=plan.case_ref,
|
|
191
|
+
agents=list(overlay.agents),
|
|
192
|
+
tags=list(overlay.tags),
|
|
193
|
+
evaluators=overlay.evaluators,
|
|
194
|
+
source_file=overlay.source_file,
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
await session.flush()
|
|
198
|
+
return sync.id
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
async def _review_of(
|
|
202
|
+
session: AsyncSession, dataset: DatasetRow, case_ref: int, case_id: str, content_hash: str
|
|
203
|
+
) -> CaseReview:
|
|
204
|
+
events = await latest_events(session, dataset.id, {case_ref: content_hash})
|
|
205
|
+
review = CaseReview.unreviewed(dataset.ref, case_id, content_hash)
|
|
206
|
+
row = events.decisive.get(case_ref)
|
|
207
|
+
if row is not None:
|
|
208
|
+
actor = await _username(session, row.actor_id)
|
|
209
|
+
assert actor is not None
|
|
210
|
+
review.last_event = CaseEvent(
|
|
211
|
+
id=row.id,
|
|
212
|
+
dataset=dataset.ref,
|
|
213
|
+
case_id=case_id,
|
|
214
|
+
content_hash=row.content_hash,
|
|
215
|
+
kind=CaseEventKind(row.kind),
|
|
216
|
+
actor=actor,
|
|
217
|
+
target=await _username(session, row.target_id),
|
|
218
|
+
created_at=row.created_at,
|
|
219
|
+
)
|
|
220
|
+
review.requested_to = await _username(session, events.requested_to.get(case_ref))
|
|
221
|
+
return review
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
async def twin_status(
|
|
225
|
+
session: AsyncSession, ws: WorkspaceRef, dataset: DatasetRow, cases: list[CaseView]
|
|
226
|
+
) -> dict[str, dict[str, Any] | None]:
|
|
227
|
+
other_id = ws.promotes_to if ws.promotes_to is not None else ws.promoted_from
|
|
228
|
+
if other_id is None or dataset.kind != "suite" or not cases:
|
|
229
|
+
return {case.case_id: None for case in cases}
|
|
230
|
+
other_ws = await _workspace(session, other_id)
|
|
231
|
+
other = await find_dataset_row(session, other_ws.name, dataset.name)
|
|
232
|
+
refs = [case.case_ref for case in cases]
|
|
233
|
+
theirs: dict[int, tuple[str, datetime | None]] = {}
|
|
234
|
+
if other is not None:
|
|
235
|
+
theirs = {
|
|
236
|
+
ref: (content_hash, removed_at)
|
|
237
|
+
for ref, content_hash, removed_at in (
|
|
238
|
+
await session.execute(
|
|
239
|
+
select(
|
|
240
|
+
models.DatasetCase.case_ref,
|
|
241
|
+
models.DatasetCase.content_hash,
|
|
242
|
+
models.DatasetCase.removed_at,
|
|
243
|
+
).where(
|
|
244
|
+
models.DatasetCase.dataset_id == other.id,
|
|
245
|
+
models.DatasetCase.case_ref.in_(refs),
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
).all()
|
|
249
|
+
}
|
|
250
|
+
mine_overlays = await overlays_extending(session, ws.id, refs)
|
|
251
|
+
their_overlays = await overlays_extending(session, other_ws.id, refs)
|
|
252
|
+
out: dict[str, dict[str, Any] | None] = {}
|
|
253
|
+
for case in cases:
|
|
254
|
+
found = theirs.get(case.case_ref)
|
|
255
|
+
other_hash = found[0] if found is not None and found[1] is None else None
|
|
256
|
+
review = (
|
|
257
|
+
await _review_of(session, other, case.case_ref, case.case_id, other_hash)
|
|
258
|
+
if other is not None and other_hash is not None
|
|
259
|
+
else None
|
|
260
|
+
)
|
|
261
|
+
out[case.case_id] = twin_summary(
|
|
262
|
+
workspace=other_ws.name,
|
|
263
|
+
dataset=dataset.name,
|
|
264
|
+
case_id=case.case_id,
|
|
265
|
+
mine=case.content_hash,
|
|
266
|
+
other=other_hash,
|
|
267
|
+
review=review,
|
|
268
|
+
my_overlays=mine_overlays.get(case.case_ref, []),
|
|
269
|
+
other_overlays=their_overlays.get(case.case_ref, []),
|
|
270
|
+
)
|
|
271
|
+
return out
|
evalkit/db/rekey.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from sqlalchemy import func, select
|
|
2
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
|
3
|
+
|
|
4
|
+
from evalkit.db import models
|
|
5
|
+
from evalkit.db.crypto import SecretBox
|
|
6
|
+
|
|
7
|
+
type Sealed = models.TraceSource | models.Agent | models.RemoteEvaluator | models.LlmProvider
|
|
8
|
+
|
|
9
|
+
SEALED: tuple[type[Sealed], ...] = (
|
|
10
|
+
models.TraceSource,
|
|
11
|
+
models.Agent,
|
|
12
|
+
models.RemoteEvaluator,
|
|
13
|
+
models.LlmProvider,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def count_sealed(session_factory: async_sessionmaker[AsyncSession]) -> int:
|
|
18
|
+
count = 0
|
|
19
|
+
async with session_factory() as session:
|
|
20
|
+
for model in SEALED:
|
|
21
|
+
count += int(
|
|
22
|
+
await session.scalar(
|
|
23
|
+
select(func.count()).where(model.api_key_encrypted.is_not(None))
|
|
24
|
+
)
|
|
25
|
+
or 0
|
|
26
|
+
)
|
|
27
|
+
return count
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def _rekey[T: Sealed](
|
|
31
|
+
session: AsyncSession, model: type[T], old: SecretBox, new: SecretBox
|
|
32
|
+
) -> int:
|
|
33
|
+
rows = (await session.scalars(select(model).where(model.api_key_encrypted.is_not(None)))).all()
|
|
34
|
+
for row in rows:
|
|
35
|
+
assert row.api_key_encrypted is not None
|
|
36
|
+
row.api_key_encrypted = new.seal(old.open(row.api_key_encrypted))
|
|
37
|
+
return len(rows)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def rekey_all(
|
|
41
|
+
session_factory: async_sessionmaker[AsyncSession], old: SecretBox, new: SecretBox
|
|
42
|
+
) -> int:
|
|
43
|
+
async with session_factory() as session:
|
|
44
|
+
count = sum([await _rekey(session, model, old, new) for model in SEALED])
|
|
45
|
+
await session.commit()
|
|
46
|
+
return count
|
evalkit/db/review.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import select, update
|
|
6
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
|
7
|
+
|
|
8
|
+
from evalkit.core.review import (
|
|
9
|
+
CaseComment,
|
|
10
|
+
CaseEvent,
|
|
11
|
+
CaseEventKind,
|
|
12
|
+
CaseReview,
|
|
13
|
+
CaseVersion,
|
|
14
|
+
NewCaseComment,
|
|
15
|
+
NewCaseEvent,
|
|
16
|
+
)
|
|
17
|
+
from evalkit.core.workspace import Reference, slug
|
|
18
|
+
from evalkit.db import models
|
|
19
|
+
from evalkit.db.cases import dataset_by_ref
|
|
20
|
+
from evalkit.db.review_state import latest_events
|
|
21
|
+
|
|
22
|
+
AfterDecision = Callable[[AsyncSession, int], Awaitable[None]]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def _case_refs(session: AsyncSession, case_ids: list[str]) -> dict[str, int]:
|
|
26
|
+
rows = await session.execute(
|
|
27
|
+
select(models.Case.case_id, models.Case.id).where(models.Case.case_id.in_(case_ids))
|
|
28
|
+
)
|
|
29
|
+
return {case_id: ref for case_id, ref in rows.all()}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def _user_ids(session: AsyncSession, names: set[str]) -> dict[str, int]:
|
|
33
|
+
rows = await session.execute(
|
|
34
|
+
select(models.User.username, models.User.id).where(models.User.username.in_(names))
|
|
35
|
+
)
|
|
36
|
+
return {name: uid for name, uid in rows.all()}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def _usernames(session: AsyncSession, ids: set[int]) -> dict[int, str]:
|
|
40
|
+
rows = await session.execute(
|
|
41
|
+
select(models.User.id, models.User.username).where(models.User.id.in_(ids))
|
|
42
|
+
)
|
|
43
|
+
return {uid: name for uid, name in rows.all()}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ReviewLog:
|
|
47
|
+
def __init__(self, session_factory: async_sessionmaker[AsyncSession]):
|
|
48
|
+
self._session_factory = session_factory
|
|
49
|
+
|
|
50
|
+
async def _events(
|
|
51
|
+
self, session: AsyncSession, dataset: str, case_id: str, rows: list[models.CaseEvent]
|
|
52
|
+
) -> list[CaseEvent]:
|
|
53
|
+
users = {r.actor_id for r in rows} | {r.target_id for r in rows if r.target_id}
|
|
54
|
+
names = await _usernames(session, users)
|
|
55
|
+
return [
|
|
56
|
+
CaseEvent(
|
|
57
|
+
id=r.id,
|
|
58
|
+
dataset=dataset,
|
|
59
|
+
case_id=case_id,
|
|
60
|
+
content_hash=r.content_hash,
|
|
61
|
+
kind=CaseEventKind(r.kind),
|
|
62
|
+
actor=names[r.actor_id],
|
|
63
|
+
target=names[r.target_id] if r.target_id else None,
|
|
64
|
+
created_at=r.created_at,
|
|
65
|
+
)
|
|
66
|
+
for r in rows
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
async def _comment(
|
|
70
|
+
self, session: AsyncSession, row: models.CaseComment, dataset: str, case_id: str
|
|
71
|
+
) -> CaseComment:
|
|
72
|
+
names = await _usernames(session, {row.author_id})
|
|
73
|
+
return CaseComment(
|
|
74
|
+
id=row.id,
|
|
75
|
+
dataset=dataset,
|
|
76
|
+
case_id=case_id,
|
|
77
|
+
content_hash=row.content_hash,
|
|
78
|
+
parent_id=row.parent_id,
|
|
79
|
+
author=names[row.author_id],
|
|
80
|
+
body=row.body,
|
|
81
|
+
resolved=row.resolved,
|
|
82
|
+
created_at=row.created_at,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
async def load(self, dataset: str, versions: list[CaseVersion]) -> dict[str, CaseReview]:
|
|
86
|
+
if not versions:
|
|
87
|
+
return {}
|
|
88
|
+
reviews = {
|
|
89
|
+
v.case_id: CaseReview.unreviewed(dataset, v.case_id, v.content_hash) for v in versions
|
|
90
|
+
}
|
|
91
|
+
async with self._session_factory() as session:
|
|
92
|
+
dataset_id = (await dataset_by_ref(session, dataset)).id
|
|
93
|
+
refs = await _case_refs(session, [v.case_id for v in versions])
|
|
94
|
+
ids = {ref: case_id for case_id, ref in refs.items()}
|
|
95
|
+
latest = await latest_events(
|
|
96
|
+
session,
|
|
97
|
+
dataset_id,
|
|
98
|
+
{refs[v.case_id]: v.content_hash for v in versions if v.case_id in refs},
|
|
99
|
+
)
|
|
100
|
+
for ref, row in latest.decisive.items():
|
|
101
|
+
[event] = await self._events(session, dataset, ids[ref], [row])
|
|
102
|
+
reviews[ids[ref]].last_event = event
|
|
103
|
+
names = await _usernames(session, set(latest.requested_to.values()))
|
|
104
|
+
for ref, target in latest.requested_to.items():
|
|
105
|
+
reviews[ids[ref]].requested_to = names[target]
|
|
106
|
+
return reviews
|
|
107
|
+
|
|
108
|
+
async def latest_hashes(self, dataset: str, case_ids: list[str]) -> dict[str, str]:
|
|
109
|
+
if not case_ids:
|
|
110
|
+
return {}
|
|
111
|
+
async with self._session_factory() as session:
|
|
112
|
+
dataset_id = (await dataset_by_ref(session, dataset)).id
|
|
113
|
+
rows = await session.execute(
|
|
114
|
+
select(models.Case.case_id, models.DatasetCase.content_hash)
|
|
115
|
+
.join(models.Case, models.Case.id == models.DatasetCase.case_ref)
|
|
116
|
+
.where(
|
|
117
|
+
models.DatasetCase.dataset_id == dataset_id,
|
|
118
|
+
models.Case.case_id.in_(case_ids),
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
return {case_id: content_hash for case_id, content_hash in rows.all()}
|
|
122
|
+
|
|
123
|
+
async def _add_events(
|
|
124
|
+
self, session: AsyncSession, events: list[NewCaseEvent]
|
|
125
|
+
) -> list[models.CaseEvent]:
|
|
126
|
+
refs = await _case_refs(session, [e.case_id for e in events])
|
|
127
|
+
users = await _user_ids(
|
|
128
|
+
session, {e.actor for e in events} | {e.target for e in events if e.target}
|
|
129
|
+
)
|
|
130
|
+
rows: list[models.CaseEvent] = []
|
|
131
|
+
for event in events:
|
|
132
|
+
dataset_id = (await dataset_by_ref(session, event.dataset)).id
|
|
133
|
+
row = models.CaseEvent(
|
|
134
|
+
dataset_id=dataset_id,
|
|
135
|
+
case_ref=refs[event.case_id],
|
|
136
|
+
content_hash=event.content_hash,
|
|
137
|
+
kind=event.kind.value,
|
|
138
|
+
actor_id=users[event.actor],
|
|
139
|
+
target_id=users[event.target] if event.target else None,
|
|
140
|
+
)
|
|
141
|
+
session.add(row)
|
|
142
|
+
await session.flush()
|
|
143
|
+
rows.append(row)
|
|
144
|
+
return rows
|
|
145
|
+
|
|
146
|
+
async def _comment_row(
|
|
147
|
+
self, session: AsyncSession, comment: NewCaseComment
|
|
148
|
+
) -> models.CaseComment:
|
|
149
|
+
dataset_id = (await dataset_by_ref(session, comment.dataset)).id
|
|
150
|
+
refs = await _case_refs(session, [comment.case_id])
|
|
151
|
+
users = await _user_ids(session, {comment.author})
|
|
152
|
+
return models.CaseComment(
|
|
153
|
+
dataset_id=dataset_id,
|
|
154
|
+
case_ref=refs[comment.case_id],
|
|
155
|
+
content_hash=comment.content_hash,
|
|
156
|
+
parent_id=comment.parent_id,
|
|
157
|
+
author_id=users[comment.author],
|
|
158
|
+
body=comment.body,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
async def append(self, events: list[NewCaseEvent]) -> None:
|
|
162
|
+
if not events:
|
|
163
|
+
return
|
|
164
|
+
async with self._session_factory() as session:
|
|
165
|
+
await self._add_events(session, events)
|
|
166
|
+
await session.commit()
|
|
167
|
+
|
|
168
|
+
async def decide(
|
|
169
|
+
self,
|
|
170
|
+
event: NewCaseEvent,
|
|
171
|
+
comment: NewCaseComment | None,
|
|
172
|
+
after: AfterDecision | None = None,
|
|
173
|
+
) -> None:
|
|
174
|
+
async with self._session_factory() as session:
|
|
175
|
+
if comment is not None:
|
|
176
|
+
session.add(await self._comment_row(session, comment))
|
|
177
|
+
[row] = await self._add_events(session, [event])
|
|
178
|
+
if after is not None:
|
|
179
|
+
await after(session, row.id)
|
|
180
|
+
await session.commit()
|
|
181
|
+
|
|
182
|
+
async def history(self, dataset: str, case_id: str) -> list[CaseEvent]:
|
|
183
|
+
async with self._session_factory() as session:
|
|
184
|
+
dataset_id = (await dataset_by_ref(session, dataset)).id
|
|
185
|
+
rows = (
|
|
186
|
+
await session.scalars(
|
|
187
|
+
select(models.CaseEvent)
|
|
188
|
+
.join(models.Case, models.Case.id == models.CaseEvent.case_ref)
|
|
189
|
+
.where(
|
|
190
|
+
models.CaseEvent.dataset_id == dataset_id, models.Case.case_id == case_id
|
|
191
|
+
)
|
|
192
|
+
.order_by(models.CaseEvent.created_at.desc(), models.CaseEvent.id.desc())
|
|
193
|
+
)
|
|
194
|
+
).all()
|
|
195
|
+
return await self._events(session, dataset, case_id, list(rows))
|
|
196
|
+
|
|
197
|
+
async def comments(self, dataset: str, case_id: str) -> list[CaseComment]:
|
|
198
|
+
async with self._session_factory() as session:
|
|
199
|
+
dataset_id = (await dataset_by_ref(session, dataset)).id
|
|
200
|
+
rows = (
|
|
201
|
+
await session.scalars(
|
|
202
|
+
select(models.CaseComment)
|
|
203
|
+
.join(models.Case, models.Case.id == models.CaseComment.case_ref)
|
|
204
|
+
.where(
|
|
205
|
+
models.CaseComment.dataset_id == dataset_id,
|
|
206
|
+
models.Case.case_id == case_id,
|
|
207
|
+
)
|
|
208
|
+
.order_by(models.CaseComment.created_at.asc(), models.CaseComment.id.asc())
|
|
209
|
+
)
|
|
210
|
+
).all()
|
|
211
|
+
return [await self._comment(session, row, dataset, case_id) for row in rows]
|
|
212
|
+
|
|
213
|
+
async def add_comment(self, comment: NewCaseComment) -> CaseComment:
|
|
214
|
+
async with self._session_factory() as session:
|
|
215
|
+
row = await self._comment_row(session, comment)
|
|
216
|
+
session.add(row)
|
|
217
|
+
await session.commit()
|
|
218
|
+
await session.refresh(row)
|
|
219
|
+
return await self._comment(session, row, comment.dataset, comment.case_id)
|
|
220
|
+
|
|
221
|
+
async def _describe(self, session: AsyncSession, row: models.CaseComment) -> CaseComment:
|
|
222
|
+
name, workspace = (
|
|
223
|
+
await session.execute(
|
|
224
|
+
select(models.Dataset.name, models.Workspace.name)
|
|
225
|
+
.join(models.Workspace, models.Workspace.id == models.Dataset.workspace_id)
|
|
226
|
+
.where(models.Dataset.id == row.dataset_id)
|
|
227
|
+
)
|
|
228
|
+
).one()
|
|
229
|
+
case_id = await session.scalar(
|
|
230
|
+
select(models.Case.case_id).where(models.Case.id == row.case_ref)
|
|
231
|
+
)
|
|
232
|
+
assert case_id is not None
|
|
233
|
+
return await self._comment(session, row, slug(Reference(name, workspace)), case_id)
|
|
234
|
+
|
|
235
|
+
async def get_comment(self, comment_id: int) -> CaseComment | None:
|
|
236
|
+
async with self._session_factory() as session:
|
|
237
|
+
row = await session.get(models.CaseComment, comment_id)
|
|
238
|
+
return None if row is None else await self._describe(session, row)
|
|
239
|
+
|
|
240
|
+
async def set_resolved(self, comment_id: int, resolved: bool) -> CaseComment:
|
|
241
|
+
async with self._session_factory() as session:
|
|
242
|
+
await session.execute(
|
|
243
|
+
update(models.CaseComment)
|
|
244
|
+
.where(models.CaseComment.id == comment_id)
|
|
245
|
+
.values(resolved=resolved)
|
|
246
|
+
)
|
|
247
|
+
await session.commit()
|
|
248
|
+
row = await session.get(models.CaseComment, comment_id)
|
|
249
|
+
assert row is not None
|
|
250
|
+
return await self._describe(session, row)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import select, tuple_
|
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
5
|
+
|
|
6
|
+
from evalkit.core.review import DECISIVE_EVENT_KINDS, CaseEventKind
|
|
7
|
+
from evalkit.db import models
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class LatestEvents:
|
|
12
|
+
decisive: dict[int, models.CaseEvent] = field(default_factory=dict)
|
|
13
|
+
requested_to: dict[int, int] = field(default_factory=dict)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def _latest(
|
|
17
|
+
session: AsyncSession, dataset_id: int, versions: dict[int, str], kinds: list[str]
|
|
18
|
+
) -> dict[int, models.CaseEvent]:
|
|
19
|
+
rows = await session.scalars(
|
|
20
|
+
select(models.CaseEvent)
|
|
21
|
+
.where(
|
|
22
|
+
models.CaseEvent.dataset_id == dataset_id,
|
|
23
|
+
tuple_(models.CaseEvent.case_ref, models.CaseEvent.content_hash).in_(
|
|
24
|
+
list(versions.items())
|
|
25
|
+
),
|
|
26
|
+
models.CaseEvent.kind.in_(kinds),
|
|
27
|
+
)
|
|
28
|
+
.distinct(models.CaseEvent.case_ref)
|
|
29
|
+
.order_by(
|
|
30
|
+
models.CaseEvent.case_ref,
|
|
31
|
+
models.CaseEvent.created_at.desc(),
|
|
32
|
+
models.CaseEvent.id.desc(),
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
return {row.case_ref: row for row in rows.all()}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def latest_events(
|
|
39
|
+
session: AsyncSession, dataset_id: int, versions: dict[int, str]
|
|
40
|
+
) -> LatestEvents:
|
|
41
|
+
if not versions:
|
|
42
|
+
return LatestEvents()
|
|
43
|
+
decisive = await _latest(session, dataset_id, versions, [k.value for k in DECISIVE_EVENT_KINDS])
|
|
44
|
+
requests = await _latest(session, dataset_id, versions, [CaseEventKind.REVIEW_REQUESTED.value])
|
|
45
|
+
return LatestEvents(
|
|
46
|
+
decisive=decisive,
|
|
47
|
+
requested_to={
|
|
48
|
+
ref: event.target_id for ref, event in requests.items() if event.target_id is not None
|
|
49
|
+
},
|
|
50
|
+
)
|