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
evalkit/client/api.py ADDED
@@ -0,0 +1,193 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from urllib.parse import quote
6
+
7
+ import httpx
8
+ from httpx_retries import Retry, RetryTransport
9
+
10
+ from evalkit import version
11
+
12
+ RETRY_STATUSES = (502, 503, 504)
13
+ RETRY_ERRORS = (httpx.NetworkError, httpx.ConnectTimeout, httpx.RemoteProtocolError)
14
+ RETRY_ATTEMPTS = 4
15
+ RETRY_BACKOFF_FACTOR = 0.5
16
+ RETRY_MAX_WAIT_SECONDS = 8.0
17
+ READ_METHODS = ("GET", "HEAD")
18
+ WRITE_METHODS = ("GET", "HEAD", "POST", "PUT", "PATCH")
19
+
20
+
21
+ class ApiRequestError(Exception):
22
+ def __init__(self, status: int, detail: str, method: str, path: str):
23
+ self.status = status
24
+ self.detail = detail
25
+ super().__init__(f"{method} {path} → {status}: {detail}")
26
+
27
+ @classmethod
28
+ def from_response(cls, resp: httpx.Response) -> ApiRequestError:
29
+ return cls(
30
+ resp.status_code, response_detail(resp), resp.request.method, resp.request.url.path
31
+ )
32
+
33
+
34
+ def response_detail(resp: httpx.Response) -> str:
35
+ if resp.content and resp.headers.get("content-type", "").startswith("application/json"):
36
+ body = resp.json()
37
+ if isinstance(body, dict) and body.get("detail") is not None:
38
+ return str(body["detail"])
39
+ return resp.reason_phrase
40
+
41
+
42
+ def retry_policy(methods: tuple[str, ...]) -> Retry:
43
+ return Retry(
44
+ total=RETRY_ATTEMPTS,
45
+ allowed_methods=methods,
46
+ status_forcelist=RETRY_STATUSES,
47
+ retry_on_exceptions=RETRY_ERRORS,
48
+ backoff_factor=RETRY_BACKOFF_FACTOR,
49
+ max_backoff_wait=RETRY_MAX_WAIT_SECONDS,
50
+ )
51
+
52
+
53
+ def retry_extensions(retry: bool | None) -> dict[str, Retry]:
54
+ if retry is None:
55
+ return {}
56
+ return {"retry": retry_policy(WRITE_METHODS) if retry else Retry(total=0)}
57
+
58
+
59
+ class VersionMismatch(Exception):
60
+ def __init__(self, local: str, remote: str):
61
+ self.local = local
62
+ self.remote = remote
63
+ super().__init__(
64
+ f"evalkit {local} does not match server {remote}; local runs need the same "
65
+ "version. Run `evalkit update` from the project directory: it reinstalls the CLI "
66
+ "and moves the evalkit-py pin in ./pyproject.toml"
67
+ )
68
+
69
+
70
+ # Shorter than uvicorn's 5s idle keep-alive, so a poll never reuses a
71
+ # connection the server is closing at the same moment.
72
+ KEEPALIVE_EXPIRY_SECONDS = 2
73
+ DEFAULT_TIMEOUT_SECONDS = 120
74
+
75
+
76
+ def timeout_seconds() -> float:
77
+ return float(os.environ.get("EVALKIT_TIMEOUT", DEFAULT_TIMEOUT_SECONDS))
78
+
79
+
80
+ def encode(segment: str) -> str:
81
+ return quote(segment, safe="")
82
+
83
+
84
+ class AsyncApi:
85
+ def __init__(
86
+ self,
87
+ server_url: str,
88
+ token: str | None,
89
+ *,
90
+ transport: httpx.AsyncBaseTransport | None = None,
91
+ ):
92
+ self._client = httpx.AsyncClient(
93
+ base_url=server_url.rstrip("/"),
94
+ headers={"Authorization": f"Bearer {token}"} if token else {},
95
+ timeout=timeout_seconds(),
96
+ transport=RetryTransport(
97
+ transport=transport
98
+ or httpx.AsyncHTTPTransport(
99
+ limits=httpx.Limits(keepalive_expiry=KEEPALIVE_EXPIRY_SECONDS)
100
+ ),
101
+ retry=retry_policy(READ_METHODS),
102
+ ),
103
+ )
104
+ self.remote_version: str | None = None
105
+
106
+ async def aclose(self) -> None:
107
+ await self._client.aclose()
108
+
109
+ async def request(
110
+ self, method: str, path: str, *, retry: bool | None = None, **kwargs
111
+ ) -> httpx.Response:
112
+ resp = await self._client.request(
113
+ method, path, extensions=retry_extensions(retry), **kwargs
114
+ )
115
+ self.remote_version = resp.headers.get("X-Evalkit-Version", self.remote_version)
116
+ if resp.status_code >= 400:
117
+ raise ApiRequestError.from_response(resp)
118
+ return resp
119
+
120
+ async def get(self, path: str, **kwargs) -> httpx.Response:
121
+ return await self.request("GET", path, **kwargs)
122
+
123
+ async def post(self, path: str, **kwargs) -> httpx.Response:
124
+ return await self.request("POST", path, **kwargs)
125
+
126
+ async def put(self, path: str, **kwargs) -> httpx.Response:
127
+ return await self.request("PUT", path, **kwargs)
128
+
129
+ async def patch(self, path: str, **kwargs) -> httpx.Response:
130
+ return await self.request("PATCH", path, **kwargs)
131
+
132
+ async def head(self, path: str, **kwargs) -> httpx.Response:
133
+ resp = await self._client.request("HEAD", path, **kwargs)
134
+ self.remote_version = resp.headers.get("X-Evalkit-Version", self.remote_version)
135
+ return resp
136
+
137
+ async def require_same_version(self) -> None:
138
+ await self.get("/api/health")
139
+ local = version.current()
140
+ if self.remote_version != local:
141
+ raise VersionMismatch(local, self.remote_version or "unknown")
142
+
143
+
144
+ class SyncApi:
145
+ def __init__(
146
+ self,
147
+ server_url: str,
148
+ token: str | None = None,
149
+ *,
150
+ transport: httpx.BaseTransport | None = None,
151
+ ):
152
+ self._client = httpx.Client(
153
+ base_url=server_url.rstrip("/"),
154
+ headers={"Authorization": f"Bearer {token}"} if token else {},
155
+ timeout=timeout_seconds(),
156
+ transport=RetryTransport(
157
+ transport=transport or httpx.HTTPTransport(), retry=retry_policy(READ_METHODS)
158
+ ),
159
+ )
160
+
161
+ def _handle(self, resp: httpx.Response) -> httpx.Response:
162
+ if resp.status_code >= 400:
163
+ raise ApiRequestError.from_response(resp)
164
+ return resp
165
+
166
+ def request(
167
+ self, method: str, path: str, *, retry: bool | None = None, **kwargs
168
+ ) -> httpx.Response:
169
+ return self._handle(
170
+ self._client.request(method, path, extensions=retry_extensions(retry), **kwargs)
171
+ )
172
+
173
+ def get(self, path: str, **kwargs) -> httpx.Response:
174
+ return self.request("GET", path, **kwargs)
175
+
176
+ def post(self, path: str, **kwargs) -> httpx.Response:
177
+ return self.request("POST", path, **kwargs)
178
+
179
+ def delete(self, path: str, **kwargs) -> httpx.Response:
180
+ return self.request("DELETE", path, **kwargs)
181
+
182
+ def post_files(self, path: str, files: list[tuple[str, Path]], **kwargs) -> httpx.Response:
183
+ parts = [("files", (name, f.read_bytes(), "application/octet-stream")) for name, f in files]
184
+ return self.request("POST", path, files=parts, **kwargs)
185
+
186
+ def patch(self, path: str, **kwargs) -> httpx.Response:
187
+ return self.request("PATCH", path, **kwargs)
188
+
189
+ def put(self, path: str, **kwargs) -> httpx.Response:
190
+ return self.request("PUT", path, **kwargs)
191
+
192
+ def head(self, path: str, **kwargs) -> httpx.Response:
193
+ return self._client.request("HEAD", path, **kwargs)
evalkit/client/blob.py ADDED
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import AsyncIterator
4
+ from pathlib import Path
5
+ from tempfile import mkdtemp
6
+
7
+ from evalkit.client.api import AsyncApi
8
+ from evalkit.store.base import BlobStream, CaseFile
9
+
10
+ FIXTURE_PREFIX = "fixtures/sha256/"
11
+ CHECK_PREFIX = "checks/"
12
+
13
+
14
+ class HttpBlobStore:
15
+ def __init__(self, api: AsyncApi, experiment_id: int | None, cache_dir: Path | None = None):
16
+ self._api = api
17
+ self._experiment_id = experiment_id
18
+ self._configured_dir = cache_dir
19
+ self._scratch_dir: Path | None = None
20
+
21
+ def _cache_dir(self) -> Path:
22
+ if self._configured_dir is not None:
23
+ return self._configured_dir
24
+ if self._scratch_dir is None:
25
+ self._scratch_dir = Path(mkdtemp(prefix="evalkit-fixtures-"))
26
+ return self._scratch_dir
27
+
28
+ def _local_path(self, key: str) -> Path | None:
29
+ if key.startswith(FIXTURE_PREFIX):
30
+ return self._cache_dir() / ".sha256" / key.removeprefix(FIXTURE_PREFIX)
31
+ if key.startswith(CHECK_PREFIX):
32
+ return self._cache_dir() / ".checks" / key.removeprefix(CHECK_PREFIX)
33
+ return None
34
+
35
+ def _experiment_path(self) -> str:
36
+ if self._experiment_id is None:
37
+ raise ValueError("no experiment bound; only fixture and check keys are available")
38
+ return f"/api/experiments/{self._experiment_id}/blob"
39
+
40
+ def _remote(self, key: str) -> str:
41
+ return f"{self._experiment_path()}/{key}"
42
+
43
+ async def put_object(self, key: str, data: bytes) -> None:
44
+ local = self._local_path(key)
45
+ if local is not None:
46
+ local.parent.mkdir(parents=True, exist_ok=True)
47
+ local.write_bytes(data)
48
+ return
49
+ await self._api.put(self._remote(key), content=data, retry=True)
50
+
51
+ async def put_stream(self, key: str, chunks: AsyncIterator[bytes]) -> None:
52
+ await self.put_object(key, b"".join([chunk async for chunk in chunks]))
53
+
54
+ async def _cached(self, key: str, local: Path) -> Path:
55
+ if not local.is_file() and key.startswith(FIXTURE_PREFIX):
56
+ resp = await self._api.get(f"/api/fixtures/sha256/{key.removeprefix(FIXTURE_PREFIX)}")
57
+ local.parent.mkdir(parents=True, exist_ok=True)
58
+ local.write_bytes(resp.content)
59
+ return local
60
+
61
+ async def get_object(self, key: str) -> bytes:
62
+ local = self._local_path(key)
63
+ if local is None:
64
+ return (await self._api.get(self._remote(key))).content
65
+ return (await self._cached(key, local)).read_bytes()
66
+
67
+ async def get_stream(self, key: str) -> BlobStream:
68
+ data = await self.get_object(key)
69
+ return BlobStream(len(data), _once(data))
70
+
71
+ async def fixture_path(self, file: CaseFile) -> Path:
72
+ named = self._cache_dir() / file.sha256 / file.key
73
+ if not named.is_file():
74
+ named.parent.mkdir(parents=True, exist_ok=True)
75
+ named.write_bytes(await self.get_object(f"{FIXTURE_PREFIX}{file.sha256}"))
76
+ return named
77
+
78
+ async def object_exists(self, key: str) -> bool:
79
+ local = self._local_path(key)
80
+ if local is not None:
81
+ return local.is_file()
82
+ return (await self._api.head(self._remote(key))).status_code == 200
83
+
84
+ async def list_objects(self, prefix: str) -> list[tuple[str, int]]:
85
+ if self._local_path(prefix) is not None:
86
+ raise ValueError("listing local keys is not supported")
87
+ body = (await self._api.get(self._experiment_path(), params={"prefix": prefix})).json()
88
+ return [(str(key), int(size)) for key, size in body["objects"]]
89
+
90
+ async def delete_object(self, key: str) -> None:
91
+ raise NotImplementedError
92
+
93
+ async def copy_object(self, source: str, destination: str) -> None:
94
+ raise NotImplementedError
95
+
96
+ async def object_url(self, key: str) -> str:
97
+ local = self._local_path(key)
98
+ if local is None:
99
+ raise ValueError(f"{key} has no URL a harness can fetch")
100
+ return (await self._cached(key, local)).resolve().as_uri()
101
+
102
+
103
+ async def _once(data: bytes) -> AsyncIterator[bytes]:
104
+ yield data
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import tempfile
5
+ import tomllib
6
+ from datetime import datetime, timedelta
7
+ from pathlib import Path
8
+ from typing import NamedTuple
9
+
10
+ CONFIG_PATH = Path.home() / ".evalkit" / "config.toml"
11
+
12
+ TOKEN_WARNING_WINDOW = timedelta(days=1)
13
+
14
+
15
+ class Connection(NamedTuple):
16
+ url: str
17
+ token: str | None
18
+ username: str | None
19
+ from_env: bool = False
20
+
21
+
22
+ def _toml_string(value: str) -> str:
23
+ escaped = value.replace("\\", "\\\\").replace('"', '\\"')
24
+ return f'"{escaped}"'
25
+
26
+
27
+ def load(path: Path | None = None) -> Connection | None:
28
+ target = path or CONFIG_PATH
29
+ if not target.is_file():
30
+ return None
31
+ data = tomllib.loads(target.read_text())
32
+ if "url" not in data:
33
+ if "profiles" in data:
34
+ raise LookupError(
35
+ f"{target} holds server profiles, which evalkit no longer supports; "
36
+ "run `evalkit login --url <url>` to replace it"
37
+ )
38
+ return None
39
+ return Connection(str(data["url"]), data.get("token"), data.get("username"))
40
+
41
+
42
+ def save(connection: Connection, path: Path | None = None) -> Path:
43
+ target = path or CONFIG_PATH
44
+ lines = [f"url = {_toml_string(connection.url)}"]
45
+ if connection.token is not None:
46
+ lines.append(f"token = {_toml_string(connection.token)}")
47
+ if connection.username is not None:
48
+ lines.append(f"username = {_toml_string(connection.username)}")
49
+ target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
50
+ target.parent.chmod(0o700)
51
+ fd, temp = tempfile.mkstemp(dir=target.parent, prefix=f".{target.name}.")
52
+ with os.fdopen(fd, "w") as file:
53
+ file.write("\n".join(lines) + "\n")
54
+ os.replace(temp, target)
55
+ return target
56
+
57
+
58
+ def active(path: Path | None = None) -> Connection:
59
+ env_url = os.environ.get("EVALKIT_SERVER_URL")
60
+ if env_url:
61
+ return Connection(env_url, os.environ.get("EVALKIT_TOKEN"), None, from_env=True)
62
+ stored = load(path or CONFIG_PATH)
63
+ if stored is None:
64
+ raise LookupError(
65
+ "no server configured — run `evalkit login --url <url>` or set EVALKIT_SERVER_URL"
66
+ )
67
+ return stored
68
+
69
+
70
+ def renew_hint(connection: Connection) -> str:
71
+ if connection.from_env:
72
+ return "set a fresh EVALKIT_TOKEN"
73
+ return f"run `evalkit login --url {connection.url}`"
74
+
75
+
76
+ def token_expiry_warning(
77
+ connection: Connection, expires_at: str | None, now: datetime
78
+ ) -> str | None:
79
+ if expires_at is None:
80
+ return None
81
+ left = datetime.fromisoformat(expires_at) - now
82
+ if left > TOKEN_WARNING_WINDOW:
83
+ return None
84
+ hours = max(left.total_seconds(), 0) / 3600
85
+ return (
86
+ f"the token for {connection.url} expires in {hours:.1f}h; "
87
+ f"{renew_hint(connection)} before a long run"
88
+ )
@@ -0,0 +1,11 @@
1
+ from evalkit.client.api import AsyncApi
2
+ from evalkit.core.remote_evaluators import RemoteEvaluator
3
+
4
+
5
+ class HttpEvaluators:
6
+ def __init__(self, api: AsyncApi):
7
+ self._api = api
8
+
9
+ async def in_workspace(self, workspace: str) -> dict[str, RemoteEvaluator]:
10
+ rows = (await self._api.get("/api/evaluators", params={"workspace": workspace})).json()
11
+ return {row["name"]: RemoteEvaluator.model_validate(row) for row in rows}
@@ -0,0 +1,17 @@
1
+ from evalkit.client.api import ApiRequestError, AsyncApi, encode
2
+ from evalkit.core.traces.sessions import SessionTooLarge, SessionTrajectory
3
+
4
+
5
+ class HttpSessionReader:
6
+ def __init__(self, api: AsyncApi):
7
+ self._api = api
8
+
9
+ async def trajectory(self, agent: str, session_id: str) -> SessionTrajectory:
10
+ path = f"/api/agents/{encode(agent)}/sessions/{encode(session_id)}/trajectory"
11
+ try:
12
+ resp = await self._api.get(path)
13
+ except ApiRequestError as exc:
14
+ if exc.status == 413:
15
+ raise SessionTooLarge(exc.detail) from exc
16
+ raise
17
+ return SessionTrajectory.model_validate(resp.json())
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import socket
5
+
6
+ from evalkit.client.api import AsyncApi, encode
7
+ from evalkit.contracts.enums import ExperimentStatus
8
+ from evalkit.core.runner import LEASE_SECONDS, ExperimentRequest
9
+ from evalkit.store.base import (
10
+ AgentRunRecord,
11
+ CaseRep,
12
+ CaseView,
13
+ EvaluationRow,
14
+ Experiment,
15
+ RunDetail,
16
+ RunForScoring,
17
+ RunRef,
18
+ Selection,
19
+ )
20
+
21
+ RENEW_TIMEOUT_SECONDS = LEASE_SECONDS / 6
22
+
23
+
24
+ def local_holder(username: str) -> str:
25
+ return f"local:{username}@{socket.gethostname()}:{os.getpid()}"
26
+
27
+
28
+ class HttpStore:
29
+ def __init__(self, api: AsyncApi):
30
+ self._api = api
31
+
32
+ async def aclose(self) -> None:
33
+ await self._api.aclose()
34
+
35
+ async def cases_for(
36
+ self, dataset: str, selection: Selection, *, agent: str | None
37
+ ) -> list[CaseView]:
38
+ rows = (
39
+ await self._api.post(
40
+ f"/api/datasets/{encode(dataset)}/cases/select",
41
+ json={
42
+ "selection": selection.model_dump(exclude_none=True),
43
+ "agent": agent,
44
+ },
45
+ retry=True,
46
+ )
47
+ ).json()
48
+ return [CaseView.model_validate(r) for r in rows]
49
+
50
+ async def start_experiment(self, req: ExperimentRequest) -> tuple[Experiment, list[str]]:
51
+ body = req.model_dump(mode="json", exclude_none=True)
52
+ created = (await self._api.post("/api/experiments", json=body)).json()
53
+ return Experiment.model_validate(created), list(created.get("dropped_cases") or [])
54
+
55
+ async def get_experiment(self, exp_id: int) -> Experiment:
56
+ return Experiment.model_validate((await self._api.get(f"/api/experiments/{exp_id}")).json())
57
+
58
+ async def pinned_cases(self, exp_id: int) -> list[CaseView]:
59
+ rows = (await self._api.get(f"/api/experiments/{exp_id}/cases")).json()
60
+ return [CaseView.model_validate(r) for r in rows]
61
+
62
+ async def transition(
63
+ self, exp_id: int, to: ExperimentStatus, holder: str | None, error: str | None = None
64
+ ) -> Experiment:
65
+ body = {"status": to.value, "holder": holder, "error": error}
66
+ resp = await self._api.patch(f"/api/experiments/{exp_id}", json=body, retry=True)
67
+ return Experiment.model_validate(resp.json())
68
+
69
+ async def renew_lease(self, exp_id: int, holder: str) -> bool:
70
+ resp = await self._api.post(
71
+ f"/api/experiments/{exp_id}/lease",
72
+ json={"holder": holder},
73
+ retry=True,
74
+ timeout=RENEW_TIMEOUT_SECONDS,
75
+ )
76
+ return bool(resp.json()["renewed"])
77
+
78
+ async def set_model(self, exp_id: int, model: str) -> None:
79
+ await self._api.patch(f"/api/experiments/{exp_id}", json={"model": model}, retry=True)
80
+
81
+ async def pending_runs(self, exp_id: int) -> list[CaseRep]:
82
+ rows = (await self._api.get(f"/api/experiments/{exp_id}/pending")).json()
83
+ return [CaseRep.model_validate(r) for r in rows]
84
+
85
+ async def harness_run_id_used(self, exp_id: int, harness_run_id: str) -> str | None:
86
+ resp = await self._api.get(
87
+ f"/api/experiments/{exp_id}/harness-run-id", params={"value": harness_run_id}
88
+ )
89
+ return resp.json()["used"]
90
+
91
+ async def record_run(
92
+ self,
93
+ exp_id: int,
94
+ case_id: str,
95
+ repetition: int,
96
+ run: AgentRunRecord,
97
+ *,
98
+ holder: str | None = None,
99
+ ) -> RunRef:
100
+ resp = await self._api.post(
101
+ f"/api/experiments/{exp_id}/runs",
102
+ json={
103
+ "case_id": case_id,
104
+ "repetition": repetition,
105
+ "run": run.model_dump(mode="json"),
106
+ "holder": holder,
107
+ },
108
+ )
109
+ return RunRef.model_validate(resp.json())
110
+
111
+ async def runs_to_score(self, exp_id: int, *, force: bool) -> list[RunForScoring]:
112
+ rows = (
113
+ await self._api.get(
114
+ f"/api/experiments/{exp_id}/runs-to-score", params={"force": str(force).lower()}
115
+ )
116
+ ).json()
117
+ return [RunForScoring.model_validate(r) for r in rows]
118
+
119
+ async def publish_evaluations(
120
+ self,
121
+ experiment_run_id: int,
122
+ rows: list[EvaluationRow],
123
+ *,
124
+ force: bool,
125
+ holder: str | None = None,
126
+ ) -> None:
127
+ await self._api.post(
128
+ f"/api/experiment-runs/{experiment_run_id}/evaluations",
129
+ json={
130
+ "rows": [r.model_dump(mode="json") for r in rows],
131
+ "force": force,
132
+ "holder": holder,
133
+ },
134
+ retry=True,
135
+ )
136
+
137
+ async def run_details(self, exp_id: int) -> list[RunDetail]:
138
+ rows = (await self._api.get(f"/api/experiments/{exp_id}/runs")).json()
139
+ return [RunDetail.model_validate(r) for r in rows]
@@ -0,0 +1,46 @@
1
+ def _fmt(value: float | None) -> str:
2
+ return f"{value:.2f}" if value is not None else " - "
3
+
4
+
5
+ def _num(value: float | None) -> str:
6
+ if value is None:
7
+ return "-"
8
+ if value and abs(value) < 1:
9
+ return f"{value:.3g}"
10
+ return f"{value:.2f}".rstrip("0").rstrip(".")
11
+
12
+
13
+ def summary_lines(report: dict) -> list[str]:
14
+ lines = [f"\nexperiment {report['experiment_id']} {report['name']} of {report['dataset']}"]
15
+ scored = {bucket: value for bucket, value in report["buckets"].items() if value is not None}
16
+ if scored:
17
+ lines.append("buckets:")
18
+ lines += [f" {bucket:<14} {_fmt(value)}" for bucket, value in scored.items()]
19
+ lines.append("evaluators:")
20
+ for stat in report["evaluators"]:
21
+ errored = f", {stat['errors']} errored" if stat.get("errors") else ""
22
+ noun = "case" if stat["cases"] == 1 else "cases"
23
+ counts = f"({stat['cases']} {noun}{errored})"
24
+ lines.append(f" {stat['name']:<40} {_fmt(stat['score'])} {counts}")
25
+ failing = [c for c in report["cases"] if c["overall"] is not None and c["overall"] < 0.8]
26
+ if failing:
27
+ lines.append("failing cases (< 0.80):")
28
+ for case in failing:
29
+ delta = f" Δ{case['delta']:+.2f}" if case.get("delta") is not None else ""
30
+ lines.append(f" {case['case_id']:<48} {_fmt(case['overall'])}{delta}")
31
+ errored_cases = [c for c in report["cases"] if c["errors"] or c["run_errors"]]
32
+ if errored_cases:
33
+ lines.append("errored cases:")
34
+ for case in errored_cases:
35
+ problems = [("run", error) for error in case["run_errors"]]
36
+ problems += sorted(case["errors"].items())
37
+ for name, error in problems:
38
+ first_line = error.splitlines()[0][:120] if error else ""
39
+ lines.append(f" {case['case_id']:<48} {name}: {first_line}")
40
+ if report["gauges"]:
41
+ lines.append("gauges:")
42
+ lines += [
43
+ f" {gauge['name']:<32} mean={_num(gauge['mean'])} median={_num(gauge['median'])}"
44
+ for gauge in report["gauges"]
45
+ ]
46
+ return lines