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,257 @@
1
+ """One importer code path for `sync`: loads and
2
+ validates YAML, lints against the other datasets in the workspace, checks that
3
+ every fixture's bytes were uploaded, then hands the cases and overlays to the
4
+ store, which diffs by content hash.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from typing import Protocol
11
+
12
+ import yaml
13
+ from pydantic import BaseModel, Field, ValidationError
14
+
15
+ from evalkit.contracts.cases import (
16
+ REGISTRY_FILENAME,
17
+ Case,
18
+ FixtureEntry,
19
+ Overlay,
20
+ content_hash,
21
+ is_case_yaml,
22
+ load_cases_from_yaml,
23
+ )
24
+ from evalkit.contracts.enums import DatasetKind, SyncSource
25
+ from evalkit.core.access import WorkspaceRef
26
+ from evalkit.core.fixtures import load_registries
27
+ from evalkit.core.lint import Finding, fixture_findings, has_errors
28
+ from evalkit.core.lint import lint as lint_cases
29
+ from evalkit.core.overlays import OverlayBlock, incoming_overlay, overlay_clashes
30
+ from evalkit.store.base import (
31
+ CarriedReview,
32
+ CaseFile,
33
+ IncomingCase,
34
+ IncomingOverlay,
35
+ SyncResult,
36
+ )
37
+
38
+
39
+ class CaseCatalog(Protocol):
40
+ async def workspace_cases(
41
+ self, workspace: WorkspaceRef, *, exclude_dataset: str | None
42
+ ) -> dict[str, str]: ...
43
+
44
+ async def overlays_on(
45
+ self, workspace: WorkspaceRef, case_ids: list[str], *, exclude_dataset: str
46
+ ) -> dict[str, list[OverlayBlock]]: ...
47
+
48
+ async def known_blobs(self, shas: list[str]) -> set[str]: ...
49
+
50
+ async def has_dataset(self, workspace: WorkspaceRef, name: str) -> bool: ...
51
+
52
+ async def sync(
53
+ self,
54
+ *,
55
+ workspace: WorkspaceRef,
56
+ dataset: str,
57
+ kind: DatasetKind,
58
+ incoming: list[IncomingCase],
59
+ overlays: list[IncomingOverlay],
60
+ full: bool,
61
+ actor_id: int,
62
+ source: SyncSource,
63
+ detail: dict,
64
+ ) -> SyncResult: ...
65
+
66
+
67
+ class ReviewReport(BaseModel):
68
+ now_unreviewed: list[str] = Field(default_factory=list)
69
+ carried: list[CarriedReview] = Field(default_factory=list)
70
+
71
+
72
+ class ImportReport(BaseModel):
73
+ created: int = 0
74
+ patched: int = 0
75
+ deleted: int = 0
76
+ overlays_applied: int = 0
77
+ findings: list[Finding] = Field(default_factory=list)
78
+ aborted: bool = False
79
+ review: ReviewReport = Field(default_factory=ReviewReport)
80
+
81
+
82
+ def load_files(
83
+ files: list[Path],
84
+ ) -> tuple[list[tuple[Case, Path]], list[tuple[Overlay, str]], list[Finding]]:
85
+ cases: list[tuple[Case, Path]] = []
86
+ overlays: list[tuple[Overlay, str]] = []
87
+ findings: list[Finding] = []
88
+ for path in files:
89
+ if not is_case_yaml(path):
90
+ continue
91
+ try:
92
+ file_cases, file_overlays = load_cases_from_yaml(path)
93
+ except (ValidationError, ValueError, yaml.YAMLError) as exc:
94
+ findings.append(
95
+ Finding(
96
+ severity="error",
97
+ code="E-SCHEMA",
98
+ case_id=path.name,
99
+ message=str(exc),
100
+ )
101
+ )
102
+ continue
103
+ cases.extend((case, path) for case in file_cases)
104
+ overlays.extend((ov, path.name) for ov in file_overlays)
105
+ return cases, overlays, findings
106
+
107
+
108
+ def case_files(case: Case, entries: dict[str, FixtureEntry]) -> list[CaseFile]:
109
+ return [
110
+ CaseFile(key=key, sha256=entries[key].sha256, context=entries[key].context)
111
+ for key in case.fixtures
112
+ if key in entries
113
+ ]
114
+
115
+
116
+ async def missing_blobs(
117
+ catalog: CaseCatalog, cases: list[Case], entries: dict[str, FixtureEntry]
118
+ ) -> list[Finding]:
119
+ wanted = [(case.id, file) for case in cases for file in case_files(case, entries)]
120
+ known = await catalog.known_blobs(sorted({file.sha256 for _, file in wanted}))
121
+ return [
122
+ Finding(
123
+ severity="error",
124
+ code="E-FIXTURE-MISSING",
125
+ case_id=case_id,
126
+ message=(
127
+ f"fixture {file.key!r} (sha256 {file.sha256}) was never uploaded; the evalkit "
128
+ "CLI uploads it from the `fixtures:` directory in evalkit.yaml when you sync"
129
+ ),
130
+ )
131
+ for case_id, file in wanted
132
+ if file.sha256 not in known
133
+ ]
134
+
135
+
136
+ def _path_tags(source_path: Path | None, root: Path | None) -> list[str]:
137
+ if source_path is None or root is None:
138
+ return []
139
+ return list(source_path.resolve().relative_to(root.resolve()).parent.parts)
140
+
141
+
142
+ def incoming_case(
143
+ case: Case, source: str, files: list[CaseFile], extra_tags: list[str]
144
+ ) -> IncomingCase:
145
+ return IncomingCase(
146
+ case_id=case.id,
147
+ content_hash=content_hash(case, [f.model_dump() for f in files]),
148
+ input=dict(case.input),
149
+ expected=case.expected,
150
+ evaluators=case.evaluators.as_metadata(),
151
+ tags=list(case.tags),
152
+ path_tags=[tag for tag in dict.fromkeys(extra_tags) if tag not in case.tags],
153
+ source=source,
154
+ files=files,
155
+ )
156
+
157
+
158
+ async def import_cases(
159
+ catalog: CaseCatalog,
160
+ *,
161
+ workspace: WorkspaceRef,
162
+ dataset: str,
163
+ kind: DatasetKind,
164
+ sourced_cases: list[tuple[Case, Path | None]],
165
+ overlays: list[tuple[Overlay, str]],
166
+ findings: list[Finding],
167
+ full: bool,
168
+ known_custom_evaluators: set[str],
169
+ actor_id: int,
170
+ source: SyncSource,
171
+ detail: dict,
172
+ entries: dict[str, FixtureEntry] | None = None,
173
+ root: Path | None = None,
174
+ ) -> ImportReport:
175
+ cases = [case for case, _ in sourced_cases]
176
+ entries = entries or {}
177
+ findings = list(findings)
178
+ findings.extend(
179
+ lint_cases(
180
+ cases,
181
+ [overlay for overlay, _ in overlays],
182
+ known_custom_evaluators=known_custom_evaluators,
183
+ other_cases=await catalog.workspace_cases(workspace, exclude_dataset=dataset),
184
+ )
185
+ )
186
+ existing = await catalog.overlays_on(
187
+ workspace, [overlay.extends for overlay, _ in overlays], exclude_dataset=dataset
188
+ )
189
+ findings.extend(overlay_clashes([overlay for overlay, _ in overlays], existing))
190
+ findings.extend(fixture_findings(cases, entries, warn_unused=False))
191
+ if has_errors(findings):
192
+ return ImportReport(findings=findings, aborted=True)
193
+ findings.extend(await missing_blobs(catalog, cases, entries))
194
+ if has_errors(findings):
195
+ return ImportReport(findings=findings, aborted=True)
196
+ if not sourced_cases and not overlays and not await catalog.has_dataset(workspace, dataset):
197
+ return ImportReport(findings=findings)
198
+ result = await catalog.sync(
199
+ workspace=workspace,
200
+ dataset=dataset,
201
+ kind=kind,
202
+ incoming=[
203
+ incoming_case(
204
+ case,
205
+ source=dataset if source_path is None else source_path.name,
206
+ files=case_files(case, entries),
207
+ extra_tags=_path_tags(source_path, root),
208
+ )
209
+ for case, source_path in sourced_cases
210
+ ],
211
+ overlays=[incoming_overlay(overlay, file) for overlay, file in overlays],
212
+ full=full,
213
+ actor_id=actor_id,
214
+ source=source,
215
+ detail=detail,
216
+ )
217
+ return ImportReport(
218
+ created=len(result.created),
219
+ patched=len(result.patched),
220
+ deleted=len(result.removed),
221
+ overlays_applied=result.overlays,
222
+ findings=findings,
223
+ review=ReviewReport(now_unreviewed=result.now_unreviewed, carried=result.carried),
224
+ )
225
+
226
+
227
+ async def import_dataset(
228
+ catalog: CaseCatalog,
229
+ *,
230
+ workspace: WorkspaceRef,
231
+ dataset: str,
232
+ files: list[Path],
233
+ full: bool,
234
+ known_custom_evaluators: set[str],
235
+ actor_id: int,
236
+ detail: dict,
237
+ root: Path | None = None,
238
+ ) -> ImportReport:
239
+ sourced_cases, overlays, findings = load_files(files)
240
+ entries, registry_findings = load_registries([p for p in files if p.name == REGISTRY_FILENAME])
241
+ sourced: list[tuple[Case, Path | None]] = list(sourced_cases)
242
+ return await import_cases(
243
+ catalog,
244
+ workspace=workspace,
245
+ dataset=dataset,
246
+ kind=DatasetKind.SUITE,
247
+ sourced_cases=sourced,
248
+ overlays=overlays,
249
+ findings=findings + registry_findings,
250
+ full=full,
251
+ known_custom_evaluators=known_custom_evaluators,
252
+ actor_id=actor_id,
253
+ source=SyncSource.GIT,
254
+ detail=detail,
255
+ entries=entries,
256
+ root=root,
257
+ )
evalkit/core/judge.py ADDED
@@ -0,0 +1,78 @@
1
+ import httpx
2
+ from pydantic import BaseModel, ValidationError
3
+
4
+ from evalkit.contracts.settings import JudgeSettings
5
+
6
+
7
+ class JudgeUsage(BaseModel):
8
+ input_tokens: int = 0
9
+ output_tokens: int = 0
10
+
11
+
12
+ def judge_headers(session_id: str | None, evaluator_name: str) -> dict[str, str]:
13
+ headers = {"x-litellm-tags": f"eval-judge,evaluator:{evaluator_name}"}
14
+ if session_id:
15
+ headers["x-litellm-session-id"] = session_id
16
+ return headers
17
+
18
+
19
+ async def call_judge[T: BaseModel](
20
+ cfg: JudgeSettings,
21
+ *,
22
+ api_key: str | None,
23
+ system_prompt: str,
24
+ user_prompt: str,
25
+ output_type: type[T],
26
+ session_id: str | None = None,
27
+ evaluator_name: str,
28
+ model: str | None = None,
29
+ ) -> tuple[T, JudgeUsage]:
30
+ usage = JudgeUsage()
31
+ messages = [
32
+ {"role": "system", "content": system_prompt},
33
+ {"role": "user", "content": user_prompt},
34
+ ]
35
+ async with httpx.AsyncClient(
36
+ base_url=cfg.base_url,
37
+ headers={"Authorization": f"Bearer {api_key}"} if api_key else {},
38
+ timeout=300,
39
+ ) as client:
40
+ for attempt in range(2):
41
+ response = await client.post(
42
+ "/chat/completions",
43
+ json={
44
+ "model": model or cfg.model,
45
+ "messages": messages,
46
+ "response_format": {
47
+ "type": "json_schema",
48
+ "json_schema": {
49
+ "name": output_type.__name__,
50
+ "schema": output_type.model_json_schema(),
51
+ },
52
+ },
53
+ "max_tokens": cfg.max_tokens,
54
+ "reasoning_effort": cfg.reasoning_effort,
55
+ },
56
+ headers=judge_headers(session_id, evaluator_name),
57
+ )
58
+ response.raise_for_status()
59
+ body = response.json()
60
+ call_usage = body.get("usage") or {}
61
+ usage.input_tokens += call_usage.get("prompt_tokens") or 0
62
+ usage.output_tokens += call_usage.get("completion_tokens") or 0
63
+ content = body["choices"][0]["message"]["content"] or ""
64
+ try:
65
+ return output_type.model_validate_json(content), usage
66
+ except ValidationError as exc:
67
+ if attempt == 1:
68
+ raise
69
+ messages = messages + [
70
+ {"role": "assistant", "content": content},
71
+ {
72
+ "role": "user",
73
+ "content": "Your previous response was not valid JSON for the "
74
+ f"required schema: {exc.errors()[0]['msg']}. "
75
+ "Respond again with only the corrected JSON object.",
76
+ },
77
+ ]
78
+ raise AssertionError("unreachable")
evalkit/core/lint.py ADDED
@@ -0,0 +1,265 @@
1
+ """Deterministic quality lint over validated eval cases — no LLM.
2
+
3
+ Severity is calibrated to confidence and blast radius: errors block sync and
4
+ fire only where the case is genuinely broken; warnings are heuristics that
5
+ never block. evalkit knows no client tools, so the jargon check looks for
6
+ tool-ish vocabulary patterns (mcp__ prefixes, double-underscore tokens,
7
+ snake_case call names) rather than a known-tool list.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ from collections.abc import Mapping
14
+ from dataclasses import dataclass
15
+ from typing import Literal
16
+
17
+ from evalkit.contracts.cases import Case, FixtureEntry, Overlay
18
+ from evalkit.contracts.harness import input_errors
19
+
20
+ _DATE_ANCHOR = re.compile(
21
+ r"\d{4}-\d{2}-\d{2}"
22
+ r"|\d{1,2}[-/ ](?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*[-/ ]?\d{2,4}"
23
+ r"|\b(?:january|february|march|april|may|june|july|august|september|october"
24
+ r"|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+\d{4}"
25
+ r"|\b20\d{2}\b"
26
+ r"|\bFY\s?\d{2,4}\b"
27
+ r"|\bQ[1-4]\b"
28
+ r"|\bas of\b"
29
+ r"|\bas on\b"
30
+ r"|\bbetween\b.{1,40}\band\b",
31
+ re.IGNORECASE,
32
+ )
33
+
34
+ _NUMERIC_VALUE = re.compile(
35
+ r"\d+\.\d+|\d+\s*%|[₹$€£]\s*\d|\b(?:million|billion|crore|crores|lakh|lakhs|bps)\b",
36
+ re.IGNORECASE,
37
+ )
38
+
39
+ _DOUBLE_UNDERSCORE_TOKEN = re.compile(r"\b\w+__\w+\b")
40
+ _SNAKE_CALL = re.compile(r"\b[a-z][a-z0-9]+(?:_[a-z0-9]+)+\s*\(")
41
+
42
+
43
+ @dataclass
44
+ class Finding:
45
+ severity: Literal["error", "warning"]
46
+ code: str
47
+ case_id: str
48
+ message: str
49
+
50
+
51
+ def _blackbox_texts(case: Case) -> list[tuple[str, str]]:
52
+ texts: list[tuple[str, str]] = [("input.question", str(case.input.get("question", "")))]
53
+ rubrics = case.evaluators.output.answer_rubrics
54
+ if rubrics is not None:
55
+ for r in rubrics.rubrics:
56
+ texts.append(("answer_rubrics.rubric", r.rubric))
57
+ tr = case.evaluators.trajectory.trajectory_rubrics
58
+ if tr is not None:
59
+ for rubric in tr.rubrics:
60
+ texts.append(("trajectory_rubrics.rubric", rubric))
61
+ pj = case.evaluators.output.presentation_judge
62
+ if pj is not None:
63
+ for pr in pj.rubrics:
64
+ texts.append(("presentation_judge.rubric", pr.rubric or pr.forbid or ""))
65
+ return texts
66
+
67
+
68
+ def _jargon_hits(text: str) -> set[str]:
69
+ hits: set[str] = set()
70
+ if "mcp__" in text.lower():
71
+ hits.add("mcp__ prefix")
72
+ hits.update(_DOUBLE_UNDERSCORE_TOKEN.findall(text))
73
+ hits.update(m.rstrip("( \t") for m in _SNAKE_CALL.findall(text))
74
+ return hits
75
+
76
+
77
+ def _e_jargon(case: Case) -> list[Finding]:
78
+ findings = []
79
+ for surface, text in _blackbox_texts(case):
80
+ hits = _jargon_hits(text)
81
+ if hits:
82
+ findings.append(
83
+ Finding(
84
+ severity="error",
85
+ code="E-JARGON",
86
+ case_id=case.id,
87
+ message=(
88
+ f"{surface} leaks agent internals ({', '.join(sorted(hits))}) "
89
+ "— black-box surfaces must stay free of tool/skill names; "
90
+ "move tool checks into tool_trajectory_match / required_tools_called "
91
+ "or a custom evaluator"
92
+ ),
93
+ )
94
+ )
95
+ return findings
96
+
97
+
98
+ def _e_reserved_tags(case_id: str, tags: list[str]) -> list[Finding]:
99
+ return [
100
+ Finding(
101
+ severity="error",
102
+ code="E-RESERVED-TAG",
103
+ case_id=case_id,
104
+ message=(
105
+ f"tag '{t}' is reserved for run-time case selection "
106
+ "(the working split) — pick another tag name"
107
+ ),
108
+ )
109
+ for t in tags
110
+ if t.startswith("working:")
111
+ ]
112
+
113
+
114
+ def _w_custom_unknown(
115
+ case_id: str, custom: dict, known_custom_evaluators: set[str]
116
+ ) -> list[Finding]:
117
+ return [
118
+ Finding(
119
+ severity="warning",
120
+ code="W-CUSTOM-UNKNOWN",
121
+ case_id=case_id,
122
+ message=(
123
+ f"custom evaluator '{name}' is not a remote evaluator here: create it with "
124
+ "evalkit evaluators create, or pass a Python evaluator with this name to "
125
+ "run_experiment"
126
+ ),
127
+ )
128
+ for name in sorted(set(custom) - known_custom_evaluators)
129
+ ]
130
+
131
+
132
+ def _w_undated(case: Case) -> list[Finding]:
133
+ rubrics = case.evaluators.output.answer_rubrics
134
+ if rubrics is None:
135
+ return []
136
+ rubric_text = " ".join(r.rubric for r in rubrics.rubrics)
137
+ if not _NUMERIC_VALUE.search(rubric_text):
138
+ return []
139
+ if _DATE_ANCHOR.search(str(case.input.get("question", "")) + " " + rubric_text):
140
+ return []
141
+ return [
142
+ Finding(
143
+ severity="warning",
144
+ code="W-UNDATED",
145
+ case_id=case.id,
146
+ message=(
147
+ "rubrics pin numeric values but neither the input nor the rubrics "
148
+ "carry a date anchor — pinned ground truth will drift"
149
+ ),
150
+ )
151
+ ]
152
+
153
+
154
+ def _w_thin_rubrics(case: Case) -> list[Finding]:
155
+ rubrics = case.evaluators.output.answer_rubrics
156
+ if rubrics is None or len(rubrics.rubrics) >= 2:
157
+ return []
158
+ return [
159
+ Finding(
160
+ severity="warning",
161
+ code="W-THIN-RUBRICS",
162
+ case_id=case.id,
163
+ message="answer_rubrics carries a single rubric — thin coverage for a whole case",
164
+ )
165
+ ]
166
+
167
+
168
+ def fixture_findings(
169
+ cases: list[Case], entries: dict[str, FixtureEntry], *, warn_unused: bool = True
170
+ ) -> list[Finding]:
171
+ findings: list[Finding] = []
172
+ used: set[str] = set()
173
+ for case in cases:
174
+ for key in case.fixtures:
175
+ used.add(key)
176
+ if key not in entries:
177
+ findings.append(
178
+ Finding(
179
+ severity="error",
180
+ code="E-FIXTURE-UNKNOWN-KEY",
181
+ case_id=case.id,
182
+ message=f"fixture key {key!r} is not declared in fixtures.yaml",
183
+ )
184
+ )
185
+ for key in entries:
186
+ if warn_unused and key not in used:
187
+ findings.append(
188
+ Finding(
189
+ severity="warning",
190
+ code="W-FIXTURE-UNUSED",
191
+ case_id=key,
192
+ message=f"fixture {key!r} is declared but no case references it",
193
+ )
194
+ )
195
+ return findings
196
+
197
+
198
+ def lint(
199
+ cases: list[Case],
200
+ overlays: list[Overlay],
201
+ *,
202
+ known_custom_evaluators: set[str],
203
+ other_cases: Mapping[str, str] | None = None,
204
+ input_schema: dict | None = None,
205
+ ) -> list[Finding]:
206
+ findings: list[Finding] = []
207
+
208
+ seen: set[str] = set()
209
+ for case in cases:
210
+ if case.id in seen:
211
+ findings.append(
212
+ Finding(
213
+ severity="error",
214
+ code="E-DUP-ID",
215
+ case_id=case.id,
216
+ message="duplicate case id in this import",
217
+ )
218
+ )
219
+ seen.add(case.id)
220
+ seen_targets: set[str] = set()
221
+ for overlay in overlays:
222
+ if overlay.extends in seen_targets:
223
+ findings.append(
224
+ Finding(
225
+ severity="error",
226
+ code="E-DUP-ID",
227
+ case_id=overlay.extends,
228
+ message="multiple overlays extend the same case in this import",
229
+ )
230
+ )
231
+ seen_targets.add(overlay.extends)
232
+
233
+ for case in cases:
234
+ findings.extend(_e_jargon(case))
235
+ findings.extend(_e_reserved_tags(case.id, case.tags))
236
+ findings.extend(_w_custom_unknown(case.id, case.evaluators.custom, known_custom_evaluators))
237
+ findings.extend(_w_undated(case))
238
+ findings.extend(_w_thin_rubrics(case))
239
+
240
+ if input_schema is not None:
241
+ for case in cases:
242
+ for error in input_errors(input_schema, case.input):
243
+ findings.append(
244
+ Finding(severity="error", code="E-INPUT-SCHEMA", case_id=case.id, message=error)
245
+ )
246
+
247
+ for overlay in overlays:
248
+ findings.extend(_e_reserved_tags(overlay.extends, overlay.tags))
249
+ findings.extend(
250
+ _w_custom_unknown(overlay.extends, overlay.evaluators.custom, known_custom_evaluators)
251
+ )
252
+ if other_cases is not None and overlay.extends not in other_cases:
253
+ findings.append(
254
+ Finding(
255
+ severity="error",
256
+ code="E-OVERLAY-TARGET",
257
+ case_id=overlay.extends,
258
+ message="overlay extends a case id no other dataset in this workspace holds",
259
+ )
260
+ )
261
+ return findings
262
+
263
+
264
+ def has_errors(findings: list[Finding]) -> bool:
265
+ return any(f.severity == "error" for f in findings)
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ from evalkit.contracts.cases import Overlay
8
+ from evalkit.core.lint import Finding
9
+ from evalkit.store.base import IncomingOverlay
10
+
11
+
12
+ class OverlayBlock(BaseModel):
13
+ source: str
14
+ agents: list[str]
15
+ tags: list[str] = Field(default_factory=list)
16
+ evaluators: dict = Field(default_factory=dict)
17
+ source_file: str | None = None
18
+
19
+
20
+ def overlay_evaluators(overlay: Overlay) -> dict:
21
+ families = {
22
+ "custom": overlay.evaluators.custom,
23
+ "trajectory": overlay.evaluators.trajectory.as_metadata(),
24
+ }
25
+ return {family: block for family, block in families.items() if block}
26
+
27
+
28
+ def incoming_overlay(overlay: Overlay, source_file: str | None) -> IncomingOverlay:
29
+ return IncomingOverlay(
30
+ target_case_id=overlay.extends,
31
+ agents=list(overlay.agents),
32
+ tags=list(overlay.tags),
33
+ evaluators=overlay_evaluators(overlay),
34
+ source_file=source_file,
35
+ )
36
+
37
+
38
+ def apply_overlay_blocks(evaluators: dict, blocks: list[OverlayBlock], agent: str | None) -> dict:
39
+ out = {family: dict(block) for family, block in evaluators.items()}
40
+ if not agent:
41
+ return out
42
+ for block in sorted(blocks, key=lambda b: b.source):
43
+ if agent.partition("@")[0] not in block.agents:
44
+ continue
45
+ for family, named in block.evaluators.items():
46
+ out[family] = {**out.get(family, {}), **named}
47
+ return out
48
+
49
+
50
+ def _names(evaluators: dict) -> set[tuple[str, str]]:
51
+ return {(family, name) for family, block in evaluators.items() for name in block}
52
+
53
+
54
+ def overlay_clashes(
55
+ overlays: list[Overlay], existing: Mapping[str, list[OverlayBlock]]
56
+ ) -> list[Finding]:
57
+ findings: list[Finding] = []
58
+ for overlay in overlays:
59
+ ours = _names(overlay_evaluators(overlay))
60
+ for block in sorted(existing.get(overlay.extends, []), key=lambda b: b.source):
61
+ agents = sorted(set(overlay.agents) & set(block.agents))
62
+ names = sorted(ours & _names(block.evaluators))
63
+ if agents and names:
64
+ findings.append(
65
+ Finding(
66
+ severity="error",
67
+ code="E-OVERLAY-CLASH",
68
+ case_id=overlay.extends,
69
+ message=(
70
+ f"{block.source} already sets "
71
+ f"{', '.join(f'{family}.{name}' for family, name in names)} "
72
+ f"for agents {', '.join(agents)}"
73
+ ),
74
+ )
75
+ )
76
+ return findings
evalkit/core/paths.py ADDED
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class UnsafePathError(ValueError):
5
+ pass
6
+
7
+
8
+ def is_safe_path(path: str) -> bool:
9
+ return all(segment not in ("", ".", "..") for segment in path.lstrip("/").split("/"))
10
+
11
+
12
+ def require_safe_path(path: str) -> str:
13
+ if not is_safe_path(path):
14
+ raise UnsafePathError(f"unsafe path {path!r}")
15
+ return path.lstrip("/")
16
+
17
+
18
+ def is_safe_run_id(run_id: str) -> bool:
19
+ return "/" not in run_id and is_safe_path(run_id)
20
+
21
+
22
+ def require_safe_run_id(run_id: str) -> str:
23
+ if not is_safe_run_id(run_id):
24
+ raise UnsafePathError(f"unsafe run id {run_id!r}")
25
+ return run_id