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/meta.py ADDED
@@ -0,0 +1,19 @@
1
+ from fastapi import APIRouter, Request
2
+
3
+ from evalkit.api.deps import AgentStoreDep, ConfigDep
4
+
5
+ router = APIRouter(prefix="/api/meta", tags=["meta"])
6
+
7
+
8
+ @router.get("")
9
+ async def meta(request: Request, cfg: ConfigDep, agents: AgentStoreDep) -> dict:
10
+ every = await agents.list_agents()
11
+ return {
12
+ "accent_color": cfg.server.accent_color,
13
+ "agents": [a.ref for a in every if a.workspace_is_shared and a.harness_url],
14
+ "agent_repos": {a.ref: a.repo_url for a in every if a.repo_url},
15
+ "settle_seconds": cfg.otel.settle_seconds,
16
+ "trace_sources": [
17
+ source.name for source in await request.app.state.trace_sources.list_sources()
18
+ ],
19
+ }
evalkit/api/otel.py ADDED
@@ -0,0 +1,350 @@
1
+ from __future__ import annotations
2
+
3
+ import gzip
4
+ import io
5
+ import json
6
+ import logging
7
+ from datetime import UTC, datetime
8
+ from typing import Any
9
+
10
+ from fastapi import APIRouter, HTTPException, Request
11
+ from fastapi.exception_handlers import http_exception_handler
12
+ from google.protobuf.message import DecodeError
13
+ from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import (
14
+ ExportTraceServiceRequest,
15
+ ExportTraceServiceResponse,
16
+ )
17
+ from opentelemetry.proto.common.v1.common_pb2 import AnyValue
18
+ from opentelemetry.proto.trace.v1.trace_pb2 import Span as ProtoSpan
19
+ from sqlalchemy.ext.asyncio import AsyncSession
20
+ from starlette.exceptions import HTTPException as StarletteHTTPException
21
+ from starlette.responses import Response
22
+
23
+ from evalkit.api.access import load_actor, workspace_ref
24
+ from evalkit.api.auth import CollectorPrincipal, Principal
25
+ from evalkit.api.deps import AgentStoreDep, ConfigDep, MappingStoreDep, SessionDep, SpanStoreDep
26
+ from evalkit.contracts.esf import MappingDocument
27
+ from evalkit.core.access import Actor, WorkspaceRef, can_write
28
+ from evalkit.core.agents import AgentDefinition, UnknownAgent, explicit
29
+ from evalkit.core.traces.mapping import session_id_of
30
+ from evalkit.core.traces.raw import RawSpan
31
+ from evalkit.core.workspace import Reference, parse_reference, slug
32
+ from evalkit.db.agents import AgentStore
33
+ from evalkit.db.models import User
34
+
35
+ router = APIRouter(prefix="/api/otel/v1", tags=["otel"])
36
+ LOGGER = logging.getLogger(__name__)
37
+
38
+ AGENT_HEADER = "X-Evalkit-Agent"
39
+ AGENT_ATTRIBUTE = "evalkit.agent"
40
+ PROTOBUF = "application/x-protobuf"
41
+ SPAN_KINDS = {
42
+ 0: "UNSPECIFIED",
43
+ 1: "INTERNAL",
44
+ 2: "SERVER",
45
+ 3: "CLIENT",
46
+ 4: "PRODUCER",
47
+ 5: "CONSUMER",
48
+ }
49
+ STATUS_CODES = {0: "UNSET", 1: "OK", 2: "ERROR"}
50
+
51
+
52
+ def _any_value(value: AnyValue) -> Any:
53
+ kind = value.WhichOneof("value")
54
+ if kind is None:
55
+ return None
56
+ if kind == "array_value":
57
+ return [_any_value(v) for v in value.array_value.values]
58
+ if kind == "kvlist_value":
59
+ return {kv.key: _any_value(kv.value) for kv in value.kvlist_value.values}
60
+ if kind == "bytes_value":
61
+ return value.bytes_value.hex()
62
+ return getattr(value, kind)
63
+
64
+
65
+ def _attributes(items: Any) -> dict[str, Any]:
66
+ return {kv.key: _any_value(kv.value) for kv in items}
67
+
68
+
69
+ def _time(nanos: int) -> datetime | None:
70
+ return datetime.fromtimestamp(nanos / 1e9, tz=UTC) if nanos else None
71
+
72
+
73
+ def _iso(nanos: int) -> str | None:
74
+ at = _time(nanos)
75
+ return at.isoformat() if at else None
76
+
77
+
78
+ def _from_proto(span: ProtoSpan, resource: dict[str, Any]) -> RawSpan:
79
+ start = _time(span.start_time_unix_nano)
80
+ if start is None:
81
+ raise HTTPException(status_code=400, detail=f"span {span.name!r} has no start time")
82
+ return RawSpan(
83
+ trace_id=span.trace_id.hex(),
84
+ span_id=span.span_id.hex(),
85
+ parent_span_id=span.parent_span_id.hex() or None,
86
+ name=span.name,
87
+ kind=SPAN_KINDS.get(span.kind, "INTERNAL"),
88
+ start_time=start,
89
+ end_time=_time(span.end_time_unix_nano),
90
+ status_code=STATUS_CODES.get(span.status.code, "UNSET"),
91
+ status_message=span.status.message or None,
92
+ attributes=_attributes(span.attributes),
93
+ events=[
94
+ {
95
+ "name": e.name,
96
+ "time": _iso(e.time_unix_nano),
97
+ "attributes": _attributes(e.attributes),
98
+ }
99
+ for e in span.events
100
+ ],
101
+ resource=resource,
102
+ )
103
+
104
+
105
+ def _json_any(value: dict[str, Any]) -> Any:
106
+ if "stringValue" in value:
107
+ return value["stringValue"]
108
+ if "intValue" in value:
109
+ return int(value["intValue"])
110
+ if "doubleValue" in value:
111
+ return float(value["doubleValue"])
112
+ if "boolValue" in value:
113
+ return bool(value["boolValue"])
114
+ if "arrayValue" in value:
115
+ return [_json_any(v) for v in value["arrayValue"].get("values", [])]
116
+ if "kvlistValue" in value:
117
+ return {kv["key"]: _json_any(kv["value"]) for kv in value["kvlistValue"].get("values", [])}
118
+ if "bytesValue" in value:
119
+ return value["bytesValue"]
120
+ return None
121
+
122
+
123
+ def _json_attributes(items: list[dict[str, Any]] | None) -> dict[str, Any]:
124
+ return {kv["key"]: _json_any(kv.get("value", {})) for kv in items or []}
125
+
126
+
127
+ def _from_json(span: dict[str, Any], resource: dict[str, Any]) -> RawSpan:
128
+ start = _time(int(span.get("startTimeUnixNano") or 0))
129
+ if start is None:
130
+ raise HTTPException(status_code=400, detail="span without startTimeUnixNano")
131
+ status = span.get("status") or {}
132
+ return RawSpan(
133
+ trace_id=span["traceId"],
134
+ span_id=span["spanId"],
135
+ parent_span_id=span.get("parentSpanId") or None,
136
+ name=span.get("name", ""),
137
+ kind=SPAN_KINDS.get(int(span.get("kind") or 0), "INTERNAL"),
138
+ start_time=start,
139
+ end_time=_time(int(span.get("endTimeUnixNano") or 0)),
140
+ status_code=STATUS_CODES.get(int(status.get("code") or 0), "UNSET"),
141
+ status_message=status.get("message") or None,
142
+ attributes=_json_attributes(span.get("attributes")),
143
+ events=[
144
+ {
145
+ "name": e.get("name"),
146
+ "time": _iso(int(e.get("timeUnixNano") or 0)),
147
+ "attributes": _json_attributes(e.get("attributes")),
148
+ }
149
+ for e in span.get("events") or []
150
+ ],
151
+ resource=resource,
152
+ )
153
+
154
+
155
+ def _decode(body: bytes, content_type: str) -> list[RawSpan]:
156
+ spans: list[RawSpan] = []
157
+ if content_type.startswith(PROTOBUF):
158
+ request = ExportTraceServiceRequest.FromString(body)
159
+ for resource_spans in request.resource_spans:
160
+ resource = _attributes(resource_spans.resource.attributes)
161
+ for scope_spans in resource_spans.scope_spans:
162
+ spans.extend(_from_proto(span, resource) for span in scope_spans.spans)
163
+ return spans
164
+ if content_type.startswith("application/json"):
165
+ payload = json.loads(body)
166
+ for resource_spans in payload.get("resourceSpans", []):
167
+ resource = _json_attributes((resource_spans.get("resource") or {}).get("attributes"))
168
+ for scope_spans in resource_spans.get("scopeSpans", []):
169
+ spans.extend(_from_json(span, resource) for span in scope_spans.get("spans", []))
170
+ return spans
171
+ raise HTTPException(
172
+ status_code=415,
173
+ detail=f"unsupported content type {content_type!r}; send {PROTOBUF} or application/json",
174
+ )
175
+
176
+
177
+ def decode_otlp(body: bytes, content_type: str) -> list[RawSpan]:
178
+ try:
179
+ return _decode(body, content_type)
180
+ except (DecodeError, ValueError, KeyError, TypeError) as exc:
181
+ raise HTTPException(status_code=400, detail=f"malformed OTLP body: {exc!r}") from exc
182
+
183
+
184
+ class Rejected(HTTPException):
185
+ def __init__(self, status_code: int, detail: str, reason: str, agent: str | None) -> None:
186
+ super().__init__(status_code=status_code, detail=detail)
187
+ self.reason = reason
188
+ self.agent = agent
189
+
190
+
191
+ def _reason(exc: StarletteHTTPException) -> str:
192
+ if isinstance(exc, Rejected):
193
+ return exc.reason
194
+ if exc.status_code == 401:
195
+ return "expired token" if exc.detail == "token expired" else "invalid token"
196
+ if exc.status_code == 403:
197
+ return "forbidden"
198
+ if exc.status_code == 413:
199
+ return "too large"
200
+ return "malformed"
201
+
202
+
203
+ async def log_rejection(request: Request, exc: Exception) -> Response:
204
+ assert isinstance(exc, StarletteHTTPException)
205
+ if request.url.path.startswith(router.prefix):
206
+ agent = exc.agent if isinstance(exc, Rejected) else request.headers.get(AGENT_HEADER)
207
+ reason = _reason(exc)
208
+ LOGGER.warning(
209
+ "otel export rejected: status=%s reason=%s agent=%s detail=%s",
210
+ exc.status_code,
211
+ reason,
212
+ agent,
213
+ exc.detail,
214
+ extra={
215
+ "otel_rejection": {
216
+ "status": exc.status_code,
217
+ "reason": reason,
218
+ "agent": agent,
219
+ "detail": exc.detail,
220
+ }
221
+ },
222
+ )
223
+ return await http_exception_handler(request, exc)
224
+
225
+
226
+ def _named_agent(span: RawSpan, header: str | None) -> str | None:
227
+ for source in (span.attributes, span.resource):
228
+ value = source.get(AGENT_ATTRIBUTE)
229
+ if value:
230
+ return str(value)
231
+ return header
232
+
233
+
234
+ def _token_agent(named: str | None, bound: AgentDefinition) -> None:
235
+ ref = Reference(bound.name, bound.workspace)
236
+ if named is None:
237
+ return
238
+ try:
239
+ parsed = parse_reference(named, bound.workspace)
240
+ except ValueError as exc:
241
+ raise Rejected(400, str(exc), "malformed", named) from exc
242
+ if parsed != ref:
243
+ raise Rejected(
244
+ 403, f"token is bound to {slug(ref)}, batch names {named}", "forbidden", named
245
+ )
246
+
247
+
248
+ def _user_agent(named: str | None, user: User) -> Reference:
249
+ if named is None:
250
+ raise Rejected(
251
+ 400,
252
+ f"name the agent with the {AGENT_HEADER} header or the {AGENT_ATTRIBUTE} span or "
253
+ "resource attribute",
254
+ "malformed",
255
+ None,
256
+ )
257
+ try:
258
+ return parse_reference(named, user.username)
259
+ except ValueError as exc:
260
+ raise Rejected(400, str(exc), "malformed", named) from exc
261
+
262
+
263
+ async def _user_record(
264
+ ref: Reference, actor: Actor, session: AsyncSession, agents: AgentStore
265
+ ) -> AgentDefinition:
266
+ agent = await agents.find(explicit(ref))
267
+ if agent is None:
268
+ unknown = UnknownAgent(explicit(ref)).args[0]
269
+ raise Rejected(404, unknown, "unknown agent", explicit(ref))
270
+ ws = await workspace_ref(session, agent.workspace)
271
+ if not can_write(actor, ws):
272
+ raise Rejected(403, f"you cannot export traces for {agent.ref}", "forbidden", agent.ref)
273
+ return _live(agent, ws)
274
+
275
+
276
+ def _live(agent: AgentDefinition, ws: WorkspaceRef) -> AgentDefinition:
277
+ if ws.archived:
278
+ raise Rejected(409, f"workspace {ws.name} is archived", "archived", agent.ref)
279
+ if agent.archived:
280
+ raise Rejected(409, f"agent {agent.ref} is archived", "archived", agent.ref)
281
+ return agent
282
+
283
+
284
+ async def _resolve(
285
+ principal: Principal,
286
+ groups: dict[str | None, list[RawSpan]],
287
+ session: AsyncSession,
288
+ agents: AgentStore,
289
+ ) -> list[tuple[AgentDefinition, list[RawSpan]]]:
290
+ if principal.agent_id is not None:
291
+ bound = await agents.get_by_id(principal.agent_id)
292
+ for named in groups:
293
+ _token_agent(named, bound)
294
+ _live(bound, await workspace_ref(session, bound.workspace))
295
+ return [(bound, [span for spans in groups.values() for span in spans])]
296
+ assert principal.user is not None
297
+ user = principal.user
298
+ actor = await load_actor(session, user)
299
+ refs = {named: _user_agent(named, user) for named in groups}
300
+ by_ref: dict[Reference, list[RawSpan]] = {}
301
+ for named, spans in groups.items():
302
+ by_ref.setdefault(refs[named], []).extend(spans)
303
+ return [
304
+ (await _user_record(ref, actor, session, agents), spans) for ref, spans in by_ref.items()
305
+ ]
306
+
307
+
308
+ @router.post("/traces")
309
+ async def export_traces(
310
+ request: Request,
311
+ principal: CollectorPrincipal,
312
+ cfg: ConfigDep,
313
+ agents: AgentStoreDep,
314
+ session: SessionDep,
315
+ spans_store: SpanStoreDep,
316
+ mappings: MappingStoreDep,
317
+ ) -> Response:
318
+ header = request.headers.get(AGENT_HEADER)
319
+ body = await request.body()
320
+ if request.headers.get("content-encoding") == "gzip":
321
+ with gzip.GzipFile(fileobj=io.BytesIO(body)) as unzipped:
322
+ body = unzipped.read(cfg.otel.max_batch_bytes + 1)
323
+ if len(body) > cfg.otel.max_batch_bytes:
324
+ raise Rejected(
325
+ 413,
326
+ f"decompressed batch exceeds otel.max_batch_bytes ({cfg.otel.max_batch_bytes})",
327
+ "too large",
328
+ header,
329
+ )
330
+ content_type = request.headers.get("content-type", "")
331
+ spans = decode_otlp(body, content_type)
332
+ groups: dict[str | None, list[RawSpan]] = {}
333
+ for span in spans:
334
+ groups.setdefault(_named_agent(span, header), []).append(span)
335
+ for record, agent_spans in await _resolve(principal, groups, session, agents):
336
+ active = await mappings.active(record.id)
337
+ document = MappingDocument.model_validate(active.document) if active else None
338
+ if document is None:
339
+ LOGGER.warning(
340
+ "agent %s has no active span mapping; its spans are stored without a session id",
341
+ record.ref,
342
+ )
343
+ for span in agent_spans:
344
+ span.session_id_hint = session_id_of(span.attributes, document)
345
+ await spans_store.upsert(record.id, agent_spans)
346
+ if content_type.startswith(PROTOBUF):
347
+ return Response(
348
+ content=ExportTraceServiceResponse().SerializeToString(), media_type=PROTOBUF
349
+ )
350
+ return Response(content=b"{}", media_type="application/json")
evalkit/api/pull.py ADDED
@@ -0,0 +1,197 @@
1
+ import io
2
+ import json
3
+ import zipfile
4
+ from dataclasses import dataclass
5
+ from datetime import UTC, datetime
6
+
7
+ import httpx
8
+ from fastapi import APIRouter, HTTPException
9
+ from pydantic import BaseModel, Field, model_validator
10
+ from sqlalchemy.ext.asyncio import AsyncSession
11
+ from starlette.responses import Response
12
+
13
+ from evalkit.api.deps import (
14
+ AgentStoreDep,
15
+ RunnerDepsDep,
16
+ SessionDep,
17
+ SessionReaderDep,
18
+ SpanStoreDep,
19
+ )
20
+ from evalkit.api.download import content_disposition
21
+ from evalkit.api.runs import BrowserRun, agent_or_404, harness_client, resolve_runs
22
+ from evalkit.contracts.atif import TrajectoryValidationError, validate_trajectory
23
+ from evalkit.core.agents import AgentDefinition, server_may_drive
24
+ from evalkit.core.costmap import load_cost_map
25
+ from evalkit.core.harness import HarnessClient
26
+ from evalkit.core.paths import UnsafePathError
27
+ from evalkit.core.pull import (
28
+ MAX_PULL,
29
+ RunBundle,
30
+ bundle_files,
31
+ index_row,
32
+ run_body,
33
+ )
34
+ from evalkit.core.runner import RunnerDeps, stored_trajectory_doc
35
+ from evalkit.core.trajectory import backfill_step_costs
36
+ from evalkit.db.runs import (
37
+ RunSource,
38
+ agent_run,
39
+ eval_context,
40
+ experiment_run_ids,
41
+ )
42
+ from evalkit.db.sessions import DbSessionReader
43
+ from evalkit.store.base import AgentRunRecord
44
+
45
+ router = APIRouter(prefix="/api/agents", tags=["agents"])
46
+
47
+ UNREACHABLE = "only the CLI that runs this agent can reach its harness"
48
+
49
+
50
+ class PullRequest(BaseModel):
51
+ source: RunSource = "all"
52
+ since: datetime | None = None
53
+ until: datetime | None = None
54
+ limit: int = Field(default=MAX_PULL, ge=1, le=MAX_PULL)
55
+ offset: int = Field(default=0, ge=0)
56
+ run_ids: list[str] | None = Field(default=None, min_length=1, max_length=MAX_PULL)
57
+ experiment_id: int | None = None
58
+
59
+ @model_validator(mode="after")
60
+ def _one_selection(self) -> "PullRequest":
61
+ if self.run_ids is not None and self.experiment_id is not None:
62
+ raise ValueError("pass run_ids or experiment_id, not both")
63
+ return self
64
+
65
+
66
+ @dataclass
67
+ class PullContext:
68
+ agent: AgentDefinition
69
+ deps: RunnerDeps
70
+ sessions: DbSessionReader
71
+ harness: HarnessClient | None
72
+ cost_map: dict
73
+
74
+
75
+ async def _trajectory(
76
+ ctx: PullContext, run: BrowserRun, stored: AgentRunRecord | None
77
+ ) -> dict | None:
78
+ if stored is not None:
79
+ doc, _ = await stored_trajectory_doc(ctx.deps.blob_store, ctx.agent.id, stored)
80
+ if doc is not None:
81
+ return doc
82
+ if ctx.agent.is_otel:
83
+ result = await ctx.sessions.read(ctx.agent.ref, run.run_id)
84
+ if result is None or result.trajectory is None:
85
+ raise LookupError("no spans for this session")
86
+ return result.trajectory
87
+ if ctx.harness is None:
88
+ raise LookupError(UNREACHABLE)
89
+ return (await ctx.harness.trajectory(run.run_id)).model_dump(exclude_none=True)
90
+
91
+
92
+ async def _bundle(ctx: PullContext, session: AsyncSession, run: BrowserRun) -> RunBundle:
93
+ stored = await agent_run(session, ctx.agent.id, run.run_id)
94
+ context = await eval_context(session, ctx.agent.id, run.run_id)
95
+ await session.commit()
96
+ bundle = RunBundle(
97
+ run_id=run.run_id,
98
+ source=run.source,
99
+ status=run.status,
100
+ created_at=run.created_at,
101
+ title=run.title,
102
+ run=run_body(run.run_id, run.source, stored, context),
103
+ eval=context,
104
+ )
105
+ try:
106
+ doc = await _trajectory(ctx, run, stored)
107
+ if doc is not None:
108
+ trajectory = validate_trajectory(doc)
109
+ backfill_step_costs(trajectory, ctx.cost_map)
110
+ bundle.trajectory = trajectory.model_dump(exclude_none=True)
111
+ except (httpx.HTTPError, TrajectoryValidationError, LookupError, UnsafePathError) as exc:
112
+ bundle.pull_error = str(exc)
113
+ return bundle
114
+
115
+
116
+ async def _select(
117
+ record: AgentDefinition, body: PullRequest, deps, spans, session
118
+ ) -> tuple[list[BrowserRun], int, str | None]:
119
+ if body.run_ids is not None:
120
+ page = await resolve_runs(record, deps, spans, session, run_ids=body.run_ids)
121
+ known = {run.run_id: run for run in page.runs}
122
+ runs = [known.get(run_id) or BrowserRun(run_id=run_id) for run_id in body.run_ids]
123
+ return runs, len(runs), None
124
+ if body.experiment_id is not None:
125
+ agent_id, ids, total = await experiment_run_ids(
126
+ session, body.experiment_id, limit=body.limit, offset=body.offset
127
+ )
128
+ if agent_id != record.id:
129
+ raise HTTPException(
130
+ status_code=404,
131
+ detail=f"experiment {body.experiment_id} did not run agent {record.ref}",
132
+ )
133
+ if not ids:
134
+ return [], total, None
135
+ picked, _, _ = await _select(record, PullRequest(run_ids=ids), deps, spans, session)
136
+ return picked, total, None
137
+ page = await resolve_runs(
138
+ record,
139
+ deps,
140
+ spans,
141
+ session,
142
+ source=body.source,
143
+ since=body.since,
144
+ until=body.until,
145
+ limit=body.limit,
146
+ offset=body.offset,
147
+ )
148
+ return page.runs, page.total, page.harness_note
149
+
150
+
151
+ @router.post("/{agent}/runs/pull")
152
+ async def pull_runs(
153
+ agent: str,
154
+ body: PullRequest,
155
+ deps: RunnerDepsDep,
156
+ agents: AgentStoreDep,
157
+ spans: SpanStoreDep,
158
+ sessions: SessionReaderDep,
159
+ session: SessionDep,
160
+ ) -> Response:
161
+ record = await agent_or_404(agents, agent)
162
+ selected, total, note = await _select(record, body, deps, spans, session)
163
+ await session.commit()
164
+ harness = await harness_client(deps, record) if server_may_drive(record) and selected else None
165
+ ctx = PullContext(
166
+ agent=record,
167
+ deps=deps,
168
+ sessions=sessions,
169
+ harness=harness,
170
+ cost_map=load_cost_map(deps.cfg.cost_map),
171
+ )
172
+ bundles = []
173
+ try:
174
+ for run in selected:
175
+ bundles.append(await _bundle(ctx, session, run))
176
+ finally:
177
+ if ctx.harness is not None:
178
+ await ctx.harness.aclose()
179
+ index = {
180
+ "agent": record.ref,
181
+ "pulled_at": datetime.now(UTC).isoformat(),
182
+ "filters": body.model_dump(mode="json"),
183
+ "total_matching": total,
184
+ "note": note,
185
+ "runs": [index_row(bundle) for bundle in bundles],
186
+ }
187
+ buffer = io.BytesIO()
188
+ with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as archive:
189
+ archive.writestr("index.json", json.dumps(index, indent=2))
190
+ for bundle in bundles:
191
+ for path, data in bundle_files(bundle).items():
192
+ archive.writestr(path, data)
193
+ return Response(
194
+ content=buffer.getvalue(),
195
+ media_type="application/zip",
196
+ headers=content_disposition(f"{record.ref}-runs.zip"),
197
+ )