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.
Files changed (371) hide show
  1. evalkit/__init__.py +0 -0
  2. evalkit/api/__init__.py +0 -0
  3. evalkit/api/access.py +74 -0
  4. evalkit/api/agents.py +159 -0
  5. evalkit/api/app.py +360 -0
  6. evalkit/api/auth.py +225 -0
  7. evalkit/api/cli_dist.py +10 -0
  8. evalkit/api/datasets.py +169 -0
  9. evalkit/api/deps.py +79 -0
  10. evalkit/api/download.py +15 -0
  11. evalkit/api/evaluators.py +121 -0
  12. evalkit/api/experiments.py +616 -0
  13. evalkit/api/fixtures.py +95 -0
  14. evalkit/api/harness_check.py +48 -0
  15. evalkit/api/imports.py +180 -0
  16. evalkit/api/limits.py +84 -0
  17. evalkit/api/llm_providers.py +120 -0
  18. evalkit/api/mcp/__init__.py +0 -0
  19. evalkit/api/mcp/auth.py +100 -0
  20. evalkit/api/mcp/descriptions.py +336 -0
  21. evalkit/api/mcp/models.py +259 -0
  22. evalkit/api/mcp/server.py +101 -0
  23. evalkit/api/mcp/tools.py +844 -0
  24. evalkit/api/meta.py +19 -0
  25. evalkit/api/otel.py +350 -0
  26. evalkit/api/pull.py +197 -0
  27. evalkit/api/review.py +271 -0
  28. evalkit/api/runs.py +555 -0
  29. evalkit/api/server_keys.py +44 -0
  30. evalkit/api/tokens.py +51 -0
  31. evalkit/api/trace_imports.py +173 -0
  32. evalkit/api/trace_sources.py +49 -0
  33. evalkit/api/traces.py +196 -0
  34. evalkit/api/users.py +72 -0
  35. evalkit/api/workspaces.py +131 -0
  36. evalkit/bundled/contracts/atif-accepted.json +5 -0
  37. evalkit/bundled/contracts/custom-evaluator-api-v1.json +243 -0
  38. evalkit/bundled/contracts/harness-api-v1.json +938 -0
  39. evalkit/bundled/contracts/span-mapping-v1.json +314 -0
  40. evalkit/bundled/docs/concepts.md +26 -0
  41. evalkit/bundled/docs/config.md +38 -0
  42. evalkit/bundled/docs/harness-api.md +44 -0
  43. evalkit/bundled/docs/install.md +60 -0
  44. evalkit/bundled/docs/login.md +36 -0
  45. evalkit/bundled/docs/mappings.md +136 -0
  46. evalkit/bundled/docs/mcp.md +44 -0
  47. evalkit/bundled/docs/quickstart.md +142 -0
  48. evalkit/bundled/docs/sdk-runs.md +171 -0
  49. evalkit/bundled/docs/tracing.md +171 -0
  50. evalkit/bundled/docs/try-locally.md +288 -0
  51. evalkit/bundled/docs/workspaces.md +45 -0
  52. evalkit/bundled/skills/authoring-eval-cases/SKILL.md +387 -0
  53. evalkit/bundled/skills/authoring-eval-cases/references/overlays.md +83 -0
  54. evalkit/bundled/skills/authoring-eval-cases/references/tool-checks.md +71 -0
  55. evalkit/bundled/skills/connect-traces/SKILL.md +296 -0
  56. evalkit/bundled/skills/connect-traces/references/gen_ai.json +42 -0
  57. evalkit/bundled/skills/connect-traces/references/openinference.json +35 -0
  58. evalkit/bundled/skills/connect-traces/references/span-mapping-schema.md +88 -0
  59. evalkit/bundled/skills/eval-driven-development/SKILL.md +458 -0
  60. evalkit/bundled/skills/eval-driven-development/references/analyze-run.md +97 -0
  61. evalkit/bundled/skills/eval-driven-development/references/diagnose.md +130 -0
  62. evalkit/bundled/skills/eval-driven-development/references/synthesize-cases.md +87 -0
  63. evalkit/bundled/skills/eval-driven-development/references/synthesize.md +165 -0
  64. evalkit/bundled/skills/evalkit-deployment/SKILL.md +332 -0
  65. evalkit/bundled/skills/evalkit-deployment/config.example.yaml +49 -0
  66. evalkit/bundled/skills/evalkit-deployment/docker-compose.yml +51 -0
  67. evalkit/bundled/skills/evalkit-deployment/references/config-and-deploy.md +333 -0
  68. evalkit/bundled/skills/evalkit-deployment/references/harness-api.md +139 -0
  69. evalkit/bundled/skills/evalkit-deployment/try/.env.example +11 -0
  70. evalkit/bundled/skills/evalkit-deployment/try/agent.py +131 -0
  71. evalkit/bundled/skills/evalkit-deployment/try/cases/acme/balance-sheet/cases.yaml +25 -0
  72. evalkit/bundled/skills/evalkit-deployment/try/cases/acme/income-statement/cases.yaml +43 -0
  73. evalkit/bundled/skills/evalkit-deployment/try/config.yaml +17 -0
  74. evalkit/bundled/skills/evalkit-deployment/try/docker-compose.yml +82 -0
  75. evalkit/bundled/skills/evalkit-deployment/try/evalkit.yaml +4 -0
  76. evalkit/bundled/skills/evalkit-deployment/try/mapping.json +42 -0
  77. evalkit/bundled/skills/evalkit-deployment/try/run.py +33 -0
  78. evalkit/bundled/skills/evalkit-usage/SKILL.md +509 -0
  79. evalkit/bundled/skills/evalkit-usage/references/suite-fixtures.md +42 -0
  80. evalkit/bundled/ui/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  81. evalkit/bundled/ui/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  82. evalkit/bundled/ui/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  83. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  84. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  85. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  86. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  87. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  88. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  89. evalkit/bundled/ui/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  90. evalkit/bundled/ui/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  91. evalkit/bundled/ui/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  92. evalkit/bundled/ui/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  93. evalkit/bundled/ui/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  94. evalkit/bundled/ui/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  95. evalkit/bundled/ui/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  96. evalkit/bundled/ui/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  97. evalkit/bundled/ui/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  98. evalkit/bundled/ui/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  99. evalkit/bundled/ui/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  100. evalkit/bundled/ui/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  101. evalkit/bundled/ui/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  102. evalkit/bundled/ui/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  103. evalkit/bundled/ui/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  104. evalkit/bundled/ui/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  105. evalkit/bundled/ui/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  106. evalkit/bundled/ui/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  107. evalkit/bundled/ui/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  108. evalkit/bundled/ui/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  109. evalkit/bundled/ui/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  110. evalkit/bundled/ui/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  111. evalkit/bundled/ui/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  112. evalkit/bundled/ui/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  113. evalkit/bundled/ui/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  114. evalkit/bundled/ui/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  115. evalkit/bundled/ui/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  116. evalkit/bundled/ui/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  117. evalkit/bundled/ui/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  118. evalkit/bundled/ui/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  119. evalkit/bundled/ui/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  120. evalkit/bundled/ui/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  121. evalkit/bundled/ui/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  122. evalkit/bundled/ui/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  123. evalkit/bundled/ui/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  124. evalkit/bundled/ui/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  125. evalkit/bundled/ui/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  126. evalkit/bundled/ui/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  127. evalkit/bundled/ui/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  128. evalkit/bundled/ui/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  129. evalkit/bundled/ui/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  130. evalkit/bundled/ui/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  131. evalkit/bundled/ui/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  132. evalkit/bundled/ui/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  133. evalkit/bundled/ui/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  134. evalkit/bundled/ui/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  135. evalkit/bundled/ui/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  136. evalkit/bundled/ui/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  137. evalkit/bundled/ui/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  138. evalkit/bundled/ui/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  139. evalkit/bundled/ui/assets/abap-08VXUWAP.js +1 -0
  140. evalkit/bundled/ui/assets/apex-BWPQTe0t.js +1 -0
  141. evalkit/bundled/ui/assets/azcli-Bc_sGQ0U.js +1 -0
  142. evalkit/bundled/ui/assets/bat-i0X4ZdIN.js +1 -0
  143. evalkit/bundled/ui/assets/bicep-B5-_aFwp.js +2 -0
  144. evalkit/bundled/ui/assets/cameligo-DMUM7wLl.js +1 -0
  145. evalkit/bundled/ui/assets/clojure-Cm7r79vr.js +1 -0
  146. evalkit/bundled/ui/assets/codicon-DF1abBS2.ttf +0 -0
  147. evalkit/bundled/ui/assets/coffee-Ba7i2nA0.js +1 -0
  148. evalkit/bundled/ui/assets/cpp-C7h46wYY.js +1 -0
  149. evalkit/bundled/ui/assets/csharp-BKxtCVv1.js +1 -0
  150. evalkit/bundled/ui/assets/csp-bTuwJoIa.js +1 -0
  151. evalkit/bundled/ui/assets/css-DIMkf-bt.js +3 -0
  152. evalkit/bundled/ui/assets/css.worker-DwfUZ3fB.js +96 -0
  153. evalkit/bundled/ui/assets/cssMode-C97l1pbB.js +1 -0
  154. evalkit/bundled/ui/assets/cypher-CVaqCwHa.js +1 -0
  155. evalkit/bundled/ui/assets/dart-onAF5SnQ.js +1 -0
  156. evalkit/bundled/ui/assets/dockerfile-DZFCIeNp.js +1 -0
  157. evalkit/bundled/ui/assets/ecl-94qehhXe.js +1 -0
  158. evalkit/bundled/ui/assets/editor-DjpsE8to.css +1 -0
  159. evalkit/bundled/ui/assets/editor.api-NTesIxw5.js +908 -0
  160. evalkit/bundled/ui/assets/editor.worker-CRz__AKW.js +33 -0
  161. evalkit/bundled/ui/assets/elixir-6RTg0lbw.js +1 -0
  162. evalkit/bundled/ui/assets/flow9-C5_-GSwl.js +1 -0
  163. evalkit/bundled/ui/assets/freemarker2-BWqEFagp.js +3 -0
  164. evalkit/bundled/ui/assets/fsharp-C8Ef5oNN.js +1 -0
  165. evalkit/bundled/ui/assets/go-C-y9NEjX.js +1 -0
  166. evalkit/bundled/ui/assets/graphql-fmXr3nnJ.js +1 -0
  167. evalkit/bundled/ui/assets/handlebars-DkIF-whN.js +1 -0
  168. evalkit/bundled/ui/assets/hcl-CpzslTdj.js +1 -0
  169. evalkit/bundled/ui/assets/html-C_rpeQXJ.js +1 -0
  170. evalkit/bundled/ui/assets/html.worker-D9ugpX4y.js +509 -0
  171. evalkit/bundled/ui/assets/htmlMode-B0YIq3DF.js +1 -0
  172. evalkit/bundled/ui/assets/index-Cw-jBH6M.js +736 -0
  173. evalkit/bundled/ui/assets/index-DKtoQZtm.css +1 -0
  174. evalkit/bundled/ui/assets/ini-sBoK_t0W.js +1 -0
  175. evalkit/bundled/ui/assets/java-BEtHBSE6.js +1 -0
  176. evalkit/bundled/ui/assets/javascript-D4EGhYA6.js +1 -0
  177. evalkit/bundled/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  178. evalkit/bundled/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  179. evalkit/bundled/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  180. evalkit/bundled/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  181. evalkit/bundled/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  182. evalkit/bundled/ui/assets/json.worker-CV9fqkUp.js +65 -0
  183. evalkit/bundled/ui/assets/jsonMode-pQjSfIH6.js +7 -0
  184. evalkit/bundled/ui/assets/julia-Bri6UV-V.js +1 -0
  185. evalkit/bundled/ui/assets/kotlin-BOotOW0E.js +1 -0
  186. evalkit/bundled/ui/assets/less-B9JPFI3C.js +2 -0
  187. evalkit/bundled/ui/assets/lexon-CfSJPG6W.js +1 -0
  188. evalkit/bundled/ui/assets/liquid-DbrvoYjD.js +1 -0
  189. evalkit/bundled/ui/assets/lspLanguageFeatures-D0B7cH6y.js +4 -0
  190. evalkit/bundled/ui/assets/lua-CsQS60Ue.js +1 -0
  191. evalkit/bundled/ui/assets/m3-D-oSqn_W.js +1 -0
  192. evalkit/bundled/ui/assets/markdown-Cimd5fb3.js +1 -0
  193. evalkit/bundled/ui/assets/mdx-B0HrujLR.js +1 -0
  194. evalkit/bundled/ui/assets/mips-CIPQ_RoX.js +1 -0
  195. evalkit/bundled/ui/assets/monacoEditor-DsoPncxW.js +68 -0
  196. evalkit/bundled/ui/assets/monacoEditor-n7clI0si.css +1 -0
  197. evalkit/bundled/ui/assets/msdax-DauUninz.js +1 -0
  198. evalkit/bundled/ui/assets/mysql-SOo6toE5.js +1 -0
  199. evalkit/bundled/ui/assets/objective-c-FvmIjYaQ.js +1 -0
  200. evalkit/bundled/ui/assets/pascal-DrH0SRf2.js +1 -0
  201. evalkit/bundled/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
  202. evalkit/bundled/ui/assets/perl-oz_6vUea.js +1 -0
  203. evalkit/bundled/ui/assets/pgsql-DTj74zXo.js +1 -0
  204. evalkit/bundled/ui/assets/php-nr791fC2.js +1 -0
  205. evalkit/bundled/ui/assets/pla-CopQ2nXW.js +1 -0
  206. evalkit/bundled/ui/assets/postiats-43DmfD33.js +1 -0
  207. evalkit/bundled/ui/assets/powerquery-D3hlyOfw.js +1 -0
  208. evalkit/bundled/ui/assets/powershell-DmHpPYUd.js +1 -0
  209. evalkit/bundled/ui/assets/protobuf-C531GsRP.js +2 -0
  210. evalkit/bundled/ui/assets/pug-Z5eAx3Zn.js +1 -0
  211. evalkit/bundled/ui/assets/python-CieHVM9n.js +1 -0
  212. evalkit/bundled/ui/assets/qsharp-DkqhCAOL.js +1 -0
  213. evalkit/bundled/ui/assets/r-BwWrilGY.js +1 -0
  214. evalkit/bundled/ui/assets/razor-sLewSMyp.js +1 -0
  215. evalkit/bundled/ui/assets/redis-ClamHrr6.js +1 -0
  216. evalkit/bundled/ui/assets/redshift-DT7zqm-g.js +1 -0
  217. evalkit/bundled/ui/assets/restructuredtext-BYgofb2h.js +1 -0
  218. evalkit/bundled/ui/assets/ruby-DezsRK8O.js +1 -0
  219. evalkit/bundled/ui/assets/rust-DdL9SqIa.js +1 -0
  220. evalkit/bundled/ui/assets/sb-CcwsVR0C.js +1 -0
  221. evalkit/bundled/ui/assets/scala-DHpiXF5c.js +1 -0
  222. evalkit/bundled/ui/assets/scheme-BeGwcela.js +1 -0
  223. evalkit/bundled/ui/assets/scss-gp-XZpBa.js +3 -0
  224. evalkit/bundled/ui/assets/shell-CC2rA5mh.js +1 -0
  225. evalkit/bundled/ui/assets/solidity-BEEn4gHE.js +1 -0
  226. evalkit/bundled/ui/assets/sophia-CRfGWb83.js +1 -0
  227. evalkit/bundled/ui/assets/sparql-D_Lu-MrJ.js +1 -0
  228. evalkit/bundled/ui/assets/sql-NEE52Syq.js +1 -0
  229. evalkit/bundled/ui/assets/st-DbInun42.js +1 -0
  230. evalkit/bundled/ui/assets/swift-Bxkupp3x.js +1 -0
  231. evalkit/bundled/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
  232. evalkit/bundled/ui/assets/tcl-DISqw1ZD.js +1 -0
  233. evalkit/bundled/ui/assets/ts.worker-DIAQkoB3.js +67741 -0
  234. evalkit/bundled/ui/assets/tsMode-BcVQ1nu0.js +11 -0
  235. evalkit/bundled/ui/assets/twig-De2hgUGE.js +1 -0
  236. evalkit/bundled/ui/assets/typescript-CUvNWd9p.js +1 -0
  237. evalkit/bundled/ui/assets/typespec-B8J7ngcE.js +1 -0
  238. evalkit/bundled/ui/assets/vb-DV3o63ZY.js +1 -0
  239. evalkit/bundled/ui/assets/wgsl-DpFanUEy.js +298 -0
  240. evalkit/bundled/ui/assets/workers-CwpYQtzp.js +1 -0
  241. evalkit/bundled/ui/assets/xml-C1HnXMdF.js +1 -0
  242. evalkit/bundled/ui/assets/yaml-D7Ey3HQu.js +1 -0
  243. evalkit/bundled/ui/favicon.svg +6 -0
  244. evalkit/bundled/ui/index.html +14 -0
  245. evalkit/cli/__init__.py +0 -0
  246. evalkit/cli/admin_commands.py +590 -0
  247. evalkit/cli/client.py +67 -0
  248. evalkit/cli/init_server.py +165 -0
  249. evalkit/cli/local_run.py +195 -0
  250. evalkit/cli/main.py +1365 -0
  251. evalkit/cli/runs_commands.py +539 -0
  252. evalkit/cli/server_commands.py +130 -0
  253. evalkit/cli/skill_targets.py +43 -0
  254. evalkit/cli/tokens_commands.py +52 -0
  255. evalkit/cli/traces_commands.py +332 -0
  256. evalkit/cli/update.py +85 -0
  257. evalkit/client/__init__.py +0 -0
  258. evalkit/client/agents.py +25 -0
  259. evalkit/client/api.py +193 -0
  260. evalkit/client/blob.py +104 -0
  261. evalkit/client/connection.py +88 -0
  262. evalkit/client/evaluators.py +11 -0
  263. evalkit/client/sessions.py +17 -0
  264. evalkit/client/store.py +139 -0
  265. evalkit/client/summary.py +46 -0
  266. evalkit/client/sync.py +198 -0
  267. evalkit/config.py +227 -0
  268. evalkit/contracts/__init__.py +0 -0
  269. evalkit/contracts/atif.py +115 -0
  270. evalkit/contracts/cases.py +456 -0
  271. evalkit/contracts/custom_evaluator.py +58 -0
  272. evalkit/contracts/enums.py +86 -0
  273. evalkit/contracts/esf.py +120 -0
  274. evalkit/contracts/harness.py +281 -0
  275. evalkit/contracts/notes.json +4 -0
  276. evalkit/contracts/notes.py +7 -0
  277. evalkit/contracts/settings.py +41 -0
  278. evalkit/core/__init__.py +0 -0
  279. evalkit/core/access.py +75 -0
  280. evalkit/core/agents.py +65 -0
  281. evalkit/core/blob.py +46 -0
  282. evalkit/core/capabilities.py +36 -0
  283. evalkit/core/costmap.py +90 -0
  284. evalkit/core/custom_eval.py +50 -0
  285. evalkit/core/data/cost_map.json +81546 -0
  286. evalkit/core/endpoints.py +31 -0
  287. evalkit/core/evaluators/__init__.py +29 -0
  288. evalkit/core/evaluators/base.py +121 -0
  289. evalkit/core/evaluators/gauges.py +84 -0
  290. evalkit/core/evaluators/presentation.py +166 -0
  291. evalkit/core/evaluators/response_time.py +21 -0
  292. evalkit/core/evaluators/rubrics.py +494 -0
  293. evalkit/core/evaluators/tool_trajectory.py +187 -0
  294. evalkit/core/evaluators/trajectory_rubrics.py +198 -0
  295. evalkit/core/fixtures.py +57 -0
  296. evalkit/core/harness.py +227 -0
  297. evalkit/core/harness_check.py +191 -0
  298. evalkit/core/importer.py +257 -0
  299. evalkit/core/judge.py +78 -0
  300. evalkit/core/lint.py +265 -0
  301. evalkit/core/overlays.py +76 -0
  302. evalkit/core/paths.py +25 -0
  303. evalkit/core/promotion.py +94 -0
  304. evalkit/core/pull.py +198 -0
  305. evalkit/core/python_eval.py +59 -0
  306. evalkit/core/remote_evaluators.py +32 -0
  307. evalkit/core/review.py +220 -0
  308. evalkit/core/run_digest.py +421 -0
  309. evalkit/core/runner.py +995 -0
  310. evalkit/core/scorecard.py +255 -0
  311. evalkit/core/traces/__init__.py +0 -0
  312. evalkit/core/traces/convert.py +336 -0
  313. evalkit/core/traces/importer.py +113 -0
  314. evalkit/core/traces/mapping.py +385 -0
  315. evalkit/core/traces/poll.py +38 -0
  316. evalkit/core/traces/raw.py +21 -0
  317. evalkit/core/traces/sessions.py +172 -0
  318. evalkit/core/traces/settle.py +14 -0
  319. evalkit/core/traces/sources/__init__.py +19 -0
  320. evalkit/core/traces/sources/base.py +25 -0
  321. evalkit/core/traces/sources/phoenix.py +121 -0
  322. evalkit/core/trajectory.py +248 -0
  323. evalkit/core/workspace.py +30 -0
  324. evalkit/credentials.py +119 -0
  325. evalkit/db/__init__.py +0 -0
  326. evalkit/db/agents.py +289 -0
  327. evalkit/db/alembic/env.py +90 -0
  328. evalkit/db/alembic/script.py.mako +28 -0
  329. evalkit/db/alembic/versions/0011_baseline.py +1002 -0
  330. evalkit/db/alembic/versions/0012_ticket_token_kind.py +27 -0
  331. evalkit/db/alembic/versions/0013_keys_and_llm_providers.py +67 -0
  332. evalkit/db/cases.py +608 -0
  333. evalkit/db/crypto.py +12 -0
  334. evalkit/db/evaluators.py +156 -0
  335. evalkit/db/experiments.py +363 -0
  336. evalkit/db/fixture_blobs.py +91 -0
  337. evalkit/db/keys.py +78 -0
  338. evalkit/db/llm_providers.py +176 -0
  339. evalkit/db/locks.py +6 -0
  340. evalkit/db/mappings.py +168 -0
  341. evalkit/db/models.py +567 -0
  342. evalkit/db/promotion.py +271 -0
  343. evalkit/db/rekey.py +46 -0
  344. evalkit/db/review.py +250 -0
  345. evalkit/db/review_state.py +50 -0
  346. evalkit/db/runs.py +533 -0
  347. evalkit/db/scorecard.py +300 -0
  348. evalkit/db/session.py +71 -0
  349. evalkit/db/sessions.py +90 -0
  350. evalkit/db/spans.py +360 -0
  351. evalkit/db/store.py +209 -0
  352. evalkit/db/tokens.py +100 -0
  353. evalkit/db/trace_imports.py +308 -0
  354. evalkit/db/trace_sources.py +131 -0
  355. evalkit/db/users.py +186 -0
  356. evalkit/db/workspaces.py +232 -0
  357. evalkit/paths.py +33 -0
  358. evalkit/sdk/__init__.py +29 -0
  359. evalkit/sdk/binding.py +69 -0
  360. evalkit/sdk/evaluators.py +56 -0
  361. evalkit/sdk/runner.py +957 -0
  362. evalkit/sdk/tracing.py +166 -0
  363. evalkit/store/__init__.py +0 -0
  364. evalkit/store/base.py +354 -0
  365. evalkit/store/blob.py +69 -0
  366. evalkit/version.py +44 -0
  367. evalkit_py-0.2.0.dev12.dist-info/METADATA +205 -0
  368. evalkit_py-0.2.0.dev12.dist-info/RECORD +371 -0
  369. evalkit_py-0.2.0.dev12.dist-info/WHEEL +4 -0
  370. evalkit_py-0.2.0.dev12.dist-info/entry_points.txt +2 -0
  371. 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