agentengine-sdk-python 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +43 -7
- agentengine_sdk_python-0.8.2.dist-info/RECORD +778 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +1 -1
- ksadk/a2a/__init__.py +2 -0
- ksadk/a2a/_space_client_events.py +381 -0
- ksadk/a2a/bootstrap.py +0 -2
- ksadk/a2a/card.py +6 -111
- ksadk/a2a/control_plane.py +239 -1
- ksadk/a2a/event_adapter.py +156 -64
- ksadk/a2a/executor.py +195 -172
- ksadk/a2a/routes.py +12 -5
- ksadk/a2a/server.py +1 -8
- ksadk/a2a/service_env.py +33 -0
- ksadk/a2a/space_client.py +43 -349
- ksadk/a2a_card.py +101 -0
- ksadk/a2ui/core.py +222 -53
- ksadk/agui/_agent_helpers.py +243 -0
- ksadk/agui/a2ui_projection.py +12 -5
- ksadk/agui/agent.py +474 -275
- ksadk/agui/config.py +11 -5
- ksadk/agui/routes.py +7 -15
- ksadk/api/client.py +530 -40
- ksadk/api/environment.py +27 -0
- ksadk/builders/code_builder.py +352 -53
- ksadk/builders/container_builder.py +71 -44
- ksadk/builders/framework_requirements.py +2 -1
- ksadk/builders/ks3_uploader.py +2 -2
- ksadk/builders/managed_runtime_builder.py +60 -28
- ksadk/cli/__init__.py +25 -0
- ksadk/cli/cmd_a2a.py +18 -31
- ksadk/cli/cmd_create.py +13 -26
- ksadk/cli/cmd_dashboard.py +15 -1
- ksadk/cli/cmd_deploy.py +28 -3
- ksadk/cli/cmd_eval.py +443 -0
- ksadk/cli/cmd_evalset.py +284 -0
- ksadk/cli/cmd_files.py +5 -1
- ksadk/cli/cmd_hermes.py +137 -163
- ksadk/cli/cmd_invoke.py +28 -38
- ksadk/cli/cmd_launch.py +12 -0
- ksadk/cli/cmd_managed_runtime.py +95 -0
- ksadk/cli/cmd_observe.py +85 -0
- ksadk/cli/cmd_openclaw.py +85 -479
- ksadk/cli/cmd_replay.py +28 -4
- ksadk/cli/cmd_run.py +29 -67
- ksadk/cli/cmd_studio.py +201 -0
- ksadk/cli/cmd_web.py +15 -6
- ksadk/cli/dry_run.py +2 -1
- ksadk/cli/env_options.py +83 -0
- ksadk/cli/global_options.py +3 -1
- ksadk/cli/hermes_env.py +151 -0
- ksadk/cli/invoke_payload.py +49 -0
- ksadk/cli/network_options.py +7 -5
- ksadk/cli/openclaw_env.py +458 -0
- ksadk/cli/runtime_bootstrap.py +51 -0
- ksadk/cli/ui.py +6 -0
- ksadk/cli/workflow_common.py +5 -5
- ksadk/codex/__init__.py +3 -3
- ksadk/codex/client.py +833 -49
- ksadk/codex/phase.py +1 -1
- ksadk/codex/runtime.py +720 -127
- ksadk/common/kop_client.py +232 -0
- ksadk/configs/__init__.py +1 -3
- ksadk/configs/env_registry.py +244 -45
- ksadk/configs/env_registry_pcm.py +181 -0
- ksadk/configs/env_var_spec.py +18 -0
- ksadk/configs/global_config.py +6 -1
- ksadk/configs/settings.py +5 -108
- ksadk/context_engine/__init__.py +98 -0
- ksadk/context_engine/assembler.py +176 -0
- ksadk/context_engine/baseline.py +419 -0
- ksadk/context_engine/cache_observability.py +228 -0
- ksadk/context_engine/capabilities.py +443 -0
- ksadk/context_engine/contributors.py +344 -0
- ksadk/context_engine/hosted_pipeline.py +417 -0
- ksadk/context_engine/models.py +117 -0
- ksadk/context_engine/planner.py +698 -0
- ksadk/context_engine/policies.py +319 -0
- ksadk/context_engine/projection.py +31 -0
- ksadk/context_engine/shadow_plan.py +266 -0
- ksadk/context_engine/tokenizer.py +164 -0
- ksadk/conversations/__init__.py +85 -94
- ksadk/conversations/context.py +126 -6
- ksadk/conversations/message_projection.py +292 -28
- ksadk/conversations/model_context.py +49 -3
- ksadk/conversations/runtime_compaction.py +207 -5
- ksadk/conversations/runtime_input.py +193 -3
- ksadk/conversations/runtime_invocation.py +81 -3
- ksadk/conversations/runtime_observability.py +157 -4
- ksadk/conversations/runtime_payloads.py +42 -0
- ksadk/conversations/runtime_preparation.py +280 -2
- ksadk/conversations/runtime_resume.py +71 -32
- ksadk/conversations/runtime_stream_events.py +94 -9
- ksadk/conversations/runtime_streaming.py +175 -22
- ksadk/conversations/semantic_summary.py +311 -1
- ksadk/conversations/session_lock.py +51 -0
- ksadk/deployment/env_forward.py +72 -0
- ksadk/deployment/managed_runtime.py +38 -0
- ksadk/deployment/manager.py +24 -8
- ksadk/deployment/providers/serverless.py +228 -92
- ksadk/evaluation/__init__.py +116 -0
- ksadk/evaluation/a2a_adapter.py +452 -0
- ksadk/evaluation/adapters.py +67 -0
- ksadk/evaluation/agent_eval_client.py +395 -0
- ksadk/evaluation/cloud_binding.py +80 -0
- ksadk/evaluation/cloud_converter.py +194 -0
- ksadk/evaluation/cloud_service.py +199 -0
- ksadk/evaluation/contracts.py +478 -0
- ksadk/evaluation/evalset.py +351 -0
- ksadk/evaluation/evaluators.py +627 -0
- ksadk/evaluation/evidence.py +258 -0
- ksadk/evaluation/executor.py +143 -0
- ksadk/evaluation/local_adapter.py +550 -0
- ksadk/evaluation/service_env.py +27 -0
- ksadk/evaluation/storage.py +68 -0
- ksadk/evaluation/studio_build_adapter.py +253 -0
- ksadk/evaluation/target.py +82 -0
- ksadk/events/__init__.py +13 -13
- ksadk/events/_v1_compat/__init__.py +1 -0
- ksadk/events/_v1_compat/models.py +299 -0
- ksadk/events/_v1_compat/parser.py +247 -0
- ksadk/events/_v1_compat/projection.py +753 -0
- ksadk/events/adapters/__init__.py +34 -0
- ksadk/events/adapters/_a2a_snapshot.py +622 -0
- ksadk/events/adapters/_a2a_support.py +275 -0
- ksadk/events/adapters/_codex_interactions.py +375 -0
- ksadk/events/adapters/_codex_items.py +801 -0
- ksadk/events/adapters/_codex_validators.py +311 -0
- ksadk/events/adapters/_langgraph_support.py +785 -0
- ksadk/events/adapters/a2a.py +841 -0
- ksadk/events/adapters/adk.py +503 -0
- ksadk/events/adapters/codex.py +717 -0
- ksadk/events/adapters/langgraph.py +745 -0
- ksadk/events/canonical.py +440 -0
- ksadk/events/canonical_replay.py +497 -0
- ksadk/events/canonical_store.py +468 -0
- ksadk/events/cold_recovery.py +257 -0
- ksadk/events/content.py +89 -0
- ksadk/events/identity.py +86 -0
- ksadk/events/pipeline.py +455 -0
- ksadk/events/projections.py +66 -0
- ksadk/events/reducer.py +661 -0
- ksadk/events/replay.py +3 -36
- ksadk/events/runtime_event.py +20 -256
- ksadk/events/session_event.py +324 -0
- ksadk/events/store.py +3 -316
- ksadk/events/v1_compat.py +41 -0
- ksadk/harness/__init__.py +3 -2
- ksadk/harness/app.py +114 -123
- ksadk/harness/config.py +1 -1
- ksadk/harness/runtime.py +467 -0
- ksadk/hermes_terminal.py +5 -0
- ksadk/identity/resolver.py +4 -3
- ksadk/interaction/__init__.py +28 -0
- ksadk/interaction/contracts.py +136 -0
- ksadk/interaction/ledger.py +189 -0
- ksadk/interaction/provider.py +122 -0
- ksadk/interaction/providers/__init__.py +52 -0
- ksadk/interaction/providers/adk.py +40 -0
- ksadk/interaction/providers/codex.py +94 -0
- ksadk/interaction/providers/langgraph.py +58 -0
- ksadk/kernel/__init__.py +117 -0
- ksadk/kernel/authorization.py +258 -0
- ksadk/kernel/bootstrap.py +1074 -0
- ksadk/kernel/contract_fingerprints.py +61 -0
- ksadk/kernel/contracts.py +362 -0
- ksadk/kernel/control.py +219 -0
- ksadk/kernel/errors.py +79 -0
- ksadk/kernel/ingress.py +1102 -0
- ksadk/kernel/mapping.py +111 -0
- ksadk/kernel/memory_store.py +1133 -0
- ksadk/kernel/postgres_store.py +1756 -0
- ksadk/kernel/recovery.py +452 -0
- ksadk/kernel/runtime_identity.py +103 -0
- ksadk/kernel/sql/001_agent_kernel.sql +120 -0
- ksadk/kernel/sqlite_store.py +1410 -0
- ksadk/kernel/state.py +145 -0
- ksadk/kernel/store.py +232 -0
- ksadk/kernel/worker.py +953 -0
- ksadk/knowledge_base/client.py +22 -4
- ksadk/knowledge_base/service.py +32 -5
- ksadk/managed_a2a_card.py +160 -0
- ksadk/managed_runtime.py +16 -9
- ksadk/memory/__init__.py +61 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
- ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
- ksadk/memory/adk/backends/sdk_ltm_backend.py +394 -5
- ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
- ksadk/memory/adk/long_term_memory.py +3 -2
- ksadk/memory/coordinator.py +461 -0
- ksadk/memory/events.py +202 -0
- ksadk/memory/extraction.py +228 -0
- ksadk/memory/ltm_backend_factory.py +16 -3
- ksadk/memory/models.py +316 -0
- ksadk/memory/policy.py +215 -0
- ksadk/memory/provider.py +47 -0
- ksadk/memory/provider_adapter.py +159 -0
- ksadk/memory/provider_resolver.py +71 -0
- ksadk/memory/providers/__init__.py +5 -0
- ksadk/memory/providers/local_sqlite.py +490 -0
- ksadk/memory/resolved_policy.py +145 -0
- ksadk/memory/service.py +198 -8
- ksadk/memory/tool.py +4 -0
- ksadk/model_proxy/bootstrap.py +16 -9
- ksadk/model_proxy/config.py +22 -1
- ksadk/model_proxy/detect.py +202 -9
- ksadk/model_proxy/namespace.py +67 -15
- ksadk/model_proxy/server.py +168 -6
- ksadk/model_proxy/transform.py +105 -3
- ksadk/observability/__init__.py +25 -0
- ksadk/observability/session_log.py +420 -0
- ksadk/observability/trajectory.py +105 -0
- ksadk/prompts/__init__.py +71 -0
- ksadk/prompts/compiler.py +215 -0
- ksadk/prompts/models.py +82 -0
- ksadk/prompts/projection.py +89 -0
- ksadk/prompts/resolved.py +137 -0
- ksadk/prompts/sources.py +233 -0
- ksadk/runners/_langgraph_runner_streams.py +822 -0
- ksadk/runners/adk_runner.py +14 -31
- ksadk/runners/base_runner.py +44 -3
- ksadk/runners/factory.py +3 -3
- ksadk/runners/langgraph_runner.py +252 -433
- ksadk/runners/utils/__init__.py +0 -10
- ksadk/runners/utils/loader.py +16 -0
- ksadk/runtime/__init__.py +67 -41
- ksadk/runtime/_runner_adapter/__init__.py +1 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
- ksadk/runtime/adapter.py +199 -16
- ksadk/runtime/conversation_execution.py +687 -0
- ksadk/runtime/executor.py +392 -0
- ksadk/runtime/factory.py +342 -0
- ksadk/runtime/framework_adapters.py +6 -12
- ksadk/runtime/hosted_finalizer.py +268 -0
- ksadk/runtime/launch.py +61 -0
- ksadk/runtime/preprocessing.py +49 -14
- ksadk/runtime/runner_adapter.py +330 -391
- ksadk/runtime/runner_loading.py +36 -0
- ksadk/runtime/usage.py +33 -0
- ksadk/sandbox/backends/e2b.py +36 -3
- ksadk/server/__init__.py +39 -4
- ksadk/server/app.py +18 -492
- ksadk/server/composition.py +36 -2
- ksadk/server/factory.py +210 -149
- ksadk/server/routes/common.py +15 -46
- ksadk/server/routes/control.py +13 -9
- ksadk/server/routes/kernel_ingress.py +258 -0
- ksadk/server/routes/misc.py +10 -33
- ksadk/server/routes/models.py +14 -6
- ksadk/server/routes/openai_compat.py +129 -27
- ksadk/server/routes/projection.py +84 -16
- ksadk/server/routes/run.py +347 -460
- ksadk/server/routes/sessions.py +37 -40
- ksadk/server/routes/streaming.py +52 -7
- ksadk/server/routes/workspace.py +54 -21
- ksadk/server/static/assets/{ArtifactsPanel-Cjnqt-8W.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-BqyqZfuB.js → CodeBlock-DOH6MVcn.js} +4 -4
- ksadk/server/static/assets/{MathMessageMarkdown-BpH7jiFM.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
- ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
- ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
- ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
- ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
- ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
- ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
- ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
- ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
- ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
- ksadk/server/static/assets/{chunk-FMBD7UC4-BGhvzDYt.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-D98vPd-E.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
- ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
- ksadk/server/static/assets/{chunk-4BX2VUAB-CsWIQRhc.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
- ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
- ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
- ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
- ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
- ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-CX3yotHq.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
- ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
- ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
- ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
- ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
- ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
- ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
- ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
- ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
- ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
- ksadk/server/static/assets/{dist-D82mtD2s.js → dist-8hqcx0sU.js} +1 -1
- ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
- ksadk/server/static/assets/{dist-DHn6nBzr.js → dist-BE7bu-DL.js} +1 -1
- ksadk/server/static/assets/{dist-eoW6tZXA.js → dist-BVDffZ0U.js} +1 -1
- ksadk/server/static/assets/{dist-C2xjE7cO.js → dist-BX8z72IC.js} +1 -1
- ksadk/server/static/assets/{dist-CgF3fj15.js → dist-Bf2M8m3N.js} +1 -1
- ksadk/server/static/assets/{dist-Bs-jQcRX.js → dist-BjU6y-A2.js} +1 -1
- ksadk/server/static/assets/{dist-Djgl2wVZ.js → dist-BytlxIDT.js} +1 -1
- ksadk/server/static/assets/{dist-CVQRZjPu.js → dist-CSh_DE14.js} +1 -1
- ksadk/server/static/assets/{dist-6CVKuTAe.js → dist-CXYYBw3_.js} +1 -1
- ksadk/server/static/assets/{dist-DlzCRy0S.js → dist-C_wsv-Qd.js} +1 -1
- ksadk/server/static/assets/{dist-DF06ORQo.js → dist-ClxGviKw.js} +1 -1
- ksadk/server/static/assets/{dist-Cf23NY4L.js → dist-CttYUqzS.js} +1 -1
- ksadk/server/static/assets/{dist-ivOya1LD.js → dist-DNp6rLRA.js} +1 -1
- ksadk/server/static/assets/{dist-BibcG1V9.js → dist-DUX-id_F.js} +1 -1
- ksadk/server/static/assets/{dist-BUHRxvDG.js → dist-DmldrHd_.js} +1 -1
- ksadk/server/static/assets/{dist-BZ9n9AjO.js → dist-DnfXs8Vn.js} +2 -2
- ksadk/server/static/assets/{dist-DqNnNFrw.js → dist-Dq9gLD7L.js} +1 -1
- ksadk/server/static/assets/{dist-CGcp2ex8.js → dist-W-TIVntx.js} +1 -1
- ksadk/server/static/assets/{dist-Cwt7koer.js → dist-dDdADKFA.js} +1 -1
- ksadk/server/static/assets/{dist-C7-3jInC.js → dist-dydu68Fl.js} +1 -1
- ksadk/server/static/assets/{dist-C99THtIA.js → dist-ngtN0DJB.js} +1 -1
- ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
- ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
- ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
- ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
- ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
- ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-DLOHGvWw.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-BIzKeejT.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-BWHoFSw2.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
- ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
- ksadk/server/static/assets/{linear-CvMBw9cA.js → linear-BHjG8b-J.js} +1 -1
- ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-CSAQxEAe.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
- ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
- ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-CxIR4JXI.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
- ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
- ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-DpGa6EAV.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-NTKNGGe5.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
- ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
- ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
- ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-jqoYs3jQ.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
- ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
- ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
- ksadk/server/static/index.html +2 -2
- ksadk/sessions/__init__.py +26 -9
- ksadk/sessions/_local_service_sync.py +688 -0
- ksadk/sessions/_local_tables.py +21 -0
- ksadk/sessions/_postgres_schema.py +305 -0
- ksadk/sessions/_postgres_tables.py +15 -0
- ksadk/sessions/base.py +80 -1
- ksadk/sessions/in_memory.py +53 -2
- ksadk/sessions/local_service.py +63 -604
- ksadk/sessions/postgres_service.py +58 -148
- ksadk/sessions/resilient.py +35 -1
- ksadk/skills/runtime/backends/e2b.py +1 -1
- ksadk/skills/service_client.py +13 -3
- ksadk/studio/__init__.py +5 -0
- ksadk/studio/agent_avatar_assets.py +120 -0
- ksadk/studio/agent_lifecycle.py +51 -0
- ksadk/studio/api.py +1598 -0
- ksadk/studio/api_catalog_routes.py +324 -0
- ksadk/studio/api_contracts.py +286 -0
- ksadk/studio/api_helpers.py +132 -0
- ksadk/studio/api_memory_routes.py +82 -0
- ksadk/studio/authoring.py +749 -0
- ksadk/studio/authoring_coordinator.py +1017 -0
- ksadk/studio/builder.py +397 -0
- ksadk/studio/capabilities.py +368 -0
- ksadk/studio/cloud.py +2099 -0
- ksadk/studio/codex_agent_service.py +694 -0
- ksadk/studio/codex_authoring.py +469 -0
- ksadk/studio/codex_builder.py +437 -0
- ksadk/studio/codex_manifest.py +307 -0
- ksadk/studio/codex_run.py +395 -0
- ksadk/studio/compiler.py +190 -0
- ksadk/studio/contracts.py +755 -0
- ksadk/studio/errors.py +47 -0
- ksadk/studio/event_store.py +214 -0
- ksadk/studio/framework_run.py +342 -0
- ksadk/studio/hosted_kernel.py +317 -0
- ksadk/studio/identifiers.py +33 -0
- ksadk/studio/manifest_resolver.py +131 -0
- ksadk/studio/mcp_runtime.py +229 -0
- ksadk/studio/model_client.py +629 -0
- ksadk/studio/model_profile_service.py +214 -0
- ksadk/studio/operations.py +275 -0
- ksadk/studio/otel_trace.py +1060 -0
- ksadk/studio/pagination.py +182 -0
- ksadk/studio/pcm_memory.py +85 -0
- ksadk/studio/python_tool_inspection.py +183 -0
- ksadk/studio/repository.py +327 -0
- ksadk/studio/resource_catalog.py +1260 -0
- ksadk/studio/run_service.py +1133 -0
- ksadk/studio/runtime_catalog.py +74 -0
- ksadk/studio/runtime_source.py +365 -0
- ksadk/studio/service.py +2249 -0
- ksadk/studio/shared_web.py +1004 -0
- ksadk/studio/skill_discovery.py +628 -0
- ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
- ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
- ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
- ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
- ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
- ksadk/studio/static/index.html +15 -0
- ksadk/studio/templates.py +722 -0
- ksadk/studio/validator.py +265 -0
- ksadk/studio/workspace.py +135 -0
- ksadk/tools/gateway.py +9 -0
- ksadk/toolsets/__init__.py +27 -1
- ksadk/toolsets/a2a.py +426 -0
- ksadk/tracing/__init__.py +19 -21
- ksadk/tracing/setup.py +317 -403
- ksadk/tracing/span_utils.py +0 -44
- ksadk/version.py +1 -1
- ksadk_runtime_common/memory_backend/registry.py +56 -56
- ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
- ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
- ksadk_runtime_common/workspace_files/router.py +8 -2
- agentengine_sdk_python-0.8.0.dist-info/RECORD +0 -578
- ksadk/a2a/card_builder.py +0 -64
- ksadk/a2a/client.py +0 -309
- ksadk/events/parser.py +0 -191
- ksadk/harness/runner.py +0 -211
- ksadk/runners/codex_runner.py +0 -132
- ksadk/runners/utils/langfuse.py +0 -230
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
- ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +0 -1
- ksadk/server/static/assets/arc-CFKwSk_B.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +0 -1
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-Dej8jX8c.js +0 -36
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-1F64jqvy.js +0 -132
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-9q7AUAKb.js +0 -10
- ksadk/server/static/assets/channel-iUGv2N24.js +0 -1
- ksadk/server/static/assets/chunk-2J33WTMH-6qgwJm7Q.js +0 -1
- ksadk/server/static/assets/chunk-55IACEB6-BK8paA20.js +0 -1
- ksadk/server/static/assets/chunk-727SXJPM-B-JEhy7h.js +0 -206
- ksadk/server/static/assets/chunk-AQP2D5EJ-DYBvMHAa.js +0 -231
- ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +0 -1
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
- ksadk/server/static/assets/dagre-BM42HDAG-C7TguAWo.js +0 -4
- ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
- ksadk/server/static/assets/diagram-2AECGRRQ-Dd17_kws.js +0 -43
- ksadk/server/static/assets/diagram-5GNKFQAL-DVIClIil.js +0 -10
- ksadk/server/static/assets/diagram-KO2AKTUF-Df_3gYQa.js +0 -3
- ksadk/server/static/assets/diagram-LMA3HP47-BhLk6ZtA.js +0 -24
- ksadk/server/static/assets/diagram-OG6HWLK6-D4GNGvuu.js +0 -24
- ksadk/server/static/assets/dist-DyvYLMW9.js +0 -1
- ksadk/server/static/assets/erDiagram-TEJ5UH35-CnfRcY2M.js +0 -85
- ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +0 -1
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-DGaJjGNJ.js +0 -162
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-DpCoB2g9.js +0 -292
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +0 -1
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-9IKVFBVT.js +0 -106
- ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
- ksadk/server/static/assets/index-QSwCHuB8.js +0 -177
- ksadk/server/static/assets/index-jOPmTOZH.css +0 -2
- ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +0 -1
- ksadk/server/static/assets/infoDiagram-5YYISTIA-CYIIsb8y.js +0 -2
- ksadk/server/static/assets/katex-DATssjp1.js +0 -265
- ksadk/server/static/assets/mermaid-parser.core-mGAcEwcq.js +0 -161
- ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +0 -1
- ksadk/server/static/assets/pieDiagram-4H26LBE5-By-ra07M.js +0 -30
- ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +0 -1
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DpW90iu3.js +0 -84
- ksadk/server/static/assets/stateDiagram-AJRCARHV-DGpd9yFG.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +0 -1
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-l3Z7-wbP.js +0 -34
- ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +0 -1
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-BR54GPmr.js +0 -78
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-SkiH8GsK.js +0 -7
- ksadk/tracing/exporters/langfuse_exporter.py +0 -460
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
ksadk/tracing/setup.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Tracing 初始化 -
|
|
2
|
+
Tracing 初始化 - 标准 OTLP 双写(Langfuse 主 + CloudMonitor 次)
|
|
3
|
+
|
|
4
|
+
每个 Python Agent 进程一个 TracerProvider,挂两个 OTLP/HTTP protobuf BatchSpanProcessor,
|
|
5
|
+
消费同一批 ReadableSpan。仅为 CloudMonitor 克隆并补充兼容属性,不修改原始 span。
|
|
6
|
+
|
|
3
7
|
支持 ADK 自动插桩 via openinference-instrumentation-google-adk
|
|
4
8
|
"""
|
|
5
9
|
|
|
6
10
|
import atexit
|
|
7
|
-
import base64
|
|
8
11
|
import importlib
|
|
12
|
+
import json
|
|
9
13
|
import logging
|
|
10
14
|
import os
|
|
11
15
|
from collections.abc import Callable, Sequence
|
|
@@ -13,6 +17,7 @@ from dataclasses import dataclass
|
|
|
13
17
|
from typing import Any, Optional
|
|
14
18
|
from urllib.parse import unquote
|
|
15
19
|
|
|
20
|
+
from opentelemetry.sdk.resources import Resource
|
|
16
21
|
from opentelemetry.sdk.trace import ReadableSpan
|
|
17
22
|
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
18
23
|
|
|
@@ -20,6 +25,25 @@ from ksadk.tracing.exporters.inmemory_exporter import InMemoryExporter
|
|
|
20
25
|
|
|
21
26
|
logger = logging.getLogger(__name__)
|
|
22
27
|
|
|
28
|
+
_CLOUD_MONITOR_RESOURCE_SPAN_ATTRIBUTES = (
|
|
29
|
+
"agentengine.account_id",
|
|
30
|
+
"agentengine.agent_id",
|
|
31
|
+
"agentengine.framework",
|
|
32
|
+
"agentengine.langfuse_project_id",
|
|
33
|
+
"agentengine.region",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
_LANGFUSE_OBSERVATION_TYPES = {
|
|
37
|
+
"LLM": "generation",
|
|
38
|
+
"EMBEDDING": "embedding",
|
|
39
|
+
"CHAIN": "chain",
|
|
40
|
+
"AGENT": "agent",
|
|
41
|
+
"TOOL": "tool",
|
|
42
|
+
"RETRIEVER": "retriever",
|
|
43
|
+
"EVALUATOR": "evaluator",
|
|
44
|
+
"GUARDRAIL": "guardrail",
|
|
45
|
+
}
|
|
46
|
+
|
|
23
47
|
|
|
24
48
|
def _batch_span_processor_kwargs() -> dict:
|
|
25
49
|
"""BatchSpanProcessor 参数:限制单次 export 体积,避免 OTLP collector 413。
|
|
@@ -41,9 +65,9 @@ def _batch_span_processor_kwargs() -> dict:
|
|
|
41
65
|
|
|
42
66
|
|
|
43
67
|
_exporter_instance: Optional[InMemoryExporter] = None
|
|
44
|
-
_langfuse_exporter: Optional[Any] = None
|
|
45
68
|
_tracing_initialized: bool = False
|
|
46
69
|
_adk_instrumented: bool = False
|
|
70
|
+
_managed_span_processors: list[Any] = []
|
|
47
71
|
|
|
48
72
|
|
|
49
73
|
@dataclass(frozen=True)
|
|
@@ -55,7 +79,7 @@ class _OtlpHttpConfig:
|
|
|
55
79
|
|
|
56
80
|
|
|
57
81
|
class _LoggingSpanExporter(SpanExporter):
|
|
58
|
-
"""
|
|
82
|
+
"""Delegating exporter that logs external trace export flow."""
|
|
59
83
|
|
|
60
84
|
def __init__(
|
|
61
85
|
self,
|
|
@@ -137,12 +161,7 @@ def _span_context(span: Any) -> Any:
|
|
|
137
161
|
if context is not None:
|
|
138
162
|
return context
|
|
139
163
|
get_context = getattr(span, "get_span_context", None)
|
|
140
|
-
if callable(get_context)
|
|
141
|
-
try:
|
|
142
|
-
return get_context()
|
|
143
|
-
except Exception:
|
|
144
|
-
return None
|
|
145
|
-
return None
|
|
164
|
+
return get_context() if callable(get_context) else None
|
|
146
165
|
|
|
147
166
|
|
|
148
167
|
def _span_id(span: Any) -> Optional[int]:
|
|
@@ -159,230 +178,244 @@ def _parent_span_id(span: Any) -> Optional[int]:
|
|
|
159
178
|
|
|
160
179
|
def _span_token_usage(span: Any) -> dict[str, int]:
|
|
161
180
|
attributes = getattr(span, "attributes", None) or {}
|
|
181
|
+
return _token_usage(attributes)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _token_usage(attributes: dict[str, Any]) -> dict[str, int]:
|
|
185
|
+
input_tokens = _coerce_int(
|
|
186
|
+
attributes.get("gen_ai.usage.input_tokens")
|
|
187
|
+
or attributes.get("llm.usage.prompt_tokens")
|
|
188
|
+
or attributes.get("llm.token_count.prompt")
|
|
189
|
+
)
|
|
190
|
+
output_tokens = _coerce_int(
|
|
191
|
+
attributes.get("gen_ai.usage.output_tokens")
|
|
192
|
+
or attributes.get("llm.usage.completion_tokens")
|
|
193
|
+
or attributes.get("llm.token_count.completion")
|
|
194
|
+
)
|
|
195
|
+
total_tokens = _coerce_int(
|
|
196
|
+
attributes.get("gen_ai.usage.total_tokens")
|
|
197
|
+
or attributes.get("llm.usage.total_tokens")
|
|
198
|
+
or attributes.get("llm.token_count.total")
|
|
199
|
+
)
|
|
162
200
|
return {
|
|
163
|
-
"input":
|
|
164
|
-
"output":
|
|
165
|
-
"
|
|
166
|
-
"
|
|
201
|
+
"input": input_tokens,
|
|
202
|
+
"output": output_tokens,
|
|
203
|
+
"total": total_tokens or input_tokens + output_tokens,
|
|
204
|
+
"cache_read": _coerce_int(
|
|
205
|
+
attributes.get("gen_ai.usage.cache_read.input_tokens")
|
|
206
|
+
or attributes.get("llm.usage.cache_read.input_tokens")
|
|
207
|
+
),
|
|
208
|
+
"reasoning_output": _coerce_int(
|
|
209
|
+
attributes.get("gen_ai.usage.reasoning.output_tokens")
|
|
210
|
+
or attributes.get("llm.usage.reasoning_tokens")
|
|
211
|
+
),
|
|
167
212
|
}
|
|
168
213
|
|
|
169
214
|
|
|
170
|
-
def
|
|
171
|
-
|
|
215
|
+
def _first_span_attribute(attributes: dict[str, Any], *keys: str) -> Any:
|
|
216
|
+
for key in keys:
|
|
217
|
+
value = attributes.get(key)
|
|
218
|
+
if value not in (None, ""):
|
|
219
|
+
return value
|
|
220
|
+
return None
|
|
172
221
|
|
|
173
222
|
|
|
174
|
-
def
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return str(name or "")
|
|
223
|
+
def _add_langfuse_compatibility_attributes(attributes: dict[str, Any]) -> None:
|
|
224
|
+
usage = _token_usage(attributes)
|
|
225
|
+
observation_kind = str(attributes.get("openinference.span.kind") or "").upper()
|
|
226
|
+
observation_type = _LANGFUSE_OBSERVATION_TYPES.get(observation_kind)
|
|
227
|
+
if observation_type is None:
|
|
228
|
+
observation_type = "generation" if _has_nonzero_token_usage(usage) else "span"
|
|
229
|
+
attributes.setdefault("langfuse.observation.type", observation_type)
|
|
182
230
|
|
|
231
|
+
provider = _first_span_attribute(
|
|
232
|
+
attributes,
|
|
233
|
+
"gen_ai.provider.name",
|
|
234
|
+
"gen_ai.system",
|
|
235
|
+
"llm.provider",
|
|
236
|
+
)
|
|
237
|
+
if provider is not None:
|
|
238
|
+
attributes.setdefault("langfuse.observation.metadata.ls_provider", provider)
|
|
183
239
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
240
|
+
model = _first_span_attribute(
|
|
241
|
+
attributes,
|
|
242
|
+
"gen_ai.request.model",
|
|
243
|
+
"llm.model_name",
|
|
244
|
+
)
|
|
245
|
+
if model is not None:
|
|
246
|
+
attributes.setdefault("langfuse.observation.model.name", model)
|
|
247
|
+
attributes.setdefault("langfuse.observation.metadata.ls_model_name", model)
|
|
248
|
+
|
|
249
|
+
if _has_nonzero_token_usage(usage):
|
|
250
|
+
attributes.setdefault("gen_ai.usage.total_tokens", usage["total"])
|
|
251
|
+
usage_details = {
|
|
252
|
+
"input": usage["input"],
|
|
253
|
+
"output": usage["output"],
|
|
254
|
+
"total": usage["total"],
|
|
255
|
+
"input_cache_read": usage["cache_read"],
|
|
256
|
+
}
|
|
257
|
+
attributes["langfuse.observation.usage_details"] = json.dumps(
|
|
258
|
+
usage_details, separators=(",", ":"), sort_keys=True
|
|
202
259
|
)
|
|
203
|
-
except Exception:
|
|
204
|
-
logger.debug("Failed to clone span for CloudMonitor token compatibility", exc_info=True)
|
|
205
|
-
return span
|
|
206
260
|
|
|
207
261
|
|
|
208
|
-
def
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
OpenInference LangChain instrumentation can emit llm.token_count.* on nested
|
|
212
|
-
ChatOpenAI spans. In LangGraph streaming these counts can be cumulative and
|
|
213
|
-
disagree with the usage returned by ksadk to RunAgent/session state. When a
|
|
214
|
-
trace already has a ksadk.conversations span with gen_ai.usage.*, strip token
|
|
215
|
-
attrs from nested OpenInference LangChain spans before exporting to
|
|
216
|
-
Langfuse. The child spans remain useful for timing and structure, but cannot
|
|
217
|
-
pollute token accounting.
|
|
218
|
-
"""
|
|
219
|
-
if not spans:
|
|
220
|
-
return spans
|
|
221
|
-
span_list = list(spans)
|
|
222
|
-
authoritative_trace_ids = {
|
|
223
|
-
_trace_id(span)
|
|
224
|
-
for span in span_list
|
|
225
|
-
if _trace_id(span) is not None
|
|
226
|
-
and _instrumentation_scope_name(span) == "ksadk.conversations"
|
|
227
|
-
and _has_nonzero_token_usage(_span_token_usage(span))
|
|
228
|
-
}
|
|
229
|
-
if not authoritative_trace_ids:
|
|
230
|
-
return spans
|
|
231
|
-
|
|
232
|
-
strip_prefixes = ("llm.token_count.", "llm.usage.", "gen_ai.usage.")
|
|
233
|
-
replacements: dict[int, Any] = {}
|
|
234
|
-
for span in span_list:
|
|
235
|
-
if _trace_id(span) not in authoritative_trace_ids:
|
|
236
|
-
continue
|
|
237
|
-
if _instrumentation_scope_name(span) != "openinference.instrumentation.langchain":
|
|
238
|
-
continue
|
|
239
|
-
attributes = dict(getattr(span, "attributes", None) or {})
|
|
240
|
-
stripped = {
|
|
241
|
-
key: value
|
|
242
|
-
for key, value in attributes.items()
|
|
243
|
-
if not any(str(key).startswith(prefix) for prefix in strip_prefixes)
|
|
244
|
-
}
|
|
245
|
-
if len(stripped) != len(attributes):
|
|
246
|
-
if str(stripped.get("openinference.span.kind") or "").upper() == "LLM":
|
|
247
|
-
stripped["openinference.span.kind"] = "CHAIN"
|
|
248
|
-
replacements[id(span)] = _clone_span_with_attributes(span, stripped)
|
|
262
|
+
def _has_nonzero_token_usage(usage: dict[str, int]) -> bool:
|
|
263
|
+
return any(value > 0 for value in usage.values())
|
|
249
264
|
|
|
250
|
-
if not replacements:
|
|
251
|
-
return spans
|
|
252
|
-
return [replacements.get(id(span), span) for span in span_list]
|
|
253
265
|
|
|
266
|
+
def _clone_cloud_monitor_resource(resource: Resource | None) -> Resource | None:
|
|
267
|
+
if resource is None:
|
|
268
|
+
return None
|
|
269
|
+
attributes = dict(resource.attributes)
|
|
270
|
+
agent_id = str(attributes.get("agentengine.agent_id") or "").strip()
|
|
271
|
+
if not agent_id or attributes.get("service.name") == agent_id:
|
|
272
|
+
return resource
|
|
273
|
+
attributes["service.name"] = agent_id
|
|
274
|
+
return Resource(attributes, schema_url=resource.schema_url)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _clone_span(
|
|
278
|
+
span: ReadableSpan,
|
|
279
|
+
*,
|
|
280
|
+
attributes: dict[str, Any],
|
|
281
|
+
resource: Resource | None,
|
|
282
|
+
) -> ReadableSpan:
|
|
283
|
+
return ReadableSpan(
|
|
284
|
+
name=span.name,
|
|
285
|
+
context=span.context,
|
|
286
|
+
parent=span.parent,
|
|
287
|
+
resource=resource,
|
|
288
|
+
attributes=attributes,
|
|
289
|
+
events=span.events,
|
|
290
|
+
links=span.links,
|
|
291
|
+
kind=span.kind,
|
|
292
|
+
status=span.status,
|
|
293
|
+
start_time=span.start_time,
|
|
294
|
+
end_time=span.end_time,
|
|
295
|
+
instrumentation_scope=getattr(span, "instrumentation_scope", None),
|
|
296
|
+
)
|
|
254
297
|
|
|
255
|
-
def _prepare_cloud_monitor_spans(spans: Any) -> Any:
|
|
256
|
-
"""Add root token rollups for CloudMonitor without changing other exporters.
|
|
257
298
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
token-bearing leaf spans and copy the result to the root span for this exporter.
|
|
263
|
-
"""
|
|
299
|
+
def _prepare_cloud_monitor_spans(
|
|
300
|
+
spans: Sequence[ReadableSpan],
|
|
301
|
+
) -> Sequence[ReadableSpan]:
|
|
302
|
+
"""Clone CloudMonitor spans with resource identity and root usage compatibility."""
|
|
264
303
|
if not spans:
|
|
265
304
|
return spans
|
|
266
305
|
span_list = list(spans)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
306
|
+
session_ids_by_trace: dict[Optional[int], Any] = {}
|
|
307
|
+
for span in span_list:
|
|
308
|
+
attributes = dict(getattr(span, "attributes", None) or {})
|
|
309
|
+
session_id = _first_span_attribute(
|
|
310
|
+
attributes,
|
|
311
|
+
"session.id",
|
|
312
|
+
"langfuse.session.id",
|
|
313
|
+
"ksadk.session_id",
|
|
314
|
+
)
|
|
315
|
+
if session_id is not None:
|
|
316
|
+
session_ids_by_trace.setdefault(_trace_id(span), session_id)
|
|
317
|
+
span_keys = {
|
|
318
|
+
(_trace_id(span), _span_id(span)) for span in span_list if _span_id(span) is not None
|
|
319
|
+
}
|
|
320
|
+
children_by_parent: dict[tuple[Optional[int], int], list[ReadableSpan]] = {}
|
|
321
|
+
roots: list[ReadableSpan] = []
|
|
273
322
|
for span in span_list:
|
|
274
|
-
|
|
275
|
-
if
|
|
323
|
+
span_id = _span_id(span)
|
|
324
|
+
if span_id is None:
|
|
276
325
|
continue
|
|
277
|
-
parent_id = _parent_span_id(span)
|
|
278
326
|
trace_id = _trace_id(span)
|
|
279
|
-
|
|
327
|
+
parent_id = _parent_span_id(span)
|
|
328
|
+
if parent_id is None or (trace_id, parent_id) not in span_keys:
|
|
280
329
|
roots.append(span)
|
|
281
330
|
else:
|
|
282
331
|
children_by_parent.setdefault((trace_id, parent_id), []).append(span)
|
|
283
332
|
|
|
284
|
-
|
|
333
|
+
usage_by_span = {
|
|
285
334
|
(_trace_id(span), _span_id(span)): _span_token_usage(span)
|
|
286
335
|
for span in span_list
|
|
287
336
|
if _span_id(span) is not None
|
|
288
337
|
}
|
|
289
|
-
|
|
290
338
|
has_token_descendant_cache: dict[tuple[Optional[int], int], bool] = {}
|
|
291
339
|
|
|
292
|
-
def has_token_descendant(span:
|
|
293
|
-
|
|
294
|
-
if
|
|
340
|
+
def has_token_descendant(span: ReadableSpan) -> bool:
|
|
341
|
+
key = (_trace_id(span), _span_id(span))
|
|
342
|
+
if key[1] is None:
|
|
295
343
|
return False
|
|
296
|
-
key = (_trace_id(span), sid)
|
|
297
344
|
if key in has_token_descendant_cache:
|
|
298
345
|
return has_token_descendant_cache[key]
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
if sid is None:
|
|
316
|
-
return
|
|
317
|
-
key = (_trace_id(span), sid)
|
|
318
|
-
own_usage = token_usage_by_id.get(key, {})
|
|
346
|
+
result = any(
|
|
347
|
+
_has_nonzero_token_usage(usage_by_span.get((_trace_id(child), _span_id(child)), {}))
|
|
348
|
+
or has_token_descendant(child)
|
|
349
|
+
for child in children_by_parent.get(key, [])
|
|
350
|
+
)
|
|
351
|
+
has_token_descendant_cache[key] = result
|
|
352
|
+
return result
|
|
353
|
+
|
|
354
|
+
def collect_leaf_usage(span: ReadableSpan, aggregate: dict[str, int]) -> None:
|
|
355
|
+
key = (_trace_id(span), _span_id(span))
|
|
356
|
+
own_usage = usage_by_span.get(key, {})
|
|
319
357
|
if _has_nonzero_token_usage(own_usage) and not has_token_descendant(span):
|
|
320
358
|
for usage_key, value in own_usage.items():
|
|
321
|
-
|
|
359
|
+
aggregate[usage_key] += value
|
|
322
360
|
return
|
|
323
361
|
for child in children_by_parent.get(key, []):
|
|
324
|
-
collect_leaf_usage(child,
|
|
362
|
+
collect_leaf_usage(child, aggregate)
|
|
325
363
|
|
|
326
|
-
|
|
364
|
+
root_usage: dict[int, dict[str, int]] = {}
|
|
327
365
|
for root in roots:
|
|
328
|
-
aggregate = {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
updated = False
|
|
335
|
-
attr_map = {
|
|
336
|
-
"gen_ai.usage.input_tokens": "input",
|
|
337
|
-
"gen_ai.usage.output_tokens": "output",
|
|
338
|
-
"gen_ai.usage.cache_read.input_tokens": "cache_read",
|
|
339
|
-
"gen_ai.usage.reasoning.output_tokens": "reasoning_output",
|
|
366
|
+
aggregate = {
|
|
367
|
+
"input": 0,
|
|
368
|
+
"output": 0,
|
|
369
|
+
"total": 0,
|
|
370
|
+
"cache_read": 0,
|
|
371
|
+
"reasoning_output": 0,
|
|
340
372
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
def _build_langfuse_otlp_config(
|
|
355
|
-
langfuse_config: dict[str, Any] | None = None,
|
|
356
|
-
) -> dict[str, Any] | None:
|
|
357
|
-
"""Build Langfuse OTLP direct exporter config from explicit config or env."""
|
|
358
|
-
if langfuse_config:
|
|
359
|
-
public_key = langfuse_config.get("public_key") or ""
|
|
360
|
-
secret_key = langfuse_config.get("secret_key") or ""
|
|
361
|
-
host = langfuse_config.get("host") or "http://localhost:3000"
|
|
362
|
-
else:
|
|
363
|
-
public_key = os.getenv("LANGFUSE_PUBLIC_KEY", "")
|
|
364
|
-
secret_key = os.getenv("LANGFUSE_SECRET_KEY", "")
|
|
365
|
-
host = (
|
|
366
|
-
os.getenv("LANGFUSE_BASE_URL") or os.getenv("LANGFUSE_HOST") or "http://localhost:3000"
|
|
367
|
-
)
|
|
368
|
-
|
|
369
|
-
if not public_key or not secret_key:
|
|
370
|
-
return None
|
|
371
|
-
|
|
372
|
-
headers = _build_langfuse_otlp_headers(public_key, secret_key)
|
|
373
|
-
return {
|
|
374
|
-
"endpoint": f"{host.rstrip('/')}/api/public/otel/v1/traces",
|
|
375
|
-
"headers": headers,
|
|
376
|
-
"protocol": "http/protobuf",
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def _build_langfuse_otlp_headers(public_key: str, secret_key: str) -> dict[str, str]:
|
|
381
|
-
auth = base64.b64encode(f"{public_key}:{secret_key}".encode("utf-8")).decode("ascii")
|
|
382
|
-
return {
|
|
383
|
-
"Authorization": f"Basic {auth}",
|
|
384
|
-
"x-langfuse-ingestion-version": "4",
|
|
373
|
+
collect_leaf_usage(root, aggregate)
|
|
374
|
+
root_usage[id(root)] = aggregate
|
|
375
|
+
|
|
376
|
+
resource_cache: dict[int, Resource | None] = {}
|
|
377
|
+
transformed: list[ReadableSpan] = []
|
|
378
|
+
usage_attributes = {
|
|
379
|
+
"gen_ai.usage.input_tokens": "input",
|
|
380
|
+
"gen_ai.usage.output_tokens": "output",
|
|
381
|
+
"gen_ai.usage.total_tokens": "total",
|
|
382
|
+
"gen_ai.usage.cache_read.input_tokens": "cache_read",
|
|
383
|
+
"gen_ai.usage.reasoning.output_tokens": "reasoning_output",
|
|
385
384
|
}
|
|
385
|
+
for span in span_list:
|
|
386
|
+
original_resource = getattr(span, "resource", None)
|
|
387
|
+
original_resource_attributes = getattr(original_resource, "attributes", {}) or {}
|
|
388
|
+
resource_key = id(original_resource)
|
|
389
|
+
if resource_key not in resource_cache:
|
|
390
|
+
resource_cache[resource_key] = _clone_cloud_monitor_resource(original_resource)
|
|
391
|
+
resource = resource_cache[resource_key]
|
|
392
|
+
attributes = dict(getattr(span, "attributes", None) or {})
|
|
393
|
+
resource_attributes = getattr(resource, "attributes", {}) or {}
|
|
394
|
+
for key in _CLOUD_MONITOR_RESOURCE_SPAN_ATTRIBUTES:
|
|
395
|
+
value = resource_attributes.get(key)
|
|
396
|
+
if value not in (None, ""):
|
|
397
|
+
attributes.setdefault(f"gen_ai.{key}", value)
|
|
398
|
+
agent_name = original_resource_attributes.get(
|
|
399
|
+
"agentengine.agent_name"
|
|
400
|
+
) or original_resource_attributes.get("service.name")
|
|
401
|
+
if agent_name not in (None, ""):
|
|
402
|
+
attributes.setdefault("gen_ai.agentengine.agent_name", agent_name)
|
|
403
|
+
aggregate = root_usage.get(id(span), {})
|
|
404
|
+
for attribute, usage_key in usage_attributes.items():
|
|
405
|
+
if _coerce_int(attributes.get(attribute)) <= 0 and aggregate.get(usage_key, 0) > 0:
|
|
406
|
+
attributes[attribute] = aggregate[usage_key]
|
|
407
|
+
|
|
408
|
+
session_id = session_ids_by_trace.get(_trace_id(span))
|
|
409
|
+
if session_id is not None:
|
|
410
|
+
attributes.setdefault("session.id", session_id)
|
|
411
|
+
_add_langfuse_compatibility_attributes(attributes)
|
|
412
|
+
|
|
413
|
+
original_attributes = dict(getattr(span, "attributes", None) or {})
|
|
414
|
+
if resource is original_resource and attributes == original_attributes:
|
|
415
|
+
transformed.append(span)
|
|
416
|
+
else:
|
|
417
|
+
transformed.append(_clone_span(span, attributes=attributes, resource=resource))
|
|
418
|
+
return transformed
|
|
386
419
|
|
|
387
420
|
|
|
388
421
|
def _parse_otlp_headers(raw: str) -> dict[str, str]:
|
|
@@ -408,7 +441,7 @@ def _decode_otlp_header_value(key: str, value: str) -> str:
|
|
|
408
441
|
for scheme in ("Basic", "Bearer"):
|
|
409
442
|
prefix = f"{scheme}+"
|
|
410
443
|
if decoded.startswith(prefix):
|
|
411
|
-
return f"{scheme} {decoded[len(prefix):]}"
|
|
444
|
+
return f"{scheme} {decoded[len(prefix) :]}"
|
|
412
445
|
return decoded
|
|
413
446
|
|
|
414
447
|
|
|
@@ -420,10 +453,6 @@ def _derive_otlp_traces_endpoint(endpoint: str) -> str:
|
|
|
420
453
|
return f"{endpoint}/v1/traces"
|
|
421
454
|
|
|
422
455
|
|
|
423
|
-
def _env_flag_disabled(value: str) -> bool:
|
|
424
|
-
return value.strip().lower() in {"0", "false", "no", "off", "disabled"}
|
|
425
|
-
|
|
426
|
-
|
|
427
456
|
def _env_flag_enabled(value: str) -> bool:
|
|
428
457
|
return value.strip().lower() in {"1", "true", "yes", "on"}
|
|
429
458
|
|
|
@@ -449,53 +478,48 @@ def _get_service_name() -> str:
|
|
|
449
478
|
|
|
450
479
|
|
|
451
480
|
def _build_cloud_monitor_otlp_http_config() -> Optional[_OtlpHttpConfig]:
|
|
452
|
-
"""Build CloudMonitor OTLP HTTP traces exporter config from
|
|
453
|
-
enabled = os.getenv("CLOUD_MONITOR_OTLP_ENABLED", "").strip()
|
|
454
|
-
if enabled and _env_flag_disabled(enabled):
|
|
455
|
-
logger.info("CloudMonitor OTLP exporter disabled by CLOUD_MONITOR_OTLP_ENABLED=%s", enabled)
|
|
456
|
-
return None
|
|
457
|
-
cloud_monitor_langfuse_configured = all(
|
|
458
|
-
os.getenv(name, "").strip()
|
|
459
|
-
for name in (
|
|
460
|
-
"CLOUD_MONITOR_LANGFUSE_PUBLIC_KEY",
|
|
461
|
-
"CLOUD_MONITOR_LANGFUSE_SECRET_KEY",
|
|
462
|
-
)
|
|
463
|
-
) and bool(
|
|
464
|
-
os.getenv("CLOUD_MONITOR_LANGFUSE_HOST", "").strip()
|
|
465
|
-
or os.getenv("CLOUD_MONITOR_OTLP_ENDPOINT", "").strip()
|
|
466
|
-
)
|
|
467
|
-
cloud_monitor_otlp_forced = _env_flag_enabled(enabled)
|
|
468
|
-
cloud_monitor_callback_requested = _env_flag_enabled(
|
|
469
|
-
os.getenv("LANGFUSE_USE_CALLBACK", "")
|
|
470
|
-
) and not _env_flag_disabled(os.getenv("CLOUD_MONITOR_LANGFUSE_ENABLED", ""))
|
|
471
|
-
if (
|
|
472
|
-
cloud_monitor_langfuse_configured
|
|
473
|
-
and cloud_monitor_callback_requested
|
|
474
|
-
and not cloud_monitor_otlp_forced
|
|
475
|
-
):
|
|
476
|
-
logger.info(
|
|
477
|
-
"CloudMonitor OTLP exporter skipped because CloudMonitor Langfuse SDK "
|
|
478
|
-
"callback is requested"
|
|
479
|
-
)
|
|
480
|
-
return None
|
|
481
|
+
"""Build CloudMonitor OTLP HTTP traces exporter config from CLOUD_MONITOR_OTLP_* env.
|
|
481
482
|
|
|
482
|
-
|
|
483
|
+
traces 专用 headers 优先于通用 headers。仅当两个 headers 环境变量整体缺失时,
|
|
484
|
+
才把旧 CLOUD_MONITOR_APP_KEY 按过渡兼容翻译为 Ksc-Appkey header。
|
|
485
|
+
"""
|
|
483
486
|
traces_endpoint = os.getenv("CLOUD_MONITOR_OTLP_TRACES_ENDPOINT", "").strip()
|
|
484
487
|
base_endpoint = os.getenv("CLOUD_MONITOR_OTLP_ENDPOINT", "").strip()
|
|
485
488
|
endpoint = traces_endpoint
|
|
486
489
|
if not endpoint and base_endpoint:
|
|
487
490
|
endpoint = _derive_otlp_traces_endpoint(base_endpoint)
|
|
488
491
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
492
|
+
raw_traces_headers = os.getenv("CLOUD_MONITOR_OTLP_TRACES_HEADERS", "")
|
|
493
|
+
raw_generic_headers = os.getenv("CLOUD_MONITOR_OTLP_HEADERS", "")
|
|
494
|
+
raw_headers = raw_traces_headers.strip() or raw_generic_headers
|
|
495
|
+
headers = _parse_otlp_headers(raw_headers)
|
|
496
|
+
# One-version compatibility applies only when both header variables are
|
|
497
|
+
# absent. A present-but-invalid traces or generic header bundle must fail
|
|
498
|
+
# closed rather than silently mixing it with the legacy AppKey.
|
|
499
|
+
if not raw_traces_headers.strip() and not raw_generic_headers.strip():
|
|
500
|
+
app_key = os.getenv("CLOUD_MONITOR_APP_KEY", "").strip()
|
|
501
|
+
if app_key:
|
|
502
|
+
if not endpoint:
|
|
503
|
+
logger.warning(
|
|
504
|
+
"CloudMonitor OTLP exporter skipped: CLOUD_MONITOR_APP_KEY fallback "
|
|
505
|
+
"requires CLOUD_MONITOR_OTLP_ENDPOINT or CLOUD_MONITOR_OTLP_TRACES_ENDPOINT"
|
|
506
|
+
)
|
|
507
|
+
return None
|
|
508
|
+
headers["Ksc-Appkey"] = app_key
|
|
509
|
+
logger.info(
|
|
510
|
+
"CloudMonitor OTLP using deprecated CLOUD_MONITOR_APP_KEY -> Ksc-Appkey fallback; "
|
|
511
|
+
"server should inject CLOUD_MONITOR_OTLP_TRACES_HEADERS or "
|
|
512
|
+
"CLOUD_MONITOR_OTLP_HEADERS instead"
|
|
513
|
+
)
|
|
514
|
+
|
|
495
515
|
if not endpoint:
|
|
516
|
+
logger.debug("CloudMonitor OTLP exporter not configured: missing endpoint")
|
|
517
|
+
return None
|
|
518
|
+
if not _has_header(headers, "Ksc-Appkey"):
|
|
496
519
|
logger.warning(
|
|
497
|
-
"CloudMonitor OTLP exporter skipped:
|
|
498
|
-
"
|
|
520
|
+
"CloudMonitor OTLP exporter skipped: Ksc-Appkey missing in "
|
|
521
|
+
"CLOUD_MONITOR_OTLP_TRACES_HEADERS/CLOUD_MONITOR_OTLP_HEADERS and no "
|
|
522
|
+
"CLOUD_MONITOR_APP_KEY fallback"
|
|
499
523
|
)
|
|
500
524
|
return None
|
|
501
525
|
|
|
@@ -511,16 +535,12 @@ def _build_cloud_monitor_otlp_http_config() -> Optional[_OtlpHttpConfig]:
|
|
|
511
535
|
)
|
|
512
536
|
return None
|
|
513
537
|
|
|
514
|
-
headers = _parse_otlp_headers(os.getenv("CLOUD_MONITOR_OTLP_HEADERS", ""))
|
|
515
|
-
headers["Ksc-Appkey"] = app_key
|
|
516
538
|
service_name = _get_service_name()
|
|
517
539
|
logger.info(
|
|
518
|
-
"CloudMonitor OTLP config resolved: endpoint=%s protocol=%s service_name=%s "
|
|
519
|
-
"app_key_present=%s header_keys=%s",
|
|
540
|
+
"CloudMonitor OTLP config resolved: endpoint=%s protocol=%s service_name=%s header_keys=%s",
|
|
520
541
|
endpoint,
|
|
521
542
|
protocol,
|
|
522
543
|
service_name or "",
|
|
523
|
-
bool(app_key),
|
|
524
544
|
",".join(sorted(headers)),
|
|
525
545
|
)
|
|
526
546
|
return _OtlpHttpConfig(
|
|
@@ -531,48 +551,12 @@ def _build_cloud_monitor_otlp_http_config() -> Optional[_OtlpHttpConfig]:
|
|
|
531
551
|
)
|
|
532
552
|
|
|
533
553
|
|
|
534
|
-
def _is_langfuse_otlp_endpoint(endpoint: str) -> bool:
|
|
535
|
-
if not endpoint:
|
|
536
|
-
return False
|
|
537
|
-
normalized_endpoint = endpoint.strip().rstrip("/")
|
|
538
|
-
langfuse_hosts = [
|
|
539
|
-
os.getenv("LANGFUSE_HOST", "").strip().rstrip("/"),
|
|
540
|
-
os.getenv("LANGFUSE_BASE_URL", "").strip().rstrip("/"),
|
|
541
|
-
]
|
|
542
|
-
if any(host and normalized_endpoint.startswith(host) for host in langfuse_hosts):
|
|
543
|
-
return True
|
|
544
|
-
return "/api/public/otel" in normalized_endpoint
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
def _is_langfuse_callback_endpoint(endpoint: str) -> bool:
|
|
548
|
-
return _env_flag_enabled(os.getenv("LANGFUSE_USE_CALLBACK", "")) and _is_langfuse_otlp_endpoint(
|
|
549
|
-
endpoint
|
|
550
|
-
)
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
def _apply_langfuse_auth_fallback(endpoint: str, headers: dict[str, str]) -> bool:
|
|
554
|
-
"""Add Langfuse OTLP auth when the platform provides endpoint + keys but no headers."""
|
|
555
|
-
if not _is_langfuse_otlp_endpoint(endpoint) or _has_header(headers, "Authorization"):
|
|
556
|
-
return False
|
|
557
|
-
|
|
558
|
-
public_key = os.getenv("LANGFUSE_PUBLIC_KEY", "").strip()
|
|
559
|
-
secret_key = os.getenv("LANGFUSE_SECRET_KEY", "").strip()
|
|
560
|
-
if not public_key or not secret_key:
|
|
561
|
-
logger.warning(
|
|
562
|
-
"Generic OTLP endpoint looks like Langfuse but LANGFUSE_PUBLIC_KEY/SECRET_KEY "
|
|
563
|
-
"are missing; Authorization header was not added"
|
|
564
|
-
)
|
|
565
|
-
return False
|
|
566
|
-
|
|
567
|
-
langfuse_headers = _build_langfuse_otlp_headers(public_key, secret_key)
|
|
568
|
-
headers["Authorization"] = langfuse_headers["Authorization"]
|
|
569
|
-
if not _has_header(headers, "x-langfuse-ingestion-version"):
|
|
570
|
-
headers["x-langfuse-ingestion-version"] = langfuse_headers["x-langfuse-ingestion-version"]
|
|
571
|
-
return True
|
|
572
|
-
|
|
573
|
-
|
|
574
554
|
def _build_generic_otlp_http_config() -> Optional[dict]:
|
|
575
|
-
"""Build generic OTLP HTTP traces exporter config from standard OTEL env.
|
|
555
|
+
"""Build generic OTLP HTTP traces exporter config from standard OTEL env.
|
|
556
|
+
|
|
557
|
+
This path consumes only the standard OTEL_EXPORTER_OTLP_* contract. Backend
|
|
558
|
+
identity and authentication are opaque to KsADK.
|
|
559
|
+
"""
|
|
576
560
|
traces_endpoint = os.getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "").strip()
|
|
577
561
|
base_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "").strip()
|
|
578
562
|
endpoint = traces_endpoint
|
|
@@ -580,13 +564,6 @@ def _build_generic_otlp_http_config() -> Optional[dict]:
|
|
|
580
564
|
endpoint = _derive_otlp_traces_endpoint(base_endpoint)
|
|
581
565
|
if not endpoint:
|
|
582
566
|
return None
|
|
583
|
-
if _is_langfuse_callback_endpoint(endpoint):
|
|
584
|
-
logger.info(
|
|
585
|
-
"Generic OTLP HTTP exporter skipped because LANGFUSE_USE_CALLBACK is enabled "
|
|
586
|
-
"for endpoint=%s",
|
|
587
|
-
endpoint,
|
|
588
|
-
)
|
|
589
|
-
return None
|
|
590
567
|
|
|
591
568
|
protocol = (
|
|
592
569
|
os.getenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", "").strip().lower()
|
|
@@ -603,53 +580,58 @@ def _build_generic_otlp_http_config() -> Optional[dict]:
|
|
|
603
580
|
"OTEL_EXPORTER_OTLP_HEADERS", ""
|
|
604
581
|
)
|
|
605
582
|
headers = _parse_otlp_headers(raw_headers)
|
|
606
|
-
langfuse_auth_fallback = _apply_langfuse_auth_fallback(endpoint, headers)
|
|
607
583
|
logger.info(
|
|
608
|
-
"Generic OTLP HTTP config resolved: endpoint=%s protocol=http/protobuf "
|
|
609
|
-
"header_keys=%s langfuse_auth_fallback=%s",
|
|
584
|
+
"Generic OTLP HTTP config resolved: endpoint=%s protocol=http/protobuf header_keys=%s",
|
|
610
585
|
endpoint,
|
|
611
586
|
",".join(sorted(headers)),
|
|
612
|
-
langfuse_auth_fallback,
|
|
613
587
|
)
|
|
614
588
|
return {
|
|
615
589
|
"endpoint": endpoint,
|
|
616
590
|
"headers": headers,
|
|
617
591
|
"protocol": "http/protobuf",
|
|
618
|
-
"langfuse_auth_fallback": langfuse_auth_fallback,
|
|
619
592
|
}
|
|
620
593
|
|
|
621
594
|
|
|
595
|
+
def _register_span_processor(provider: Any, processor: Any) -> None:
|
|
596
|
+
provider.add_span_processor(processor)
|
|
597
|
+
_managed_span_processors.append(processor)
|
|
598
|
+
|
|
599
|
+
|
|
622
600
|
def setup_tracing(
|
|
623
601
|
enable_inmemory: bool = True,
|
|
624
|
-
enable_langfuse: bool | None = None,
|
|
602
|
+
enable_langfuse: bool | None = None,
|
|
625
603
|
langfuse_config: dict[str, Any] | None = None,
|
|
626
604
|
enable_otlp: bool = False,
|
|
627
605
|
otlp_endpoint: str = "localhost:4317",
|
|
628
606
|
enable_adk_instrumentation: bool = True, # Auto-instrument ADK
|
|
629
|
-
use_callback_only: bool | None = None, # Explicit override
|
|
630
607
|
**kwargs,
|
|
631
608
|
) -> Optional[InMemoryExporter]:
|
|
632
609
|
"""初始化 Tracing (支持多 Exporter)
|
|
633
610
|
|
|
634
611
|
Args:
|
|
635
612
|
enable_inmemory: 是否启用内存 Exporter (Web UI 使用)
|
|
636
|
-
enable_langfuse:
|
|
637
|
-
langfuse_config:
|
|
638
|
-
enable_otlp: 是否启用 OTLP Exporter
|
|
639
|
-
otlp_endpoint: OTLP 端点地址
|
|
613
|
+
enable_langfuse: 已废弃并忽略;请配置标准 OTEL_EXPORTER_OTLP_*
|
|
614
|
+
langfuse_config: 已废弃并忽略;认证信息放入标准 OTLP headers
|
|
615
|
+
enable_otlp: 是否启用 OTLP gRPC Exporter
|
|
616
|
+
otlp_endpoint: OTLP gRPC 端点地址
|
|
640
617
|
enable_adk_instrumentation: 是否启用 ADK 自动插桩
|
|
641
|
-
use_callback_only: 是否仅使用 CallbackHandler (防止 OTel 重复)
|
|
642
618
|
|
|
643
619
|
Returns:
|
|
644
620
|
InMemoryExporter 实例 (用于 Web UI 获取 traces)
|
|
645
621
|
"""
|
|
646
|
-
global _exporter_instance,
|
|
622
|
+
global _exporter_instance, _tracing_initialized, _adk_instrumented
|
|
647
623
|
|
|
648
624
|
# 防止重复初始化
|
|
649
625
|
if _tracing_initialized:
|
|
650
626
|
logger.debug("Tracing already initialized, skipping")
|
|
651
627
|
return _exporter_instance
|
|
652
628
|
|
|
629
|
+
if enable_langfuse or langfuse_config:
|
|
630
|
+
logger.warning(
|
|
631
|
+
"enable_langfuse/langfuse_config are ignored; configure the standard "
|
|
632
|
+
"OTEL_EXPORTER_OTLP_* contract instead"
|
|
633
|
+
)
|
|
634
|
+
|
|
653
635
|
try:
|
|
654
636
|
from opentelemetry import trace
|
|
655
637
|
from opentelemetry.sdk.trace import TracerProvider
|
|
@@ -661,11 +643,9 @@ def setup_tracing(
|
|
|
661
643
|
# 检查是否已有 TracerProvider (避免覆盖)
|
|
662
644
|
existing_provider = trace.get_tracer_provider()
|
|
663
645
|
if existing_provider and hasattr(existing_provider, "add_span_processor"):
|
|
664
|
-
# 使用现有 provider,直接添加 processor
|
|
665
646
|
provider = existing_provider
|
|
666
647
|
logger.debug("Using existing TracerProvider")
|
|
667
648
|
else:
|
|
668
|
-
# 创建新的 TracerProvider
|
|
669
649
|
provider = TracerProvider()
|
|
670
650
|
trace.set_tracer_provider(provider)
|
|
671
651
|
logger.debug("Created new TracerProvider")
|
|
@@ -674,11 +654,10 @@ def setup_tracing(
|
|
|
674
654
|
if enable_inmemory:
|
|
675
655
|
exporter = InMemoryExporter(max_traces=kwargs.get("max_traces", 1000))
|
|
676
656
|
_exporter_instance = exporter
|
|
677
|
-
provider
|
|
657
|
+
_register_span_processor(provider, SimpleSpanProcessor(exporter))
|
|
678
658
|
logger.info("InMemory exporter enabled")
|
|
679
659
|
|
|
680
|
-
# 2. Generic OTLP HTTP exporter
|
|
681
|
-
# This keeps user code backend-agnostic; Langfuse is only one possible OTLP backend.
|
|
660
|
+
# 2. Generic OTLP HTTP exporter (标准 OTEL_* —— 托管 Agent 的 Langfuse 主路)
|
|
682
661
|
generic_otlp_config = _build_generic_otlp_http_config()
|
|
683
662
|
if generic_otlp_config:
|
|
684
663
|
try:
|
|
@@ -696,17 +675,10 @@ def setup_tracing(
|
|
|
696
675
|
endpoint=generic_otlp_config["endpoint"],
|
|
697
676
|
service_name=_get_service_name(),
|
|
698
677
|
header_keys=sorted(generic_otlp_config["headers"]),
|
|
699
|
-
span_transform=(
|
|
700
|
-
_prepare_langfuse_spans
|
|
701
|
-
if _is_langfuse_otlp_endpoint(generic_otlp_config["endpoint"])
|
|
702
|
-
else None
|
|
703
|
-
),
|
|
704
678
|
)
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
**_batch_span_processor_kwargs(),
|
|
709
|
-
)
|
|
679
|
+
_register_span_processor(
|
|
680
|
+
provider,
|
|
681
|
+
BatchSpanProcessor(otlp_exporter, **_batch_span_processor_kwargs()),
|
|
710
682
|
)
|
|
711
683
|
logger.info(
|
|
712
684
|
"Generic OTLP HTTP exporter enabled: %s (%s) headers=%s",
|
|
@@ -719,9 +691,7 @@ def setup_tracing(
|
|
|
719
691
|
except Exception as e:
|
|
720
692
|
logger.error(f"Failed to initialize generic OTLP HTTP exporter: {e}")
|
|
721
693
|
|
|
722
|
-
# 3. CloudMonitor OTLP HTTP exporter
|
|
723
|
-
# This uses CloudMonitor-specific env vars, so existing Langfuse / generic OTLP paths
|
|
724
|
-
# continue to behave exactly as configured by the user or platform.
|
|
694
|
+
# 3. CloudMonitor OTLP HTTP exporter (次路 —— 平台 CLOUD_MONITOR_OTLP_*)
|
|
725
695
|
cloud_monitor_config = _build_cloud_monitor_otlp_http_config()
|
|
726
696
|
if cloud_monitor_config:
|
|
727
697
|
try:
|
|
@@ -741,11 +711,9 @@ def setup_tracing(
|
|
|
741
711
|
header_keys=sorted(cloud_monitor_config.headers),
|
|
742
712
|
span_transform=_prepare_cloud_monitor_spans,
|
|
743
713
|
)
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
**_batch_span_processor_kwargs(),
|
|
748
|
-
)
|
|
714
|
+
_register_span_processor(
|
|
715
|
+
provider,
|
|
716
|
+
BatchSpanProcessor(cloud_monitor_exporter, **_batch_span_processor_kwargs()),
|
|
749
717
|
)
|
|
750
718
|
logger.info(
|
|
751
719
|
"CloudMonitor OTLP exporter enabled: endpoint=%s protocol=%s service_name=%s",
|
|
@@ -758,64 +726,7 @@ def setup_tracing(
|
|
|
758
726
|
except Exception as e:
|
|
759
727
|
logger.error(f"Failed to initialize CloudMonitor OTLP exporter: {e}")
|
|
760
728
|
|
|
761
|
-
# 4.
|
|
762
|
-
# 注意: 对于 LangGraph/LangChain 框架,推荐使用 CallbackHandler 而非 OTel Exporter
|
|
763
|
-
# 同时使用两者会导致重复的 trace
|
|
764
|
-
langfuse_enabled = enable_langfuse
|
|
765
|
-
if langfuse_enabled is None:
|
|
766
|
-
# Auto-detect from environment variables
|
|
767
|
-
langfuse_enabled = not generic_otlp_config and bool(
|
|
768
|
-
os.getenv("LANGFUSE_PUBLIC_KEY") or (langfuse_config or {}).get("public_key")
|
|
769
|
-
)
|
|
770
|
-
|
|
771
|
-
# 检查是否应该禁用 LangfuseExporter (当使用 LangChain/LangGraph 时)
|
|
772
|
-
# 优先使用显式参数,否则读取环境变量
|
|
773
|
-
if use_callback_only is None:
|
|
774
|
-
use_callback_only = os.getenv("LANGFUSE_USE_CALLBACK", "false").lower() == "true"
|
|
775
|
-
|
|
776
|
-
if langfuse_enabled and not use_callback_only:
|
|
777
|
-
try:
|
|
778
|
-
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
|
779
|
-
OTLPSpanExporter as HttpOTLPSpanExporter,
|
|
780
|
-
)
|
|
781
|
-
|
|
782
|
-
config = _build_langfuse_otlp_config(langfuse_config)
|
|
783
|
-
if config:
|
|
784
|
-
raw_langfuse_otlp_exporter = HttpOTLPSpanExporter(
|
|
785
|
-
endpoint=config["endpoint"],
|
|
786
|
-
headers=config["headers"],
|
|
787
|
-
)
|
|
788
|
-
otlp_exporter = _LoggingSpanExporter(
|
|
789
|
-
raw_langfuse_otlp_exporter,
|
|
790
|
-
name="Langfuse OTLP",
|
|
791
|
-
endpoint=config["endpoint"],
|
|
792
|
-
service_name=_get_service_name(),
|
|
793
|
-
header_keys=sorted(config["headers"]),
|
|
794
|
-
span_transform=_prepare_langfuse_spans,
|
|
795
|
-
)
|
|
796
|
-
provider.add_span_processor(
|
|
797
|
-
BatchSpanProcessor(
|
|
798
|
-
otlp_exporter,
|
|
799
|
-
**_batch_span_processor_kwargs(),
|
|
800
|
-
)
|
|
801
|
-
)
|
|
802
|
-
logger.info(
|
|
803
|
-
"Langfuse OTLP exporter enabled: %s (%s) headers=%s",
|
|
804
|
-
config["endpoint"],
|
|
805
|
-
config["protocol"],
|
|
806
|
-
",".join(sorted(config["headers"])),
|
|
807
|
-
)
|
|
808
|
-
else:
|
|
809
|
-
logger.warning("Langfuse credentials not found, skipping")
|
|
810
|
-
|
|
811
|
-
except ImportError as e:
|
|
812
|
-
logger.warning(f"Langfuse OTLP exporter not available: {e}")
|
|
813
|
-
except Exception as e:
|
|
814
|
-
logger.error(f"Failed to initialize Langfuse OTLP exporter: {e}")
|
|
815
|
-
elif langfuse_enabled:
|
|
816
|
-
logger.info("Langfuse will use CallbackHandler (recommended for LangChain/LangGraph)")
|
|
817
|
-
|
|
818
|
-
# 5. OTLP Exporter (optional)
|
|
729
|
+
# 4. OTLP gRPC Exporter (optional compatibility path)
|
|
819
730
|
if enable_otlp:
|
|
820
731
|
try:
|
|
821
732
|
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
|
|
@@ -823,17 +734,15 @@ def setup_tracing(
|
|
|
823
734
|
)
|
|
824
735
|
|
|
825
736
|
grpc_otlp_exporter = GrpcOTLPSpanExporter(endpoint=otlp_endpoint)
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
**_batch_span_processor_kwargs(),
|
|
830
|
-
)
|
|
737
|
+
_register_span_processor(
|
|
738
|
+
provider,
|
|
739
|
+
BatchSpanProcessor(grpc_otlp_exporter, **_batch_span_processor_kwargs()),
|
|
831
740
|
)
|
|
832
741
|
logger.info(f"OTLP exporter enabled: {otlp_endpoint}")
|
|
833
742
|
except ImportError:
|
|
834
743
|
logger.warning("OTLP exporter not installed")
|
|
835
744
|
|
|
836
|
-
#
|
|
745
|
+
# 5. ADK Auto-Instrumentation (for Google ADK projects)
|
|
837
746
|
if enable_adk_instrumentation and not _adk_instrumented:
|
|
838
747
|
try:
|
|
839
748
|
instrumentation_module = importlib.import_module(
|
|
@@ -851,7 +760,7 @@ def setup_tracing(
|
|
|
851
760
|
except Exception as e:
|
|
852
761
|
logger.debug(f"ADK instrumentation failed: {e}")
|
|
853
762
|
|
|
854
|
-
#
|
|
763
|
+
# 6. LangChain Auto-Instrumentation (covers LangChain + LangGraph LLM/tool/node spans)
|
|
855
764
|
if enable_adk_instrumentation:
|
|
856
765
|
try:
|
|
857
766
|
from openinference.instrumentation.langchain import LangChainInstrumentor
|
|
@@ -874,18 +783,28 @@ def setup_tracing(
|
|
|
874
783
|
|
|
875
784
|
|
|
876
785
|
def shutdown_tracing():
|
|
877
|
-
"""
|
|
878
|
-
global _langfuse_exporter, _tracing_initialized
|
|
786
|
+
"""Flush and stop every processor installed by this module.
|
|
879
787
|
|
|
880
|
-
|
|
788
|
+
The global provider may belong to another library, so KsADK never shuts the
|
|
789
|
+
provider itself. It owns and closes only the processors it registered.
|
|
790
|
+
"""
|
|
791
|
+
processors = list(_managed_span_processors)
|
|
792
|
+
_managed_span_processors.clear()
|
|
793
|
+
for processor in processors:
|
|
881
794
|
try:
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
795
|
+
processor.force_flush(timeout_millis=30000)
|
|
796
|
+
except TypeError:
|
|
797
|
+
try:
|
|
798
|
+
processor.force_flush()
|
|
799
|
+
except Exception:
|
|
800
|
+
logger.exception("Tracing processor force_flush failed")
|
|
885
801
|
except Exception:
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
802
|
+
logger.exception("Tracing processor force_flush failed")
|
|
803
|
+
for processor in processors:
|
|
804
|
+
try:
|
|
805
|
+
processor.shutdown()
|
|
806
|
+
except Exception:
|
|
807
|
+
logger.exception("Tracing processor shutdown failed")
|
|
889
808
|
|
|
890
809
|
|
|
891
810
|
def get_memory_exporter() -> Optional[InMemoryExporter]:
|
|
@@ -893,11 +812,6 @@ def get_memory_exporter() -> Optional[InMemoryExporter]:
|
|
|
893
812
|
return _exporter_instance
|
|
894
813
|
|
|
895
814
|
|
|
896
|
-
def get_langfuse_exporter():
|
|
897
|
-
"""获取 Langfuse Exporter 实例"""
|
|
898
|
-
return _langfuse_exporter
|
|
899
|
-
|
|
900
|
-
|
|
901
815
|
def get_tracer(name: str = "ksadk"):
|
|
902
816
|
"""获取 Tracer"""
|
|
903
817
|
try:
|