agentengine-sdk-python 0.6.4__py3-none-any.whl → 0.7.0__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 (486) hide show
  1. agentengine_sdk_python-0.7.0.dist-info/METADATA +181 -0
  2. agentengine_sdk_python-0.7.0.dist-info/RECORD +482 -0
  3. {agentengine_sdk_python-0.6.4.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +1 -1
  4. agentengine_sdk_python-0.7.0.dist-info/entry_points.txt +3 -0
  5. agentengine_sdk_python-0.7.0.dist-info/licenses/LICENSE +175 -0
  6. agentengine_sdk_python-0.7.0.dist-info/top_level.txt +2 -0
  7. ksadk/__init__.py +13 -0
  8. ksadk/__main__.py +4 -0
  9. ksadk/a2a/__init__.py +15 -0
  10. ksadk/a2a/card_builder.py +64 -0
  11. ksadk/a2a/client.py +309 -0
  12. ksadk/a2a/executor.py +201 -0
  13. ksadk/a2a/server.py +79 -0
  14. ksadk/agents/__init__.py +20 -0
  15. ksadk/agents/base.py +370 -0
  16. ksadk/agents/context.py +38 -0
  17. ksadk/agents/event.py +35 -0
  18. ksadk/agents/loop_agent.py +71 -0
  19. ksadk/agents/parallel_agent.py +109 -0
  20. ksadk/agents/runner_adapter.py +24 -0
  21. ksadk/agents/sequential_agent.py +34 -0
  22. ksadk/api/__init__.py +3 -0
  23. ksadk/api/client.py +2246 -0
  24. ksadk/builders/__init__.py +20 -0
  25. ksadk/builders/base.py +66 -0
  26. ksadk/builders/code_builder.py +1990 -0
  27. ksadk/builders/container_builder.py +786 -0
  28. ksadk/builders/framework_requirements.py +59 -0
  29. ksadk/builders/ks3_uploader.py +389 -0
  30. ksadk/builders/mcp_builder.py +476 -0
  31. ksadk/builders/requirements_utils.py +75 -0
  32. ksadk/cli/__init__.py +531 -0
  33. ksadk/cli/__main__.py +4 -0
  34. ksadk/cli/agent_ref.py +204 -0
  35. ksadk/cli/cmd_a2a.py +167 -0
  36. ksadk/cli/cmd_agent.py +201 -0
  37. ksadk/cli/cmd_build.py +270 -0
  38. ksadk/cli/cmd_completion.py +304 -0
  39. ksadk/cli/cmd_config.py +758 -0
  40. ksadk/cli/cmd_create.py +2304 -0
  41. ksadk/cli/cmd_dashboard.py +900 -0
  42. ksadk/cli/cmd_deploy.py +704 -0
  43. ksadk/cli/cmd_destroy.py +345 -0
  44. ksadk/cli/cmd_files.py +1227 -0
  45. ksadk/cli/cmd_hermes.py +1263 -0
  46. ksadk/cli/cmd_invoke.py +1503 -0
  47. ksadk/cli/cmd_launch.py +564 -0
  48. ksadk/cli/cmd_mcp.py +1179 -0
  49. ksadk/cli/cmd_model.py +302 -0
  50. ksadk/cli/cmd_openclaw.py +3922 -0
  51. ksadk/cli/cmd_run.py +239 -0
  52. ksadk/cli/cmd_status.py +588 -0
  53. ksadk/cli/cmd_version.py +522 -0
  54. ksadk/cli/cmd_web.py +288 -0
  55. ksadk/cli/deploy_utils.py +87 -0
  56. ksadk/cli/dry_run.py +214 -0
  57. ksadk/cli/env_options.py +82 -0
  58. ksadk/cli/error_utils.py +525 -0
  59. ksadk/cli/global_options.py +31 -0
  60. ksadk/cli/local_runtime.py +97 -0
  61. ksadk/cli/model_catalog.py +136 -0
  62. ksadk/cli/network_options.py +412 -0
  63. ksadk/cli/resource_common.py +621 -0
  64. ksadk/cli/storage.py +84 -0
  65. ksadk/cli/ui.py +431 -0
  66. ksadk/cli/workflow_common.py +552 -0
  67. ksadk/common/__init__.py +14 -0
  68. ksadk/common/aicp_env.py +128 -0
  69. ksadk/common/auth.py +190 -0
  70. ksadk/common/constants.py +84 -0
  71. ksadk/common/llm_utils.py +61 -0
  72. ksadk/configs/__init__.py +61 -0
  73. ksadk/configs/env_registry.py +343 -0
  74. ksadk/configs/global_config.py +200 -0
  75. ksadk/configs/settings.py +706 -0
  76. ksadk/conversations/__init__.py +95 -0
  77. ksadk/conversations/attachment_storage.py +379 -0
  78. ksadk/conversations/attachments.py +774 -0
  79. ksadk/conversations/compaction_pipeline.py +387 -0
  80. ksadk/conversations/compaction_prompt.py +128 -0
  81. ksadk/conversations/context.py +303 -0
  82. ksadk/conversations/message_projection.py +269 -0
  83. ksadk/conversations/model_context.py +370 -0
  84. ksadk/conversations/model_options.py +136 -0
  85. ksadk/conversations/normalize.py +446 -0
  86. ksadk/conversations/reasoning_markup.py +91 -0
  87. ksadk/conversations/run_kinds.py +61 -0
  88. ksadk/conversations/run_status.py +53 -0
  89. ksadk/conversations/runtime.py +5276 -0
  90. ksadk/conversations/semantic_summary.py +359 -0
  91. ksadk/conversations/session_title.py +254 -0
  92. ksadk/deployment/__init__.py +83 -0
  93. ksadk/deployment/agent_access.py +226 -0
  94. ksadk/deployment/base.py +229 -0
  95. ksadk/deployment/manager.py +332 -0
  96. ksadk/deployment/providers/__init__.py +10 -0
  97. ksadk/deployment/providers/serverless.py +1115 -0
  98. ksadk/deployment/registry.py +120 -0
  99. ksadk/deployment/state.py +78 -0
  100. ksadk/deployment/ui_config.py +21 -0
  101. ksadk/detection/__init__.py +7 -0
  102. ksadk/detection/detector.py +431 -0
  103. ksadk/detection/mcp_detector.py +250 -0
  104. ksadk/hermes_terminal.py +467 -0
  105. ksadk/identity/__init__.py +17 -0
  106. ksadk/identity/resolver.py +385 -0
  107. ksadk/knowledge_base/__init__.py +62 -0
  108. ksadk/knowledge_base/adk_tool.py +47 -0
  109. ksadk/knowledge_base/client.py +297 -0
  110. ksadk/knowledge_base/langchain_tool.py +55 -0
  111. ksadk/knowledge_base/service.py +66 -0
  112. ksadk/knowledge_base/tool.py +50 -0
  113. ksadk/markdown.py +169 -0
  114. ksadk/mcp_runtime/__init__.py +231 -0
  115. ksadk/memory/__init__.py +57 -0
  116. ksadk/memory/adk/__init__.py +51 -0
  117. ksadk/memory/adk/backends/__init__.py +5 -0
  118. ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
  119. ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
  120. ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
  121. ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
  122. ksadk/memory/adk/long_term_memory.py +273 -0
  123. ksadk/memory/adk/resilient_session_service.py +268 -0
  124. ksadk/memory/adk/short_term_memory.py +304 -0
  125. ksadk/memory/adk_tool.py +31 -0
  126. ksadk/memory/backends/__init__.py +6 -0
  127. ksadk/memory/backends/base.py +103 -0
  128. ksadk/memory/backends/memory.py +112 -0
  129. ksadk/memory/backends/redis.py +146 -0
  130. ksadk/memory/langchain_tool.py +39 -0
  131. ksadk/memory/ltm_backend_factory.py +24 -0
  132. ksadk/memory/manager.py +215 -0
  133. ksadk/memory/service.py +172 -0
  134. ksadk/memory/tool.py +96 -0
  135. ksadk/model_policy.py +223 -0
  136. ksadk/openclaw_gateway.py +378 -0
  137. ksadk/runners/__init__.py +8 -0
  138. ksadk/runners/adk_runner.py +1994 -0
  139. ksadk/runners/base_runner.py +305 -0
  140. ksadk/runners/deepagents_runner.py +13 -0
  141. ksadk/runners/factory.py +73 -0
  142. ksadk/runners/langchain_runner.py +751 -0
  143. ksadk/runners/langgraph_runner.py +985 -0
  144. ksadk/runners/patch_langchain.py +187 -0
  145. ksadk/runners/remote_runner.py +792 -0
  146. ksadk/runners/usage_accumulator.py +38 -0
  147. ksadk/runners/utils/__init__.py +19 -0
  148. ksadk/runners/utils/langfuse.py +230 -0
  149. ksadk/runners/utils/loader.py +62 -0
  150. ksadk/runtime_context.py +166 -0
  151. ksadk/runtime_state.py +25 -0
  152. ksadk/sandbox/__init__.py +28 -0
  153. ksadk/sandbox/backends/__init__.py +9 -0
  154. ksadk/sandbox/backends/e2b.py +154 -0
  155. ksadk/sandbox/backends/local_process.py +131 -0
  156. ksadk/sandbox/base.py +101 -0
  157. ksadk/sandbox/factory.py +53 -0
  158. ksadk/sandbox/registry.py +187 -0
  159. ksadk/server/__init__.py +7 -0
  160. ksadk/server/api_models.py +73 -0
  161. ksadk/server/app.py +3931 -0
  162. ksadk/server/static/assets/ArtifactsPanel-D-SSn2ee.js +1 -0
  163. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +9 -0
  164. ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  165. ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  166. ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  167. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  168. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  169. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  170. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  171. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  172. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  173. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  174. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  175. ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  176. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  177. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  178. ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  179. ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  180. ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  181. ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  182. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  183. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  184. ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  185. ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  186. ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  187. ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  188. ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  189. ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  190. ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  191. ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  192. ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  193. ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  194. ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  195. ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  196. ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  197. ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  198. ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  199. ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  200. ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  201. ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  202. ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  203. ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  204. ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  205. ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  206. ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  207. ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  208. ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  209. ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  210. ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  211. ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  212. ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  213. ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  214. ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  215. ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  216. ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  217. ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  218. ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  219. ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  220. ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  221. ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  222. ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  223. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +8 -0
  224. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
  225. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +303 -0
  226. ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
  227. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +1 -0
  228. ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
  229. ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
  230. ksadk/server/static/assets/arc-BA13sHc9.js +1 -0
  231. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
  232. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-DzJzgVoL.js +36 -0
  233. ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
  234. ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
  235. ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
  236. ksadk/server/static/assets/blockDiagram-GPEHLZMM-UTdVcZhn.js +132 -0
  237. ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
  238. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dix0GD4m.js +10 -0
  239. ksadk/server/static/assets/channel-CTuydARX.js +1 -0
  240. ksadk/server/static/assets/chunk-2J33WTMH-C2JxuZ42.js +1 -0
  241. ksadk/server/static/assets/chunk-4BX2VUAB-Be57QPgD.js +1 -0
  242. ksadk/server/static/assets/chunk-55IACEB6-WjSuy4nv.js +1 -0
  243. ksadk/server/static/assets/chunk-727SXJPM-BOovP-b5.js +206 -0
  244. ksadk/server/static/assets/chunk-AQP2D5EJ-DOmZNmWj.js +231 -0
  245. ksadk/server/static/assets/chunk-FMBD7UC4-DzwlodkU.js +15 -0
  246. ksadk/server/static/assets/chunk-ND2GUHAM-DMqAikEU.js +1 -0
  247. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
  248. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
  249. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
  250. ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
  251. ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
  252. ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
  253. ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
  254. ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
  255. ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
  256. ksadk/server/static/assets/cose-bilkent-S5V4N54A-Zhy2GBnP.js +1 -0
  257. ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
  258. ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
  259. ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
  260. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
  261. ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
  262. ksadk/server/static/assets/dagre-BM42HDAG-CbKrtIUz.js +4 -0
  263. ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
  264. ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
  265. ksadk/server/static/assets/diagram-2AECGRRQ-BRApgQRK.js +43 -0
  266. ksadk/server/static/assets/diagram-5GNKFQAL-D0xbnDiK.js +10 -0
  267. ksadk/server/static/assets/diagram-KO2AKTUF-CVugVI9L.js +3 -0
  268. ksadk/server/static/assets/diagram-LMA3HP47-DozUGgee.js +24 -0
  269. ksadk/server/static/assets/diagram-OG6HWLK6-ZOYsKlru.js +24 -0
  270. ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
  271. ksadk/server/static/assets/dist-2hInLS_J.js +9 -0
  272. ksadk/server/static/assets/dist-8EEyGgDx.js +1 -0
  273. ksadk/server/static/assets/dist-8_SfK1IP.js +1 -0
  274. ksadk/server/static/assets/dist-B0NP_Z0z.js +23 -0
  275. ksadk/server/static/assets/dist-BIJVDMuB.js +1 -0
  276. ksadk/server/static/assets/dist-BmlMPsCc.js +2 -0
  277. ksadk/server/static/assets/dist-BrCcQ47T.js +1 -0
  278. ksadk/server/static/assets/dist-BssVmjIo.js +6 -0
  279. ksadk/server/static/assets/dist-C3HIIaN5.js +1 -0
  280. ksadk/server/static/assets/dist-CPBtqRTW.js +1 -0
  281. ksadk/server/static/assets/dist-CkEKJFtk.js +1 -0
  282. ksadk/server/static/assets/dist-D-JMdWPQ.js +1 -0
  283. ksadk/server/static/assets/dist-D-xOD048.js +1 -0
  284. ksadk/server/static/assets/dist-D57wnGWd.js +1 -0
  285. ksadk/server/static/assets/dist-DL_NKc_z.js +1 -0
  286. ksadk/server/static/assets/dist-DMezvBGJ.js +1 -0
  287. ksadk/server/static/assets/dist-DmFaq_Iy.js +1 -0
  288. ksadk/server/static/assets/dist-Dp0k7bWI.js +1 -0
  289. ksadk/server/static/assets/dist-DpKtt4Bo.js +1 -0
  290. ksadk/server/static/assets/dist-DyOkZmze.js +11 -0
  291. ksadk/server/static/assets/dist-DyWsb78Q.js +1 -0
  292. ksadk/server/static/assets/dist-gX6GOLKq.js +1 -0
  293. ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
  294. ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
  295. ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
  296. ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
  297. ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
  298. ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
  299. ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
  300. ksadk/server/static/assets/erDiagram-TEJ5UH35-Bk-9bgE5.js +85 -0
  301. ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
  302. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
  303. ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
  304. ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
  305. ksadk/server/static/assets/flowDiagram-I6XJVG4X-Dn9kZVuZ.js +162 -0
  306. ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
  307. ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
  308. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BGxXlJqm.js +292 -0
  309. ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
  310. ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
  311. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
  312. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-C7Q0X-wi.js +106 -0
  313. ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
  314. ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
  315. ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
  316. ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
  317. ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
  318. ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
  319. ksadk/server/static/assets/index-C00Lwg3L.js +165 -0
  320. ksadk/server/static/assets/index-DAURbCsP.css +1 -0
  321. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
  322. ksadk/server/static/assets/infoDiagram-5YYISTIA-BO_CSqq5.js +2 -0
  323. ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
  324. ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-eYLPLd46.js +70 -0
  325. ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
  326. ksadk/server/static/assets/journeyDiagram-JHISSGLW-De9geSb0.js +139 -0
  327. ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
  328. ksadk/server/static/assets/kanban-definition-UN3LZRKU-Cs80kQto.js +89 -0
  329. ksadk/server/static/assets/katex-DSrCFD8n.js +265 -0
  330. ksadk/server/static/assets/linear-CN9sbKTF.js +1 -0
  331. ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
  332. ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
  333. ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
  334. ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
  335. ksadk/server/static/assets/mermaid-parser.core-DlgSBU8l.js +161 -0
  336. ksadk/server/static/assets/mindmap-definition-RKZ34NQL-9afz7Oug.js +96 -0
  337. ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
  338. ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
  339. ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
  340. ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
  341. ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
  342. ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
  343. ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
  344. ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
  345. ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
  346. ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
  347. ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
  348. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
  349. ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
  350. ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
  351. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
  352. ksadk/server/static/assets/pieDiagram-4H26LBE5-DPv_VstN.js +30 -0
  353. ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
  354. ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
  355. ksadk/server/static/assets/properties-C357ku5U.js +1 -0
  356. ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
  357. ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
  358. ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
  359. ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
  360. ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
  361. ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-o77wZ1XK.js +7 -0
  362. ksadk/server/static/assets/r-DADKenSm.js +1 -0
  363. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
  364. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-BYVw81VC.js +84 -0
  365. ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
  366. ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
  367. ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-B7nZFMKT.js +40 -0
  368. ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
  369. ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
  370. ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-BGRRrFw5.js +162 -0
  371. ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
  372. ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
  373. ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
  374. ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
  375. ksadk/server/static/assets/solr-BBopId9w.js +1 -0
  376. ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
  377. ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
  378. ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
  379. ksadk/server/static/assets/stateDiagram-AJRCARHV-BUfHCoPs.js +1 -0
  380. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
  381. ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
  382. ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
  383. ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
  384. ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
  385. ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
  386. ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
  387. ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
  388. ksadk/server/static/assets/timeline-definition-PNZ67QCA-FbCTGrf9.js +120 -0
  389. ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
  390. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
  391. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
  392. ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
  393. ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
  394. ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
  395. ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
  396. ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
  397. ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
  398. ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
  399. ksadk/server/static/assets/vennDiagram-CIIHVFJN-DWgwtPsJ.js +34 -0
  400. ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
  401. ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
  402. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-2x8G-tTM.js +78 -0
  404. ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
  405. ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
  406. ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
  407. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-Drma3AYl.js +7 -0
  408. ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
  409. ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
  410. ksadk/server/static/favicon.svg +1 -0
  411. ksadk/server/static/icons.svg +24 -0
  412. ksadk/server/static/index.html +14 -0
  413. ksadk/server/terminal_sessions.py +551 -0
  414. ksadk/sessions/__init__.py +279 -0
  415. ksadk/sessions/base.py +354 -0
  416. ksadk/sessions/continuity.py +337 -0
  417. ksadk/sessions/errors.py +7 -0
  418. ksadk/sessions/in_memory.py +273 -0
  419. ksadk/sessions/local_service.py +949 -0
  420. ksadk/sessions/postgres_service.py +901 -0
  421. ksadk/sessions/resilience.py +52 -0
  422. ksadk/sessions/resilient.py +360 -0
  423. ksadk/sessions/sqlite_service.py +13 -0
  424. ksadk/skills/__init__.py +9 -0
  425. ksadk/skills/loader.py +46 -0
  426. ksadk/skills/models.py +120 -0
  427. ksadk/skills/package_store.py +104 -0
  428. ksadk/skills/runtime/__init__.py +17 -0
  429. ksadk/skills/runtime/agent.py +183 -0
  430. ksadk/skills/runtime/artifacts.py +39 -0
  431. ksadk/skills/runtime/backends/__init__.py +1 -0
  432. ksadk/skills/runtime/backends/disabled.py +8 -0
  433. ksadk/skills/runtime/backends/e2b.py +168 -0
  434. ksadk/skills/runtime/backends/local.py +95 -0
  435. ksadk/skills/runtime/base.py +103 -0
  436. ksadk/skills/runtime/executor.py +219 -0
  437. ksadk/skills/runtime/factory.py +37 -0
  438. ksadk/skills/runtime/loader.py +131 -0
  439. ksadk/skills/runtime/registry.py +150 -0
  440. ksadk/skills/runtime/request.py +67 -0
  441. ksadk/skills/service_client.py +239 -0
  442. ksadk/skills/service_env.py +58 -0
  443. ksadk/skills/tool_defs.py +221 -0
  444. ksadk/terminal_client.py +69 -0
  445. ksadk/terminal_exec_policy.py +229 -0
  446. ksadk/tools/__init__.py +17 -0
  447. ksadk/tools/gateway.py +230 -0
  448. ksadk/tools/result_budget.py +154 -0
  449. ksadk/toolsets/__init__.py +854 -0
  450. ksadk/toolsets/_langchain.py +11 -0
  451. ksadk/toolsets/platform.py +93 -0
  452. ksadk/toolsets/sandbox.py +310 -0
  453. ksadk/toolsets/skills.py +217 -0
  454. ksadk/toolsets/web.py +266 -0
  455. ksadk/toolsets/workspace.py +917 -0
  456. ksadk/toolsets/workspace_state.py +44 -0
  457. ksadk/tracing/__init__.py +37 -0
  458. ksadk/tracing/exporters/__init__.py +1 -0
  459. ksadk/tracing/exporters/inmemory_exporter.py +116 -0
  460. ksadk/tracing/exporters/langfuse_exporter.py +445 -0
  461. ksadk/tracing/setup.py +892 -0
  462. ksadk/tracing/span_utils.py +114 -0
  463. ksadk/tui/__init__.py +27 -0
  464. ksadk/tui/loop.py +1877 -0
  465. ksadk/tui/stream_render.py +68 -0
  466. ksadk/ui_config.py +168 -0
  467. ksadk/version.py +4 -0
  468. ksadk_runtime_common/__init__.py +20 -0
  469. ksadk_runtime_common/memory_backend/__init__.py +29 -0
  470. ksadk_runtime_common/memory_backend/manifest.py +79 -0
  471. ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
  472. ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
  473. ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
  474. ksadk_runtime_common/memory_backend/registry.py +56 -0
  475. ksadk_runtime_common/memory_backend/render.py +71 -0
  476. ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
  477. ksadk_runtime_common/workspace_files/__init__.py +29 -0
  478. ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
  479. ksadk_runtime_common/workspace_files/constants.py +9 -0
  480. ksadk_runtime_common/workspace_files/path_utils.py +71 -0
  481. ksadk_runtime_common/workspace_files/preview.py +96 -0
  482. ksadk_runtime_common/workspace_files/router.py +254 -0
  483. agentengine_sdk_python/__init__.py +0 -3
  484. agentengine_sdk_python-0.6.4.dist-info/METADATA +0 -48
  485. agentengine_sdk_python-0.6.4.dist-info/RECORD +0 -5
  486. agentengine_sdk_python-0.6.4.dist-info/top_level.txt +0 -1
ksadk/tracing/setup.py ADDED
@@ -0,0 +1,892 @@
1
+ """
2
+ Tracing 初始化 - 支持多 Exporter (InMemory + Langfuse + OTLP)
3
+ 支持 ADK 自动插桩 via openinference-instrumentation-google-adk
4
+ """
5
+
6
+ import atexit
7
+ import base64
8
+ import logging
9
+ import os
10
+ from dataclasses import dataclass
11
+ from typing import Any, Optional
12
+ from urllib.parse import unquote
13
+
14
+ from ksadk.tracing.exporters.inmemory_exporter import InMemoryExporter
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def _batch_span_processor_kwargs() -> dict:
20
+ """BatchSpanProcessor 参数:限制单次 export 体积,避免 OTLP collector 413。
21
+
22
+ 默认 max_export_batch_size=64(OTel 默认 512),单 batch 过大会触发 collector
23
+ `Request Entity Too Large (413)`。可用 KSADK_OTLP_MAX_EXPORT_BATCH_SIZE 覆盖。
24
+ """
25
+ try:
26
+ max_batch = int(os.environ.get("KSADK_OTLP_MAX_EXPORT_BATCH_SIZE", "64"))
27
+ except ValueError:
28
+ max_batch = 64
29
+ if max_batch <= 0:
30
+ max_batch = 64
31
+ return {
32
+ "max_queue_size": max(512, max_batch * 8),
33
+ "max_export_batch_size": max_batch,
34
+ "export_timeout_millis": 30000,
35
+ }
36
+
37
+
38
+ _exporter_instance: Optional[InMemoryExporter] = None
39
+ _langfuse_exporter: Optional[Any] = None
40
+ _tracing_initialized: bool = False
41
+ _adk_instrumented: bool = False
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class _OtlpHttpConfig:
46
+ endpoint: str
47
+ headers: dict[str, str]
48
+ protocol: str
49
+ service_name: str
50
+
51
+
52
+ class _LoggingSpanExporter:
53
+ """Small delegating exporter that logs external trace export flow."""
54
+
55
+ def __init__(
56
+ self,
57
+ exporter: Any,
58
+ *,
59
+ name: str,
60
+ endpoint: str,
61
+ service_name: str,
62
+ header_keys: list[str],
63
+ span_transform: Any = None,
64
+ ):
65
+ self._exporter = exporter
66
+ self._name = name
67
+ self._endpoint = endpoint
68
+ self._service_name = service_name
69
+ self._header_keys = header_keys
70
+ self._span_transform = span_transform
71
+
72
+ def export(self, spans):
73
+ span_count = len(spans) if hasattr(spans, "__len__") else "unknown"
74
+ logger.info(
75
+ "%s export started: endpoint=%s service_name=%s spans=%s headers=%s",
76
+ self._name,
77
+ self._endpoint,
78
+ self._service_name or "",
79
+ span_count,
80
+ ",".join(self._header_keys),
81
+ )
82
+ try:
83
+ export_spans = self._span_transform(spans) if self._span_transform else spans
84
+ result = self._exporter.export(export_spans)
85
+ except Exception:
86
+ logger.exception(
87
+ "%s export failed: endpoint=%s service_name=%s spans=%s",
88
+ self._name,
89
+ self._endpoint,
90
+ self._service_name or "",
91
+ span_count,
92
+ )
93
+ raise
94
+ logger.info(
95
+ "%s export result: endpoint=%s service_name=%s spans=%s result=%s",
96
+ self._name,
97
+ self._endpoint,
98
+ self._service_name or "",
99
+ span_count,
100
+ result,
101
+ )
102
+ return result
103
+
104
+ def shutdown(self):
105
+ shutdown = getattr(self._exporter, "shutdown", None)
106
+ if shutdown is None:
107
+ return None
108
+ logger.info("%s exporter shutdown: endpoint=%s", self._name, self._endpoint)
109
+ return shutdown()
110
+
111
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
112
+ force_flush = getattr(self._exporter, "force_flush", None)
113
+ if force_flush is None:
114
+ return True
115
+ logger.info(
116
+ "%s exporter force_flush: endpoint=%s timeout_millis=%s",
117
+ self._name,
118
+ self._endpoint,
119
+ timeout_millis,
120
+ )
121
+ return force_flush(timeout_millis)
122
+
123
+
124
+ def _coerce_int(value: Any) -> int:
125
+ if value is None or isinstance(value, bool):
126
+ return 0
127
+ try:
128
+ return int(value)
129
+ except (TypeError, ValueError):
130
+ try:
131
+ return int(float(value))
132
+ except (TypeError, ValueError):
133
+ return 0
134
+
135
+
136
+ def _span_context(span: Any) -> Any:
137
+ context = getattr(span, "context", None)
138
+ if context is not None:
139
+ return context
140
+ get_context = getattr(span, "get_span_context", None)
141
+ if callable(get_context):
142
+ try:
143
+ return get_context()
144
+ except Exception:
145
+ return None
146
+ return None
147
+
148
+
149
+ def _span_id(span: Any) -> Optional[int]:
150
+ return getattr(_span_context(span), "span_id", None)
151
+
152
+
153
+ def _trace_id(span: Any) -> Optional[int]:
154
+ return getattr(_span_context(span), "trace_id", None)
155
+
156
+
157
+ def _parent_span_id(span: Any) -> Optional[int]:
158
+ return getattr(getattr(span, "parent", None), "span_id", None)
159
+
160
+
161
+ def _span_token_usage(span: Any) -> dict[str, int]:
162
+ attributes = getattr(span, "attributes", None) or {}
163
+ return {
164
+ "input": _coerce_int(attributes.get("gen_ai.usage.input_tokens")),
165
+ "output": _coerce_int(attributes.get("gen_ai.usage.output_tokens")),
166
+ "cache_read": _coerce_int(attributes.get("gen_ai.usage.cache_read.input_tokens")),
167
+ "reasoning_output": _coerce_int(
168
+ attributes.get("gen_ai.usage.reasoning.output_tokens")
169
+ ),
170
+ }
171
+
172
+
173
+ def _has_nonzero_token_usage(usage: dict[str, int]) -> bool:
174
+ return any(value > 0 for value in usage.values())
175
+
176
+
177
+ def _instrumentation_scope_name(span: Any) -> str:
178
+ scope = getattr(span, "instrumentation_scope", None)
179
+ name = getattr(scope, "name", None)
180
+ if name:
181
+ return str(name)
182
+ info = getattr(span, "instrumentation_info", None)
183
+ name = getattr(info, "name", None)
184
+ return str(name or "")
185
+
186
+
187
+ def _clone_span_with_attributes(span: Any, attributes: dict[str, Any]) -> Any:
188
+ try:
189
+ from opentelemetry.sdk.trace import ReadableSpan
190
+
191
+ return ReadableSpan(
192
+ name=span.name,
193
+ context=span.context,
194
+ parent=span.parent,
195
+ resource=span.resource,
196
+ attributes=attributes,
197
+ events=span.events,
198
+ links=span.links,
199
+ kind=span.kind,
200
+ instrumentation_info=getattr(span, "instrumentation_info", None),
201
+ status=span.status,
202
+ start_time=span.start_time,
203
+ end_time=span.end_time,
204
+ instrumentation_scope=getattr(span, "instrumentation_scope", None),
205
+ )
206
+ except Exception:
207
+ logger.debug("Failed to clone span for CloudMonitor token compatibility", exc_info=True)
208
+ return span
209
+
210
+
211
+ def _prepare_langfuse_spans(spans: Any) -> Any:
212
+ """Keep ksadk response usage authoritative for Langfuse exports.
213
+
214
+ OpenInference LangChain instrumentation can emit llm.token_count.* on nested
215
+ ChatOpenAI spans. In LangGraph streaming these counts can be cumulative and
216
+ disagree with the usage returned by ksadk to RunAgent/session state. When a
217
+ trace already has a ksadk.conversations span with gen_ai.usage.*, strip token
218
+ attrs from nested OpenInference LangChain spans before exporting to
219
+ Langfuse. The child spans remain useful for timing and structure, but cannot
220
+ pollute token accounting.
221
+ """
222
+ if not spans:
223
+ return spans
224
+ span_list = list(spans)
225
+ authoritative_trace_ids = {
226
+ _trace_id(span)
227
+ for span in span_list
228
+ if _trace_id(span) is not None
229
+ and _instrumentation_scope_name(span) == "ksadk.conversations"
230
+ and _has_nonzero_token_usage(_span_token_usage(span))
231
+ }
232
+ if not authoritative_trace_ids:
233
+ return spans
234
+
235
+ strip_prefixes = ("llm.token_count.", "llm.usage.", "gen_ai.usage.")
236
+ replacements: dict[int, Any] = {}
237
+ for span in span_list:
238
+ if _trace_id(span) not in authoritative_trace_ids:
239
+ continue
240
+ if _instrumentation_scope_name(span) != "openinference.instrumentation.langchain":
241
+ continue
242
+ attributes = dict(getattr(span, "attributes", None) or {})
243
+ stripped = {
244
+ key: value
245
+ for key, value in attributes.items()
246
+ if not any(str(key).startswith(prefix) for prefix in strip_prefixes)
247
+ }
248
+ if len(stripped) != len(attributes):
249
+ if str(stripped.get("openinference.span.kind") or "").upper() == "LLM":
250
+ stripped["openinference.span.kind"] = "CHAIN"
251
+ replacements[id(span)] = _clone_span_with_attributes(span, stripped)
252
+
253
+ if not replacements:
254
+ return spans
255
+ return [replacements.get(id(span), span) for span in span_list]
256
+
257
+
258
+ def _prepare_cloud_monitor_spans(spans: Any) -> Any:
259
+ """Add root token rollups for CloudMonitor without changing other exporters.
260
+
261
+ CloudMonitor already parses gen_ai.usage.* on LLM child spans, but its root-span
262
+ and overview APIs currently use root-level token fields. ADK/OpenInference often
263
+ emits token usage on nested LLM spans only, and sometimes duplicates the same LLM
264
+ usage on a parent and a child span. To avoid double counting, aggregate only
265
+ token-bearing leaf spans and copy the result to the root span for this exporter.
266
+ """
267
+ if not spans:
268
+ return spans
269
+ span_list = list(spans)
270
+ span_ids = {_span_id(span) for span in span_list if _span_id(span) is not None}
271
+ if not span_ids:
272
+ return spans
273
+
274
+ children_by_parent: dict[tuple[Optional[int], int], list[Any]] = {}
275
+ roots: list[Any] = []
276
+ for span in span_list:
277
+ sid = _span_id(span)
278
+ if sid is None:
279
+ continue
280
+ parent_id = _parent_span_id(span)
281
+ trace_id = _trace_id(span)
282
+ if parent_id is None or parent_id not in span_ids:
283
+ roots.append(span)
284
+ else:
285
+ children_by_parent.setdefault((trace_id, parent_id), []).append(span)
286
+
287
+ token_usage_by_id = {
288
+ (_trace_id(span), _span_id(span)): _span_token_usage(span)
289
+ for span in span_list
290
+ if _span_id(span) is not None
291
+ }
292
+
293
+ has_token_descendant_cache: dict[tuple[Optional[int], int], bool] = {}
294
+
295
+ def has_token_descendant(span: Any) -> bool:
296
+ sid = _span_id(span)
297
+ key = (_trace_id(span), sid)
298
+ if sid is None:
299
+ return False
300
+ if key in has_token_descendant_cache:
301
+ return has_token_descendant_cache[key]
302
+ for child in children_by_parent.get(key, []):
303
+ child_key = (_trace_id(child), _span_id(child))
304
+ if _has_nonzero_token_usage(token_usage_by_id.get(child_key, {})):
305
+ has_token_descendant_cache[key] = True
306
+ return True
307
+ if has_token_descendant(child):
308
+ has_token_descendant_cache[key] = True
309
+ return True
310
+ has_token_descendant_cache[key] = False
311
+ return False
312
+
313
+ def collect_leaf_usage(span: Any, usage: dict[str, int]) -> None:
314
+ sid = _span_id(span)
315
+ key = (_trace_id(span), sid)
316
+ own_usage = token_usage_by_id.get(key, {})
317
+ if _has_nonzero_token_usage(own_usage) and not has_token_descendant(span):
318
+ for usage_key, value in own_usage.items():
319
+ usage[usage_key] += value
320
+ return
321
+ for child in children_by_parent.get(key, []):
322
+ collect_leaf_usage(child, usage)
323
+
324
+ replacements: dict[int, Any] = {}
325
+ for root in roots:
326
+ aggregate = {"input": 0, "output": 0, "cache_read": 0, "reasoning_output": 0}
327
+ collect_leaf_usage(root, aggregate)
328
+ if not _has_nonzero_token_usage(aggregate):
329
+ continue
330
+
331
+ attributes = dict(getattr(root, "attributes", None) or {})
332
+ updated = False
333
+ attr_map = {
334
+ "gen_ai.usage.input_tokens": "input",
335
+ "gen_ai.usage.output_tokens": "output",
336
+ "gen_ai.usage.cache_read.input_tokens": "cache_read",
337
+ "gen_ai.usage.reasoning.output_tokens": "reasoning_output",
338
+ }
339
+ for attr_name, usage_key in attr_map.items():
340
+ if _coerce_int(attributes.get(attr_name)) <= 0 and aggregate[usage_key] > 0:
341
+ attributes[attr_name] = aggregate[usage_key]
342
+ updated = True
343
+
344
+ if updated:
345
+ replacements[id(root)] = _clone_span_with_attributes(root, attributes)
346
+
347
+ if not replacements:
348
+ return spans
349
+ return [replacements.get(id(span), span) for span in span_list]
350
+
351
+
352
+ def _build_langfuse_otlp_config(langfuse_config: dict = None) -> Optional[dict]:
353
+ """Build Langfuse OTLP direct exporter config from explicit config or env."""
354
+ if langfuse_config:
355
+ public_key = langfuse_config.get("public_key") or ""
356
+ secret_key = langfuse_config.get("secret_key") or ""
357
+ host = langfuse_config.get("host") or "http://localhost:3000"
358
+ else:
359
+ public_key = os.getenv("LANGFUSE_PUBLIC_KEY", "")
360
+ secret_key = os.getenv("LANGFUSE_SECRET_KEY", "")
361
+ host = os.getenv("LANGFUSE_BASE_URL") or os.getenv("LANGFUSE_HOST") or "http://localhost:3000"
362
+
363
+ if not public_key or not secret_key:
364
+ return None
365
+
366
+ headers = _build_langfuse_otlp_headers(public_key, secret_key)
367
+ return {
368
+ "endpoint": f"{host.rstrip('/')}/api/public/otel/v1/traces",
369
+ "headers": headers,
370
+ "protocol": "http/protobuf",
371
+ }
372
+
373
+
374
+ def _build_langfuse_otlp_headers(public_key: str, secret_key: str) -> dict[str, str]:
375
+ auth = base64.b64encode(f"{public_key}:{secret_key}".encode("utf-8")).decode("ascii")
376
+ return {
377
+ "Authorization": f"Basic {auth}",
378
+ "x-langfuse-ingestion-version": "4",
379
+ }
380
+
381
+
382
+ def _parse_otlp_headers(raw: str) -> dict[str, str]:
383
+ """Parse OTEL_EXPORTER_OTLP_HEADERS into an HTTP headers dict."""
384
+ headers: dict[str, str] = {}
385
+ for part in (raw or "").split(","):
386
+ item = part.strip()
387
+ if not item or "=" not in item:
388
+ continue
389
+ key, value = item.split("=", 1)
390
+ key = key.strip()
391
+ if not key:
392
+ continue
393
+ headers[key] = _decode_otlp_header_value(key, value)
394
+ return headers
395
+
396
+
397
+ def _decode_otlp_header_value(key: str, value: str) -> str:
398
+ decoded = unquote(value.strip())
399
+ if key.strip().lower() != "authorization":
400
+ return decoded
401
+
402
+ for scheme in ("Basic", "Bearer"):
403
+ prefix = f"{scheme}+"
404
+ if decoded.startswith(prefix):
405
+ return f"{scheme} {decoded[len(prefix):]}"
406
+ return decoded
407
+
408
+
409
+ def _derive_otlp_traces_endpoint(endpoint: str) -> str:
410
+ """Derive the HTTP trace endpoint from a generic OTLP endpoint."""
411
+ endpoint = endpoint.strip().rstrip("/")
412
+ if endpoint.endswith("/v1/traces"):
413
+ return endpoint
414
+ return f"{endpoint}/v1/traces"
415
+
416
+
417
+ def _env_flag_disabled(value: str) -> bool:
418
+ return value.strip().lower() in {"0", "false", "no", "off", "disabled"}
419
+
420
+
421
+ def _env_flag_enabled(value: str) -> bool:
422
+ return value.strip().lower() in {"1", "true", "yes", "on"}
423
+
424
+
425
+ def _has_header(headers: dict[str, str], header_name: str) -> bool:
426
+ expected = header_name.strip().lower()
427
+ return any(key.strip().lower() == expected for key in headers)
428
+
429
+
430
+ def _get_service_name() -> str:
431
+ service_name = os.getenv("OTEL_SERVICE_NAME", "").strip()
432
+ if service_name:
433
+ return service_name
434
+
435
+ for part in os.getenv("OTEL_RESOURCE_ATTRIBUTES", "").split(","):
436
+ item = part.strip()
437
+ if not item or "=" not in item:
438
+ continue
439
+ key, value = item.split("=", 1)
440
+ if key.strip() == "service.name":
441
+ return unquote(value.strip())
442
+ return ""
443
+
444
+
445
+ def _build_cloud_monitor_otlp_http_config() -> Optional[_OtlpHttpConfig]:
446
+ """Build CloudMonitor OTLP HTTP traces exporter config from CLOUD_MONITOR_* env."""
447
+ enabled = os.getenv("CLOUD_MONITOR_OTLP_ENABLED", "").strip()
448
+ if enabled and _env_flag_disabled(enabled):
449
+ logger.info("CloudMonitor OTLP exporter disabled by CLOUD_MONITOR_OTLP_ENABLED=%s", enabled)
450
+ return None
451
+ cloud_monitor_langfuse_configured = all(
452
+ os.getenv(name, "").strip()
453
+ for name in (
454
+ "CLOUD_MONITOR_LANGFUSE_PUBLIC_KEY",
455
+ "CLOUD_MONITOR_LANGFUSE_SECRET_KEY",
456
+ )
457
+ ) and bool(
458
+ os.getenv("CLOUD_MONITOR_LANGFUSE_HOST", "").strip()
459
+ or os.getenv("CLOUD_MONITOR_OTLP_ENDPOINT", "").strip()
460
+ )
461
+ cloud_monitor_otlp_forced = _env_flag_enabled(enabled)
462
+ cloud_monitor_callback_requested = _env_flag_enabled(
463
+ os.getenv("LANGFUSE_USE_CALLBACK", "")
464
+ ) and not _env_flag_disabled(os.getenv("CLOUD_MONITOR_LANGFUSE_ENABLED", ""))
465
+ if (
466
+ cloud_monitor_langfuse_configured
467
+ and cloud_monitor_callback_requested
468
+ and not cloud_monitor_otlp_forced
469
+ ):
470
+ logger.info(
471
+ "CloudMonitor OTLP exporter skipped because CloudMonitor Langfuse SDK "
472
+ "callback is requested"
473
+ )
474
+ return None
475
+
476
+ app_key = os.getenv("CLOUD_MONITOR_APP_KEY", "").strip()
477
+ traces_endpoint = os.getenv("CLOUD_MONITOR_OTLP_TRACES_ENDPOINT", "").strip()
478
+ base_endpoint = os.getenv("CLOUD_MONITOR_OTLP_ENDPOINT", "").strip()
479
+ endpoint = traces_endpoint
480
+ if not endpoint and base_endpoint:
481
+ endpoint = _derive_otlp_traces_endpoint(base_endpoint)
482
+
483
+ if not app_key and not endpoint:
484
+ logger.debug("CloudMonitor OTLP exporter not configured: missing app key and endpoint")
485
+ return None
486
+ if not app_key:
487
+ logger.warning("CloudMonitor OTLP exporter skipped: CLOUD_MONITOR_APP_KEY is missing")
488
+ return None
489
+ if not endpoint:
490
+ logger.warning(
491
+ "CloudMonitor OTLP exporter skipped: CLOUD_MONITOR_OTLP_ENDPOINT or "
492
+ "CLOUD_MONITOR_OTLP_TRACES_ENDPOINT is missing"
493
+ )
494
+ return None
495
+
496
+ protocol = (
497
+ os.getenv("CLOUD_MONITOR_OTLP_TRACES_PROTOCOL", "").strip().lower()
498
+ or os.getenv("CLOUD_MONITOR_OTLP_PROTOCOL", "").strip().lower()
499
+ or "http/protobuf"
500
+ )
501
+ if protocol != "http/protobuf":
502
+ logger.warning(
503
+ "CloudMonitor OTLP exporter skipped: unsupported protocol %s, expected http/protobuf",
504
+ protocol,
505
+ )
506
+ return None
507
+
508
+ headers = _parse_otlp_headers(os.getenv("CLOUD_MONITOR_OTLP_HEADERS", ""))
509
+ headers["Ksc-Appkey"] = app_key
510
+ service_name = _get_service_name()
511
+ logger.info(
512
+ "CloudMonitor OTLP config resolved: endpoint=%s protocol=%s service_name=%s "
513
+ "app_key_present=%s header_keys=%s",
514
+ endpoint,
515
+ protocol,
516
+ service_name or "",
517
+ bool(app_key),
518
+ ",".join(sorted(headers)),
519
+ )
520
+ return _OtlpHttpConfig(
521
+ endpoint=endpoint,
522
+ headers=headers,
523
+ protocol=protocol,
524
+ service_name=service_name,
525
+ )
526
+
527
+
528
+ def _is_langfuse_otlp_endpoint(endpoint: str) -> bool:
529
+ if not endpoint:
530
+ return False
531
+ normalized_endpoint = endpoint.strip().rstrip("/")
532
+ langfuse_hosts = [
533
+ os.getenv("LANGFUSE_HOST", "").strip().rstrip("/"),
534
+ os.getenv("LANGFUSE_BASE_URL", "").strip().rstrip("/"),
535
+ ]
536
+ if any(host and normalized_endpoint.startswith(host) for host in langfuse_hosts):
537
+ return True
538
+ return "/api/public/otel" in normalized_endpoint
539
+
540
+
541
+ def _is_langfuse_callback_endpoint(endpoint: str) -> bool:
542
+ return _env_flag_enabled(os.getenv("LANGFUSE_USE_CALLBACK", "")) and _is_langfuse_otlp_endpoint(
543
+ endpoint
544
+ )
545
+
546
+
547
+ def _apply_langfuse_auth_fallback(endpoint: str, headers: dict[str, str]) -> bool:
548
+ """Add Langfuse OTLP auth when the platform provides endpoint + keys but no headers."""
549
+ if not _is_langfuse_otlp_endpoint(endpoint) or _has_header(headers, "Authorization"):
550
+ return False
551
+
552
+ public_key = os.getenv("LANGFUSE_PUBLIC_KEY", "").strip()
553
+ secret_key = os.getenv("LANGFUSE_SECRET_KEY", "").strip()
554
+ if not public_key or not secret_key:
555
+ logger.warning(
556
+ "Generic OTLP endpoint looks like Langfuse but LANGFUSE_PUBLIC_KEY/SECRET_KEY "
557
+ "are missing; Authorization header was not added"
558
+ )
559
+ return False
560
+
561
+ langfuse_headers = _build_langfuse_otlp_headers(public_key, secret_key)
562
+ headers["Authorization"] = langfuse_headers["Authorization"]
563
+ if not _has_header(headers, "x-langfuse-ingestion-version"):
564
+ headers["x-langfuse-ingestion-version"] = langfuse_headers[
565
+ "x-langfuse-ingestion-version"
566
+ ]
567
+ return True
568
+
569
+
570
+ def _build_generic_otlp_http_config() -> Optional[dict]:
571
+ """Build generic OTLP HTTP traces exporter config from standard OTEL env."""
572
+ traces_endpoint = os.getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "").strip()
573
+ base_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "").strip()
574
+ endpoint = traces_endpoint
575
+ if not endpoint and base_endpoint:
576
+ endpoint = _derive_otlp_traces_endpoint(base_endpoint)
577
+ if not endpoint:
578
+ return None
579
+ if _is_langfuse_callback_endpoint(endpoint):
580
+ logger.info(
581
+ "Generic OTLP HTTP exporter skipped because LANGFUSE_USE_CALLBACK is enabled "
582
+ "for endpoint=%s",
583
+ endpoint,
584
+ )
585
+ return None
586
+
587
+ protocol = (
588
+ os.getenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", "").strip().lower()
589
+ or os.getenv("OTEL_EXPORTER_OTLP_PROTOCOL", "").strip().lower()
590
+ )
591
+ if protocol and protocol != "http/protobuf":
592
+ logger.warning(
593
+ "Unsupported OTEL_EXPORTER_OTLP protocol for KsADK auto HTTP exporter: %s",
594
+ protocol,
595
+ )
596
+ return None
597
+
598
+ raw_headers = (
599
+ os.getenv("OTEL_EXPORTER_OTLP_TRACES_HEADERS", "").strip()
600
+ or os.getenv("OTEL_EXPORTER_OTLP_HEADERS", "")
601
+ )
602
+ headers = _parse_otlp_headers(raw_headers)
603
+ langfuse_auth_fallback = _apply_langfuse_auth_fallback(endpoint, headers)
604
+ logger.info(
605
+ "Generic OTLP HTTP config resolved: endpoint=%s protocol=http/protobuf "
606
+ "header_keys=%s langfuse_auth_fallback=%s",
607
+ endpoint,
608
+ ",".join(sorted(headers)),
609
+ langfuse_auth_fallback,
610
+ )
611
+ return {
612
+ "endpoint": endpoint,
613
+ "headers": headers,
614
+ "protocol": "http/protobuf",
615
+ "langfuse_auth_fallback": langfuse_auth_fallback,
616
+ }
617
+
618
+
619
+ def setup_tracing(
620
+ enable_inmemory: bool = True,
621
+ enable_langfuse: bool = None, # Auto-detect from env
622
+ langfuse_config: dict = None,
623
+ enable_otlp: bool = False,
624
+ otlp_endpoint: str = "localhost:4317",
625
+ enable_adk_instrumentation: bool = True, # Auto-instrument ADK
626
+ use_callback_only: bool = None, # Explicit override
627
+ **kwargs
628
+ ) -> Optional[InMemoryExporter]:
629
+ """初始化 Tracing (支持多 Exporter)
630
+
631
+ Args:
632
+ enable_inmemory: 是否启用内存 Exporter (Web UI 使用)
633
+ enable_langfuse: 是否启用 Langfuse (None = 自动检测环境变量)
634
+ langfuse_config: Langfuse 配置 {"public_key", "secret_key", "host"}
635
+ enable_otlp: 是否启用 OTLP Exporter
636
+ otlp_endpoint: OTLP 端点地址
637
+ enable_adk_instrumentation: 是否启用 ADK 自动插桩
638
+ use_callback_only: 是否仅使用 CallbackHandler (防止 OTel 重复)
639
+
640
+ Returns:
641
+ InMemoryExporter 实例 (用于 Web UI 获取 traces)
642
+ """
643
+ global _exporter_instance, _langfuse_exporter, _tracing_initialized, _adk_instrumented
644
+
645
+ # 防止重复初始化
646
+ if _tracing_initialized:
647
+ logger.debug("Tracing already initialized, skipping")
648
+ return _exporter_instance
649
+
650
+ try:
651
+ from opentelemetry import trace
652
+ from opentelemetry.sdk.trace import TracerProvider
653
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
654
+ except ImportError:
655
+ logger.warning("OpenTelemetry not installed, tracing disabled")
656
+ return None
657
+
658
+ # 检查是否已有 TracerProvider (避免覆盖)
659
+ existing_provider = trace.get_tracer_provider()
660
+ if existing_provider and hasattr(existing_provider, 'add_span_processor'):
661
+ # 使用现有 provider,直接添加 processor
662
+ provider = existing_provider
663
+ logger.debug("Using existing TracerProvider")
664
+ else:
665
+ # 创建新的 TracerProvider
666
+ provider = TracerProvider()
667
+ trace.set_tracer_provider(provider)
668
+ logger.debug("Created new TracerProvider")
669
+
670
+ # 1. InMemory Exporter (for Web UI)
671
+ if enable_inmemory:
672
+ exporter = InMemoryExporter(max_traces=kwargs.get("max_traces", 1000))
673
+ _exporter_instance = exporter
674
+ provider.add_span_processor(SimpleSpanProcessor(exporter))
675
+ logger.info("InMemory exporter enabled")
676
+
677
+ # 2. Generic OTLP HTTP exporter from standard environment variables.
678
+ # This keeps user code backend-agnostic; Langfuse is only one possible OTLP backend.
679
+ generic_otlp_config = _build_generic_otlp_http_config()
680
+ if generic_otlp_config:
681
+ try:
682
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
683
+
684
+ otlp_exporter = OTLPSpanExporter(
685
+ endpoint=generic_otlp_config["endpoint"],
686
+ headers=generic_otlp_config["headers"],
687
+ )
688
+ otlp_exporter = _LoggingSpanExporter(
689
+ otlp_exporter,
690
+ name="Generic OTLP",
691
+ endpoint=generic_otlp_config["endpoint"],
692
+ service_name=_get_service_name(),
693
+ header_keys=sorted(generic_otlp_config["headers"]),
694
+ span_transform=(
695
+ _prepare_langfuse_spans
696
+ if _is_langfuse_otlp_endpoint(generic_otlp_config["endpoint"])
697
+ else None
698
+ ),
699
+ )
700
+ provider.add_span_processor(
701
+ BatchSpanProcessor(
702
+ otlp_exporter,
703
+ **_batch_span_processor_kwargs(),
704
+ )
705
+ )
706
+ logger.info(
707
+ "Generic OTLP HTTP exporter enabled: %s (%s) headers=%s",
708
+ generic_otlp_config["endpoint"],
709
+ generic_otlp_config["protocol"],
710
+ ",".join(sorted(generic_otlp_config["headers"])),
711
+ )
712
+ except ImportError as e:
713
+ logger.warning(f"Generic OTLP HTTP exporter not available: {e}")
714
+ except Exception as e:
715
+ logger.error(f"Failed to initialize generic OTLP HTTP exporter: {e}")
716
+
717
+ # 3. CloudMonitor OTLP HTTP exporter.
718
+ # This uses CloudMonitor-specific env vars, so existing Langfuse / generic OTLP paths
719
+ # continue to behave exactly as configured by the user or platform.
720
+ cloud_monitor_config = _build_cloud_monitor_otlp_http_config()
721
+ if cloud_monitor_config:
722
+ try:
723
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
724
+
725
+ cloud_monitor_exporter = OTLPSpanExporter(
726
+ endpoint=cloud_monitor_config.endpoint,
727
+ headers=cloud_monitor_config.headers,
728
+ )
729
+ cloud_monitor_exporter = _LoggingSpanExporter(
730
+ cloud_monitor_exporter,
731
+ name="CloudMonitor OTLP",
732
+ endpoint=cloud_monitor_config.endpoint,
733
+ service_name=cloud_monitor_config.service_name,
734
+ header_keys=sorted(cloud_monitor_config.headers),
735
+ span_transform=_prepare_cloud_monitor_spans,
736
+ )
737
+ provider.add_span_processor(
738
+ BatchSpanProcessor(
739
+ cloud_monitor_exporter,
740
+ **_batch_span_processor_kwargs(),
741
+ )
742
+ )
743
+ logger.info(
744
+ "CloudMonitor OTLP exporter enabled: endpoint=%s protocol=%s service_name=%s",
745
+ cloud_monitor_config.endpoint,
746
+ cloud_monitor_config.protocol,
747
+ cloud_monitor_config.service_name or "",
748
+ )
749
+ except ImportError as e:
750
+ logger.warning(f"CloudMonitor OTLP exporter not available: {e}")
751
+ except Exception as e:
752
+ logger.error(f"Failed to initialize CloudMonitor OTLP exporter: {e}")
753
+
754
+ # 4. Langfuse OTLP direct exporter (auto-detect or explicit config)
755
+ # 注意: 对于 LangGraph/LangChain 框架,推荐使用 CallbackHandler 而非 OTel Exporter
756
+ # 同时使用两者会导致重复的 trace
757
+ langfuse_enabled = enable_langfuse
758
+ if langfuse_enabled is None:
759
+ # Auto-detect from environment variables
760
+ langfuse_enabled = (
761
+ not generic_otlp_config
762
+ and bool(os.getenv("LANGFUSE_PUBLIC_KEY") or (langfuse_config or {}).get("public_key"))
763
+ )
764
+
765
+ # 检查是否应该禁用 LangfuseExporter (当使用 LangChain/LangGraph 时)
766
+ # 优先使用显式参数,否则读取环境变量
767
+ if use_callback_only is None:
768
+ use_callback_only = os.getenv("LANGFUSE_USE_CALLBACK", "false").lower() == "true"
769
+
770
+ if langfuse_enabled and not use_callback_only:
771
+ try:
772
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
773
+
774
+ config = _build_langfuse_otlp_config(langfuse_config)
775
+ if config:
776
+ otlp_exporter = OTLPSpanExporter(
777
+ endpoint=config["endpoint"],
778
+ headers=config["headers"],
779
+ )
780
+ otlp_exporter = _LoggingSpanExporter(
781
+ otlp_exporter,
782
+ name="Langfuse OTLP",
783
+ endpoint=config["endpoint"],
784
+ service_name=_get_service_name(),
785
+ header_keys=sorted(config["headers"]),
786
+ span_transform=_prepare_langfuse_spans,
787
+ )
788
+ provider.add_span_processor(
789
+ BatchSpanProcessor(
790
+ otlp_exporter,
791
+ **_batch_span_processor_kwargs(),
792
+ )
793
+ )
794
+ logger.info(
795
+ "Langfuse OTLP exporter enabled: %s (%s) headers=%s",
796
+ config["endpoint"],
797
+ config["protocol"],
798
+ ",".join(sorted(config["headers"])),
799
+ )
800
+ else:
801
+ logger.warning("Langfuse credentials not found, skipping")
802
+
803
+ except ImportError as e:
804
+ logger.warning(f"Langfuse OTLP exporter not available: {e}")
805
+ except Exception as e:
806
+ logger.error(f"Failed to initialize Langfuse OTLP exporter: {e}")
807
+ elif langfuse_enabled:
808
+ logger.info("Langfuse will use CallbackHandler (recommended for LangChain/LangGraph)")
809
+
810
+ # 5. OTLP Exporter (optional)
811
+ if enable_otlp:
812
+ try:
813
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
814
+ otlp_exporter = OTLPSpanExporter(endpoint=otlp_endpoint)
815
+ provider.add_span_processor(
816
+ BatchSpanProcessor(
817
+ otlp_exporter,
818
+ **_batch_span_processor_kwargs(),
819
+ )
820
+ )
821
+ logger.info(f"OTLP exporter enabled: {otlp_endpoint}")
822
+ except ImportError:
823
+ logger.warning("OTLP exporter not installed")
824
+
825
+ # 6. ADK Auto-Instrumentation (for Google ADK projects)
826
+ if enable_adk_instrumentation and not _adk_instrumented:
827
+ try:
828
+ from openinference.instrumentation.google_adk import GoogleADKInstrumentor
829
+ GoogleADKInstrumentor().instrument()
830
+ _adk_instrumented = True
831
+ logger.info("Google ADK instrumentation enabled")
832
+ except ImportError:
833
+ logger.debug(
834
+ "openinference-instrumentation-google-adk not installed, "
835
+ "ADK auto-instrumentation disabled"
836
+ )
837
+ except Exception as e:
838
+ logger.debug(f"ADK instrumentation failed: {e}")
839
+
840
+ # 7. LangChain Auto-Instrumentation
841
+ if enable_adk_instrumentation:
842
+ try:
843
+ from openinference.instrumentation.langchain import LangChainInstrumentor
844
+ LangChainInstrumentor().instrument()
845
+ logger.info("LangChain instrumentation enabled")
846
+ except ImportError:
847
+ logger.warning(
848
+ "Auto-instrumentation skipped: 'openinference-instrumentation-langchain' "
849
+ "not installed. Install with `pip install ksadk[tracing]` for detailed traces."
850
+ )
851
+ except Exception as e:
852
+ logger.error(f"LangChain instrumentation failed: {e}")
853
+
854
+ # Register graceful shutdown
855
+ atexit.register(shutdown_tracing)
856
+
857
+ _tracing_initialized = True
858
+ return _exporter_instance
859
+
860
+
861
+ def shutdown_tracing():
862
+ """Gracefully shutdown tracing to prevent Ctrl+C errors"""
863
+ global _langfuse_exporter, _tracing_initialized
864
+
865
+ if _langfuse_exporter is not None:
866
+ try:
867
+ if hasattr(_langfuse_exporter, '_exporter'):
868
+ _langfuse_exporter._exporter.shutdown()
869
+ logger.debug("Langfuse exporter shutdown gracefully")
870
+ except Exception:
871
+ pass # Ignore shutdown errors
872
+
873
+ _tracing_initialized = False
874
+
875
+
876
+ def get_memory_exporter() -> Optional[InMemoryExporter]:
877
+ """获取当前的 Memory Exporter 实例"""
878
+ return _exporter_instance
879
+
880
+
881
+ def get_langfuse_exporter():
882
+ """获取 Langfuse Exporter 实例"""
883
+ return _langfuse_exporter
884
+
885
+
886
+ def get_tracer(name: str = "ksadk"):
887
+ """获取 Tracer"""
888
+ try:
889
+ from opentelemetry import trace
890
+ return trace.get_tracer(name)
891
+ except ImportError:
892
+ return None