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
ksadk/memory/service.py
CHANGED
|
@@ -8,15 +8,25 @@ import os
|
|
|
8
8
|
from typing import Any, cast
|
|
9
9
|
|
|
10
10
|
from ksadk.common.aicp_env import resolve_aicp_connection
|
|
11
|
-
from ksadk.memory.adk.backends.base_ltm_backend import
|
|
11
|
+
from ksadk.memory.adk.backends.base_ltm_backend import (
|
|
12
|
+
CAP_SESSION_STATUS,
|
|
13
|
+
CAP_STRUCTURED_SEARCH,
|
|
14
|
+
BaseLongTermMemoryBackend,
|
|
15
|
+
)
|
|
12
16
|
from ksadk.memory.ltm_backend_factory import get_long_term_memory_backend_cls
|
|
17
|
+
from ksadk.memory.models import (
|
|
18
|
+
LongTermMemoryRecord,
|
|
19
|
+
MemoryExtractionStatus,
|
|
20
|
+
MemoryMutationResult,
|
|
21
|
+
UnsupportedMemoryOperation,
|
|
22
|
+
)
|
|
13
23
|
|
|
14
24
|
logger = logging.getLogger(__name__)
|
|
15
25
|
|
|
16
26
|
|
|
17
27
|
def format_memory_entries(entries: list[str]) -> str:
|
|
18
28
|
if not entries:
|
|
19
|
-
return "
|
|
29
|
+
return "" # empty → "" not "未找到"(§10.8)
|
|
20
30
|
|
|
21
31
|
formatted_entries: list[str] = []
|
|
22
32
|
for index, entry in enumerate(entries, 1):
|
|
@@ -35,6 +45,11 @@ def format_memory_entries(entries: list[str]) -> str:
|
|
|
35
45
|
return "\n\n".join(formatted_entries)
|
|
36
46
|
|
|
37
47
|
|
|
48
|
+
def _normalize_content(text: str) -> str:
|
|
49
|
+
"""归一化正文,用于写后确认的可见性匹配(去空白,小写)。"""
|
|
50
|
+
return "".join(str(text or "").split()).lower()
|
|
51
|
+
|
|
52
|
+
|
|
38
53
|
class LongTermMemoryService:
|
|
39
54
|
def __init__(
|
|
40
55
|
self,
|
|
@@ -126,14 +141,137 @@ class LongTermMemoryService:
|
|
|
126
141
|
top_k=top_k if top_k is not None else self.top_k,
|
|
127
142
|
)
|
|
128
143
|
|
|
144
|
+
def search_records(
|
|
145
|
+
self, *, user_id: str, query: str, top_k: int | None = None
|
|
146
|
+
) -> list[LongTermMemoryRecord]:
|
|
147
|
+
"""结构化检索(§7.5):优先走 backend.search_records,返回带服务端 ID 的记录。
|
|
148
|
+
|
|
149
|
+
旧 backend 不支持时抛出 UnsupportedMemoryOperation,
|
|
150
|
+
由上层降级为只读 search/add,不得伪造 ID。
|
|
151
|
+
"""
|
|
152
|
+
return self._backend.search_records(
|
|
153
|
+
user_id=user_id,
|
|
154
|
+
query=query,
|
|
155
|
+
top_k=top_k if top_k is not None else self.top_k,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def update_memory(
|
|
159
|
+
self,
|
|
160
|
+
*,
|
|
161
|
+
user_id: str,
|
|
162
|
+
memory_id: str,
|
|
163
|
+
content: str,
|
|
164
|
+
) -> MemoryMutationResult:
|
|
165
|
+
"""按 ID 原地更新记忆(§7.4);不支持的 backend 抛稳定异常。"""
|
|
166
|
+
return self._backend.update_memory(
|
|
167
|
+
user_id=user_id,
|
|
168
|
+
memory_id=memory_id,
|
|
169
|
+
content=content,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def delete_memory(self, *, user_id: str, memory_id: str) -> MemoryMutationResult:
|
|
173
|
+
"""按 ID 软删除记忆(§7.4);重复删除归一为 already_absent。"""
|
|
174
|
+
return self._backend.delete_memory(user_id=user_id, memory_id=memory_id)
|
|
175
|
+
|
|
176
|
+
def list_memory_records(
|
|
177
|
+
self,
|
|
178
|
+
*,
|
|
179
|
+
user_id: str,
|
|
180
|
+
query: str = "",
|
|
181
|
+
page: int = 1,
|
|
182
|
+
page_size: int = 20,
|
|
183
|
+
) -> list[LongTermMemoryRecord]:
|
|
184
|
+
"""ListMemories 透传:写后确认可见性 / 取当前 MemoryId。"""
|
|
185
|
+
return self._backend.list_memory_records(
|
|
186
|
+
user_id=user_id, query=query, page=page, page_size=page_size
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def get_extraction_status(
|
|
190
|
+
self,
|
|
191
|
+
*,
|
|
192
|
+
user_id: str,
|
|
193
|
+
session_id: str,
|
|
194
|
+
confirm_searchable: bool = False,
|
|
195
|
+
expected_content: str = "",
|
|
196
|
+
) -> MemoryExtractionStatus:
|
|
197
|
+
"""写后确认(§7.7):查询 Session 后台提取状态。
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
confirm_searchable: True 且 State=100 时,进一步通过
|
|
201
|
+
ListMemories 确认目标记录可见(目标达成后 searchable=True)。
|
|
202
|
+
expected_content: 用于在 ListMemories 中定位目标记录的归一化正文。
|
|
203
|
+
"""
|
|
204
|
+
if CAP_SESSION_STATUS not in self._backend.capabilities():
|
|
205
|
+
raise UnsupportedMemoryOperation(
|
|
206
|
+
f"{type(self._backend).__name__} does not support session status"
|
|
207
|
+
)
|
|
208
|
+
status = self._backend.get_extraction_status(user_id=user_id, session_id=session_id)
|
|
209
|
+
if (
|
|
210
|
+
confirm_searchable
|
|
211
|
+
and status.status == "extracted"
|
|
212
|
+
and CAP_STRUCTURED_SEARCH in self._backend.capabilities()
|
|
213
|
+
and expected_content.strip()
|
|
214
|
+
):
|
|
215
|
+
# 用 expected_content 作为 Query 语义过滤,避免大记忆库时目标不在首页。
|
|
216
|
+
records = self.list_memory_records(
|
|
217
|
+
user_id=user_id, query=expected_content, page_size=50
|
|
218
|
+
)
|
|
219
|
+
if self._find_matching_record(records, expected_content) is not None:
|
|
220
|
+
status = MemoryExtractionStatus(
|
|
221
|
+
session_id=status.session_id,
|
|
222
|
+
state=status.state,
|
|
223
|
+
status=status.status,
|
|
224
|
+
searchable=True,
|
|
225
|
+
message=status.message,
|
|
226
|
+
)
|
|
227
|
+
return status
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def _find_matching_record(
|
|
231
|
+
records: list[LongTermMemoryRecord], expected_content: str
|
|
232
|
+
) -> LongTermMemoryRecord | None:
|
|
233
|
+
"""按归一化正文等值/包含匹配目标记录(方案 N3:可见性判定规则)。
|
|
234
|
+
|
|
235
|
+
expected_content 为空时不作匹配(返回 None),避免误判任意记录为可见。
|
|
236
|
+
"""
|
|
237
|
+
normalized = _normalize_content(expected_content)
|
|
238
|
+
if not normalized:
|
|
239
|
+
return None
|
|
240
|
+
for record in records:
|
|
241
|
+
if normalized == _normalize_content(record.content):
|
|
242
|
+
return record
|
|
243
|
+
for record in records:
|
|
244
|
+
if normalized in _normalize_content(record.content):
|
|
245
|
+
return record
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
def capabilities(self) -> set[str]:
|
|
249
|
+
"""透传 backend 能力声明(§7.3)。"""
|
|
250
|
+
return set(self._backend.capabilities())
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def last_error(self) -> str:
|
|
254
|
+
"""最近一次后端失败原因(成功调用前置空,失败时填充)。
|
|
255
|
+
|
|
256
|
+
后端(SDK/HTTP)失败时可能吞掉异常返空列表而非抛错,这里把该信号暴露给
|
|
257
|
+
``build_context``,以区分"后端吞错返空"与"真无记忆"。
|
|
258
|
+
"""
|
|
259
|
+
return str(getattr(self._backend, "last_error", "") or "")
|
|
260
|
+
|
|
129
261
|
def search_text(self, *, user_id: str, query: str, top_k: int | None = None) -> str:
|
|
262
|
+
"""检索长期记忆并格式化为文本(方案 §10.8:错误不得混入正文)。
|
|
263
|
+
|
|
264
|
+
Provider 异常时返回空字符串而非错误文本——错误文本会被当作记忆正文注入模型上下文,
|
|
265
|
+
污染回答。需要区分"真无记忆"与"后端失败"的调用方应改用 ``build_context()``
|
|
266
|
+
或检查 ``self.last_error``。
|
|
267
|
+
"""
|
|
130
268
|
try:
|
|
131
269
|
return format_memory_entries(
|
|
132
270
|
self.search_entries(user_id=user_id, query=query, top_k=top_k)
|
|
133
271
|
)
|
|
134
272
|
except Exception as exc:
|
|
135
273
|
logger.error("load_memory failed: %s", exc)
|
|
136
|
-
return
|
|
274
|
+
return ""
|
|
137
275
|
|
|
138
276
|
def save_event_strings(
|
|
139
277
|
self,
|
|
@@ -141,18 +279,52 @@ class LongTermMemoryService:
|
|
|
141
279
|
user_id: str,
|
|
142
280
|
event_strings: list[str],
|
|
143
281
|
metadata: dict[str, Any] | None = None,
|
|
282
|
+
session_id: str | None = None,
|
|
283
|
+
flush: bool | None = None,
|
|
144
284
|
) -> bool:
|
|
285
|
+
"""保存事件字符串。
|
|
286
|
+
|
|
287
|
+
显式参数优先于 metadata(方案 §7.6):
|
|
288
|
+
flush: None 表示未指定,回退到 metadata["flush"];
|
|
289
|
+
True/False 显式覆盖 metadata。
|
|
290
|
+
session_id: None 回退到 metadata["session_id"]。
|
|
291
|
+
兼容期旧调用(仅 metadata)行为不变。
|
|
292
|
+
"""
|
|
293
|
+
base_metadata = dict(metadata or {})
|
|
294
|
+
effective_flush = flush if flush is not None else base_metadata.get("flush")
|
|
295
|
+
if session_id is not None:
|
|
296
|
+
base_metadata["session_id"] = session_id
|
|
297
|
+
effective_session_id = base_metadata.get("session_id")
|
|
298
|
+
# 只有显式为 True 时才携带 flush;False/未携带时不传,
|
|
299
|
+
# 让服务端走默认累积策略(§5.2)。
|
|
300
|
+
if effective_flush is True:
|
|
301
|
+
base_metadata["flush"] = True
|
|
302
|
+
else:
|
|
303
|
+
base_metadata.pop("flush", None)
|
|
145
304
|
return bool(
|
|
146
305
|
self._backend.save_memory(
|
|
147
306
|
user_id=user_id,
|
|
148
307
|
event_strings=event_strings,
|
|
149
|
-
metadata=
|
|
308
|
+
metadata=base_metadata,
|
|
309
|
+
session_id=effective_session_id,
|
|
310
|
+
flush=effective_flush,
|
|
150
311
|
)
|
|
151
312
|
)
|
|
152
313
|
|
|
153
314
|
def save_text(
|
|
154
|
-
self,
|
|
315
|
+
self,
|
|
316
|
+
*,
|
|
317
|
+
user_id: str,
|
|
318
|
+
content: str,
|
|
319
|
+
metadata: dict[str, Any] | None = None,
|
|
320
|
+
session_id: str | None = None,
|
|
321
|
+
flush: bool | None = None,
|
|
155
322
|
) -> bool:
|
|
323
|
+
"""保存自包含事实文本。
|
|
324
|
+
|
|
325
|
+
显式 flush=True 用于显式、内容自包含的持久事实保存
|
|
326
|
+
(如 ksadk_memory_add);普通 sync_turn 不传 flush(§3.1/§8.7)。
|
|
327
|
+
"""
|
|
156
328
|
payload = {
|
|
157
329
|
"role": "user",
|
|
158
330
|
"parts": [{"text": content}],
|
|
@@ -162,6 +334,8 @@ class LongTermMemoryService:
|
|
|
162
334
|
user_id=user_id,
|
|
163
335
|
event_strings=[json.dumps(payload, ensure_ascii=False)],
|
|
164
336
|
metadata=metadata,
|
|
337
|
+
session_id=session_id,
|
|
338
|
+
flush=flush,
|
|
165
339
|
)
|
|
166
340
|
|
|
167
341
|
def build_context(
|
|
@@ -176,7 +350,15 @@ class LongTermMemoryService:
|
|
|
176
350
|
return None
|
|
177
351
|
if not self.is_configured():
|
|
178
352
|
return None
|
|
353
|
+
try:
|
|
354
|
+
entries = self.search_entries(user_id=user_id, query=normalized, top_k=top_k)
|
|
355
|
+
except Exception as exc:
|
|
356
|
+
logger.error("load_memory failed: %s", exc)
|
|
357
|
+
return {"query": normalized, "formatted_text": "", "error": str(exc)}
|
|
358
|
+
backend_error = self.last_error
|
|
359
|
+
if not entries and backend_error:
|
|
360
|
+
return {"query": normalized, "formatted_text": "", "error": backend_error}
|
|
179
361
|
return {
|
|
180
362
|
"query": normalized,
|
|
181
|
-
"formatted_text":
|
|
363
|
+
"formatted_text": format_memory_entries(entries),
|
|
182
364
|
}
|
ksadk/model_proxy/bootstrap.py
CHANGED
|
@@ -21,6 +21,7 @@ from .server import ProxyServer
|
|
|
21
21
|
# 进程级单例:setup_environment 多次调用只起一个 proxy
|
|
22
22
|
_proxy: Optional[ProxyServer] = None
|
|
23
23
|
_original_base: Optional[str] = None
|
|
24
|
+
_original_base_env: Optional[dict[str, str | None]] = None
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def setup_proxy_redirect_if_enabled(
|
|
@@ -35,7 +36,7 @@ def setup_proxy_redirect_if_enabled(
|
|
|
35
36
|
返回 proxy base_url(已重定向)或 None(未启用)。多次调用幂等(单例)。
|
|
36
37
|
上游凭证来自 ProxyConfig(从 env 读),不下发给子进程(凭证闭合)。
|
|
37
38
|
"""
|
|
38
|
-
global _proxy, _original_base
|
|
39
|
+
global _proxy, _original_base, _original_base_env
|
|
39
40
|
if _proxy is not None:
|
|
40
41
|
return _proxy.base_url # 幂等:已起
|
|
41
42
|
gate = gate or ProxyGate.from_env()
|
|
@@ -48,9 +49,7 @@ def setup_proxy_redirect_if_enabled(
|
|
|
48
49
|
or os.environ.get("OPENAI_API_BASE")
|
|
49
50
|
or ""
|
|
50
51
|
)
|
|
51
|
-
key = (
|
|
52
|
-
api_key or os.environ.get("OPENAI_API_KEY") or os.environ.get("LLM_API_KEY") or ""
|
|
53
|
-
)
|
|
52
|
+
key = api_key or os.environ.get("OPENAI_API_KEY") or os.environ.get("LLM_API_KEY") or ""
|
|
54
53
|
token = local_token or os.environ.get("KSADK_PROXY_TOKEN") or ""
|
|
55
54
|
if not upstream or not key:
|
|
56
55
|
return None # 缺凭证/上游,不启用(保持原 env)
|
|
@@ -59,7 +58,11 @@ def setup_proxy_redirect_if_enabled(
|
|
|
59
58
|
srv.start()
|
|
60
59
|
_proxy = srv
|
|
61
60
|
# 记录原 base 并重定向(双别名都指向 proxy)
|
|
62
|
-
|
|
61
|
+
_original_base_env = {
|
|
62
|
+
"OPENAI_BASE_URL": os.environ.get("OPENAI_BASE_URL"),
|
|
63
|
+
"OPENAI_API_BASE": os.environ.get("OPENAI_API_BASE"),
|
|
64
|
+
}
|
|
65
|
+
_original_base = _original_base_env["OPENAI_BASE_URL"] or _original_base_env["OPENAI_API_BASE"]
|
|
63
66
|
os.environ["OPENAI_BASE_URL"] = srv.base_url
|
|
64
67
|
os.environ["OPENAI_API_BASE"] = srv.base_url
|
|
65
68
|
if not token:
|
|
@@ -70,11 +73,15 @@ def setup_proxy_redirect_if_enabled(
|
|
|
70
73
|
|
|
71
74
|
def teardown_proxy_redirect() -> None:
|
|
72
75
|
"""回收 proxy 并恢复原 OPENAI_BASE_URL(进程退出/卸载时调)。"""
|
|
73
|
-
global _proxy, _original_base
|
|
76
|
+
global _proxy, _original_base, _original_base_env
|
|
74
77
|
if _proxy is not None:
|
|
75
78
|
_proxy.stop()
|
|
76
79
|
_proxy = None
|
|
77
|
-
if
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
if _original_base_env is not None:
|
|
81
|
+
for key, value in _original_base_env.items():
|
|
82
|
+
if value is None:
|
|
83
|
+
os.environ.pop(key, None)
|
|
84
|
+
else:
|
|
85
|
+
os.environ[key] = value
|
|
86
|
+
_original_base_env = None
|
|
80
87
|
_original_base = None
|
ksadk/model_proxy/detect.py
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
超时/5xx/429/401/403 一律 "unknown",不改变判定(故障 ≠ 能力缺失)。
|
|
13
13
|
- ``stream_delta_ok`` 需真发流式请求才能判定,成本高,本模块默认 None(不探),
|
|
14
14
|
由调用方按需触发或默认走转换层(转换层自己生成完整 delta)。
|
|
15
|
+
- Codex 直连还需功能性 tool probes:真实发送当前 Codex 会声明的
|
|
16
|
+
``additional_tools`` 工具面。任一必需类型被拒时保留
|
|
17
|
+
``responses_supported=True``,同时推荐经 Chat 转换层执行。
|
|
15
18
|
"""
|
|
16
19
|
|
|
17
20
|
from __future__ import annotations
|
|
@@ -24,6 +27,12 @@ import httpx
|
|
|
24
27
|
|
|
25
28
|
Verdict = Literal["supported", "unsupported", "unknown"]
|
|
26
29
|
|
|
30
|
+
# These are Codex wire-contract capabilities, not provider/model allowlists.
|
|
31
|
+
# namespace/custom are required for a native Responses turn; web_search is an
|
|
32
|
+
# optional built-in that may be disabled without downgrading the whole protocol.
|
|
33
|
+
CODEX_DIRECT_REQUIRED_TOOL_TYPES = frozenset({"namespace", "custom"})
|
|
34
|
+
CODEX_OPTIONAL_TOOL_TYPES = frozenset({"web_search"})
|
|
35
|
+
|
|
27
36
|
|
|
28
37
|
@dataclass
|
|
29
38
|
class ModelCapabilities:
|
|
@@ -31,7 +40,7 @@ class ModelCapabilities:
|
|
|
31
40
|
|
|
32
41
|
responses_supported: bool | None = None # None = 未知(未探/不确定)
|
|
33
42
|
stream_delta_ok: bool | None = None # 流式增量 delta;None = 未探
|
|
34
|
-
tool_types: set[str] = field(default_factory=set) #
|
|
43
|
+
tool_types: set[str] = field(default_factory=set) # 已实测支持的 Codex 工具类型
|
|
35
44
|
preferred_protocol: str = "chat" # "responses" | "chat":路由该走哪条
|
|
36
45
|
checked_at: float = 0.0
|
|
37
46
|
verdict: Verdict = "unknown" # 最近一次探测结论
|
|
@@ -65,7 +74,10 @@ def probe_responses_capability(
|
|
|
65
74
|
):
|
|
66
75
|
"""功能性 probe /v1/responses,返回能力矩阵(sync) 或 coroutine(async)。
|
|
67
76
|
|
|
68
|
-
- 200
|
|
77
|
+
- 纯文本 200 且结构合法后,再逐个探测真实 Codex 工具类型
|
|
78
|
+
- namespace/custom 成功→ preferred_protocol=responses
|
|
79
|
+
- 仅 web_search 被拒→仍为 responses,但不把 web_search 记入 tool_types
|
|
80
|
+
- namespace/custom 被拒→ supported,preferred_protocol=chat
|
|
69
81
|
- 200 但非 responses 结构(网关伪 200)→ unsupported,preferred_protocol=chat
|
|
70
82
|
- 404/405/400 unknown → unsupported,preferred_protocol=chat
|
|
71
83
|
- 超时/5xx/429/401/403 → unknown,preferred_protocol 保持默认 chat(保守走转换层)
|
|
@@ -86,16 +98,47 @@ def _probe_sync(
|
|
|
86
98
|
caps = ModelCapabilities(checked_at=time.time(), verdict="unknown")
|
|
87
99
|
url = f"{base.rstrip('/')}/responses"
|
|
88
100
|
headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
|
|
89
|
-
payload = {"model": model, "input": "hi", "max_output_tokens": 1, "stream": False}
|
|
90
101
|
try:
|
|
91
|
-
r = client.post(
|
|
102
|
+
r = client.post(
|
|
103
|
+
url,
|
|
104
|
+
json=_base_probe_payload(model),
|
|
105
|
+
headers=headers,
|
|
106
|
+
timeout=timeout,
|
|
107
|
+
)
|
|
92
108
|
except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError):
|
|
93
109
|
return _finalize(caps)
|
|
94
110
|
try:
|
|
95
111
|
data = r.json()
|
|
96
112
|
except ValueError:
|
|
97
113
|
data = None
|
|
98
|
-
|
|
114
|
+
caps = _apply_response(caps, r.status_code, r.text, data)
|
|
115
|
+
if not caps.responses_supported:
|
|
116
|
+
return caps
|
|
117
|
+
for tool_type, payload in _codex_tool_probe_payloads(model):
|
|
118
|
+
try:
|
|
119
|
+
tool_response = client.post(
|
|
120
|
+
url,
|
|
121
|
+
json=payload,
|
|
122
|
+
headers=headers,
|
|
123
|
+
timeout=timeout,
|
|
124
|
+
)
|
|
125
|
+
except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError):
|
|
126
|
+
caps.verdict = "unknown"
|
|
127
|
+
return _finalize(caps)
|
|
128
|
+
try:
|
|
129
|
+
tool_data = tool_response.json()
|
|
130
|
+
except ValueError:
|
|
131
|
+
tool_data = None
|
|
132
|
+
caps = _apply_tool_response(
|
|
133
|
+
caps,
|
|
134
|
+
tool_type,
|
|
135
|
+
tool_response.status_code,
|
|
136
|
+
tool_response.text,
|
|
137
|
+
tool_data,
|
|
138
|
+
)
|
|
139
|
+
if tool_type not in caps.tool_types:
|
|
140
|
+
return caps
|
|
141
|
+
return _finalize(caps)
|
|
99
142
|
|
|
100
143
|
|
|
101
144
|
async def _probe_async(
|
|
@@ -104,16 +147,162 @@ async def _probe_async(
|
|
|
104
147
|
caps = ModelCapabilities(checked_at=time.time(), verdict="unknown")
|
|
105
148
|
url = f"{base.rstrip('/')}/responses"
|
|
106
149
|
headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
|
|
107
|
-
payload = {"model": model, "input": "hi", "max_output_tokens": 1, "stream": False}
|
|
108
150
|
try:
|
|
109
|
-
r = await client.post(
|
|
151
|
+
r = await client.post(
|
|
152
|
+
url,
|
|
153
|
+
json=_base_probe_payload(model),
|
|
154
|
+
headers=headers,
|
|
155
|
+
timeout=timeout,
|
|
156
|
+
)
|
|
110
157
|
except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError):
|
|
111
158
|
return _finalize(caps)
|
|
112
159
|
try:
|
|
113
160
|
data = r.json()
|
|
114
161
|
except ValueError:
|
|
115
162
|
data = None
|
|
116
|
-
|
|
163
|
+
caps = _apply_response(caps, r.status_code, r.text, data)
|
|
164
|
+
if not caps.responses_supported:
|
|
165
|
+
return caps
|
|
166
|
+
for tool_type, payload in _codex_tool_probe_payloads(model):
|
|
167
|
+
try:
|
|
168
|
+
tool_response = await client.post(
|
|
169
|
+
url,
|
|
170
|
+
json=payload,
|
|
171
|
+
headers=headers,
|
|
172
|
+
timeout=timeout,
|
|
173
|
+
)
|
|
174
|
+
except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError):
|
|
175
|
+
caps.verdict = "unknown"
|
|
176
|
+
return _finalize(caps)
|
|
177
|
+
try:
|
|
178
|
+
tool_data = tool_response.json()
|
|
179
|
+
except ValueError:
|
|
180
|
+
tool_data = None
|
|
181
|
+
caps = _apply_tool_response(
|
|
182
|
+
caps,
|
|
183
|
+
tool_type,
|
|
184
|
+
tool_response.status_code,
|
|
185
|
+
tool_response.text,
|
|
186
|
+
tool_data,
|
|
187
|
+
)
|
|
188
|
+
if tool_type not in caps.tool_types:
|
|
189
|
+
return caps
|
|
190
|
+
return _finalize(caps)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _base_probe_payload(model: str) -> dict[str, Any]:
|
|
194
|
+
return {"model": model, "input": "hi", "max_output_tokens": 1, "stream": False}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _namespace_probe_payload(model: str) -> dict[str, Any]:
|
|
198
|
+
"""Return the smallest real Codex 0.147 dynamic-tool declaration."""
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
"model": model,
|
|
202
|
+
"input": [
|
|
203
|
+
{
|
|
204
|
+
"type": "additional_tools",
|
|
205
|
+
"role": "developer",
|
|
206
|
+
"tools": [
|
|
207
|
+
{
|
|
208
|
+
"type": "namespace",
|
|
209
|
+
"name": "functions",
|
|
210
|
+
"description": "KsADK Codex capability probe",
|
|
211
|
+
"tools": [
|
|
212
|
+
{
|
|
213
|
+
"type": "function",
|
|
214
|
+
"name": "probe",
|
|
215
|
+
"description": "Probe Codex namespace tool support",
|
|
216
|
+
"parameters": {
|
|
217
|
+
"type": "object",
|
|
218
|
+
"properties": {},
|
|
219
|
+
"additionalProperties": False,
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
],
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"type": "message",
|
|
228
|
+
"role": "user",
|
|
229
|
+
"content": [{"type": "input_text", "text": "Reply with OK."}],
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
"max_output_tokens": 1,
|
|
233
|
+
"stream": False,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _custom_probe_payload(model: str) -> dict[str, Any]:
|
|
238
|
+
"""Return the smallest Codex namespaced freeform-tool declaration."""
|
|
239
|
+
|
|
240
|
+
payload = _namespace_probe_payload(model)
|
|
241
|
+
payload["input"][0]["tools"][0]["tools"] = [
|
|
242
|
+
{
|
|
243
|
+
"type": "custom",
|
|
244
|
+
"name": "probe",
|
|
245
|
+
"description": "Probe Codex custom tool support",
|
|
246
|
+
"format": {
|
|
247
|
+
"type": "grammar",
|
|
248
|
+
"syntax": "lark",
|
|
249
|
+
"definition": 'start: "OK"',
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
return payload
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _web_search_probe_payload(model: str) -> dict[str, Any]:
|
|
257
|
+
"""Return Codex's ``additional_tools`` built-in web-search declaration."""
|
|
258
|
+
|
|
259
|
+
payload = _namespace_probe_payload(model)
|
|
260
|
+
payload["input"][0]["tools"] = [{"type": "web_search"}]
|
|
261
|
+
return payload
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _codex_tool_probe_payloads(model: str) -> tuple[tuple[str, dict[str, Any]], ...]:
|
|
265
|
+
"""Return the model-agnostic direct tool surface required by Codex."""
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
("namespace", _namespace_probe_payload(model)),
|
|
269
|
+
("custom", _custom_probe_payload(model)),
|
|
270
|
+
("web_search", _web_search_probe_payload(model)),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _apply_tool_response(
|
|
275
|
+
caps: ModelCapabilities, tool_type: str, status: int, text: str, data: Any
|
|
276
|
+
) -> ModelCapabilities:
|
|
277
|
+
valid_envelope = isinstance(data, dict) and "output" in data and "status" in data
|
|
278
|
+
envelope_failed = valid_envelope and (
|
|
279
|
+
str(data.get("status") or "").lower() == "failed" or bool(data.get("error"))
|
|
280
|
+
)
|
|
281
|
+
if status == 200 and valid_envelope and not envelope_failed:
|
|
282
|
+
caps.tool_types.add(tool_type)
|
|
283
|
+
caps.verdict = "supported"
|
|
284
|
+
return _finalize(caps)
|
|
285
|
+
|
|
286
|
+
low = (text or "").lower()
|
|
287
|
+
dialect_marker = any(
|
|
288
|
+
marker in low
|
|
289
|
+
for marker in (
|
|
290
|
+
tool_type.lower(),
|
|
291
|
+
"additional_tools",
|
|
292
|
+
"invalid value",
|
|
293
|
+
"supported values",
|
|
294
|
+
"not supported",
|
|
295
|
+
"unrecognized",
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
dialect_rejected = (
|
|
299
|
+
status in (400, 404, 405, 422) and (status in (404, 405) or dialect_marker)
|
|
300
|
+
) or (status == 200 and envelope_failed and dialect_marker)
|
|
301
|
+
if not dialect_rejected:
|
|
302
|
+
# Transient failures do not establish direct compatibility and should
|
|
303
|
+
# not be cached as a native Codex tool-capable endpoint.
|
|
304
|
+
caps.verdict = "unknown"
|
|
305
|
+
return _finalize(caps)
|
|
117
306
|
|
|
118
307
|
|
|
119
308
|
def _apply_response(
|
|
@@ -136,7 +325,11 @@ def _apply_response(
|
|
|
136
325
|
|
|
137
326
|
def _finalize(caps: ModelCapabilities) -> ModelCapabilities:
|
|
138
327
|
"""根据 verdict 定 preferred_protocol(保守:不确定也走转换层 chat)。"""
|
|
139
|
-
if
|
|
328
|
+
if (
|
|
329
|
+
caps.verdict == "supported"
|
|
330
|
+
and caps.responses_supported
|
|
331
|
+
and CODEX_DIRECT_REQUIRED_TOOL_TYPES.issubset(caps.tool_types)
|
|
332
|
+
):
|
|
140
333
|
caps.preferred_protocol = "responses"
|
|
141
334
|
else:
|
|
142
335
|
# unsupported 或 unknown 都默认 chat(走转换层):unknown 时走转换层更安全,
|
ksadk/model_proxy/namespace.py
CHANGED
|
@@ -83,13 +83,19 @@ def build_restore_map(tools: list[Any] | None) -> dict[str, dict[str, str]]:
|
|
|
83
83
|
if not namespace:
|
|
84
84
|
continue
|
|
85
85
|
for child in _namespace_children(tool):
|
|
86
|
-
if not isinstance(child, dict) or child.get("type")
|
|
86
|
+
if not isinstance(child, dict) or child.get("type") not in {
|
|
87
|
+
"function",
|
|
88
|
+
"custom",
|
|
89
|
+
}:
|
|
87
90
|
continue
|
|
88
91
|
name = (child.get("name") or "").strip()
|
|
89
92
|
if not name:
|
|
90
93
|
continue
|
|
91
94
|
flat = flatten_namespace_tool_name(namespace, name)
|
|
92
|
-
|
|
95
|
+
entry = {"namespace": namespace, "name": name}
|
|
96
|
+
if child.get("type") == "custom":
|
|
97
|
+
entry["custom"] = "true"
|
|
98
|
+
restore.setdefault(flat, entry)
|
|
93
99
|
return restore
|
|
94
100
|
|
|
95
101
|
|
|
@@ -104,7 +110,7 @@ def _rewrite_qualified_calls(value: Any, owners: dict[str, dict[str, str]]) -> b
|
|
|
104
110
|
for item in value:
|
|
105
111
|
changed |= _rewrite_qualified_calls(item, owners)
|
|
106
112
|
elif isinstance(value, dict):
|
|
107
|
-
if value.get("type")
|
|
113
|
+
if value.get("type") in {"function_call", "custom_tool_call"}:
|
|
108
114
|
namespace = (value.get("namespace") or "").strip()
|
|
109
115
|
name = (value.get("name") or "").strip()
|
|
110
116
|
if namespace and name:
|
|
@@ -142,24 +148,39 @@ def flatten_request_namespaces(body: dict) -> dict[str, dict[str, str]]:
|
|
|
142
148
|
if tool.get("type") == "namespace":
|
|
143
149
|
namespace = (tool.get("name") or "").strip()
|
|
144
150
|
for child in _namespace_children(tool):
|
|
145
|
-
if not isinstance(child, dict) or child.get("type")
|
|
151
|
+
if not isinstance(child, dict) or child.get("type") not in {
|
|
152
|
+
"function",
|
|
153
|
+
"custom",
|
|
154
|
+
}:
|
|
146
155
|
continue
|
|
147
156
|
name = (child.get("name") or "").strip()
|
|
148
157
|
if not name or not namespace:
|
|
149
158
|
continue
|
|
150
159
|
flat = flatten_namespace_tool_name(namespace, name)
|
|
151
160
|
if flat in seen_flat:
|
|
152
|
-
raise ValueError(
|
|
153
|
-
f"namespace 拍平撞名:{flat} 来自不同 child,上游无法消歧"
|
|
154
|
-
)
|
|
161
|
+
raise ValueError(f"namespace 拍平撞名:{flat} 来自不同 child,上游无法消歧")
|
|
155
162
|
seen_flat.add(flat)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
if child.get("type") == "custom":
|
|
164
|
+
flat_tools.append(
|
|
165
|
+
{
|
|
166
|
+
"type": "custom",
|
|
167
|
+
"name": flat,
|
|
168
|
+
"description": child.get("description", ""),
|
|
169
|
+
**({"format": child["format"]} if "format" in child else {}),
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
else:
|
|
173
|
+
flat_tools.append(
|
|
174
|
+
{
|
|
175
|
+
"type": "function",
|
|
176
|
+
"name": flat,
|
|
177
|
+
"description": child.get("description", ""),
|
|
178
|
+
"parameters": child.get(
|
|
179
|
+
"parameters", {"type": "object", "properties": {}}
|
|
180
|
+
),
|
|
181
|
+
**({"strict": child["strict"]} if "strict" in child else {}),
|
|
182
|
+
}
|
|
183
|
+
)
|
|
163
184
|
else:
|
|
164
185
|
flat_tools.append(tool)
|
|
165
186
|
body["tools"] = flat_tools
|
|
@@ -194,7 +215,10 @@ def restore_function_call(item: dict, restore_map: dict[str, dict[str, str]]) ->
|
|
|
194
215
|
item["type"] = "custom_tool_call"
|
|
195
216
|
item["name"] = entry["name"]
|
|
196
217
|
item["input"] = text_input
|
|
197
|
-
|
|
218
|
+
if entry.get("namespace"):
|
|
219
|
+
item["namespace"] = entry["namespace"]
|
|
220
|
+
else:
|
|
221
|
+
item.pop("namespace", None)
|
|
198
222
|
return item
|
|
199
223
|
item["name"] = entry["name"]
|
|
200
224
|
item["namespace"] = entry["namespace"]
|