agentengine-sdk-python 0.8.1__py3-none-any.whl → 0.8.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
- ksadk/a2a/_space_client_events.py +381 -0
- ksadk/a2a/event_adapter.py +156 -64
- ksadk/a2a/executor.py +179 -63
- ksadk/a2a/space_client.py +6 -333
- ksadk/a2ui/core.py +222 -53
- ksadk/agui/_agent_helpers.py +243 -0
- ksadk/agui/a2ui_projection.py +12 -5
- ksadk/agui/agent.py +443 -259
- ksadk/api/client.py +525 -27
- ksadk/builders/code_builder.py +284 -8
- ksadk/builders/container_builder.py +8 -1
- ksadk/builders/managed_runtime_builder.py +39 -35
- ksadk/cli/__init__.py +17 -0
- ksadk/cli/cmd_dashboard.py +15 -1
- ksadk/cli/cmd_deploy.py +10 -2
- ksadk/cli/cmd_eval.py +66 -9
- ksadk/cli/cmd_evalset.py +284 -0
- ksadk/cli/cmd_files.py +5 -1
- ksadk/cli/cmd_hermes.py +1 -126
- ksadk/cli/cmd_invoke.py +28 -38
- ksadk/cli/cmd_managed_runtime.py +95 -0
- ksadk/cli/cmd_observe.py +85 -0
- ksadk/cli/cmd_openclaw.py +1 -435
- ksadk/cli/cmd_replay.py +28 -4
- ksadk/cli/cmd_studio.py +99 -8
- ksadk/cli/cmd_web.py +10 -2
- ksadk/cli/hermes_env.py +151 -0
- ksadk/cli/invoke_payload.py +49 -0
- ksadk/cli/openclaw_env.py +458 -0
- ksadk/codex/client.py +180 -30
- ksadk/codex/runtime.py +428 -326
- ksadk/configs/env_registry.py +66 -12
- ksadk/configs/env_registry_pcm.py +181 -0
- ksadk/configs/env_var_spec.py +18 -0
- ksadk/configs/global_config.py +5 -0
- 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/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 +184 -2
- ksadk/conversations/runtime_invocation.py +81 -3
- ksadk/conversations/runtime_observability.py +141 -0
- ksadk/conversations/runtime_payloads.py +42 -0
- ksadk/conversations/runtime_preparation.py +272 -2
- ksadk/conversations/runtime_resume.py +20 -1
- ksadk/conversations/runtime_stream_events.py +94 -9
- 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 +5 -0
- ksadk/deployment/providers/serverless.py +143 -69
- ksadk/evaluation/__init__.py +32 -0
- ksadk/evaluation/a2a_adapter.py +9 -1
- ksadk/evaluation/adapters.py +18 -5
- 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 +73 -4
- ksadk/evaluation/evaluators.py +183 -17
- ksadk/evaluation/evidence.py +258 -0
- ksadk/evaluation/executor.py +31 -6
- ksadk/evaluation/local_adapter.py +550 -0
- ksadk/evaluation/service_env.py +27 -0
- ksadk/evaluation/studio_build_adapter.py +253 -0
- ksadk/evaluation/target.py +15 -6
- 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 -265
- ksadk/events/session_event.py +324 -0
- ksadk/events/store.py +3 -316
- ksadk/events/v1_compat.py +41 -0
- ksadk/harness/runtime.py +160 -57
- 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 +7 -0
- ksadk/knowledge_base/service.py +32 -5
- 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 +384 -2
- ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
- 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 +188 -6
- ksadk/model_proxy/bootstrap.py +16 -9
- ksadk/model_proxy/detect.py +202 -9
- ksadk/model_proxy/namespace.py +39 -15
- ksadk/model_proxy/server.py +24 -5
- ksadk/model_proxy/transform.py +60 -2
- 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 +5 -5
- ksadk/runners/base_runner.py +16 -0
- ksadk/runners/factory.py +1 -2
- ksadk/runners/langgraph_runner.py +198 -437
- ksadk/runtime/_runner_adapter/__init__.py +1 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
- ksadk/runtime/adapter.py +118 -2
- ksadk/runtime/conversation_execution.py +316 -182
- ksadk/runtime/executor.py +144 -3
- ksadk/runtime/factory.py +193 -7
- ksadk/runtime/hosted_finalizer.py +268 -0
- ksadk/runtime/launch.py +14 -1
- ksadk/runtime/preprocessing.py +25 -3
- ksadk/runtime/runner_adapter.py +288 -508
- ksadk/runtime/usage.py +33 -0
- ksadk/sandbox/backends/e2b.py +35 -2
- ksadk/server/composition.py +8 -0
- ksadk/server/factory.py +69 -6
- ksadk/server/routes/kernel_ingress.py +258 -0
- ksadk/server/routes/openai_compat.py +88 -8
- ksadk/server/routes/projection.py +35 -20
- ksadk/server/routes/run.py +94 -0
- ksadk/server/routes/sessions.py +2 -0
- ksadk/server/routes/streaming.py +5 -2
- ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
- ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.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-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
- ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
- ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.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-ChNs_Mdk.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-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
- ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
- ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
- ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
- ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
- ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
- ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
- ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
- ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
- ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
- ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
- ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
- ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
- ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
- ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
- ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
- ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
- ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
- ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
- ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
- ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
- ksadk/server/static/assets/{dist-CmZHzlwZ.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-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
- ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
- ksadk/server/static/assets/{linear-hdBcYzM8.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-Bjy_2v4_.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-BmkRbwCg.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-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.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-DEz24vV0.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/studio/api.py +577 -25
- ksadk/studio/api_contracts.py +114 -5
- ksadk/studio/api_memory_routes.py +82 -0
- ksadk/studio/authoring.py +252 -27
- ksadk/studio/authoring_coordinator.py +799 -67
- ksadk/studio/builder.py +88 -11
- ksadk/studio/capabilities.py +17 -0
- ksadk/studio/cloud.py +1916 -95
- ksadk/studio/codex_agent_service.py +125 -14
- ksadk/studio/codex_authoring.py +469 -0
- ksadk/studio/codex_builder.py +138 -25
- ksadk/studio/codex_manifest.py +51 -22
- ksadk/studio/codex_run.py +46 -3
- ksadk/studio/compiler.py +1 -0
- ksadk/studio/contracts.py +114 -3
- ksadk/studio/event_store.py +25 -95
- ksadk/studio/framework_run.py +231 -3
- ksadk/studio/hosted_kernel.py +317 -0
- ksadk/studio/manifest_resolver.py +131 -0
- ksadk/studio/model_client.py +95 -9
- ksadk/studio/model_profile_service.py +1 -1
- ksadk/studio/operations.py +37 -5
- ksadk/studio/otel_trace.py +348 -42
- ksadk/studio/pcm_memory.py +85 -0
- ksadk/studio/repository.py +23 -2
- ksadk/studio/resource_catalog.py +43 -0
- ksadk/studio/run_service.py +563 -137
- ksadk/studio/runtime_source.py +42 -16
- ksadk/studio/service.py +1180 -72
- ksadk/studio/shared_web.py +32 -22
- ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
- ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/templates.py +100 -8
- ksadk/studio/validator.py +19 -0
- ksadk/studio/workspace.py +30 -32
- ksadk/tracing/setup.py +304 -3
- ksadk/version.py +1 -1
- ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
- ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
- ksadk/events/parser.py +0 -191
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
- ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
- ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
- ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
- ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
- ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
- ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
- ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
- ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
- ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
- ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
- ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
- ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
- ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
- ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
- ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
- ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
- ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
- ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
- ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
- ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
- ksadk/server/static/assets/index-R52pJJvg.js +0 -177
- ksadk/server/static/assets/index-jJr3_955.css +0 -2
- ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
- ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
- ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
- ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
- ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
- ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
- ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
- ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
- ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
- ksadk/studio/evaluation.py +0 -192
- ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
- ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""Shadow 基线采集器(评测方案第 10 节阶段 1:建立 Baseline)。
|
|
2
|
+
|
|
3
|
+
当前处于 shadow 阶段:shadow plan 不改运行行为,本采集器只把每次 turn 的可观测信号
|
|
4
|
+
落盘成结构化记录,作为后续 A/B 对比的基准。对齐评测方案:
|
|
5
|
+
|
|
6
|
+
- §3 A/B 记录字段(commit / runner_type / model / policy_version / prompt_hash ...)
|
|
7
|
+
- §5.4 效率指标(input/output token、PTL rate、compaction、accounting accuracy)
|
|
8
|
+
- §7.8 采集指标(prompt/history/memory/tool token、prompt_hash、capability hash、
|
|
9
|
+
planned/projected/actual accuracy)
|
|
10
|
+
|
|
11
|
+
采集器只读 shadow plan dict 与 runtime usage/事件,不接触模型正文、凭证或敏感内容
|
|
12
|
+
(安全要求 §19:默认只记录 hash、长度、类型和脱敏摘要)。输出 JSONL,每行一条 turn 记录。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import atexit
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import threading
|
|
21
|
+
import time
|
|
22
|
+
from dataclasses import asdict, dataclass, field
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Mapping
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _safe_commit() -> str:
|
|
28
|
+
"""取当前 git commit(脱敏:只取短 hash,失败时返回占位符,不抛异常)。"""
|
|
29
|
+
import subprocess
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
["git", "rev-parse", "--short", "HEAD"],
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
timeout=2,
|
|
37
|
+
)
|
|
38
|
+
if result.returncode == 0:
|
|
39
|
+
return result.stdout.strip() or "unknown"
|
|
40
|
+
except Exception: # noqa: BLE001
|
|
41
|
+
pass
|
|
42
|
+
return os.environ.get("KSADK_EVAL_COMMIT", "unknown")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _ksadk_version() -> str:
|
|
46
|
+
try:
|
|
47
|
+
from ksadk.version import VERSION
|
|
48
|
+
|
|
49
|
+
return str(VERSION)
|
|
50
|
+
except Exception: # noqa: BLE001
|
|
51
|
+
return "unknown"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class BaselineTurnRecord:
|
|
56
|
+
"""单次 turn 的基线记录(评测方案 §12.4 单 Case 记录 + §7.8 采集指标)。"""
|
|
57
|
+
|
|
58
|
+
# 版本与环境(§3)
|
|
59
|
+
ksadk_commit: str = ""
|
|
60
|
+
ksadk_version: str = ""
|
|
61
|
+
context_policy_version: str = ""
|
|
62
|
+
runner_type: str = ""
|
|
63
|
+
deployment_mode: str = ""
|
|
64
|
+
integration_mode: str = ""
|
|
65
|
+
accounting_accuracy: str = ""
|
|
66
|
+
capability_hash: str = ""
|
|
67
|
+
model: str = ""
|
|
68
|
+
execution_target: str = ""
|
|
69
|
+
|
|
70
|
+
# 上下文计划标识与 hash(§7.8)
|
|
71
|
+
plan_id: str = ""
|
|
72
|
+
prompt_content_hash: str = ""
|
|
73
|
+
prompt_stable_prefix_hash: str = ""
|
|
74
|
+
tokenizer: str = ""
|
|
75
|
+
|
|
76
|
+
# token 分类(§7.8 prompt/history/memory/tool token)
|
|
77
|
+
tokens_by_kind: dict[str, int] = field(default_factory=dict)
|
|
78
|
+
prompt_tokens_by_section: dict[str, int] = field(default_factory=dict)
|
|
79
|
+
planned_input_tokens: int = 0
|
|
80
|
+
|
|
81
|
+
# runtime 实际 usage(§5.4,runtime_reported 时才有)
|
|
82
|
+
runtime_reported_input_tokens: int | None = None
|
|
83
|
+
runtime_output_tokens: int | None = None
|
|
84
|
+
cache_read_tokens: int | None = None
|
|
85
|
+
cache_creation_tokens: int | None = None
|
|
86
|
+
cache_status: str = ""
|
|
87
|
+
unexpected_break: bool = False
|
|
88
|
+
|
|
89
|
+
# 效率与稳定性(§5.4)
|
|
90
|
+
compaction_triggered: bool = False
|
|
91
|
+
compaction_trigger: str = ""
|
|
92
|
+
prompt_too_long: bool = False
|
|
93
|
+
retry_attempts: int = 0
|
|
94
|
+
turn_latency_ms: int | None = None
|
|
95
|
+
time_to_first_token_ms: int | None = None
|
|
96
|
+
|
|
97
|
+
# 可诊断性(§5.4 opaque request rate)
|
|
98
|
+
capability_mismatch: bool = False
|
|
99
|
+
|
|
100
|
+
# 元数据
|
|
101
|
+
session_id: str = ""
|
|
102
|
+
invocation_id: str = ""
|
|
103
|
+
recorded_at: str = ""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class BaselineCollector:
|
|
107
|
+
"""进程内基线采集器:从 shadow plan dict + runtime usage 累积 turn 记录。
|
|
108
|
+
|
|
109
|
+
用法:在 conversation runtime 旁路(shadow plan 已挂在 prepared.shadow_context_plan)
|
|
110
|
+
调用 ``record_turn(plan, usage=..., latency_ms=...)``;运行结束后 ``dump(path)`` 落盘。
|
|
111
|
+
采集器本身不挂任何 span/不进决策路径,纯旁路只读。
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, *, execution_target: str = "local") -> None:
|
|
115
|
+
self._records: list[BaselineTurnRecord] = []
|
|
116
|
+
self._records_lock = threading.RLock()
|
|
117
|
+
self._commit = _safe_commit()
|
|
118
|
+
self._version = _ksadk_version()
|
|
119
|
+
self._execution_target = execution_target
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def records(self) -> list[BaselineTurnRecord]:
|
|
123
|
+
with self._records_lock:
|
|
124
|
+
return list(self._records)
|
|
125
|
+
|
|
126
|
+
def record_turn(
|
|
127
|
+
self,
|
|
128
|
+
plan: Mapping[str, Any] | None,
|
|
129
|
+
*,
|
|
130
|
+
session_id: str = "",
|
|
131
|
+
invocation_id: str = "",
|
|
132
|
+
model: str = "",
|
|
133
|
+
usage: Mapping[str, Any] | None = None,
|
|
134
|
+
compaction_triggered: bool = False,
|
|
135
|
+
compaction_trigger: str = "",
|
|
136
|
+
prompt_too_long: bool = False,
|
|
137
|
+
retry_attempts: int = 0,
|
|
138
|
+
turn_latency_ms: int | None = None,
|
|
139
|
+
time_to_first_token_ms: int | None = None,
|
|
140
|
+
capability_mismatch: bool = False,
|
|
141
|
+
) -> BaselineTurnRecord:
|
|
142
|
+
"""从 shadow plan dict 构造一条 turn 记录并累积。
|
|
143
|
+
|
|
144
|
+
``plan`` 为 None(如未生成 shadow plan)时仍记录一条最小记录,标注
|
|
145
|
+
``accounting_accuracy=opaque``,便于统计 opaque request rate(§5.4)。
|
|
146
|
+
"""
|
|
147
|
+
record = BaselineTurnRecord(
|
|
148
|
+
ksadk_commit=self._commit,
|
|
149
|
+
ksadk_version=self._version,
|
|
150
|
+
execution_target=self._execution_target,
|
|
151
|
+
session_id=session_id,
|
|
152
|
+
invocation_id=invocation_id,
|
|
153
|
+
model=str(model or ""),
|
|
154
|
+
recorded_at=_utc_now_iso(),
|
|
155
|
+
compaction_triggered=compaction_triggered,
|
|
156
|
+
compaction_trigger=compaction_trigger,
|
|
157
|
+
prompt_too_long=prompt_too_long,
|
|
158
|
+
retry_attempts=retry_attempts,
|
|
159
|
+
turn_latency_ms=turn_latency_ms,
|
|
160
|
+
time_to_first_token_ms=time_to_first_token_ms,
|
|
161
|
+
capability_mismatch=capability_mismatch,
|
|
162
|
+
)
|
|
163
|
+
if isinstance(plan, Mapping) and plan:
|
|
164
|
+
record.context_policy_version = str(plan.get("policy_version") or "")
|
|
165
|
+
record.runner_type = str(plan.get("runtime_type") or "")
|
|
166
|
+
record.deployment_mode = str(plan.get("deployment_mode") or "local")
|
|
167
|
+
record.integration_mode = str(plan.get("integration_mode") or "")
|
|
168
|
+
record.accounting_accuracy = str(plan.get("accounting_accuracy") or "opaque")
|
|
169
|
+
record.capability_hash = str(plan.get("capability_hash") or "")
|
|
170
|
+
record.plan_id = str(plan.get("plan_id") or "")
|
|
171
|
+
record.prompt_content_hash = str(plan.get("prompt_content_hash") or "")
|
|
172
|
+
record.prompt_stable_prefix_hash = str(plan.get("prompt_stable_prefix_hash") or "")
|
|
173
|
+
record.tokenizer = str(plan.get("tokenizer") or "")
|
|
174
|
+
tbk = plan.get("tokens_by_kind")
|
|
175
|
+
if isinstance(tbk, Mapping):
|
|
176
|
+
record.tokens_by_kind = {str(k): int(v or 0) for k, v in tbk.items()}
|
|
177
|
+
tbs = plan.get("prompt_tokens_by_section")
|
|
178
|
+
if isinstance(tbs, Mapping):
|
|
179
|
+
record.prompt_tokens_by_section = {str(k): int(v or 0) for k, v in tbs.items()}
|
|
180
|
+
record.planned_input_tokens = int(plan.get("planned_input_tokens") or 0)
|
|
181
|
+
else:
|
|
182
|
+
record.accounting_accuracy = "opaque"
|
|
183
|
+
|
|
184
|
+
if isinstance(usage, Mapping) and usage:
|
|
185
|
+
record.runtime_reported_input_tokens = _opt_int(
|
|
186
|
+
usage.get("input_tokens") or usage.get("prompt_tokens")
|
|
187
|
+
)
|
|
188
|
+
record.runtime_output_tokens = _opt_int(
|
|
189
|
+
usage.get("output_tokens") or usage.get("completion_tokens")
|
|
190
|
+
)
|
|
191
|
+
details = usage.get("input_token_details") or usage.get("input_tokens_details")
|
|
192
|
+
if isinstance(details, Mapping):
|
|
193
|
+
record.cache_read_tokens = _opt_int(
|
|
194
|
+
details.get("cached_tokens")
|
|
195
|
+
or details.get("cached")
|
|
196
|
+
or details.get("cache_read")
|
|
197
|
+
)
|
|
198
|
+
record.cache_read_tokens = (
|
|
199
|
+
_opt_int(usage.get("cache_read_input_tokens")) or record.cache_read_tokens
|
|
200
|
+
)
|
|
201
|
+
record.cache_creation_tokens = _opt_int(usage.get("cache_creation_input_tokens"))
|
|
202
|
+
|
|
203
|
+
# cache_status/unexpected_break 由 span 路径(_set_prompt_cache_attributes)同源诊断
|
|
204
|
+
# 并写入 trace;baseline 只记录 raw cache tokens,不重复跑 registry(避免与 span 路径
|
|
205
|
+
# 共享 registry 时的记录顺序污染)。summary 的 unexpected_cache_break_count 据此如实
|
|
206
|
+
# 为 0;完整诊断看 trace。如需 baseline 独立诊断,后续 PR 用独立 registry。
|
|
207
|
+
with self._records_lock:
|
|
208
|
+
self._records.append(record)
|
|
209
|
+
if _flush_each_turn_enabled():
|
|
210
|
+
self.dump(os.environ.get(_BASELINE_PATH_ENV, _DEFAULT_BASELINE_PATH))
|
|
211
|
+
return record
|
|
212
|
+
|
|
213
|
+
def summary(self) -> dict[str, Any]:
|
|
214
|
+
"""汇总指标(评测方案 §12.2 Scorecard 的基线版)。"""
|
|
215
|
+
with self._records_lock:
|
|
216
|
+
if not self._records:
|
|
217
|
+
return {"turn_count": 0}
|
|
218
|
+
total = len(self._records)
|
|
219
|
+
planned = [r.planned_input_tokens for r in self._records if r.planned_input_tokens]
|
|
220
|
+
reported = [
|
|
221
|
+
r.runtime_reported_input_tokens
|
|
222
|
+
for r in self._records
|
|
223
|
+
if r.runtime_reported_input_tokens is not None
|
|
224
|
+
]
|
|
225
|
+
latencies = [r.turn_latency_ms for r in self._records if r.turn_latency_ms is not None]
|
|
226
|
+
return {
|
|
227
|
+
"turn_count": total,
|
|
228
|
+
"ptl_rate": _ratio(sum(1 for r in self._records if r.prompt_too_long), total),
|
|
229
|
+
"compaction_count": sum(1 for r in self._records if r.compaction_triggered),
|
|
230
|
+
"ptl_recovery_count": sum(
|
|
231
|
+
1
|
|
232
|
+
for r in self._records
|
|
233
|
+
if r.prompt_too_long and r.retry_attempts >= 1 and not _is_failed(r)
|
|
234
|
+
),
|
|
235
|
+
"opaque_request_rate": _ratio(
|
|
236
|
+
sum(1 for r in self._records if r.accounting_accuracy == "opaque"), total
|
|
237
|
+
),
|
|
238
|
+
"capability_mismatch_count": sum(1 for r in self._records if r.capability_mismatch),
|
|
239
|
+
"unexpected_cache_break_count": sum(1 for r in self._records if r.unexpected_break),
|
|
240
|
+
"planned_input_tokens": _stats(planned),
|
|
241
|
+
"runtime_reported_input_tokens": _stats(reported),
|
|
242
|
+
"turn_latency_ms": _stats(latencies),
|
|
243
|
+
"runner_type_breakdown": _count_by([r.runner_type for r in self._records]),
|
|
244
|
+
"accounting_accuracy_breakdown": _count_by(
|
|
245
|
+
[r.accounting_accuracy for r in self._records]
|
|
246
|
+
),
|
|
247
|
+
"stable_prefix_hash_changes": _count_distinct(
|
|
248
|
+
[
|
|
249
|
+
r.prompt_stable_prefix_hash
|
|
250
|
+
for r in self._records
|
|
251
|
+
if r.prompt_stable_prefix_hash
|
|
252
|
+
]
|
|
253
|
+
),
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
def dump(self, path: str | Path) -> Path:
|
|
257
|
+
"""落盘 JSONL(每行一条 turn 记录)+ 末尾一条 ``__summary__`` 汇总。"""
|
|
258
|
+
out = Path(path)
|
|
259
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
260
|
+
temporary = out.with_name(f".{out.name}.{os.getpid()}.tmp")
|
|
261
|
+
with self._records_lock:
|
|
262
|
+
with temporary.open("w", encoding="utf-8") as fh:
|
|
263
|
+
for record in self._records:
|
|
264
|
+
fh.write(json.dumps(asdict(record), ensure_ascii=False) + "\n")
|
|
265
|
+
fh.write(json.dumps({"__summary__": self.summary()}, ensure_ascii=False) + "\n")
|
|
266
|
+
os.replace(temporary, out)
|
|
267
|
+
return out
|
|
268
|
+
|
|
269
|
+
def clear(self) -> None:
|
|
270
|
+
with self._records_lock:
|
|
271
|
+
self._records.clear()
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _opt_int(value: Any) -> int | None:
|
|
275
|
+
if value is None:
|
|
276
|
+
return None
|
|
277
|
+
try:
|
|
278
|
+
return int(value)
|
|
279
|
+
except (TypeError, ValueError):
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _ratio(numerator: int, denominator: int) -> float:
|
|
284
|
+
return round(numerator / denominator, 4) if denominator else 0.0
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _stats(values: list[int]) -> dict[str, float]:
|
|
288
|
+
if not values:
|
|
289
|
+
return {"count": 0}
|
|
290
|
+
sorted_vals = sorted(values)
|
|
291
|
+
n = len(sorted_vals)
|
|
292
|
+
p95_idx = min(n - 1, int(n * 0.95))
|
|
293
|
+
return {
|
|
294
|
+
"count": n,
|
|
295
|
+
"mean": round(sum(values) / n, 2),
|
|
296
|
+
"median": sorted_vals[n // 2],
|
|
297
|
+
"p95": sorted_vals[p95_idx],
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _count_by(values: list[str]) -> dict[str, int]:
|
|
302
|
+
counts: dict[str, int] = {}
|
|
303
|
+
for value in values:
|
|
304
|
+
counts[value] = counts.get(value, 0) + 1
|
|
305
|
+
return counts
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _count_distinct(values: list[str]) -> int:
|
|
309
|
+
return len(set(values))
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _is_failed(record: BaselineTurnRecord) -> bool:
|
|
313
|
+
# 没有 runtime output 且无 planned token 视为失败 turn(粗略,供 PTL recovery 统计)。
|
|
314
|
+
return record.runtime_output_tokens is None and record.planned_input_tokens == 0
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _utc_now_iso() -> str:
|
|
318
|
+
# 不能用 datetime.now()(脚本环境可能受限);用 time + 手动格式化 UTC。
|
|
319
|
+
t = time.time()
|
|
320
|
+
secs = int(t)
|
|
321
|
+
millis = int((t - secs) * 1000)
|
|
322
|
+
g = time.gmtime(secs)
|
|
323
|
+
return (
|
|
324
|
+
f"{g.tm_year:04d}-{g.tm_mon:02d}-{g.tm_mday:02d}T"
|
|
325
|
+
f"{g.tm_hour:02d}:{g.tm_min:02d}:{g.tm_sec:02d}.{millis:03d}Z"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# ---------------------------------------------------------------------------
|
|
330
|
+
# 进程级单例 + env-gated 采集挂载
|
|
331
|
+
# ---------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
_BASELINE_COLLECT_ENV = "KSADK_BASELINE_COLLECT"
|
|
334
|
+
_BASELINE_PATH_ENV = "KSADK_BASELINE_PATH"
|
|
335
|
+
_DEFAULT_BASELINE_PATH = "/tmp/ksadk-context-baseline.jsonl"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _flush_each_turn_enabled() -> bool:
|
|
339
|
+
return str(os.environ.get("KSADK_BASELINE_FLUSH_EACH_TURN", "")).strip().lower() in {
|
|
340
|
+
"1",
|
|
341
|
+
"true",
|
|
342
|
+
"yes",
|
|
343
|
+
"on",
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
_singleton_lock = threading.Lock()
|
|
348
|
+
_singleton: BaselineCollector | None = None
|
|
349
|
+
_atexit_registered = False
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def baseline_collection_enabled() -> bool:
|
|
353
|
+
"""是否启用基线采集(env ``KSADK_BASELINE_COLLECT=1/true/on``)。"""
|
|
354
|
+
return str(os.environ.get(_BASELINE_COLLECT_ENV, "")).strip().lower() in (
|
|
355
|
+
"1",
|
|
356
|
+
"true",
|
|
357
|
+
"yes",
|
|
358
|
+
"on",
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _baseline_path() -> str:
|
|
363
|
+
return str(os.environ.get(_BASELINE_PATH_ENV, "") or _DEFAULT_BASELINE_PATH)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def get_baseline_collector() -> BaselineCollector | None:
|
|
367
|
+
"""返回进程级采集器单例;未启用(env 未开)时返回 None。
|
|
368
|
+
|
|
369
|
+
首次启用时注册 atexit dump,保证进程结束自动落盘(评测方案 §10 阶段 1)。
|
|
370
|
+
"""
|
|
371
|
+
global _singleton, _atexit_registered
|
|
372
|
+
if not baseline_collection_enabled():
|
|
373
|
+
return None
|
|
374
|
+
with _singleton_lock:
|
|
375
|
+
if _singleton is None:
|
|
376
|
+
execution_target = str(os.environ.get("KSADK_BASELINE_EXECUTION_TARGET", "runtime"))
|
|
377
|
+
_singleton = BaselineCollector(execution_target=execution_target)
|
|
378
|
+
if not _atexit_registered:
|
|
379
|
+
atexit.register(_atexit_dump)
|
|
380
|
+
_atexit_registered = True
|
|
381
|
+
return _singleton
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _atexit_dump() -> None:
|
|
385
|
+
global _singleton
|
|
386
|
+
if _singleton is None or not _singleton.records:
|
|
387
|
+
return
|
|
388
|
+
try:
|
|
389
|
+
_singleton.dump(_baseline_path())
|
|
390
|
+
except Exception: # noqa: BLE001
|
|
391
|
+
# 采集不能影响进程退出/线上行为。
|
|
392
|
+
pass
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def reset_baseline_collector_for_tests() -> None:
|
|
396
|
+
"""测试用:重置单例与 atexit 标记。"""
|
|
397
|
+
global _singleton, _atexit_registered
|
|
398
|
+
with _singleton_lock:
|
|
399
|
+
_singleton = None
|
|
400
|
+
_atexit_registered = False
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def record_baseline_turn(
|
|
404
|
+
plan: Mapping[str, Any] | None,
|
|
405
|
+
**kwargs: Any,
|
|
406
|
+
) -> None:
|
|
407
|
+
"""env-gated 旁路采集入口:未启用时 no-op,启用时委托给单例 ``record_turn``。
|
|
408
|
+
|
|
409
|
+
供 conversation runtime 旁路调用:传入 ``prepared.shadow_context_plan`` 与
|
|
410
|
+
usage/compaction/PTL/latency 等真实信号。不抛异常、不进决策路径、不改线上行为。
|
|
411
|
+
"""
|
|
412
|
+
collector = get_baseline_collector()
|
|
413
|
+
if collector is None:
|
|
414
|
+
return
|
|
415
|
+
try:
|
|
416
|
+
collector.record_turn(plan, **kwargs)
|
|
417
|
+
except Exception: # noqa: BLE001
|
|
418
|
+
# 采集失败绝不影响主链路。
|
|
419
|
+
pass
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Prompt Cache 失效诊断(方案 7.5)。
|
|
2
|
+
|
|
3
|
+
KsADK 不实现通用 Completion Cache(ADR-013)。本模块只消费 Provider/Runtime 返回的
|
|
4
|
+
prompt cache usage,诊断稳定前缀是否意外失效:
|
|
5
|
+
|
|
6
|
+
- AgentVersion/模型/稳定 section/projection version 变化 → ``expected_invalidation``。
|
|
7
|
+
- 稳定前缀未变但 cache_read 大幅下降(cache_creation>0 且 cache_read≈0)→ 疑似
|
|
8
|
+
``unexpected_break``。
|
|
9
|
+
- 无 runtime usage 或无稳定前缀 → ``opaque`` / ``no_cache_info``,不推断命中率。
|
|
10
|
+
|
|
11
|
+
PR2 只落地诊断原语 + 把 raw 信号记到 span;跨 turn 的"大幅下降"需要历史 hash,本 PR 用
|
|
12
|
+
进程内 best-effort 的"上一稳定前缀"记录(见 ``CacheBreakRegistry``),pod 重启后清空,
|
|
13
|
+
精度如实标注。完成正式跨 session 历史留后续 PR。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import Any, Mapping
|
|
20
|
+
|
|
21
|
+
from ksadk.context_engine.capabilities import ContextAccuracy
|
|
22
|
+
|
|
23
|
+
CacheBreakStatus = str
|
|
24
|
+
"""``cached`` / ``expected_invalidation`` / ``unexpected_break``
|
|
25
|
+
/ ``no_cache_info`` / ``opaque``."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class CacheBreakDiagnosis:
|
|
30
|
+
"""单次请求的 prompt cache 失效诊断结果。"""
|
|
31
|
+
|
|
32
|
+
status: CacheBreakStatus
|
|
33
|
+
cache_read_tokens: int = 0
|
|
34
|
+
cache_creation_tokens: int = 0
|
|
35
|
+
stable_prefix_hash: str = ""
|
|
36
|
+
previous_stable_prefix_hash: str = ""
|
|
37
|
+
unexpected_break: bool = False
|
|
38
|
+
expected_invalidation: bool = False
|
|
39
|
+
break_reason: str = ""
|
|
40
|
+
accounting_accuracy: ContextAccuracy = "opaque"
|
|
41
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _extract_cache_tokens(usage: Mapping[str, Any] | None) -> tuple[int, int]:
|
|
45
|
+
"""从 runtime usage 取 (cache_read_tokens, cache_creation_tokens)。
|
|
46
|
+
|
|
47
|
+
兼容 OpenAI(prompt_tokens_details.cached_tokens / cache_read)与 Anthropic
|
|
48
|
+
(cache_read_input_tokens / cache_creation_input_tokens)两种字段命名。
|
|
49
|
+
"""
|
|
50
|
+
if not isinstance(usage, Mapping):
|
|
51
|
+
return 0, 0
|
|
52
|
+
cache_read = 0
|
|
53
|
+
cache_creation = 0
|
|
54
|
+
|
|
55
|
+
# Anthropic 风格顶层字段
|
|
56
|
+
for read_key in ("cache_read_input_tokens", "cache_read_tokens"):
|
|
57
|
+
value = usage.get(read_key)
|
|
58
|
+
if value is not None:
|
|
59
|
+
try:
|
|
60
|
+
cache_read = max(cache_read, int(value))
|
|
61
|
+
except (TypeError, ValueError):
|
|
62
|
+
pass
|
|
63
|
+
for creation_key in ("cache_creation_input_tokens", "cache_creation_tokens"):
|
|
64
|
+
value = usage.get(creation_key)
|
|
65
|
+
if value is not None:
|
|
66
|
+
try:
|
|
67
|
+
cache_creation = max(cache_creation, int(value))
|
|
68
|
+
except (TypeError, ValueError):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
# OpenAI / 通用 input_token_details.cached
|
|
72
|
+
input_details = usage.get("input_token_details") or usage.get("input_tokens_details")
|
|
73
|
+
if isinstance(input_details, Mapping):
|
|
74
|
+
cached = (
|
|
75
|
+
input_details.get("cached_tokens")
|
|
76
|
+
or input_details.get("cached")
|
|
77
|
+
or input_details.get("cache_read")
|
|
78
|
+
)
|
|
79
|
+
if cached is not None:
|
|
80
|
+
try:
|
|
81
|
+
cache_read = max(cache_read, int(cached))
|
|
82
|
+
except (TypeError, ValueError):
|
|
83
|
+
pass
|
|
84
|
+
prompt_details = usage.get("prompt_tokens_details")
|
|
85
|
+
if isinstance(prompt_details, Mapping):
|
|
86
|
+
cached = prompt_details.get("cached_tokens")
|
|
87
|
+
if cached is not None:
|
|
88
|
+
try:
|
|
89
|
+
cache_read = max(cache_read, int(cached))
|
|
90
|
+
except (TypeError, ValueError):
|
|
91
|
+
pass
|
|
92
|
+
return cache_read, cache_creation
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# 阈值:稳定前缀未变但 cache_read 低于此比例 + 本轮有 cache_creation → 疑似 unexpected break。
|
|
96
|
+
_UNEXPECTED_CACHE_READ_RATIO = 0.10
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def diagnose_cache_break(
|
|
100
|
+
*,
|
|
101
|
+
stable_prefix_hash: str,
|
|
102
|
+
previous_stable_prefix_hash: str | None,
|
|
103
|
+
usage: Mapping[str, Any] | None,
|
|
104
|
+
accounting_accuracy: ContextAccuracy = "opaque",
|
|
105
|
+
expected_invalidation_signal: bool = False,
|
|
106
|
+
) -> CacheBreakDiagnosis:
|
|
107
|
+
"""诊断本次请求的 prompt cache 失效情况(方案 7.5)。
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
stable_prefix_hash: 本轮稳定前缀 hash(来自 ``CompiledPrompt``)。
|
|
111
|
+
previous_stable_prefix_hash: 上一轮稳定前缀 hash(None 表示无历史/首次)。
|
|
112
|
+
usage: Runtime/Provider 返回的 usage mapping(含 cache_read/creation)。
|
|
113
|
+
accounting_accuracy: 该 Runner 的 token 观测精度。
|
|
114
|
+
expected_invalidation_signal: 调用方已知本轮发生了版本/模型/projection 变化。
|
|
115
|
+
"""
|
|
116
|
+
cache_read, cache_creation = _extract_cache_tokens(usage)
|
|
117
|
+
|
|
118
|
+
# opaque:Runner 不暴露可靠 usage(方案 6.3)。
|
|
119
|
+
if accounting_accuracy == "opaque":
|
|
120
|
+
return CacheBreakDiagnosis(
|
|
121
|
+
status="opaque",
|
|
122
|
+
cache_read_tokens=cache_read,
|
|
123
|
+
cache_creation_tokens=cache_creation,
|
|
124
|
+
stable_prefix_hash=stable_prefix_hash,
|
|
125
|
+
accounting_accuracy=accounting_accuracy,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# 无稳定前缀 → 无法判断是否意外失效,只记录 raw 信号。
|
|
129
|
+
if not stable_prefix_hash:
|
|
130
|
+
return CacheBreakDiagnosis(
|
|
131
|
+
status="no_cache_info",
|
|
132
|
+
cache_read_tokens=cache_read,
|
|
133
|
+
cache_creation_tokens=cache_creation,
|
|
134
|
+
stable_prefix_hash=stable_prefix_hash,
|
|
135
|
+
accounting_accuracy=accounting_accuracy,
|
|
136
|
+
break_reason="no stable prefix to diagnose",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# 无 runtime usage → runtime_reported/estimated 都可能拿不到 cache 字段。
|
|
140
|
+
if cache_read == 0 and cache_creation == 0 and not isinstance(usage, Mapping):
|
|
141
|
+
return CacheBreakDiagnosis(
|
|
142
|
+
status="no_cache_info",
|
|
143
|
+
stable_prefix_hash=stable_prefix_hash,
|
|
144
|
+
accounting_accuracy=accounting_accuracy,
|
|
145
|
+
break_reason="no runtime usage reported",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
hash_changed = (
|
|
149
|
+
bool(previous_stable_prefix_hash) and previous_stable_prefix_hash != stable_prefix_hash
|
|
150
|
+
)
|
|
151
|
+
if hash_changed or expected_invalidation_signal:
|
|
152
|
+
return CacheBreakDiagnosis(
|
|
153
|
+
status="expected_invalidation",
|
|
154
|
+
cache_read_tokens=cache_read,
|
|
155
|
+
cache_creation_tokens=cache_creation,
|
|
156
|
+
stable_prefix_hash=stable_prefix_hash,
|
|
157
|
+
previous_stable_prefix_hash=previous_stable_prefix_hash or "",
|
|
158
|
+
expected_invalidation=True,
|
|
159
|
+
accounting_accuracy=accounting_accuracy,
|
|
160
|
+
break_reason="stable prefix changed or explicit invalidation signal",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if cache_read > 0:
|
|
164
|
+
# 稳定前缀未变且命中 → cached。
|
|
165
|
+
return CacheBreakDiagnosis(
|
|
166
|
+
status="cached",
|
|
167
|
+
cache_read_tokens=cache_read,
|
|
168
|
+
cache_creation_tokens=cache_creation,
|
|
169
|
+
stable_prefix_hash=stable_prefix_hash,
|
|
170
|
+
previous_stable_prefix_hash=previous_stable_prefix_hash or "",
|
|
171
|
+
accounting_accuracy=accounting_accuracy,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# 稳定前缀未变、无 cache_read、但有 cache_creation → 疑似 unexpected break。
|
|
175
|
+
if cache_creation > 0 and previous_stable_prefix_hash == stable_prefix_hash:
|
|
176
|
+
return CacheBreakDiagnosis(
|
|
177
|
+
status="unexpected_break",
|
|
178
|
+
cache_read_tokens=cache_read,
|
|
179
|
+
cache_creation_tokens=cache_creation,
|
|
180
|
+
stable_prefix_hash=stable_prefix_hash,
|
|
181
|
+
previous_stable_prefix_hash=previous_stable_prefix_hash or "",
|
|
182
|
+
unexpected_break=True,
|
|
183
|
+
accounting_accuracy=accounting_accuracy,
|
|
184
|
+
break_reason="stable prefix unchanged but cache created instead of read",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
return CacheBreakDiagnosis(
|
|
188
|
+
status="no_cache_info",
|
|
189
|
+
cache_read_tokens=cache_read,
|
|
190
|
+
cache_creation_tokens=cache_creation,
|
|
191
|
+
stable_prefix_hash=stable_prefix_hash,
|
|
192
|
+
previous_stable_prefix_hash=previous_stable_prefix_hash or "",
|
|
193
|
+
accounting_accuracy=accounting_accuracy,
|
|
194
|
+
break_reason="no decisive cache signal",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class CacheBreakRegistry:
|
|
199
|
+
"""进程内 best-effort 的"上一稳定前缀"记录,按 session 维度。
|
|
200
|
+
|
|
201
|
+
用于跨 turn 检测稳定前缀是否变化。仅存活于进程内,pod 重启后清空;精度如实标注为
|
|
202
|
+
``estimated``/``runtime_reported``(由调用方传入),不伪装成持久事实源。
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def __init__(self, *, limit: int = 1024) -> None:
|
|
206
|
+
self._limit = limit
|
|
207
|
+
self._store: dict[str, str] = {}
|
|
208
|
+
|
|
209
|
+
def previous(self, session_id: str) -> str | None:
|
|
210
|
+
return self._store.get(session_id)
|
|
211
|
+
|
|
212
|
+
def record(self, session_id: str, stable_prefix_hash: str) -> None:
|
|
213
|
+
if not session_id or not stable_prefix_hash:
|
|
214
|
+
return
|
|
215
|
+
if session_id not in self._store and len(self._store) >= self._limit:
|
|
216
|
+
# 简单淘汰:丢一个最早的(dict 保序)。不做 LRU 复杂度。
|
|
217
|
+
self._store.pop(next(iter(self._store)))
|
|
218
|
+
self._store[session_id] = stable_prefix_hash
|
|
219
|
+
|
|
220
|
+
def clear(self) -> None:
|
|
221
|
+
self._store.clear()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
_DEFAULT_REGISTRY = CacheBreakRegistry()
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def get_default_cache_break_registry() -> CacheBreakRegistry:
|
|
228
|
+
return _DEFAULT_REGISTRY
|