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,173 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, HTTPException, Request
|
|
5
|
+
from pydantic import AwareDatetime, BaseModel, model_validator
|
|
6
|
+
|
|
7
|
+
from evalkit.api.access import ActorDep
|
|
8
|
+
from evalkit.api.deps import AgentStoreDep, HolderDep, MappingStoreDep, SessionDep
|
|
9
|
+
from evalkit.api.experiments import task_holder
|
|
10
|
+
from evalkit.api.traces import agent_or_404, require_otel, require_write
|
|
11
|
+
from evalkit.contracts.enums import TraceImportStatus
|
|
12
|
+
from evalkit.contracts.esf import MappingDocument
|
|
13
|
+
from evalkit.core.agents import AgentDefinition
|
|
14
|
+
from evalkit.core.runner import error_text
|
|
15
|
+
from evalkit.core.traces.importer import IMPORT_STOPPED_ERROR, guarded_import
|
|
16
|
+
from evalkit.core.traces.sources import client_for
|
|
17
|
+
from evalkit.db.mappings import MappingStore
|
|
18
|
+
from evalkit.db.trace_imports import TraceImportStore, TraceImportView
|
|
19
|
+
from evalkit.db.trace_sources import ResolvedTraceSource
|
|
20
|
+
|
|
21
|
+
router = APIRouter(prefix="/api/agents", tags=["traces"])
|
|
22
|
+
LOGGER = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
ImportTasks = dict[int, tuple[str, asyncio.Task]]
|
|
25
|
+
NO_SESSION_MAPPING = (
|
|
26
|
+
"activate a mapping with session_id before importing a time range; "
|
|
27
|
+
"import single sessions to try mappings first"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RangeImportRequest(BaseModel):
|
|
32
|
+
source: str
|
|
33
|
+
project: str
|
|
34
|
+
since: AwareDatetime
|
|
35
|
+
until: AwareDatetime
|
|
36
|
+
|
|
37
|
+
@model_validator(mode="after")
|
|
38
|
+
def ordered(self) -> "RangeImportRequest":
|
|
39
|
+
if self.since >= self.until:
|
|
40
|
+
raise ValueError("since must be before until")
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _imports(request: Request) -> TraceImportStore:
|
|
45
|
+
return request.app.state.trace_imports
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def _session_mapping(mappings: MappingStore, agent: AgentDefinition) -> MappingDocument:
|
|
49
|
+
active = await mappings.active(agent.id)
|
|
50
|
+
document = MappingDocument.model_validate(active.document) if active else None
|
|
51
|
+
if document is None or document.session_id is None:
|
|
52
|
+
raise HTTPException(status_code=409, detail=NO_SESSION_MAPPING)
|
|
53
|
+
return document
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _log_failure(task: asyncio.Task) -> None:
|
|
57
|
+
if task.cancelled():
|
|
58
|
+
return
|
|
59
|
+
exc = task.exception()
|
|
60
|
+
if exc is not None:
|
|
61
|
+
LOGGER.error("trace import task %s failed: %s", task.get_name(), error_text(exc))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _launch(
|
|
65
|
+
request: Request,
|
|
66
|
+
import_id: int,
|
|
67
|
+
holder: str,
|
|
68
|
+
source: ResolvedTraceSource,
|
|
69
|
+
mapping: MappingDocument,
|
|
70
|
+
) -> None:
|
|
71
|
+
client = client_for(
|
|
72
|
+
source.provider,
|
|
73
|
+
source.url,
|
|
74
|
+
source.api_key.get_secret_value() if source.api_key else None,
|
|
75
|
+
request.app.state.cfg.otel.import_page_traces,
|
|
76
|
+
)
|
|
77
|
+
store = _imports(request)
|
|
78
|
+
task = asyncio.create_task(
|
|
79
|
+
guarded_import(import_id, holder, client, store, request.app.state.spans, mapping),
|
|
80
|
+
name=f"import:{import_id}",
|
|
81
|
+
)
|
|
82
|
+
registry: ImportTasks = request.app.state.import_tasks
|
|
83
|
+
registry[import_id] = (holder, task)
|
|
84
|
+
task.add_done_callback(lambda t: registry.pop(import_id, None))
|
|
85
|
+
task.add_done_callback(_log_failure)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def interrupt_imports(store: TraceImportStore, registry: ImportTasks) -> None:
|
|
89
|
+
tracked = list(registry.items())
|
|
90
|
+
for _, (_, task) in tracked:
|
|
91
|
+
task.cancel()
|
|
92
|
+
await asyncio.gather(*(task for _, (_, task) in tracked), return_exceptions=True)
|
|
93
|
+
for import_id, (holder, task) in tracked:
|
|
94
|
+
if task.cancelled():
|
|
95
|
+
await store.finish(import_id, holder, TraceImportStatus.FAILED, IMPORT_STOPPED_ERROR)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@router.post("/{agent}/imports", status_code=202)
|
|
99
|
+
async def start_import(
|
|
100
|
+
agent: str,
|
|
101
|
+
body: RangeImportRequest,
|
|
102
|
+
request: Request,
|
|
103
|
+
agents: AgentStoreDep,
|
|
104
|
+
mappings: MappingStoreDep,
|
|
105
|
+
session: SessionDep,
|
|
106
|
+
actor: ActorDep,
|
|
107
|
+
pod: HolderDep,
|
|
108
|
+
) -> TraceImportView:
|
|
109
|
+
_, record = await agent_or_404(agents, agent)
|
|
110
|
+
await require_write(session, actor, record)
|
|
111
|
+
require_otel(record)
|
|
112
|
+
mapping = await _session_mapping(mappings, record)
|
|
113
|
+
source = await request.app.state.trace_sources.resolve(body.source)
|
|
114
|
+
holder = task_holder(pod)
|
|
115
|
+
view = await _imports(request).create(
|
|
116
|
+
record.id, source.id, body.project, body.since, body.until, actor.id, holder
|
|
117
|
+
)
|
|
118
|
+
_launch(request, view.id, holder, source, mapping)
|
|
119
|
+
return view
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@router.get("/{agent}/imports")
|
|
123
|
+
async def list_imports(
|
|
124
|
+
agent: str, request: Request, agents: AgentStoreDep
|
|
125
|
+
) -> list[TraceImportView]:
|
|
126
|
+
_, record = await agent_or_404(agents, agent)
|
|
127
|
+
return await _imports(request).recent(record.id)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@router.get("/{agent}/imports/{import_id}")
|
|
131
|
+
async def get_import(
|
|
132
|
+
agent: str, import_id: int, request: Request, agents: AgentStoreDep
|
|
133
|
+
) -> TraceImportView:
|
|
134
|
+
_, record = await agent_or_404(agents, agent)
|
|
135
|
+
return await _imports(request).get(record.id, import_id)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@router.post("/{agent}/imports/{import_id}/stop")
|
|
139
|
+
async def stop_import(
|
|
140
|
+
agent: str,
|
|
141
|
+
import_id: int,
|
|
142
|
+
request: Request,
|
|
143
|
+
agents: AgentStoreDep,
|
|
144
|
+
session: SessionDep,
|
|
145
|
+
actor: ActorDep,
|
|
146
|
+
) -> TraceImportView:
|
|
147
|
+
_, record = await agent_or_404(agents, agent)
|
|
148
|
+
await require_write(session, actor, record)
|
|
149
|
+
return await _imports(request).stop(record.id, import_id)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@router.post("/{agent}/imports/{import_id}/continue")
|
|
153
|
+
async def continue_import(
|
|
154
|
+
agent: str,
|
|
155
|
+
import_id: int,
|
|
156
|
+
request: Request,
|
|
157
|
+
agents: AgentStoreDep,
|
|
158
|
+
mappings: MappingStoreDep,
|
|
159
|
+
session: SessionDep,
|
|
160
|
+
actor: ActorDep,
|
|
161
|
+
pod: HolderDep,
|
|
162
|
+
) -> TraceImportView:
|
|
163
|
+
_, record = await agent_or_404(agents, agent)
|
|
164
|
+
await require_write(session, actor, record)
|
|
165
|
+
require_otel(record)
|
|
166
|
+
mapping = await _session_mapping(mappings, record)
|
|
167
|
+
store = _imports(request)
|
|
168
|
+
current = await store.get(record.id, import_id)
|
|
169
|
+
source = await request.app.state.trace_sources.resolve(current.source)
|
|
170
|
+
holder = task_holder(pod)
|
|
171
|
+
view = await store.restart(record.id, import_id, holder)
|
|
172
|
+
_launch(request, view.id, holder, source, mapping)
|
|
173
|
+
return view
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from fastapi import APIRouter, Request
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
from evalkit.api.auth import AdminUser
|
|
5
|
+
from evalkit.contracts.enums import TraceProvider
|
|
6
|
+
from evalkit.db.trace_sources import TraceSourceStore
|
|
7
|
+
|
|
8
|
+
router = APIRouter(prefix="/api/trace-sources", tags=["trace sources"])
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _store(request: Request) -> TraceSourceStore:
|
|
12
|
+
return request.app.state.trace_sources
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.get("")
|
|
16
|
+
async def list_trace_sources(request: Request, include_archived: bool = False) -> list[dict]:
|
|
17
|
+
rows = await _store(request).list_sources(include_archived=include_archived)
|
|
18
|
+
return [row.model_dump(mode="json") for row in rows]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CreateTraceSource(BaseModel):
|
|
22
|
+
name: str
|
|
23
|
+
provider: TraceProvider
|
|
24
|
+
url: str
|
|
25
|
+
api_key: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@router.post("")
|
|
29
|
+
async def create_trace_source(body: CreateTraceSource, request: Request, admin: AdminUser) -> dict:
|
|
30
|
+
created = await _store(request).create(body.name, body.provider, body.url, body.api_key)
|
|
31
|
+
return created.model_dump(mode="json")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EditTraceSource(BaseModel):
|
|
35
|
+
url: str | None = None
|
|
36
|
+
api_key: str | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@router.patch("/{name}")
|
|
40
|
+
async def edit_trace_source(
|
|
41
|
+
name: str, body: EditTraceSource, request: Request, admin: AdminUser
|
|
42
|
+
) -> dict:
|
|
43
|
+
edited = await _store(request).edit(name, url=body.url, api_key=body.api_key)
|
|
44
|
+
return edited.model_dump(mode="json")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@router.post("/{name}/archive")
|
|
48
|
+
async def archive_trace_source(name: str, request: Request, admin: AdminUser) -> dict:
|
|
49
|
+
return (await _store(request).archive(name)).model_dump(mode="json")
|
evalkit/api/traces.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections.abc import AsyncIterator
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from fastapi import APIRouter, HTTPException, Request
|
|
7
|
+
from fastapi.responses import JSONResponse, Response, StreamingResponse
|
|
8
|
+
from pydantic import BaseModel, ValidationError
|
|
9
|
+
from pydantic_core import to_json
|
|
10
|
+
|
|
11
|
+
from evalkit.api.access import ActorDep, ensure_active, require, workspace_ref
|
|
12
|
+
from evalkit.api.deps import (
|
|
13
|
+
AgentStoreDep,
|
|
14
|
+
MappingStoreDep,
|
|
15
|
+
SessionDep,
|
|
16
|
+
SessionReaderDep,
|
|
17
|
+
SpanStoreDep,
|
|
18
|
+
)
|
|
19
|
+
from evalkit.api.experiments import slug_or_400
|
|
20
|
+
from evalkit.contracts.esf import MappingDocument
|
|
21
|
+
from evalkit.core.access import can_write
|
|
22
|
+
from evalkit.core.agents import AgentDefinition, UnknownAgent, explicit
|
|
23
|
+
from evalkit.core.traces.mapping import session_id_of
|
|
24
|
+
from evalkit.core.traces.raw import RawSpan
|
|
25
|
+
from evalkit.core.traces.sessions import SessionTrajectory
|
|
26
|
+
from evalkit.core.traces.sources import client_for
|
|
27
|
+
from evalkit.core.workspace import Reference
|
|
28
|
+
from evalkit.db.agents import AgentStore
|
|
29
|
+
|
|
30
|
+
router = APIRouter(prefix="/api", tags=["traces"])
|
|
31
|
+
LOGGER = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def session_too_large(request: Request, exc: Exception) -> JSONResponse:
|
|
35
|
+
LOGGER.warning("refused session read %s: %s", request.url.path, exc)
|
|
36
|
+
return JSONResponse(status_code=413, content={"detail": str(exc)})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def agent_or_404(agents: AgentStore, agent: str) -> tuple[Reference, AgentDefinition]:
|
|
40
|
+
ref = slug_or_400(agent)
|
|
41
|
+
return ref, await agents.get(explicit(ref))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def require_write(session: SessionDep, actor: ActorDep, agent: AgentDefinition) -> None:
|
|
45
|
+
ws = await workspace_ref(session, agent.workspace)
|
|
46
|
+
require(can_write(actor, ws), f"you cannot change {agent.ref}'s mappings or traces")
|
|
47
|
+
ensure_active(ws)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def require_otel(agent: AgentDefinition) -> None:
|
|
51
|
+
if not agent.is_otel:
|
|
52
|
+
raise HTTPException(
|
|
53
|
+
status_code=400,
|
|
54
|
+
detail=f"{agent.ref} takes ATIF trajectories; only OTel agents import traces",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@router.get("/agents/{agent}/sessions/{session_id}/trajectory")
|
|
59
|
+
async def session_trajectory(
|
|
60
|
+
agent: str,
|
|
61
|
+
session_id: str,
|
|
62
|
+
sessions: SessionReaderDep,
|
|
63
|
+
mappings: MappingStoreDep,
|
|
64
|
+
mapping: Literal["active", "draft"] = "active",
|
|
65
|
+
esf: bool = False,
|
|
66
|
+
) -> Response:
|
|
67
|
+
if mapping == "draft":
|
|
68
|
+
record = await sessions.agent(agent)
|
|
69
|
+
if await mappings.draft(record.id) is None:
|
|
70
|
+
raise HTTPException(
|
|
71
|
+
status_code=404,
|
|
72
|
+
detail=f"no draft mapping for {agent}; push one with evalkit traces mapping push",
|
|
73
|
+
)
|
|
74
|
+
result = await sessions.read(agent, session_id, mapping=mapping, include_esf=esf)
|
|
75
|
+
if result is None:
|
|
76
|
+
raise UnknownAgent(agent)
|
|
77
|
+
if result.esf is None:
|
|
78
|
+
return Response(content=to_json(result), media_type="application/json")
|
|
79
|
+
return StreamingResponse(_with_esf(result), media_type="application/json")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def _with_esf(result: SessionTrajectory) -> AsyncIterator[bytes]:
|
|
83
|
+
esf = result.esf or []
|
|
84
|
+
yield to_json(result, exclude={"esf"})[:-1] + b',"esf":['
|
|
85
|
+
for i, span in enumerate(esf):
|
|
86
|
+
yield (b"," if i else b"") + to_json(span)
|
|
87
|
+
yield b"]}"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def _json_array(spans: AsyncIterator[RawSpan]) -> AsyncIterator[bytes]:
|
|
91
|
+
yield b"["
|
|
92
|
+
first = True
|
|
93
|
+
async for span in spans:
|
|
94
|
+
yield (b"" if first else b",") + to_json(span)
|
|
95
|
+
first = False
|
|
96
|
+
yield b"]"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@router.get("/agents/{agent}/sessions/{session_id}/spans")
|
|
100
|
+
async def session_spans(
|
|
101
|
+
agent: str, session_id: str, sessions: SessionReaderDep
|
|
102
|
+
) -> StreamingResponse:
|
|
103
|
+
record = await sessions.agent(agent)
|
|
104
|
+
if record is None:
|
|
105
|
+
raise UnknownAgent(agent)
|
|
106
|
+
return StreamingResponse(
|
|
107
|
+
_json_array(sessions.raw(record, session_id)), media_type="application/json"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@router.get("/agents/{agent}/mappings")
|
|
112
|
+
async def list_mappings(agent: str, agents: AgentStoreDep, mappings: MappingStoreDep) -> list[dict]:
|
|
113
|
+
_, record = await agent_or_404(agents, agent)
|
|
114
|
+
return [m.model_dump(mode="json") for m in await mappings.list(record.id)]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@router.put("/agents/{agent}/mappings/draft")
|
|
118
|
+
async def put_draft(
|
|
119
|
+
agent: str,
|
|
120
|
+
body: dict,
|
|
121
|
+
agents: AgentStoreDep,
|
|
122
|
+
mappings: MappingStoreDep,
|
|
123
|
+
session: SessionDep,
|
|
124
|
+
actor: ActorDep,
|
|
125
|
+
activate: bool = False,
|
|
126
|
+
) -> dict:
|
|
127
|
+
_, record = await agent_or_404(agents, agent)
|
|
128
|
+
await require_write(session, actor, record)
|
|
129
|
+
try:
|
|
130
|
+
document = MappingDocument.model_validate(body)
|
|
131
|
+
except ValidationError as exc:
|
|
132
|
+
first = exc.errors()[0]
|
|
133
|
+
location = ".".join(str(p) for p in first["loc"])
|
|
134
|
+
raise HTTPException(status_code=400, detail=f"{location}: {first['msg']}") from exc
|
|
135
|
+
draft = await mappings.put_draft(
|
|
136
|
+
record.id, document.model_dump(by_alias=True, exclude_none=True), actor.username
|
|
137
|
+
)
|
|
138
|
+
if activate:
|
|
139
|
+
draft = await mappings.activate(record.id, draft.version)
|
|
140
|
+
return draft.model_dump(mode="json")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@router.post("/agents/{agent}/mappings/{version}/activate")
|
|
144
|
+
async def activate_mapping(
|
|
145
|
+
agent: str,
|
|
146
|
+
version: int,
|
|
147
|
+
agents: AgentStoreDep,
|
|
148
|
+
mappings: MappingStoreDep,
|
|
149
|
+
session: SessionDep,
|
|
150
|
+
actor: ActorDep,
|
|
151
|
+
) -> dict:
|
|
152
|
+
_, record = await agent_or_404(agents, agent)
|
|
153
|
+
await require_write(session, actor, record)
|
|
154
|
+
try:
|
|
155
|
+
return (await mappings.activate(record.id, version)).model_dump(mode="json")
|
|
156
|
+
except KeyError as exc:
|
|
157
|
+
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class ImportRequest(BaseModel):
|
|
161
|
+
source: str
|
|
162
|
+
project: str
|
|
163
|
+
session_id: str
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@router.post("/agents/{agent}/sessions/import")
|
|
167
|
+
async def import_session(
|
|
168
|
+
agent: str,
|
|
169
|
+
body: ImportRequest,
|
|
170
|
+
request: Request,
|
|
171
|
+
agents: AgentStoreDep,
|
|
172
|
+
spans: SpanStoreDep,
|
|
173
|
+
mappings: MappingStoreDep,
|
|
174
|
+
session: SessionDep,
|
|
175
|
+
actor: ActorDep,
|
|
176
|
+
) -> dict:
|
|
177
|
+
_, record = await agent_or_404(agents, agent)
|
|
178
|
+
await require_write(session, actor, record)
|
|
179
|
+
require_otel(record)
|
|
180
|
+
source = await request.app.state.trace_sources.resolve(body.source)
|
|
181
|
+
client = client_for(
|
|
182
|
+
source.provider,
|
|
183
|
+
source.url,
|
|
184
|
+
source.api_key.get_secret_value() if source.api_key else None,
|
|
185
|
+
request.app.state.cfg.otel.import_page_traces,
|
|
186
|
+
)
|
|
187
|
+
try:
|
|
188
|
+
fetched = await client.session_spans(body.project, body.session_id)
|
|
189
|
+
except httpx.HTTPError as exc:
|
|
190
|
+
raise HTTPException(status_code=502, detail=f"{body.source}: {exc}") from exc
|
|
191
|
+
active = await mappings.active(record.id)
|
|
192
|
+
document = MappingDocument.model_validate(active.document) if active else None
|
|
193
|
+
for span in fetched:
|
|
194
|
+
span.session_id_hint = session_id_of(span.attributes, document) or body.session_id
|
|
195
|
+
imported = await spans.upsert(record.id, fetched, external_trace_source_id=source.id)
|
|
196
|
+
return {"imported": imported}
|
evalkit/api/users.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from fastapi import APIRouter, Request
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
from evalkit.api.auth import AdminUser
|
|
5
|
+
from evalkit.db.users import UserStore
|
|
6
|
+
|
|
7
|
+
router = APIRouter(prefix="/api/users", tags=["users"])
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _users(request: Request) -> UserStore:
|
|
11
|
+
return UserStore(request.app.state.session_factory)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@router.get("")
|
|
15
|
+
async def list_users(request: Request, admin: AdminUser) -> list[dict]:
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
"id": u.id,
|
|
19
|
+
"username": u.username,
|
|
20
|
+
"is_admin": u.is_admin,
|
|
21
|
+
"archived": u.archived_at is not None,
|
|
22
|
+
"created_at": u.created_at.isoformat(),
|
|
23
|
+
}
|
|
24
|
+
for u in await _users(request).list()
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CreateUserRequest(BaseModel):
|
|
29
|
+
username: str
|
|
30
|
+
password: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@router.post("")
|
|
34
|
+
async def create_user(body: CreateUserRequest, request: Request, admin: AdminUser) -> dict:
|
|
35
|
+
await _users(request).create(body.username, body.password)
|
|
36
|
+
return {"username": body.username}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@router.post("/{username}/archive")
|
|
40
|
+
async def archive_user(username: str, request: Request, admin: AdminUser) -> dict:
|
|
41
|
+
await _users(request).archive(username)
|
|
42
|
+
return {"archived": username}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@router.post("/{username}/restore")
|
|
46
|
+
async def restore_user(username: str, request: Request, admin: AdminUser) -> dict:
|
|
47
|
+
await _users(request).restore(username)
|
|
48
|
+
return {"restored": username}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SetPasswordRequest(BaseModel):
|
|
52
|
+
password: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@router.post("/{username}/password")
|
|
56
|
+
async def set_password(
|
|
57
|
+
username: str, body: SetPasswordRequest, request: Request, admin: AdminUser
|
|
58
|
+
) -> dict:
|
|
59
|
+
await _users(request).set_password(username, body.password)
|
|
60
|
+
return {"password_set": username}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@router.post("/{username}/promote")
|
|
64
|
+
async def promote_user(username: str, request: Request, admin: AdminUser) -> dict:
|
|
65
|
+
await _users(request).set_admin(username, True)
|
|
66
|
+
return {"promoted": username}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@router.post("/{username}/demote")
|
|
70
|
+
async def demote_user(username: str, request: Request, admin: AdminUser) -> dict:
|
|
71
|
+
await _users(request).set_admin(username, False)
|
|
72
|
+
return {"demoted": username}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from datetime import UTC, datetime
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException, Request
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from evalkit.api.access import ActorDep, require
|
|
7
|
+
from evalkit.api.auth import AdminUser
|
|
8
|
+
from evalkit.contracts.enums import WorkspaceRole
|
|
9
|
+
from evalkit.core.access import Actor, WorkspaceRef
|
|
10
|
+
from evalkit.db.workspaces import WorkspaceStore
|
|
11
|
+
|
|
12
|
+
router = APIRouter(prefix="/api/workspaces", tags=["workspaces"])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _store(request: Request) -> WorkspaceStore:
|
|
16
|
+
return WorkspaceStore(request.app.state.session_factory)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _view(ws: WorkspaceRef, names: dict[int, str]) -> dict:
|
|
20
|
+
return {
|
|
21
|
+
"id": ws.id,
|
|
22
|
+
"name": ws.name,
|
|
23
|
+
"type": "shared" if ws.is_shared else "personal",
|
|
24
|
+
"owner": ws.name if not ws.is_shared else None,
|
|
25
|
+
"archived": ws.archived,
|
|
26
|
+
"promotes_to": names.get(ws.promotes_to) if ws.promotes_to is not None else None,
|
|
27
|
+
"promoted_from": names.get(ws.promoted_from) if ws.promoted_from is not None else None,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def _one(store: WorkspaceStore, ws: WorkspaceRef) -> dict:
|
|
32
|
+
linked = [i for i in (ws.promotes_to, ws.promoted_from) if i is not None]
|
|
33
|
+
names = {i: (await store.get_by_id(i)).name for i in linked}
|
|
34
|
+
return _view(ws, names)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.get("")
|
|
38
|
+
async def list_workspaces(request: Request, include_archived: bool = False) -> list[dict]:
|
|
39
|
+
rows = await _store(request).list(include_archived=True)
|
|
40
|
+
names = {ws.id: ws.name for ws in rows}
|
|
41
|
+
return [_view(ws, names) for ws in rows if include_archived or not ws.archived]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CreateWorkspace(BaseModel):
|
|
45
|
+
name: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@router.post("")
|
|
49
|
+
async def create_workspace(body: CreateWorkspace, request: Request, admin: AdminUser) -> dict:
|
|
50
|
+
store = _store(request)
|
|
51
|
+
return await _one(store, await store.create_shared(body.name, admin.id))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@router.get("/{ws}")
|
|
55
|
+
async def get_workspace(ws: str, request: Request) -> dict:
|
|
56
|
+
store = _store(request)
|
|
57
|
+
return await _one(store, await store.get_by_name(ws))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class UpdateWorkspace(BaseModel):
|
|
61
|
+
name: str | None = None
|
|
62
|
+
promotes_to: str | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@router.patch("/{ws}")
|
|
66
|
+
async def update_workspace(
|
|
67
|
+
ws: str, body: UpdateWorkspace, request: Request, admin: AdminUser
|
|
68
|
+
) -> dict:
|
|
69
|
+
store = _store(request)
|
|
70
|
+
update: list[str] = []
|
|
71
|
+
if "promotes_to" in body.model_fields_set:
|
|
72
|
+
await store.set_promotes_to(ws, body.promotes_to)
|
|
73
|
+
if body.promotes_to is not None:
|
|
74
|
+
update.append(
|
|
75
|
+
f"CI that syncs into @{body.promotes_to} must sync into @{ws} instead; "
|
|
76
|
+
f"evalkit sync --workspace {body.promotes_to} now fails"
|
|
77
|
+
)
|
|
78
|
+
if body.name is not None:
|
|
79
|
+
await store.rename(ws, body.name)
|
|
80
|
+
update += [
|
|
81
|
+
f"developer evalkit.yaml credentials keys ending in @{ws}",
|
|
82
|
+
f"CI and scripts that pass --workspace {ws} or refer to name@{ws}",
|
|
83
|
+
f"bookmarked UI links that contain @{ws}",
|
|
84
|
+
]
|
|
85
|
+
current = await store.get_by_name(body.name or ws)
|
|
86
|
+
return {**(await _one(store, current)), "update": update}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _may_archive(actor: Actor, ws: WorkspaceRef) -> bool:
|
|
90
|
+
return actor.is_admin if ws.is_shared else ws.owner_user_id == actor.id
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@router.post("/{ws}/archive")
|
|
94
|
+
async def archive_workspace(ws: str, request: Request, actor: ActorDep) -> dict:
|
|
95
|
+
store = _store(request)
|
|
96
|
+
ref = await store.get_by_name(ws)
|
|
97
|
+
require(_may_archive(actor, ref), "only admins archive shared workspaces, owners their own")
|
|
98
|
+
return await _one(store, await store.set_archived(ws, datetime.now(UTC)))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@router.post("/{ws}/restore")
|
|
102
|
+
async def restore_workspace(ws: str, request: Request, actor: ActorDep) -> dict:
|
|
103
|
+
store = _store(request)
|
|
104
|
+
ref = await store.get_by_name(ws)
|
|
105
|
+
require(_may_archive(actor, ref), "only admins restore shared workspaces, owners their own")
|
|
106
|
+
return await _one(store, await store.set_archived(ws, None))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@router.get("/{ws}/members")
|
|
110
|
+
async def list_members(ws: str, request: Request, role: WorkspaceRole | None = None) -> list[dict]:
|
|
111
|
+
return [m.model_dump(mode="json") for m in await _store(request).members(ws, role)]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AddMember(BaseModel):
|
|
115
|
+
username: str
|
|
116
|
+
role: WorkspaceRole
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@router.post("/{ws}/members")
|
|
120
|
+
async def add_member(ws: str, body: AddMember, request: Request, admin: AdminUser) -> dict:
|
|
121
|
+
await _store(request).add_member(ws, body.username, body.role, admin.id)
|
|
122
|
+
return {"workspace": ws, "username": body.username, "role": body.role}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@router.delete("/{ws}/members/{username}/{role}")
|
|
126
|
+
async def remove_member(
|
|
127
|
+
ws: str, username: str, role: WorkspaceRole, request: Request, admin: AdminUser
|
|
128
|
+
) -> dict:
|
|
129
|
+
if not await _store(request).remove_member(ws, username, role):
|
|
130
|
+
raise HTTPException(status_code=404, detail=f"{username} is not a {role} of {ws}")
|
|
131
|
+
return {"removed": username, "role": role}
|