evalkit-py 0.2.0.dev12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. evalkit/__init__.py +0 -0
  2. evalkit/api/__init__.py +0 -0
  3. evalkit/api/access.py +74 -0
  4. evalkit/api/agents.py +159 -0
  5. evalkit/api/app.py +360 -0
  6. evalkit/api/auth.py +225 -0
  7. evalkit/api/cli_dist.py +10 -0
  8. evalkit/api/datasets.py +169 -0
  9. evalkit/api/deps.py +79 -0
  10. evalkit/api/download.py +15 -0
  11. evalkit/api/evaluators.py +121 -0
  12. evalkit/api/experiments.py +616 -0
  13. evalkit/api/fixtures.py +95 -0
  14. evalkit/api/harness_check.py +48 -0
  15. evalkit/api/imports.py +180 -0
  16. evalkit/api/limits.py +84 -0
  17. evalkit/api/llm_providers.py +120 -0
  18. evalkit/api/mcp/__init__.py +0 -0
  19. evalkit/api/mcp/auth.py +100 -0
  20. evalkit/api/mcp/descriptions.py +336 -0
  21. evalkit/api/mcp/models.py +259 -0
  22. evalkit/api/mcp/server.py +101 -0
  23. evalkit/api/mcp/tools.py +844 -0
  24. evalkit/api/meta.py +19 -0
  25. evalkit/api/otel.py +350 -0
  26. evalkit/api/pull.py +197 -0
  27. evalkit/api/review.py +271 -0
  28. evalkit/api/runs.py +555 -0
  29. evalkit/api/server_keys.py +44 -0
  30. evalkit/api/tokens.py +51 -0
  31. evalkit/api/trace_imports.py +173 -0
  32. evalkit/api/trace_sources.py +49 -0
  33. evalkit/api/traces.py +196 -0
  34. evalkit/api/users.py +72 -0
  35. evalkit/api/workspaces.py +131 -0
  36. evalkit/bundled/contracts/atif-accepted.json +5 -0
  37. evalkit/bundled/contracts/custom-evaluator-api-v1.json +243 -0
  38. evalkit/bundled/contracts/harness-api-v1.json +938 -0
  39. evalkit/bundled/contracts/span-mapping-v1.json +314 -0
  40. evalkit/bundled/docs/concepts.md +26 -0
  41. evalkit/bundled/docs/config.md +38 -0
  42. evalkit/bundled/docs/harness-api.md +44 -0
  43. evalkit/bundled/docs/install.md +60 -0
  44. evalkit/bundled/docs/login.md +36 -0
  45. evalkit/bundled/docs/mappings.md +136 -0
  46. evalkit/bundled/docs/mcp.md +44 -0
  47. evalkit/bundled/docs/quickstart.md +142 -0
  48. evalkit/bundled/docs/sdk-runs.md +171 -0
  49. evalkit/bundled/docs/tracing.md +171 -0
  50. evalkit/bundled/docs/try-locally.md +288 -0
  51. evalkit/bundled/docs/workspaces.md +45 -0
  52. evalkit/bundled/skills/authoring-eval-cases/SKILL.md +387 -0
  53. evalkit/bundled/skills/authoring-eval-cases/references/overlays.md +83 -0
  54. evalkit/bundled/skills/authoring-eval-cases/references/tool-checks.md +71 -0
  55. evalkit/bundled/skills/connect-traces/SKILL.md +296 -0
  56. evalkit/bundled/skills/connect-traces/references/gen_ai.json +42 -0
  57. evalkit/bundled/skills/connect-traces/references/openinference.json +35 -0
  58. evalkit/bundled/skills/connect-traces/references/span-mapping-schema.md +88 -0
  59. evalkit/bundled/skills/eval-driven-development/SKILL.md +458 -0
  60. evalkit/bundled/skills/eval-driven-development/references/analyze-run.md +97 -0
  61. evalkit/bundled/skills/eval-driven-development/references/diagnose.md +130 -0
  62. evalkit/bundled/skills/eval-driven-development/references/synthesize-cases.md +87 -0
  63. evalkit/bundled/skills/eval-driven-development/references/synthesize.md +165 -0
  64. evalkit/bundled/skills/evalkit-deployment/SKILL.md +332 -0
  65. evalkit/bundled/skills/evalkit-deployment/config.example.yaml +49 -0
  66. evalkit/bundled/skills/evalkit-deployment/docker-compose.yml +51 -0
  67. evalkit/bundled/skills/evalkit-deployment/references/config-and-deploy.md +333 -0
  68. evalkit/bundled/skills/evalkit-deployment/references/harness-api.md +139 -0
  69. evalkit/bundled/skills/evalkit-deployment/try/.env.example +11 -0
  70. evalkit/bundled/skills/evalkit-deployment/try/agent.py +131 -0
  71. evalkit/bundled/skills/evalkit-deployment/try/cases/acme/balance-sheet/cases.yaml +25 -0
  72. evalkit/bundled/skills/evalkit-deployment/try/cases/acme/income-statement/cases.yaml +43 -0
  73. evalkit/bundled/skills/evalkit-deployment/try/config.yaml +17 -0
  74. evalkit/bundled/skills/evalkit-deployment/try/docker-compose.yml +82 -0
  75. evalkit/bundled/skills/evalkit-deployment/try/evalkit.yaml +4 -0
  76. evalkit/bundled/skills/evalkit-deployment/try/mapping.json +42 -0
  77. evalkit/bundled/skills/evalkit-deployment/try/run.py +33 -0
  78. evalkit/bundled/skills/evalkit-usage/SKILL.md +509 -0
  79. evalkit/bundled/skills/evalkit-usage/references/suite-fixtures.md +42 -0
  80. evalkit/bundled/ui/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  81. evalkit/bundled/ui/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  82. evalkit/bundled/ui/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  83. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  84. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  85. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  86. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  87. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  88. evalkit/bundled/ui/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  89. evalkit/bundled/ui/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  90. evalkit/bundled/ui/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  91. evalkit/bundled/ui/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  92. evalkit/bundled/ui/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  93. evalkit/bundled/ui/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  94. evalkit/bundled/ui/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  95. evalkit/bundled/ui/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  96. evalkit/bundled/ui/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  97. evalkit/bundled/ui/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  98. evalkit/bundled/ui/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  99. evalkit/bundled/ui/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  100. evalkit/bundled/ui/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  101. evalkit/bundled/ui/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  102. evalkit/bundled/ui/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  103. evalkit/bundled/ui/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  104. evalkit/bundled/ui/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  105. evalkit/bundled/ui/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  106. evalkit/bundled/ui/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  107. evalkit/bundled/ui/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  108. evalkit/bundled/ui/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  109. evalkit/bundled/ui/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  110. evalkit/bundled/ui/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  111. evalkit/bundled/ui/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  112. evalkit/bundled/ui/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  113. evalkit/bundled/ui/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  114. evalkit/bundled/ui/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  115. evalkit/bundled/ui/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  116. evalkit/bundled/ui/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  117. evalkit/bundled/ui/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  118. evalkit/bundled/ui/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  119. evalkit/bundled/ui/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  120. evalkit/bundled/ui/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  121. evalkit/bundled/ui/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  122. evalkit/bundled/ui/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  123. evalkit/bundled/ui/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  124. evalkit/bundled/ui/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  125. evalkit/bundled/ui/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  126. evalkit/bundled/ui/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  127. evalkit/bundled/ui/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  128. evalkit/bundled/ui/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  129. evalkit/bundled/ui/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  130. evalkit/bundled/ui/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  131. evalkit/bundled/ui/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  132. evalkit/bundled/ui/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  133. evalkit/bundled/ui/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  134. evalkit/bundled/ui/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  135. evalkit/bundled/ui/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  136. evalkit/bundled/ui/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  137. evalkit/bundled/ui/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  138. evalkit/bundled/ui/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  139. evalkit/bundled/ui/assets/abap-08VXUWAP.js +1 -0
  140. evalkit/bundled/ui/assets/apex-BWPQTe0t.js +1 -0
  141. evalkit/bundled/ui/assets/azcli-Bc_sGQ0U.js +1 -0
  142. evalkit/bundled/ui/assets/bat-i0X4ZdIN.js +1 -0
  143. evalkit/bundled/ui/assets/bicep-B5-_aFwp.js +2 -0
  144. evalkit/bundled/ui/assets/cameligo-DMUM7wLl.js +1 -0
  145. evalkit/bundled/ui/assets/clojure-Cm7r79vr.js +1 -0
  146. evalkit/bundled/ui/assets/codicon-DF1abBS2.ttf +0 -0
  147. evalkit/bundled/ui/assets/coffee-Ba7i2nA0.js +1 -0
  148. evalkit/bundled/ui/assets/cpp-C7h46wYY.js +1 -0
  149. evalkit/bundled/ui/assets/csharp-BKxtCVv1.js +1 -0
  150. evalkit/bundled/ui/assets/csp-bTuwJoIa.js +1 -0
  151. evalkit/bundled/ui/assets/css-DIMkf-bt.js +3 -0
  152. evalkit/bundled/ui/assets/css.worker-DwfUZ3fB.js +96 -0
  153. evalkit/bundled/ui/assets/cssMode-C97l1pbB.js +1 -0
  154. evalkit/bundled/ui/assets/cypher-CVaqCwHa.js +1 -0
  155. evalkit/bundled/ui/assets/dart-onAF5SnQ.js +1 -0
  156. evalkit/bundled/ui/assets/dockerfile-DZFCIeNp.js +1 -0
  157. evalkit/bundled/ui/assets/ecl-94qehhXe.js +1 -0
  158. evalkit/bundled/ui/assets/editor-DjpsE8to.css +1 -0
  159. evalkit/bundled/ui/assets/editor.api-NTesIxw5.js +908 -0
  160. evalkit/bundled/ui/assets/editor.worker-CRz__AKW.js +33 -0
  161. evalkit/bundled/ui/assets/elixir-6RTg0lbw.js +1 -0
  162. evalkit/bundled/ui/assets/flow9-C5_-GSwl.js +1 -0
  163. evalkit/bundled/ui/assets/freemarker2-BWqEFagp.js +3 -0
  164. evalkit/bundled/ui/assets/fsharp-C8Ef5oNN.js +1 -0
  165. evalkit/bundled/ui/assets/go-C-y9NEjX.js +1 -0
  166. evalkit/bundled/ui/assets/graphql-fmXr3nnJ.js +1 -0
  167. evalkit/bundled/ui/assets/handlebars-DkIF-whN.js +1 -0
  168. evalkit/bundled/ui/assets/hcl-CpzslTdj.js +1 -0
  169. evalkit/bundled/ui/assets/html-C_rpeQXJ.js +1 -0
  170. evalkit/bundled/ui/assets/html.worker-D9ugpX4y.js +509 -0
  171. evalkit/bundled/ui/assets/htmlMode-B0YIq3DF.js +1 -0
  172. evalkit/bundled/ui/assets/index-Cw-jBH6M.js +736 -0
  173. evalkit/bundled/ui/assets/index-DKtoQZtm.css +1 -0
  174. evalkit/bundled/ui/assets/ini-sBoK_t0W.js +1 -0
  175. evalkit/bundled/ui/assets/java-BEtHBSE6.js +1 -0
  176. evalkit/bundled/ui/assets/javascript-D4EGhYA6.js +1 -0
  177. evalkit/bundled/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  178. evalkit/bundled/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  179. evalkit/bundled/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  180. evalkit/bundled/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  181. evalkit/bundled/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  182. evalkit/bundled/ui/assets/json.worker-CV9fqkUp.js +65 -0
  183. evalkit/bundled/ui/assets/jsonMode-pQjSfIH6.js +7 -0
  184. evalkit/bundled/ui/assets/julia-Bri6UV-V.js +1 -0
  185. evalkit/bundled/ui/assets/kotlin-BOotOW0E.js +1 -0
  186. evalkit/bundled/ui/assets/less-B9JPFI3C.js +2 -0
  187. evalkit/bundled/ui/assets/lexon-CfSJPG6W.js +1 -0
  188. evalkit/bundled/ui/assets/liquid-DbrvoYjD.js +1 -0
  189. evalkit/bundled/ui/assets/lspLanguageFeatures-D0B7cH6y.js +4 -0
  190. evalkit/bundled/ui/assets/lua-CsQS60Ue.js +1 -0
  191. evalkit/bundled/ui/assets/m3-D-oSqn_W.js +1 -0
  192. evalkit/bundled/ui/assets/markdown-Cimd5fb3.js +1 -0
  193. evalkit/bundled/ui/assets/mdx-B0HrujLR.js +1 -0
  194. evalkit/bundled/ui/assets/mips-CIPQ_RoX.js +1 -0
  195. evalkit/bundled/ui/assets/monacoEditor-DsoPncxW.js +68 -0
  196. evalkit/bundled/ui/assets/monacoEditor-n7clI0si.css +1 -0
  197. evalkit/bundled/ui/assets/msdax-DauUninz.js +1 -0
  198. evalkit/bundled/ui/assets/mysql-SOo6toE5.js +1 -0
  199. evalkit/bundled/ui/assets/objective-c-FvmIjYaQ.js +1 -0
  200. evalkit/bundled/ui/assets/pascal-DrH0SRf2.js +1 -0
  201. evalkit/bundled/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
  202. evalkit/bundled/ui/assets/perl-oz_6vUea.js +1 -0
  203. evalkit/bundled/ui/assets/pgsql-DTj74zXo.js +1 -0
  204. evalkit/bundled/ui/assets/php-nr791fC2.js +1 -0
  205. evalkit/bundled/ui/assets/pla-CopQ2nXW.js +1 -0
  206. evalkit/bundled/ui/assets/postiats-43DmfD33.js +1 -0
  207. evalkit/bundled/ui/assets/powerquery-D3hlyOfw.js +1 -0
  208. evalkit/bundled/ui/assets/powershell-DmHpPYUd.js +1 -0
  209. evalkit/bundled/ui/assets/protobuf-C531GsRP.js +2 -0
  210. evalkit/bundled/ui/assets/pug-Z5eAx3Zn.js +1 -0
  211. evalkit/bundled/ui/assets/python-CieHVM9n.js +1 -0
  212. evalkit/bundled/ui/assets/qsharp-DkqhCAOL.js +1 -0
  213. evalkit/bundled/ui/assets/r-BwWrilGY.js +1 -0
  214. evalkit/bundled/ui/assets/razor-sLewSMyp.js +1 -0
  215. evalkit/bundled/ui/assets/redis-ClamHrr6.js +1 -0
  216. evalkit/bundled/ui/assets/redshift-DT7zqm-g.js +1 -0
  217. evalkit/bundled/ui/assets/restructuredtext-BYgofb2h.js +1 -0
  218. evalkit/bundled/ui/assets/ruby-DezsRK8O.js +1 -0
  219. evalkit/bundled/ui/assets/rust-DdL9SqIa.js +1 -0
  220. evalkit/bundled/ui/assets/sb-CcwsVR0C.js +1 -0
  221. evalkit/bundled/ui/assets/scala-DHpiXF5c.js +1 -0
  222. evalkit/bundled/ui/assets/scheme-BeGwcela.js +1 -0
  223. evalkit/bundled/ui/assets/scss-gp-XZpBa.js +3 -0
  224. evalkit/bundled/ui/assets/shell-CC2rA5mh.js +1 -0
  225. evalkit/bundled/ui/assets/solidity-BEEn4gHE.js +1 -0
  226. evalkit/bundled/ui/assets/sophia-CRfGWb83.js +1 -0
  227. evalkit/bundled/ui/assets/sparql-D_Lu-MrJ.js +1 -0
  228. evalkit/bundled/ui/assets/sql-NEE52Syq.js +1 -0
  229. evalkit/bundled/ui/assets/st-DbInun42.js +1 -0
  230. evalkit/bundled/ui/assets/swift-Bxkupp3x.js +1 -0
  231. evalkit/bundled/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
  232. evalkit/bundled/ui/assets/tcl-DISqw1ZD.js +1 -0
  233. evalkit/bundled/ui/assets/ts.worker-DIAQkoB3.js +67741 -0
  234. evalkit/bundled/ui/assets/tsMode-BcVQ1nu0.js +11 -0
  235. evalkit/bundled/ui/assets/twig-De2hgUGE.js +1 -0
  236. evalkit/bundled/ui/assets/typescript-CUvNWd9p.js +1 -0
  237. evalkit/bundled/ui/assets/typespec-B8J7ngcE.js +1 -0
  238. evalkit/bundled/ui/assets/vb-DV3o63ZY.js +1 -0
  239. evalkit/bundled/ui/assets/wgsl-DpFanUEy.js +298 -0
  240. evalkit/bundled/ui/assets/workers-CwpYQtzp.js +1 -0
  241. evalkit/bundled/ui/assets/xml-C1HnXMdF.js +1 -0
  242. evalkit/bundled/ui/assets/yaml-D7Ey3HQu.js +1 -0
  243. evalkit/bundled/ui/favicon.svg +6 -0
  244. evalkit/bundled/ui/index.html +14 -0
  245. evalkit/cli/__init__.py +0 -0
  246. evalkit/cli/admin_commands.py +590 -0
  247. evalkit/cli/client.py +67 -0
  248. evalkit/cli/init_server.py +165 -0
  249. evalkit/cli/local_run.py +195 -0
  250. evalkit/cli/main.py +1365 -0
  251. evalkit/cli/runs_commands.py +539 -0
  252. evalkit/cli/server_commands.py +130 -0
  253. evalkit/cli/skill_targets.py +43 -0
  254. evalkit/cli/tokens_commands.py +52 -0
  255. evalkit/cli/traces_commands.py +332 -0
  256. evalkit/cli/update.py +85 -0
  257. evalkit/client/__init__.py +0 -0
  258. evalkit/client/agents.py +25 -0
  259. evalkit/client/api.py +193 -0
  260. evalkit/client/blob.py +104 -0
  261. evalkit/client/connection.py +88 -0
  262. evalkit/client/evaluators.py +11 -0
  263. evalkit/client/sessions.py +17 -0
  264. evalkit/client/store.py +139 -0
  265. evalkit/client/summary.py +46 -0
  266. evalkit/client/sync.py +198 -0
  267. evalkit/config.py +227 -0
  268. evalkit/contracts/__init__.py +0 -0
  269. evalkit/contracts/atif.py +115 -0
  270. evalkit/contracts/cases.py +456 -0
  271. evalkit/contracts/custom_evaluator.py +58 -0
  272. evalkit/contracts/enums.py +86 -0
  273. evalkit/contracts/esf.py +120 -0
  274. evalkit/contracts/harness.py +281 -0
  275. evalkit/contracts/notes.json +4 -0
  276. evalkit/contracts/notes.py +7 -0
  277. evalkit/contracts/settings.py +41 -0
  278. evalkit/core/__init__.py +0 -0
  279. evalkit/core/access.py +75 -0
  280. evalkit/core/agents.py +65 -0
  281. evalkit/core/blob.py +46 -0
  282. evalkit/core/capabilities.py +36 -0
  283. evalkit/core/costmap.py +90 -0
  284. evalkit/core/custom_eval.py +50 -0
  285. evalkit/core/data/cost_map.json +81546 -0
  286. evalkit/core/endpoints.py +31 -0
  287. evalkit/core/evaluators/__init__.py +29 -0
  288. evalkit/core/evaluators/base.py +121 -0
  289. evalkit/core/evaluators/gauges.py +84 -0
  290. evalkit/core/evaluators/presentation.py +166 -0
  291. evalkit/core/evaluators/response_time.py +21 -0
  292. evalkit/core/evaluators/rubrics.py +494 -0
  293. evalkit/core/evaluators/tool_trajectory.py +187 -0
  294. evalkit/core/evaluators/trajectory_rubrics.py +198 -0
  295. evalkit/core/fixtures.py +57 -0
  296. evalkit/core/harness.py +227 -0
  297. evalkit/core/harness_check.py +191 -0
  298. evalkit/core/importer.py +257 -0
  299. evalkit/core/judge.py +78 -0
  300. evalkit/core/lint.py +265 -0
  301. evalkit/core/overlays.py +76 -0
  302. evalkit/core/paths.py +25 -0
  303. evalkit/core/promotion.py +94 -0
  304. evalkit/core/pull.py +198 -0
  305. evalkit/core/python_eval.py +59 -0
  306. evalkit/core/remote_evaluators.py +32 -0
  307. evalkit/core/review.py +220 -0
  308. evalkit/core/run_digest.py +421 -0
  309. evalkit/core/runner.py +995 -0
  310. evalkit/core/scorecard.py +255 -0
  311. evalkit/core/traces/__init__.py +0 -0
  312. evalkit/core/traces/convert.py +336 -0
  313. evalkit/core/traces/importer.py +113 -0
  314. evalkit/core/traces/mapping.py +385 -0
  315. evalkit/core/traces/poll.py +38 -0
  316. evalkit/core/traces/raw.py +21 -0
  317. evalkit/core/traces/sessions.py +172 -0
  318. evalkit/core/traces/settle.py +14 -0
  319. evalkit/core/traces/sources/__init__.py +19 -0
  320. evalkit/core/traces/sources/base.py +25 -0
  321. evalkit/core/traces/sources/phoenix.py +121 -0
  322. evalkit/core/trajectory.py +248 -0
  323. evalkit/core/workspace.py +30 -0
  324. evalkit/credentials.py +119 -0
  325. evalkit/db/__init__.py +0 -0
  326. evalkit/db/agents.py +289 -0
  327. evalkit/db/alembic/env.py +90 -0
  328. evalkit/db/alembic/script.py.mako +28 -0
  329. evalkit/db/alembic/versions/0011_baseline.py +1002 -0
  330. evalkit/db/alembic/versions/0012_ticket_token_kind.py +27 -0
  331. evalkit/db/alembic/versions/0013_keys_and_llm_providers.py +67 -0
  332. evalkit/db/cases.py +608 -0
  333. evalkit/db/crypto.py +12 -0
  334. evalkit/db/evaluators.py +156 -0
  335. evalkit/db/experiments.py +363 -0
  336. evalkit/db/fixture_blobs.py +91 -0
  337. evalkit/db/keys.py +78 -0
  338. evalkit/db/llm_providers.py +176 -0
  339. evalkit/db/locks.py +6 -0
  340. evalkit/db/mappings.py +168 -0
  341. evalkit/db/models.py +567 -0
  342. evalkit/db/promotion.py +271 -0
  343. evalkit/db/rekey.py +46 -0
  344. evalkit/db/review.py +250 -0
  345. evalkit/db/review_state.py +50 -0
  346. evalkit/db/runs.py +533 -0
  347. evalkit/db/scorecard.py +300 -0
  348. evalkit/db/session.py +71 -0
  349. evalkit/db/sessions.py +90 -0
  350. evalkit/db/spans.py +360 -0
  351. evalkit/db/store.py +209 -0
  352. evalkit/db/tokens.py +100 -0
  353. evalkit/db/trace_imports.py +308 -0
  354. evalkit/db/trace_sources.py +131 -0
  355. evalkit/db/users.py +186 -0
  356. evalkit/db/workspaces.py +232 -0
  357. evalkit/paths.py +33 -0
  358. evalkit/sdk/__init__.py +29 -0
  359. evalkit/sdk/binding.py +69 -0
  360. evalkit/sdk/evaluators.py +56 -0
  361. evalkit/sdk/runner.py +957 -0
  362. evalkit/sdk/tracing.py +166 -0
  363. evalkit/store/__init__.py +0 -0
  364. evalkit/store/base.py +354 -0
  365. evalkit/store/blob.py +69 -0
  366. evalkit/version.py +44 -0
  367. evalkit_py-0.2.0.dev12.dist-info/METADATA +205 -0
  368. evalkit_py-0.2.0.dev12.dist-info/RECORD +371 -0
  369. evalkit_py-0.2.0.dev12.dist-info/WHEEL +4 -0
  370. evalkit_py-0.2.0.dev12.dist-info/entry_points.txt +2 -0
  371. evalkit_py-0.2.0.dev12.dist-info/licenses/LICENSE +661 -0
evalkit/client/sync.py ADDED
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+ import subprocess
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ from evalkit.client.api import SyncApi
9
+ from evalkit.contracts.cases import REGISTRY_FILENAME, is_case_yaml, load_cases_from_yaml
10
+ from evalkit.core.fixtures import load_registries, sha256_hex
11
+ from evalkit.core.workspace import Reference, parse_reference
12
+ from evalkit.store.base import GitRevision, Selection
13
+
14
+
15
+ @dataclass
16
+ class ResolvedPath:
17
+ ref: Reference
18
+ dataset_dir: Path | None
19
+ selection: Selection
20
+ subpath: str | None = None
21
+
22
+
23
+ def _case_ids(file: Path) -> list[str]:
24
+ cases, _ = load_cases_from_yaml(file)
25
+ return [case.id for case in cases]
26
+
27
+
28
+ def _selection_for(subpath: str, dataset_dir: Path | None) -> Selection:
29
+ if not subpath:
30
+ return Selection(all=True)
31
+ on_disk = dataset_dir / subpath if dataset_dir is not None else None
32
+ if on_disk is not None and on_disk.is_dir():
33
+ return Selection(tag=on_disk.name)
34
+ if on_disk is not None and on_disk.is_file():
35
+ return Selection(case_ids=_case_ids(on_disk))
36
+ if Path(subpath).suffix in (".yaml", ".yml"):
37
+ raise ValueError(f"{subpath} is not on disk; selecting a file needs its case ids")
38
+ return Selection(tag=Path(subpath).name)
39
+
40
+
41
+ def _on_disk(candidate: Path, cases_root: Path | None) -> tuple[Path, str]:
42
+ resolved = candidate.resolve()
43
+ if cases_root is not None and resolved.is_relative_to(cases_root.resolve()):
44
+ relative = resolved.relative_to(cases_root.resolve())
45
+ if not relative.parts:
46
+ raise ValueError(f"{candidate} is the cases root; pass a dataset directory under it")
47
+ return cases_root.resolve() / relative.parts[0], Path(*relative.parts[1:]).as_posix()
48
+ if resolved.is_dir():
49
+ return resolved, ""
50
+ return resolved.parent, resolved.name
51
+
52
+
53
+ def resolve_path(arg: str, cases_root: Path | None, my_workspace: str) -> ResolvedPath:
54
+ candidate = Path(arg)
55
+ if candidate.exists():
56
+ dataset_dir, subpath = _on_disk(candidate, cases_root)
57
+ ref = Reference(dataset_dir.name, my_workspace)
58
+ if subpath == ".":
59
+ subpath = ""
60
+ return ResolvedPath(ref, dataset_dir, _selection_for(subpath, dataset_dir), subpath or None)
61
+ ref_text, _, subpath = arg.partition("/")
62
+ ref = parse_reference(ref_text, my_workspace)
63
+ own = ref.workspace == my_workspace
64
+ if own and cases_root is None:
65
+ raise ValueError(
66
+ f"{ref.name} is in your workspace, so it syncs from disk: set `cases:` in "
67
+ "evalkit.yaml, or pass the dataset's directory"
68
+ )
69
+ on_disk = cases_root / ref.name if cases_root is not None else None
70
+ if on_disk is not None and not on_disk.is_dir():
71
+ on_disk = None
72
+ if own and on_disk is None:
73
+ raise ValueError(f"no dataset directory {ref.name!r} under {cases_root}")
74
+ return ResolvedPath(
75
+ ref, on_disk if own else None, _selection_for(subpath, on_disk), subpath or None
76
+ )
77
+
78
+
79
+ def resolve_dataset(dataset: str, workspace: str | None, my_workspace: str) -> Reference:
80
+ ref = parse_reference(dataset, my_workspace)
81
+ if "@" in dataset and workspace:
82
+ raise ValueError("dataset already names a workspace; drop the workspace argument")
83
+ if workspace:
84
+ return Reference(ref.name, workspace)
85
+ return ref
86
+
87
+
88
+ def _git(args: list[str], cwd: Path) -> str | None:
89
+ if shutil.which("git") is None:
90
+ return None
91
+ result = subprocess.run(["git", *args], cwd=cwd, capture_output=True, text=True)
92
+ return result.stdout.strip() if result.returncode == 0 else None
93
+
94
+
95
+ def git_revision(path: Path) -> GitRevision:
96
+ cwd = path if path.is_dir() else path.parent
97
+ sha = _git(["rev-parse", "HEAD"], cwd)
98
+ if sha is None:
99
+ return GitRevision()
100
+ status = _git(["status", "--porcelain", "--", str(path)], cwd)
101
+ return GitRevision(
102
+ sha=sha, branch=_git(["rev-parse", "--abbrev-ref", "HEAD"], cwd), dirty=bool(status)
103
+ )
104
+
105
+
106
+ def _nearest_registry(start: Path) -> Path | None:
107
+ for directory in (start, *start.parents):
108
+ candidate = directory / REGISTRY_FILENAME
109
+ if candidate.is_file():
110
+ return candidate
111
+ return None
112
+
113
+
114
+ def sync_files(dataset_dir: Path, subpath: str | None = None) -> list[tuple[str, Path]]:
115
+ start = dataset_dir / subpath if subpath else dataset_dir
116
+ if not start.exists():
117
+ raise ValueError(
118
+ f"{subpath} is not a folder or file under {dataset_dir}; sync needs one on disk"
119
+ )
120
+ if start.is_dir():
121
+ files = sorted(
122
+ f
123
+ for pattern in ("*.yaml", "*.yml")
124
+ for f in start.rglob(pattern)
125
+ if f.is_file()
126
+ and not any(part.startswith(".") for part in f.relative_to(dataset_dir).parts)
127
+ and is_case_yaml(f.relative_to(dataset_dir))
128
+ )
129
+ else:
130
+ files = [start]
131
+ out = [(f.relative_to(dataset_dir).as_posix(), f) for f in files]
132
+ registry = _nearest_registry(dataset_dir)
133
+ if registry is not None:
134
+ out.append((REGISTRY_FILENAME, registry))
135
+ return out
136
+
137
+
138
+ def upload_fixtures(
139
+ client: SyncApi, files: list[tuple[str, Path]], fixtures_dir: Path
140
+ ) -> list[str]:
141
+ registry = [path for name, path in files if name == REGISTRY_FILENAME]
142
+ entries, _ = load_registries(registry)
143
+ referenced: set[str] = set()
144
+ for name, path in files:
145
+ if name == REGISTRY_FILENAME:
146
+ continue
147
+ cases, _ = load_cases_from_yaml(path)
148
+ referenced.update(key for case in cases for key in case.fixtures)
149
+ uploaded: list[str] = []
150
+ for key in sorted(referenced):
151
+ entry = entries.get(key)
152
+ if entry is None:
153
+ continue
154
+ url = f"/api/fixtures/sha256/{entry.sha256}"
155
+ if client.head(url).status_code == 200:
156
+ continue
157
+ local = fixtures_dir / key
158
+ if not local.is_file():
159
+ raise ValueError(
160
+ f"fixture {key!r} is registered but {local} does not exist and the server has "
161
+ f"no bytes with sha256 {entry.sha256}"
162
+ )
163
+ data = local.read_bytes()
164
+ if sha256_hex(data) != entry.sha256:
165
+ raise ValueError(
166
+ f"fixture {key!r} on disk has sha256 {sha256_hex(data)} but {REGISTRY_FILENAME} "
167
+ f"says {entry.sha256}; update the registry entry"
168
+ )
169
+ client.put(url, content=data, retry=True)
170
+ uploaded.append(key)
171
+ return uploaded
172
+
173
+
174
+ def sync_dataset(
175
+ client: SyncApi,
176
+ dataset_dir: Path,
177
+ *,
178
+ name: str,
179
+ workspace: str,
180
+ revision: GitRevision,
181
+ subpath: str | None = None,
182
+ fixtures_dir: Path | None = None,
183
+ actor: str | None = None,
184
+ ) -> dict:
185
+ params: dict[str, object] = {
186
+ "dataset": name,
187
+ "workspace": workspace,
188
+ "prune": subpath is None,
189
+ }
190
+ for key, value in revision.model_dump().items():
191
+ if value is not None:
192
+ params[key] = value
193
+ if actor is not None:
194
+ params["actor"] = actor
195
+ files = sync_files(dataset_dir, subpath)
196
+ if fixtures_dir is not None:
197
+ upload_fixtures(client, files, fixtures_dir)
198
+ return client.post_files("/api/sync", files, params=params).json()
evalkit/config.py ADDED
@@ -0,0 +1,227 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from collections.abc import Mapping
5
+ from pathlib import Path
6
+ from typing import Literal
7
+
8
+ import yaml
9
+ from pydantic import BaseModel, ConfigDict, model_validator
10
+
11
+ from evalkit.credentials import CredentialsConfig, load_dotenv_beside
12
+
13
+
14
+ class MissingEnvError(ValueError):
15
+ def __init__(self, source: Path, key: str, name: str):
16
+ self.source = source
17
+ self.key = key
18
+ self.name = name
19
+ super().__init__(f"{source}: {key} needs env {name} (or env:{name}:<default>)")
20
+
21
+
22
+ class ConfigModel(BaseModel):
23
+ model_config = ConfigDict(extra="forbid", hide_input_in_errors=True)
24
+
25
+
26
+ class ServerConfig(ConfigModel):
27
+ base_url: str
28
+ accent_color: str | None = None
29
+
30
+
31
+ class DbConfig(ConfigModel):
32
+ url: str
33
+ migrate_on_start: bool = False
34
+
35
+
36
+ class BlobConfig(ConfigModel):
37
+ url: str
38
+
39
+
40
+ class CostMapConfig(ConfigModel):
41
+ source: str = "bundled"
42
+
43
+
44
+ class AuthConfig(ConfigModel):
45
+ token_ttl_hours: int = 720
46
+
47
+
48
+ class McpOAuthConfig(ConfigModel):
49
+ issuer: str
50
+ jwks_uri: str
51
+ audience: str | None = None
52
+ algorithm: str = "RS256"
53
+ scope: str = "evalkit:read"
54
+
55
+
56
+ class McpConfig(ConfigModel):
57
+ oauth: McpOAuthConfig | None = None
58
+
59
+
60
+ class OtelConfig(ConfigModel):
61
+ settle_seconds: int = 10
62
+ max_batch_bytes: int = 16 * 1024 * 1024
63
+ max_session_bytes: int = 384 * 1024 * 1024
64
+ import_page_traces: int = 25
65
+
66
+
67
+ class SentryConfig(ConfigModel):
68
+ dsn: str
69
+ environment: str | None = None
70
+ traces_sample_rate: float = 0.0
71
+ max_request_body_size: Literal["never", "small", "medium", "always"] = "never"
72
+
73
+
74
+ MB = 1024 * 1024
75
+
76
+
77
+ class LimitsConfig(ConfigModel):
78
+ fixture_upload_bytes: int = 500 * MB
79
+ experiment_blob_bytes: int = 500 * MB
80
+ case_upload_bytes: int = 50 * MB
81
+ request_bytes: int = 10 * MB
82
+
83
+
84
+ class RunnerConfig(ConfigModel):
85
+ cost_map: CostMapConfig = CostMapConfig()
86
+
87
+
88
+ # TODO: remove in 0.5.0, two minor versions after 0.3.0 moved credentials and
89
+ # judge_default to the database; _shell and _moved_to_db_commands only serve that check.
90
+ def _shell(value: object) -> str:
91
+ text = str(value)
92
+ return f"${text.removeprefix('env:')}" if text.startswith("env:") else text
93
+
94
+
95
+ def _moved_to_db_commands(data: dict) -> list[str]:
96
+ credentials = data.get("credentials") or {}
97
+ judge = data.get("judge_default") or {}
98
+ commands = [
99
+ f"evalkit llm-providers create <name> --base-url {_shell(url)} "
100
+ f"--api-key-env {str(ref).removeprefix('env:')}"
101
+ for url, ref in (credentials.get("judges") or {}).items()
102
+ ]
103
+ if judge:
104
+ commands.append(
105
+ f"evalkit judge-default set --provider <name> "
106
+ f"--model {_shell(judge.get('model', '<model>'))}"
107
+ )
108
+ commands += [
109
+ f"evalkit agents edit {ref} --api-key-env {str(env).removeprefix('env:')}"
110
+ for ref, env in (credentials.get("agents") or {}).items()
111
+ ]
112
+ commands += [
113
+ f"evalkit evaluators edit {ref} --api-key-env {str(env).removeprefix('env:')}"
114
+ for ref, env in (credentials.get("evaluators") or {}).items()
115
+ ]
116
+ return commands
117
+
118
+
119
+ class Config(RunnerConfig):
120
+ server: ServerConfig
121
+ db: DbConfig
122
+ blob: BlobConfig
123
+ auth: AuthConfig = AuthConfig()
124
+ mcp: McpConfig = McpConfig()
125
+ otel: OtelConfig = OtelConfig()
126
+ limits: LimitsConfig = LimitsConfig()
127
+ sentry: SentryConfig | None = None
128
+ encryption_key: str | None = None
129
+ allow_private_endpoints: bool = False
130
+
131
+ # TODO: remove in 0.5.0, two minor versions after 0.3.0 moved credentials and
132
+ # judge_default to the database.
133
+ @model_validator(mode="before")
134
+ @classmethod
135
+ def _keys_moved_to_db(cls, data: object) -> object:
136
+ if isinstance(data, dict) and ("credentials" in data or "judge_default" in data):
137
+ commands = "\n".join(f" {c}" for c in _moved_to_db_commands(data))
138
+ raise ValueError(
139
+ "credentials and judge_default moved to the database. Remove them from the "
140
+ "config, start the server, then run (as an admin, with the keys in your env):\n"
141
+ f"{commands}"
142
+ )
143
+ return data
144
+
145
+
146
+ LOCAL_CONFIG_FILENAME = "evalkit.yaml"
147
+
148
+
149
+ class LocalConfig(RunnerConfig):
150
+ path: Path
151
+ credentials: CredentialsConfig = CredentialsConfig()
152
+ cases: Path | None = None
153
+ fixtures: Path | None = None
154
+
155
+ @classmethod
156
+ def find(cls, start: Path) -> LocalConfig | None:
157
+ start = start.resolve()
158
+ for directory in (start, *start.parents):
159
+ candidate = directory / LOCAL_CONFIG_FILENAME
160
+ if candidate.is_file():
161
+ return load_local_config(candidate)
162
+ return None
163
+
164
+ def environ(self) -> dict[str, str]:
165
+ return load_dotenv_beside(self.path)
166
+
167
+ def resolve_env(self, *, scoring: bool = True) -> LocalConfig:
168
+ data = self.model_dump()
169
+ if scoring:
170
+ environ = self.environ()
171
+ data["cost_map"] = resolve_env_refs(data["cost_map"], self.path, "cost_map", environ)
172
+ return LocalConfig.model_validate(data)
173
+
174
+
175
+ UNRESOLVED_KEYS = frozenset({"credentials"})
176
+
177
+
178
+ def resolve_env_refs(obj: object, source: Path, key: str, environ: Mapping[str, str]) -> object:
179
+ if isinstance(obj, dict):
180
+ return {
181
+ k: v
182
+ if k in UNRESOLVED_KEYS and not key
183
+ else resolve_env_refs(v, source, _join(key, k), environ)
184
+ for k, v in obj.items()
185
+ }
186
+ if isinstance(obj, list):
187
+ return [
188
+ resolve_env_refs(v, source, f"{key}[{_item_label(v, i)}]", environ)
189
+ for i, v in enumerate(obj)
190
+ ]
191
+ if isinstance(obj, str) and obj.startswith("env:"):
192
+ name, has_default, default = obj[4:].partition(":")
193
+ if has_default:
194
+ return environ.get(name, default)
195
+ if name not in environ:
196
+ raise MissingEnvError(source, key, name)
197
+ return environ[name]
198
+ return obj
199
+
200
+
201
+ def _join(key: str, child: object) -> str:
202
+ return f"{key}.{child}" if key else str(child)
203
+
204
+
205
+ def _item_label(item: object, index: int) -> object:
206
+ if isinstance(item, dict) and isinstance(item.get("name"), str):
207
+ return item["name"]
208
+ return index
209
+
210
+
211
+ def load_config(path: Path) -> Config:
212
+ raw = yaml.safe_load(path.read_text())
213
+ data = resolve_env_refs(raw, path, "", os.environ)
214
+ return Config.model_validate(data)
215
+
216
+
217
+ def load_local_config(path: Path) -> LocalConfig:
218
+ data = yaml.safe_load(path.read_text()) or {}
219
+ assert isinstance(data, dict)
220
+ base = path.resolve().parent
221
+ cfg = LocalConfig.model_validate({**data, "path": path.resolve()})
222
+ return cfg.model_copy(
223
+ update={
224
+ "cases": (base / cfg.cases).resolve() if cfg.cases is not None else None,
225
+ "fixtures": (base / cfg.fixtures).resolve() if cfg.fixtures is not None else None,
226
+ }
227
+ )
File without changes
@@ -0,0 +1,115 @@
1
+ from typing import Any, Literal
2
+
3
+ from pydantic import BaseModel, ConfigDict, ValidationError
4
+
5
+ ACCEPTED_SCHEMA_VERSIONS = frozenset({"ATIF-v1.8"})
6
+
7
+
8
+ class TrajectoryValidationError(Exception):
9
+ pass
10
+
11
+
12
+ class AtifModel(BaseModel):
13
+ model_config = ConfigDict(extra="allow")
14
+
15
+
16
+ class AtifAgent(AtifModel):
17
+ name: str
18
+ version: str | None = None
19
+ model_name: str | None = None
20
+ tool_definitions: list[dict[str, Any]] | None = None
21
+ extra: dict[str, Any] | None = None
22
+
23
+
24
+ class ContentPart(AtifModel):
25
+ type: str
26
+ text: str | None = None
27
+ media_type: str | None = None
28
+ path: str | None = None
29
+
30
+
31
+ class ToolCall(AtifModel):
32
+ tool_call_id: str
33
+ function_name: str
34
+ arguments: dict[str, Any]
35
+ extra: dict[str, Any] | None = None
36
+
37
+
38
+ class SubagentTrajectoryRef(AtifModel):
39
+ trajectory_id: str | None = None
40
+ trajectory_path: str | None = None
41
+ session_id: str | None = None
42
+ extra: dict[str, Any] | None = None
43
+
44
+
45
+ class ObservationResult(AtifModel):
46
+ source_call_id: str | None = None
47
+ content: str | list[ContentPart] | None = None
48
+ subagent_trajectory_ref: list[SubagentTrajectoryRef] | None = None
49
+ extra: dict[str, Any] | None = None
50
+
51
+
52
+ class Observation(AtifModel):
53
+ results: list[ObservationResult]
54
+
55
+
56
+ class StepMetrics(AtifModel):
57
+ prompt_tokens: int | None = None
58
+ completion_tokens: int | None = None
59
+ cached_tokens: int | None = None
60
+ cost_usd: float | None = None
61
+ extra: dict[str, Any] | None = None
62
+
63
+
64
+ class FinalMetrics(AtifModel):
65
+ total_prompt_tokens: int | None = None
66
+ total_completion_tokens: int | None = None
67
+ total_cached_tokens: int | None = None
68
+ total_cost_usd: float | None = None
69
+ total_steps: int | None = None
70
+
71
+
72
+ class Step(AtifModel):
73
+ step_id: int
74
+ timestamp: str | None = None
75
+ source: Literal["user", "agent", "system"]
76
+ message: str | list[ContentPart] | None = None
77
+ reasoning_content: str | None = None
78
+ tool_calls: list[ToolCall] | None = None
79
+ observation: Observation | None = None
80
+ metrics: StepMetrics | None = None
81
+ llm_call_count: int | None = None
82
+ model_name: str | None = None
83
+
84
+
85
+ class Trajectory(AtifModel):
86
+ schema_version: str
87
+ session_id: str | None = None
88
+ trajectory_id: str | None = None
89
+ agent: AtifAgent | None = None
90
+ steps: list[Step]
91
+ final_metrics: FinalMetrics | None = None
92
+ subagent_trajectories: list["Trajectory"] | None = None
93
+ extra: dict[str, Any] | None = None
94
+
95
+
96
+ def validate_trajectory(data: dict) -> Trajectory:
97
+ version = data.get("schema_version")
98
+ if version not in ACCEPTED_SCHEMA_VERSIONS:
99
+ raise TrajectoryValidationError(
100
+ f"unsupported schema_version {version!r}; accepted: {sorted(ACCEPTED_SCHEMA_VERSIONS)}"
101
+ )
102
+ try:
103
+ return Trajectory.model_validate(data)
104
+ except ValidationError as exc:
105
+ missing = "; ".join(
106
+ f"{'.'.join(str(loc) for loc in err['loc'])}: {err['msg']}" for err in exc.errors()
107
+ )
108
+ raise TrajectoryValidationError(f"invalid ATIF document: {missing}") from exc
109
+
110
+
111
+ def subagent_link_warnings(trajectory: Trajectory) -> list[str]:
112
+ missing = sum(1 for sub in trajectory.subagent_trajectories or [] if not sub.trajectory_id)
113
+ if not missing:
114
+ return []
115
+ return [f"{missing} embedded subagent(s) have no trajectory_id (required since ATIF-v1.7)"]