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/spans.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator, Mapping
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
from typing import Any, NamedTuple
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from sqlalchemy import (
|
|
10
|
+
BigInteger,
|
|
11
|
+
ColumnElement,
|
|
12
|
+
Text,
|
|
13
|
+
and_,
|
|
14
|
+
any_,
|
|
15
|
+
case,
|
|
16
|
+
cast,
|
|
17
|
+
func,
|
|
18
|
+
not_,
|
|
19
|
+
or_,
|
|
20
|
+
select,
|
|
21
|
+
type_coerce,
|
|
22
|
+
update,
|
|
23
|
+
)
|
|
24
|
+
from sqlalchemy.dialects.postgresql import ARRAY, insert
|
|
25
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
|
26
|
+
|
|
27
|
+
from evalkit.core.traces.mapping import SDK_ROOT_ATTRIBUTE, AttributeKeys
|
|
28
|
+
from evalkit.core.traces.raw import RawSpan
|
|
29
|
+
from evalkit.db import models
|
|
30
|
+
from evalkit.db.runs import RunSource, eval_run_ids
|
|
31
|
+
|
|
32
|
+
UPSERT_CHUNK = 1000
|
|
33
|
+
SPAN_KEY = ("agent_id", "trace_id", "span_id")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SessionSummary(BaseModel):
|
|
37
|
+
session_id: str | None
|
|
38
|
+
trace_id: str
|
|
39
|
+
title: str | None
|
|
40
|
+
created_at: datetime
|
|
41
|
+
span_count: int
|
|
42
|
+
sdk_root: dict[str, Any] | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _aware(value: datetime | None) -> datetime | None:
|
|
46
|
+
if value is None or value.tzinfo is not None:
|
|
47
|
+
return value
|
|
48
|
+
return value.replace(tzinfo=UTC)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
SPAN_COLUMNS = (
|
|
52
|
+
models.Span.trace_id,
|
|
53
|
+
models.Span.span_id,
|
|
54
|
+
models.Span.parent_span_id,
|
|
55
|
+
models.Span.name,
|
|
56
|
+
models.Span.kind,
|
|
57
|
+
models.Span.start_time,
|
|
58
|
+
models.Span.end_time,
|
|
59
|
+
models.Span.status_code,
|
|
60
|
+
models.Span.status_message,
|
|
61
|
+
models.Span.attributes,
|
|
62
|
+
models.Span.received_at,
|
|
63
|
+
)
|
|
64
|
+
HEAD_COLUMNS = tuple(c for c in SPAN_COLUMNS if c is not models.Span.attributes)
|
|
65
|
+
STREAM_BATCH = 4
|
|
66
|
+
SESSION_ORDER = (models.Span.start_time, models.Span.id)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _to_raw(row: Mapping[Any, Any]) -> RawSpan:
|
|
70
|
+
start = _aware(row["start_time"])
|
|
71
|
+
assert start is not None
|
|
72
|
+
return RawSpan(
|
|
73
|
+
trace_id=row["trace_id"],
|
|
74
|
+
span_id=row["span_id"],
|
|
75
|
+
parent_span_id=row["parent_span_id"],
|
|
76
|
+
name=row["name"],
|
|
77
|
+
kind=row["kind"],
|
|
78
|
+
start_time=start,
|
|
79
|
+
end_time=_aware(row["end_time"]),
|
|
80
|
+
status_code=row["status_code"],
|
|
81
|
+
status_message=row["status_message"],
|
|
82
|
+
attributes=row["attributes"],
|
|
83
|
+
events=row.get("events", []),
|
|
84
|
+
resource=row.get("resource", {}),
|
|
85
|
+
received_at=_aware(row["received_at"]),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class AttributeDrop(NamedTuple):
|
|
90
|
+
rows: list[int]
|
|
91
|
+
keys: AttributeKeys
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SessionRead:
|
|
95
|
+
def __init__(self, session: AsyncSession, where: ColumnElement[bool]):
|
|
96
|
+
self._session = session
|
|
97
|
+
self._where = where
|
|
98
|
+
|
|
99
|
+
async def probes(self, keys: set[str]) -> list[tuple[int, RawSpan]]:
|
|
100
|
+
ordered = sorted(keys)
|
|
101
|
+
values = [models.Span.attributes[key].label(f"probe_{i}") for i, key in enumerate(ordered)]
|
|
102
|
+
statement = (
|
|
103
|
+
select(models.Span.id, *HEAD_COLUMNS, *values)
|
|
104
|
+
.where(self._where)
|
|
105
|
+
.order_by(*SESSION_ORDER)
|
|
106
|
+
)
|
|
107
|
+
out: list[tuple[int, RawSpan]] = []
|
|
108
|
+
for row in (await self._session.execute(statement)).mappings():
|
|
109
|
+
found = {key: row[f"probe_{i}"] for i, key in enumerate(ordered)}
|
|
110
|
+
attributes = {key: value for key, value in found.items() if value is not None}
|
|
111
|
+
out.append((row["id"], _to_raw({**row, "attributes": attributes})))
|
|
112
|
+
return out
|
|
113
|
+
|
|
114
|
+
def _attributes(self, drop: AttributeDrop) -> ColumnElement[Any]:
|
|
115
|
+
attributes = models.Span.attributes.expression
|
|
116
|
+
if not drop.rows or not (drop.keys.keys or drop.keys.prefixes):
|
|
117
|
+
return attributes
|
|
118
|
+
each = func.jsonb_each(attributes).table_valued("key", "value")
|
|
119
|
+
rebuilt = func.jsonb_object_agg(each.c.key, each.c.value)
|
|
120
|
+
dropped = models.Span.id == any_(cast(drop.rows, ARRAY(BigInteger)))
|
|
121
|
+
removed = or_(
|
|
122
|
+
each.c.key.in_(sorted(drop.keys.keys)),
|
|
123
|
+
*[func.substr(each.c.key, 1, len(p)) == p for p in sorted(drop.keys.prefixes)],
|
|
124
|
+
)
|
|
125
|
+
kept = select(rebuilt).select_from(each).where(not_(removed)).scalar_subquery()
|
|
126
|
+
return type_coerce(case((dropped, kept), else_=attributes), models.Span.attributes.type)
|
|
127
|
+
|
|
128
|
+
async def attribute_bytes(self, drop: AttributeDrop) -> int:
|
|
129
|
+
size = func.octet_length(cast(self._attributes(drop), Text))
|
|
130
|
+
statement = select(func.coalesce(func.sum(size), 0)).where(self._where)
|
|
131
|
+
return int((await self._session.execute(statement)).scalar_one())
|
|
132
|
+
|
|
133
|
+
async def stream(self, drop: AttributeDrop) -> AsyncIterator[RawSpan]:
|
|
134
|
+
statement = (
|
|
135
|
+
select(*HEAD_COLUMNS, self._attributes(drop).label("attributes"))
|
|
136
|
+
.where(self._where)
|
|
137
|
+
.order_by(*SESSION_ORDER)
|
|
138
|
+
.execution_options(yield_per=STREAM_BATCH)
|
|
139
|
+
)
|
|
140
|
+
result = await self._session.stream(statement)
|
|
141
|
+
async for row in result.mappings():
|
|
142
|
+
yield _to_raw(row)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class SpanStore:
|
|
146
|
+
def __init__(self, session_factory: async_sessionmaker[AsyncSession]):
|
|
147
|
+
self._sessions = session_factory
|
|
148
|
+
|
|
149
|
+
async def upsert(
|
|
150
|
+
self, agent_id: int, spans: list[RawSpan], *, external_trace_source_id: int | None = None
|
|
151
|
+
) -> int:
|
|
152
|
+
if not spans:
|
|
153
|
+
return 0
|
|
154
|
+
roots: dict[str, str | None] = {
|
|
155
|
+
s.trace_id: s.session_id_hint for s in spans if s.parent_span_id is None
|
|
156
|
+
}
|
|
157
|
+
batch_roots = set(roots)
|
|
158
|
+
async with self._sessions() as session:
|
|
159
|
+
unknown = {s.trace_id for s in spans if s.trace_id not in roots}
|
|
160
|
+
if unknown:
|
|
161
|
+
stored = await session.execute(
|
|
162
|
+
select(models.Span.trace_id, models.Span.session_id).where(
|
|
163
|
+
models.Span.agent_id == agent_id,
|
|
164
|
+
models.Span.trace_id.in_(unknown),
|
|
165
|
+
models.Span.parent_span_id.is_(None),
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
roots.update({trace_id: session_id for trace_id, session_id in stored.all()})
|
|
169
|
+
now = datetime.now(UTC)
|
|
170
|
+
rows = {
|
|
171
|
+
(span.trace_id, span.span_id): dict(
|
|
172
|
+
agent_id=agent_id,
|
|
173
|
+
trace_id=span.trace_id,
|
|
174
|
+
span_id=span.span_id,
|
|
175
|
+
parent_span_id=span.parent_span_id,
|
|
176
|
+
session_id=roots.get(span.trace_id) or span.session_id_hint,
|
|
177
|
+
name=span.name,
|
|
178
|
+
kind=span.kind,
|
|
179
|
+
start_time=span.start_time,
|
|
180
|
+
end_time=span.end_time,
|
|
181
|
+
status_code=span.status_code,
|
|
182
|
+
status_message=span.status_message,
|
|
183
|
+
attributes=span.attributes,
|
|
184
|
+
events=span.events,
|
|
185
|
+
resource=span.resource,
|
|
186
|
+
received_at=now,
|
|
187
|
+
external_trace_source_id=external_trace_source_id,
|
|
188
|
+
)
|
|
189
|
+
for span in spans
|
|
190
|
+
}
|
|
191
|
+
values = list(rows.values())
|
|
192
|
+
for start in range(0, len(values), UPSERT_CHUNK):
|
|
193
|
+
statement = insert(models.Span).values(values[start : start + UPSERT_CHUNK])
|
|
194
|
+
await session.execute(
|
|
195
|
+
statement.on_conflict_do_update(
|
|
196
|
+
index_elements=list(SPAN_KEY),
|
|
197
|
+
set_={k: statement.excluded[k] for k in values[0] if k not in SPAN_KEY},
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
for trace_id in batch_roots:
|
|
201
|
+
session_id = roots[trace_id]
|
|
202
|
+
if session_id:
|
|
203
|
+
await session.execute(
|
|
204
|
+
update(models.Span)
|
|
205
|
+
.where(
|
|
206
|
+
models.Span.agent_id == agent_id,
|
|
207
|
+
models.Span.trace_id == trace_id,
|
|
208
|
+
or_(
|
|
209
|
+
models.Span.session_id.is_(None),
|
|
210
|
+
models.Span.session_id != session_id,
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
.values(session_id=session_id)
|
|
214
|
+
)
|
|
215
|
+
await session.commit()
|
|
216
|
+
return len(spans)
|
|
217
|
+
|
|
218
|
+
def _trace_ids_of(self, agent_id: int, session_id: str):
|
|
219
|
+
return select(models.Span.trace_id).where(
|
|
220
|
+
models.Span.agent_id == agent_id,
|
|
221
|
+
models.Span.session_id == session_id,
|
|
222
|
+
models.Span.parent_span_id.is_(None),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def _in_session(self, agent_id: int, session_id: str) -> ColumnElement[bool]:
|
|
226
|
+
return and_(
|
|
227
|
+
models.Span.agent_id == agent_id,
|
|
228
|
+
or_(
|
|
229
|
+
models.Span.session_id == session_id,
|
|
230
|
+
models.Span.trace_id == session_id,
|
|
231
|
+
models.Span.trace_id.in_(self._trace_ids_of(agent_id, session_id)),
|
|
232
|
+
),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
@asynccontextmanager
|
|
236
|
+
async def read_session(self, agent_id: int, session_id: str) -> AsyncIterator[SessionRead]:
|
|
237
|
+
async with self._sessions() as session:
|
|
238
|
+
await session.connection(execution_options={"isolation_level": "REPEATABLE READ"})
|
|
239
|
+
yield SessionRead(session, self._in_session(agent_id, session_id))
|
|
240
|
+
|
|
241
|
+
async def stream_session_spans(self, agent_id: int, session_id: str) -> AsyncIterator[RawSpan]:
|
|
242
|
+
statement = (
|
|
243
|
+
select(*SPAN_COLUMNS, models.Span.events, models.Span.resource)
|
|
244
|
+
.where(self._in_session(agent_id, session_id))
|
|
245
|
+
.order_by(*SESSION_ORDER)
|
|
246
|
+
.execution_options(yield_per=STREAM_BATCH)
|
|
247
|
+
)
|
|
248
|
+
async with self._sessions() as session:
|
|
249
|
+
result = await session.stream(statement)
|
|
250
|
+
async for row in result.mappings():
|
|
251
|
+
yield _to_raw(row)
|
|
252
|
+
|
|
253
|
+
async def session_spans(self, agent_id: int, session_id: str) -> list[RawSpan]:
|
|
254
|
+
return [span async for span in self.stream_session_spans(agent_id, session_id)]
|
|
255
|
+
|
|
256
|
+
async def list_sessions(
|
|
257
|
+
self,
|
|
258
|
+
agent_id: int,
|
|
259
|
+
*,
|
|
260
|
+
query: str | None,
|
|
261
|
+
limit: int,
|
|
262
|
+
offset: int,
|
|
263
|
+
since: datetime | None = None,
|
|
264
|
+
until: datetime | None = None,
|
|
265
|
+
linked: RunSource = "all",
|
|
266
|
+
) -> tuple[list[SessionSummary], int]:
|
|
267
|
+
where = [models.Span.agent_id == agent_id, models.Span.session_id.is_not(None)]
|
|
268
|
+
if query:
|
|
269
|
+
where.append(models.Span.session_id.startswith(query))
|
|
270
|
+
if linked == "eval":
|
|
271
|
+
where.append(models.Span.session_id.in_(eval_run_ids(agent_id)))
|
|
272
|
+
elif linked == "app":
|
|
273
|
+
where.append(models.Span.session_id.not_in(eval_run_ids(agent_id)))
|
|
274
|
+
newest = func.max(models.Span.start_time)
|
|
275
|
+
started = func.min(models.Span.start_time)
|
|
276
|
+
grouped = (
|
|
277
|
+
select(
|
|
278
|
+
models.Span.session_id,
|
|
279
|
+
started.label("started"),
|
|
280
|
+
newest.label("newest"),
|
|
281
|
+
func.count().label("n"),
|
|
282
|
+
)
|
|
283
|
+
.where(*where)
|
|
284
|
+
.group_by(models.Span.session_id)
|
|
285
|
+
)
|
|
286
|
+
if since is not None:
|
|
287
|
+
grouped = grouped.having(started >= since)
|
|
288
|
+
if until is not None:
|
|
289
|
+
grouped = grouped.having(started < until)
|
|
290
|
+
async with self._sessions() as session:
|
|
291
|
+
total = (
|
|
292
|
+
await session.execute(select(func.count()).select_from(grouped.subquery()))
|
|
293
|
+
).scalar_one()
|
|
294
|
+
page = (
|
|
295
|
+
await session.execute(grouped.order_by(newest.desc()).limit(limit).offset(offset))
|
|
296
|
+
).all()
|
|
297
|
+
ids = [row.session_id for row in page]
|
|
298
|
+
titles: dict[str, str] = {}
|
|
299
|
+
sdk_roots: dict[str, dict[str, Any]] = {}
|
|
300
|
+
traces: dict[str, str] = {}
|
|
301
|
+
if ids:
|
|
302
|
+
roots = await session.execute(
|
|
303
|
+
select(models.Span.session_id, models.Span.name, models.Span.attributes)
|
|
304
|
+
.where(
|
|
305
|
+
models.Span.agent_id == agent_id,
|
|
306
|
+
models.Span.session_id.in_(ids),
|
|
307
|
+
models.Span.parent_span_id.is_(None),
|
|
308
|
+
)
|
|
309
|
+
.order_by(models.Span.start_time)
|
|
310
|
+
)
|
|
311
|
+
for session_id, name, attributes in roots.all():
|
|
312
|
+
titles.setdefault(session_id, name)
|
|
313
|
+
if attributes.get(SDK_ROOT_ATTRIBUTE):
|
|
314
|
+
sdk_roots.setdefault(session_id, attributes)
|
|
315
|
+
first = await session.execute(
|
|
316
|
+
select(models.Span.session_id, models.Span.trace_id)
|
|
317
|
+
.where(models.Span.agent_id == agent_id, models.Span.session_id.in_(ids))
|
|
318
|
+
.order_by(models.Span.start_time)
|
|
319
|
+
)
|
|
320
|
+
for session_id, trace_id in first.all():
|
|
321
|
+
traces.setdefault(session_id, trace_id)
|
|
322
|
+
return [
|
|
323
|
+
SessionSummary(
|
|
324
|
+
session_id=row.session_id,
|
|
325
|
+
trace_id=traces.get(row.session_id, ""),
|
|
326
|
+
title=titles.get(row.session_id),
|
|
327
|
+
created_at=_aware(row.started) or row.started,
|
|
328
|
+
span_count=row.n,
|
|
329
|
+
sdk_root=sdk_roots.get(row.session_id),
|
|
330
|
+
)
|
|
331
|
+
for row in page
|
|
332
|
+
], total
|
|
333
|
+
|
|
334
|
+
async def list_unsessioned(
|
|
335
|
+
self, agent_id: int, *, limit: int, offset: int
|
|
336
|
+
) -> tuple[list[SessionSummary], int]:
|
|
337
|
+
where = [models.Span.agent_id == agent_id, models.Span.session_id.is_(None)]
|
|
338
|
+
started = func.min(models.Span.start_time)
|
|
339
|
+
grouped = (
|
|
340
|
+
select(models.Span.trace_id, started.label("started"), func.count().label("n"))
|
|
341
|
+
.where(*where)
|
|
342
|
+
.group_by(models.Span.trace_id)
|
|
343
|
+
)
|
|
344
|
+
async with self._sessions() as session:
|
|
345
|
+
total = (
|
|
346
|
+
await session.execute(select(func.count()).select_from(grouped.subquery()))
|
|
347
|
+
).scalar_one()
|
|
348
|
+
page = (
|
|
349
|
+
await session.execute(grouped.order_by(started.desc()).limit(limit).offset(offset))
|
|
350
|
+
).all()
|
|
351
|
+
return [
|
|
352
|
+
SessionSummary(
|
|
353
|
+
session_id=None,
|
|
354
|
+
trace_id=row.trace_id,
|
|
355
|
+
title=None,
|
|
356
|
+
created_at=_aware(row.started) or row.started,
|
|
357
|
+
span_count=row.n,
|
|
358
|
+
)
|
|
359
|
+
for row in page
|
|
360
|
+
], total
|
evalkit/db/store.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from sqlalchemy import select
|
|
2
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
|
3
|
+
|
|
4
|
+
from evalkit.contracts.enums import DatasetKind, ExperimentStatus, SyncSource
|
|
5
|
+
from evalkit.core.access import WorkspaceRef
|
|
6
|
+
from evalkit.core.overlays import OverlayBlock
|
|
7
|
+
from evalkit.db import cases, experiments, fixture_blobs, models, runs, scorecard
|
|
8
|
+
from evalkit.db.cases import sync_dataset
|
|
9
|
+
from evalkit.store.base import (
|
|
10
|
+
AgentRunRecord,
|
|
11
|
+
AggregateRows,
|
|
12
|
+
CaseRep,
|
|
13
|
+
CaseView,
|
|
14
|
+
DatasetRow,
|
|
15
|
+
EvaluationRow,
|
|
16
|
+
Experiment,
|
|
17
|
+
IncomingCase,
|
|
18
|
+
IncomingOverlay,
|
|
19
|
+
NewExperiment,
|
|
20
|
+
RunDetail,
|
|
21
|
+
RunForScoring,
|
|
22
|
+
RunRef,
|
|
23
|
+
Selection,
|
|
24
|
+
SyncResult,
|
|
25
|
+
select_cases,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PgStore:
|
|
30
|
+
def __init__(self, session_factory: async_sessionmaker[AsyncSession]):
|
|
31
|
+
self._sf = session_factory
|
|
32
|
+
|
|
33
|
+
async def aclose(self) -> None:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
async def dataset(self, ref: str) -> DatasetRow:
|
|
37
|
+
async with self._sf() as s:
|
|
38
|
+
return await cases.dataset_by_ref(s, ref)
|
|
39
|
+
|
|
40
|
+
async def cases_for(
|
|
41
|
+
self, dataset: str, selection: Selection, *, agent: str | None
|
|
42
|
+
) -> list[CaseView]:
|
|
43
|
+
async with self._sf() as s:
|
|
44
|
+
row = await cases.dataset_by_ref(s, dataset)
|
|
45
|
+
selected = select_cases(await cases.current_cases(s, row, agent=agent), selection)
|
|
46
|
+
return selected
|
|
47
|
+
|
|
48
|
+
async def create_experiment(self, req: NewExperiment) -> Experiment:
|
|
49
|
+
async with self._sf() as s:
|
|
50
|
+
exp = await experiments.create_experiment(s, req)
|
|
51
|
+
await s.commit()
|
|
52
|
+
return exp
|
|
53
|
+
|
|
54
|
+
async def get_experiment(self, exp_id: int) -> Experiment:
|
|
55
|
+
async with self._sf() as s:
|
|
56
|
+
return await experiments.get_experiment(s, exp_id)
|
|
57
|
+
|
|
58
|
+
async def pinned_cases(self, exp_id: int) -> list[CaseView]:
|
|
59
|
+
async with self._sf() as s:
|
|
60
|
+
return await experiments.pinned_cases(s, exp_id)
|
|
61
|
+
|
|
62
|
+
async def transition(
|
|
63
|
+
self, exp_id: int, to: ExperimentStatus, holder: str | None, error: str | None = None
|
|
64
|
+
) -> Experiment:
|
|
65
|
+
async with self._sf() as s:
|
|
66
|
+
exp = await experiments.transition(s, exp_id, to, holder, error)
|
|
67
|
+
await s.commit()
|
|
68
|
+
return exp
|
|
69
|
+
|
|
70
|
+
async def renew_lease(self, exp_id: int, holder: str) -> bool:
|
|
71
|
+
async with self._sf() as s:
|
|
72
|
+
renewed = await experiments.renew_lease(s, exp_id, holder)
|
|
73
|
+
await s.commit()
|
|
74
|
+
return renewed
|
|
75
|
+
|
|
76
|
+
async def set_model(self, exp_id: int, model: str) -> None:
|
|
77
|
+
async with self._sf() as s:
|
|
78
|
+
await experiments.set_model(s, exp_id, model)
|
|
79
|
+
await s.commit()
|
|
80
|
+
|
|
81
|
+
async def pending_runs(self, exp_id: int) -> list[CaseRep]:
|
|
82
|
+
async with self._sf() as s:
|
|
83
|
+
return await runs.pending_runs(s, exp_id)
|
|
84
|
+
|
|
85
|
+
async def harness_run_id_used(self, exp_id: int, harness_run_id: str) -> str | None:
|
|
86
|
+
async with self._sf() as s:
|
|
87
|
+
return await runs.harness_run_id_used(s, exp_id, harness_run_id)
|
|
88
|
+
|
|
89
|
+
async def record_run(
|
|
90
|
+
self,
|
|
91
|
+
exp_id: int,
|
|
92
|
+
case_id: str,
|
|
93
|
+
repetition: int,
|
|
94
|
+
run: AgentRunRecord,
|
|
95
|
+
*,
|
|
96
|
+
holder: str | None = None,
|
|
97
|
+
) -> RunRef:
|
|
98
|
+
async with self._sf() as s:
|
|
99
|
+
ref = await runs.record_run(s, exp_id, case_id, repetition, run, None, holder=holder)
|
|
100
|
+
await s.commit()
|
|
101
|
+
return ref
|
|
102
|
+
|
|
103
|
+
async def runs_to_score(self, exp_id: int, *, force: bool) -> list[RunForScoring]:
|
|
104
|
+
async with self._sf() as s:
|
|
105
|
+
return await runs.runs_to_score(s, exp_id, force=force)
|
|
106
|
+
|
|
107
|
+
async def publish_evaluations(
|
|
108
|
+
self,
|
|
109
|
+
experiment_run_id: int,
|
|
110
|
+
rows: list[EvaluationRow],
|
|
111
|
+
*,
|
|
112
|
+
force: bool,
|
|
113
|
+
holder: str | None = None,
|
|
114
|
+
) -> None:
|
|
115
|
+
async with self._sf() as s:
|
|
116
|
+
await runs.publish_evaluations(s, experiment_run_id, rows, force=force, holder=holder)
|
|
117
|
+
await s.commit()
|
|
118
|
+
|
|
119
|
+
async def aggregate(self, exp_id: int) -> AggregateRows:
|
|
120
|
+
async with self._sf() as s:
|
|
121
|
+
return await scorecard.aggregate(s, exp_id)
|
|
122
|
+
|
|
123
|
+
async def run_details(self, exp_id: int) -> list[RunDetail]:
|
|
124
|
+
async with self._sf() as s:
|
|
125
|
+
return await runs.run_details(s, exp_id)
|
|
126
|
+
|
|
127
|
+
async def _dataset_id(self, s: AsyncSession, workspace: WorkspaceRef, name: str) -> int | None:
|
|
128
|
+
return await s.scalar(
|
|
129
|
+
select(models.Dataset.id).where(
|
|
130
|
+
models.Dataset.workspace_id == workspace.id, models.Dataset.name == name
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def workspace_cases(
|
|
135
|
+
self, workspace: WorkspaceRef, *, exclude_dataset: str | None
|
|
136
|
+
) -> dict[str, str]:
|
|
137
|
+
async with self._sf() as s:
|
|
138
|
+
excluded = (
|
|
139
|
+
await self._dataset_id(s, workspace, exclude_dataset)
|
|
140
|
+
if exclude_dataset is not None
|
|
141
|
+
else None
|
|
142
|
+
)
|
|
143
|
+
return await cases.workspace_case_index(s, workspace.id, exclude_dataset=excluded)
|
|
144
|
+
|
|
145
|
+
async def overlays_on(
|
|
146
|
+
self, workspace: WorkspaceRef, case_ids: list[str], *, exclude_dataset: str
|
|
147
|
+
) -> dict[str, list[OverlayBlock]]:
|
|
148
|
+
if not case_ids:
|
|
149
|
+
return {}
|
|
150
|
+
async with self._sf() as s:
|
|
151
|
+
rows = await s.execute(
|
|
152
|
+
select(models.Overlay, models.Dataset.name, models.Case.case_id)
|
|
153
|
+
.join(models.Dataset, models.Dataset.id == models.Overlay.source_dataset_id)
|
|
154
|
+
.join(models.Case, models.Case.id == models.Overlay.target_case)
|
|
155
|
+
.where(
|
|
156
|
+
models.Dataset.workspace_id == workspace.id,
|
|
157
|
+
models.Dataset.name != exclude_dataset,
|
|
158
|
+
models.Case.case_id.in_(case_ids),
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
out: dict[str, list[OverlayBlock]] = {}
|
|
162
|
+
for overlay, source, case_id in rows.all():
|
|
163
|
+
out.setdefault(case_id, []).append(
|
|
164
|
+
OverlayBlock(
|
|
165
|
+
source=source,
|
|
166
|
+
agents=list(overlay.agents),
|
|
167
|
+
tags=list(overlay.tags),
|
|
168
|
+
evaluators=overlay.evaluators,
|
|
169
|
+
source_file=overlay.source_file,
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
return out
|
|
173
|
+
|
|
174
|
+
async def has_dataset(self, workspace: WorkspaceRef, name: str) -> bool:
|
|
175
|
+
async with self._sf() as s:
|
|
176
|
+
return await self._dataset_id(s, workspace, name) is not None
|
|
177
|
+
|
|
178
|
+
async def known_blobs(self, shas: list[str]) -> set[str]:
|
|
179
|
+
async with self._sf() as s:
|
|
180
|
+
return await fixture_blobs.known_blobs(s, shas)
|
|
181
|
+
|
|
182
|
+
async def sync(
|
|
183
|
+
self,
|
|
184
|
+
*,
|
|
185
|
+
workspace: WorkspaceRef,
|
|
186
|
+
dataset: str,
|
|
187
|
+
kind: DatasetKind,
|
|
188
|
+
incoming: list[IncomingCase],
|
|
189
|
+
overlays: list[IncomingOverlay],
|
|
190
|
+
full: bool,
|
|
191
|
+
actor_id: int,
|
|
192
|
+
source: SyncSource,
|
|
193
|
+
detail: dict,
|
|
194
|
+
) -> SyncResult:
|
|
195
|
+
async with self._sf() as s:
|
|
196
|
+
result = await sync_dataset(
|
|
197
|
+
s,
|
|
198
|
+
workspace=workspace,
|
|
199
|
+
dataset=dataset,
|
|
200
|
+
kind=kind,
|
|
201
|
+
cases=incoming,
|
|
202
|
+
overlays=overlays,
|
|
203
|
+
full=full,
|
|
204
|
+
actor_id=actor_id,
|
|
205
|
+
source=source,
|
|
206
|
+
detail=detail,
|
|
207
|
+
)
|
|
208
|
+
await s.commit()
|
|
209
|
+
return result
|
evalkit/db/tokens.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import secrets
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from sqlalchemy import CursorResult, delete, select
|
|
10
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
|
11
|
+
|
|
12
|
+
from evalkit.contracts.enums import TokenKind
|
|
13
|
+
from evalkit.core.workspace import Reference, slug
|
|
14
|
+
from evalkit.db import models
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def token_hash(token: str) -> str:
|
|
18
|
+
return hashlib.sha256(token.encode()).hexdigest()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AgentTokenRecord(BaseModel):
|
|
22
|
+
id: int
|
|
23
|
+
agent: str
|
|
24
|
+
created_by: str
|
|
25
|
+
created_at: datetime
|
|
26
|
+
last_used_at: datetime | None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AgentTokens:
|
|
30
|
+
def __init__(self, session_factory: async_sessionmaker[AsyncSession]):
|
|
31
|
+
self._sessions = session_factory
|
|
32
|
+
|
|
33
|
+
async def create(self, *, agent_id: int, user_id: int) -> tuple[str, AgentTokenRecord]:
|
|
34
|
+
token = secrets.token_urlsafe(32)
|
|
35
|
+
async with self._sessions() as session:
|
|
36
|
+
row = models.AuthToken(
|
|
37
|
+
user_id=user_id,
|
|
38
|
+
token_hash=token_hash(token),
|
|
39
|
+
expires_at=None,
|
|
40
|
+
kind=TokenKind.AGENT,
|
|
41
|
+
agent_id=agent_id,
|
|
42
|
+
)
|
|
43
|
+
session.add(row)
|
|
44
|
+
await session.commit()
|
|
45
|
+
await session.refresh(row)
|
|
46
|
+
records = await self._records(session, [row])
|
|
47
|
+
return token, records[0]
|
|
48
|
+
|
|
49
|
+
async def _records(
|
|
50
|
+
self, session: AsyncSession, rows: list[models.AuthToken]
|
|
51
|
+
) -> list[AgentTokenRecord]:
|
|
52
|
+
out: list[AgentTokenRecord] = []
|
|
53
|
+
for row in rows:
|
|
54
|
+
name, workspace = (
|
|
55
|
+
await session.execute(
|
|
56
|
+
select(models.Agent.name, models.Workspace.name)
|
|
57
|
+
.join(models.Workspace, models.Workspace.id == models.Agent.workspace_id)
|
|
58
|
+
.where(models.Agent.id == row.agent_id)
|
|
59
|
+
)
|
|
60
|
+
).one()
|
|
61
|
+
user = (
|
|
62
|
+
await session.execute(select(models.User).where(models.User.id == row.user_id))
|
|
63
|
+
).scalar_one()
|
|
64
|
+
out.append(
|
|
65
|
+
AgentTokenRecord(
|
|
66
|
+
id=row.id,
|
|
67
|
+
agent=slug(Reference(name, workspace)),
|
|
68
|
+
created_by=user.username,
|
|
69
|
+
created_at=row.created_at,
|
|
70
|
+
last_used_at=row.last_used_at,
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
return out
|
|
74
|
+
|
|
75
|
+
async def list(self, *, user_id: int | None) -> list[AgentTokenRecord]:
|
|
76
|
+
where = [models.AuthToken.kind == TokenKind.AGENT]
|
|
77
|
+
if user_id is not None:
|
|
78
|
+
where.append(models.AuthToken.user_id == user_id)
|
|
79
|
+
async with self._sessions() as session:
|
|
80
|
+
rows = (
|
|
81
|
+
(
|
|
82
|
+
await session.execute(
|
|
83
|
+
select(models.AuthToken).where(*where).order_by(models.AuthToken.id)
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
.scalars()
|
|
87
|
+
.all()
|
|
88
|
+
)
|
|
89
|
+
return await self._records(session, list(rows))
|
|
90
|
+
|
|
91
|
+
async def revoke(self, token_id: int, *, user_id: int | None) -> bool:
|
|
92
|
+
where = [models.AuthToken.id == token_id, models.AuthToken.kind == TokenKind.AGENT]
|
|
93
|
+
if user_id is not None:
|
|
94
|
+
where.append(models.AuthToken.user_id == user_id)
|
|
95
|
+
async with self._sessions() as session:
|
|
96
|
+
result = cast(
|
|
97
|
+
CursorResult, await session.execute(delete(models.AuthToken).where(*where))
|
|
98
|
+
)
|
|
99
|
+
await session.commit()
|
|
100
|
+
return bool(result.rowcount)
|