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,385 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from fnmatch import fnmatchcase
|
|
7
|
+
from typing import Any, NamedTuple
|
|
8
|
+
|
|
9
|
+
from evalkit.contracts.esf import (
|
|
10
|
+
AttributeRule,
|
|
11
|
+
EsfKind,
|
|
12
|
+
EsfMessage,
|
|
13
|
+
EsfSpan,
|
|
14
|
+
EsfToolCall,
|
|
15
|
+
MappingDocument,
|
|
16
|
+
MessageFormat,
|
|
17
|
+
)
|
|
18
|
+
from evalkit.core.traces.raw import RawSpan
|
|
19
|
+
|
|
20
|
+
SDK_ROOT_ATTRIBUTE = "evalkit.sdk_root"
|
|
21
|
+
TASK_SESSION_ATTRIBUTE = "evalkit.task_session"
|
|
22
|
+
SDK_SESSION_ATTRIBUTE = "session.id"
|
|
23
|
+
KINDS: frozenset[str] = frozenset({"llm", "tool", "agent", "chain", "retriever", "other"})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
SDK_ROOT_MAPPING = MappingDocument(
|
|
27
|
+
schema_version="1",
|
|
28
|
+
kind=AttributeRule(attribute="openinference.span.kind", values={"CHAIN": "chain"}),
|
|
29
|
+
session_id=AttributeRule(attribute=SDK_SESSION_ATTRIBUTE),
|
|
30
|
+
input=AttributeRule(attribute="input.value", json=True),
|
|
31
|
+
output=AttributeRule(attribute="output.value"),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def mapping_for(
|
|
36
|
+
attributes: dict[str, Any], active: MappingDocument | None
|
|
37
|
+
) -> MappingDocument | None:
|
|
38
|
+
if attributes.get(SDK_ROOT_ATTRIBUTE):
|
|
39
|
+
return SDK_ROOT_MAPPING
|
|
40
|
+
return active
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def kind_attributes(active: MappingDocument | None) -> set[str]:
|
|
44
|
+
docs = [active, SDK_ROOT_MAPPING]
|
|
45
|
+
return {SDK_ROOT_ATTRIBUTE} | {d.kind.attribute for d in docs if d and d.kind}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AttributeKeys(NamedTuple):
|
|
49
|
+
keys: frozenset[str]
|
|
50
|
+
prefixes: frozenset[str]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _rule_keys(rules: list[AttributeRule | None]) -> AttributeKeys:
|
|
54
|
+
present = [r for r in rules if r is not None]
|
|
55
|
+
return AttributeKeys(
|
|
56
|
+
frozenset(r.attribute for r in present if r.message_format != "openinference"),
|
|
57
|
+
frozenset(f"{r.attribute}." for r in present if r.message_format == "openinference"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _llm_reads(doc: MappingDocument, active: MappingDocument | None) -> AttributeKeys:
|
|
62
|
+
tokens = doc.tokens
|
|
63
|
+
rules = [doc.kind, doc.session_id, doc.model, doc.output_messages, doc.cost_usd]
|
|
64
|
+
rules += [tokens.prompt, tokens.completion, tokens.cached, tokens.cache_write]
|
|
65
|
+
read = _rule_keys(rules)
|
|
66
|
+
linked = {SDK_ROOT_ATTRIBUTE, TASK_SESSION_ATTRIBUTE, SDK_SESSION_ATTRIBUTE}
|
|
67
|
+
if active is not None and active.session_id is not None:
|
|
68
|
+
linked.add(active.session_id.attribute)
|
|
69
|
+
return AttributeKeys(read.keys | linked, read.prefixes)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def input_context(docs: list[MappingDocument], active: MappingDocument | None) -> AttributeKeys:
|
|
73
|
+
context = [_rule_keys([d.input_messages, d.input, d.system_instructions]) for d in docs]
|
|
74
|
+
reads = [_llm_reads(d, active) for d in docs]
|
|
75
|
+
read_keys = {k for r in reads for k in r.keys}
|
|
76
|
+
read_prefixes = {p for r in reads for p in r.prefixes}
|
|
77
|
+
keys = {
|
|
78
|
+
k
|
|
79
|
+
for c in context
|
|
80
|
+
for k in c.keys
|
|
81
|
+
if k not in read_keys and not any(k.startswith(p) for p in read_prefixes)
|
|
82
|
+
}
|
|
83
|
+
prefixes = {
|
|
84
|
+
p
|
|
85
|
+
for c in context
|
|
86
|
+
for p in c.prefixes
|
|
87
|
+
if not any(k.startswith(p) for k in read_keys)
|
|
88
|
+
and not any(p.startswith(q) or q.startswith(p) for q in read_prefixes)
|
|
89
|
+
}
|
|
90
|
+
return AttributeKeys(frozenset(keys), frozenset(prefixes))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def session_id_of(attributes: dict[str, Any], active: MappingDocument | None) -> str | None:
|
|
94
|
+
if attributes.get(SDK_ROOT_ATTRIBUTE) or attributes.get(TASK_SESSION_ATTRIBUTE):
|
|
95
|
+
key: str | None = SDK_SESSION_ATTRIBUTE
|
|
96
|
+
else:
|
|
97
|
+
key = active.session_id.attribute if active and active.session_id else None
|
|
98
|
+
value = attributes.get(key) if key else None
|
|
99
|
+
return None if value in (None, "") else str(value)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def try_json(value: Any) -> Any:
|
|
103
|
+
if not isinstance(value, str):
|
|
104
|
+
return value
|
|
105
|
+
try:
|
|
106
|
+
return json.loads(value)
|
|
107
|
+
except ValueError:
|
|
108
|
+
return value
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _arguments(value: Any) -> dict[str, Any] | str:
|
|
112
|
+
parsed = try_json(value)
|
|
113
|
+
if isinstance(parsed, dict):
|
|
114
|
+
return parsed
|
|
115
|
+
return value if isinstance(value, str) else json.dumps(value)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _text(content: Any) -> str | None:
|
|
119
|
+
if content is None:
|
|
120
|
+
return None
|
|
121
|
+
if isinstance(content, str):
|
|
122
|
+
return content
|
|
123
|
+
if isinstance(content, list):
|
|
124
|
+
parts = [p.get("text") for p in content if isinstance(p, dict) and p.get("type") == "text"]
|
|
125
|
+
return "".join(p for p in parts if p) or None
|
|
126
|
+
return json.dumps(content)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
_INDEXED = re.compile(r"^(\d+)\.(.*)$")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _content_text(parts: list[dict[str, Any]], kind: str) -> str | None:
|
|
133
|
+
texts = [
|
|
134
|
+
str(p["message_content.text"])
|
|
135
|
+
for p in parts
|
|
136
|
+
if p.get("message_content.type") == kind and p.get("message_content.text") is not None
|
|
137
|
+
]
|
|
138
|
+
return "".join(texts) or None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _openinference_messages(
|
|
142
|
+
attributes: dict[str, Any], key: str
|
|
143
|
+
) -> tuple[list[EsfMessage], set[str]]:
|
|
144
|
+
prefix = f"{key}."
|
|
145
|
+
read: set[str] = set()
|
|
146
|
+
messages: dict[int, dict[str, Any]] = {}
|
|
147
|
+
calls: dict[int, dict[int, dict[str, Any]]] = {}
|
|
148
|
+
for attr, value in attributes.items():
|
|
149
|
+
if not attr.startswith(prefix):
|
|
150
|
+
continue
|
|
151
|
+
matched = _INDEXED.match(attr[len(prefix) :])
|
|
152
|
+
if matched is None:
|
|
153
|
+
continue
|
|
154
|
+
read.add(attr)
|
|
155
|
+
index, rest = int(matched.group(1)), matched.group(2)
|
|
156
|
+
if rest.startswith("message.tool_calls."):
|
|
157
|
+
call_match = _INDEXED.match(rest.removeprefix("message.tool_calls."))
|
|
158
|
+
if call_match is None:
|
|
159
|
+
continue
|
|
160
|
+
call_index, call_field = int(call_match.group(1)), call_match.group(2)
|
|
161
|
+
calls.setdefault(index, {}).setdefault(call_index, {})[call_field] = value
|
|
162
|
+
else:
|
|
163
|
+
messages.setdefault(index, {})[rest] = value
|
|
164
|
+
out: list[EsfMessage] = []
|
|
165
|
+
for index in sorted(set(messages) | set(calls)):
|
|
166
|
+
fields = messages.get(index, {})
|
|
167
|
+
contents: dict[int, dict[str, Any]] = {}
|
|
168
|
+
for field, value in fields.items():
|
|
169
|
+
content_match = _INDEXED.match(field.removeprefix("message.contents."))
|
|
170
|
+
if field.startswith("message.contents.") and content_match is not None:
|
|
171
|
+
part = contents.setdefault(int(content_match.group(1)), {})
|
|
172
|
+
part[content_match.group(2)] = value
|
|
173
|
+
parts = [contents[j] for j in sorted(contents)]
|
|
174
|
+
tool_calls = [
|
|
175
|
+
EsfToolCall(
|
|
176
|
+
id=str(c.get("tool_call.id") or f"{index}.{j}"),
|
|
177
|
+
name=str(c.get("tool_call.function.name") or ""),
|
|
178
|
+
arguments=_arguments(c.get("tool_call.function.arguments")),
|
|
179
|
+
)
|
|
180
|
+
for j, c in sorted(calls.get(index, {}).items())
|
|
181
|
+
]
|
|
182
|
+
out.append(
|
|
183
|
+
EsfMessage(
|
|
184
|
+
role=str(fields.get("message.role") or "assistant"),
|
|
185
|
+
text=_text(fields.get("message.content")) or _content_text(parts, "text"),
|
|
186
|
+
reasoning=_content_text(parts, "reasoning"),
|
|
187
|
+
tool_calls=tool_calls or None,
|
|
188
|
+
tool_call_id=fields.get("message.tool_call_id"),
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
return out, read
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _gen_ai_messages(attributes: dict[str, Any], key: str) -> tuple[list[EsfMessage], set[str]]:
|
|
195
|
+
raw = try_json(attributes.get(key))
|
|
196
|
+
if not isinstance(raw, list):
|
|
197
|
+
return [], {key}
|
|
198
|
+
out: list[EsfMessage] = []
|
|
199
|
+
for message in raw:
|
|
200
|
+
if not isinstance(message, dict):
|
|
201
|
+
continue
|
|
202
|
+
texts: list[str] = []
|
|
203
|
+
reasoning: list[str] = []
|
|
204
|
+
tool_calls: list[EsfToolCall] = []
|
|
205
|
+
tool_call_id: str | None = None
|
|
206
|
+
for part in message.get("parts") or []:
|
|
207
|
+
if not isinstance(part, dict):
|
|
208
|
+
continue
|
|
209
|
+
kind = part.get("type")
|
|
210
|
+
if kind == "text" and part.get("content") is not None:
|
|
211
|
+
texts.append(str(part["content"]))
|
|
212
|
+
elif kind == "reasoning" and part.get("content") is not None:
|
|
213
|
+
reasoning.append(str(part["content"]))
|
|
214
|
+
elif kind == "tool_call":
|
|
215
|
+
tool_calls.append(
|
|
216
|
+
EsfToolCall(
|
|
217
|
+
id=str(part.get("id") or ""),
|
|
218
|
+
name=str(part.get("name") or ""),
|
|
219
|
+
arguments=_arguments(part.get("arguments")),
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
elif kind == "tool_call_response":
|
|
223
|
+
tool_call_id = str(part.get("id") or "") or None
|
|
224
|
+
result = part["response"] if "response" in part else part.get("result")
|
|
225
|
+
texts.append(result if isinstance(result, str) else json.dumps(result))
|
|
226
|
+
out.append(
|
|
227
|
+
EsfMessage(
|
|
228
|
+
role=str(message.get("role") or "assistant"),
|
|
229
|
+
text="".join(texts) or None,
|
|
230
|
+
reasoning="".join(reasoning) or None,
|
|
231
|
+
tool_calls=tool_calls or None,
|
|
232
|
+
tool_call_id=tool_call_id,
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
return out, {key}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def gen_ai_messages(value: Any) -> list[EsfMessage] | None:
|
|
239
|
+
if not isinstance(value, list) or not value:
|
|
240
|
+
return None
|
|
241
|
+
if not all(isinstance(m, dict) and isinstance(m.get("parts"), list) for m in value):
|
|
242
|
+
return None
|
|
243
|
+
return _gen_ai_messages({"messages": value}, "messages")[0]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _instructions(value: Any) -> str | None:
|
|
247
|
+
if isinstance(value, list):
|
|
248
|
+
texts = [
|
|
249
|
+
str(p.get("content") if p.get("content") is not None else p.get("text") or "")
|
|
250
|
+
for p in value
|
|
251
|
+
if isinstance(p, dict) and p.get("type") == "text"
|
|
252
|
+
]
|
|
253
|
+
return "".join(texts) or None
|
|
254
|
+
return _str(value) or None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _openai_messages(attributes: dict[str, Any], key: str) -> tuple[list[EsfMessage], set[str]]:
|
|
258
|
+
raw = try_json(attributes.get(key))
|
|
259
|
+
if not isinstance(raw, list):
|
|
260
|
+
return [], {key}
|
|
261
|
+
out: list[EsfMessage] = []
|
|
262
|
+
for message in raw:
|
|
263
|
+
if not isinstance(message, dict):
|
|
264
|
+
continue
|
|
265
|
+
tool_calls = [
|
|
266
|
+
EsfToolCall(
|
|
267
|
+
id=str(c.get("id") or ""),
|
|
268
|
+
name=str((c.get("function") or {}).get("name") or ""),
|
|
269
|
+
arguments=_arguments((c.get("function") or {}).get("arguments")),
|
|
270
|
+
)
|
|
271
|
+
for c in message.get("tool_calls") or []
|
|
272
|
+
if isinstance(c, dict)
|
|
273
|
+
]
|
|
274
|
+
out.append(
|
|
275
|
+
EsfMessage(
|
|
276
|
+
role=str(message.get("role") or "assistant"),
|
|
277
|
+
text=_text(message.get("content")),
|
|
278
|
+
tool_calls=tool_calls or None,
|
|
279
|
+
tool_call_id=message.get("tool_call_id"),
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
return out, {key}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
MESSAGE_FORMATS: dict[
|
|
286
|
+
MessageFormat, Callable[[dict[str, Any], str], tuple[list[EsfMessage], set[str]]]
|
|
287
|
+
] = {
|
|
288
|
+
"openinference": _openinference_messages,
|
|
289
|
+
"gen_ai": _gen_ai_messages,
|
|
290
|
+
"openai": _openai_messages,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _int(value: Any) -> int | None:
|
|
295
|
+
if value is None or isinstance(value, bool):
|
|
296
|
+
return None
|
|
297
|
+
if isinstance(value, int | float):
|
|
298
|
+
return int(value)
|
|
299
|
+
if isinstance(value, str) and value.strip().lstrip("-").isdigit():
|
|
300
|
+
return int(value)
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _float(value: Any) -> float | None:
|
|
305
|
+
if value is None or isinstance(value, bool):
|
|
306
|
+
return None
|
|
307
|
+
if isinstance(value, int | float):
|
|
308
|
+
return float(value)
|
|
309
|
+
if isinstance(value, str):
|
|
310
|
+
try:
|
|
311
|
+
return float(value)
|
|
312
|
+
except ValueError:
|
|
313
|
+
return None
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _str(value: Any) -> str | None:
|
|
318
|
+
return None if value is None else str(value)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def to_esf(raw: RawSpan, mapping: MappingDocument | None) -> EsfSpan:
|
|
322
|
+
read: set[str] = set()
|
|
323
|
+
attributes = raw.attributes
|
|
324
|
+
|
|
325
|
+
def rule(r: AttributeRule | None) -> Any:
|
|
326
|
+
if r is None:
|
|
327
|
+
return None
|
|
328
|
+
if r.message_format is not None:
|
|
329
|
+
messages, keys = MESSAGE_FORMATS[r.message_format](attributes, r.attribute)
|
|
330
|
+
read.update(keys)
|
|
331
|
+
return messages
|
|
332
|
+
read.add(r.attribute)
|
|
333
|
+
value = attributes.get(r.attribute)
|
|
334
|
+
if r.values is not None:
|
|
335
|
+
value = r.values.get(str(value), "other") if value is not None else None
|
|
336
|
+
if r.json_:
|
|
337
|
+
value = try_json(value)
|
|
338
|
+
return value
|
|
339
|
+
|
|
340
|
+
doc = mapping if mapping is not None else MappingDocument(schema_version="1")
|
|
341
|
+
known = doc.known_attributes
|
|
342
|
+
rule(doc.session_id)
|
|
343
|
+
kind_value = rule(doc.kind)
|
|
344
|
+
kind: EsfKind = kind_value if kind_value in KINDS else "other"
|
|
345
|
+
is_llm = kind == "llm"
|
|
346
|
+
span = EsfSpan(
|
|
347
|
+
span_id=raw.span_id,
|
|
348
|
+
trace_id=raw.trace_id,
|
|
349
|
+
parent_id=raw.parent_span_id,
|
|
350
|
+
name=raw.name,
|
|
351
|
+
kind=kind,
|
|
352
|
+
started_at=raw.start_time,
|
|
353
|
+
ended_at=raw.end_time,
|
|
354
|
+
error=(raw.status_message or "error") if raw.status_code == "ERROR" else None,
|
|
355
|
+
model=_str(rule(doc.model)),
|
|
356
|
+
input_messages=rule(doc.input_messages) if is_llm else None,
|
|
357
|
+
output_messages=rule(doc.output_messages) if is_llm else None,
|
|
358
|
+
input=rule(doc.input) if not is_llm else None,
|
|
359
|
+
output=rule(doc.output) if not is_llm else None,
|
|
360
|
+
tool_name=_str(rule(doc.tool.name)) if kind == "tool" else None,
|
|
361
|
+
tool_call_id=_str(rule(doc.tool.call_id)) if kind in ("tool", "agent") else None,
|
|
362
|
+
agent_name=_str(rule(doc.agent_name)) if kind == "agent" else None,
|
|
363
|
+
prompt_tokens=_int(rule(doc.tokens.prompt)),
|
|
364
|
+
completion_tokens=_int(rule(doc.tokens.completion)),
|
|
365
|
+
cached_tokens=_int(rule(doc.tokens.cached)),
|
|
366
|
+
cache_write_tokens=_int(rule(doc.tokens.cache_write)),
|
|
367
|
+
cost_usd=_float(rule(doc.cost_usd)),
|
|
368
|
+
)
|
|
369
|
+
if is_llm and (system := _instructions(rule(doc.system_instructions))):
|
|
370
|
+
span.input_messages = [EsfMessage(role="system", text=system), *(span.input_messages or [])]
|
|
371
|
+
if kind == "tool":
|
|
372
|
+
if doc.tool.arguments is not None:
|
|
373
|
+
span.input = rule(doc.tool.arguments)
|
|
374
|
+
if doc.tool.result is not None:
|
|
375
|
+
span.output = rule(doc.tool.result)
|
|
376
|
+
sdk_stamped = any(k.startswith("evalkit.") for k in attributes)
|
|
377
|
+
span.unmapped = [
|
|
378
|
+
k
|
|
379
|
+
for k in attributes
|
|
380
|
+
if k not in read
|
|
381
|
+
and not k.startswith("evalkit.")
|
|
382
|
+
and not (sdk_stamped and k == SDK_SESSION_ATTRIBUTE)
|
|
383
|
+
and not any(fnmatchcase(k, pattern) for pattern in known)
|
|
384
|
+
]
|
|
385
|
+
return span
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
from collections.abc import Awaitable, Callable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from evalkit.core.traces.sessions import SessionReader, SessionTooLarge
|
|
10
|
+
|
|
11
|
+
DEFAULT_POLL_INTERVAL = 5.0
|
|
12
|
+
LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def poll_trajectory(
|
|
16
|
+
sessions: SessionReader,
|
|
17
|
+
agent: str,
|
|
18
|
+
session_id: str,
|
|
19
|
+
deadline_seconds: float,
|
|
20
|
+
*,
|
|
21
|
+
poll_interval: float = DEFAULT_POLL_INTERVAL,
|
|
22
|
+
clock: Callable[[], float] = time.monotonic,
|
|
23
|
+
sleep: Callable[[float], Awaitable[None]] = asyncio.sleep,
|
|
24
|
+
) -> tuple[dict[str, Any] | None, str]:
|
|
25
|
+
started = clock()
|
|
26
|
+
while True:
|
|
27
|
+
try:
|
|
28
|
+
resp = await sessions.trajectory(agent, session_id)
|
|
29
|
+
except SessionTooLarge as exc:
|
|
30
|
+
LOGGER.warning("no trajectory for session %s: %s", session_id, exc)
|
|
31
|
+
return None, "unavailable"
|
|
32
|
+
if resp.settled and resp.root_present:
|
|
33
|
+
return resp.trajectory, "valid"
|
|
34
|
+
if clock() - started > deadline_seconds:
|
|
35
|
+
if resp.root_present:
|
|
36
|
+
return resp.trajectory, "partial"
|
|
37
|
+
return None, "unavailable"
|
|
38
|
+
await sleep(poll_interval)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RawSpan(BaseModel):
|
|
8
|
+
trace_id: str
|
|
9
|
+
span_id: str
|
|
10
|
+
parent_span_id: str | None = None
|
|
11
|
+
name: str
|
|
12
|
+
kind: str = "INTERNAL"
|
|
13
|
+
start_time: datetime
|
|
14
|
+
end_time: datetime | None = None
|
|
15
|
+
status_code: str = "UNSET"
|
|
16
|
+
status_message: str | None = None
|
|
17
|
+
attributes: dict[str, Any] = Field(default_factory=dict)
|
|
18
|
+
events: list[dict[str, Any]] = Field(default_factory=list)
|
|
19
|
+
resource: dict[str, Any] = Field(default_factory=dict)
|
|
20
|
+
received_at: datetime | None = None
|
|
21
|
+
session_id_hint: str | None = Field(default=None, exclude=True)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, NamedTuple, Protocol
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from evalkit.contracts.esf import EsfSpan, MappingDocument
|
|
10
|
+
from evalkit.core.traces.convert import first_llm_spans, to_atif
|
|
11
|
+
from evalkit.core.traces.mapping import (
|
|
12
|
+
SDK_ROOT_ATTRIBUTE,
|
|
13
|
+
TASK_SESSION_ATTRIBUTE,
|
|
14
|
+
AttributeKeys,
|
|
15
|
+
input_context,
|
|
16
|
+
kind_attributes,
|
|
17
|
+
mapping_for,
|
|
18
|
+
session_id_of,
|
|
19
|
+
to_esf,
|
|
20
|
+
)
|
|
21
|
+
from evalkit.core.traces.raw import RawSpan
|
|
22
|
+
from evalkit.core.traces.settle import root_present, settled
|
|
23
|
+
|
|
24
|
+
LINK_ATTRIBUTES = (SDK_ROOT_ATTRIBUTE, TASK_SESSION_ATTRIBUTE)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SessionTrajectory(BaseModel):
|
|
28
|
+
settled: bool
|
|
29
|
+
root_present: bool
|
|
30
|
+
span_count: int
|
|
31
|
+
mapping_version: int | None = None
|
|
32
|
+
unmapped_attributes: list[str] = Field(default_factory=list)
|
|
33
|
+
trajectory: dict[str, Any] | None = None
|
|
34
|
+
esf: list[EsfSpan] | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SessionReader(Protocol):
|
|
38
|
+
async def trajectory(self, agent: str, session_id: str) -> SessionTrajectory: ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def agent_spans(spans: list[RawSpan]) -> list[RawSpan]:
|
|
42
|
+
linked = [
|
|
43
|
+
s
|
|
44
|
+
for s in spans
|
|
45
|
+
if s.parent_span_id is None
|
|
46
|
+
and s.attributes.get(SDK_ROOT_ATTRIBUTE)
|
|
47
|
+
and s.attributes.get(TASK_SESSION_ATTRIBUTE)
|
|
48
|
+
]
|
|
49
|
+
if not linked:
|
|
50
|
+
return spans
|
|
51
|
+
linked_traces = {s.trace_id for s in linked}
|
|
52
|
+
others = [s for s in spans if s.trace_id not in linked_traces]
|
|
53
|
+
if others:
|
|
54
|
+
return others
|
|
55
|
+
parents = {s.parent_span_id for s in spans}
|
|
56
|
+
if any(root.span_id in parents for root in linked):
|
|
57
|
+
return spans
|
|
58
|
+
return []
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SessionTooLarge(Exception):
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class LaterLlmSpans(NamedTuple):
|
|
66
|
+
positions: list[int]
|
|
67
|
+
context: AttributeKeys
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def probe_attributes(active: MappingDocument | None) -> set[str]:
|
|
71
|
+
return kind_attributes(active) | set(LINK_ATTRIBUTES)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def later_llm_spans(probes: list[RawSpan], active: MappingDocument | None) -> LaterLlmSpans:
|
|
75
|
+
position = {id(span): i for i, span in enumerate(probes)}
|
|
76
|
+
kept = agent_spans(probes)
|
|
77
|
+
mappings = [mapping_for(span.attributes, active) for span in kept]
|
|
78
|
+
esf = [to_esf(span, mapping) for span, mapping in zip(kept, mappings, strict=True)]
|
|
79
|
+
first = {id(span) for span in first_llm_spans(esf, active)}
|
|
80
|
+
later = [
|
|
81
|
+
(position[id(raw)], mapping)
|
|
82
|
+
for raw, mapping, span in zip(kept, mappings, esf, strict=True)
|
|
83
|
+
if span.kind == "llm" and id(span) not in first and mapping is not None
|
|
84
|
+
]
|
|
85
|
+
docs = list({id(mapping): mapping for _, mapping in later}.values())
|
|
86
|
+
return LaterLlmSpans([i for i, _ in later], input_context(docs, active))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class SessionBuilder:
|
|
90
|
+
def __init__(self, active_mapping: MappingDocument | None):
|
|
91
|
+
self._active_mapping = active_mapping
|
|
92
|
+
self._texts: dict[str, str] = {}
|
|
93
|
+
self._spans: list[tuple[RawSpan, EsfSpan, str | None]] = []
|
|
94
|
+
|
|
95
|
+
def _shared(self, text: str | None) -> str | None:
|
|
96
|
+
return None if text is None else self._texts.setdefault(text, text)
|
|
97
|
+
|
|
98
|
+
def add(self, raw: RawSpan) -> None:
|
|
99
|
+
attributes = raw.attributes
|
|
100
|
+
esf = to_esf(raw, mapping_for(attributes, self._active_mapping))
|
|
101
|
+
for message in [*(esf.input_messages or []), *(esf.output_messages or [])]:
|
|
102
|
+
message.text = self._shared(message.text)
|
|
103
|
+
message.reasoning = self._shared(message.reasoning)
|
|
104
|
+
head = raw.model_copy(
|
|
105
|
+
update={
|
|
106
|
+
"attributes": {k: attributes[k] for k in LINK_ATTRIBUTES if k in attributes},
|
|
107
|
+
"events": [],
|
|
108
|
+
"resource": {},
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
self._spans.append((head, esf, session_id_of(attributes, self._active_mapping)))
|
|
112
|
+
|
|
113
|
+
def build(
|
|
114
|
+
self,
|
|
115
|
+
*,
|
|
116
|
+
agent_name: str,
|
|
117
|
+
mapping_version: int | None,
|
|
118
|
+
now: datetime,
|
|
119
|
+
settle_seconds: int,
|
|
120
|
+
include_esf: bool = False,
|
|
121
|
+
) -> SessionTrajectory:
|
|
122
|
+
kept_heads = {id(head) for head in agent_spans([head for head, _, _ in self._spans])}
|
|
123
|
+
kept = [entry for entry in self._spans if id(entry[0]) in kept_heads]
|
|
124
|
+
self._spans = []
|
|
125
|
+
self._texts = {}
|
|
126
|
+
heads = [head for head, _, _ in kept]
|
|
127
|
+
esf = [span for _, span, _ in kept]
|
|
128
|
+
unmapped = sorted({key for s in esf for key in s.unmapped})
|
|
129
|
+
result = SessionTrajectory(
|
|
130
|
+
settled=settled(heads, now, settle_seconds),
|
|
131
|
+
root_present=root_present(heads),
|
|
132
|
+
span_count=len(heads),
|
|
133
|
+
mapping_version=mapping_version,
|
|
134
|
+
unmapped_attributes=unmapped,
|
|
135
|
+
esf=esf if include_esf else None,
|
|
136
|
+
)
|
|
137
|
+
if not heads:
|
|
138
|
+
return result
|
|
139
|
+
received = [s.received_at for s in heads if s.received_at is not None]
|
|
140
|
+
session_id = next((found for _, _, found in kept if found), None)
|
|
141
|
+
trajectory = to_atif(
|
|
142
|
+
esf,
|
|
143
|
+
self._active_mapping,
|
|
144
|
+
agent_name=agent_name,
|
|
145
|
+
session_id=session_id,
|
|
146
|
+
mapping_version=mapping_version,
|
|
147
|
+
incomplete_ended_at=max(received) if received else now,
|
|
148
|
+
)
|
|
149
|
+
result.trajectory = trajectory.model_dump(exclude_none=True)
|
|
150
|
+
return result
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def build_session_trajectory(
|
|
154
|
+
spans: Iterable[RawSpan],
|
|
155
|
+
*,
|
|
156
|
+
agent_name: str,
|
|
157
|
+
active_mapping: MappingDocument | None,
|
|
158
|
+
mapping_version: int | None,
|
|
159
|
+
now: datetime,
|
|
160
|
+
settle_seconds: int,
|
|
161
|
+
include_esf: bool = False,
|
|
162
|
+
) -> SessionTrajectory:
|
|
163
|
+
builder = SessionBuilder(active_mapping)
|
|
164
|
+
for span in spans:
|
|
165
|
+
builder.add(span)
|
|
166
|
+
return builder.build(
|
|
167
|
+
agent_name=agent_name,
|
|
168
|
+
mapping_version=mapping_version,
|
|
169
|
+
now=now,
|
|
170
|
+
settle_seconds=settle_seconds,
|
|
171
|
+
include_esf=include_esf,
|
|
172
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
|
|
3
|
+
from evalkit.core.traces.raw import RawSpan
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def settled(spans: list[RawSpan], now: datetime, settle_seconds: int) -> bool:
|
|
7
|
+
received = [s.received_at for s in spans if s.received_at is not None]
|
|
8
|
+
if not received:
|
|
9
|
+
return False
|
|
10
|
+
return max(received) < now - timedelta(seconds=settle_seconds)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def root_present(spans: list[RawSpan]) -> bool:
|
|
14
|
+
return any(s.parent_span_id is None for s in spans)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
|
|
3
|
+
from evalkit.contracts.enums import TraceProvider
|
|
4
|
+
from evalkit.core.traces.sources.base import TracePage, TraceSourceClient
|
|
5
|
+
from evalkit.core.traces.sources.phoenix import PhoenixClient
|
|
6
|
+
|
|
7
|
+
__all__ = ["TracePage", "TraceSourceClient", "client_for"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def client_for(
|
|
11
|
+
provider: TraceProvider,
|
|
12
|
+
url: str,
|
|
13
|
+
api_key: str | None,
|
|
14
|
+
page_traces: int,
|
|
15
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
16
|
+
) -> TraceSourceClient:
|
|
17
|
+
match provider:
|
|
18
|
+
case TraceProvider.PHOENIX:
|
|
19
|
+
return PhoenixClient(url, api_key, page_traces, transport=transport)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from collections.abc import AsyncIterator
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
from evalkit.core.traces.raw import RawSpan
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class TracePage:
|
|
11
|
+
traces: list[tuple[str, datetime]]
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def last_start(self) -> datetime:
|
|
15
|
+
return self.traces[-1][1]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TraceSourceClient(Protocol):
|
|
19
|
+
async def session_spans(self, project: str, session_id: str) -> list[RawSpan]: ...
|
|
20
|
+
|
|
21
|
+
def trace_pages(
|
|
22
|
+
self, project: str, since: datetime, until: datetime
|
|
23
|
+
) -> AsyncIterator[TracePage]: ...
|
|
24
|
+
|
|
25
|
+
def trace_spans(self, project: str, trace_ids: list[str]) -> AsyncIterator[list[RawSpan]]: ...
|