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.
- agentengine_sdk_python-0.7.0.dist-info/METADATA +181 -0
- agentengine_sdk_python-0.7.0.dist-info/RECORD +482 -0
- {agentengine_sdk_python-0.6.4.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +1 -1
- agentengine_sdk_python-0.7.0.dist-info/entry_points.txt +3 -0
- agentengine_sdk_python-0.7.0.dist-info/licenses/LICENSE +175 -0
- agentengine_sdk_python-0.7.0.dist-info/top_level.txt +2 -0
- ksadk/__init__.py +13 -0
- ksadk/__main__.py +4 -0
- ksadk/a2a/__init__.py +15 -0
- ksadk/a2a/card_builder.py +64 -0
- ksadk/a2a/client.py +309 -0
- ksadk/a2a/executor.py +201 -0
- ksadk/a2a/server.py +79 -0
- ksadk/agents/__init__.py +20 -0
- ksadk/agents/base.py +370 -0
- ksadk/agents/context.py +38 -0
- ksadk/agents/event.py +35 -0
- ksadk/agents/loop_agent.py +71 -0
- ksadk/agents/parallel_agent.py +109 -0
- ksadk/agents/runner_adapter.py +24 -0
- ksadk/agents/sequential_agent.py +34 -0
- ksadk/api/__init__.py +3 -0
- ksadk/api/client.py +2246 -0
- ksadk/builders/__init__.py +20 -0
- ksadk/builders/base.py +66 -0
- ksadk/builders/code_builder.py +1990 -0
- ksadk/builders/container_builder.py +786 -0
- ksadk/builders/framework_requirements.py +59 -0
- ksadk/builders/ks3_uploader.py +389 -0
- ksadk/builders/mcp_builder.py +476 -0
- ksadk/builders/requirements_utils.py +75 -0
- ksadk/cli/__init__.py +531 -0
- ksadk/cli/__main__.py +4 -0
- ksadk/cli/agent_ref.py +204 -0
- ksadk/cli/cmd_a2a.py +167 -0
- ksadk/cli/cmd_agent.py +201 -0
- ksadk/cli/cmd_build.py +270 -0
- ksadk/cli/cmd_completion.py +304 -0
- ksadk/cli/cmd_config.py +758 -0
- ksadk/cli/cmd_create.py +2304 -0
- ksadk/cli/cmd_dashboard.py +900 -0
- ksadk/cli/cmd_deploy.py +704 -0
- ksadk/cli/cmd_destroy.py +345 -0
- ksadk/cli/cmd_files.py +1227 -0
- ksadk/cli/cmd_hermes.py +1263 -0
- ksadk/cli/cmd_invoke.py +1503 -0
- ksadk/cli/cmd_launch.py +564 -0
- ksadk/cli/cmd_mcp.py +1179 -0
- ksadk/cli/cmd_model.py +302 -0
- ksadk/cli/cmd_openclaw.py +3922 -0
- ksadk/cli/cmd_run.py +239 -0
- ksadk/cli/cmd_status.py +588 -0
- ksadk/cli/cmd_version.py +522 -0
- ksadk/cli/cmd_web.py +288 -0
- ksadk/cli/deploy_utils.py +87 -0
- ksadk/cli/dry_run.py +214 -0
- ksadk/cli/env_options.py +82 -0
- ksadk/cli/error_utils.py +525 -0
- ksadk/cli/global_options.py +31 -0
- ksadk/cli/local_runtime.py +97 -0
- ksadk/cli/model_catalog.py +136 -0
- ksadk/cli/network_options.py +412 -0
- ksadk/cli/resource_common.py +621 -0
- ksadk/cli/storage.py +84 -0
- ksadk/cli/ui.py +431 -0
- ksadk/cli/workflow_common.py +552 -0
- ksadk/common/__init__.py +14 -0
- ksadk/common/aicp_env.py +128 -0
- ksadk/common/auth.py +190 -0
- ksadk/common/constants.py +84 -0
- ksadk/common/llm_utils.py +61 -0
- ksadk/configs/__init__.py +61 -0
- ksadk/configs/env_registry.py +343 -0
- ksadk/configs/global_config.py +200 -0
- ksadk/configs/settings.py +706 -0
- ksadk/conversations/__init__.py +95 -0
- ksadk/conversations/attachment_storage.py +379 -0
- ksadk/conversations/attachments.py +774 -0
- ksadk/conversations/compaction_pipeline.py +387 -0
- ksadk/conversations/compaction_prompt.py +128 -0
- ksadk/conversations/context.py +303 -0
- ksadk/conversations/message_projection.py +269 -0
- ksadk/conversations/model_context.py +370 -0
- ksadk/conversations/model_options.py +136 -0
- ksadk/conversations/normalize.py +446 -0
- ksadk/conversations/reasoning_markup.py +91 -0
- ksadk/conversations/run_kinds.py +61 -0
- ksadk/conversations/run_status.py +53 -0
- ksadk/conversations/runtime.py +5276 -0
- ksadk/conversations/semantic_summary.py +359 -0
- ksadk/conversations/session_title.py +254 -0
- ksadk/deployment/__init__.py +83 -0
- ksadk/deployment/agent_access.py +226 -0
- ksadk/deployment/base.py +229 -0
- ksadk/deployment/manager.py +332 -0
- ksadk/deployment/providers/__init__.py +10 -0
- ksadk/deployment/providers/serverless.py +1115 -0
- ksadk/deployment/registry.py +120 -0
- ksadk/deployment/state.py +78 -0
- ksadk/deployment/ui_config.py +21 -0
- ksadk/detection/__init__.py +7 -0
- ksadk/detection/detector.py +431 -0
- ksadk/detection/mcp_detector.py +250 -0
- ksadk/hermes_terminal.py +467 -0
- ksadk/identity/__init__.py +17 -0
- ksadk/identity/resolver.py +385 -0
- ksadk/knowledge_base/__init__.py +62 -0
- ksadk/knowledge_base/adk_tool.py +47 -0
- ksadk/knowledge_base/client.py +297 -0
- ksadk/knowledge_base/langchain_tool.py +55 -0
- ksadk/knowledge_base/service.py +66 -0
- ksadk/knowledge_base/tool.py +50 -0
- ksadk/markdown.py +169 -0
- ksadk/mcp_runtime/__init__.py +231 -0
- ksadk/memory/__init__.py +57 -0
- ksadk/memory/adk/__init__.py +51 -0
- ksadk/memory/adk/backends/__init__.py +5 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
- ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
- ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
- ksadk/memory/adk/long_term_memory.py +273 -0
- ksadk/memory/adk/resilient_session_service.py +268 -0
- ksadk/memory/adk/short_term_memory.py +304 -0
- ksadk/memory/adk_tool.py +31 -0
- ksadk/memory/backends/__init__.py +6 -0
- ksadk/memory/backends/base.py +103 -0
- ksadk/memory/backends/memory.py +112 -0
- ksadk/memory/backends/redis.py +146 -0
- ksadk/memory/langchain_tool.py +39 -0
- ksadk/memory/ltm_backend_factory.py +24 -0
- ksadk/memory/manager.py +215 -0
- ksadk/memory/service.py +172 -0
- ksadk/memory/tool.py +96 -0
- ksadk/model_policy.py +223 -0
- ksadk/openclaw_gateway.py +378 -0
- ksadk/runners/__init__.py +8 -0
- ksadk/runners/adk_runner.py +1994 -0
- ksadk/runners/base_runner.py +305 -0
- ksadk/runners/deepagents_runner.py +13 -0
- ksadk/runners/factory.py +73 -0
- ksadk/runners/langchain_runner.py +751 -0
- ksadk/runners/langgraph_runner.py +985 -0
- ksadk/runners/patch_langchain.py +187 -0
- ksadk/runners/remote_runner.py +792 -0
- ksadk/runners/usage_accumulator.py +38 -0
- ksadk/runners/utils/__init__.py +19 -0
- ksadk/runners/utils/langfuse.py +230 -0
- ksadk/runners/utils/loader.py +62 -0
- ksadk/runtime_context.py +166 -0
- ksadk/runtime_state.py +25 -0
- ksadk/sandbox/__init__.py +28 -0
- ksadk/sandbox/backends/__init__.py +9 -0
- ksadk/sandbox/backends/e2b.py +154 -0
- ksadk/sandbox/backends/local_process.py +131 -0
- ksadk/sandbox/base.py +101 -0
- ksadk/sandbox/factory.py +53 -0
- ksadk/sandbox/registry.py +187 -0
- ksadk/server/__init__.py +7 -0
- ksadk/server/api_models.py +73 -0
- ksadk/server/app.py +3931 -0
- ksadk/server/static/assets/ArtifactsPanel-D-SSn2ee.js +1 -0
- ksadk/server/static/assets/CodeBlock-P16VOLYi.js +9 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +8 -0
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
- ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +303 -0
- ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
- ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +1 -0
- ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
- ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
- ksadk/server/static/assets/arc-BA13sHc9.js +1 -0
- ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-DzJzgVoL.js +36 -0
- ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
- ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
- ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-UTdVcZhn.js +132 -0
- ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dix0GD4m.js +10 -0
- ksadk/server/static/assets/channel-CTuydARX.js +1 -0
- ksadk/server/static/assets/chunk-2J33WTMH-C2JxuZ42.js +1 -0
- ksadk/server/static/assets/chunk-4BX2VUAB-Be57QPgD.js +1 -0
- ksadk/server/static/assets/chunk-55IACEB6-WjSuy4nv.js +1 -0
- ksadk/server/static/assets/chunk-727SXJPM-BOovP-b5.js +206 -0
- ksadk/server/static/assets/chunk-AQP2D5EJ-DOmZNmWj.js +231 -0
- ksadk/server/static/assets/chunk-FMBD7UC4-DzwlodkU.js +15 -0
- ksadk/server/static/assets/chunk-ND2GUHAM-DMqAikEU.js +1 -0
- ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
- ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
- ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
- ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
- ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
- ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
- ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
- ksadk/server/static/assets/cose-bilkent-S5V4N54A-Zhy2GBnP.js +1 -0
- ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
- ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
- ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
- ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
- ksadk/server/static/assets/dagre-BM42HDAG-CbKrtIUz.js +4 -0
- ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
- ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
- ksadk/server/static/assets/diagram-2AECGRRQ-BRApgQRK.js +43 -0
- ksadk/server/static/assets/diagram-5GNKFQAL-D0xbnDiK.js +10 -0
- ksadk/server/static/assets/diagram-KO2AKTUF-CVugVI9L.js +3 -0
- ksadk/server/static/assets/diagram-LMA3HP47-DozUGgee.js +24 -0
- ksadk/server/static/assets/diagram-OG6HWLK6-ZOYsKlru.js +24 -0
- ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
- ksadk/server/static/assets/dist-2hInLS_J.js +9 -0
- ksadk/server/static/assets/dist-8EEyGgDx.js +1 -0
- ksadk/server/static/assets/dist-8_SfK1IP.js +1 -0
- ksadk/server/static/assets/dist-B0NP_Z0z.js +23 -0
- ksadk/server/static/assets/dist-BIJVDMuB.js +1 -0
- ksadk/server/static/assets/dist-BmlMPsCc.js +2 -0
- ksadk/server/static/assets/dist-BrCcQ47T.js +1 -0
- ksadk/server/static/assets/dist-BssVmjIo.js +6 -0
- ksadk/server/static/assets/dist-C3HIIaN5.js +1 -0
- ksadk/server/static/assets/dist-CPBtqRTW.js +1 -0
- ksadk/server/static/assets/dist-CkEKJFtk.js +1 -0
- ksadk/server/static/assets/dist-D-JMdWPQ.js +1 -0
- ksadk/server/static/assets/dist-D-xOD048.js +1 -0
- ksadk/server/static/assets/dist-D57wnGWd.js +1 -0
- ksadk/server/static/assets/dist-DL_NKc_z.js +1 -0
- ksadk/server/static/assets/dist-DMezvBGJ.js +1 -0
- ksadk/server/static/assets/dist-DmFaq_Iy.js +1 -0
- ksadk/server/static/assets/dist-Dp0k7bWI.js +1 -0
- ksadk/server/static/assets/dist-DpKtt4Bo.js +1 -0
- ksadk/server/static/assets/dist-DyOkZmze.js +11 -0
- ksadk/server/static/assets/dist-DyWsb78Q.js +1 -0
- ksadk/server/static/assets/dist-gX6GOLKq.js +1 -0
- ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
- ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
- ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
- ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
- ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
- ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
- ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
- ksadk/server/static/assets/erDiagram-TEJ5UH35-Bk-9bgE5.js +85 -0
- ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
- ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
- ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
- ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-Dn9kZVuZ.js +162 -0
- ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
- ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BGxXlJqm.js +292 -0
- ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
- ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-C7Q0X-wi.js +106 -0
- ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
- ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
- ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
- ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
- ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
- ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
- ksadk/server/static/assets/index-C00Lwg3L.js +165 -0
- ksadk/server/static/assets/index-DAURbCsP.css +1 -0
- ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
- ksadk/server/static/assets/infoDiagram-5YYISTIA-BO_CSqq5.js +2 -0
- ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
- ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-eYLPLd46.js +70 -0
- ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
- ksadk/server/static/assets/journeyDiagram-JHISSGLW-De9geSb0.js +139 -0
- ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
- ksadk/server/static/assets/kanban-definition-UN3LZRKU-Cs80kQto.js +89 -0
- ksadk/server/static/assets/katex-DSrCFD8n.js +265 -0
- ksadk/server/static/assets/linear-CN9sbKTF.js +1 -0
- ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
- ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
- ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
- ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
- ksadk/server/static/assets/mermaid-parser.core-DlgSBU8l.js +161 -0
- ksadk/server/static/assets/mindmap-definition-RKZ34NQL-9afz7Oug.js +96 -0
- ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
- ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
- ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
- ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
- ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
- ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
- ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
- ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
- ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
- ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
- ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
- ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
- ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
- ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
- ksadk/server/static/assets/pieDiagram-4H26LBE5-DPv_VstN.js +30 -0
- ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
- ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
- ksadk/server/static/assets/properties-C357ku5U.js +1 -0
- ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
- ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
- ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
- ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
- ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
- ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-o77wZ1XK.js +7 -0
- ksadk/server/static/assets/r-DADKenSm.js +1 -0
- ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-BYVw81VC.js +84 -0
- ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
- ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
- ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-B7nZFMKT.js +40 -0
- ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
- ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
- ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-BGRRrFw5.js +162 -0
- ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
- ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
- ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
- ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
- ksadk/server/static/assets/solr-BBopId9w.js +1 -0
- ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
- ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
- ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
- ksadk/server/static/assets/stateDiagram-AJRCARHV-BUfHCoPs.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
- ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
- ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
- ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
- ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
- ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
- ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
- ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
- ksadk/server/static/assets/timeline-definition-PNZ67QCA-FbCTGrf9.js +120 -0
- ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
- ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
- ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
- ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
- ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
- ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
- ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
- ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
- ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-DWgwtPsJ.js +34 -0
- ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
- ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
- ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-2x8G-tTM.js +78 -0
- ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
- ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
- ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-Drma3AYl.js +7 -0
- ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
- ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
- ksadk/server/static/favicon.svg +1 -0
- ksadk/server/static/icons.svg +24 -0
- ksadk/server/static/index.html +14 -0
- ksadk/server/terminal_sessions.py +551 -0
- ksadk/sessions/__init__.py +279 -0
- ksadk/sessions/base.py +354 -0
- ksadk/sessions/continuity.py +337 -0
- ksadk/sessions/errors.py +7 -0
- ksadk/sessions/in_memory.py +273 -0
- ksadk/sessions/local_service.py +949 -0
- ksadk/sessions/postgres_service.py +901 -0
- ksadk/sessions/resilience.py +52 -0
- ksadk/sessions/resilient.py +360 -0
- ksadk/sessions/sqlite_service.py +13 -0
- ksadk/skills/__init__.py +9 -0
- ksadk/skills/loader.py +46 -0
- ksadk/skills/models.py +120 -0
- ksadk/skills/package_store.py +104 -0
- ksadk/skills/runtime/__init__.py +17 -0
- ksadk/skills/runtime/agent.py +183 -0
- ksadk/skills/runtime/artifacts.py +39 -0
- ksadk/skills/runtime/backends/__init__.py +1 -0
- ksadk/skills/runtime/backends/disabled.py +8 -0
- ksadk/skills/runtime/backends/e2b.py +168 -0
- ksadk/skills/runtime/backends/local.py +95 -0
- ksadk/skills/runtime/base.py +103 -0
- ksadk/skills/runtime/executor.py +219 -0
- ksadk/skills/runtime/factory.py +37 -0
- ksadk/skills/runtime/loader.py +131 -0
- ksadk/skills/runtime/registry.py +150 -0
- ksadk/skills/runtime/request.py +67 -0
- ksadk/skills/service_client.py +239 -0
- ksadk/skills/service_env.py +58 -0
- ksadk/skills/tool_defs.py +221 -0
- ksadk/terminal_client.py +69 -0
- ksadk/terminal_exec_policy.py +229 -0
- ksadk/tools/__init__.py +17 -0
- ksadk/tools/gateway.py +230 -0
- ksadk/tools/result_budget.py +154 -0
- ksadk/toolsets/__init__.py +854 -0
- ksadk/toolsets/_langchain.py +11 -0
- ksadk/toolsets/platform.py +93 -0
- ksadk/toolsets/sandbox.py +310 -0
- ksadk/toolsets/skills.py +217 -0
- ksadk/toolsets/web.py +266 -0
- ksadk/toolsets/workspace.py +917 -0
- ksadk/toolsets/workspace_state.py +44 -0
- ksadk/tracing/__init__.py +37 -0
- ksadk/tracing/exporters/__init__.py +1 -0
- ksadk/tracing/exporters/inmemory_exporter.py +116 -0
- ksadk/tracing/exporters/langfuse_exporter.py +445 -0
- ksadk/tracing/setup.py +892 -0
- ksadk/tracing/span_utils.py +114 -0
- ksadk/tui/__init__.py +27 -0
- ksadk/tui/loop.py +1877 -0
- ksadk/tui/stream_render.py +68 -0
- ksadk/ui_config.py +168 -0
- ksadk/version.py +4 -0
- ksadk_runtime_common/__init__.py +20 -0
- ksadk_runtime_common/memory_backend/__init__.py +29 -0
- ksadk_runtime_common/memory_backend/manifest.py +79 -0
- ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
- ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
- ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
- ksadk_runtime_common/memory_backend/registry.py +56 -0
- ksadk_runtime_common/memory_backend/render.py +71 -0
- ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
- ksadk_runtime_common/workspace_files/__init__.py +29 -0
- ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
- ksadk_runtime_common/workspace_files/constants.py +9 -0
- ksadk_runtime_common/workspace_files/path_utils.py +71 -0
- ksadk_runtime_common/workspace_files/preview.py +96 -0
- ksadk_runtime_common/workspace_files/router.py +254 -0
- agentengine_sdk_python/__init__.py +0 -3
- agentengine_sdk_python-0.6.4.dist-info/METADATA +0 -48
- agentengine_sdk_python-0.6.4.dist-info/RECORD +0 -5
- 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
|