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/api/auth.py ADDED
@@ -0,0 +1,225 @@
1
+ import secrets
2
+ from dataclasses import dataclass
3
+ from datetime import UTC, datetime, timedelta
4
+ from functools import cache
5
+ from typing import Annotated
6
+
7
+ from fastapi import APIRouter, Depends, HTTPException, Request
8
+ from pydantic import BaseModel
9
+ from sqlalchemy import delete, select
10
+ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
11
+
12
+ from evalkit.api.deps import SessionDep
13
+ from evalkit.contracts.enums import TokenKind
14
+ from evalkit.db.models import AuthToken, User, Workspace, WorkspaceMember
15
+ from evalkit.db.tokens import token_hash
16
+ from evalkit.db.users import PASSWORD_MAX_BYTES, UserStore, hash_password, verify_password
17
+
18
+ router = APIRouter(prefix="/api/auth", tags=["auth"])
19
+
20
+ DOWNLOAD_TICKET_TTL = timedelta(seconds=60)
21
+
22
+
23
+ @cache
24
+ def _dummy_password_hash() -> str:
25
+ return hash_password(secrets.token_urlsafe(16))
26
+
27
+
28
+ async def issue_token(
29
+ session: AsyncSession, user: User, ttl: timedelta, kind: TokenKind = TokenKind.USER
30
+ ) -> str:
31
+ token = secrets.token_urlsafe(32)
32
+ session.add(
33
+ AuthToken(
34
+ user_id=user.id,
35
+ token_hash=token_hash(token),
36
+ expires_at=datetime.now(UTC) + ttl,
37
+ kind=kind,
38
+ )
39
+ )
40
+ await session.commit()
41
+ return token
42
+
43
+
44
+ def _bearer(request: Request) -> str:
45
+ header = request.headers.get("authorization") or ""
46
+ if not header.startswith("Bearer "):
47
+ raise HTTPException(status_code=401, detail="missing bearer token")
48
+ return header.removeprefix("Bearer ")
49
+
50
+
51
+ async def _valid_token(session: AsyncSession, token: str) -> AuthToken:
52
+ row = (
53
+ await session.execute(select(AuthToken).where(AuthToken.token_hash == token_hash(token)))
54
+ ).scalar_one_or_none()
55
+ if row is None:
56
+ raise HTTPException(status_code=401, detail="invalid token")
57
+ expires = token_expiry(row)
58
+ if expires is not None and expires < datetime.now(UTC):
59
+ raise HTTPException(status_code=401, detail="token expired")
60
+ return row
61
+
62
+
63
+ async def _valid_bearer_token(session: AsyncSession, token: str) -> AuthToken:
64
+ row = await _valid_token(session, token)
65
+ if row.kind == TokenKind.TICKET:
66
+ raise HTTPException(status_code=401, detail="a download ticket is not a bearer token")
67
+ row.last_used_at = datetime.now(UTC)
68
+ return row
69
+
70
+
71
+ async def _valid_bearer(request: Request, session: AsyncSession) -> AuthToken:
72
+ return await _valid_bearer_token(session, _bearer(request))
73
+
74
+
75
+ # A person's bearer token, by the rules every non-collector route applies; /mcp
76
+ # calls it too, so the two surfaces accept exactly the same tokens.
77
+ async def bearer_login(session: AsyncSession, token: str) -> tuple[AuthToken, User]:
78
+ row = await _valid_bearer_token(session, token)
79
+ if row.kind == TokenKind.AGENT:
80
+ raise HTTPException(status_code=403, detail="agent tokens may only export traces")
81
+ return row, await _active_user(session, row.user_id)
82
+
83
+
84
+ def token_expiry(row: AuthToken) -> datetime | None:
85
+ if row.expires_at is None:
86
+ return None
87
+ return row.expires_at if row.expires_at.tzinfo else row.expires_at.replace(tzinfo=UTC)
88
+
89
+
90
+ # A download ticket is a single-use token carried in the query string so the
91
+ # browser can save a response straight to disk. URLs leak into logs and
92
+ # history, so a ticket only ever authorises a GET; a bearer header always wins.
93
+ async def current_user(request: Request, session: SessionDep) -> User:
94
+ ticket = request.query_params.get("ticket")
95
+ if ticket is not None and request.method == "GET" and "authorization" not in request.headers:
96
+ row = await _valid_token(session, ticket)
97
+ if row.kind != TokenKind.TICKET:
98
+ raise HTTPException(status_code=401, detail="not a download ticket")
99
+ await session.delete(row)
100
+ user = await _active_user(session, row.user_id)
101
+ else:
102
+ row, user = await bearer_login(session, _bearer(request))
103
+ request.state.token_expires_at = token_expiry(row)
104
+ await session.commit()
105
+ return user
106
+
107
+
108
+ async def _active_user(session: AsyncSession, user_id: int) -> User:
109
+ user = (await session.execute(select(User).where(User.id == user_id))).scalar_one()
110
+ if user.archived_at is not None:
111
+ raise HTTPException(status_code=401, detail="user is archived")
112
+ return user
113
+
114
+
115
+ CurrentUser = Annotated[User, Depends(current_user)]
116
+
117
+
118
+ @dataclass(frozen=True)
119
+ class Principal:
120
+ user: User | None
121
+ agent_id: int | None
122
+
123
+
124
+ async def collector_principal(request: Request, session: SessionDep) -> Principal:
125
+ row = await _valid_bearer(request, session)
126
+ if row.kind == TokenKind.AGENT:
127
+ await session.commit()
128
+ return Principal(user=None, agent_id=row.agent_id)
129
+ user = await _active_user(session, row.user_id)
130
+ await session.commit()
131
+ return Principal(user=user, agent_id=None)
132
+
133
+
134
+ CollectorPrincipal = Annotated[Principal, Depends(collector_principal)]
135
+
136
+
137
+ async def require_admin(user: CurrentUser) -> User:
138
+ if not user.is_admin:
139
+ raise HTTPException(status_code=403, detail="admin only")
140
+ return user
141
+
142
+
143
+ AdminUser = Annotated[User, Depends(require_admin)]
144
+
145
+
146
+ class LoginRequest(BaseModel):
147
+ username: str
148
+ password: str
149
+
150
+
151
+ @router.post("/login")
152
+ async def login(body: LoginRequest, request: Request, session: SessionDep) -> dict:
153
+ user = (
154
+ await session.execute(select(User).where(User.username == body.username))
155
+ ).scalar_one_or_none()
156
+ if len(body.password.encode()) > PASSWORD_MAX_BYTES:
157
+ raise HTTPException(
158
+ status_code=400, detail=f"password is longer than {PASSWORD_MAX_BYTES} bytes"
159
+ )
160
+ if user is None:
161
+ verify_password(body.password, _dummy_password_hash())
162
+ raise HTTPException(status_code=401, detail="invalid username or password")
163
+ if not verify_password(body.password, user.password_hash):
164
+ raise HTTPException(status_code=401, detail="invalid username or password")
165
+ if user.archived_at is not None:
166
+ raise HTTPException(status_code=401, detail="user is archived")
167
+ ttl = timedelta(hours=request.app.state.cfg.auth.token_ttl_hours)
168
+ token = await issue_token(session, user, ttl)
169
+ return {"token": token, "username": user.username, "is_admin": user.is_admin}
170
+
171
+
172
+ @router.post("/download-ticket")
173
+ async def download_ticket(session: SessionDep, user: CurrentUser) -> dict:
174
+ await session.execute(delete(AuthToken).where(AuthToken.expires_at < datetime.now(UTC)))
175
+ return {"ticket": await issue_token(session, user, DOWNLOAD_TICKET_TTL, TokenKind.TICKET)}
176
+
177
+
178
+ @router.post("/logout")
179
+ async def logout(request: Request, session: SessionDep, user: CurrentUser) -> dict:
180
+ token = _bearer(request)
181
+ await session.execute(delete(AuthToken).where(AuthToken.token_hash == token_hash(token)))
182
+ await session.commit()
183
+ return {"ok": True}
184
+
185
+
186
+ @router.get("/me")
187
+ async def me(request: Request, session: SessionDep, user: CurrentUser) -> dict:
188
+ rows = (
189
+ await session.execute(
190
+ select(Workspace.name, WorkspaceMember.role)
191
+ .join(Workspace, Workspace.id == WorkspaceMember.workspace_id)
192
+ .where(WorkspaceMember.user_id == user.id)
193
+ .order_by(Workspace.name, WorkspaceMember.role)
194
+ )
195
+ ).all()
196
+ roles: dict[str, list[str]] = {}
197
+ for name, role in rows:
198
+ roles.setdefault(name, []).append(role)
199
+ personal = await session.scalar(
200
+ select(Workspace.name).where(
201
+ Workspace.owner_user_id == user.id, Workspace.name == user.username
202
+ )
203
+ )
204
+ return {
205
+ "username": user.username,
206
+ "is_admin": user.is_admin,
207
+ "personal_workspace": personal,
208
+ "roles": roles,
209
+ "token_expires_at": getattr(request.state, "token_expires_at", None),
210
+ }
211
+
212
+
213
+ async def bootstrap_admin(
214
+ session_factory: async_sessionmaker[AsyncSession], password: str | None
215
+ ) -> None:
216
+ async with session_factory() as session:
217
+ existing = await session.scalar(select(User.id).where(User.is_admin.is_(True)))
218
+ if existing is not None:
219
+ return
220
+ if not password:
221
+ raise RuntimeError(
222
+ "no admin user exists and EVALKIT_ADMIN_PASSWORD is not set; "
223
+ "set it and start the server again to create the admin user"
224
+ )
225
+ await UserStore(session_factory).create("admin", password, is_admin=True)
@@ -0,0 +1,10 @@
1
+ from fastapi import APIRouter
2
+
3
+ from evalkit import version
4
+
5
+ router = APIRouter(prefix="/api/cli", tags=["cli"])
6
+
7
+
8
+ @router.get("")
9
+ async def cli_info() -> dict:
10
+ return {"version": version.current(), "package": version.DISTRIBUTION}
@@ -0,0 +1,169 @@
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+
4
+ from evalkit.api.access import ActorDep, ensure_active, require, workspace_ref
5
+ from evalkit.api.deps import PgStoreDep, ReviewLogDep, SessionDep
6
+ from evalkit.api.experiments import LIST_LIMIT, with_progress
7
+ from evalkit.contracts.enums import ExperimentStatus
8
+ from evalkit.core.access import can_write
9
+ from evalkit.core.review import CaseVersion, is_reviewable, review_summary
10
+ from evalkit.db import experiments as db_experiments
11
+ from evalkit.db.agents import agent_id_for
12
+ from evalkit.db.cases import create_dataset, current_cases, dataset_by_ref, list_datasets, splits
13
+ from evalkit.db.promotion import twin_status
14
+ from evalkit.db.review import ReviewLog
15
+ from evalkit.db.scorecard import case_history
16
+ from evalkit.store.base import CaseView, DatasetRow, Selection
17
+
18
+ router = APIRouter(prefix="/api/datasets", tags=["datasets"])
19
+
20
+
21
+ class CaseSelection(BaseModel):
22
+ selection: Selection
23
+ agent: str | None = None
24
+
25
+
26
+ def _dataset(row: DatasetRow) -> dict:
27
+ return {**row.model_dump(mode="json"), "ref": row.ref}
28
+
29
+
30
+ @router.get("")
31
+ async def list_all(session: SessionDep, workspace: str | None = None) -> list[dict]:
32
+ return [_dataset(row) for row in await list_datasets(session, workspace)]
33
+
34
+
35
+ class CreateDataset(BaseModel):
36
+ name: str
37
+ workspace: str
38
+
39
+
40
+ @router.post("")
41
+ async def create(body: CreateDataset, session: SessionDep, actor: ActorDep) -> dict:
42
+ ws = await workspace_ref(session, body.workspace)
43
+ require(can_write(actor, ws), f"you cannot create datasets in @{body.workspace}")
44
+ ensure_active(ws)
45
+ row = await create_dataset(session, ws, body.name)
46
+ await session.commit()
47
+ return _dataset(row)
48
+
49
+
50
+ @router.get("/{ref}")
51
+ async def get_dataset(ref: str, session: SessionDep) -> dict:
52
+ return _dataset(await dataset_by_ref(session, ref))
53
+
54
+
55
+ async def _with_review(
56
+ session: SessionDep, review_log: ReviewLog, dataset: DatasetRow, cases: list[CaseView]
57
+ ) -> list[dict]:
58
+ ws = await workspace_ref(session, dataset.workspace)
59
+ twins = await twin_status(session, ws, dataset, cases)
60
+ if not is_reviewable(dataset):
61
+ return [
62
+ case.model_dump(mode="json") | {"review": None, "twin": twins[case.case_id]}
63
+ for case in cases
64
+ ]
65
+ reviews = await review_log.load(dataset.ref, [CaseVersion.of(case) for case in cases])
66
+ return [
67
+ case.model_dump(mode="json")
68
+ | {"review": review_summary(reviews[case.case_id]), "twin": twins[case.case_id]}
69
+ for case in cases
70
+ ]
71
+
72
+
73
+ @router.get("/{ref}/cases")
74
+ async def list_cases(
75
+ ref: str,
76
+ session: SessionDep,
77
+ review_log: ReviewLogDep,
78
+ tag: str | None = None,
79
+ q: str | None = None,
80
+ ) -> list[dict]:
81
+ dataset = await dataset_by_ref(session, ref)
82
+ cases = await current_cases(session, dataset)
83
+ if tag:
84
+ cases = [case for case in cases if tag in case.split_tags]
85
+ if q:
86
+ needle = q.lower()
87
+ cases = [
88
+ case
89
+ for case in cases
90
+ if needle in case.case_id.lower() or needle in str(case.input).lower()
91
+ ]
92
+ return await _with_review(session, review_log, dataset, cases)
93
+
94
+
95
+ @router.get("/{ref}/cases/{case_id}")
96
+ async def get_case(ref: str, case_id: str, session: SessionDep, review_log: ReviewLogDep) -> dict:
97
+ dataset = await dataset_by_ref(session, ref)
98
+ case = next((c for c in await current_cases(session, dataset) if c.case_id == case_id), None)
99
+ if case is None:
100
+ raise HTTPException(status_code=404, detail=f"unknown case: {case_id}")
101
+ [row] = await _with_review(session, review_log, dataset, [case])
102
+ return row
103
+
104
+
105
+ @router.post("/{ref}/cases/select")
106
+ async def select(ref: str, body: CaseSelection, store: PgStoreDep) -> list[dict]:
107
+ cases = await store.cases_for(ref, body.selection, agent=body.agent)
108
+ return [case.model_dump(mode="json") for case in cases]
109
+
110
+
111
+ @router.get("/{ref}/splits")
112
+ async def list_splits(ref: str, session: SessionDep) -> list[dict]:
113
+ dataset = await dataset_by_ref(session, ref)
114
+ return [{"name": tag, "count": count} for tag, count in await splits(session, dataset)]
115
+
116
+
117
+ @router.get("/{ref}/experiments")
118
+ async def list_experiments(
119
+ ref: str,
120
+ session: SessionDep,
121
+ status: ExperimentStatus | None = None,
122
+ agent: str | None = None,
123
+ limit: int = 50,
124
+ offset: int = 0,
125
+ ) -> list[dict]:
126
+ dataset = await dataset_by_ref(session, ref)
127
+ found = await db_experiments.list_experiments(
128
+ session,
129
+ dataset_id=dataset.id,
130
+ status=status,
131
+ agent_id=await agent_id_for(session, agent) if agent else None,
132
+ limit=min(limit, LIST_LIMIT),
133
+ offset=offset,
134
+ )
135
+ return await with_progress(session, found)
136
+
137
+
138
+ @router.get("/{ref}/cases/{case_id}/history")
139
+ async def case_revisions(ref: str, case_id: str, session: SessionDep) -> dict:
140
+ dataset = await dataset_by_ref(session, ref)
141
+ current = next((c for c in await current_cases(session, dataset) if c.case_id == case_id), None)
142
+ if current is None:
143
+ raise HTTPException(status_code=404, detail=f"unknown case: {case_id}")
144
+ return {
145
+ "case_id": case_id,
146
+ "current": current.model_dump(mode="json"),
147
+ "revisions": [{"content_hash": current.content_hash, "kind": "current"}],
148
+ }
149
+
150
+
151
+ @router.get("/{ref}/cases/{case_id}/scores")
152
+ async def case_scores(
153
+ ref: str, case_id: str, session: SessionDep, limit: int = 10, offset: int = 0
154
+ ) -> dict:
155
+ dataset = await dataset_by_ref(session, ref)
156
+ points = await case_history(session, dataset.id, [case_id], limit=limit, offset=offset)
157
+ return {
158
+ "case_id": case_id,
159
+ "points": [p.model_dump(mode="json") for p in points.get(case_id, [])],
160
+ }
161
+
162
+
163
+ @router.get("/{ref}/case-scores")
164
+ async def dataset_case_scores(ref: str, session: SessionDep, experiments: int = 5) -> dict:
165
+ dataset = await dataset_by_ref(session, ref)
166
+ by_case = await case_history(session, dataset.id, None, limit=experiments)
167
+ return {
168
+ case_id: [p.model_dump(mode="json") for p in points] for case_id, points in by_case.items()
169
+ }
evalkit/api/deps.py ADDED
@@ -0,0 +1,79 @@
1
+ from collections.abc import AsyncIterator
2
+ from typing import Annotated
3
+
4
+ from fastapi import Depends, Request
5
+ from sqlalchemy.ext.asyncio import AsyncSession
6
+
7
+ from evalkit.config import Config
8
+ from evalkit.core.runner import RunnerDeps
9
+ from evalkit.db.agents import AgentStore
10
+ from evalkit.db.keys import KeyLoader
11
+ from evalkit.db.mappings import MappingStore
12
+ from evalkit.db.review import ReviewLog
13
+ from evalkit.db.sessions import DbSessionReader
14
+ from evalkit.db.spans import SpanStore
15
+ from evalkit.db.store import PgStore
16
+ from evalkit.db.tokens import AgentTokens
17
+
18
+
19
+ def get_config(request: Request) -> Config:
20
+ return request.app.state.cfg
21
+
22
+
23
+ def get_runner_deps(request: Request) -> RunnerDeps:
24
+ return request.app.state.runner_deps
25
+
26
+
27
+ def get_keys(request: Request) -> KeyLoader:
28
+ return request.app.state.keys
29
+
30
+
31
+ def get_review_log(request: Request) -> ReviewLog:
32
+ return request.app.state.review_log
33
+
34
+
35
+ def get_store(request: Request) -> PgStore:
36
+ return request.app.state.store
37
+
38
+
39
+ def get_holder(request: Request) -> str:
40
+ return request.app.state.holder
41
+
42
+
43
+ def get_agents(request: Request) -> AgentStore:
44
+ return request.app.state.agents
45
+
46
+
47
+ def get_tokens(request: Request) -> AgentTokens:
48
+ return request.app.state.tokens
49
+
50
+
51
+ def get_spans(request: Request) -> SpanStore:
52
+ return request.app.state.spans
53
+
54
+
55
+ def get_mappings(request: Request) -> MappingStore:
56
+ return request.app.state.mappings
57
+
58
+
59
+ def get_sessions(request: Request) -> DbSessionReader:
60
+ return request.app.state.sessions
61
+
62
+
63
+ async def get_session(request: Request) -> AsyncIterator[AsyncSession]:
64
+ async with request.app.state.session_factory() as session:
65
+ yield session
66
+
67
+
68
+ ConfigDep = Annotated[Config, Depends(get_config)]
69
+ RunnerDepsDep = Annotated[RunnerDeps, Depends(get_runner_deps)]
70
+ SessionDep = Annotated[AsyncSession, Depends(get_session)]
71
+ ReviewLogDep = Annotated[ReviewLog, Depends(get_review_log)]
72
+ PgStoreDep = Annotated[PgStore, Depends(get_store)]
73
+ HolderDep = Annotated[str, Depends(get_holder)]
74
+ AgentStoreDep = Annotated[AgentStore, Depends(get_agents)]
75
+ AgentTokensDep = Annotated[AgentTokens, Depends(get_tokens)]
76
+ SpanStoreDep = Annotated[SpanStore, Depends(get_spans)]
77
+ MappingStoreDep = Annotated[MappingStore, Depends(get_mappings)]
78
+ SessionReaderDep = Annotated[DbSessionReader, Depends(get_sessions)]
79
+ KeysDep = Annotated[KeyLoader, Depends(get_keys)]
@@ -0,0 +1,15 @@
1
+ import re
2
+ from urllib.parse import quote
3
+
4
+ # Agent-controlled filenames flow into Content-Disposition; Starlette encodes
5
+ # headers as latin-1, so anything outside this set goes into filename* only.
6
+ _ASCII_SAFE = re.compile(r"[^A-Za-z0-9._-]")
7
+
8
+
9
+ def content_disposition(name: str) -> dict[str, str]:
10
+ ascii_name = _ASCII_SAFE.sub("_", name).strip("._") or "file"
11
+ return {
12
+ "Content-Disposition": (
13
+ f"attachment; filename=\"{ascii_name}\"; filename*=UTF-8''{quote(name, safe='')}"
14
+ )
15
+ }
@@ -0,0 +1,121 @@
1
+ from fastapi import APIRouter, Request
2
+ from pydantic import BaseModel
3
+
4
+ from evalkit.api.access import ActorDep, writable, writable_endpoint
5
+ from evalkit.api.deps import ConfigDep, KeysDep, SessionDep
6
+ from evalkit.contracts.enums import EvaluatorKind
7
+ from evalkit.contracts.settings import RemoteEvaluatorSettings
8
+ from evalkit.core.remote_evaluators import RemoteEvaluator
9
+ from evalkit.db.evaluators import RemoteEvaluatorStore
10
+
11
+ router = APIRouter(prefix="/api/evaluators", tags=["evaluators"])
12
+
13
+
14
+ def _store(request: Request) -> RemoteEvaluatorStore:
15
+ return RemoteEvaluatorStore(request.app.state.session_factory)
16
+
17
+
18
+ def _view(evaluator: RemoteEvaluator) -> dict:
19
+ return {**evaluator.model_dump(mode="json"), "ref": evaluator.ref}
20
+
21
+
22
+ @router.get("")
23
+ async def list_evaluators(
24
+ request: Request, workspace: str | None = None, include_archived: bool = False
25
+ ) -> list[dict]:
26
+ rows = await _store(request).list_evaluators(
27
+ workspace=workspace, include_archived=include_archived
28
+ )
29
+ return [_view(e) for e in rows]
30
+
31
+
32
+ class CreateEvaluator(BaseModel):
33
+ workspace: str
34
+ name: str
35
+ kind: EvaluatorKind
36
+ settings: RemoteEvaluatorSettings
37
+ api_key: str | None = None
38
+
39
+
40
+ @router.post("")
41
+ async def create_evaluator(
42
+ body: CreateEvaluator,
43
+ request: Request,
44
+ keys: KeysDep,
45
+ session: SessionDep,
46
+ actor: ActorDep,
47
+ cfg: ConfigDep,
48
+ ) -> dict:
49
+ await writable_endpoint(
50
+ session,
51
+ actor,
52
+ cfg,
53
+ body.workspace,
54
+ api_key="api_key" in body.model_fields_set,
55
+ url=body.settings.endpoint,
56
+ )
57
+ created = await _store(request).create(
58
+ body.workspace,
59
+ body.name,
60
+ body.kind,
61
+ body.settings,
62
+ keys.seal(body.api_key),
63
+ )
64
+ return _view(created)
65
+
66
+
67
+ class EditEvaluator(BaseModel):
68
+ settings: RemoteEvaluatorSettings | None = None
69
+ api_key: str | None = None
70
+
71
+
72
+ @router.patch("/{ref}")
73
+ async def edit_evaluator(
74
+ ref: str,
75
+ body: EditEvaluator,
76
+ request: Request,
77
+ keys: KeysDep,
78
+ session: SessionDep,
79
+ actor: ActorDep,
80
+ cfg: ConfigDep,
81
+ ) -> dict:
82
+ current = await _store(request).get(ref)
83
+ moved = body.settings is not None and body.settings.endpoint != current.settings.endpoint
84
+ await writable_endpoint(
85
+ session,
86
+ actor,
87
+ cfg,
88
+ current.workspace,
89
+ api_key="api_key" in body.model_fields_set,
90
+ url=body.settings.endpoint if moved and body.settings is not None else None,
91
+ )
92
+ edited = await _store(request).edit(
93
+ ref,
94
+ body.settings,
95
+ api_key_encrypted=keys.seal(body.api_key),
96
+ clear_api_key="api_key" in body.model_fields_set and not body.api_key,
97
+ )
98
+ return _view(edited)
99
+
100
+
101
+ @router.get("/{ref}")
102
+ async def get_evaluator(ref: str, request: Request) -> dict:
103
+ return _view(await _store(request).get(ref))
104
+
105
+
106
+ @router.post("/{ref}/archive")
107
+ async def archive_evaluator(
108
+ ref: str, request: Request, session: SessionDep, actor: ActorDep
109
+ ) -> dict:
110
+ current = await _store(request).get(ref)
111
+ await writable(session, actor, current.workspace)
112
+ return _view(await _store(request).archive(ref))
113
+
114
+
115
+ @router.post("/{ref}/restore")
116
+ async def restore_evaluator(
117
+ ref: str, request: Request, session: SessionDep, actor: ActorDep
118
+ ) -> dict:
119
+ current = await _store(request).get(ref)
120
+ await writable(session, actor, current.workspace)
121
+ return _view(await _store(request).restore(ref))