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,44 @@
1
+ # MCP
2
+
3
+ The server is also an MCP server at `{{origin}}/mcp` (streamable HTTP). An agent connected to it can read every agent's runs and trajectories, and every experiment's scores. It cannot change anything.
4
+
5
+ ## Connect
6
+
7
+ With an evalkit login token (`evalkit login` stores it in `~/.evalkit/config.toml`):
8
+
9
+ ```bash
10
+ claude mcp add --transport http evalkit {{origin}}/mcp --header "Authorization: Bearer $EVALKIT_TOKEN"
11
+ ```
12
+
13
+ Only login tokens work here: agent tokens (`evalkit tokens create`) and download tickets are refused.
14
+
15
+ When the server's config has an `mcp.oauth` block, MCP clients that speak OAuth (Claude, Cursor, Codex) can connect with the URL alone: the server points them to the identity provider, and a token issued for `{{origin}}/mcp` with the configured scope (`mcp.oauth.scope`, default `evalkit:read`) is accepted. Evalkit login tokens keep working next to it.
16
+
17
+ ```yaml
18
+ mcp:
19
+ oauth:
20
+ issuer: https://id.example.com
21
+ jwks_uri: https://id.example.com/.well-known/jwks.json
22
+ # audience: defaults to <server.base_url>/mcp
23
+ # scope: your identity provider's name for read access; defaults to evalkit:read
24
+ ```
25
+
26
+ An OAuth token is not an evalkit user. Whoever the identity provider gives that scope reads everything an evalkit login reads, which today is every workspace, and archiving someone in evalkit does not cut off their OAuth access: revoke it at the identity provider.
27
+
28
+ ## Tools
29
+
30
+ | Tool | What it returns |
31
+ |---|---|
32
+ | `find_agents` | Agents and the id (`name@workspace`) the run tools take |
33
+ | `find_runs` | An agent's runs in a time window, newest first: id, title, time, `user_id`, `client_id`, app or eval. `include` adds each run's `metrics`, `turns` or `tool_calls` (what it called, with which arguments). `failed_only` keeps runs with a failure; `tool_error` keeps runs where that tool errored. `timezone` reads date-only bounds as `get_run_stats` does. Up to 5,000 rows when listing alone, 200 with `include` or a filter |
34
+ | `get_runs` | Up to 200 runs by id: turns, failed turns, failures, duration, cost and where it came from, tokens, cost by model, tool errors by tool, opening question, final answer; `include` adds `turns` and `tool_calls` |
35
+ | `get_run_stats` | Totals over a time window, or one row per day, user, source, or tool that errored; `group_by` takes several at once and reads the window once |
36
+ | `get_trajectory` | One run's ATIF trajectory, paged by step, long text clipped |
37
+ | `get_run_files`, `download_run_file` | Files the run produced |
38
+ | `find_experiments`, `get_experiments`, `get_experiment_runs` | Experiments, their scorecard, and per-case scores with evaluator explanations |
39
+
40
+ Every tool answers `{data, meta}`, with `data` a list of rows. Search tools page with `limit` and `offset` (`meta.next_offset` is the next page); tools that take ids answer one row per id in input order, with `note: {"code": "not_found"}` for an unknown one. Dates are `start_date` / `end_date` (`YYYY-MM-DD`, inclusive, or an ISO 8601 timestamp). Money is `{"value", "unit": "currency", "currency": "USD"}`. Errors read `<code>: <detail>`. Tools that read trajectories have a time budget for the whole call, listing included: a run not read in time comes back as a row with `error` and `detail` instead of metrics, counted in `meta.failed` by `get_runs`; ask for it again with `get_runs`. Optional fields with no value are left out of rows. When listing alone uses the budget, the call fails with `harness_timeout`; narrow the window. With `failed_only` or `tool_error`, `find_runs` reads the window again on every call, so take one page and fetch the runs not read with `get_runs` rather than paging on.
41
+
42
+ A turn is one user message and everything the agent did until the next one. `cost_source` is `reported` when the harness priced the run, `estimated` when evalkit priced it from token counts, `mixed` for both. A turn counts as failed when the harness marks a step in it with `extra.is_error` (see [Harness API](harness-api.md)).
43
+
44
+ `user_id` and `client_id` are what the harness recorded. Resolve them to people and firms with your identity service.
@@ -0,0 +1,142 @@
1
+ # Quickstart
2
+
3
+ From nothing to a scored experiment on an evalkit server your team already runs. Each step links to its full page; [Concepts](concepts.md) explains the terms.
4
+
5
+ No server yet? [Try locally](try-locally.md) runs one on your machine with a sample agent and your own LLM, and ends with how to deploy one for your team.
6
+
7
+ ## 1. Pick how evalkit reaches your agent
8
+
9
+ | Path | What you build | Pick it when |
10
+ | -------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
11
+ | SDK run + traces | A Python script that calls your agent once per case, and an OpenTelemetry exporter to this server | The default: start here. Runs start from your machine or CI |
12
+ | Harness API | An HTTP service: start a run, report its status, serve its ATIF trajectory | A deployed agent the server should drive (shared agents, runs started from the UI), or you need the Workspace tab |
13
+ | Harness API + traces | The same service minus the trajectory endpoint; the trajectory comes from your spans | You want the Harness API but your agent already emits OpenTelemetry spans |
14
+
15
+ The Workspace tab on a run lists the files the agent wrote. evalkit reads them through the Harness API files endpoints, so only the two Harness API paths have it. See [Harness API](harness-api.md) for what each endpoint enables.
16
+
17
+ ## 2. Install and log in
18
+
19
+ ```bash
20
+ uv tool install --reinstall "evalkit-py=={{version}}"
21
+ evalkit login --url {{origin}}
22
+ ```
23
+
24
+ More in [Install](install.md) and [Login](login.md).
25
+
26
+ ## 3. Write `evalkit.yaml` and one case
27
+
28
+ At the root of the repository that holds your eval cases:
29
+
30
+ ```yaml
31
+ cases: eval/cases
32
+ fixtures: eval/fixtures
33
+
34
+ credentials:
35
+ agents:
36
+ myagent: env:MYAGENT_API_KEY # only for a Harness API agent that needs a key
37
+ llm_providers:
38
+ https://litellm.example.com: env:LITELLM_API_KEY # the server's judge base URL
39
+ ```
40
+
41
+ Put the keys in a `.env` next to `evalkit.yaml`. Then create your agent on the server once:
42
+
43
+ ```bash
44
+ # SDK path: a code-driven agent whose trajectory comes from its traces
45
+ evalkit agents create myagent --trajectory-source otel --mapping openinference.json
46
+ # Harness API path
47
+ evalkit agents create myagent --harness-url http://localhost:8080/evalkit/v1
48
+ ```
49
+
50
+ `openinference.json` (or `gen_ai.json`) comes from the `connect-traces` skill.
51
+
52
+ Each directory under `cases` is a dataset. Add `eval/cases/smoke/cases.yaml`:
53
+
54
+ ```yaml
55
+ cases:
56
+ - id: store-count
57
+ input:
58
+ question: "How many company-operated stores did Starbucks have at FY2021 year-end?"
59
+ evaluators:
60
+ output:
61
+ answer_rubrics:
62
+ rubrics:
63
+ - rubric: "States 17,133 company-operated stores at FY2021 year-end."
64
+ type: factual_retrieval
65
+ ```
66
+
67
+ More in [Config](config.md).
68
+
69
+ ## 4. Run it
70
+
71
+ SDK path: install the SDK into your project (see [Install](install.md)), then write `eval/run.py`:
72
+
73
+ ```python
74
+ from evalkit.sdk import run_experiment, tracing
75
+ from myagent import ask
76
+
77
+ tracing.register(agent="myagent")
78
+
79
+ def task(input: dict) -> str:
80
+ return ask(input["question"])
81
+
82
+ if __name__ == "__main__":
83
+ run_experiment(dataset="smoke", agent="myagent", task=task, dry_run=True)
84
+ ```
85
+
86
+ `dry_run=True` runs one case without creating an experiment and prints what the server made of the spans. When it looks right, drop `dry_run`; `run_experiment` then returns a result whose `url` is the experiment page. More in [SDK runs](sdk-runs.md) and [Tracing](tracing.md).
87
+
88
+ Harness API path:
89
+
90
+ ```bash
91
+ evalkit harness check myagent # exercises every capability the harness declares
92
+ evalkit run smoke --agent myagent
93
+ ```
94
+
95
+ A run executes every case it selects, whether the dataset is in your own workspace (`smoke`) or a shared one (`smoke@dev`). Review state never filters a run.
96
+
97
+ ## 5. Read the results
98
+
99
+ `evalkit run` prints the scorecard when it finishes. On this server, open the dataset under Datasets and pick the experiment. The experiment page has the scorecard; each run opens its trajectory, scores and, on the Harness API paths, its workspace files. If converted spans look wrong (missing messages, wrong span kinds), check one session with `evalkit traces check myagent <session id>` and see [Mappings](mappings.md).
100
+
101
+ ## 6. Attach a file to a case
102
+
103
+ Drop the file in the `fixtures` directory from your `evalkit.yaml`, register it once in `fixtures.yaml` at the root of your `cases` directory, and name it in the case. The `context` line is what the agent is told about the file.
104
+
105
+ ```bash
106
+ cp ~/Downloads/meridian-balance-sheet.csv <fixtures>/
107
+ shasum -a 256 <fixtures>/meridian-balance-sheet.csv # sha256 for the registry
108
+ ```
109
+
110
+ ```yaml
111
+ # <cases>/fixtures.yaml
112
+ fixtures:
113
+ - key: meridian-balance-sheet.csv
114
+ sha256: 652d32…eece7
115
+ context: Meridian's FY2025 balance sheet, USD thousands.
116
+ ```
117
+
118
+ ```yaml
119
+ # in the case
120
+ - id: net-debt-fy2025
121
+ fixtures: [meridian-balance-sheet.csv]
122
+ input:
123
+ question: Using only the attached file, what was net debt at FY2025 year-end?
124
+ ```
125
+
126
+ A run or sync uploads any fixture whose sha256 the server doesn't hold yet, and the server keeps one copy per sha256. So you only need the files you touch, and if the bytes on disk don't match the registry the run stops and shows both hashes.
127
+
128
+ ## 7. Iterate and promote
129
+
130
+ ```bash
131
+ evalkit evaluate <experiment-id> # re-score the same runs after an evaluator change; no agent calls
132
+ evalkit runs pull --experiment <experiment-id> # download every run (trajectory, case, scores) to diagnose failures
133
+ ```
134
+
135
+ Cases reach a shared dataset through a PR. On merge, CI syncs them into a shared workspace such as `dev`, where they start Unreviewed; a reviewer of that workspace approves them, and if `dev` is linked to `prod`, approval promotes them there.
136
+
137
+ ```bash
138
+ evalkit review request finance@dev --case-id net-debt-fy2025 --reviewer <user>
139
+ evalkit review approve finance@dev net-debt-fy2025 # reviewers only
140
+ ```
141
+
142
+ If an admin linked `dev` to a promotion target (`evalkit workspaces promote-to dev prod`), that approval also lands the case in `finance@prod`, approved. `prod` takes cases only that way: no sync, no separate review. The case page in either workspace shows the other's version and links to it.
@@ -0,0 +1,171 @@
1
+ # SDK runs
2
+
3
+ `run_experiment` runs an experiment from your own code: it calls your task once per case and repetition, stores the runs on this server, and scores them with the judge, the remote evaluators of the agent's workspace and any Python evaluators you pass. The agent is a code-driven agent on the server (no harness URL), created once with `evalkit agents create`. Invoking the agent, timeouts and stopping are your code.
4
+
5
+ ```python
6
+ # eval/run.py — uv run python eval/run.py
7
+ from evalkit.sdk import run_experiment, RunResult, evaluator, tracing
8
+ from myagent import MyAgent, setup_tracing
9
+
10
+ setup_tracing() # your own OpenTelemetry provider and instrumentors
11
+ tracing.register(agent="myagent-sdk") # add this server as an export destination
12
+ agent = MyAgent()
13
+
14
+ def task(input: dict, files: list) -> str:
15
+ return agent.ask(input["question"], attachments=[f.path for f in files])
16
+
17
+ @evaluator(name="citation_check")
18
+ def citation_check(case, run) -> float:
19
+ cited = {c["url"] for c in (run.output or {}).get("citations", [])}
20
+ return 1.0 if cited >= set(case.config["required"]) else 0.0
21
+
22
+ if __name__ == "__main__":
23
+ result = run_experiment(dataset="finance", agent="myagent-sdk", task=task,
24
+ evaluators=[citation_check], concurrency=4)
25
+ print(result.url)
26
+ ```
27
+
28
+ ## The task
29
+
30
+ A sync or async callable. Parameters bind **by name** from this set, so their order does not matter; any other required parameter is an error before the first call, and an optional one keeps its default.
31
+
32
+ | Parameter | Value |
33
+ | ---------- | --------------------------------------------------------------------- |
34
+ | `input` | the case input, verbatim, minus `files` |
35
+ | `files` | `list[Fixture]`, the case's fixture files, downloaded before the call |
36
+ | `model` | the `model` argument of `run_experiment`, or `None` |
37
+ | `metadata` | a dict with the keys below |
38
+
39
+ A `Fixture` has:
40
+
41
+ - `name`: the fixture key the case names.
42
+ - `path`: where the file sits on this machine.
43
+ - `context`: the file's description from the fixture registry, or `None`. A Harness API agent gets the same text with each file; show it to your agent the same way (for example an "Attached files" block ahead of the question).
44
+
45
+ `metadata` holds:
46
+
47
+ - `session_id`: the SDK's session id for this run, the `session.id` of its `evalkit.run` span.
48
+ - `experiment_id`: the experiment, or `None` on a dry run.
49
+ - `case_id`: the case.
50
+ - `repetition`: 1 for the first repetition, 2 for the second, and so on.
51
+ - `tags`: the case's tags.
52
+
53
+ `model` is the only way `run_experiment(model=...)` reaches your agent. The experiment records the requested model either way, so a task without a `model` parameter would silently run the agent's default; `run_experiment` prints a warning before the first call when that happens.
54
+
55
+ A task with exactly one parameter whose name is not in the table (`def task(q)`) receives the input. Return a `str` (the answer), a `dict` (the structured output) or a `RunResult`:
56
+
57
+ ```python
58
+ from evalkit.sdk import RunResult
59
+
60
+ RunResult(answer="…", output={"citations": []}, model="gpt-5",
61
+ session_id="…", # override the session the SDK polls
62
+ trajectory={...}) # push an ATIF document instead of tracing
63
+ ```
64
+
65
+ A task that raises records the run as `failed` with the exception and the experiment continues.
66
+
67
+ ## An agent in another process
68
+
69
+ The SDK stamps `session.id` and the `evalkit.*` attributes only on spans made in this Python process. An agent behind HTTP or a job queue, or written in another language, joins the run in one of two ways. Pick one, not both.
70
+
71
+ - **Continue the trace.** Inject the W3C trace context into the request and have the agent start its spans from it. Every span in a trace belongs to the session of the trace's root, the SDK's `evalkit.run` span, even if the agent stamps its own `session.id` or `gen_ai.conversation.id`.
72
+
73
+ ```python
74
+ from opentelemetry import propagate
75
+
76
+ def task(input: dict) -> str:
77
+ headers: dict[str, str] = {}
78
+ propagate.inject(headers) # adds traceparent
79
+ return httpx.post(AGENT_URL, json=input, headers=headers).json()["answer"]
80
+ ```
81
+
82
+ - **Return the agent's session.** The agent puts its own session id on its root span as `session.id`, and the task returns it. Use this when the trace context cannot reach the agent, for example through a queue. The SDK moves its `evalkit.run` span into that session, so no empty session is left behind, and the trajectory is built from the agent's trace.
83
+
84
+ ```python
85
+ def task(input: dict) -> RunResult:
86
+ job = agent_api.start(input["question"]) # your API returns the session it opened
87
+ return RunResult(answer=job.wait(), session_id=job.session_id)
88
+ ```
89
+
90
+ If your API cannot tell you the session id, send it one instead: pass `metadata["session_id"]` along with the job and have the agent use it as its session id (or wrap the job in `tracing.eval_run(metadata)`, see [Tracing](tracing.md)). Its spans then join the SDK's session and the task returns only the answer.
91
+
92
+ ```python
93
+ def task(input: dict, metadata: dict) -> str:
94
+ return queue.submit(input["question"], session_id=metadata["session_id"]).wait()
95
+ ```
96
+
97
+ Either way the Runs page links the session to its experiment and case.
98
+
99
+ The agent's exporter needs its own token. Mint one with `evalkit tokens create --agent <name>`; it never expires. Your login token expires, and an exporter that holds it then drops every span with nothing but a line in its own log.
100
+
101
+ ## Datasets and workspaces
102
+
103
+ - `dataset="finance"` runs your own workspace's copy; if `finance` is a directory under `cases` it is synced first.
104
+ - `dataset="finance@dev"` or `dataset="finance", workspace="dev"` runs the shared cases, every review state included.
105
+ - `case_ids=[...]`, `split="..."`, `repetitions`, `model` and `resume=<experiment id>` mean what the same options mean on `evalkit run`.
106
+ - `dry_run=True` runs one case (`dry_run=N` runs N) without creating an experiment and prints the answer and what the server made of the session. If the server cannot read a session, that case prints a `trajectory read failed` line and the dry run goes on. Use it while wiring up tracing; the session shows on the Runs page with its case question and case id.
107
+
108
+ ## Notebooks
109
+
110
+ `run_experiment` refuses to run inside a running event loop. In a notebook use the async form:
111
+
112
+ ```python
113
+ from evalkit.sdk import arun_experiment
114
+
115
+ result = await arun_experiment(dataset="finance", agent="myagent-sdk", task=task)
116
+ ```
117
+
118
+ ## The agent and the judge
119
+
120
+ `agent="myagent-sdk"` names an agent on the server; a bare name means your own workspace. Create it once:
121
+
122
+ ```bash
123
+ evalkit agents create myagent-sdk --trajectory-source otel --mapping openinference.json \
124
+ --max-concurrent-runs 4 --provides thinking=true
125
+ ```
126
+
127
+ Its `answer` rule and `exclude_spans` live in its span mapping. `evalkit.yaml` is still required; it holds the cases, fixtures and credentials.
128
+
129
+ The judge is the server's default unless you pass one:
130
+
131
+ ```python
132
+ from evalkit.sdk import Judge, run_experiment
133
+
134
+ run_experiment(dataset="finance", agent="myagent-sdk", task=task,
135
+ judge=Judge(base_url="https://litellm.example.com", model="gpt-5"))
136
+ ```
137
+
138
+ An admin sets the default judge with `evalkit judge-default set`. Either way, the SDK reads the judge's key from `credentials.llm_providers` in `evalkit.yaml`, keyed by the judge's base URL.
139
+
140
+ ## Capabilities
141
+
142
+ A code-driven agent has no `GET /capabilities`, so evalkit writes its capabilities onto each experiment itself: `trajectory`, `tool_calls`, `token_usage` and `run_listing` are true; `thinking`, `artifacts` and `workspace` are false. Evaluators gated on a capability skip when it is false. evalkit does not read capabilities from the spans. Set `provides` on the agent (`evalkit agents create --provides`, or `evalkit agents edit`) to declare what it really does, for example `thinking: true` when its spans carry the model's reasoning. Declare only what works: a false `true` produces failing scores blamed on the agent.
143
+
144
+ ## Python evaluators
145
+
146
+ `@evaluator(name=..., kind="evaluator" | "gauge", requires_trajectory=False)` turns a function into an evaluator. It receives the same `EvalCase` and `EvalRun` the HTTP Custom Evaluator API sends: the case id, input, expected block and this evaluator's config from the case YAML; the answer, structured output and the ATIF trajectory as a dict. Return a float, a `ScoreItem`, or a list of them. A case selects it by name under `evaluators.custom`, exactly like an HTTP evaluator.
147
+
148
+ ## Bring your own benchmark
149
+
150
+ A published benchmark is a suite plus a Python evaluator:
151
+
152
+ 1. Write a script that turns the benchmark's file into case YAML. Each case gets the question as `input`. Its grading data (rubrics, reference answer) goes under `evaluators.custom.<grader name>`.
153
+ 2. `evalkit sync <dir>` puts it in a dataset like any suite.
154
+ 3. Write the grader as an `@evaluator` with that name. It reads the grading data from `case.config`, and calls its own judge models if it needs any. Pass it to `run_experiment`.
155
+
156
+ `evalkit lint` and `evalkit sync` warn with `W-CUSTOM-UNKNOWN` for the grader name, because the server can't see a Python evaluator. That warning is expected.
157
+
158
+ ## Re-scoring
159
+
160
+ Python evaluators live in your script, so a re-score that needs them starts from the script:
161
+
162
+ ```python
163
+ from evalkit.sdk import evaluate_experiment
164
+
165
+ evaluate_experiment(experiment_id="…", evaluators=[citation_check],
166
+ refetch_trajectories=True)
167
+ ```
168
+
169
+ `refetch_trajectories` polls the collector again for runs whose trajectory was `partial` or `unavailable` and re-scores them. `evalkit evaluate` refuses experiments that used Python evaluators and points here.
170
+
171
+ Next: [Tracing](tracing.md).
@@ -0,0 +1,171 @@
1
+ # Tracing
2
+
3
+ The collector is an OTLP/HTTP endpoint on this server:
4
+
5
+ ```
6
+ POST {{origin}}/api/otel/v1/traces
7
+ ```
8
+
9
+ It accepts `application/x-protobuf` and `application/json`, gzip or not, from any OpenTelemetry SDK. Spans are stored as received and converted to ATIF with the agent's mapping when a run or the Runs page asks for them.
10
+
11
+ ## From Python with the SDK
12
+
13
+ ```python
14
+ from evalkit.sdk import tracing
15
+
16
+ tracing.register(agent="myagent-sdk")
17
+ ```
18
+
19
+ `register` adds an exporter for this server to your global tracer provider, creating one if none is set. Your existing exporters keep running. The exporter carries an `X-Evalkit-Agent` header, which names the agent on the server; the `evalkit.agent` resource attribute is only set when `register` creates the provider itself. It reads the server URL from `EVALKIT_SERVER_URL` or your `evalkit login`, and the token from `EVALKIT_OTEL_TOKEN` (an agent token), then `EVALKIT_TOKEN`, then your `evalkit login`. It needs the `otel` extra.
20
+
21
+ ### An existing span pipeline
22
+
23
+ `register` changes the global provider. When you build your own pipeline, take evalkit's parts and put them where you need them:
24
+
25
+ ```python
26
+ from evalkit.sdk import tracing
27
+
28
+ exporter = tracing.exporter(agent="myagent-sdk") # OTLPSpanExporter: URL, token and X-Evalkit-Agent set
29
+ processor = tracing.processor(agent="myagent-sdk") # batch processor around that exporter
30
+ provider.add_span_processor(processor) # what register does for you
31
+ headers = tracing.headers(agent="myagent-sdk") # the same headers, for an exporter you build yourself
32
+ ```
33
+
34
+ All three resolve the server and token the same way `register` does. `processor` also stamps the run's `session.id` and `evalkit.*` attributes on spans that start under a task call (or under `tracing.eval_run`, below).
35
+
36
+ Spans handed straight to a processor or exporter, for example by a bridge that converts another tool's spans, never pass through the provider, so `register` cannot see them. Hand them to `tracing.processor(...)` too: give the bridge that processor, or one processor that fans out to all of yours and evalkit's (OpenTelemetry's `SynchronousMultiSpanProcessor`). Those spans are already finished, so `processor` cannot stamp them: the bridge copies `session.id` onto each span it converts. How eval-only export treats them is below.
37
+
38
+ ### Choosing what a shared worker exports
39
+
40
+ A worker that serves both app runs (production, staging or manual use) and eval runs exports every span it makes, so every app turn also becomes a session on this server, which you can browse on the Runs page and pull with `evalkit runs pull <agent> --source app`. Whether to send app traffic is your choice. `eval_run` marks eval runs either way, and to send only those, filter on the mark:
41
+
42
+ ```python
43
+ from evalkit.sdk import tracing
44
+
45
+ provider.add_span_processor(tracing.processor(agent="myagent", only=tracing.is_eval_span))
46
+
47
+ def handle_job(job):
48
+ if job.evalkit: # the task's metadata, sent along with the job
49
+ with tracing.eval_run(job.evalkit):
50
+ return run_agent(job)
51
+ return run_agent(job)
52
+ ```
53
+
54
+ The task passes its `metadata` argument along with the job (see [SDK runs](sdk-runs.md)). `eval_run` stamps `session.id` (the SDK's session id), `evalkit.case_id`, `evalkit.experiment_id` and `evalkit.repetition` on every span started inside it, so the worker's spans join the run's session and the task returns only the answer. `is_eval_span` is true for a span that carries `evalkit.case_id`. The stamp lives in a context variable: spans started in another thread need that thread to run inside `eval_run` too.
55
+
56
+ A harness agent learns that a run is an eval run from the `metadata` of `POST /runs`: `experiment_id`, `case_id` and `repetition`. That is what it passes to `eval_run`, with the `run_id` it returns as the session id: `tracing.eval_run({**metadata, "session_id": run_id})`.
57
+
58
+ `only` decides by trace, not by span. It is any function that takes a span and returns a bool, and it runs on each span as the span starts and again as it ends. Once it is true for one span, the processor keeps every span of that span's trace that reaches it: spans started inside `eval_run`, the job's own spans started before `eval_run` in the same trace, and finished spans a bridge hands straight to the processor. Spans of other traces are dropped. A custom `only` picks whole traces the same way.
59
+
60
+ The rule for order: a span is kept when its trace is already marked by the time the span ends. The first span started inside `eval_run` marks its trace as it starts, before anything under it ends, so spans that end before it and spans a bridge hands over later are both kept. A span that ends before any span of its trace was marked is dropped. A bridge that may hand spans over that early can copy the `evalkit.*` attributes onto them, so each one marks its own trace. A bridge should copy `session.id` either way: the server takes a trace's session from its root span when the root reaches it, and from each span's own `session.id` otherwise.
61
+
62
+ The processor remembers the 10,000 most recently used eval traces and forgets the oldest. Each kept span refreshes its trace, so a trace is only forgotten once 10,000 other eval traces have been used since its last span.
63
+
64
+ Spans from another process in the same trace, such as an MCP gateway pod the agent calls, never reach this processor. They go through that process's own exporter, so that process makes its own choice: either it continues the trace, wraps its work in `eval_run` with the run's metadata sent along with the request, and filters with `only=tracing.is_eval_span` too, or it exports everything.
65
+
66
+ Do not use a sampler for this: a sampler drops spans for every exporter, your own backend included.
67
+
68
+ Around every task call the SDK opens a root span named `evalkit.run` with `session.id` set to the run id, and stamps `session.id`, `evalkit.experiment_id`, `evalkit.case_id` and `evalkit.repetition` onto every span your code starts underneath it. That is what groups a trace into one session. When the task returns `RunResult(session_id=...)`, the root span takes that session id instead, so it joins the agent's session rather than sitting alone; see [SDK runs](sdk-runs.md) for agents in another process.
69
+
70
+ ## From any OpenTelemetry SDK
71
+
72
+ Point the exporter at the collector and name the agent, either with a header or an attribute:
73
+
74
+ ```bash
75
+ export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT={{origin}}/api/otel/v1/traces
76
+ export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <token>,X-Evalkit-Agent=myagent"
77
+ export OTEL_RESOURCE_ATTRIBUTES="evalkit.agent=myagent" # alternative to the header
78
+ ```
79
+
80
+ The collector picks the agent per span: the span's `evalkit.agent` attribute first, then the resource's, then the `X-Evalkit-Agent` header. One process that serves several agents can therefore share one exporter and set `evalkit.agent` on each agent's spans. The value is an agent reference (`myagent` or `myagent@workspace`). Every agent a batch names is checked as if it came in the header: an agent token writes only its own agent, a login token only agents in workspaces you can write to (your own, or a shared one where you are a writer). If any span names an agent the token may not write, the whole batch is refused.
81
+
82
+ Put the session id on the root span of each trace, in the attribute your mapping's `session_id` rule names (`session.id` in `openinference.json`, `gen_ai.conversation.id` in `gen_ai.json`). Every span in a trace belongs to the session of the trace's root, even one that carries a session attribute of its own. Only when the root has no session do child spans keep theirs.
83
+
84
+ ## Deployed agents
85
+
86
+ A service cannot carry a person's token. Mint an agent token, bound to one agent and valid only for the collector:
87
+
88
+ ```bash
89
+ evalkit tokens create --agent ouragent@dev # needs writer on dev
90
+ evalkit tokens list
91
+ evalkit tokens revoke <id>
92
+ ```
93
+
94
+ Set it on the deployment as `EVALKIT_OTEL_TOKEN` next to `EVALKIT_SERVER_URL`; `tracing.register`, `tracing.exporter` and `tracing.processor` pick both up. For a non-Python exporter put it in `OTEL_EXPORTER_OTLP_HEADERS`. Use an agent token for any exporter outside the process that calls `run_experiment` too, even on your laptop: the login token expires under a long-running worker.
95
+
96
+ The collector logs every refused export as `otel export rejected` with the status, the reason (`expired token`, `invalid token`, `unknown agent`, `forbidden`, `too large`, `malformed`) and the agent. OpenTelemetry exporters only log a failed export on their side, so a silent agent is worth checking in both logs.
97
+
98
+ ## Size limits and large spans
99
+
100
+ LLM spans from OpenInference and GenAI instrumentations carry the full input of each call: the messages so far, the system prompt and the tool schemas. Every turn repeats the whole context, so a long agent session grows with roughly the square of its turns. A 14-minute chat with subagents can store hundreds of MB of span attributes.
101
+
102
+ The server has two limits, both set in its config:
103
+
104
+ | Limit | Default | What happens past it |
105
+ | ------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
106
+ | `otel.max_batch_bytes` | 16 MiB | One export request (after gzip is undone) gets `413` and its spans are dropped. Exporters do not split a refused batch, so keep batches small. |
107
+ | `otel.max_session_bytes` | 384 MiB | Reading the session (a run's trajectory, `evalkit traces check`, the Mappings tab) gets `413` with the session's size and the limit. A run records the trajectory as `unavailable` and logs the message. `--dump spans.json` still works. |
108
+
109
+ `otel.max_session_bytes` counts the span attributes a trajectory build loads, not everything stored. evalkit reads the input messages (and system prompt) of an LLM span only for the first LLM call of each agent and subagent; later calls add only their output, tool calls and tokens to the trajectory. So the build leaves the input of every later LLM call in the database and does not count it. The limit keeps one session from using up the server's memory, so raise it only together with the server's memory limit.
110
+
111
+ For agents with large spans, send fewer spans per export and give each export more time. For the Python SDK, `tracing.register` included:
112
+
113
+ ```bash
114
+ export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=16 # spans per export; default 512
115
+ export OTEL_BSP_MAX_QUEUE_SIZE=2048 # spans waiting for export; raise it if the exporter logs "Queue full"
116
+ export OTEL_EXPORTER_OTLP_TIMEOUT=60 # seconds per export in Python; default 10
117
+ ```
118
+
119
+ Other SDKs follow the OpenTelemetry spec and read `OTEL_EXPORTER_OTLP_TIMEOUT` in milliseconds (`60000`). A 10 MB span can take more than 10 s to upload, longer than the default timeout. In an OpenTelemetry Collector, set `send_batch_max_size` on the `batch` processor and `timeout` on the `otlphttp` exporter instead.
120
+
121
+ ## Exporting to evalkit and another backend
122
+
123
+ The standard OTLP variables name one endpoint. To keep exporting to your tracing backend and send the same spans here, either:
124
+
125
+ - add a second `otlphttp` exporter to the OpenTelemetry Collector the agent already exports to (recommended for deployed agents), or
126
+ - add a second span processor and exporter in code. `tracing.register` does this for Python.
127
+
128
+ ```yaml
129
+ exporters:
130
+ otlphttp/evalkit:
131
+ traces_endpoint: {{origin}}/api/otel/v1/traces
132
+ headers:
133
+ Authorization: Bearer ${env:EVALKIT_OTEL_TOKEN}
134
+ X-Evalkit-Agent: ouragent@dev
135
+ service:
136
+ pipelines:
137
+ traces:
138
+ exporters: [otlp/existing, otlphttp/evalkit]
139
+ ```
140
+
141
+ The collector speaks OTLP over HTTP only, not gRPC. Bodies may be chunked with no `Content-Length` (the JavaScript exporter sends them that way); a proxy in front of the collector must allow that.
142
+
143
+ ## The main agent and subagents
144
+
145
+ Mark the agent's top-level span as an AGENT span (`openinference.span.kind=AGENT`, or `gen_ai.operation.name=invoke_agent`). evalkit builds the main trajectory from it, and every AGENT span inside it becomes a subagent trajectory named by the span's `agent.name` (`gen_ai.agent.name`), or by the span name when that is missing. When the root span is not an AGENT span, as with the SDK's `evalkit.run` span, evalkit takes the earliest AGENT span that is not inside another one as the main agent. If that span is a tool call that starts a subagent partway through the run, the subagent's steps land in the main trajectory and the call has no result, so mark the top-level span as an AGENT span rather than relying on this.
146
+
147
+ ## Harness agents with traces
148
+
149
+ A harness agent created with `--trajectory-source otel` keeps the whole Harness API flow and skips `GET /runs/{id}/trajectory`: it puts the run id it returned on its spans, in the attribute its mapping's `session_id` rule names, and evalkit polls the session until it settles. The harness declares `provides.trajectory: false`; evalkit still runs the trajectory evaluators because the trajectory comes from the collector.
150
+
151
+ The session evalkit polls is the `run_id` that `POST /runs` returns, so there is no separate session field. A queue-backed agent whose worker picks its own session id returns that id as `run_id`, or puts the `run_id` it returned on its spans in that session attribute. To export only eval runs from a harness that also serves production, see Choosing what a shared worker exports above.
152
+
153
+ ## Importing history from a trace source
154
+
155
+ An admin registers a trace source (a Phoenix server) once. After that, any
156
+ user can import traces from it into an OTel agent they can write, with
157
+ `evalkit traces import` or the agent page's Import tab.
158
+
159
+ - The trace source's API key is shared with every user in effect: anyone can
160
+ import any project that key can read.
161
+ - Imported traces land in the agent's workspace. Every user can read every
162
+ workspace, so traces imported into your personal workspace are visible to
163
+ everyone on the server. See [Where runs happen](workspaces.md).
164
+
165
+ ## Checking a session
166
+
167
+ ```bash
168
+ evalkit traces check myagent-sdk <session id>
169
+ ```
170
+
171
+ prints the settle state, span kinds, tokens, the attributes the mapping did not read and the ATIF summary. `--dump spans.json` writes the raw spans. `--dump esf.json` leaves out the input messages of every LLM call after each agent's first, as the trajectory does not use them; `spans.json` has the raw values. A session over `otel.max_session_bytes` fails the check with its size and the limit (see Size limits and large spans above). If the kinds or messages look wrong, the agent needs a mapping: see [Mappings](mappings.md).