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,590 @@
1
+ import json
2
+ import os
3
+ from collections.abc import Callable
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+ from evalkit.cli.client import ApiClient, ApiError
9
+ from evalkit.client.api import encode
10
+ from evalkit.contracts.notes import ENDPOINT_NOTE
11
+ from evalkit.core.agents import explicit
12
+ from evalkit.core.workspace import parse_reference
13
+
14
+ workspaces_app = typer.Typer(no_args_is_help=True, help="Workspaces and their members.")
15
+ members_app = typer.Typer(no_args_is_help=True, help="Writers and reviewers of a shared workspace.")
16
+ workspaces_app.add_typer(members_app, name="members")
17
+ agents_app = typer.Typer(no_args_is_help=True, help="Agents: create, edit, list, archive.")
18
+ trace_sources_app = typer.Typer(
19
+ no_args_is_help=True, help="Third-party trace stores for evalkit traces import (admin only)."
20
+ )
21
+ evaluators_app = typer.Typer(
22
+ no_args_is_help=True, help="Remote evaluators (Custom Evaluator API endpoints) per workspace."
23
+ )
24
+ llm_providers_app = typer.Typer(
25
+ no_args_is_help=True,
26
+ help="LLM providers: OpenAI-compatible endpoints the server's judge calls (admin only).",
27
+ )
28
+ judge_default_app = typer.Typer(
29
+ no_args_is_help=True, help="The judge experiments use unless they pick their own."
30
+ )
31
+
32
+ API_KEY_ENV = typer.Option(
33
+ None,
34
+ "--api-key-env",
35
+ help="Env var on this machine that holds the API key; the key is sent once and "
36
+ "stored encrypted.",
37
+ )
38
+ CLEAR_API_KEY = typer.Option(False, "--clear-api-key", help="Remove the stored API key.")
39
+
40
+ ROLE = typer.Option(..., "--role", help="writer or reviewer.")
41
+
42
+
43
+ def _provides(pairs: list[str]) -> dict[str, bool] | None:
44
+ if not pairs:
45
+ return None
46
+ parsed: dict[str, bool] = {}
47
+ for pair in pairs:
48
+ key, sep, value = pair.partition("=")
49
+ if not sep or value not in ("true", "false"):
50
+ raise ApiError(f"--provides takes key=true or key=false, got {pair!r}")
51
+ parsed[key] = value == "true"
52
+ return parsed
53
+
54
+
55
+ def _settings(values: dict[str, int | None], provides: list[str]) -> dict:
56
+ settings: dict = {key: value for key, value in values.items() if value is not None}
57
+ flags = _provides(provides)
58
+ if flags is not None:
59
+ settings["provides"] = flags
60
+ return settings
61
+
62
+
63
+ def _print_agent(agent: dict) -> None:
64
+ typer.echo(f"{agent['ref']} ({agent['workspace_type']}, driven by {agent['driver']})")
65
+ typer.echo(f" harness_url {agent['harness_url'] or '-'}")
66
+ typer.echo(f" trajectory_source {agent['trajectory_source']}")
67
+ typer.echo(f" repo_url {agent['repo_url'] or '-'}")
68
+ for key, value in agent["settings"].items():
69
+ typer.echo(f" {key:<18} {'-' if value is None else value}")
70
+ if agent["workspace_type"] == "shared":
71
+ typer.echo(f" api key {'set' if agent['has_api_key'] else 'not set'}")
72
+ if agent["archived"]:
73
+ typer.echo(" archived")
74
+
75
+
76
+ def register(
77
+ app: typer.Typer, client_factory: Callable[[], ApiClient], whoami: Callable[[], str]
78
+ ) -> None:
79
+ app.add_typer(workspaces_app, name="workspaces")
80
+ app.add_typer(agents_app, name="agents")
81
+ app.add_typer(evaluators_app, name="evaluators")
82
+ app.add_typer(trace_sources_app, name="trace-sources")
83
+ app.add_typer(llm_providers_app, name="llm-providers")
84
+ app.add_typer(judge_default_app, name="judge-default")
85
+
86
+ def env_value(name: str | None) -> str | None:
87
+ if name is None:
88
+ return None
89
+ if name not in os.environ:
90
+ raise ApiError(f"--api-key-env names {name}, which is not set")
91
+ return os.environ[name]
92
+
93
+ def key_change(api_key_env: str | None, clear: bool) -> dict:
94
+ if api_key_env is not None and clear:
95
+ raise ApiError("pass --api-key-env or --clear-api-key, not both")
96
+ if clear:
97
+ return {"api_key": None}
98
+ if api_key_env is not None:
99
+ return {"api_key": env_value(api_key_env)}
100
+ return {}
101
+
102
+ @trace_sources_app.command("create")
103
+ def trace_sources_create(
104
+ name: str,
105
+ url: str = typer.Option(..., "--url", help=ENDPOINT_NOTE),
106
+ provider: str = typer.Option("phoenix", "--provider"),
107
+ api_key_env: str | None = typer.Option(
108
+ None, "--api-key-env", help="Env var on this machine that holds the API key."
109
+ ),
110
+ ):
111
+ """Register a trace source. The key is sent once and stored encrypted.
112
+
113
+ Example: evalkit trace-sources create phx --url https://phx --api-key-env PHX_KEY
114
+ """
115
+ body = {"name": name, "provider": provider, "url": url, "api_key": env_value(api_key_env)}
116
+ created = client_factory().post("/api/trace-sources", json=body).json()
117
+ typer.echo(f"created trace source {created['name']} ({created['provider']})")
118
+
119
+ @trace_sources_app.command("edit")
120
+ def trace_sources_edit(
121
+ name: str,
122
+ url: str | None = typer.Option(None, "--url", help=ENDPOINT_NOTE),
123
+ api_key_env: str | None = typer.Option(None, "--api-key-env"),
124
+ ):
125
+ """Change a trace source's URL or key.
126
+
127
+ Example: evalkit trace-sources edit phx --url https://x
128
+ """
129
+ body = {"url": url, "api_key": env_value(api_key_env)}
130
+ client_factory().patch(f"/api/trace-sources/{encode(name)}", json=body)
131
+ typer.echo(f"edited trace source {name}")
132
+
133
+ @trace_sources_app.command("list")
134
+ def trace_sources_list():
135
+ """List trace sources; keys are never shown. Example: evalkit trace-sources list"""
136
+ for row in client_factory().get("/api/trace-sources").json():
137
+ typer.echo(f"{row['name']:<24} {row['provider']:<8} {row['url']}")
138
+
139
+ @trace_sources_app.command("archive")
140
+ def trace_sources_archive(name: str):
141
+ """Archive a trace source. Example: evalkit trace-sources archive phoenix-prod"""
142
+ client_factory().post(f"/api/trace-sources/{encode(name)}/archive")
143
+ typer.echo(f"archived trace source {name}")
144
+
145
+ @evaluators_app.command("create")
146
+ def evaluators_create(
147
+ name: str,
148
+ endpoint: str = typer.Option(..., "--endpoint", help=ENDPOINT_NOTE),
149
+ kind: str = typer.Option("score", "--kind", help="score or gauge."),
150
+ timeout: int = typer.Option(120, "--timeout", help="Seconds."),
151
+ workspace: str | None = typer.Option(
152
+ None, "--workspace", help="Default: your personal workspace."
153
+ ),
154
+ api_key_env: str | None = API_KEY_ENV,
155
+ ):
156
+ """Register a remote evaluator. In a shared workspace its key is stored on the
157
+ server (--api-key-env); in your personal workspace it goes in evalkit.yaml
158
+ credentials.evaluators.
159
+
160
+ Runs score with the evaluators in the agent's workspace.
161
+ Example: evalkit evaluators create compliance --workspace dev --endpoint https://x/eval
162
+ """
163
+ body = {
164
+ "workspace": workspace or whoami(),
165
+ "name": name,
166
+ "kind": kind,
167
+ "settings": {"endpoint": endpoint, "timeout_seconds": timeout},
168
+ **key_change(api_key_env, False),
169
+ }
170
+ created = client_factory().post("/api/evaluators", json=body).json()
171
+ typer.echo(f"created {created['ref']} ({created['kind']})")
172
+
173
+ @evaluators_app.command("edit")
174
+ def evaluators_edit(
175
+ ref: str,
176
+ endpoint: str | None = typer.Option(None, "--endpoint", help=ENDPOINT_NOTE),
177
+ timeout: int | None = typer.Option(None, "--timeout"),
178
+ api_key_env: str | None = API_KEY_ENV,
179
+ clear_api_key: bool = CLEAR_API_KEY,
180
+ ):
181
+ """Change a remote evaluator's endpoint, timeout or API key. A new endpoint clears
182
+ the stored key unless --api-key-env sets one in the same call.
183
+
184
+ Example: evalkit evaluators edit compliance@dev --api-key-env COMPLIANCE_KEY
185
+ """
186
+ resolved = ref_of(ref)
187
+ client = client_factory()
188
+ current = client.get(f"/api/evaluators/{encode(resolved)}").json()
189
+ settings = {
190
+ **current["settings"],
191
+ **{
192
+ k: v
193
+ for k, v in {"endpoint": endpoint, "timeout_seconds": timeout}.items()
194
+ if v is not None
195
+ },
196
+ }
197
+ edited = client.patch(
198
+ f"/api/evaluators/{encode(resolved)}",
199
+ json={"settings": settings, **key_change(api_key_env, clear_api_key)},
200
+ )
201
+ typer.echo(f"edited {edited.json()['ref']}")
202
+
203
+ @evaluators_app.command("show")
204
+ def evaluators_show(ref: str):
205
+ """Show a remote evaluator. Example: evalkit evaluators show compliance@dev"""
206
+ row = client_factory().get(f"/api/evaluators/{encode(ref_of(ref))}").json()
207
+ typer.echo(f"{row['ref']} ({row['kind']})")
208
+ typer.echo(f" endpoint {row['settings']['endpoint']}")
209
+ typer.echo(f" timeout {row['settings']['timeout_seconds']}s")
210
+ typer.echo(f" api key {'set' if row['has_api_key'] else 'not set'}")
211
+ if row["archived"]:
212
+ typer.echo(" archived")
213
+
214
+ @evaluators_app.command("list")
215
+ def evaluators_list(workspace: str | None = typer.Option(None, "--workspace")):
216
+ """List remote evaluators. Example: evalkit evaluators list --workspace dev"""
217
+ params = {"workspace": workspace} if workspace else {}
218
+ for row in client_factory().get("/api/evaluators", params=params).json():
219
+ typer.echo(f"{row['ref']:<32} {row['kind']:<6} {row['settings']['endpoint']}")
220
+
221
+ @evaluators_app.command("archive")
222
+ def evaluators_archive(ref: str):
223
+ """Archive a remote evaluator. Example: evalkit evaluators archive compliance@dev"""
224
+ resolved = ref_of(ref)
225
+ client_factory().post(f"/api/evaluators/{encode(resolved)}/archive")
226
+ typer.echo(f"archived {resolved}")
227
+
228
+ @evaluators_app.command("restore")
229
+ def evaluators_restore(ref: str):
230
+ """Restore an archived remote evaluator.
231
+
232
+ Example: evalkit evaluators restore compliance@dev
233
+ """
234
+ resolved = ref_of(ref)
235
+ client_factory().post(f"/api/evaluators/{encode(resolved)}/restore")
236
+ typer.echo(f"restored {resolved}")
237
+
238
+ @llm_providers_app.command("create")
239
+ def llm_providers_create(
240
+ name: str,
241
+ base_url: str = typer.Option(..., "--base-url", help=ENDPOINT_NOTE),
242
+ api_key_env: str | None = API_KEY_ENV,
243
+ ):
244
+ """Register an OpenAI-compatible LLM provider (admin).
245
+
246
+ Example: evalkit llm-providers create openai --base-url https://api.openai.com/v1
247
+ """
248
+ body = {"name": name, "base_url": base_url, **key_change(api_key_env, False)}
249
+ created = client_factory().post("/api/llm-providers", json=body).json()
250
+ typer.echo(f"created LLM provider {created['name']} ({created['base_url']})")
251
+
252
+ @llm_providers_app.command("edit")
253
+ def llm_providers_edit(
254
+ name: str,
255
+ base_url: str | None = typer.Option(None, "--base-url", help=ENDPOINT_NOTE),
256
+ api_key_env: str | None = API_KEY_ENV,
257
+ clear_api_key: bool = CLEAR_API_KEY,
258
+ ):
259
+ """Change a provider's base URL or API key (admin). A new base URL clears the
260
+ stored key unless --api-key-env sets one in the same call.
261
+
262
+ Example: evalkit llm-providers edit openai --api-key-env OPENAI_API_KEY
263
+ """
264
+ body = {"base_url": base_url, **key_change(api_key_env, clear_api_key)}
265
+ client_factory().patch(f"/api/llm-providers/{encode(name)}", json=body)
266
+ typer.echo(f"edited LLM provider {name}")
267
+
268
+ @llm_providers_app.command("list")
269
+ def llm_providers_list(
270
+ archived: bool = typer.Option(False, "--archived", help="Include archived providers."),
271
+ ):
272
+ """List LLM providers; keys are never shown. Example: evalkit llm-providers list"""
273
+ rows = client_factory().get("/api/llm-providers", params={"include_archived": archived})
274
+ for row in rows.json():
275
+ key = "key set" if row["has_api_key"] else "no key"
276
+ state = "archived" if row["archived"] else ""
277
+ typer.echo(f"{row['name']:<24} {row['base_url']:<40} {key:<8} {state}")
278
+
279
+ @llm_providers_app.command("show")
280
+ def llm_providers_show(name: str):
281
+ """Show an LLM provider. Example: evalkit llm-providers show openai"""
282
+ row = client_factory().get(f"/api/llm-providers/{encode(name)}").json()
283
+ typer.echo(row["name"])
284
+ typer.echo(f" base_url {row['base_url']}")
285
+ typer.echo(f" api key {'set' if row['has_api_key'] else 'not set'}")
286
+ if row["archived"]:
287
+ typer.echo(" archived")
288
+
289
+ @llm_providers_app.command("archive")
290
+ def llm_providers_archive(name: str):
291
+ """Archive an LLM provider (admin). Example: evalkit llm-providers archive openai"""
292
+ client_factory().post(f"/api/llm-providers/{encode(name)}/archive")
293
+ typer.echo(f"archived LLM provider {name}")
294
+
295
+ @llm_providers_app.command("restore")
296
+ def llm_providers_restore(name: str):
297
+ """Restore an archived LLM provider (admin).
298
+
299
+ Example: evalkit llm-providers restore openai
300
+ """
301
+ client_factory().post(f"/api/llm-providers/{encode(name)}/restore")
302
+ typer.echo(f"restored LLM provider {name}")
303
+
304
+ @judge_default_app.command("show")
305
+ def judge_default_show():
306
+ """Show the default judge. Example: evalkit judge-default show"""
307
+ for key, value in client_factory().get("/api/judge-default").json().items():
308
+ typer.echo(f"{key:<18} {value}")
309
+
310
+ @judge_default_app.command("set")
311
+ def judge_default_set(
312
+ provider: str = typer.Option(..., "--provider", help="An LLM provider name."),
313
+ model: str = typer.Option(..., "--model"),
314
+ effort: str | None = typer.Option(None, "--effort", help="Reasoning effort."),
315
+ max_tokens: int | None = typer.Option(None, "--max-tokens"),
316
+ concurrency: int | None = typer.Option(None, "--concurrency"),
317
+ ):
318
+ """Set the judge experiments use unless they pick their own (admin). Options you
319
+ leave out keep their current value.
320
+
321
+ Example: evalkit judge-default set --provider openai --model gpt-5
322
+ """
323
+ client = client_factory()
324
+ base_url = client.get(f"/api/llm-providers/{encode(provider)}").json()["base_url"]
325
+ judge = {
326
+ "base_url": base_url,
327
+ "model": model,
328
+ "reasoning_effort": effort,
329
+ "max_tokens": max_tokens,
330
+ "concurrency": concurrency,
331
+ }
332
+ client.patch("/api/judge-default", json=judge)
333
+ typer.echo(f"default judge: {model} on {provider} ({base_url})")
334
+
335
+ def ref_of(text: str) -> str:
336
+ try:
337
+ return explicit(parse_reference(text, whoami()))
338
+ except ValueError as exc:
339
+ raise ApiError(str(exc)) from exc
340
+
341
+ @agents_app.command("create")
342
+ def agents_create(
343
+ name: str,
344
+ workspace: str | None = typer.Option(
345
+ None, "--workspace", help="Default: your personal workspace."
346
+ ),
347
+ harness_url: str | None = typer.Option(
348
+ None,
349
+ "--harness-url",
350
+ help=f"Omit for a code-driven agent (SDK runs only). {ENDPOINT_NOTE}",
351
+ ),
352
+ trajectory_source: str = typer.Option("atif", "--trajectory-source", help="atif or otel."),
353
+ mapping: Path | None = typer.Option(
354
+ None,
355
+ "--mapping",
356
+ exists=True,
357
+ dir_okay=False,
358
+ help="Span mapping JSON; required with --trajectory-source otel.",
359
+ ),
360
+ repo_url: str | None = typer.Option(None, "--repo-url"),
361
+ max_concurrent_runs: int | None = typer.Option(None, "--max-concurrent-runs"),
362
+ run_timeout: int | None = typer.Option(None, "--run-timeout"),
363
+ poll_interval: int | None = typer.Option(None, "--poll-interval"),
364
+ stop_grace: int | None = typer.Option(None, "--stop-grace"),
365
+ trajectory_wait: int | None = typer.Option(None, "--trajectory-wait"),
366
+ provides: list[str] = typer.Option([], "--provides", help="key=true|false, repeatable."),
367
+ api_key_env: str | None = API_KEY_ENV,
368
+ ):
369
+ """Create an agent. In a shared workspace its key is stored on the server
370
+ (--api-key-env); in your personal workspace it goes in evalkit.yaml
371
+ credentials.agents.
372
+
373
+ Example: evalkit agents create ouragent --workspace dev --harness-url https://agent/evalkit/v1
374
+ """
375
+ settings = _settings(
376
+ {
377
+ "max_concurrent_runs": max_concurrent_runs,
378
+ "run_timeout_seconds": run_timeout,
379
+ "poll_interval_seconds": poll_interval,
380
+ "stop_grace_seconds": stop_grace,
381
+ "trajectory_wait_seconds": trajectory_wait,
382
+ },
383
+ provides,
384
+ )
385
+ body = {
386
+ "workspace": workspace or whoami(),
387
+ "name": name,
388
+ "harness_url": harness_url,
389
+ "trajectory_source": trajectory_source,
390
+ "repo_url": repo_url,
391
+ "settings": settings,
392
+ "mapping": json.loads(mapping.read_text()) if mapping else None,
393
+ **key_change(api_key_env, False),
394
+ }
395
+ _print_agent(client_factory().post("/api/agents", json=body).json())
396
+ if mapping:
397
+ typer.echo(f" mapping v1, active (from {mapping.name})")
398
+
399
+ @agents_app.command("edit")
400
+ def agents_edit(
401
+ ref: str,
402
+ harness_url: str | None = typer.Option(None, "--harness-url", help=ENDPOINT_NOTE),
403
+ repo_url: str | None = typer.Option(None, "--repo-url"),
404
+ trajectory_source: str | None = typer.Option(
405
+ None, "--trajectory-source", help="atif or otel."
406
+ ),
407
+ mapping: Path | None = typer.Option(
408
+ None,
409
+ "--mapping",
410
+ exists=True,
411
+ dir_okay=False,
412
+ help="Span mapping JSON, made the active version. Needed to switch to otel "
413
+ "unless the agent already has an active mapping.",
414
+ ),
415
+ max_concurrent_runs: int | None = typer.Option(None, "--max-concurrent-runs"),
416
+ run_timeout: int | None = typer.Option(None, "--run-timeout"),
417
+ poll_interval: int | None = typer.Option(None, "--poll-interval"),
418
+ stop_grace: int | None = typer.Option(None, "--stop-grace"),
419
+ trajectory_wait: int | None = typer.Option(None, "--trajectory-wait"),
420
+ provides: list[str] = typer.Option([], "--provides", help="key=true|false, repeatable."),
421
+ api_key_env: str | None = API_KEY_ENV,
422
+ clear_api_key: bool = CLEAR_API_KEY,
423
+ ):
424
+ """Change an agent's harness URL, API key, repo URL, trajectory source or settings;
425
+ other values stay. A new harness URL clears the stored key unless --api-key-env
426
+ sets one in the same call. Switching the trajectory source is refused while one of
427
+ the agent's experiments is running; experiments that already ran keep the source
428
+ they ran with.
429
+
430
+ Example: evalkit agents edit mybot --trajectory-source otel --mapping gen_ai.json
431
+ """
432
+ path = f"/api/agents/{encode(ref_of(ref))}"
433
+ client = client_factory()
434
+ current = client.get(path).json()
435
+ changed = _settings(
436
+ {
437
+ "max_concurrent_runs": max_concurrent_runs,
438
+ "run_timeout_seconds": run_timeout,
439
+ "poll_interval_seconds": poll_interval,
440
+ "stop_grace_seconds": stop_grace,
441
+ "trajectory_wait_seconds": trajectory_wait,
442
+ },
443
+ provides,
444
+ )
445
+ body: dict = {"settings": {**current["settings"], **changed}}
446
+ if harness_url is not None:
447
+ body["harness_url"] = harness_url
448
+ if repo_url is not None:
449
+ body["repo_url"] = repo_url
450
+ if trajectory_source is not None:
451
+ body["trajectory_source"] = trajectory_source
452
+ if mapping is not None:
453
+ body["mapping"] = json.loads(mapping.read_text())
454
+ body.update(key_change(api_key_env, clear_api_key))
455
+ _print_agent(client.patch(path, json=body).json())
456
+
457
+ @agents_app.command("show")
458
+ def agents_show(ref: str):
459
+ """Show an agent's settings. Example: evalkit agents show ouragent@dev"""
460
+ _print_agent(client_factory().get(f"/api/agents/{encode(ref_of(ref))}").json())
461
+
462
+ @agents_app.command("list")
463
+ def agents_list(
464
+ workspace: str | None = typer.Option(None, "--workspace"),
465
+ archived: bool = typer.Option(False, "--archived", help="Include archived agents."),
466
+ ):
467
+ """List agents. Example: evalkit agents list --workspace dev"""
468
+ params: dict = {"include_archived": archived}
469
+ if workspace:
470
+ params["workspace"] = workspace
471
+ for agent in client_factory().get("/api/agents", params=params).json():
472
+ state = "archived" if agent["archived"] else ""
473
+ typer.echo(
474
+ f"{agent['ref']:<32} {agent['driver']:<7} {agent['trajectory_source']:<5} "
475
+ f"{agent['harness_url'] or '-'} {state}"
476
+ )
477
+
478
+ @agents_app.command("archive")
479
+ def agents_archive(ref: str):
480
+ """Archive an agent: it no longer runs; its runs and results stay. Undo it with
481
+ evalkit agents restore.
482
+
483
+ Example: evalkit agents archive ouragent@dev
484
+ """
485
+ resolved = ref_of(ref)
486
+ client_factory().post(f"/api/agents/{encode(resolved)}/archive")
487
+ typer.echo(f"archived {resolved}")
488
+
489
+ @agents_app.command("restore")
490
+ def agents_restore(ref: str):
491
+ """Restore an archived agent, so it can run again.
492
+
493
+ Example: evalkit agents restore ouragent@dev
494
+ """
495
+ resolved = ref_of(ref)
496
+ client_factory().post(f"/api/agents/{encode(resolved)}/restore")
497
+ typer.echo(f"restored {resolved}")
498
+
499
+ @workspaces_app.command("list")
500
+ def workspaces_list(
501
+ archived: bool = typer.Option(False, "--archived", help="Include archived workspaces."),
502
+ ):
503
+ """List workspaces. Example: evalkit workspaces list"""
504
+ rows = client_factory().get("/api/workspaces", params={"include_archived": archived})
505
+ for ws in rows.json():
506
+ state = "archived" if ws["archived"] else ""
507
+ owner = ws["owner"] or "-"
508
+ typer.echo(f"{ws['name']:<24} {ws['type']:<9} owner {owner:<16} {state}")
509
+
510
+ @workspaces_app.command("create")
511
+ def workspaces_create(name: str):
512
+ """Create a shared workspace (admin only). Example: evalkit workspaces create dev"""
513
+ client_factory().post("/api/workspaces", json={"name": name})
514
+ typer.echo(f"created @{name}")
515
+
516
+ @workspaces_app.command("rename")
517
+ def workspaces_rename(name: str, new_name: str):
518
+ """Rename a shared workspace (admin only). Example: evalkit workspaces rename dev staging"""
519
+ body = client_factory().patch(f"/api/workspaces/{name}", json={"name": new_name}).json()
520
+ typer.echo(f"renamed @{name} to @{new_name}; now update:")
521
+ for line in body["update"]:
522
+ typer.echo(f" - {line}")
523
+
524
+ @workspaces_app.command("promote-to")
525
+ def workspaces_promote_to(
526
+ source: str,
527
+ target: str | None = typer.Argument(None, help="The promotion target workspace."),
528
+ none: bool = typer.Option(False, "--none", help="Clear the link."),
529
+ ):
530
+ """Make TARGET the promotion target of SOURCE (admin only).
531
+
532
+ Cases approved in a suite of SOURCE land, approved, in the same-named suite of
533
+ TARGET. TARGET is then fed by promotion only: syncs and review decisions there
534
+ are refused. One target per source, no chains.
535
+ Example: evalkit workspaces promote-to dev prod · evalkit workspaces promote-to dev --none
536
+ """
537
+ if none == (target is not None):
538
+ raise ApiError("pass a target workspace or --none")
539
+ body = (
540
+ client_factory().patch(f"/api/workspaces/{source}", json={"promotes_to": target}).json()
541
+ )
542
+ typer.echo(f"{source} promotes to {body['promotes_to'] or '-'}")
543
+ for line in body["update"]:
544
+ typer.echo(f" update: {line}")
545
+
546
+ @workspaces_app.command("archive")
547
+ def workspaces_archive(name: str):
548
+ """Archive a workspace: it can no longer be written to.
549
+
550
+ Admins archive shared workspaces; a user may archive their own.
551
+ Example: evalkit workspaces archive dev
552
+ """
553
+ client_factory().post(f"/api/workspaces/{name}/archive")
554
+ typer.echo(f"archived @{name}")
555
+
556
+ @workspaces_app.command("restore")
557
+ def workspaces_restore(name: str):
558
+ """Restore an archived workspace. Example: evalkit workspaces restore dev"""
559
+ client_factory().post(f"/api/workspaces/{name}/restore")
560
+ typer.echo(f"restored @{name}")
561
+
562
+ @members_app.command("list")
563
+ def members_list(
564
+ workspace: str,
565
+ role: str | None = typer.Option(None, "--role", help="writer or reviewer."),
566
+ ):
567
+ """List a workspace's members. Example: evalkit workspaces members list dev"""
568
+ params = {"role": role} if role else {}
569
+ for m in client_factory().get(f"/api/workspaces/{workspace}/members", params=params).json():
570
+ typer.echo(f"{m['username']:<24} {m['role']:<9} granted by {m['granted_by']}")
571
+
572
+ @members_app.command("add")
573
+ def members_add(workspace: str, username: str, role: str = ROLE):
574
+ """Grant a role on a shared workspace (admin only).
575
+
576
+ Example: evalkit workspaces members add dev alice --role reviewer
577
+ """
578
+ client_factory().post(
579
+ f"/api/workspaces/{workspace}/members", json={"username": username, "role": role}
580
+ )
581
+ typer.echo(f"{username} is a {role} of @{workspace}")
582
+
583
+ @members_app.command("remove")
584
+ def members_remove(workspace: str, username: str, role: str = ROLE):
585
+ """Revoke a role (admin only).
586
+
587
+ Example: evalkit workspaces members remove dev alice --role reviewer
588
+ """
589
+ client_factory().delete(f"/api/workspaces/{workspace}/members/{username}/{role}")
590
+ typer.echo(f"{username} is no longer a {role} of @{workspace}")
evalkit/cli/client.py ADDED
@@ -0,0 +1,67 @@
1
+ import httpx
2
+ import typer
3
+
4
+ from evalkit import version
5
+ from evalkit.client.api import ApiRequestError, SyncApi
6
+
7
+
8
+ class ApiError(typer.Exit):
9
+ def __init__(self, message: str):
10
+ typer.secho(message, fg="red", err=True)
11
+ super().__init__(code=1)
12
+
13
+
14
+ class ApiClient(SyncApi):
15
+ def __init__(
16
+ self,
17
+ server_url: str,
18
+ token: str | None = None,
19
+ *,
20
+ transport: httpx.BaseTransport | None = None,
21
+ check_version: bool = True,
22
+ ):
23
+ super().__init__(server_url, token, transport=transport)
24
+ self._server_url = server_url.rstrip("/")
25
+ self._version_checked = not check_version
26
+
27
+ def _check_version(self, resp: httpx.Response) -> None:
28
+ if self._version_checked:
29
+ return
30
+ self._version_checked = True
31
+ remote = resp.headers.get("X-Evalkit-Version")
32
+ if remote is None:
33
+ return
34
+ local = version.current()
35
+ match version.compare(local, remote):
36
+ case version.VersionComparison.SAME:
37
+ return
38
+ case version.VersionComparison.INCOMPATIBLE:
39
+ raise ApiError(
40
+ f"evalkit {local} cannot talk to server {remote}: "
41
+ f"run `evalkit update --server {self._server_url}`"
42
+ )
43
+ case version.VersionComparison.DIFFERENT:
44
+ typer.secho(
45
+ f"evalkit {local} differs from server {remote}; "
46
+ f"run `evalkit update --server {self._server_url}`",
47
+ fg="yellow",
48
+ err=True,
49
+ )
50
+
51
+ def request(
52
+ self, method: str, path: str, *, retry: bool | None = None, **kwargs
53
+ ) -> httpx.Response:
54
+ if not self._version_checked and method not in ("GET", "HEAD"):
55
+ self._check_version(self._client.get("/api/health"))
56
+ return super().request(method, path, retry=retry, **kwargs)
57
+
58
+ def _handle(self, resp: httpx.Response) -> httpx.Response:
59
+ self._check_version(resp)
60
+ if resp.status_code >= 400:
61
+ raise ApiError(str(ApiRequestError.from_response(resp)))
62
+ return resp
63
+
64
+ def head(self, path: str, **kwargs) -> httpx.Response:
65
+ resp = super().head(path, **kwargs)
66
+ self._check_version(resp)
67
+ return resp