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,44 @@
|
|
|
1
|
+
# MCP
|
|
2
|
+
|
|
3
|
+
The server is also an MCP server at `{{origin}}/mcp` (streamable HTTP). An agent connected to it can read every agent's runs and trajectories, and every experiment's scores. It cannot change anything.
|
|
4
|
+
|
|
5
|
+
## Connect
|
|
6
|
+
|
|
7
|
+
With an evalkit login token (`evalkit login` stores it in `~/.evalkit/config.toml`):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
claude mcp add --transport http evalkit {{origin}}/mcp --header "Authorization: Bearer $EVALKIT_TOKEN"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Only login tokens work here: agent tokens (`evalkit tokens create`) and download tickets are refused.
|
|
14
|
+
|
|
15
|
+
When the server's config has an `mcp.oauth` block, MCP clients that speak OAuth (Claude, Cursor, Codex) can connect with the URL alone: the server points them to the identity provider, and a token issued for `{{origin}}/mcp` with the configured scope (`mcp.oauth.scope`, default `evalkit:read`) is accepted. Evalkit login tokens keep working next to it.
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
mcp:
|
|
19
|
+
oauth:
|
|
20
|
+
issuer: https://id.example.com
|
|
21
|
+
jwks_uri: https://id.example.com/.well-known/jwks.json
|
|
22
|
+
# audience: defaults to <server.base_url>/mcp
|
|
23
|
+
# scope: your identity provider's name for read access; defaults to evalkit:read
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
An OAuth token is not an evalkit user. Whoever the identity provider gives that scope reads everything an evalkit login reads, which today is every workspace, and archiving someone in evalkit does not cut off their OAuth access: revoke it at the identity provider.
|
|
27
|
+
|
|
28
|
+
## Tools
|
|
29
|
+
|
|
30
|
+
| Tool | What it returns |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `find_agents` | Agents and the id (`name@workspace`) the run tools take |
|
|
33
|
+
| `find_runs` | An agent's runs in a time window, newest first: id, title, time, `user_id`, `client_id`, app or eval. `include` adds each run's `metrics`, `turns` or `tool_calls` (what it called, with which arguments). `failed_only` keeps runs with a failure; `tool_error` keeps runs where that tool errored. `timezone` reads date-only bounds as `get_run_stats` does. Up to 5,000 rows when listing alone, 200 with `include` or a filter |
|
|
34
|
+
| `get_runs` | Up to 200 runs by id: turns, failed turns, failures, duration, cost and where it came from, tokens, cost by model, tool errors by tool, opening question, final answer; `include` adds `turns` and `tool_calls` |
|
|
35
|
+
| `get_run_stats` | Totals over a time window, or one row per day, user, source, or tool that errored; `group_by` takes several at once and reads the window once |
|
|
36
|
+
| `get_trajectory` | One run's ATIF trajectory, paged by step, long text clipped |
|
|
37
|
+
| `get_run_files`, `download_run_file` | Files the run produced |
|
|
38
|
+
| `find_experiments`, `get_experiments`, `get_experiment_runs` | Experiments, their scorecard, and per-case scores with evaluator explanations |
|
|
39
|
+
|
|
40
|
+
Every tool answers `{data, meta}`, with `data` a list of rows. Search tools page with `limit` and `offset` (`meta.next_offset` is the next page); tools that take ids answer one row per id in input order, with `note: {"code": "not_found"}` for an unknown one. Dates are `start_date` / `end_date` (`YYYY-MM-DD`, inclusive, or an ISO 8601 timestamp). Money is `{"value", "unit": "currency", "currency": "USD"}`. Errors read `<code>: <detail>`. Tools that read trajectories have a time budget for the whole call, listing included: a run not read in time comes back as a row with `error` and `detail` instead of metrics, counted in `meta.failed` by `get_runs`; ask for it again with `get_runs`. Optional fields with no value are left out of rows. When listing alone uses the budget, the call fails with `harness_timeout`; narrow the window. With `failed_only` or `tool_error`, `find_runs` reads the window again on every call, so take one page and fetch the runs not read with `get_runs` rather than paging on.
|
|
41
|
+
|
|
42
|
+
A turn is one user message and everything the agent did until the next one. `cost_source` is `reported` when the harness priced the run, `estimated` when evalkit priced it from token counts, `mixed` for both. A turn counts as failed when the harness marks a step in it with `extra.is_error` (see [Harness API](harness-api.md)).
|
|
43
|
+
|
|
44
|
+
`user_id` and `client_id` are what the harness recorded. Resolve them to people and firms with your identity service.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Quickstart
|
|
2
|
+
|
|
3
|
+
From nothing to a scored experiment on an evalkit server your team already runs. Each step links to its full page; [Concepts](concepts.md) explains the terms.
|
|
4
|
+
|
|
5
|
+
No server yet? [Try locally](try-locally.md) runs one on your machine with a sample agent and your own LLM, and ends with how to deploy one for your team.
|
|
6
|
+
|
|
7
|
+
## 1. Pick how evalkit reaches your agent
|
|
8
|
+
|
|
9
|
+
| Path | What you build | Pick it when |
|
|
10
|
+
| -------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| SDK run + traces | A Python script that calls your agent once per case, and an OpenTelemetry exporter to this server | The default: start here. Runs start from your machine or CI |
|
|
12
|
+
| Harness API | An HTTP service: start a run, report its status, serve its ATIF trajectory | A deployed agent the server should drive (shared agents, runs started from the UI), or you need the Workspace tab |
|
|
13
|
+
| Harness API + traces | The same service minus the trajectory endpoint; the trajectory comes from your spans | You want the Harness API but your agent already emits OpenTelemetry spans |
|
|
14
|
+
|
|
15
|
+
The Workspace tab on a run lists the files the agent wrote. evalkit reads them through the Harness API files endpoints, so only the two Harness API paths have it. See [Harness API](harness-api.md) for what each endpoint enables.
|
|
16
|
+
|
|
17
|
+
## 2. Install and log in
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv tool install --reinstall "evalkit-py=={{version}}"
|
|
21
|
+
evalkit login --url {{origin}}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
More in [Install](install.md) and [Login](login.md).
|
|
25
|
+
|
|
26
|
+
## 3. Write `evalkit.yaml` and one case
|
|
27
|
+
|
|
28
|
+
At the root of the repository that holds your eval cases:
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
cases: eval/cases
|
|
32
|
+
fixtures: eval/fixtures
|
|
33
|
+
|
|
34
|
+
credentials:
|
|
35
|
+
agents:
|
|
36
|
+
myagent: env:MYAGENT_API_KEY # only for a Harness API agent that needs a key
|
|
37
|
+
llm_providers:
|
|
38
|
+
https://litellm.example.com: env:LITELLM_API_KEY # the server's judge base URL
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Put the keys in a `.env` next to `evalkit.yaml`. Then create your agent on the server once:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# SDK path: a code-driven agent whose trajectory comes from its traces
|
|
45
|
+
evalkit agents create myagent --trajectory-source otel --mapping openinference.json
|
|
46
|
+
# Harness API path
|
|
47
|
+
evalkit agents create myagent --harness-url http://localhost:8080/evalkit/v1
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`openinference.json` (or `gen_ai.json`) comes from the `connect-traces` skill.
|
|
51
|
+
|
|
52
|
+
Each directory under `cases` is a dataset. Add `eval/cases/smoke/cases.yaml`:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
cases:
|
|
56
|
+
- id: store-count
|
|
57
|
+
input:
|
|
58
|
+
question: "How many company-operated stores did Starbucks have at FY2021 year-end?"
|
|
59
|
+
evaluators:
|
|
60
|
+
output:
|
|
61
|
+
answer_rubrics:
|
|
62
|
+
rubrics:
|
|
63
|
+
- rubric: "States 17,133 company-operated stores at FY2021 year-end."
|
|
64
|
+
type: factual_retrieval
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
More in [Config](config.md).
|
|
68
|
+
|
|
69
|
+
## 4. Run it
|
|
70
|
+
|
|
71
|
+
SDK path: install the SDK into your project (see [Install](install.md)), then write `eval/run.py`:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from evalkit.sdk import run_experiment, tracing
|
|
75
|
+
from myagent import ask
|
|
76
|
+
|
|
77
|
+
tracing.register(agent="myagent")
|
|
78
|
+
|
|
79
|
+
def task(input: dict) -> str:
|
|
80
|
+
return ask(input["question"])
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
run_experiment(dataset="smoke", agent="myagent", task=task, dry_run=True)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`dry_run=True` runs one case without creating an experiment and prints what the server made of the spans. When it looks right, drop `dry_run`; `run_experiment` then returns a result whose `url` is the experiment page. More in [SDK runs](sdk-runs.md) and [Tracing](tracing.md).
|
|
87
|
+
|
|
88
|
+
Harness API path:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
evalkit harness check myagent # exercises every capability the harness declares
|
|
92
|
+
evalkit run smoke --agent myagent
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
A run executes every case it selects, whether the dataset is in your own workspace (`smoke`) or a shared one (`smoke@dev`). Review state never filters a run.
|
|
96
|
+
|
|
97
|
+
## 5. Read the results
|
|
98
|
+
|
|
99
|
+
`evalkit run` prints the scorecard when it finishes. On this server, open the dataset under Datasets and pick the experiment. The experiment page has the scorecard; each run opens its trajectory, scores and, on the Harness API paths, its workspace files. If converted spans look wrong (missing messages, wrong span kinds), check one session with `evalkit traces check myagent <session id>` and see [Mappings](mappings.md).
|
|
100
|
+
|
|
101
|
+
## 6. Attach a file to a case
|
|
102
|
+
|
|
103
|
+
Drop the file in the `fixtures` directory from your `evalkit.yaml`, register it once in `fixtures.yaml` at the root of your `cases` directory, and name it in the case. The `context` line is what the agent is told about the file.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cp ~/Downloads/meridian-balance-sheet.csv <fixtures>/
|
|
107
|
+
shasum -a 256 <fixtures>/meridian-balance-sheet.csv # sha256 for the registry
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
# <cases>/fixtures.yaml
|
|
112
|
+
fixtures:
|
|
113
|
+
- key: meridian-balance-sheet.csv
|
|
114
|
+
sha256: 652d32…eece7
|
|
115
|
+
context: Meridian's FY2025 balance sheet, USD thousands.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
# in the case
|
|
120
|
+
- id: net-debt-fy2025
|
|
121
|
+
fixtures: [meridian-balance-sheet.csv]
|
|
122
|
+
input:
|
|
123
|
+
question: Using only the attached file, what was net debt at FY2025 year-end?
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A run or sync uploads any fixture whose sha256 the server doesn't hold yet, and the server keeps one copy per sha256. So you only need the files you touch, and if the bytes on disk don't match the registry the run stops and shows both hashes.
|
|
127
|
+
|
|
128
|
+
## 7. Iterate and promote
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
evalkit evaluate <experiment-id> # re-score the same runs after an evaluator change; no agent calls
|
|
132
|
+
evalkit runs pull --experiment <experiment-id> # download every run (trajectory, case, scores) to diagnose failures
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Cases reach a shared dataset through a PR. On merge, CI syncs them into a shared workspace such as `dev`, where they start Unreviewed; a reviewer of that workspace approves them, and if `dev` is linked to `prod`, approval promotes them there.
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
evalkit review request finance@dev --case-id net-debt-fy2025 --reviewer <user>
|
|
139
|
+
evalkit review approve finance@dev net-debt-fy2025 # reviewers only
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
If an admin linked `dev` to a promotion target (`evalkit workspaces promote-to dev prod`), that approval also lands the case in `finance@prod`, approved. `prod` takes cases only that way: no sync, no separate review. The case page in either workspace shows the other's version and links to it.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# SDK runs
|
|
2
|
+
|
|
3
|
+
`run_experiment` runs an experiment from your own code: it calls your task once per case and repetition, stores the runs on this server, and scores them with the judge, the remote evaluators of the agent's workspace and any Python evaluators you pass. The agent is a code-driven agent on the server (no harness URL), created once with `evalkit agents create`. Invoking the agent, timeouts and stopping are your code.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
# eval/run.py — uv run python eval/run.py
|
|
7
|
+
from evalkit.sdk import run_experiment, RunResult, evaluator, tracing
|
|
8
|
+
from myagent import MyAgent, setup_tracing
|
|
9
|
+
|
|
10
|
+
setup_tracing() # your own OpenTelemetry provider and instrumentors
|
|
11
|
+
tracing.register(agent="myagent-sdk") # add this server as an export destination
|
|
12
|
+
agent = MyAgent()
|
|
13
|
+
|
|
14
|
+
def task(input: dict, files: list) -> str:
|
|
15
|
+
return agent.ask(input["question"], attachments=[f.path for f in files])
|
|
16
|
+
|
|
17
|
+
@evaluator(name="citation_check")
|
|
18
|
+
def citation_check(case, run) -> float:
|
|
19
|
+
cited = {c["url"] for c in (run.output or {}).get("citations", [])}
|
|
20
|
+
return 1.0 if cited >= set(case.config["required"]) else 0.0
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
result = run_experiment(dataset="finance", agent="myagent-sdk", task=task,
|
|
24
|
+
evaluators=[citation_check], concurrency=4)
|
|
25
|
+
print(result.url)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## The task
|
|
29
|
+
|
|
30
|
+
A sync or async callable. Parameters bind **by name** from this set, so their order does not matter; any other required parameter is an error before the first call, and an optional one keeps its default.
|
|
31
|
+
|
|
32
|
+
| Parameter | Value |
|
|
33
|
+
| ---------- | --------------------------------------------------------------------- |
|
|
34
|
+
| `input` | the case input, verbatim, minus `files` |
|
|
35
|
+
| `files` | `list[Fixture]`, the case's fixture files, downloaded before the call |
|
|
36
|
+
| `model` | the `model` argument of `run_experiment`, or `None` |
|
|
37
|
+
| `metadata` | a dict with the keys below |
|
|
38
|
+
|
|
39
|
+
A `Fixture` has:
|
|
40
|
+
|
|
41
|
+
- `name`: the fixture key the case names.
|
|
42
|
+
- `path`: where the file sits on this machine.
|
|
43
|
+
- `context`: the file's description from the fixture registry, or `None`. A Harness API agent gets the same text with each file; show it to your agent the same way (for example an "Attached files" block ahead of the question).
|
|
44
|
+
|
|
45
|
+
`metadata` holds:
|
|
46
|
+
|
|
47
|
+
- `session_id`: the SDK's session id for this run, the `session.id` of its `evalkit.run` span.
|
|
48
|
+
- `experiment_id`: the experiment, or `None` on a dry run.
|
|
49
|
+
- `case_id`: the case.
|
|
50
|
+
- `repetition`: 1 for the first repetition, 2 for the second, and so on.
|
|
51
|
+
- `tags`: the case's tags.
|
|
52
|
+
|
|
53
|
+
`model` is the only way `run_experiment(model=...)` reaches your agent. The experiment records the requested model either way, so a task without a `model` parameter would silently run the agent's default; `run_experiment` prints a warning before the first call when that happens.
|
|
54
|
+
|
|
55
|
+
A task with exactly one parameter whose name is not in the table (`def task(q)`) receives the input. Return a `str` (the answer), a `dict` (the structured output) or a `RunResult`:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from evalkit.sdk import RunResult
|
|
59
|
+
|
|
60
|
+
RunResult(answer="…", output={"citations": []}, model="gpt-5",
|
|
61
|
+
session_id="…", # override the session the SDK polls
|
|
62
|
+
trajectory={...}) # push an ATIF document instead of tracing
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A task that raises records the run as `failed` with the exception and the experiment continues.
|
|
66
|
+
|
|
67
|
+
## An agent in another process
|
|
68
|
+
|
|
69
|
+
The SDK stamps `session.id` and the `evalkit.*` attributes only on spans made in this Python process. An agent behind HTTP or a job queue, or written in another language, joins the run in one of two ways. Pick one, not both.
|
|
70
|
+
|
|
71
|
+
- **Continue the trace.** Inject the W3C trace context into the request and have the agent start its spans from it. Every span in a trace belongs to the session of the trace's root, the SDK's `evalkit.run` span, even if the agent stamps its own `session.id` or `gen_ai.conversation.id`.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from opentelemetry import propagate
|
|
75
|
+
|
|
76
|
+
def task(input: dict) -> str:
|
|
77
|
+
headers: dict[str, str] = {}
|
|
78
|
+
propagate.inject(headers) # adds traceparent
|
|
79
|
+
return httpx.post(AGENT_URL, json=input, headers=headers).json()["answer"]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- **Return the agent's session.** The agent puts its own session id on its root span as `session.id`, and the task returns it. Use this when the trace context cannot reach the agent, for example through a queue. The SDK moves its `evalkit.run` span into that session, so no empty session is left behind, and the trajectory is built from the agent's trace.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
def task(input: dict) -> RunResult:
|
|
86
|
+
job = agent_api.start(input["question"]) # your API returns the session it opened
|
|
87
|
+
return RunResult(answer=job.wait(), session_id=job.session_id)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If your API cannot tell you the session id, send it one instead: pass `metadata["session_id"]` along with the job and have the agent use it as its session id (or wrap the job in `tracing.eval_run(metadata)`, see [Tracing](tracing.md)). Its spans then join the SDK's session and the task returns only the answer.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
def task(input: dict, metadata: dict) -> str:
|
|
94
|
+
return queue.submit(input["question"], session_id=metadata["session_id"]).wait()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Either way the Runs page links the session to its experiment and case.
|
|
98
|
+
|
|
99
|
+
The agent's exporter needs its own token. Mint one with `evalkit tokens create --agent <name>`; it never expires. Your login token expires, and an exporter that holds it then drops every span with nothing but a line in its own log.
|
|
100
|
+
|
|
101
|
+
## Datasets and workspaces
|
|
102
|
+
|
|
103
|
+
- `dataset="finance"` runs your own workspace's copy; if `finance` is a directory under `cases` it is synced first.
|
|
104
|
+
- `dataset="finance@dev"` or `dataset="finance", workspace="dev"` runs the shared cases, every review state included.
|
|
105
|
+
- `case_ids=[...]`, `split="..."`, `repetitions`, `model` and `resume=<experiment id>` mean what the same options mean on `evalkit run`.
|
|
106
|
+
- `dry_run=True` runs one case (`dry_run=N` runs N) without creating an experiment and prints the answer and what the server made of the session. If the server cannot read a session, that case prints a `trajectory read failed` line and the dry run goes on. Use it while wiring up tracing; the session shows on the Runs page with its case question and case id.
|
|
107
|
+
|
|
108
|
+
## Notebooks
|
|
109
|
+
|
|
110
|
+
`run_experiment` refuses to run inside a running event loop. In a notebook use the async form:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from evalkit.sdk import arun_experiment
|
|
114
|
+
|
|
115
|
+
result = await arun_experiment(dataset="finance", agent="myagent-sdk", task=task)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## The agent and the judge
|
|
119
|
+
|
|
120
|
+
`agent="myagent-sdk"` names an agent on the server; a bare name means your own workspace. Create it once:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
evalkit agents create myagent-sdk --trajectory-source otel --mapping openinference.json \
|
|
124
|
+
--max-concurrent-runs 4 --provides thinking=true
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Its `answer` rule and `exclude_spans` live in its span mapping. `evalkit.yaml` is still required; it holds the cases, fixtures and credentials.
|
|
128
|
+
|
|
129
|
+
The judge is the server's default unless you pass one:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from evalkit.sdk import Judge, run_experiment
|
|
133
|
+
|
|
134
|
+
run_experiment(dataset="finance", agent="myagent-sdk", task=task,
|
|
135
|
+
judge=Judge(base_url="https://litellm.example.com", model="gpt-5"))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
An admin sets the default judge with `evalkit judge-default set`. Either way, the SDK reads the judge's key from `credentials.llm_providers` in `evalkit.yaml`, keyed by the judge's base URL.
|
|
139
|
+
|
|
140
|
+
## Capabilities
|
|
141
|
+
|
|
142
|
+
A code-driven agent has no `GET /capabilities`, so evalkit writes its capabilities onto each experiment itself: `trajectory`, `tool_calls`, `token_usage` and `run_listing` are true; `thinking`, `artifacts` and `workspace` are false. Evaluators gated on a capability skip when it is false. evalkit does not read capabilities from the spans. Set `provides` on the agent (`evalkit agents create --provides`, or `evalkit agents edit`) to declare what it really does, for example `thinking: true` when its spans carry the model's reasoning. Declare only what works: a false `true` produces failing scores blamed on the agent.
|
|
143
|
+
|
|
144
|
+
## Python evaluators
|
|
145
|
+
|
|
146
|
+
`@evaluator(name=..., kind="evaluator" | "gauge", requires_trajectory=False)` turns a function into an evaluator. It receives the same `EvalCase` and `EvalRun` the HTTP Custom Evaluator API sends: the case id, input, expected block and this evaluator's config from the case YAML; the answer, structured output and the ATIF trajectory as a dict. Return a float, a `ScoreItem`, or a list of them. A case selects it by name under `evaluators.custom`, exactly like an HTTP evaluator.
|
|
147
|
+
|
|
148
|
+
## Bring your own benchmark
|
|
149
|
+
|
|
150
|
+
A published benchmark is a suite plus a Python evaluator:
|
|
151
|
+
|
|
152
|
+
1. Write a script that turns the benchmark's file into case YAML. Each case gets the question as `input`. Its grading data (rubrics, reference answer) goes under `evaluators.custom.<grader name>`.
|
|
153
|
+
2. `evalkit sync <dir>` puts it in a dataset like any suite.
|
|
154
|
+
3. Write the grader as an `@evaluator` with that name. It reads the grading data from `case.config`, and calls its own judge models if it needs any. Pass it to `run_experiment`.
|
|
155
|
+
|
|
156
|
+
`evalkit lint` and `evalkit sync` warn with `W-CUSTOM-UNKNOWN` for the grader name, because the server can't see a Python evaluator. That warning is expected.
|
|
157
|
+
|
|
158
|
+
## Re-scoring
|
|
159
|
+
|
|
160
|
+
Python evaluators live in your script, so a re-score that needs them starts from the script:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from evalkit.sdk import evaluate_experiment
|
|
164
|
+
|
|
165
|
+
evaluate_experiment(experiment_id="…", evaluators=[citation_check],
|
|
166
|
+
refetch_trajectories=True)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`refetch_trajectories` polls the collector again for runs whose trajectory was `partial` or `unavailable` and re-scores them. `evalkit evaluate` refuses experiments that used Python evaluators and points here.
|
|
170
|
+
|
|
171
|
+
Next: [Tracing](tracing.md).
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Tracing
|
|
2
|
+
|
|
3
|
+
The collector is an OTLP/HTTP endpoint on this server:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
POST {{origin}}/api/otel/v1/traces
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
It accepts `application/x-protobuf` and `application/json`, gzip or not, from any OpenTelemetry SDK. Spans are stored as received and converted to ATIF with the agent's mapping when a run or the Runs page asks for them.
|
|
10
|
+
|
|
11
|
+
## From Python with the SDK
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from evalkit.sdk import tracing
|
|
15
|
+
|
|
16
|
+
tracing.register(agent="myagent-sdk")
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`register` adds an exporter for this server to your global tracer provider, creating one if none is set. Your existing exporters keep running. The exporter carries an `X-Evalkit-Agent` header, which names the agent on the server; the `evalkit.agent` resource attribute is only set when `register` creates the provider itself. It reads the server URL from `EVALKIT_SERVER_URL` or your `evalkit login`, and the token from `EVALKIT_OTEL_TOKEN` (an agent token), then `EVALKIT_TOKEN`, then your `evalkit login`. It needs the `otel` extra.
|
|
20
|
+
|
|
21
|
+
### An existing span pipeline
|
|
22
|
+
|
|
23
|
+
`register` changes the global provider. When you build your own pipeline, take evalkit's parts and put them where you need them:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from evalkit.sdk import tracing
|
|
27
|
+
|
|
28
|
+
exporter = tracing.exporter(agent="myagent-sdk") # OTLPSpanExporter: URL, token and X-Evalkit-Agent set
|
|
29
|
+
processor = tracing.processor(agent="myagent-sdk") # batch processor around that exporter
|
|
30
|
+
provider.add_span_processor(processor) # what register does for you
|
|
31
|
+
headers = tracing.headers(agent="myagent-sdk") # the same headers, for an exporter you build yourself
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
All three resolve the server and token the same way `register` does. `processor` also stamps the run's `session.id` and `evalkit.*` attributes on spans that start under a task call (or under `tracing.eval_run`, below).
|
|
35
|
+
|
|
36
|
+
Spans handed straight to a processor or exporter, for example by a bridge that converts another tool's spans, never pass through the provider, so `register` cannot see them. Hand them to `tracing.processor(...)` too: give the bridge that processor, or one processor that fans out to all of yours and evalkit's (OpenTelemetry's `SynchronousMultiSpanProcessor`). Those spans are already finished, so `processor` cannot stamp them: the bridge copies `session.id` onto each span it converts. How eval-only export treats them is below.
|
|
37
|
+
|
|
38
|
+
### Choosing what a shared worker exports
|
|
39
|
+
|
|
40
|
+
A worker that serves both app runs (production, staging or manual use) and eval runs exports every span it makes, so every app turn also becomes a session on this server, which you can browse on the Runs page and pull with `evalkit runs pull <agent> --source app`. Whether to send app traffic is your choice. `eval_run` marks eval runs either way, and to send only those, filter on the mark:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from evalkit.sdk import tracing
|
|
44
|
+
|
|
45
|
+
provider.add_span_processor(tracing.processor(agent="myagent", only=tracing.is_eval_span))
|
|
46
|
+
|
|
47
|
+
def handle_job(job):
|
|
48
|
+
if job.evalkit: # the task's metadata, sent along with the job
|
|
49
|
+
with tracing.eval_run(job.evalkit):
|
|
50
|
+
return run_agent(job)
|
|
51
|
+
return run_agent(job)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The task passes its `metadata` argument along with the job (see [SDK runs](sdk-runs.md)). `eval_run` stamps `session.id` (the SDK's session id), `evalkit.case_id`, `evalkit.experiment_id` and `evalkit.repetition` on every span started inside it, so the worker's spans join the run's session and the task returns only the answer. `is_eval_span` is true for a span that carries `evalkit.case_id`. The stamp lives in a context variable: spans started in another thread need that thread to run inside `eval_run` too.
|
|
55
|
+
|
|
56
|
+
A harness agent learns that a run is an eval run from the `metadata` of `POST /runs`: `experiment_id`, `case_id` and `repetition`. That is what it passes to `eval_run`, with the `run_id` it returns as the session id: `tracing.eval_run({**metadata, "session_id": run_id})`.
|
|
57
|
+
|
|
58
|
+
`only` decides by trace, not by span. It is any function that takes a span and returns a bool, and it runs on each span as the span starts and again as it ends. Once it is true for one span, the processor keeps every span of that span's trace that reaches it: spans started inside `eval_run`, the job's own spans started before `eval_run` in the same trace, and finished spans a bridge hands straight to the processor. Spans of other traces are dropped. A custom `only` picks whole traces the same way.
|
|
59
|
+
|
|
60
|
+
The rule for order: a span is kept when its trace is already marked by the time the span ends. The first span started inside `eval_run` marks its trace as it starts, before anything under it ends, so spans that end before it and spans a bridge hands over later are both kept. A span that ends before any span of its trace was marked is dropped. A bridge that may hand spans over that early can copy the `evalkit.*` attributes onto them, so each one marks its own trace. A bridge should copy `session.id` either way: the server takes a trace's session from its root span when the root reaches it, and from each span's own `session.id` otherwise.
|
|
61
|
+
|
|
62
|
+
The processor remembers the 10,000 most recently used eval traces and forgets the oldest. Each kept span refreshes its trace, so a trace is only forgotten once 10,000 other eval traces have been used since its last span.
|
|
63
|
+
|
|
64
|
+
Spans from another process in the same trace, such as an MCP gateway pod the agent calls, never reach this processor. They go through that process's own exporter, so that process makes its own choice: either it continues the trace, wraps its work in `eval_run` with the run's metadata sent along with the request, and filters with `only=tracing.is_eval_span` too, or it exports everything.
|
|
65
|
+
|
|
66
|
+
Do not use a sampler for this: a sampler drops spans for every exporter, your own backend included.
|
|
67
|
+
|
|
68
|
+
Around every task call the SDK opens a root span named `evalkit.run` with `session.id` set to the run id, and stamps `session.id`, `evalkit.experiment_id`, `evalkit.case_id` and `evalkit.repetition` onto every span your code starts underneath it. That is what groups a trace into one session. When the task returns `RunResult(session_id=...)`, the root span takes that session id instead, so it joins the agent's session rather than sitting alone; see [SDK runs](sdk-runs.md) for agents in another process.
|
|
69
|
+
|
|
70
|
+
## From any OpenTelemetry SDK
|
|
71
|
+
|
|
72
|
+
Point the exporter at the collector and name the agent, either with a header or an attribute:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT={{origin}}/api/otel/v1/traces
|
|
76
|
+
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <token>,X-Evalkit-Agent=myagent"
|
|
77
|
+
export OTEL_RESOURCE_ATTRIBUTES="evalkit.agent=myagent" # alternative to the header
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The collector picks the agent per span: the span's `evalkit.agent` attribute first, then the resource's, then the `X-Evalkit-Agent` header. One process that serves several agents can therefore share one exporter and set `evalkit.agent` on each agent's spans. The value is an agent reference (`myagent` or `myagent@workspace`). Every agent a batch names is checked as if it came in the header: an agent token writes only its own agent, a login token only agents in workspaces you can write to (your own, or a shared one where you are a writer). If any span names an agent the token may not write, the whole batch is refused.
|
|
81
|
+
|
|
82
|
+
Put the session id on the root span of each trace, in the attribute your mapping's `session_id` rule names (`session.id` in `openinference.json`, `gen_ai.conversation.id` in `gen_ai.json`). Every span in a trace belongs to the session of the trace's root, even one that carries a session attribute of its own. Only when the root has no session do child spans keep theirs.
|
|
83
|
+
|
|
84
|
+
## Deployed agents
|
|
85
|
+
|
|
86
|
+
A service cannot carry a person's token. Mint an agent token, bound to one agent and valid only for the collector:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
evalkit tokens create --agent ouragent@dev # needs writer on dev
|
|
90
|
+
evalkit tokens list
|
|
91
|
+
evalkit tokens revoke <id>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Set it on the deployment as `EVALKIT_OTEL_TOKEN` next to `EVALKIT_SERVER_URL`; `tracing.register`, `tracing.exporter` and `tracing.processor` pick both up. For a non-Python exporter put it in `OTEL_EXPORTER_OTLP_HEADERS`. Use an agent token for any exporter outside the process that calls `run_experiment` too, even on your laptop: the login token expires under a long-running worker.
|
|
95
|
+
|
|
96
|
+
The collector logs every refused export as `otel export rejected` with the status, the reason (`expired token`, `invalid token`, `unknown agent`, `forbidden`, `too large`, `malformed`) and the agent. OpenTelemetry exporters only log a failed export on their side, so a silent agent is worth checking in both logs.
|
|
97
|
+
|
|
98
|
+
## Size limits and large spans
|
|
99
|
+
|
|
100
|
+
LLM spans from OpenInference and GenAI instrumentations carry the full input of each call: the messages so far, the system prompt and the tool schemas. Every turn repeats the whole context, so a long agent session grows with roughly the square of its turns. A 14-minute chat with subagents can store hundreds of MB of span attributes.
|
|
101
|
+
|
|
102
|
+
The server has two limits, both set in its config:
|
|
103
|
+
|
|
104
|
+
| Limit | Default | What happens past it |
|
|
105
|
+
| ------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
106
|
+
| `otel.max_batch_bytes` | 16 MiB | One export request (after gzip is undone) gets `413` and its spans are dropped. Exporters do not split a refused batch, so keep batches small. |
|
|
107
|
+
| `otel.max_session_bytes` | 384 MiB | Reading the session (a run's trajectory, `evalkit traces check`, the Mappings tab) gets `413` with the session's size and the limit. A run records the trajectory as `unavailable` and logs the message. `--dump spans.json` still works. |
|
|
108
|
+
|
|
109
|
+
`otel.max_session_bytes` counts the span attributes a trajectory build loads, not everything stored. evalkit reads the input messages (and system prompt) of an LLM span only for the first LLM call of each agent and subagent; later calls add only their output, tool calls and tokens to the trajectory. So the build leaves the input of every later LLM call in the database and does not count it. The limit keeps one session from using up the server's memory, so raise it only together with the server's memory limit.
|
|
110
|
+
|
|
111
|
+
For agents with large spans, send fewer spans per export and give each export more time. For the Python SDK, `tracing.register` included:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=16 # spans per export; default 512
|
|
115
|
+
export OTEL_BSP_MAX_QUEUE_SIZE=2048 # spans waiting for export; raise it if the exporter logs "Queue full"
|
|
116
|
+
export OTEL_EXPORTER_OTLP_TIMEOUT=60 # seconds per export in Python; default 10
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Other SDKs follow the OpenTelemetry spec and read `OTEL_EXPORTER_OTLP_TIMEOUT` in milliseconds (`60000`). A 10 MB span can take more than 10 s to upload, longer than the default timeout. In an OpenTelemetry Collector, set `send_batch_max_size` on the `batch` processor and `timeout` on the `otlphttp` exporter instead.
|
|
120
|
+
|
|
121
|
+
## Exporting to evalkit and another backend
|
|
122
|
+
|
|
123
|
+
The standard OTLP variables name one endpoint. To keep exporting to your tracing backend and send the same spans here, either:
|
|
124
|
+
|
|
125
|
+
- add a second `otlphttp` exporter to the OpenTelemetry Collector the agent already exports to (recommended for deployed agents), or
|
|
126
|
+
- add a second span processor and exporter in code. `tracing.register` does this for Python.
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
exporters:
|
|
130
|
+
otlphttp/evalkit:
|
|
131
|
+
traces_endpoint: {{origin}}/api/otel/v1/traces
|
|
132
|
+
headers:
|
|
133
|
+
Authorization: Bearer ${env:EVALKIT_OTEL_TOKEN}
|
|
134
|
+
X-Evalkit-Agent: ouragent@dev
|
|
135
|
+
service:
|
|
136
|
+
pipelines:
|
|
137
|
+
traces:
|
|
138
|
+
exporters: [otlp/existing, otlphttp/evalkit]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The collector speaks OTLP over HTTP only, not gRPC. Bodies may be chunked with no `Content-Length` (the JavaScript exporter sends them that way); a proxy in front of the collector must allow that.
|
|
142
|
+
|
|
143
|
+
## The main agent and subagents
|
|
144
|
+
|
|
145
|
+
Mark the agent's top-level span as an AGENT span (`openinference.span.kind=AGENT`, or `gen_ai.operation.name=invoke_agent`). evalkit builds the main trajectory from it, and every AGENT span inside it becomes a subagent trajectory named by the span's `agent.name` (`gen_ai.agent.name`), or by the span name when that is missing. When the root span is not an AGENT span, as with the SDK's `evalkit.run` span, evalkit takes the earliest AGENT span that is not inside another one as the main agent. If that span is a tool call that starts a subagent partway through the run, the subagent's steps land in the main trajectory and the call has no result, so mark the top-level span as an AGENT span rather than relying on this.
|
|
146
|
+
|
|
147
|
+
## Harness agents with traces
|
|
148
|
+
|
|
149
|
+
A harness agent created with `--trajectory-source otel` keeps the whole Harness API flow and skips `GET /runs/{id}/trajectory`: it puts the run id it returned on its spans, in the attribute its mapping's `session_id` rule names, and evalkit polls the session until it settles. The harness declares `provides.trajectory: false`; evalkit still runs the trajectory evaluators because the trajectory comes from the collector.
|
|
150
|
+
|
|
151
|
+
The session evalkit polls is the `run_id` that `POST /runs` returns, so there is no separate session field. A queue-backed agent whose worker picks its own session id returns that id as `run_id`, or puts the `run_id` it returned on its spans in that session attribute. To export only eval runs from a harness that also serves production, see Choosing what a shared worker exports above.
|
|
152
|
+
|
|
153
|
+
## Importing history from a trace source
|
|
154
|
+
|
|
155
|
+
An admin registers a trace source (a Phoenix server) once. After that, any
|
|
156
|
+
user can import traces from it into an OTel agent they can write, with
|
|
157
|
+
`evalkit traces import` or the agent page's Import tab.
|
|
158
|
+
|
|
159
|
+
- The trace source's API key is shared with every user in effect: anyone can
|
|
160
|
+
import any project that key can read.
|
|
161
|
+
- Imported traces land in the agent's workspace. Every user can read every
|
|
162
|
+
workspace, so traces imported into your personal workspace are visible to
|
|
163
|
+
everyone on the server. See [Where runs happen](workspaces.md).
|
|
164
|
+
|
|
165
|
+
## Checking a session
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
evalkit traces check myagent-sdk <session id>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
prints the settle state, span kinds, tokens, the attributes the mapping did not read and the ATIF summary. `--dump spans.json` writes the raw spans. `--dump esf.json` leaves out the input messages of every LLM call after each agent's first, as the trajectory does not use them; `spans.json` has the raw values. A session over `otel.max_session_bytes` fails the check with its size and the limit (see Size limits and large spans above). If the kinds or messages look wrong, the agent needs a mapping: see [Mappings](mappings.md).
|