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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from evalkit.core.traces.raw import RawSpan
|
|
10
|
+
from evalkit.core.traces.sources.base import TracePage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def flatten_attributes(nested: dict[str, Any], prefix: str = "") -> dict[str, Any]:
|
|
14
|
+
flat: dict[str, Any] = {}
|
|
15
|
+
for key, value in nested.items():
|
|
16
|
+
name = f"{prefix}{key}"
|
|
17
|
+
if isinstance(value, dict):
|
|
18
|
+
flat.update(flatten_attributes(value, f"{name}."))
|
|
19
|
+
elif isinstance(value, list) and value and all(isinstance(v, dict) for v in value):
|
|
20
|
+
for i, item in enumerate(value):
|
|
21
|
+
flat.update(flatten_attributes(item, f"{name}.{i}."))
|
|
22
|
+
else:
|
|
23
|
+
flat[name] = value
|
|
24
|
+
return flat
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _to_raw(span: dict[str, Any]) -> RawSpan:
|
|
28
|
+
context = span.get("context") or {}
|
|
29
|
+
end = span.get("end_time")
|
|
30
|
+
return RawSpan(
|
|
31
|
+
trace_id=str(context.get("trace_id") or span.get("trace_id")),
|
|
32
|
+
span_id=str(context.get("span_id") or span.get("span_id")),
|
|
33
|
+
parent_span_id=span.get("parent_id") or None,
|
|
34
|
+
name=str(span.get("name") or ""),
|
|
35
|
+
kind=str(span.get("span_kind") or "INTERNAL"),
|
|
36
|
+
start_time=datetime.fromisoformat(span["start_time"]),
|
|
37
|
+
end_time=datetime.fromisoformat(end) if end else None,
|
|
38
|
+
status_code=str(span.get("status_code") or "UNSET"),
|
|
39
|
+
status_message=span.get("status_message") or None,
|
|
40
|
+
attributes=flatten_attributes(span.get("attributes") or {}),
|
|
41
|
+
events=[
|
|
42
|
+
{
|
|
43
|
+
"name": e.get("name"),
|
|
44
|
+
"time": e.get("timestamp"),
|
|
45
|
+
"attributes": e.get("attributes") or {},
|
|
46
|
+
}
|
|
47
|
+
for e in span.get("events") or []
|
|
48
|
+
],
|
|
49
|
+
resource={},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
SPAN_PAGE = 1000
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class PhoenixClient:
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
url: str,
|
|
60
|
+
api_key: str | None,
|
|
61
|
+
page_traces: int,
|
|
62
|
+
*,
|
|
63
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
64
|
+
):
|
|
65
|
+
self._url = url.rstrip("/")
|
|
66
|
+
self._api_key = api_key
|
|
67
|
+
self._page_traces = page_traces
|
|
68
|
+
self._transport = transport
|
|
69
|
+
|
|
70
|
+
async def _pages(
|
|
71
|
+
self, path: str, params: dict[str, str | list[str]]
|
|
72
|
+
) -> AsyncIterator[list[dict[str, Any]]]:
|
|
73
|
+
cursor: str | None = None
|
|
74
|
+
async with httpx.AsyncClient(
|
|
75
|
+
base_url=self._url,
|
|
76
|
+
headers={"Authorization": f"Bearer {self._api_key}"} if self._api_key else {},
|
|
77
|
+
timeout=120,
|
|
78
|
+
transport=self._transport,
|
|
79
|
+
) as client:
|
|
80
|
+
while True:
|
|
81
|
+
resp = await client.get(
|
|
82
|
+
path, params={**params, **({"cursor": cursor} if cursor else {})}
|
|
83
|
+
)
|
|
84
|
+
resp.raise_for_status()
|
|
85
|
+
body = resp.json()
|
|
86
|
+
rows = body.get("data") or []
|
|
87
|
+
if rows:
|
|
88
|
+
yield rows
|
|
89
|
+
cursor = body.get("next_cursor")
|
|
90
|
+
if not cursor or not rows:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
def _traces(self, project: str, params: dict[str, str]) -> AsyncIterator[list[dict[str, Any]]]:
|
|
94
|
+
return self._pages(
|
|
95
|
+
f"/v1/projects/{project}/traces",
|
|
96
|
+
{"sort": "start_time", "order": "asc", "limit": str(self._page_traces), **params},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
async def trace_spans(self, project: str, trace_ids: list[str]) -> AsyncIterator[list[RawSpan]]:
|
|
100
|
+
params: dict[str, str | list[str]] = {"trace_id": trace_ids, "limit": str(SPAN_PAGE)}
|
|
101
|
+
async for spans in self._pages(f"/v1/projects/{project}/spans", params):
|
|
102
|
+
yield [_to_raw(span) for span in spans]
|
|
103
|
+
|
|
104
|
+
async def session_spans(self, project: str, session_id: str) -> list[RawSpan]:
|
|
105
|
+
found: list[RawSpan] = []
|
|
106
|
+
async for traces in self._traces(project, {"session_identifier": session_id}):
|
|
107
|
+
async for spans in self.trace_spans(project, [t["trace_id"] for t in traces]):
|
|
108
|
+
found.extend(spans)
|
|
109
|
+
return found
|
|
110
|
+
|
|
111
|
+
async def trace_pages(
|
|
112
|
+
self, project: str, since: datetime, until: datetime
|
|
113
|
+
) -> AsyncIterator[TracePage]:
|
|
114
|
+
window = {"start_time": since.isoformat(), "end_time": until.isoformat()}
|
|
115
|
+
async for traces in self._traces(project, window):
|
|
116
|
+
yield TracePage(
|
|
117
|
+
traces=[
|
|
118
|
+
(str(trace["trace_id"]), datetime.fromisoformat(trace["start_time"]))
|
|
119
|
+
for trace in traces
|
|
120
|
+
]
|
|
121
|
+
)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from evalkit.contracts.atif import ContentPart, ObservationResult, Step, ToolCall, Trajectory
|
|
6
|
+
from evalkit.core.costmap import cost_usd, resolve_rates
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _message_text(message: str | list[ContentPart] | None) -> str:
|
|
10
|
+
if message is None:
|
|
11
|
+
return ""
|
|
12
|
+
if isinstance(message, str):
|
|
13
|
+
return message
|
|
14
|
+
return " ".join(part.text for part in message if part.type == "text" and part.text)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _result_text(result: ObservationResult) -> str:
|
|
18
|
+
if result.content is None:
|
|
19
|
+
return ""
|
|
20
|
+
if isinstance(result.content, str):
|
|
21
|
+
return result.content
|
|
22
|
+
return " ".join(part.text for part in result.content if part.type == "text" and part.text)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TokenTotals(BaseModel):
|
|
26
|
+
input: int | None = None
|
|
27
|
+
output: int | None = None
|
|
28
|
+
cached: int | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TrajectoryMetrics(BaseModel):
|
|
32
|
+
agent: str
|
|
33
|
+
input_tokens: int | None = None
|
|
34
|
+
output_tokens: int | None = None
|
|
35
|
+
cached_tokens: int | None = None
|
|
36
|
+
cost_usd: float | None = None
|
|
37
|
+
total_tool_calls: int = 0
|
|
38
|
+
errored_tool_calls: int = 0
|
|
39
|
+
turns: int = 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RunMetrics(BaseModel):
|
|
43
|
+
duration_ms: int | None = None
|
|
44
|
+
input_tokens: int | None = None
|
|
45
|
+
output_tokens: int | None = None
|
|
46
|
+
cached_tokens: int | None = None
|
|
47
|
+
cost_usd: float | None = None
|
|
48
|
+
total_tool_calls: int = 0
|
|
49
|
+
errored_tool_calls: int = 0
|
|
50
|
+
subagents: int = 0
|
|
51
|
+
turns: int = 0
|
|
52
|
+
by_trajectory: dict[str, TrajectoryMetrics] = Field(default_factory=dict)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _add_reported[Num: (int, float)](total: Num | None, value: Num | None) -> Num | None:
|
|
56
|
+
if total is None:
|
|
57
|
+
return value
|
|
58
|
+
if value is None:
|
|
59
|
+
return total
|
|
60
|
+
return total + value
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _errored_results(step: Step) -> int:
|
|
64
|
+
if step.observation is None:
|
|
65
|
+
return 0
|
|
66
|
+
return sum(1 for r in step.observation.results if (r.extra or {}).get("is_error") is True)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _own_metrics(trajectory: Trajectory, agent: str) -> TrajectoryMetrics:
|
|
70
|
+
metrics = TrajectoryMetrics(agent=agent)
|
|
71
|
+
for step in trajectory.steps:
|
|
72
|
+
if step.source == "agent":
|
|
73
|
+
metrics.turns += 1
|
|
74
|
+
metrics.total_tool_calls += len(step.tool_calls or [])
|
|
75
|
+
metrics.errored_tool_calls += _errored_results(step)
|
|
76
|
+
if step.metrics is not None:
|
|
77
|
+
metrics.input_tokens = _add_reported(metrics.input_tokens, step.metrics.prompt_tokens)
|
|
78
|
+
metrics.output_tokens = _add_reported(
|
|
79
|
+
metrics.output_tokens, step.metrics.completion_tokens
|
|
80
|
+
)
|
|
81
|
+
metrics.cached_tokens = _add_reported(metrics.cached_tokens, step.metrics.cached_tokens)
|
|
82
|
+
metrics.cost_usd = _add_reported(metrics.cost_usd, step.metrics.cost_usd)
|
|
83
|
+
if metrics.cost_usd is not None:
|
|
84
|
+
metrics.cost_usd = round(metrics.cost_usd, 10)
|
|
85
|
+
return metrics
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TrajectoryView:
|
|
89
|
+
def __init__(self, trajectory: Trajectory):
|
|
90
|
+
self.trajectory = trajectory
|
|
91
|
+
|
|
92
|
+
def _all_steps(self) -> list[Step]:
|
|
93
|
+
steps = list(self.trajectory.steps)
|
|
94
|
+
for sub in self.trajectory.subagent_trajectories or []:
|
|
95
|
+
steps.extend(sub.steps)
|
|
96
|
+
return steps
|
|
97
|
+
|
|
98
|
+
def tool_calls(self) -> list[ToolCall]:
|
|
99
|
+
calls: list[ToolCall] = []
|
|
100
|
+
for step in self._all_steps():
|
|
101
|
+
calls.extend(step.tool_calls or [])
|
|
102
|
+
return calls
|
|
103
|
+
|
|
104
|
+
def errored_tool_call_count(self) -> int:
|
|
105
|
+
return sum(_errored_results(step) for step in self._all_steps())
|
|
106
|
+
|
|
107
|
+
def token_totals(self) -> TokenTotals:
|
|
108
|
+
final = self.trajectory.final_metrics
|
|
109
|
+
if final is not None and any(
|
|
110
|
+
v is not None
|
|
111
|
+
for v in (
|
|
112
|
+
final.total_prompt_tokens,
|
|
113
|
+
final.total_completion_tokens,
|
|
114
|
+
final.total_cached_tokens,
|
|
115
|
+
)
|
|
116
|
+
):
|
|
117
|
+
return TokenTotals(
|
|
118
|
+
input=final.total_prompt_tokens,
|
|
119
|
+
output=final.total_completion_tokens,
|
|
120
|
+
cached=final.total_cached_tokens,
|
|
121
|
+
)
|
|
122
|
+
totals = {"input": None, "output": None, "cached": None}
|
|
123
|
+
keys = {"input": "prompt_tokens", "output": "completion_tokens", "cached": "cached_tokens"}
|
|
124
|
+
for step in self._all_steps():
|
|
125
|
+
if step.metrics is None:
|
|
126
|
+
continue
|
|
127
|
+
for name, attr in keys.items():
|
|
128
|
+
value = getattr(step.metrics, attr)
|
|
129
|
+
if value is not None:
|
|
130
|
+
totals[name] = (totals[name] or 0) + value
|
|
131
|
+
return TokenTotals(**totals)
|
|
132
|
+
|
|
133
|
+
def turn_count(self) -> int:
|
|
134
|
+
return sum(1 for step in self.trajectory.steps if step.source == "agent")
|
|
135
|
+
|
|
136
|
+
def subagent_count(self) -> int:
|
|
137
|
+
return len(self.trajectory.subagent_trajectories or [])
|
|
138
|
+
|
|
139
|
+
def metrics_by_trajectory(self) -> dict[str, TrajectoryMetrics]:
|
|
140
|
+
main = self.trajectory
|
|
141
|
+
split = {"main": _own_metrics(main, main.agent.name if main.agent else "main")}
|
|
142
|
+
for i, sub in enumerate(self.trajectory.subagent_trajectories or []):
|
|
143
|
+
key = sub.trajectory_id or f"subagent_trajectories[{i}]"
|
|
144
|
+
split[key] = _own_metrics(sub, (sub.agent.name if sub.agent else None) or "subagent")
|
|
145
|
+
return split
|
|
146
|
+
|
|
147
|
+
def render_steps(self) -> list[str]:
|
|
148
|
+
lines: list[str] = []
|
|
149
|
+
for step in self._all_steps():
|
|
150
|
+
if step.reasoning_content:
|
|
151
|
+
lines.append(f"[reasoning] {step.reasoning_content}")
|
|
152
|
+
text = _message_text(step.message)
|
|
153
|
+
if text:
|
|
154
|
+
lines.append(f"[{step.source}] {text}")
|
|
155
|
+
results_by_call: dict[str, ObservationResult] = {}
|
|
156
|
+
if step.observation is not None:
|
|
157
|
+
for result in step.observation.results:
|
|
158
|
+
if result.source_call_id:
|
|
159
|
+
results_by_call[result.source_call_id] = result
|
|
160
|
+
for call in step.tool_calls or []:
|
|
161
|
+
args = json.dumps(call.arguments, ensure_ascii=False, default=str)
|
|
162
|
+
line = f"[tool] {call.function_name} args: {args}"
|
|
163
|
+
matched = results_by_call.get(call.tool_call_id)
|
|
164
|
+
if matched is not None:
|
|
165
|
+
line += f" result: {_result_text(matched)}"
|
|
166
|
+
if (matched.extra or {}).get("is_error") is True:
|
|
167
|
+
line += " [ERROR]"
|
|
168
|
+
lines.append(line)
|
|
169
|
+
return [f"{i}. {line}" for i, line in enumerate(lines, 1)]
|
|
170
|
+
|
|
171
|
+
def render(self) -> str:
|
|
172
|
+
numbered = "\n".join(self.render_steps()) or "(none)"
|
|
173
|
+
return f"Recorded steps (in order):\n{numbered}"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def trajectory_model(trajectory: Trajectory) -> str | None:
|
|
177
|
+
if trajectory.agent and trajectory.agent.model_name:
|
|
178
|
+
return trajectory.agent.model_name
|
|
179
|
+
for step in trajectory.steps:
|
|
180
|
+
if step.model_name:
|
|
181
|
+
return step.model_name
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _price_steps(steps: list[Step], cost_map: dict, default_model: str | None) -> None:
|
|
186
|
+
for step in steps:
|
|
187
|
+
metrics = step.metrics
|
|
188
|
+
# Only fill a gap: a step with token counts but no price. A zero from
|
|
189
|
+
# the harness counts as a gap too: harnesses that lack rates for a
|
|
190
|
+
# model (pi defaults them to 0) report 0.0 rather than omitting it.
|
|
191
|
+
if metrics is None or metrics.cost_usd or metrics.prompt_tokens is None:
|
|
192
|
+
continue
|
|
193
|
+
model = step.model_name or default_model
|
|
194
|
+
if model is None:
|
|
195
|
+
continue
|
|
196
|
+
rates = resolve_rates(cost_map, model)
|
|
197
|
+
if rates is None:
|
|
198
|
+
continue
|
|
199
|
+
metrics.cost_usd = cost_usd(
|
|
200
|
+
rates,
|
|
201
|
+
metrics.prompt_tokens,
|
|
202
|
+
metrics.completion_tokens or 0,
|
|
203
|
+
metrics.cached_tokens or 0,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def backfill_step_costs(trajectory: Trajectory, cost_map: dict, model: str | None = None) -> None:
|
|
208
|
+
# The run's model override is the fallback when the harness didn't record a
|
|
209
|
+
# model on the trajectory; a step's own model_name still wins.
|
|
210
|
+
default_model = model or trajectory_model(trajectory)
|
|
211
|
+
_price_steps(trajectory.steps, cost_map, default_model)
|
|
212
|
+
for sub in trajectory.subagent_trajectories or []:
|
|
213
|
+
_price_steps(sub.steps, cost_map, trajectory_model(sub) or default_model)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _sum_step_costs(view: TrajectoryView) -> float | None:
|
|
217
|
+
costs = [
|
|
218
|
+
step.metrics.cost_usd
|
|
219
|
+
for step in view._all_steps()
|
|
220
|
+
if step.metrics is not None and step.metrics.cost_usd is not None
|
|
221
|
+
]
|
|
222
|
+
return round(sum(costs), 10) if costs else None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def derive_metrics(view: TrajectoryView, wall_ms: int | None) -> RunMetrics:
|
|
226
|
+
totals = view.token_totals()
|
|
227
|
+
final = view.trajectory.final_metrics
|
|
228
|
+
# Prefer a harness-reported total; otherwise sum the per-step costs.
|
|
229
|
+
# backfill_step_costs prices any step the harness left unset before the
|
|
230
|
+
# trajectory is stored, so the gauge and the UI cost view read the same
|
|
231
|
+
# per-step numbers.
|
|
232
|
+
cost = (
|
|
233
|
+
final.total_cost_usd
|
|
234
|
+
if (final is not None and final.total_cost_usd)
|
|
235
|
+
else _sum_step_costs(view)
|
|
236
|
+
)
|
|
237
|
+
return RunMetrics(
|
|
238
|
+
duration_ms=wall_ms,
|
|
239
|
+
input_tokens=totals.input,
|
|
240
|
+
output_tokens=totals.output,
|
|
241
|
+
cached_tokens=totals.cached,
|
|
242
|
+
cost_usd=cost,
|
|
243
|
+
total_tool_calls=len(view.tool_calls()),
|
|
244
|
+
errored_tool_calls=view.errored_tool_call_count(),
|
|
245
|
+
subagents=view.subagent_count(),
|
|
246
|
+
turns=view.turn_count(),
|
|
247
|
+
by_trajectory=view.metrics_by_trajectory(),
|
|
248
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import NamedTuple
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Reference(NamedTuple):
|
|
7
|
+
name: str
|
|
8
|
+
workspace: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BareReference(ValueError):
|
|
12
|
+
def __init__(self, text: str):
|
|
13
|
+
super().__init__(f"use name@workspace, not {text!r}")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_reference(text: str, default_workspace: str) -> Reference:
|
|
17
|
+
name, sep, workspace = text.partition("@")
|
|
18
|
+
if not name or (sep and (not workspace or "@" in workspace)):
|
|
19
|
+
raise ValueError(f"malformed reference: {text!r}")
|
|
20
|
+
return Reference(name, workspace if sep else default_workspace)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_slug(value: str) -> Reference:
|
|
24
|
+
if "@" not in value:
|
|
25
|
+
raise BareReference(value)
|
|
26
|
+
return parse_reference(value, "")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def slug(ref: Reference) -> str:
|
|
30
|
+
return f"{ref.name}@{ref.workspace}"
|
evalkit/credentials.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Literal, Protocol
|
|
6
|
+
|
|
7
|
+
from dotenv import dotenv_values
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
|
9
|
+
|
|
10
|
+
Kind = Literal["agent", "evaluator", "llm_provider"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MissingCredential(ValueError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
ENV_REF = re.compile(r"^env:[A-Za-z_][A-Za-z0-9_]*$")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CredentialsConfig(BaseModel):
|
|
21
|
+
model_config = ConfigDict(extra="forbid", hide_input_in_errors=True)
|
|
22
|
+
|
|
23
|
+
agents: dict[str, str] = {}
|
|
24
|
+
evaluators: dict[str, str] = {}
|
|
25
|
+
llm_providers: dict[str, str] = {}
|
|
26
|
+
|
|
27
|
+
# TODO: remove in 0.5.0, two minor versions after 0.3.0 renamed credentials.judges
|
|
28
|
+
# to credentials.llm_providers.
|
|
29
|
+
@model_validator(mode="before")
|
|
30
|
+
@classmethod
|
|
31
|
+
def _judges_renamed(cls, data: object) -> object:
|
|
32
|
+
if isinstance(data, dict) and "judges" in data:
|
|
33
|
+
raise ValueError("credentials.judges is now credentials.llm_providers")
|
|
34
|
+
return data
|
|
35
|
+
|
|
36
|
+
@field_validator("agents", "evaluators", "llm_providers")
|
|
37
|
+
@classmethod
|
|
38
|
+
def _env_refs_only(cls, value: dict[str, str]) -> dict[str, str]:
|
|
39
|
+
bad = [k for k, v in value.items() if not ENV_REF.match(v)]
|
|
40
|
+
if bad:
|
|
41
|
+
raise ValueError(f"credentials must be env:NAME references (no defaults): {bad}")
|
|
42
|
+
return value
|
|
43
|
+
|
|
44
|
+
@field_validator("llm_providers")
|
|
45
|
+
@classmethod
|
|
46
|
+
def _provider_keys_are_urls_or_env_refs(cls, value: dict[str, str]) -> dict[str, str]:
|
|
47
|
+
bad = [k for k in value if k.startswith("env:") and not ENV_REF.match(k)]
|
|
48
|
+
if bad:
|
|
49
|
+
raise ValueError(f"a credentials.llm_providers key is a base URL or env:NAME: {bad}")
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class KeySource(Protocol):
|
|
54
|
+
def key_for(self, kind: Kind, ref: str, url: str | None = None) -> str | None: ...
|
|
55
|
+
|
|
56
|
+
def require_judge_key(self, base_url: str) -> str | None: ...
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CredentialRegistry:
|
|
60
|
+
def __init__(
|
|
61
|
+
self, cfg: CredentialsConfig, environ: Mapping[str, str], *, home: str | None = None
|
|
62
|
+
):
|
|
63
|
+
self._cfg = cfg
|
|
64
|
+
self._env = environ
|
|
65
|
+
self._home = home
|
|
66
|
+
|
|
67
|
+
def _table(self, kind: Kind) -> dict[str, str]:
|
|
68
|
+
if kind == "llm_provider":
|
|
69
|
+
return {
|
|
70
|
+
url: v
|
|
71
|
+
for k, v in self._cfg.llm_providers.items()
|
|
72
|
+
if (url := self._judge_url(k)) is not None
|
|
73
|
+
}
|
|
74
|
+
return {"agent": self._cfg.agents, "evaluator": self._cfg.evaluators}[kind]
|
|
75
|
+
|
|
76
|
+
def _judge_url(self, key: str) -> str | None:
|
|
77
|
+
if not key.startswith("env:"):
|
|
78
|
+
return key
|
|
79
|
+
return self._env.get(key.removeprefix("env:"))
|
|
80
|
+
|
|
81
|
+
def _unset_judge_keys(self) -> list[str]:
|
|
82
|
+
return [
|
|
83
|
+
k
|
|
84
|
+
for k in self._cfg.llm_providers
|
|
85
|
+
if k.startswith("env:") and self._judge_url(k) is None
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
def _entry(self, kind: Kind, ref: str) -> str | None:
|
|
89
|
+
table = self._table(kind)
|
|
90
|
+
if ref in table:
|
|
91
|
+
return table[ref]
|
|
92
|
+
name, _, workspace = ref.partition("@")
|
|
93
|
+
if kind != "llm_provider" and self._home is not None and workspace == self._home:
|
|
94
|
+
return table.get(name)
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def key_for(self, kind: Kind, ref: str, url: str | None = None) -> str | None:
|
|
98
|
+
entry = self._entry(kind, ref)
|
|
99
|
+
if entry is None:
|
|
100
|
+
return None
|
|
101
|
+
name = entry.removeprefix("env:")
|
|
102
|
+
if name not in self._env:
|
|
103
|
+
raise MissingCredential(f"credentials.{kind}s[{ref!r}] needs env var {name}")
|
|
104
|
+
return self._env[name]
|
|
105
|
+
|
|
106
|
+
def require_judge_key(self, base_url: str) -> str:
|
|
107
|
+
key = self.key_for("llm_provider", base_url)
|
|
108
|
+
if key is None:
|
|
109
|
+
unset = self._unset_judge_keys()
|
|
110
|
+
hint = f" (unset: {', '.join(k.removeprefix('env:') for k in unset)})" if unset else ""
|
|
111
|
+
raise MissingCredential(f"no credentials.llm_providers entry for {base_url}{hint}")
|
|
112
|
+
return key
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def load_dotenv_beside(config_path: Path) -> dict[str, str]:
|
|
116
|
+
file_values = {
|
|
117
|
+
k: v for k, v in dotenv_values(config_path.parent / ".env").items() if v is not None
|
|
118
|
+
}
|
|
119
|
+
return {**file_values, **os.environ}
|
evalkit/db/__init__.py
ADDED
|
File without changes
|