agentengine-sdk-python 0.8.1__py3-none-any.whl → 0.8.2__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 (411) hide show
  1. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
  2. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
  3. ksadk/a2a/_space_client_events.py +381 -0
  4. ksadk/a2a/event_adapter.py +156 -64
  5. ksadk/a2a/executor.py +179 -63
  6. ksadk/a2a/space_client.py +6 -333
  7. ksadk/a2ui/core.py +222 -53
  8. ksadk/agui/_agent_helpers.py +243 -0
  9. ksadk/agui/a2ui_projection.py +12 -5
  10. ksadk/agui/agent.py +443 -259
  11. ksadk/api/client.py +525 -27
  12. ksadk/builders/code_builder.py +284 -8
  13. ksadk/builders/container_builder.py +8 -1
  14. ksadk/builders/managed_runtime_builder.py +39 -35
  15. ksadk/cli/__init__.py +17 -0
  16. ksadk/cli/cmd_dashboard.py +15 -1
  17. ksadk/cli/cmd_deploy.py +10 -2
  18. ksadk/cli/cmd_eval.py +66 -9
  19. ksadk/cli/cmd_evalset.py +284 -0
  20. ksadk/cli/cmd_files.py +5 -1
  21. ksadk/cli/cmd_hermes.py +1 -126
  22. ksadk/cli/cmd_invoke.py +28 -38
  23. ksadk/cli/cmd_managed_runtime.py +95 -0
  24. ksadk/cli/cmd_observe.py +85 -0
  25. ksadk/cli/cmd_openclaw.py +1 -435
  26. ksadk/cli/cmd_replay.py +28 -4
  27. ksadk/cli/cmd_studio.py +99 -8
  28. ksadk/cli/cmd_web.py +10 -2
  29. ksadk/cli/hermes_env.py +151 -0
  30. ksadk/cli/invoke_payload.py +49 -0
  31. ksadk/cli/openclaw_env.py +458 -0
  32. ksadk/codex/client.py +180 -30
  33. ksadk/codex/runtime.py +428 -326
  34. ksadk/configs/env_registry.py +66 -12
  35. ksadk/configs/env_registry_pcm.py +181 -0
  36. ksadk/configs/env_var_spec.py +18 -0
  37. ksadk/configs/global_config.py +5 -0
  38. ksadk/context_engine/__init__.py +98 -0
  39. ksadk/context_engine/assembler.py +176 -0
  40. ksadk/context_engine/baseline.py +419 -0
  41. ksadk/context_engine/cache_observability.py +228 -0
  42. ksadk/context_engine/capabilities.py +443 -0
  43. ksadk/context_engine/contributors.py +344 -0
  44. ksadk/context_engine/hosted_pipeline.py +417 -0
  45. ksadk/context_engine/models.py +117 -0
  46. ksadk/context_engine/planner.py +698 -0
  47. ksadk/context_engine/policies.py +319 -0
  48. ksadk/context_engine/projection.py +31 -0
  49. ksadk/context_engine/shadow_plan.py +266 -0
  50. ksadk/context_engine/tokenizer.py +164 -0
  51. ksadk/conversations/context.py +126 -6
  52. ksadk/conversations/message_projection.py +292 -28
  53. ksadk/conversations/model_context.py +49 -3
  54. ksadk/conversations/runtime_compaction.py +207 -5
  55. ksadk/conversations/runtime_input.py +184 -2
  56. ksadk/conversations/runtime_invocation.py +81 -3
  57. ksadk/conversations/runtime_observability.py +141 -0
  58. ksadk/conversations/runtime_payloads.py +42 -0
  59. ksadk/conversations/runtime_preparation.py +272 -2
  60. ksadk/conversations/runtime_resume.py +20 -1
  61. ksadk/conversations/runtime_stream_events.py +94 -9
  62. ksadk/conversations/semantic_summary.py +311 -1
  63. ksadk/conversations/session_lock.py +51 -0
  64. ksadk/deployment/env_forward.py +72 -0
  65. ksadk/deployment/managed_runtime.py +5 -0
  66. ksadk/deployment/providers/serverless.py +143 -69
  67. ksadk/evaluation/__init__.py +32 -0
  68. ksadk/evaluation/a2a_adapter.py +9 -1
  69. ksadk/evaluation/adapters.py +18 -5
  70. ksadk/evaluation/agent_eval_client.py +395 -0
  71. ksadk/evaluation/cloud_binding.py +80 -0
  72. ksadk/evaluation/cloud_converter.py +194 -0
  73. ksadk/evaluation/cloud_service.py +199 -0
  74. ksadk/evaluation/contracts.py +73 -4
  75. ksadk/evaluation/evaluators.py +183 -17
  76. ksadk/evaluation/evidence.py +258 -0
  77. ksadk/evaluation/executor.py +31 -6
  78. ksadk/evaluation/local_adapter.py +550 -0
  79. ksadk/evaluation/service_env.py +27 -0
  80. ksadk/evaluation/studio_build_adapter.py +253 -0
  81. ksadk/evaluation/target.py +15 -6
  82. ksadk/events/__init__.py +13 -13
  83. ksadk/events/_v1_compat/__init__.py +1 -0
  84. ksadk/events/_v1_compat/models.py +299 -0
  85. ksadk/events/_v1_compat/parser.py +247 -0
  86. ksadk/events/_v1_compat/projection.py +753 -0
  87. ksadk/events/adapters/__init__.py +34 -0
  88. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  89. ksadk/events/adapters/_a2a_support.py +275 -0
  90. ksadk/events/adapters/_codex_interactions.py +375 -0
  91. ksadk/events/adapters/_codex_items.py +801 -0
  92. ksadk/events/adapters/_codex_validators.py +311 -0
  93. ksadk/events/adapters/_langgraph_support.py +785 -0
  94. ksadk/events/adapters/a2a.py +841 -0
  95. ksadk/events/adapters/adk.py +503 -0
  96. ksadk/events/adapters/codex.py +717 -0
  97. ksadk/events/adapters/langgraph.py +745 -0
  98. ksadk/events/canonical.py +440 -0
  99. ksadk/events/canonical_replay.py +497 -0
  100. ksadk/events/canonical_store.py +468 -0
  101. ksadk/events/cold_recovery.py +257 -0
  102. ksadk/events/content.py +89 -0
  103. ksadk/events/identity.py +86 -0
  104. ksadk/events/pipeline.py +455 -0
  105. ksadk/events/projections.py +66 -0
  106. ksadk/events/reducer.py +661 -0
  107. ksadk/events/replay.py +3 -36
  108. ksadk/events/runtime_event.py +20 -265
  109. ksadk/events/session_event.py +324 -0
  110. ksadk/events/store.py +3 -316
  111. ksadk/events/v1_compat.py +41 -0
  112. ksadk/harness/runtime.py +160 -57
  113. ksadk/identity/resolver.py +4 -3
  114. ksadk/interaction/__init__.py +28 -0
  115. ksadk/interaction/contracts.py +136 -0
  116. ksadk/interaction/ledger.py +189 -0
  117. ksadk/interaction/provider.py +122 -0
  118. ksadk/interaction/providers/__init__.py +52 -0
  119. ksadk/interaction/providers/adk.py +40 -0
  120. ksadk/interaction/providers/codex.py +94 -0
  121. ksadk/interaction/providers/langgraph.py +58 -0
  122. ksadk/kernel/__init__.py +117 -0
  123. ksadk/kernel/authorization.py +258 -0
  124. ksadk/kernel/bootstrap.py +1074 -0
  125. ksadk/kernel/contract_fingerprints.py +61 -0
  126. ksadk/kernel/contracts.py +362 -0
  127. ksadk/kernel/control.py +219 -0
  128. ksadk/kernel/errors.py +79 -0
  129. ksadk/kernel/ingress.py +1102 -0
  130. ksadk/kernel/mapping.py +111 -0
  131. ksadk/kernel/memory_store.py +1133 -0
  132. ksadk/kernel/postgres_store.py +1756 -0
  133. ksadk/kernel/recovery.py +452 -0
  134. ksadk/kernel/runtime_identity.py +103 -0
  135. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  136. ksadk/kernel/sqlite_store.py +1410 -0
  137. ksadk/kernel/state.py +145 -0
  138. ksadk/kernel/store.py +232 -0
  139. ksadk/kernel/worker.py +953 -0
  140. ksadk/knowledge_base/client.py +7 -0
  141. ksadk/knowledge_base/service.py +32 -5
  142. ksadk/memory/__init__.py +61 -0
  143. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  144. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  145. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  146. ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
  147. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  148. ksadk/memory/coordinator.py +461 -0
  149. ksadk/memory/events.py +202 -0
  150. ksadk/memory/extraction.py +228 -0
  151. ksadk/memory/ltm_backend_factory.py +16 -3
  152. ksadk/memory/models.py +316 -0
  153. ksadk/memory/policy.py +215 -0
  154. ksadk/memory/provider.py +47 -0
  155. ksadk/memory/provider_adapter.py +159 -0
  156. ksadk/memory/provider_resolver.py +71 -0
  157. ksadk/memory/providers/__init__.py +5 -0
  158. ksadk/memory/providers/local_sqlite.py +490 -0
  159. ksadk/memory/resolved_policy.py +145 -0
  160. ksadk/memory/service.py +188 -6
  161. ksadk/model_proxy/bootstrap.py +16 -9
  162. ksadk/model_proxy/detect.py +202 -9
  163. ksadk/model_proxy/namespace.py +39 -15
  164. ksadk/model_proxy/server.py +24 -5
  165. ksadk/model_proxy/transform.py +60 -2
  166. ksadk/observability/__init__.py +25 -0
  167. ksadk/observability/session_log.py +420 -0
  168. ksadk/observability/trajectory.py +105 -0
  169. ksadk/prompts/__init__.py +71 -0
  170. ksadk/prompts/compiler.py +215 -0
  171. ksadk/prompts/models.py +82 -0
  172. ksadk/prompts/projection.py +89 -0
  173. ksadk/prompts/resolved.py +137 -0
  174. ksadk/prompts/sources.py +233 -0
  175. ksadk/runners/_langgraph_runner_streams.py +822 -0
  176. ksadk/runners/adk_runner.py +5 -5
  177. ksadk/runners/base_runner.py +16 -0
  178. ksadk/runners/factory.py +1 -2
  179. ksadk/runners/langgraph_runner.py +198 -437
  180. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  181. ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
  182. ksadk/runtime/adapter.py +118 -2
  183. ksadk/runtime/conversation_execution.py +316 -182
  184. ksadk/runtime/executor.py +144 -3
  185. ksadk/runtime/factory.py +193 -7
  186. ksadk/runtime/hosted_finalizer.py +268 -0
  187. ksadk/runtime/launch.py +14 -1
  188. ksadk/runtime/preprocessing.py +25 -3
  189. ksadk/runtime/runner_adapter.py +288 -508
  190. ksadk/runtime/usage.py +33 -0
  191. ksadk/sandbox/backends/e2b.py +35 -2
  192. ksadk/server/composition.py +8 -0
  193. ksadk/server/factory.py +69 -6
  194. ksadk/server/routes/kernel_ingress.py +258 -0
  195. ksadk/server/routes/openai_compat.py +88 -8
  196. ksadk/server/routes/projection.py +35 -20
  197. ksadk/server/routes/run.py +94 -0
  198. ksadk/server/routes/sessions.py +2 -0
  199. ksadk/server/routes/streaming.py +5 -2
  200. ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
  201. ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
  202. ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
  203. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  204. ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
  205. ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
  206. ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
  207. ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
  208. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
  209. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
  210. ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
  211. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
  212. ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
  213. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
  214. ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
  215. ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
  216. ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
  217. ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
  218. ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
  219. ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
  220. ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
  221. ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
  222. ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
  223. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
  224. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
  225. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
  226. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
  227. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
  228. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  229. ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
  230. ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
  231. ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
  232. ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
  233. ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
  234. ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
  235. ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
  236. ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
  237. ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
  238. ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
  239. ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
  240. ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
  241. ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
  242. ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
  243. ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
  244. ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
  245. ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
  246. ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
  247. ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
  248. ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
  249. ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
  250. ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
  251. ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
  252. ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
  253. ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
  254. ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
  255. ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
  256. ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
  257. ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-ngtN0DJB.js} +1 -1
  258. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
  259. ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
  260. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
  261. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
  262. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
  263. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
  264. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
  265. ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
  266. ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
  267. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
  268. ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
  269. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
  270. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
  271. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
  272. ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
  273. ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-BHjG8b-J.js} +1 -1
  274. ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
  275. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
  276. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
  277. ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
  278. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
  279. ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
  280. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
  281. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
  282. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
  283. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
  284. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
  285. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
  286. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
  287. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
  288. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
  289. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
  290. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
  291. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
  292. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
  293. ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
  294. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
  295. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
  296. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
  297. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
  298. ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
  299. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
  300. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
  301. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
  302. ksadk/server/static/index.html +2 -2
  303. ksadk/sessions/__init__.py +26 -9
  304. ksadk/sessions/_local_service_sync.py +688 -0
  305. ksadk/sessions/_local_tables.py +21 -0
  306. ksadk/sessions/_postgres_schema.py +305 -0
  307. ksadk/sessions/_postgres_tables.py +15 -0
  308. ksadk/sessions/base.py +80 -1
  309. ksadk/sessions/in_memory.py +53 -2
  310. ksadk/sessions/local_service.py +63 -604
  311. ksadk/sessions/postgres_service.py +58 -148
  312. ksadk/sessions/resilient.py +35 -1
  313. ksadk/studio/api.py +577 -25
  314. ksadk/studio/api_contracts.py +114 -5
  315. ksadk/studio/api_memory_routes.py +82 -0
  316. ksadk/studio/authoring.py +252 -27
  317. ksadk/studio/authoring_coordinator.py +799 -67
  318. ksadk/studio/builder.py +88 -11
  319. ksadk/studio/capabilities.py +17 -0
  320. ksadk/studio/cloud.py +1916 -95
  321. ksadk/studio/codex_agent_service.py +125 -14
  322. ksadk/studio/codex_authoring.py +469 -0
  323. ksadk/studio/codex_builder.py +138 -25
  324. ksadk/studio/codex_manifest.py +51 -22
  325. ksadk/studio/codex_run.py +46 -3
  326. ksadk/studio/compiler.py +1 -0
  327. ksadk/studio/contracts.py +114 -3
  328. ksadk/studio/event_store.py +25 -95
  329. ksadk/studio/framework_run.py +231 -3
  330. ksadk/studio/hosted_kernel.py +317 -0
  331. ksadk/studio/manifest_resolver.py +131 -0
  332. ksadk/studio/model_client.py +95 -9
  333. ksadk/studio/model_profile_service.py +1 -1
  334. ksadk/studio/operations.py +37 -5
  335. ksadk/studio/otel_trace.py +348 -42
  336. ksadk/studio/pcm_memory.py +85 -0
  337. ksadk/studio/repository.py +23 -2
  338. ksadk/studio/resource_catalog.py +43 -0
  339. ksadk/studio/run_service.py +563 -137
  340. ksadk/studio/runtime_source.py +42 -16
  341. ksadk/studio/service.py +1180 -72
  342. ksadk/studio/shared_web.py +32 -22
  343. ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
  344. ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
  345. ksadk/studio/static/index.html +2 -2
  346. ksadk/studio/templates.py +100 -8
  347. ksadk/studio/validator.py +19 -0
  348. ksadk/studio/workspace.py +30 -32
  349. ksadk/tracing/setup.py +304 -3
  350. ksadk/version.py +1 -1
  351. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  352. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  353. ksadk/events/parser.py +0 -191
  354. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  355. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
  356. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
  357. ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
  358. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
  359. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
  360. ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
  361. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
  362. ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
  363. ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
  364. ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
  365. ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
  366. ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
  367. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
  368. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
  369. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
  370. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  371. ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
  372. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  373. ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
  374. ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
  375. ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
  376. ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
  377. ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
  378. ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
  379. ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
  380. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
  381. ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
  382. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
  383. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
  384. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
  385. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  386. ksadk/server/static/assets/index-R52pJJvg.js +0 -177
  387. ksadk/server/static/assets/index-jJr3_955.css +0 -2
  388. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
  389. ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
  390. ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
  391. ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
  392. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
  393. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
  394. ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
  395. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
  396. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
  397. ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
  398. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
  399. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
  400. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
  401. ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
  402. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
  404. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
  405. ksadk/studio/evaluation.py +0 -192
  406. ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
  407. ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
  408. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
  409. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
  410. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
  411. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,199 @@
1
+ """Cloud EvalSet publication orchestration shared by CLI and Studio."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from pathlib import Path
7
+ from typing import Protocol
8
+
9
+ from pydantic import Field
10
+
11
+ from .cloud_binding import CloudBinding, CloudBindingStore
12
+ from .cloud_converter import (
13
+ CloudDatasetSnapshot,
14
+ evalset_from_dataset_snapshot,
15
+ evalset_to_dataset_snapshot,
16
+ )
17
+ from .contracts import CloudDatasetRef, DataPolicy, EvalSetVersion, EvaluationModel
18
+
19
+
20
+ class CloudEvalSetPreviewError(ValueError):
21
+ """Raised before an EvalSet body is allowed to leave the local process."""
22
+
23
+
24
+ class CloudEvalSetPublishResult(EvaluationModel):
25
+ """Provider acknowledgement for one immutable Dataset snapshot."""
26
+
27
+ dataset_id: str = Field(min_length=1)
28
+ dataset_version: int = Field(ge=1)
29
+ project_id: str | None = None
30
+ schema_hash: str = Field(min_length=64, max_length=64)
31
+ content_digest: str = Field(min_length=64, max_length=64)
32
+ row_count: int = Field(ge=0)
33
+
34
+
35
+ class CloudEvalSetPullResult(EvaluationModel):
36
+ """One validated immutable cloud snapshot and its normalized local form."""
37
+
38
+ snapshot: CloudDatasetSnapshot
39
+ evalset: EvalSetVersion
40
+ cloud_dataset: CloudDatasetRef
41
+
42
+
43
+ class CloudEvalSetCatalogItem(EvaluationModel):
44
+ """One immutable Dataset version exposed by the cloud catalog."""
45
+
46
+ dataset_id: str = Field(min_length=1)
47
+ name: str = Field(min_length=1)
48
+ project_id: str | None = None
49
+ version: int = Field(ge=1)
50
+ schema_hash: str = Field(min_length=64, max_length=64)
51
+ content_digest: str = Field(min_length=64, max_length=64)
52
+ row_count: int = Field(ge=0)
53
+
54
+
55
+ class CloudDatasetClient(Protocol):
56
+ """Minimal provider contract required before a snapshot can be published."""
57
+
58
+ async def publish_snapshot(
59
+ self,
60
+ snapshot: CloudDatasetSnapshot,
61
+ *,
62
+ dataset_id: str | None,
63
+ base_version: int | None,
64
+ idempotency_key: str,
65
+ ) -> CloudEvalSetPublishResult: ...
66
+
67
+ async def read_snapshot(
68
+ self,
69
+ dataset_id: str,
70
+ version: int,
71
+ *,
72
+ project_id: str | None = None,
73
+ ) -> CloudDatasetSnapshot: ...
74
+
75
+ async def list_datasets(
76
+ self,
77
+ *,
78
+ project_id: str | None = None,
79
+ ) -> list[CloudEvalSetCatalogItem]: ...
80
+
81
+
82
+ class CloudEvalSetService:
83
+ """Validate, publish, and bind EvalSets without changing runtime execution."""
84
+
85
+ def __init__(
86
+ self,
87
+ workspace_root: str | Path,
88
+ client: CloudDatasetClient,
89
+ *,
90
+ provider: str = "agent-eval/evalsmith",
91
+ ):
92
+ self.bindings = CloudBindingStore(workspace_root)
93
+ self.client = client
94
+ self.provider = provider
95
+
96
+ def preview(self, evalset: EvalSetVersion, *, data_policy: DataPolicy) -> CloudDatasetSnapshot:
97
+ """Return the exact outgoing snapshot or fail before any network request."""
98
+
99
+ if data_policy is DataPolicy.LOCAL_ONLY:
100
+ raise CloudEvalSetPreviewError("DataPolicy=local_only 禁止上传 EvalSet 正文")
101
+ if data_policy is DataPolicy.METADATA_ONLY:
102
+ raise CloudEvalSetPreviewError(
103
+ "DataPolicy=metadata_only 不能发布包含 Case 正文的 EvalSet"
104
+ )
105
+ if data_policy is not DataPolicy.FULL_TRACE:
106
+ raise CloudEvalSetPreviewError("当前端云评测集发布仅支持显式 DataPolicy=full_trace")
107
+ return evalset_to_dataset_snapshot(evalset)
108
+
109
+ async def publish(
110
+ self,
111
+ evalset: EvalSetVersion,
112
+ *,
113
+ evalset_path: str,
114
+ data_policy: DataPolicy,
115
+ dataset_id: str | None = None,
116
+ idempotency_key: str | None = None,
117
+ ) -> CloudEvalSetPublishResult:
118
+ """Publish a full snapshot to an existing Dataset and advance its binding."""
119
+
120
+ if idempotency_key is not None and not idempotency_key.strip():
121
+ raise ValueError("Idempotency-Key 不能为空")
122
+ snapshot = self.preview(evalset, data_policy=data_policy)
123
+ existing = self.bindings.read(evalset_path)
124
+ requested_dataset_id = str(dataset_id or "").strip() or None
125
+ target_dataset_id = requested_dataset_id or (existing.dataset_id if existing else None)
126
+ if target_dataset_id is None:
127
+ raise ValueError("datasetId is required for the first publish of an EvalSet")
128
+ resolved_idempotency_key = idempotency_key or self._idempotency_key(
129
+ target_dataset_id,
130
+ snapshot.content_digest,
131
+ )
132
+ result = await self.client.publish_snapshot(
133
+ snapshot,
134
+ dataset_id=target_dataset_id,
135
+ base_version=None,
136
+ idempotency_key=resolved_idempotency_key,
137
+ )
138
+ if result.dataset_id != target_dataset_id:
139
+ raise CloudEvalSetPreviewError("云端返回的 datasetId 与目标 Dataset 不一致")
140
+ if result.content_digest != snapshot.content_digest:
141
+ raise CloudEvalSetPreviewError("云端返回的 contentDigest 与本地预检结果不一致")
142
+ if result.schema_hash != snapshot.schema_hash:
143
+ raise CloudEvalSetPreviewError("云端返回的 schemaHash 与本地预检结果不一致")
144
+ if result.row_count != len(snapshot.rows):
145
+ raise CloudEvalSetPreviewError("云端返回的 RowCount 与本地预检结果不一致")
146
+ self.bindings.write(
147
+ CloudBinding(
148
+ evalset_path=evalset_path,
149
+ content_digest=snapshot.content_digest,
150
+ provider=self.provider,
151
+ project_id=result.project_id,
152
+ dataset_id=result.dataset_id,
153
+ dataset_version=result.dataset_version,
154
+ schema_hash=result.schema_hash,
155
+ )
156
+ )
157
+ return result
158
+
159
+ @staticmethod
160
+ def _idempotency_key(dataset_id: str, content_digest: str) -> str:
161
+ identity = f"{dataset_id}:{content_digest}".encode("utf-8")
162
+ return f"ksadk-evalset-{hashlib.sha256(identity).hexdigest()}"
163
+
164
+ async def pull(
165
+ self,
166
+ *,
167
+ dataset_id: str,
168
+ version: int,
169
+ project_id: str | None = None,
170
+ ) -> CloudEvalSetPullResult:
171
+ """Read and validate one immutable Dataset version before execution."""
172
+
173
+ if not dataset_id.strip() or version < 1:
174
+ raise ValueError("datasetId 和 version 必须有效")
175
+ snapshot = await self.client.read_snapshot(
176
+ dataset_id,
177
+ version,
178
+ project_id=project_id,
179
+ )
180
+ evalset = evalset_from_dataset_snapshot(snapshot)
181
+ if evalset.content_digest != snapshot.content_digest:
182
+ raise CloudEvalSetPreviewError("云端 snapshot 的 contentDigest 校验失败")
183
+ reference = CloudDatasetRef(
184
+ provider=self.provider,
185
+ project_id=project_id,
186
+ dataset_id=dataset_id,
187
+ version=version,
188
+ schema_hash=snapshot.schema_hash,
189
+ content_digest=snapshot.content_digest,
190
+ row_count=len(snapshot.rows),
191
+ )
192
+ return CloudEvalSetPullResult(
193
+ snapshot=snapshot,
194
+ evalset=evalset,
195
+ cloud_dataset=reference,
196
+ )
197
+
198
+ async def catalog(self, *, project_id: str | None = None) -> list[CloudEvalSetCatalogItem]:
199
+ return await self.client.list_datasets(project_id=project_id)
@@ -58,8 +58,11 @@ class AssertionType(str, Enum):
58
58
  RUNTIME_MAX_LATENCY_MS = "runtime.maxLatencyMs"
59
59
  RUNTIME_MAX_INPUT_TOKENS = "runtime.maxInputTokens"
60
60
  RUNTIME_MAX_OUTPUT_TOKENS = "runtime.maxOutputTokens"
61
+ RUNTIME_MAX_TOTAL_TOKENS = "runtime.maxTotalTokens"
61
62
  TOOL_CALLED = "tool.called"
62
63
  TOOL_NOT_CALLED = "tool.notCalled"
64
+ TOOL_SUCCEEDED = "tool.succeeded"
65
+ TOOL_SEQUENCE = "tool.sequence"
63
66
 
64
67
 
65
68
  class DataPolicy(str, Enum):
@@ -134,6 +137,13 @@ class AssertionSpec(EvaluationModel):
134
137
  raise ValueError(f"{self.type} 的 value 必须是非负数字")
135
138
  if self.value < 0:
136
139
  raise ValueError(f"{self.type} 的 value 必须是非负数字")
140
+ elif self.type is AssertionType.TOOL_SEQUENCE:
141
+ if (
142
+ not isinstance(self.value, list)
143
+ or not self.value
144
+ or any(not isinstance(item, str) or not item.strip() for item in self.value)
145
+ ):
146
+ raise ValueError("tool.sequence 的 value 必须是非空工具名称数组")
137
147
  elif not isinstance(self.value, str):
138
148
  raise ValueError(f"{self.type} 的 value 必须是字符串")
139
149
  return self
@@ -155,9 +165,21 @@ class EvalCase(EvaluationModel):
155
165
  data = dict(value)
156
166
  if "input" in data and "turns" not in data:
157
167
  turn = {"input": data.pop("input")}
158
- for field in ("expected_output", "expectedOutput", "expected_tools", "expectedTools"):
168
+ for field in (
169
+ "expected_output",
170
+ "expectedOutput",
171
+ "reference_output",
172
+ "referenceOutput",
173
+ "expected_tools",
174
+ "expectedTools",
175
+ ):
159
176
  if field in data:
160
- turn[field] = data.pop(field)
177
+ normalized_field = (
178
+ "expected_output"
179
+ if field in {"reference_output", "referenceOutput"}
180
+ else field
181
+ )
182
+ turn[normalized_field] = data.pop(field)
161
183
  data["turns"] = [turn]
162
184
  return data
163
185
 
@@ -200,6 +222,18 @@ class EvalSetVersion(EvaluationModel):
200
222
  return _content_digest(payload)
201
223
 
202
224
 
225
+ class CloudDatasetRef(EvaluationModel):
226
+ """Immutable reference to the exact cloud Dataset snapshot used by a run."""
227
+
228
+ provider: str = Field(min_length=1, max_length=256)
229
+ project_id: str | None = Field(default=None, min_length=1, max_length=256)
230
+ dataset_id: str = Field(min_length=1, max_length=256)
231
+ version: int = Field(ge=1)
232
+ schema_hash: str = Field(min_length=64, max_length=64)
233
+ content_digest: str = Field(min_length=64, max_length=64)
234
+ row_count: int = Field(default=0, ge=0)
235
+
236
+
203
237
  # ---------------------------------------------------------------------------
204
238
  # Target identity & references
205
239
  # ---------------------------------------------------------------------------
@@ -262,6 +296,7 @@ class EvaluationRequest(EvaluationModel):
262
296
  target: TargetRef
263
297
  config: EvaluationConfig = Field(default_factory=EvaluationConfig)
264
298
  report_dir: str | None = Field(default=None, min_length=1, max_length=2048)
299
+ cloud_dataset: CloudDatasetRef | None = None
265
300
 
266
301
 
267
302
  class EvalRunSpec(EvaluationModel):
@@ -273,6 +308,7 @@ class EvalRunSpec(EvaluationModel):
273
308
  config: EvaluationConfig = Field(default_factory=EvaluationConfig)
274
309
  environment_digest: str = Field(default="", max_length=128)
275
310
  attempt: int = Field(default=1, ge=1)
311
+ cloud_dataset: CloudDatasetRef | None = None
276
312
 
277
313
 
278
314
  # ---------------------------------------------------------------------------
@@ -288,11 +324,25 @@ class TraceRef(EvaluationModel):
288
324
  run_id: str | None = None
289
325
  trace_id: str | None = None
290
326
  root_span_id: str | None = None
327
+ session_id: str | None = None
328
+ invocation_id: str | None = None
329
+ seq_start: int | None = Field(default=None, ge=1)
330
+ seq_end: int | None = Field(default=None, ge=1)
331
+ remote_task_id: str | None = None
291
332
  seq_id: int | None = Field(default=None, ge=1)
292
333
 
293
334
  @model_validator(mode="after")
294
335
  def require_reference(self) -> "TraceRef":
295
- if not any((self.run_id, self.trace_id, self.seq_id)):
336
+ if not any(
337
+ (
338
+ self.run_id,
339
+ self.trace_id,
340
+ self.session_id,
341
+ self.invocation_id,
342
+ self.remote_task_id,
343
+ self.seq_id,
344
+ )
345
+ ):
296
346
  raise ValueError("TraceRef 至少需要一个可查询 ID")
297
347
  return self
298
348
 
@@ -306,6 +356,16 @@ class UsageSnapshot(EvaluationModel):
306
356
  reported: bool = False
307
357
 
308
358
 
359
+ class ToolCallEvidence(EvaluationModel):
360
+ """Non-sensitive projection of one runtime tool invocation."""
361
+
362
+ call_id: str = Field(min_length=1, max_length=256)
363
+ name: str = Field(min_length=1, max_length=256)
364
+ status: Literal["SUCCEEDED", "ERROR", "INCOMPLETE"]
365
+ seq_start: int | None = Field(default=None, ge=1)
366
+ seq_end: int | None = Field(default=None, ge=1)
367
+
368
+
309
369
  class TargetRun(EvaluationModel):
310
370
  """Normalized result returned by a target adapter for one case."""
311
371
 
@@ -316,6 +376,8 @@ class TargetRun(EvaluationModel):
316
376
  error_code: str | None = None
317
377
  error_message: str | None = None
318
378
  trace_ref: TraceRef | None = None
379
+ trace_refs: list[TraceRef] = Field(default_factory=list)
380
+ tool_calls: list[ToolCallEvidence] = Field(default_factory=list)
319
381
  metadata: dict[str, Any] = Field(default_factory=dict)
320
382
 
321
383
 
@@ -377,7 +439,14 @@ class EvalRunReport(EvaluationModel):
377
439
  self.summary = self._summarize_cases()
378
440
  expected = self.compute_digest()
379
441
  if self.report_digest and self.report_digest != expected:
380
- raise ValueError("reportDigest 与规范化报告内容不一致")
442
+ legacy_payload = self.model_dump(
443
+ mode="json", by_alias=False, exclude={"report_digest"}
444
+ )
445
+ legacy_payload["spec"].pop("cloud_dataset", None)
446
+ if self.spec.cloud_dataset is not None or self.report_digest != _content_digest(
447
+ legacy_payload
448
+ ):
449
+ raise ValueError("reportDigest 与规范化报告内容不一致")
381
450
  self.report_digest = expected
382
451
  return self
383
452
 
@@ -49,11 +49,15 @@ class _EvaluatorDefinition:
49
49
 
50
50
  REFERENCE_MATCH_EVALUATOR = "reference_match@v1"
51
51
  LLM_JUDGE_EVALUATOR = "llm_judge@v1"
52
- DEFAULT_EVALUATORS = (
52
+ BUSINESS_STANDARD_EVALUATOR = "business_standard@v1"
53
+ LEGACY_ASSERTION_EVALUATORS = (
53
54
  "response_contract@v1",
54
55
  "runtime_budget@v1",
55
56
  "tool_trajectory@v1",
56
57
  )
58
+ # Kept for external imports only. Empty evaluator selection uses the
59
+ # data-derived automatic plan in _automatic_evaluator_names instead.
60
+ DEFAULT_EVALUATORS = LEGACY_ASSERTION_EVALUATORS
57
61
  _RESPONSE_MATCH_THRESHOLD = 0.8
58
62
  _TOKEN_PATTERN = re.compile(r"[A-Za-z0-9_]+|[\u4e00-\u9fff]")
59
63
 
@@ -67,7 +71,7 @@ def evaluate_case(
67
71
  """Run selected evaluators in request order."""
68
72
 
69
73
  context = _EvaluationContext(case, target_run, config or EvaluationConfig())
70
- evaluators = _resolve_evaluators(evaluator_names)
74
+ evaluators = _resolve_evaluators(evaluator_names, context)
71
75
  return _run_evaluators(context, evaluators)
72
76
 
73
77
 
@@ -82,14 +86,58 @@ async def evaluate_case_async(
82
86
  return await asyncio.to_thread(evaluate_case, case, target_run, evaluator_names, config)
83
87
 
84
88
 
85
- def _resolve_evaluators(evaluator_names: list[str]) -> list[_EvaluatorDefinition]:
86
- selected_names = evaluator_names or DEFAULT_EVALUATORS
89
+ def _resolve_evaluators(
90
+ evaluator_names: list[str], context: _EvaluationContext
91
+ ) -> list[_EvaluatorDefinition]:
92
+ selected_names = evaluator_names or _automatic_evaluator_names(context.case, context.config)
87
93
  unsupported_names = [name for name in selected_names if name not in _EVALUATOR_REGISTRY]
88
94
  if unsupported_names:
89
95
  raise ValueError(f"不支持的评估器: {', '.join(unsupported_names)}")
90
96
  return [_EVALUATOR_REGISTRY[name] for name in selected_names]
91
97
 
92
98
 
99
+ def resolve_evaluator_plan(
100
+ cases: list[EvalCase],
101
+ evaluator_names: list[str],
102
+ config: EvaluationConfig,
103
+ ) -> list[str]:
104
+ """Return the explicit or data-derived evaluator plan for an EvalSet."""
105
+
106
+ if evaluator_names:
107
+ unsupported_names = [name for name in evaluator_names if name not in _EVALUATOR_REGISTRY]
108
+ if unsupported_names:
109
+ raise ValueError(f"不支持的评估器: {', '.join(unsupported_names)}")
110
+ return list(evaluator_names)
111
+
112
+ plan: list[str] = []
113
+ for case in cases:
114
+ for evaluator_name in _automatic_evaluator_names(case, config):
115
+ if evaluator_name not in plan:
116
+ plan.append(evaluator_name)
117
+ return plan
118
+
119
+
120
+ def _automatic_evaluator_names(case: EvalCase, config: EvaluationConfig) -> list[str]:
121
+ """Select only the evaluators whose business standard is present in a Case."""
122
+
123
+ names: list[str] = []
124
+ if _response_assertions(case):
125
+ names.append("response_contract@v1")
126
+ if _runtime_assertions(case):
127
+ names.append("runtime_budget@v1")
128
+ if _tool_requirements(case):
129
+ names.append("tool_trajectory@v1")
130
+
131
+ if _final_expected_output(case):
132
+ if _judge_unavailable_reason(config) is None:
133
+ names.insert(0, LLM_JUDGE_EVALUATOR)
134
+ else:
135
+ names.insert(0, REFERENCE_MATCH_EVALUATOR)
136
+ elif not _response_assertions(case):
137
+ names.insert(0, BUSINESS_STANDARD_EVALUATOR)
138
+ return names
139
+
140
+
93
141
  def _run_evaluators(
94
142
  context: _EvaluationContext,
95
143
  evaluators: list[_EvaluatorDefinition],
@@ -159,6 +207,21 @@ def _evaluate_llm_judge(
159
207
  return [_judge_score_metric(score, context.config.judge_model)]
160
208
 
161
209
 
210
+ def _evaluate_business_standard(context: _EvaluationContext) -> list[MetricResult]:
211
+ """Prevent execution-only Cases from being reported as business-quality passes."""
212
+
213
+ return [
214
+ MetricResult(
215
+ name="response_quality",
216
+ status=MetricStatus.UNAVAILABLE,
217
+ evidence={
218
+ "evaluator": BUSINESS_STANDARD_EVALUATOR,
219
+ "reason": "Case 未提供响应业务标准",
220
+ },
221
+ )
222
+ ]
223
+
224
+
162
225
  def _evaluate_response_contract(
163
226
  context: _EvaluationContext,
164
227
  ) -> list[MetricResult]:
@@ -184,24 +247,102 @@ def _evaluate_runtime_budget(
184
247
  def _evaluate_tool_trajectory(
185
248
  context: _EvaluationContext,
186
249
  ) -> list[MetricResult]:
187
- """Report unavailable until A2A exposes normalized tool trajectories."""
250
+ """Evaluate tool requirements against normalized RuntimeEvent evidence."""
188
251
 
189
252
  requirements = _tool_requirements(context.case)
190
253
  if not requirements:
191
254
  return []
192
255
 
193
- return [
194
- MetricResult(
256
+ if context.target_run.trace_ref is None:
257
+ return [
258
+ MetricResult(
259
+ name="tool_trajectory",
260
+ status=MetricStatus.UNAVAILABLE,
261
+ required=required,
262
+ evidence={
263
+ "assertion": assertion_type,
264
+ "tool": expected,
265
+ "reason": "Target 未提供可查询的标准化工具轨迹",
266
+ },
267
+ )
268
+ for assertion_type, expected, required in requirements
269
+ ]
270
+
271
+ results: list[MetricResult] = []
272
+ for assertion_type, expected, required in requirements:
273
+ results.append(
274
+ _tool_metric(
275
+ assertion_type,
276
+ expected,
277
+ required,
278
+ context.target_run.tool_calls,
279
+ )
280
+ )
281
+ return results
282
+
283
+
284
+ def _tool_metric(
285
+ assertion_type: str,
286
+ expected: str | list[str],
287
+ required: bool,
288
+ tool_calls: list[Any],
289
+ ) -> MetricResult:
290
+ if assertion_type == AssertionType.TOOL_SEQUENCE.value:
291
+ expected_sequence = list(expected) if isinstance(expected, list) else []
292
+ ordered_calls = sorted(
293
+ (call for call in tool_calls if call.status == "SUCCEEDED"),
294
+ key=lambda call: call.seq_start if call.seq_start is not None else float("inf"),
295
+ )
296
+ actual_sequence = [call.name for call in ordered_calls]
297
+ matched_calls = _ordered_tool_subsequence(ordered_calls, expected_sequence)
298
+ passed = len(matched_calls) == len(expected_sequence)
299
+ return MetricResult(
195
300
  name="tool_trajectory",
196
- status=MetricStatus.UNAVAILABLE,
301
+ status=MetricStatus.PASS if passed else MetricStatus.FAIL,
302
+ score=1.0 if passed else 0.0,
197
303
  required=required,
198
304
  evidence={
199
305
  "assertion": assertion_type,
200
- "reason": "A2A target 未提供标准化工具轨迹",
306
+ "expectedSequence": expected_sequence,
307
+ "actualSequence": actual_sequence,
308
+ "matchedCallIds": matched_calls,
201
309
  },
202
310
  )
203
- for assertion_type, required in requirements
204
- ]
311
+
312
+ tool_name = str(expected)
313
+ matched = [call for call in tool_calls if call.name == tool_name]
314
+ if assertion_type == AssertionType.TOOL_NOT_CALLED.value:
315
+ passed = not matched
316
+ matched_ids = [call.call_id for call in matched]
317
+ elif assertion_type == AssertionType.TOOL_SUCCEEDED.value:
318
+ matched_ids = [call.call_id for call in matched if call.status == "SUCCEEDED"]
319
+ passed = bool(matched_ids)
320
+ else:
321
+ matched_ids = [call.call_id for call in matched]
322
+ passed = bool(matched_ids)
323
+ return MetricResult(
324
+ name="tool_trajectory",
325
+ status=MetricStatus.PASS if passed else MetricStatus.FAIL,
326
+ score=1.0 if passed else 0.0,
327
+ required=required,
328
+ evidence={
329
+ "assertion": assertion_type,
330
+ "tool": tool_name,
331
+ "matchedCallIds": matched_ids,
332
+ },
333
+ )
334
+
335
+
336
+ def _ordered_tool_subsequence(tool_calls: list[Any], expected_sequence: list[str]) -> list[str]:
337
+ matched_call_ids: list[str] = []
338
+ expected_index = 0
339
+ for call in tool_calls:
340
+ if expected_index == len(expected_sequence):
341
+ break
342
+ if call.name == expected_sequence[expected_index]:
343
+ matched_call_ids.append(call.call_id)
344
+ expected_index += 1
345
+ return matched_call_ids
205
346
 
206
347
 
207
348
  def _response_assertions(case: EvalCase) -> list[AssertionSpec]:
@@ -220,16 +361,37 @@ def _runtime_assertions(case: EvalCase) -> list[AssertionSpec]:
220
361
  ]
221
362
 
222
363
 
223
- def _tool_requirements(case: EvalCase) -> list[tuple[str, bool]]:
364
+ def _tool_requirements(case: EvalCase) -> list[tuple[str, str | list[str], bool]]:
224
365
  requirements = [
225
- (assertion.type.value, assertion.required)
366
+ (
367
+ assertion.type.value,
368
+ assertion.value,
369
+ assertion.required,
370
+ )
226
371
  for assertion in case.assertions
227
- if assertion.type in {AssertionType.TOOL_CALLED, AssertionType.TOOL_NOT_CALLED}
372
+ if assertion.type
373
+ in {
374
+ AssertionType.TOOL_CALLED,
375
+ AssertionType.TOOL_NOT_CALLED,
376
+ AssertionType.TOOL_SUCCEEDED,
377
+ AssertionType.TOOL_SEQUENCE,
378
+ }
228
379
  ]
229
- requirements.extend(("tool.expected", True) for turn in case.turns for _ in turn.expected_tools)
380
+ requirements.extend(
381
+ ("tool.expected", str(tool.get("name") or ""), True)
382
+ for turn in case.turns
383
+ for tool in turn.expected_tools
384
+ if str(tool.get("name") or "").strip()
385
+ )
230
386
  return requirements
231
387
 
232
388
 
389
+ def evaluate_tool_trajectory(case: EvalCase, target_run: TargetRun) -> list[MetricResult]:
390
+ """Compatibility entry point for direct deterministic tool evaluation."""
391
+
392
+ return _evaluate_tool_trajectory(_EvaluationContext(case, target_run, EvaluationConfig()))
393
+
394
+
233
395
  def _response_metric(assertion: AssertionSpec, output: str) -> MetricResult:
234
396
  reason = ""
235
397
  if assertion.type is AssertionType.RESPONSE_EQUALS:
@@ -262,6 +424,8 @@ def _runtime_metric(assertion: AssertionSpec, target_run: TargetRun) -> MetricRe
262
424
  actual = None
263
425
  elif assertion.type is AssertionType.RUNTIME_MAX_INPUT_TOKENS:
264
426
  actual = target_run.usage.input_tokens
427
+ elif assertion.type is AssertionType.RUNTIME_MAX_TOTAL_TOKENS:
428
+ actual = target_run.usage.total_tokens
265
429
  else:
266
430
  actual = target_run.usage.output_tokens
267
431
 
@@ -429,8 +593,7 @@ def _run_llm_judge(
429
593
  metric = GEval(
430
594
  name="Response quality",
431
595
  criteria=(
432
- "Determine whether the actual output is factually correct "
433
- "based on the expected output."
596
+ "Determine whether the actual output is factually correct based on the expected output."
434
597
  ),
435
598
  evaluation_params=[
436
599
  LLMTestCaseParams.INPUT,
@@ -451,6 +614,9 @@ def _run_llm_judge(
451
614
 
452
615
 
453
616
  _EVALUATORS = (
617
+ _EvaluatorDefinition(
618
+ BUSINESS_STANDARD_EVALUATOR, "response_quality", _evaluate_business_standard
619
+ ),
454
620
  _EvaluatorDefinition("response_contract@v1", "response_contract", _evaluate_response_contract),
455
621
  _EvaluatorDefinition("runtime_budget@v1", "runtime_budget", _evaluate_runtime_budget),
456
622
  _EvaluatorDefinition("tool_trajectory@v1", "tool_trajectory", _evaluate_tool_trajectory),