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,333 @@
|
|
|
1
|
+
# Config and deployment
|
|
2
|
+
|
|
3
|
+
## Trying evalkit first
|
|
4
|
+
|
|
5
|
+
To see evalkit work before deploying it, run the try-it stack in `../try/`,
|
|
6
|
+
following [Try locally](../../evalkit-docs/try-locally.md). It uses the same
|
|
7
|
+
image and your own LLM, and runs a sample agent through an SDK run with traces.
|
|
8
|
+
|
|
9
|
+
## Deployment shape
|
|
10
|
+
|
|
11
|
+
One prebuilt docker image: server + built UI + skills + cost-map snapshot. This skill ships a [`docker-compose.yml`](../docker-compose.yml)
|
|
12
|
+
that runs `server` + `postgres`; it pulls the image from
|
|
13
|
+
`EVALKIT_IMAGE` (set it to the image you were given — registries differ per
|
|
14
|
+
client, so there is no default). Postgres holds everything evalkit stores
|
|
15
|
+
except bytes: users, workspaces, agents, cases, experiments and scores.
|
|
16
|
+
External services are first-class:
|
|
17
|
+
|
|
18
|
+
- An existing Postgres: set `db.url` and drop the bundled postgres service.
|
|
19
|
+
- Blob storage: `file://` (mount a volume) or `s3://`-style URL.
|
|
20
|
+
|
|
21
|
+
The **UI is part of the same server image** and served at the server root —
|
|
22
|
+
there is no separate UI deployment. At runtime the FastAPI app serves
|
|
23
|
+
`/assets` and returns `index.html` for every non-`/api` path (so client-side
|
|
24
|
+
routes survive a refresh); deploying the image deploys the UI. The PyPI
|
|
25
|
+
wheel (`evalkit-py`) bundles the UI too, so `pip install "evalkit-py[server]"`
|
|
26
|
+
and `evalkit serve` also serve it.
|
|
27
|
+
|
|
28
|
+
## Setup sequence (exact commands)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Copy this skill's deployment files into your deploy dir, then edit them:
|
|
32
|
+
cp <this-skill-dir>/config.example.yaml config.yaml # fill in: blob, encryption_key
|
|
33
|
+
cp <this-skill-dir>/docker-compose.yml .
|
|
34
|
+
# Env referenced by config.example.yaml + compose: EVALKIT_IMAGE (the server
|
|
35
|
+
# image you were given), POSTGRES_PASSWORD, EVALKIT_ENCRYPTION_KEY.
|
|
36
|
+
# API keys are not in config: the commands below store them in the database.
|
|
37
|
+
# First start: EVALKIT_ADMIN_PASSWORD (8+ characters, at most 72 bytes)
|
|
38
|
+
# creates the admin user; a server with no admin and no
|
|
39
|
+
# EVALKIT_ADMIN_PASSWORD refuses to start. Put these in a .env
|
|
40
|
+
# file next to docker-compose.yml.
|
|
41
|
+
# With db.migrate_on_start: true in config.yaml (see "Migrations on start"),
|
|
42
|
+
# this one command starts everything and migrates:
|
|
43
|
+
docker compose up -d
|
|
44
|
+
# Without it, migrate before starting the server — it verifies the schema and
|
|
45
|
+
# refuses to boot when it is missing or outdated:
|
|
46
|
+
# docker compose up -d postgres
|
|
47
|
+
# docker compose run --rm --no-deps server evalkit db upgrade --config /etc/evalkit/config.yaml
|
|
48
|
+
# docker compose up -d
|
|
49
|
+
uv tool install --reinstall "evalkit-py==$(curl -fsSL http://localhost:8020/api/cli | jq -r .version)" # the CLI, from PyPI at the server's version
|
|
50
|
+
evalkit login --url http://localhost:8020 # admin / $EVALKIT_ADMIN_PASSWORD
|
|
51
|
+
evalkit users add <teammate> # optional, admin only
|
|
52
|
+
evalkit workspaces create dev # admin only; then prod
|
|
53
|
+
evalkit workspaces members add dev <teammate> --role writer # or --role reviewer
|
|
54
|
+
evalkit llm-providers create openai --base-url https://api.openai.com/v1 --api-key-env OPENAI_API_KEY # admin only
|
|
55
|
+
evalkit judge-default set --provider openai --model gpt-5 # admin only
|
|
56
|
+
evalkit agents create <name> --workspace dev --harness-url https://agent-dev.internal/evalkit/v1 --api-key-env AGENT_DEV_KEY
|
|
57
|
+
evalkit sync <cases-dir>/<suite> --workspace dev # your case YAML; uploads its fixtures too
|
|
58
|
+
evalkit harness check <name>@dev # all declared capabilities must pass
|
|
59
|
+
evalkit run <suite>@dev --agent <name>@dev # first experiment
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The UI serves at the server root (http://localhost:8020); the server's own
|
|
63
|
+
API reference is at `/docs` (Swagger, generated by FastAPI). Config flow: the
|
|
64
|
+
compose file bind-mounts your `config.yaml` to `/etc/evalkit/config.yaml` and
|
|
65
|
+
passes secrets as environment variables, which `env:VAR` values resolve at
|
|
66
|
+
load.
|
|
67
|
+
|
|
68
|
+
**Health check:** the server exposes `GET /api/health` → `{"ok": true}` (200
|
|
69
|
+
once the app is up). Use it for load-balancer / Kubernetes liveness and
|
|
70
|
+
readiness probes rather than `/` (which returns the SPA `index.html`).
|
|
71
|
+
|
|
72
|
+
## Migrations on start
|
|
73
|
+
|
|
74
|
+
`db.migrate_on_start: true` makes the server run `evalkit db upgrade` itself
|
|
75
|
+
before it starts serving. It holds a Postgres advisory lock while it
|
|
76
|
+
migrates, so replicas that start together migrate one at a time and the rest
|
|
77
|
+
find the schema current.
|
|
78
|
+
|
|
79
|
+
- Turn it on for a single-node compose deployment: `docker compose up -d` is
|
|
80
|
+
then the whole start, and an upgrade is pull the new image and `up -d`
|
|
81
|
+
again.
|
|
82
|
+
- Leave it off when a separate step runs migrations, for example a Helm
|
|
83
|
+
pre-upgrade hook, or when you want to back up and run a migration by hand.
|
|
84
|
+
A release whose notes call for a maintenance window needs that window whatever this setting says.
|
|
85
|
+
- There is no downgrade. Back up the database before you pull a new image
|
|
86
|
+
with this on, the same as before a manual `db upgrade`.
|
|
87
|
+
|
|
88
|
+
## Self-managed Postgres
|
|
89
|
+
|
|
90
|
+
To use an existing Postgres instead of the bundled `postgres` service, create
|
|
91
|
+
a dedicated role and database, then point `db.url` at it and drop the
|
|
92
|
+
`postgres` service (and its `depends_on`) from `docker-compose.yml`. evalkit
|
|
93
|
+
keeps all its records in this database; back it up like any production
|
|
94
|
+
database.
|
|
95
|
+
|
|
96
|
+
As a Postgres superuser (e.g. `psql -U postgres`):
|
|
97
|
+
|
|
98
|
+
```sql
|
|
99
|
+
CREATE ROLE evalkit WITH LOGIN PASSWORD '<password>';
|
|
100
|
+
-- Postgres 16+ (and managed Postgres like RDS/Aurora/Cloud SQL, where the
|
|
101
|
+
-- admin is not a true superuser) requires you to be a member of a role to
|
|
102
|
+
-- create objects owned by it. Grant it to yourself first:
|
|
103
|
+
GRANT evalkit TO CURRENT_USER;
|
|
104
|
+
CREATE DATABASE evalkit OWNER evalkit;
|
|
105
|
+
-- `evalkit db upgrade` creates tables in the public schema. On Postgres 15+
|
|
106
|
+
-- non-owners cannot create in `public` by default, so make evalkit own it:
|
|
107
|
+
\connect evalkit
|
|
108
|
+
ALTER SCHEMA public OWNER TO evalkit;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`env:` resolves a whole value only (it can't be embedded mid-string), so keep
|
|
112
|
+
the password out of `config.yaml` by putting the entire URL in an env var —
|
|
113
|
+
exactly as the bundled compose does for the built-in postgres:
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
# config.yaml
|
|
117
|
+
db:
|
|
118
|
+
url: env:EVALKIT_DATABASE_URL
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# .env (async psycopg driver, host reachable from the server container)
|
|
123
|
+
EVALKIT_DATABASE_URL=postgresql+psycopg://evalkit:<password>@<host>:5432/evalkit
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Run `evalkit db upgrade` (via `docker compose run ... server evalkit db upgrade`
|
|
127
|
+
or the CLI) to create the schema, exactly as in the setup sequence above.
|
|
128
|
+
|
|
129
|
+
## config.yaml
|
|
130
|
+
|
|
131
|
+
Start from `config.example.yaml`. Any string value supports `env:VAR`
|
|
132
|
+
indirection, resolved at load time — keep secrets out of the file.
|
|
133
|
+
|
|
134
|
+
| Section | What it holds |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `server` | `base_url` (used in links the CLI prints), optional `accent_color` (any CSS colour, e.g. `'#dc2626'`; tints the web UI so each environment is told apart at a glance) |
|
|
137
|
+
| `db` | Postgres URL (`postgresql+psycopg://…`); optional `migrate_on_start` (default `false`, see below) |
|
|
138
|
+
| `blob` | one blob URL: `fixtures/sha256/<sha256>` (case input files), `trajectories/`, `artifacts/` |
|
|
139
|
+
| `encryption_key` | Fernet key that encrypts the stored API keys of agents, remote evaluators, LLM providers and trace sources. The server refuses to start without it once any exist. `evalkit db rekey --config config.yaml --old-key-env OLD --new-key-env NEW` re-encrypts all of them after a change. |
|
|
140
|
+
| `allow_private_endpoints` | default `false`. The server refuses (400) a harness URL, evaluator endpoint or LLM provider base URL that resolves to a loopback, private, link-local, unspecified, multicast or reserved address, or does not resolve. Set `true` when they run in the same cluster or on `host.docker.internal`. |
|
|
141
|
+
| `sentry` | optional: `dsn`, `environment`, `traces_sample_rate` (default `0.0`, errors only), `max_request_body_size` (default `never`; `small` up to 1 KB, `medium` up to 10 KB, `always`; bodies can hold client case and trajectory data). With it, the server reports unhandled errors, 500 responses and error logs to Sentry, tagged with release `evalkit@<version>`. IPs, cookies and sensitive headers are never sent. |
|
|
142
|
+
| `cost_map` | `bundled` (snapshot in the image) or a path/URL override |
|
|
143
|
+
| `auth` | `token_ttl_hours` for UI/CLI tokens |
|
|
144
|
+
| `mcp` | optional `oauth`: `issuer`, `jwks_uri`, optional `audience` (default `<server.base_url>/mcp`), `algorithm` (default `RS256`), `scope` (default `evalkit:read`: the scope name your identity provider grants for read access). With it, `/mcp` also accepts that identity provider's JWTs carrying `scope`; evalkit login tokens work either way |
|
|
145
|
+
| `otel` | collector limits, below |
|
|
146
|
+
| `limits` | request body caps, below |
|
|
147
|
+
|
|
148
|
+
### Request body limits
|
|
149
|
+
|
|
150
|
+
The server reads request bodies as a stream and stops at the cap. A request
|
|
151
|
+
over a cap gets `413` before the server reads past it, and the server logs
|
|
152
|
+
the route, the cap and the size received.
|
|
153
|
+
Fixture uploads and trajectory or artifact writes stream straight to blob
|
|
154
|
+
storage, so a large upload does not sit in server memory. Downloads of
|
|
155
|
+
fixtures, experiment blobs and stored run files stream too. Sending a
|
|
156
|
+
fixture to a harness as a multipart upload first copies it to a temporary
|
|
157
|
+
file in the server's temp directory (`/tmp` in the image). Give it room for
|
|
158
|
+
the fixtures of the runs in flight: each agent's `max_concurrent_runs`,
|
|
159
|
+
summed over the agents running at once, times the largest case's fixtures.
|
|
160
|
+
In Kubernetes, `/tmp` on the container counts against the pod's ephemeral
|
|
161
|
+
storage, and running out gets the pod evicted: mount an `emptyDir` at `/tmp`
|
|
162
|
+
and set `ephemeral-storage` requests and limits to cover it. Run workspace
|
|
163
|
+
files and folder zips stream too, from blob storage or the harness, one
|
|
164
|
+
chunk at a time.
|
|
165
|
+
|
|
166
|
+
On S3 or another object store, add a bucket lifecycle rule that aborts
|
|
167
|
+
incomplete multipart uploads after a day and expires objects under
|
|
168
|
+
`fixtures/staging/` after a day. A client that disconnects, or a server that
|
|
169
|
+
stops, in the middle of an upload can leave either behind, and evalkit does
|
|
170
|
+
not clean them up.
|
|
171
|
+
|
|
172
|
+
| Key | Default | Applies to |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `limits.fixture_upload_bytes` | 500 MB (`524288000`) | one fixture file uploaded by `evalkit sync` |
|
|
175
|
+
| `limits.experiment_blob_bytes` | 500 MB (`524288000`) | one trajectory or artifact a local or SDK run writes |
|
|
176
|
+
| `limits.case_upload_bytes` | 50 MB (`52428800`) | the case YAML files of one `evalkit sync` or `evalkit lint` |
|
|
177
|
+
| `limits.request_bytes` | 10 MB (`10485760`) | every other request body (JSON) |
|
|
178
|
+
| `otel.max_batch_bytes` | 16 MB | one collector export, see below |
|
|
179
|
+
|
|
180
|
+
The `413` detail names the cap and its key, and the CLI prints it, e.g.
|
|
181
|
+
`fixture upload is larger than the 500 MB limit (server config limits.fixture_upload_bytes)`.
|
|
182
|
+
Raise the key in `config.yaml` and restart the server to allow larger
|
|
183
|
+
bodies. A proxy in front of the server may have its own, smaller limit.
|
|
184
|
+
|
|
185
|
+
`credentials:` and `judge_default:` are no longer accepted. A config that
|
|
186
|
+
still has them stops the server at start, and the error prints the commands
|
|
187
|
+
that recreate them in the database.
|
|
188
|
+
|
|
189
|
+
Agents, remote evaluators, their API keys, LLM providers, the default judge,
|
|
190
|
+
trace sources, workspaces and their reviewers are not in `config.yaml`. They live in the database and are managed with
|
|
191
|
+
the CLI or the UI:
|
|
192
|
+
|
|
193
|
+
| Thing | Create with | Notes |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| Agent | `evalkit agents create <name> --workspace dev --harness-url … --api-key-env VAR` | admin or writer; the key is stored encrypted and never shown (`agents show` prints `api key set\|not set`); omit `--harness-url` for a code-driven agent; `--trajectory-source otel` needs `--mapping`; run settings such as `--max-concurrent-runs` (min'd with the harness's own limit), `--run-timeout`, `--stop-grace` are flags; `evalkit agents edit` changes them |
|
|
196
|
+
| Remote evaluator | `evalkit evaluators create <name> --workspace dev --endpoint … --kind score --api-key-env VAR` | admin or writer; `--kind gauge` for measurements; runs use the evaluators in the agent's workspace |
|
|
197
|
+
| LLM provider | `evalkit llm-providers create <name> --base-url … --api-key-env VAR` | admin only; server runs may only use a judge whose base URL is an active provider |
|
|
198
|
+
| Default judge | `evalkit judge-default set --provider <name> --model M [--effort E] [--max-tokens N] [--concurrency N]` | admin only; `evalkit judge-default show` prints it. Use a reproducibility-tested model |
|
|
199
|
+
| Trace source | `evalkit trace-sources create <name> --url … --api-key-env VAR` | admin only to create; any user can import from it (see below) |
|
|
200
|
+
| Shared workspace | `evalkit workspaces create <name>` | admin only |
|
|
201
|
+
| Writer / reviewer | `evalkit workspaces members add <ws> <user> --role writer\|reviewer` | admin only |
|
|
202
|
+
|
|
203
|
+
Boundary rule — config vs capabilities: config answers "where and how do I
|
|
204
|
+
reach you"; capabilities answer "what can you do once reached". Where both
|
|
205
|
+
express a limit (concurrency), evalkit takes the minimum.
|
|
206
|
+
|
|
207
|
+
## Outbound URLs and network egress
|
|
208
|
+
|
|
209
|
+
- The server calls these URLs:
|
|
210
|
+
- a shared agent's `harness_url` (set by an admin or a writer)
|
|
211
|
+
- shared remote evaluator endpoints (set by an admin or a writer)
|
|
212
|
+
- LLM provider base URLs, used by judges (set by an admin)
|
|
213
|
+
- trace source URLs (set by an admin)
|
|
214
|
+
- Without `allow_private_endpoints: true`, the server refuses agent, evaluator
|
|
215
|
+
and LLM provider URLs that resolve to internal addresses: localhost, private
|
|
216
|
+
ranges, link-local (including `169.254.169.254`), or that don't resolve.
|
|
217
|
+
- The server never calls URLs from personal workspaces.
|
|
218
|
+
- A server run whose agent, evaluator or LLM provider has no stored key calls
|
|
219
|
+
it without a key header. The create, resume or evaluate response carries
|
|
220
|
+
`warnings`, also logged.
|
|
221
|
+
- The server does not follow redirects on these calls, so a harness or
|
|
222
|
+
evaluator cannot bounce it to another address.
|
|
223
|
+
- One more outbound call: the LiteLLM cost map. The server image bundles it,
|
|
224
|
+
so the default setup makes no call. A server run from source without it
|
|
225
|
+
fetches it from `raw.githubusercontent.com`, and a `cost_map.source` set to
|
|
226
|
+
a URL is fetched at start. Allow that host if you restrict egress, or set
|
|
227
|
+
`cost_map.source` to a file path.
|
|
228
|
+
- If this matters on your network, restrict the server's egress there, e.g.
|
|
229
|
+
block `169.254.169.254` and any internal ranges the server does not need.
|
|
230
|
+
|
|
231
|
+
## Upgrades
|
|
232
|
+
|
|
233
|
+
Pull the new image → back up the database → with `migrate_on_start`, start
|
|
234
|
+
the new server (it migrates first); without it, stop or hold the new server →
|
|
235
|
+
`evalkit db upgrade` (same compose-run pattern as setup) → start the server → developers update the CLI with the install command
|
|
236
|
+
or `evalkit update` (every CLI call nudges them until they do), which also moves a project's `evalkit-py` pin and
|
|
237
|
+
refreshes the skills in client repos that vendor them. Deploy only builds that are on PyPI: every `develop`
|
|
238
|
+
(`X.Y.Z.devN`) and `main` (`X.Y.Z`) build of the publish workflow is.
|
|
239
|
+
|
|
240
|
+
## CLI for the deployer
|
|
241
|
+
|
|
242
|
+
The server image already contains the CLI — `docker compose run --rm server
|
|
243
|
+
evalkit …` works with no local install. Everyone else installs `evalkit-py`
|
|
244
|
+
from PyPI at the server's version: `GET /api/cli` (unauthenticated) returns
|
|
245
|
+
`{"version": …, "package": "evalkit-py"}`, and the `/docs/install` page in the
|
|
246
|
+
UI shows the commands with the version filled in. A proxy in front of the
|
|
247
|
+
server must pass `/api/cli` without auth. For what to hand every other developer on the team, follow the
|
|
248
|
+
**evalkit-usage** skill.
|
|
249
|
+
Client suites that need input files register them per that skill's
|
|
250
|
+
`references/suite-fixtures.md`.
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
## OpenTelemetry collector and trace sources
|
|
254
|
+
|
|
255
|
+
The server accepts OTLP/HTTP spans at `POST /api/otel/v1/traces` (protobuf
|
|
256
|
+
or JSON, gzip accepted) with a user token or an agent token, and converts a
|
|
257
|
+
session's spans to ATIF on demand. The optional `otel` block:
|
|
258
|
+
|
|
259
|
+
```yaml
|
|
260
|
+
otel:
|
|
261
|
+
settle_seconds: 10 # a session is settled when its newest span arrived longer ago than this
|
|
262
|
+
max_batch_bytes: 16777216 # one export request; 413 beyond it
|
|
263
|
+
max_session_bytes: 402653184 # span attributes one session read loads; 413 beyond it
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
An agent whose trajectories come from spans is created with
|
|
267
|
+
`--trajectory-source otel --mapping mapping.json`. The mapping's `answer`
|
|
268
|
+
(`root`, a span name, or `<span name>.<output key>`) and `exclude_spans`
|
|
269
|
+
say where the answer comes from and which spans to leave out. The agent's
|
|
270
|
+
`--trajectory-wait` (default 60 seconds) is how long a run waits for its
|
|
271
|
+
session to settle, and `--provides key=true` sets capability flags evalkit
|
|
272
|
+
cannot infer from spans.
|
|
273
|
+
|
|
274
|
+
`settle_seconds` and `trajectory_wait_seconds` answer two different
|
|
275
|
+
questions. `settle_seconds` is the server's rule for "this session is
|
|
276
|
+
complete": no span has arrived for that long, so the batch exporter (which
|
|
277
|
+
ships every 5 s by default) has nothing left. `trajectory_wait_seconds` is
|
|
278
|
+
the runner's patience: how long one run keeps polling for that settled
|
|
279
|
+
state before recording the trajectory as `partial` (root span seen) or
|
|
280
|
+
`unavailable` (nothing seen) and moving on. Raise the first when spans
|
|
281
|
+
arrive in bursts with long gaps; raise the second when the agent keeps
|
|
282
|
+
emitting for a while after it answers.
|
|
283
|
+
|
|
284
|
+
The two size limits protect the server, not the database. An export
|
|
285
|
+
request over `max_batch_bytes` (16 MiB, after gzip) gets `413` and its spans
|
|
286
|
+
are dropped. Building one session's trajectory (for a run, `evalkit traces
|
|
287
|
+
check` or the Mappings tab) needs memory in proportion to the span
|
|
288
|
+
attributes it loads, so a session over `max_session_bytes` (384 MiB) gets
|
|
289
|
+
`413` with its size and the limit instead of being loaded. LLM spans carry
|
|
290
|
+
the full input of each call, so a long agent session can store hundreds of
|
|
291
|
+
MB. A build loads that input only for the first LLM call of each agent and
|
|
292
|
+
subagent, and the limit counts only what a build loads, so the stored size
|
|
293
|
+
of a session can be far above the limit.
|
|
294
|
+
|
|
295
|
+
The server's memory limit is what these defaults are sized for. They keep
|
|
296
|
+
two session reads at once (the most the server runs together) inside a
|
|
297
|
+
2 GiB container: one read of the largest session allowed peaks at about
|
|
298
|
+
0.6 GB above the idle server (about 200 MB). With less memory, lower
|
|
299
|
+
`max_session_bytes`; raise it only with more memory. The server builds
|
|
300
|
+
sessions in its own process, so a server killed for memory drops every
|
|
301
|
+
request it is serving until it restarts; with a single replica, every route
|
|
302
|
+
returns 502 until then.
|
|
303
|
+
|
|
304
|
+
Agents with large spans should export in small batches with a longer
|
|
305
|
+
timeout: `OTEL_BSP_MAX_EXPORT_BATCH_SIZE=16` and
|
|
306
|
+
`OTEL_EXPORTER_OTLP_TIMEOUT=60` (seconds in Python; other SDKs read
|
|
307
|
+
milliseconds). The server's Tracing docs page and the connect-traces skill
|
|
308
|
+
give the agent's developers the same settings.
|
|
309
|
+
|
|
310
|
+
Spans land in the `spans` table; mappings in `span_mappings`; agent tokens
|
|
311
|
+
are rows in `auth_tokens` with `kind = agent`. History comes from a trace
|
|
312
|
+
source registered with `evalkit trace-sources create`:
|
|
313
|
+
|
|
314
|
+
- `evalkit traces import <agent> <session id> --source <trace source> --project <name>`
|
|
315
|
+
pulls one session.
|
|
316
|
+
- `evalkit traces import <agent> --source <trace source> --project <name> --since <time>
|
|
317
|
+
[--until <time>]` pulls every trace that started in the range as a job on the
|
|
318
|
+
server, `otel.import_page_traces` traces per request to the source. Jobs are rows
|
|
319
|
+
in `trace_imports`; `evalkit traces imports <agent>` lists them, and a failed or
|
|
320
|
+
stopped job continues with `--continue`. A range import needs an active mapping
|
|
321
|
+
with a `session_id` rule.
|
|
322
|
+
- The agent page's Import tab does both.
|
|
323
|
+
|
|
324
|
+
Trace sources are server-wide. Any user can import from any trace source
|
|
325
|
+
into an agent they can write, so developers can mine cases from traces on
|
|
326
|
+
their own. This means the source's API key is shared with every user in
|
|
327
|
+
effect: anyone can import any project that key can read. Give each trace
|
|
328
|
+
source a key scoped to only the projects you are happy to share with every
|
|
329
|
+
user. Imported traces are readable by every user too, since every user can
|
|
330
|
+
read every workspace.
|
|
331
|
+
|
|
332
|
+
Retention for `spans` is not built yet; size the database for the traffic you
|
|
333
|
+
point at the collector and the history you import.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Implementing the Harness API (v1)
|
|
2
|
+
|
|
3
|
+
The HTTP contract evalkit uses to drive the client's agent. Base URL is the
|
|
4
|
+
agent's `--harness-url` (convention: `https://<host>/evalkit/v1`). Every
|
|
5
|
+
request from evalkit carries the agent's key in an `x-api-key: <key>` header
|
|
6
|
+
(the server sends the shared agent's stored key; a local run sends
|
|
7
|
+
`credentials.agents` from the developer's `evalkit.yaml`) — reject anything else with 401. Exact request/response schemas: run
|
|
8
|
+
`evalkit contracts export --target <dir>` to write `harness-api-v1.json`
|
|
9
|
+
(OpenAPI, generated from the source-of-truth pydantic models) plus the other
|
|
10
|
+
contract files, matching the installed evalkit version.
|
|
11
|
+
|
|
12
|
+
## Endpoints
|
|
13
|
+
|
|
14
|
+
| Endpoint | Purpose |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `GET /capabilities` | Self-declaration: `contract_version`, `agent`, `provides` (trajectory, tool_calls, thinking, token_usage, artifacts, workspace, run_listing), `accepts` (files, file_urls, model_override, stop), `limits.max_concurrent_runs`, optional `input_schema` and `check_input` (below). Snapshotted onto every experiment. |
|
|
17
|
+
| `POST /runs` | Start one agent run in a fresh session. Body: `{input, model?, metadata?, files?}`. `input` is the case input exactly as written; evalkit never rewrites it. When the case has fixture files and you declare `accepts.file_urls`, the JSON body carries `files: [{name, url, context}]` and you fetch each url yourself (the url is evalkit's blob store url plus the object key, e.g. `s3://bucket/fixtures/sha256/<sha256>`; your agent harness system must have access). If you declare only `accepts.files`, evalkit instead sends `multipart/form-data` with a `payload` JSON part (carrying `files: [{name, context}]`, no urls) plus one part per file. `context` is the file's description from the suite's fixture registry; show it to your agent however suits it (for example, an "Attached files" block ahead of the question). URLs win when both are declared; a harness declaring neither never receives fixture cases. Return `201 {run_id}` — your own id, typically the session id. For an agent whose trajectory source is `otel` this `run_id` is the session evalkit polls; a queue-backed harness whose worker opens its own session returns that session id here, or stamps this `run_id` on the job's spans in the attribute its mapping's `session_id` rule names. |
|
|
18
|
+
| `GET /runs/{id}` | Poll status: `queued | running | completed | failed | cancelled`, plus `answer` and/or `output` (a completed run carries at least one; below), optional `artifacts` list, optional `error` (a completed run may carry a non-fatal error, e.g. a partial answer). |
|
|
19
|
+
| `POST /runs/{id}/stop` | Cancel a run. Called on timeout when `accepts.stop`. Return as soon as the stop is requested. Report a terminal status from `GET /runs/{id}` once `GET /runs/{id}/trajectory` returns the run's full trajectory. evalkit keeps polling for up to `stop_grace_seconds` (agent config, default 120), then fetches the trajectory. |
|
|
20
|
+
| `GET /runs/{id}/trajectory` | The run's trajectory as an ATIF document (below). Only meaningful after a terminal status; evalkit fetches it once and persists it. |
|
|
21
|
+
| `GET /runs?query=&user_id=&client_id=&limit=&offset=&since=&until=` | Optional (`run_listing`): list runs/sessions, newest first, for the UI browser and `evalkit runs list` / `runs pull`. `{runs: [{run_id, title, user_id, client_id, created_at}], total}`. `since` / `until` (ISO 8601) keep runs created at or after `since` and before `until`; evalkit drops runs outside the window itself when a harness ignores them, and `evalkit harness check` warns. A harness without multi-tenancy returns fixed dummy user/client ids. Returned ids must work with the other `/runs/{id}` endpoints. A harness that keeps several of its own runs per session (for example one per chat message) and answers each with the whole session sets `session_id` on every item: evalkit's run is the session, so it lists items sharing a `session_id` once, as the newest. |
|
|
22
|
+
| `GET /runs/{id}/files` and `GET /runs/{id}/files/{path}` | Optional (`workspace`): workspace listing `{files: [{path, size, content_type}]}` and raw file bytes. Powers the UI workspace viewer and artifact collection. |
|
|
23
|
+
|
|
24
|
+
Notes:
|
|
25
|
+
|
|
26
|
+
- `metadata` on `POST /runs` carries `{experiment_id, case_id, repetition}` so
|
|
27
|
+
the harness can stamp its own sessions/logs and keep eval traffic
|
|
28
|
+
distinguishable from app traffic (runs outside any experiment). Ignoring it
|
|
29
|
+
is allowed.
|
|
30
|
+
- Timeouts, polling intervals, and retries are evalkit-side config, not
|
|
31
|
+
contract fields.
|
|
32
|
+
|
|
33
|
+
## Typed agents: `output`, `input_schema`, `check_input`
|
|
34
|
+
|
|
35
|
+
For agents that take typed inputs and return typed results (classifiers,
|
|
36
|
+
taggers, extractors), rather than answering a question in prose:
|
|
37
|
+
|
|
38
|
+
- `output` on `GET /runs/{id}`: the agent's structured result as a JSON
|
|
39
|
+
object, for example `{"label": "EVENT"}`. Send it instead of JSON-encoding
|
|
40
|
+
the result into `answer`. A completed run may carry `output` only, `answer`
|
|
41
|
+
only, or both. evalkit stores `output` as-is and passes it parsed to custom
|
|
42
|
+
evaluators as `run.output`; text-based evaluators (rubric judges) and the
|
|
43
|
+
Custom Evaluator API's `run.answer` get `output` as canonical JSON (sorted
|
|
44
|
+
keys) when `answer` is absent. An evalkit older than 0.2.0 still requires
|
|
45
|
+
`answer`.
|
|
46
|
+
- `input_schema` in capabilities: a JSON Schema (draft 2020-12 unless its own
|
|
47
|
+
`$schema` says otherwise) for the `input` you accept. It never covers
|
|
48
|
+
`files`, which evalkit sends separately. Without one, evalkit assumes the
|
|
49
|
+
default question-and-answer schema, `{"type": "object", "required":
|
|
50
|
+
["question"], "properties": {"question": {"type": "string"}}}`, so every
|
|
51
|
+
agent is checked the same way. Before an experiment starts,
|
|
52
|
+
evalkit checks every selected case's input against it and refuses the
|
|
53
|
+
whole experiment, listing each failing case, if any fails. Case authors can
|
|
54
|
+
run the same check on demand with `evalkit lint <dir> --agent <name>`. An
|
|
55
|
+
invalid schema makes the capabilities response invalid.
|
|
56
|
+
- `check_input` in capabilities: the input `evalkit harness check` sends in
|
|
57
|
+
place of its default probe `{"question": "Hi. Reply with one word."}`.
|
|
58
|
+
Declare it whenever that default doesn't fit your `input_schema` or your
|
|
59
|
+
agent. It must pass `input_schema` (or the default schema), or the
|
|
60
|
+
capabilities response is invalid.
|
|
61
|
+
|
|
62
|
+
## The trajectory: ATIF
|
|
63
|
+
|
|
64
|
+
evalkit accepts ATIF (Agent Trajectory Interchange Format). The spec is the
|
|
65
|
+
Harbor RFC, and it is the source of truth; evalkit doesn't copy it or publish a
|
|
66
|
+
schema of its own. Pinned version:
|
|
67
|
+
https://github.com/harbor-framework/harbor/blob/74cc6312018c349c6bd2400c89a0ac4983ac1085/rfcs/0001-trajectory-format.md.
|
|
68
|
+
Accepted `schema_version` values ship in `atif-accepted.json` (from
|
|
69
|
+
`evalkit contracts export`; currently `ATIF-v1.8`).
|
|
70
|
+
|
|
71
|
+
Required subset (validation fails without these):
|
|
72
|
+
|
|
73
|
+
- `schema_version`
|
|
74
|
+
- `steps[]`, each with `step_id` (sequential int from 1), `source`
|
|
75
|
+
(`user` / `agent` / `system`), and `message` (string or content-part list)
|
|
76
|
+
- tool interactions as `tool_calls[]` (`tool_call_id`, `function_name`,
|
|
77
|
+
`arguments`) paired to `observation.results[]` entries by `source_call_id`
|
|
78
|
+
|
|
79
|
+
Everything else is optional and capability-declared: `reasoning_content`
|
|
80
|
+
(thinking), per-step `metrics` (`prompt_tokens`, `completion_tokens`,
|
|
81
|
+
`cached_tokens`, `cost_usd`), `model_name`, `timestamp`, `final_metrics`,
|
|
82
|
+
`subagent_trajectories` (a list of embedded, independently valid trajectories),
|
|
83
|
+
`extra`.
|
|
84
|
+
|
|
85
|
+
Follow these RFC rules; evalkit's metrics depend on them:
|
|
86
|
+
|
|
87
|
+
- Set `prompt_tokens` to every input token the model processed, cached ones
|
|
88
|
+
included, and `cached_tokens` to the cached part of it. evalkit prices
|
|
89
|
+
`prompt_tokens - cached_tokens` at the input rate and `cached_tokens` at the
|
|
90
|
+
cache-read rate. Put provider-specific counts such as cache writes in
|
|
91
|
+
`metrics.extra`.
|
|
92
|
+
- Make the root `final_metrics` token totals cover the whole run, every
|
|
93
|
+
embedded subagent included. evalkit's token gauges read the root totals.
|
|
94
|
+
- Give every embedded subagent a `trajectory_id`, unique within
|
|
95
|
+
`subagent_trajectories`, and set `subagent_trajectory_ref:
|
|
96
|
+
[{"trajectory_id": ...}]` on the result of the tool call that started it.
|
|
97
|
+
Link by `trajectory_id`; `session_id` may be shared across a run.
|
|
98
|
+
`evalkit harness check` warns when ids are missing, and a later release will
|
|
99
|
+
require them.
|
|
100
|
+
- Set a subagent's `agent.name` to its type (e.g. `researcher`). evalkit
|
|
101
|
+
records it next to each subagent's metrics.
|
|
102
|
+
- Set `agent.name` and `agent.version` on every trajectory.
|
|
103
|
+
|
|
104
|
+
### evalkit `extra` conventions
|
|
105
|
+
|
|
106
|
+
ATIF leaves room for producer metadata in `extra`. These are the only fields
|
|
107
|
+
evalkit reads from it. All are optional; anything else in `extra` is kept and
|
|
108
|
+
ignored.
|
|
109
|
+
|
|
110
|
+
| Field | On | Type | Meaning | evalkit uses it for |
|
|
111
|
+
|---|---|---|---|---|
|
|
112
|
+
| `is_error` | `ObservationResult.extra` | bool | The tool call failed. Keep the error message in `content`. | `errored_tool_calls` gauge; judges and diagnosis |
|
|
113
|
+
| `timestamp` | `ObservationResult.extra` | ISO 8601 string | When the result came back. | Tool time: this minus the calling step's `timestamp` |
|
|
114
|
+
| `error_type` | `ObservationResult.extra` | string | Your own class for an errored result (e.g. `mcp_error`, `exit_code`). Free-form. | Grouping errors without parsing message text |
|
|
115
|
+
| `full_output_path` | `ObservationResult.extra` | string | When `content` is a shortened version of the real output, a path your `GET /runs/{id}/files/{path}` serves with the full output. Keep `content` as what the agent saw. | `evalkit runs files <dir> <run_id> --path <it>` fetches it |
|
|
116
|
+
| `is_error` | `Step.extra` | bool | The turn this step belongs to failed (timed out, the model call errored, the agent crashed). Put it on the step that records the failure, often a `system` step. | `failed_turns` in the MCP `get_runs` and `get_run_stats` |
|
|
117
|
+
| `error` | `Step.extra` | string | The failure message, when the step's `message` is not it. | The turn's `error` in MCP `get_runs` |
|
|
118
|
+
|
|
119
|
+
Conventions:
|
|
120
|
+
|
|
121
|
+
- A turn is one user step and every step after it until the next user step.
|
|
122
|
+
A multi-turn chat session is one run with several user steps.
|
|
123
|
+
- The final answer is NOT read from the trajectory — it comes from the run
|
|
124
|
+
status response. The trajectory endpoint returns a pure ATIF document.
|
|
125
|
+
- Convert from your native session format at read time (a converter from your
|
|
126
|
+
session log to ATIF), or record ATIF natively. Keep the converter total: a
|
|
127
|
+
session your converter can't handle should fail loudly, not emit a truncated
|
|
128
|
+
document.
|
|
129
|
+
|
|
130
|
+
## Verification
|
|
131
|
+
|
|
132
|
+
`evalkit harness check <name>@dev` exercises: capabilities shape → the
|
|
133
|
+
probe input against `input_schema` (or the default schema) → a trivial run (with
|
|
134
|
+
`check_input`, else "Hi. Reply with one word.") to a terminal status with an
|
|
135
|
+
`answer` or `output`, carrying one
|
|
136
|
+
small input file by url when `accepts.file_urls` or as multipart when only
|
|
137
|
+
`accepts.files` → trajectory ATIF validation → workspace files, run listing,
|
|
138
|
+
and stop where declared. All declared capabilities must pass before running
|
|
139
|
+
real experiments.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copy to .env and fill in the LLM. docker compose and the evalkit CLI and SDK
|
|
2
|
+
# (next to evalkit.yaml) all read .env.
|
|
3
|
+
#
|
|
4
|
+
# Any OpenAI-compatible endpoint that supports tool calls and structured
|
|
5
|
+
# output: OpenAI, a LiteLLM proxy, vLLM, Ollama, ...
|
|
6
|
+
LLM_BASE_URL=https://api.openai.com/v1
|
|
7
|
+
LLM_API_KEY=
|
|
8
|
+
LLM_MODEL=gpt-5-mini
|
|
9
|
+
|
|
10
|
+
# The evalkit admin password on this machine.
|
|
11
|
+
EVALKIT_ADMIN_PASSWORD=evalkit-admin
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from opentelemetry import trace
|
|
7
|
+
|
|
8
|
+
NAME = "analyst"
|
|
9
|
+
MAX_TURNS = 6
|
|
10
|
+
FACTS = {
|
|
11
|
+
"revenue_fy2025": "48.2 million",
|
|
12
|
+
"revenue_fy2024": "41.5 million",
|
|
13
|
+
"gross_profit_fy2025": "18.5 million",
|
|
14
|
+
"debt_fy2025": "20.0 million",
|
|
15
|
+
"cash_fy2025": "12.7 million",
|
|
16
|
+
}
|
|
17
|
+
INSTRUCTIONS = (
|
|
18
|
+
"You are a financial analyst answering questions about Acme Widgets. "
|
|
19
|
+
"Look up every figure with lookup_financials; never guess one. "
|
|
20
|
+
"State figures in millions, and percentages to one decimal place."
|
|
21
|
+
)
|
|
22
|
+
TOOLS = [
|
|
23
|
+
{
|
|
24
|
+
"type": "function",
|
|
25
|
+
"function": {
|
|
26
|
+
"name": "lookup_financials",
|
|
27
|
+
"description": "One reported figure for Acme Widgets.",
|
|
28
|
+
"parameters": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {"metric": {"type": "string", "enum": sorted(FACTS)}},
|
|
31
|
+
"required": ["metric"],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
tracer = trace.get_tracer("acme-analyst")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def lookup_financials(metric: str) -> str:
|
|
41
|
+
return FACTS.get(metric, f"unknown metric {metric}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def gen_ai_message(message: dict[str, Any]) -> dict[str, Any]:
|
|
45
|
+
if message["role"] == "tool":
|
|
46
|
+
parts = [
|
|
47
|
+
{
|
|
48
|
+
"type": "tool_call_response",
|
|
49
|
+
"id": message["tool_call_id"],
|
|
50
|
+
"response": message["content"],
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
else:
|
|
54
|
+
parts = [{"type": "text", "content": message["content"]}] if message.get("content") else []
|
|
55
|
+
parts += [
|
|
56
|
+
{
|
|
57
|
+
"type": "tool_call",
|
|
58
|
+
"id": call["id"],
|
|
59
|
+
"name": call["function"]["name"],
|
|
60
|
+
"arguments": json.loads(call["function"]["arguments"] or "{}"),
|
|
61
|
+
}
|
|
62
|
+
for call in message.get("tool_calls") or []
|
|
63
|
+
]
|
|
64
|
+
return {"role": message["role"], "parts": parts}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Analyst:
|
|
68
|
+
def __init__(self, client: httpx.Client | None = None):
|
|
69
|
+
self.model = os.environ["LLM_MODEL"]
|
|
70
|
+
self.client = client or httpx.Client(
|
|
71
|
+
base_url=os.environ["LLM_BASE_URL"],
|
|
72
|
+
headers={"Authorization": f"Bearer {os.environ['LLM_API_KEY']}"},
|
|
73
|
+
timeout=120,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def chat(self, messages: list[dict[str, Any]]) -> dict[str, Any]:
|
|
77
|
+
with tracer.start_as_current_span(f"chat {self.model}") as span:
|
|
78
|
+
span.set_attribute("gen_ai.operation.name", "chat")
|
|
79
|
+
span.set_attribute("gen_ai.request.model", self.model)
|
|
80
|
+
span.set_attribute(
|
|
81
|
+
"gen_ai.system_instructions",
|
|
82
|
+
json.dumps([{"type": "text", "content": INSTRUCTIONS}]),
|
|
83
|
+
)
|
|
84
|
+
span.set_attribute(
|
|
85
|
+
"gen_ai.input.messages", json.dumps([gen_ai_message(m) for m in messages])
|
|
86
|
+
)
|
|
87
|
+
response = self.client.post(
|
|
88
|
+
"/chat/completions",
|
|
89
|
+
json={
|
|
90
|
+
"model": self.model,
|
|
91
|
+
"messages": [{"role": "system", "content": INSTRUCTIONS}, *messages],
|
|
92
|
+
"tools": TOOLS,
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
response.raise_for_status()
|
|
96
|
+
body = response.json()
|
|
97
|
+
reply = body["choices"][0]["message"]
|
|
98
|
+
span.set_attribute("gen_ai.output.messages", json.dumps([gen_ai_message(reply)]))
|
|
99
|
+
usage = body.get("usage") or {}
|
|
100
|
+
span.set_attribute("gen_ai.usage.input_tokens", usage.get("prompt_tokens") or 0)
|
|
101
|
+
span.set_attribute("gen_ai.usage.output_tokens", usage.get("completion_tokens") or 0)
|
|
102
|
+
return reply
|
|
103
|
+
|
|
104
|
+
def call_tool(self, call: dict[str, Any]) -> str:
|
|
105
|
+
name = call["function"]["name"]
|
|
106
|
+
with tracer.start_as_current_span(f"execute_tool {name}") as span:
|
|
107
|
+
span.set_attribute("gen_ai.operation.name", "execute_tool")
|
|
108
|
+
span.set_attribute("gen_ai.tool.name", name)
|
|
109
|
+
span.set_attribute("gen_ai.tool.call.id", call["id"])
|
|
110
|
+
span.set_attribute("gen_ai.tool.call.arguments", call["function"]["arguments"])
|
|
111
|
+
arguments = json.loads(call["function"]["arguments"] or "{}")
|
|
112
|
+
result = lookup_financials(**arguments)
|
|
113
|
+
span.set_attribute("gen_ai.tool.call.result", result)
|
|
114
|
+
return result
|
|
115
|
+
|
|
116
|
+
def ask(self, question: str) -> str:
|
|
117
|
+
with tracer.start_as_current_span(f"invoke_agent {NAME}") as span:
|
|
118
|
+
span.set_attribute("gen_ai.operation.name", "invoke_agent")
|
|
119
|
+
span.set_attribute("gen_ai.agent.name", NAME)
|
|
120
|
+
messages: list[dict[str, Any]] = [{"role": "user", "content": question}]
|
|
121
|
+
for _ in range(MAX_TURNS):
|
|
122
|
+
reply = self.chat(messages)
|
|
123
|
+
messages.append(
|
|
124
|
+
{k: v for k, v in reply.items() if k in ("role", "content", "tool_calls")}
|
|
125
|
+
)
|
|
126
|
+
if not reply.get("tool_calls"):
|
|
127
|
+
return reply.get("content") or ""
|
|
128
|
+
for call in reply["tool_calls"]:
|
|
129
|
+
result = self.call_tool(call)
|
|
130
|
+
messages.append({"role": "tool", "tool_call_id": call["id"], "content": result})
|
|
131
|
+
raise RuntimeError(f"no answer after {MAX_TURNS} model calls")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
cases:
|
|
2
|
+
- id: acme-net-debt
|
|
3
|
+
input:
|
|
4
|
+
question: What was Acme Widgets' net debt at FY2025 year-end?
|
|
5
|
+
evaluators:
|
|
6
|
+
output:
|
|
7
|
+
answer_rubrics:
|
|
8
|
+
rubrics:
|
|
9
|
+
- rubric: States debt of 20.0 million and cash of 12.7 million.
|
|
10
|
+
type: factual_retrieval
|
|
11
|
+
- rubric: States net debt of 7.3 million.
|
|
12
|
+
type: derived_computation
|
|
13
|
+
trajectory:
|
|
14
|
+
required_tools_called:
|
|
15
|
+
tools: [lookup_financials]
|
|
16
|
+
|
|
17
|
+
- id: acme-operating-margin
|
|
18
|
+
input:
|
|
19
|
+
question: What was Acme Widgets' operating margin in FY2025?
|
|
20
|
+
evaluators:
|
|
21
|
+
output:
|
|
22
|
+
answer_rubrics:
|
|
23
|
+
rubrics:
|
|
24
|
+
- rubric: Says the operating margin cannot be determined because operating income is not available, and does not state an operating margin figure.
|
|
25
|
+
type: analysis_interpretation
|