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,385 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from collections.abc import Callable
6
+ from fnmatch import fnmatchcase
7
+ from typing import Any, NamedTuple
8
+
9
+ from evalkit.contracts.esf import (
10
+ AttributeRule,
11
+ EsfKind,
12
+ EsfMessage,
13
+ EsfSpan,
14
+ EsfToolCall,
15
+ MappingDocument,
16
+ MessageFormat,
17
+ )
18
+ from evalkit.core.traces.raw import RawSpan
19
+
20
+ SDK_ROOT_ATTRIBUTE = "evalkit.sdk_root"
21
+ TASK_SESSION_ATTRIBUTE = "evalkit.task_session"
22
+ SDK_SESSION_ATTRIBUTE = "session.id"
23
+ KINDS: frozenset[str] = frozenset({"llm", "tool", "agent", "chain", "retriever", "other"})
24
+
25
+
26
+ SDK_ROOT_MAPPING = MappingDocument(
27
+ schema_version="1",
28
+ kind=AttributeRule(attribute="openinference.span.kind", values={"CHAIN": "chain"}),
29
+ session_id=AttributeRule(attribute=SDK_SESSION_ATTRIBUTE),
30
+ input=AttributeRule(attribute="input.value", json=True),
31
+ output=AttributeRule(attribute="output.value"),
32
+ )
33
+
34
+
35
+ def mapping_for(
36
+ attributes: dict[str, Any], active: MappingDocument | None
37
+ ) -> MappingDocument | None:
38
+ if attributes.get(SDK_ROOT_ATTRIBUTE):
39
+ return SDK_ROOT_MAPPING
40
+ return active
41
+
42
+
43
+ def kind_attributes(active: MappingDocument | None) -> set[str]:
44
+ docs = [active, SDK_ROOT_MAPPING]
45
+ return {SDK_ROOT_ATTRIBUTE} | {d.kind.attribute for d in docs if d and d.kind}
46
+
47
+
48
+ class AttributeKeys(NamedTuple):
49
+ keys: frozenset[str]
50
+ prefixes: frozenset[str]
51
+
52
+
53
+ def _rule_keys(rules: list[AttributeRule | None]) -> AttributeKeys:
54
+ present = [r for r in rules if r is not None]
55
+ return AttributeKeys(
56
+ frozenset(r.attribute for r in present if r.message_format != "openinference"),
57
+ frozenset(f"{r.attribute}." for r in present if r.message_format == "openinference"),
58
+ )
59
+
60
+
61
+ def _llm_reads(doc: MappingDocument, active: MappingDocument | None) -> AttributeKeys:
62
+ tokens = doc.tokens
63
+ rules = [doc.kind, doc.session_id, doc.model, doc.output_messages, doc.cost_usd]
64
+ rules += [tokens.prompt, tokens.completion, tokens.cached, tokens.cache_write]
65
+ read = _rule_keys(rules)
66
+ linked = {SDK_ROOT_ATTRIBUTE, TASK_SESSION_ATTRIBUTE, SDK_SESSION_ATTRIBUTE}
67
+ if active is not None and active.session_id is not None:
68
+ linked.add(active.session_id.attribute)
69
+ return AttributeKeys(read.keys | linked, read.prefixes)
70
+
71
+
72
+ def input_context(docs: list[MappingDocument], active: MappingDocument | None) -> AttributeKeys:
73
+ context = [_rule_keys([d.input_messages, d.input, d.system_instructions]) for d in docs]
74
+ reads = [_llm_reads(d, active) for d in docs]
75
+ read_keys = {k for r in reads for k in r.keys}
76
+ read_prefixes = {p for r in reads for p in r.prefixes}
77
+ keys = {
78
+ k
79
+ for c in context
80
+ for k in c.keys
81
+ if k not in read_keys and not any(k.startswith(p) for p in read_prefixes)
82
+ }
83
+ prefixes = {
84
+ p
85
+ for c in context
86
+ for p in c.prefixes
87
+ if not any(k.startswith(p) for k in read_keys)
88
+ and not any(p.startswith(q) or q.startswith(p) for q in read_prefixes)
89
+ }
90
+ return AttributeKeys(frozenset(keys), frozenset(prefixes))
91
+
92
+
93
+ def session_id_of(attributes: dict[str, Any], active: MappingDocument | None) -> str | None:
94
+ if attributes.get(SDK_ROOT_ATTRIBUTE) or attributes.get(TASK_SESSION_ATTRIBUTE):
95
+ key: str | None = SDK_SESSION_ATTRIBUTE
96
+ else:
97
+ key = active.session_id.attribute if active and active.session_id else None
98
+ value = attributes.get(key) if key else None
99
+ return None if value in (None, "") else str(value)
100
+
101
+
102
+ def try_json(value: Any) -> Any:
103
+ if not isinstance(value, str):
104
+ return value
105
+ try:
106
+ return json.loads(value)
107
+ except ValueError:
108
+ return value
109
+
110
+
111
+ def _arguments(value: Any) -> dict[str, Any] | str:
112
+ parsed = try_json(value)
113
+ if isinstance(parsed, dict):
114
+ return parsed
115
+ return value if isinstance(value, str) else json.dumps(value)
116
+
117
+
118
+ def _text(content: Any) -> str | None:
119
+ if content is None:
120
+ return None
121
+ if isinstance(content, str):
122
+ return content
123
+ if isinstance(content, list):
124
+ parts = [p.get("text") for p in content if isinstance(p, dict) and p.get("type") == "text"]
125
+ return "".join(p for p in parts if p) or None
126
+ return json.dumps(content)
127
+
128
+
129
+ _INDEXED = re.compile(r"^(\d+)\.(.*)$")
130
+
131
+
132
+ def _content_text(parts: list[dict[str, Any]], kind: str) -> str | None:
133
+ texts = [
134
+ str(p["message_content.text"])
135
+ for p in parts
136
+ if p.get("message_content.type") == kind and p.get("message_content.text") is not None
137
+ ]
138
+ return "".join(texts) or None
139
+
140
+
141
+ def _openinference_messages(
142
+ attributes: dict[str, Any], key: str
143
+ ) -> tuple[list[EsfMessage], set[str]]:
144
+ prefix = f"{key}."
145
+ read: set[str] = set()
146
+ messages: dict[int, dict[str, Any]] = {}
147
+ calls: dict[int, dict[int, dict[str, Any]]] = {}
148
+ for attr, value in attributes.items():
149
+ if not attr.startswith(prefix):
150
+ continue
151
+ matched = _INDEXED.match(attr[len(prefix) :])
152
+ if matched is None:
153
+ continue
154
+ read.add(attr)
155
+ index, rest = int(matched.group(1)), matched.group(2)
156
+ if rest.startswith("message.tool_calls."):
157
+ call_match = _INDEXED.match(rest.removeprefix("message.tool_calls."))
158
+ if call_match is None:
159
+ continue
160
+ call_index, call_field = int(call_match.group(1)), call_match.group(2)
161
+ calls.setdefault(index, {}).setdefault(call_index, {})[call_field] = value
162
+ else:
163
+ messages.setdefault(index, {})[rest] = value
164
+ out: list[EsfMessage] = []
165
+ for index in sorted(set(messages) | set(calls)):
166
+ fields = messages.get(index, {})
167
+ contents: dict[int, dict[str, Any]] = {}
168
+ for field, value in fields.items():
169
+ content_match = _INDEXED.match(field.removeprefix("message.contents."))
170
+ if field.startswith("message.contents.") and content_match is not None:
171
+ part = contents.setdefault(int(content_match.group(1)), {})
172
+ part[content_match.group(2)] = value
173
+ parts = [contents[j] for j in sorted(contents)]
174
+ tool_calls = [
175
+ EsfToolCall(
176
+ id=str(c.get("tool_call.id") or f"{index}.{j}"),
177
+ name=str(c.get("tool_call.function.name") or ""),
178
+ arguments=_arguments(c.get("tool_call.function.arguments")),
179
+ )
180
+ for j, c in sorted(calls.get(index, {}).items())
181
+ ]
182
+ out.append(
183
+ EsfMessage(
184
+ role=str(fields.get("message.role") or "assistant"),
185
+ text=_text(fields.get("message.content")) or _content_text(parts, "text"),
186
+ reasoning=_content_text(parts, "reasoning"),
187
+ tool_calls=tool_calls or None,
188
+ tool_call_id=fields.get("message.tool_call_id"),
189
+ )
190
+ )
191
+ return out, read
192
+
193
+
194
+ def _gen_ai_messages(attributes: dict[str, Any], key: str) -> tuple[list[EsfMessage], set[str]]:
195
+ raw = try_json(attributes.get(key))
196
+ if not isinstance(raw, list):
197
+ return [], {key}
198
+ out: list[EsfMessage] = []
199
+ for message in raw:
200
+ if not isinstance(message, dict):
201
+ continue
202
+ texts: list[str] = []
203
+ reasoning: list[str] = []
204
+ tool_calls: list[EsfToolCall] = []
205
+ tool_call_id: str | None = None
206
+ for part in message.get("parts") or []:
207
+ if not isinstance(part, dict):
208
+ continue
209
+ kind = part.get("type")
210
+ if kind == "text" and part.get("content") is not None:
211
+ texts.append(str(part["content"]))
212
+ elif kind == "reasoning" and part.get("content") is not None:
213
+ reasoning.append(str(part["content"]))
214
+ elif kind == "tool_call":
215
+ tool_calls.append(
216
+ EsfToolCall(
217
+ id=str(part.get("id") or ""),
218
+ name=str(part.get("name") or ""),
219
+ arguments=_arguments(part.get("arguments")),
220
+ )
221
+ )
222
+ elif kind == "tool_call_response":
223
+ tool_call_id = str(part.get("id") or "") or None
224
+ result = part["response"] if "response" in part else part.get("result")
225
+ texts.append(result if isinstance(result, str) else json.dumps(result))
226
+ out.append(
227
+ EsfMessage(
228
+ role=str(message.get("role") or "assistant"),
229
+ text="".join(texts) or None,
230
+ reasoning="".join(reasoning) or None,
231
+ tool_calls=tool_calls or None,
232
+ tool_call_id=tool_call_id,
233
+ )
234
+ )
235
+ return out, {key}
236
+
237
+
238
+ def gen_ai_messages(value: Any) -> list[EsfMessage] | None:
239
+ if not isinstance(value, list) or not value:
240
+ return None
241
+ if not all(isinstance(m, dict) and isinstance(m.get("parts"), list) for m in value):
242
+ return None
243
+ return _gen_ai_messages({"messages": value}, "messages")[0]
244
+
245
+
246
+ def _instructions(value: Any) -> str | None:
247
+ if isinstance(value, list):
248
+ texts = [
249
+ str(p.get("content") if p.get("content") is not None else p.get("text") or "")
250
+ for p in value
251
+ if isinstance(p, dict) and p.get("type") == "text"
252
+ ]
253
+ return "".join(texts) or None
254
+ return _str(value) or None
255
+
256
+
257
+ def _openai_messages(attributes: dict[str, Any], key: str) -> tuple[list[EsfMessage], set[str]]:
258
+ raw = try_json(attributes.get(key))
259
+ if not isinstance(raw, list):
260
+ return [], {key}
261
+ out: list[EsfMessage] = []
262
+ for message in raw:
263
+ if not isinstance(message, dict):
264
+ continue
265
+ tool_calls = [
266
+ EsfToolCall(
267
+ id=str(c.get("id") or ""),
268
+ name=str((c.get("function") or {}).get("name") or ""),
269
+ arguments=_arguments((c.get("function") or {}).get("arguments")),
270
+ )
271
+ for c in message.get("tool_calls") or []
272
+ if isinstance(c, dict)
273
+ ]
274
+ out.append(
275
+ EsfMessage(
276
+ role=str(message.get("role") or "assistant"),
277
+ text=_text(message.get("content")),
278
+ tool_calls=tool_calls or None,
279
+ tool_call_id=message.get("tool_call_id"),
280
+ )
281
+ )
282
+ return out, {key}
283
+
284
+
285
+ MESSAGE_FORMATS: dict[
286
+ MessageFormat, Callable[[dict[str, Any], str], tuple[list[EsfMessage], set[str]]]
287
+ ] = {
288
+ "openinference": _openinference_messages,
289
+ "gen_ai": _gen_ai_messages,
290
+ "openai": _openai_messages,
291
+ }
292
+
293
+
294
+ def _int(value: Any) -> int | None:
295
+ if value is None or isinstance(value, bool):
296
+ return None
297
+ if isinstance(value, int | float):
298
+ return int(value)
299
+ if isinstance(value, str) and value.strip().lstrip("-").isdigit():
300
+ return int(value)
301
+ return None
302
+
303
+
304
+ def _float(value: Any) -> float | None:
305
+ if value is None or isinstance(value, bool):
306
+ return None
307
+ if isinstance(value, int | float):
308
+ return float(value)
309
+ if isinstance(value, str):
310
+ try:
311
+ return float(value)
312
+ except ValueError:
313
+ return None
314
+ return None
315
+
316
+
317
+ def _str(value: Any) -> str | None:
318
+ return None if value is None else str(value)
319
+
320
+
321
+ def to_esf(raw: RawSpan, mapping: MappingDocument | None) -> EsfSpan:
322
+ read: set[str] = set()
323
+ attributes = raw.attributes
324
+
325
+ def rule(r: AttributeRule | None) -> Any:
326
+ if r is None:
327
+ return None
328
+ if r.message_format is not None:
329
+ messages, keys = MESSAGE_FORMATS[r.message_format](attributes, r.attribute)
330
+ read.update(keys)
331
+ return messages
332
+ read.add(r.attribute)
333
+ value = attributes.get(r.attribute)
334
+ if r.values is not None:
335
+ value = r.values.get(str(value), "other") if value is not None else None
336
+ if r.json_:
337
+ value = try_json(value)
338
+ return value
339
+
340
+ doc = mapping if mapping is not None else MappingDocument(schema_version="1")
341
+ known = doc.known_attributes
342
+ rule(doc.session_id)
343
+ kind_value = rule(doc.kind)
344
+ kind: EsfKind = kind_value if kind_value in KINDS else "other"
345
+ is_llm = kind == "llm"
346
+ span = EsfSpan(
347
+ span_id=raw.span_id,
348
+ trace_id=raw.trace_id,
349
+ parent_id=raw.parent_span_id,
350
+ name=raw.name,
351
+ kind=kind,
352
+ started_at=raw.start_time,
353
+ ended_at=raw.end_time,
354
+ error=(raw.status_message or "error") if raw.status_code == "ERROR" else None,
355
+ model=_str(rule(doc.model)),
356
+ input_messages=rule(doc.input_messages) if is_llm else None,
357
+ output_messages=rule(doc.output_messages) if is_llm else None,
358
+ input=rule(doc.input) if not is_llm else None,
359
+ output=rule(doc.output) if not is_llm else None,
360
+ tool_name=_str(rule(doc.tool.name)) if kind == "tool" else None,
361
+ tool_call_id=_str(rule(doc.tool.call_id)) if kind in ("tool", "agent") else None,
362
+ agent_name=_str(rule(doc.agent_name)) if kind == "agent" else None,
363
+ prompt_tokens=_int(rule(doc.tokens.prompt)),
364
+ completion_tokens=_int(rule(doc.tokens.completion)),
365
+ cached_tokens=_int(rule(doc.tokens.cached)),
366
+ cache_write_tokens=_int(rule(doc.tokens.cache_write)),
367
+ cost_usd=_float(rule(doc.cost_usd)),
368
+ )
369
+ if is_llm and (system := _instructions(rule(doc.system_instructions))):
370
+ span.input_messages = [EsfMessage(role="system", text=system), *(span.input_messages or [])]
371
+ if kind == "tool":
372
+ if doc.tool.arguments is not None:
373
+ span.input = rule(doc.tool.arguments)
374
+ if doc.tool.result is not None:
375
+ span.output = rule(doc.tool.result)
376
+ sdk_stamped = any(k.startswith("evalkit.") for k in attributes)
377
+ span.unmapped = [
378
+ k
379
+ for k in attributes
380
+ if k not in read
381
+ and not k.startswith("evalkit.")
382
+ and not (sdk_stamped and k == SDK_SESSION_ATTRIBUTE)
383
+ and not any(fnmatchcase(k, pattern) for pattern in known)
384
+ ]
385
+ return span
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import time
6
+ from collections.abc import Awaitable, Callable
7
+ from typing import Any
8
+
9
+ from evalkit.core.traces.sessions import SessionReader, SessionTooLarge
10
+
11
+ DEFAULT_POLL_INTERVAL = 5.0
12
+ LOGGER = logging.getLogger(__name__)
13
+
14
+
15
+ async def poll_trajectory(
16
+ sessions: SessionReader,
17
+ agent: str,
18
+ session_id: str,
19
+ deadline_seconds: float,
20
+ *,
21
+ poll_interval: float = DEFAULT_POLL_INTERVAL,
22
+ clock: Callable[[], float] = time.monotonic,
23
+ sleep: Callable[[float], Awaitable[None]] = asyncio.sleep,
24
+ ) -> tuple[dict[str, Any] | None, str]:
25
+ started = clock()
26
+ while True:
27
+ try:
28
+ resp = await sessions.trajectory(agent, session_id)
29
+ except SessionTooLarge as exc:
30
+ LOGGER.warning("no trajectory for session %s: %s", session_id, exc)
31
+ return None, "unavailable"
32
+ if resp.settled and resp.root_present:
33
+ return resp.trajectory, "valid"
34
+ if clock() - started > deadline_seconds:
35
+ if resp.root_present:
36
+ return resp.trajectory, "partial"
37
+ return None, "unavailable"
38
+ await sleep(poll_interval)
@@ -0,0 +1,21 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class RawSpan(BaseModel):
8
+ trace_id: str
9
+ span_id: str
10
+ parent_span_id: str | None = None
11
+ name: str
12
+ kind: str = "INTERNAL"
13
+ start_time: datetime
14
+ end_time: datetime | None = None
15
+ status_code: str = "UNSET"
16
+ status_message: str | None = None
17
+ attributes: dict[str, Any] = Field(default_factory=dict)
18
+ events: list[dict[str, Any]] = Field(default_factory=list)
19
+ resource: dict[str, Any] = Field(default_factory=dict)
20
+ received_at: datetime | None = None
21
+ session_id_hint: str | None = Field(default=None, exclude=True)
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from datetime import datetime
5
+ from typing import Any, NamedTuple, Protocol
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from evalkit.contracts.esf import EsfSpan, MappingDocument
10
+ from evalkit.core.traces.convert import first_llm_spans, to_atif
11
+ from evalkit.core.traces.mapping import (
12
+ SDK_ROOT_ATTRIBUTE,
13
+ TASK_SESSION_ATTRIBUTE,
14
+ AttributeKeys,
15
+ input_context,
16
+ kind_attributes,
17
+ mapping_for,
18
+ session_id_of,
19
+ to_esf,
20
+ )
21
+ from evalkit.core.traces.raw import RawSpan
22
+ from evalkit.core.traces.settle import root_present, settled
23
+
24
+ LINK_ATTRIBUTES = (SDK_ROOT_ATTRIBUTE, TASK_SESSION_ATTRIBUTE)
25
+
26
+
27
+ class SessionTrajectory(BaseModel):
28
+ settled: bool
29
+ root_present: bool
30
+ span_count: int
31
+ mapping_version: int | None = None
32
+ unmapped_attributes: list[str] = Field(default_factory=list)
33
+ trajectory: dict[str, Any] | None = None
34
+ esf: list[EsfSpan] | None = None
35
+
36
+
37
+ class SessionReader(Protocol):
38
+ async def trajectory(self, agent: str, session_id: str) -> SessionTrajectory: ...
39
+
40
+
41
+ def agent_spans(spans: list[RawSpan]) -> list[RawSpan]:
42
+ linked = [
43
+ s
44
+ for s in spans
45
+ if s.parent_span_id is None
46
+ and s.attributes.get(SDK_ROOT_ATTRIBUTE)
47
+ and s.attributes.get(TASK_SESSION_ATTRIBUTE)
48
+ ]
49
+ if not linked:
50
+ return spans
51
+ linked_traces = {s.trace_id for s in linked}
52
+ others = [s for s in spans if s.trace_id not in linked_traces]
53
+ if others:
54
+ return others
55
+ parents = {s.parent_span_id for s in spans}
56
+ if any(root.span_id in parents for root in linked):
57
+ return spans
58
+ return []
59
+
60
+
61
+ class SessionTooLarge(Exception):
62
+ pass
63
+
64
+
65
+ class LaterLlmSpans(NamedTuple):
66
+ positions: list[int]
67
+ context: AttributeKeys
68
+
69
+
70
+ def probe_attributes(active: MappingDocument | None) -> set[str]:
71
+ return kind_attributes(active) | set(LINK_ATTRIBUTES)
72
+
73
+
74
+ def later_llm_spans(probes: list[RawSpan], active: MappingDocument | None) -> LaterLlmSpans:
75
+ position = {id(span): i for i, span in enumerate(probes)}
76
+ kept = agent_spans(probes)
77
+ mappings = [mapping_for(span.attributes, active) for span in kept]
78
+ esf = [to_esf(span, mapping) for span, mapping in zip(kept, mappings, strict=True)]
79
+ first = {id(span) for span in first_llm_spans(esf, active)}
80
+ later = [
81
+ (position[id(raw)], mapping)
82
+ for raw, mapping, span in zip(kept, mappings, esf, strict=True)
83
+ if span.kind == "llm" and id(span) not in first and mapping is not None
84
+ ]
85
+ docs = list({id(mapping): mapping for _, mapping in later}.values())
86
+ return LaterLlmSpans([i for i, _ in later], input_context(docs, active))
87
+
88
+
89
+ class SessionBuilder:
90
+ def __init__(self, active_mapping: MappingDocument | None):
91
+ self._active_mapping = active_mapping
92
+ self._texts: dict[str, str] = {}
93
+ self._spans: list[tuple[RawSpan, EsfSpan, str | None]] = []
94
+
95
+ def _shared(self, text: str | None) -> str | None:
96
+ return None if text is None else self._texts.setdefault(text, text)
97
+
98
+ def add(self, raw: RawSpan) -> None:
99
+ attributes = raw.attributes
100
+ esf = to_esf(raw, mapping_for(attributes, self._active_mapping))
101
+ for message in [*(esf.input_messages or []), *(esf.output_messages or [])]:
102
+ message.text = self._shared(message.text)
103
+ message.reasoning = self._shared(message.reasoning)
104
+ head = raw.model_copy(
105
+ update={
106
+ "attributes": {k: attributes[k] for k in LINK_ATTRIBUTES if k in attributes},
107
+ "events": [],
108
+ "resource": {},
109
+ }
110
+ )
111
+ self._spans.append((head, esf, session_id_of(attributes, self._active_mapping)))
112
+
113
+ def build(
114
+ self,
115
+ *,
116
+ agent_name: str,
117
+ mapping_version: int | None,
118
+ now: datetime,
119
+ settle_seconds: int,
120
+ include_esf: bool = False,
121
+ ) -> SessionTrajectory:
122
+ kept_heads = {id(head) for head in agent_spans([head for head, _, _ in self._spans])}
123
+ kept = [entry for entry in self._spans if id(entry[0]) in kept_heads]
124
+ self._spans = []
125
+ self._texts = {}
126
+ heads = [head for head, _, _ in kept]
127
+ esf = [span for _, span, _ in kept]
128
+ unmapped = sorted({key for s in esf for key in s.unmapped})
129
+ result = SessionTrajectory(
130
+ settled=settled(heads, now, settle_seconds),
131
+ root_present=root_present(heads),
132
+ span_count=len(heads),
133
+ mapping_version=mapping_version,
134
+ unmapped_attributes=unmapped,
135
+ esf=esf if include_esf else None,
136
+ )
137
+ if not heads:
138
+ return result
139
+ received = [s.received_at for s in heads if s.received_at is not None]
140
+ session_id = next((found for _, _, found in kept if found), None)
141
+ trajectory = to_atif(
142
+ esf,
143
+ self._active_mapping,
144
+ agent_name=agent_name,
145
+ session_id=session_id,
146
+ mapping_version=mapping_version,
147
+ incomplete_ended_at=max(received) if received else now,
148
+ )
149
+ result.trajectory = trajectory.model_dump(exclude_none=True)
150
+ return result
151
+
152
+
153
+ def build_session_trajectory(
154
+ spans: Iterable[RawSpan],
155
+ *,
156
+ agent_name: str,
157
+ active_mapping: MappingDocument | None,
158
+ mapping_version: int | None,
159
+ now: datetime,
160
+ settle_seconds: int,
161
+ include_esf: bool = False,
162
+ ) -> SessionTrajectory:
163
+ builder = SessionBuilder(active_mapping)
164
+ for span in spans:
165
+ builder.add(span)
166
+ return builder.build(
167
+ agent_name=agent_name,
168
+ mapping_version=mapping_version,
169
+ now=now,
170
+ settle_seconds=settle_seconds,
171
+ include_esf=include_esf,
172
+ )
@@ -0,0 +1,14 @@
1
+ from datetime import datetime, timedelta
2
+
3
+ from evalkit.core.traces.raw import RawSpan
4
+
5
+
6
+ def settled(spans: list[RawSpan], now: datetime, settle_seconds: int) -> bool:
7
+ received = [s.received_at for s in spans if s.received_at is not None]
8
+ if not received:
9
+ return False
10
+ return max(received) < now - timedelta(seconds=settle_seconds)
11
+
12
+
13
+ def root_present(spans: list[RawSpan]) -> bool:
14
+ return any(s.parent_span_id is None for s in spans)
@@ -0,0 +1,19 @@
1
+ import httpx
2
+
3
+ from evalkit.contracts.enums import TraceProvider
4
+ from evalkit.core.traces.sources.base import TracePage, TraceSourceClient
5
+ from evalkit.core.traces.sources.phoenix import PhoenixClient
6
+
7
+ __all__ = ["TracePage", "TraceSourceClient", "client_for"]
8
+
9
+
10
+ def client_for(
11
+ provider: TraceProvider,
12
+ url: str,
13
+ api_key: str | None,
14
+ page_traces: int,
15
+ transport: httpx.AsyncBaseTransport | None = None,
16
+ ) -> TraceSourceClient:
17
+ match provider:
18
+ case TraceProvider.PHOENIX:
19
+ return PhoenixClient(url, api_key, page_traces, transport=transport)
@@ -0,0 +1,25 @@
1
+ from collections.abc import AsyncIterator
2
+ from dataclasses import dataclass
3
+ from datetime import datetime
4
+ from typing import Protocol
5
+
6
+ from evalkit.core.traces.raw import RawSpan
7
+
8
+
9
+ @dataclass
10
+ class TracePage:
11
+ traces: list[tuple[str, datetime]]
12
+
13
+ @property
14
+ def last_start(self) -> datetime:
15
+ return self.traces[-1][1]
16
+
17
+
18
+ class TraceSourceClient(Protocol):
19
+ async def session_spans(self, project: str, session_id: str) -> list[RawSpan]: ...
20
+
21
+ def trace_pages(
22
+ self, project: str, since: datetime, until: datetime
23
+ ) -> AsyncIterator[TracePage]: ...
24
+
25
+ def trace_spans(self, project: str, trace_ids: list[str]) -> AsyncIterator[list[RawSpan]]: ...