agentengine-sdk-python 0.6.9__py3-none-any.whl → 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +41 -23
- agentengine_sdk_python-0.8.0.dist-info/RECORD +578 -0
- ksadk/a2a/__init__.py +178 -11
- ksadk/a2a/bootstrap.py +334 -0
- ksadk/a2a/card.py +120 -0
- ksadk/a2a/context_store.py +203 -0
- ksadk/a2a/control_plane.py +818 -0
- ksadk/a2a/credential.py +99 -0
- ksadk/a2a/event_adapter.py +149 -0
- ksadk/a2a/executor.py +406 -89
- ksadk/a2a/external_transport.py +516 -0
- ksadk/a2a/identity.py +304 -0
- ksadk/a2a/ids.py +22 -0
- ksadk/a2a/langgraph.py +285 -0
- ksadk/a2a/resume_store.py +201 -0
- ksadk/a2a/routes.py +105 -0
- ksadk/a2a/server.py +107 -60
- ksadk/a2a/space_client.py +1101 -0
- ksadk/a2a/task_adapter.py +423 -0
- ksadk/a2a/task_event_dispatcher.py +167 -0
- ksadk/a2a/task_event_outbox.py +326 -0
- ksadk/a2a/task_store.py +206 -0
- ksadk/a2ui/__init__.py +27 -0
- ksadk/a2ui/core.py +186 -0
- ksadk/a2ui/models.py +166 -0
- ksadk/a2ui/renderer.py +198 -0
- ksadk/agui/__init__.py +9 -0
- ksadk/agui/a2ui_projection.py +92 -0
- ksadk/agui/agent.py +857 -0
- ksadk/agui/config.py +86 -0
- ksadk/agui/routes.py +55 -0
- ksadk/api/client.py +252 -191
- ksadk/builders/__init__.py +2 -0
- ksadk/builders/base.py +16 -14
- ksadk/builders/code_builder.py +318 -236
- ksadk/builders/container_builder.py +187 -167
- ksadk/builders/framework_requirements.py +29 -11
- ksadk/builders/ks3_uploader.py +58 -27
- ksadk/builders/managed_runtime_builder.py +145 -0
- ksadk/builders/mcp_builder.py +91 -92
- ksadk/builders/requirements_utils.py +1 -5
- ksadk/cli/__init__.py +89 -171
- ksadk/cli/cmd_a2a.py +254 -70
- ksadk/cli/cmd_agent.py +19 -5
- ksadk/cli/cmd_build.py +158 -29
- ksadk/cli/cmd_completion.py +47 -35
- ksadk/cli/cmd_config.py +267 -198
- ksadk/cli/cmd_create.py +533 -726
- ksadk/cli/cmd_dashboard.py +120 -45
- ksadk/cli/cmd_deploy.py +117 -58
- ksadk/cli/cmd_destroy.py +44 -14
- ksadk/cli/cmd_files.py +63 -24
- ksadk/cli/cmd_hermes.py +173 -90
- ksadk/cli/cmd_invoke.py +388 -159
- ksadk/cli/cmd_launch.py +64 -28
- ksadk/cli/cmd_mcp.py +103 -64
- ksadk/cli/cmd_model.py +14 -5
- ksadk/cli/cmd_openclaw.py +488 -228
- ksadk/cli/cmd_replay.py +71 -0
- ksadk/cli/cmd_run.py +44 -14
- ksadk/cli/cmd_status.py +80 -31
- ksadk/cli/cmd_version.py +20 -13
- ksadk/cli/cmd_web.py +89 -13
- ksadk/cli/deploy_utils.py +20 -20
- ksadk/cli/dry_run.py +5 -3
- ksadk/cli/error_utils.py +37 -12
- ksadk/cli/global_options.py +1 -2
- ksadk/cli/local_runtime.py +1 -2
- ksadk/cli/model_catalog.py +6 -2
- ksadk/cli/network_options.py +38 -16
- ksadk/cli/resource_common.py +6 -2
- ksadk/cli/storage.py +0 -1
- ksadk/cli/ui.py +5 -3
- ksadk/cli/workflow_common.py +51 -17
- ksadk/codex/__init__.py +12 -0
- ksadk/codex/client.py +408 -0
- ksadk/codex/phase.py +143 -0
- ksadk/codex/runtime.py +449 -0
- ksadk/common/aicp_env.py +0 -1
- ksadk/common/auth.py +2 -2
- ksadk/common/constants.py +1 -3
- ksadk/common/llm_utils.py +17 -13
- ksadk/compat/__init__.py +5 -0
- ksadk/compat/adk_compat.py +200 -0
- ksadk/compat/copilotkit_a2ui.py +330 -0
- ksadk/configs/__init__.py +12 -12
- ksadk/configs/env_registry.py +501 -100
- ksadk/configs/global_config.py +2 -1
- ksadk/configs/settings.py +151 -124
- ksadk/conversations/__init__.py +7 -5
- ksadk/conversations/attachment_storage.py +16 -11
- ksadk/conversations/attachments.py +44 -36
- ksadk/conversations/compaction_pipeline.py +12 -12
- ksadk/conversations/compaction_prompt.py +9 -5
- ksadk/conversations/context.py +221 -6
- ksadk/conversations/message_projection.py +656 -0
- ksadk/conversations/model_context.py +22 -6
- ksadk/conversations/model_options.py +0 -1
- ksadk/conversations/normalize.py +20 -16
- ksadk/conversations/reasoning_markup.py +1 -1
- ksadk/conversations/runtime.py +339 -5187
- ksadk/conversations/runtime_compaction.py +333 -0
- ksadk/conversations/runtime_constants.py +27 -0
- ksadk/conversations/runtime_governance.py +185 -0
- ksadk/conversations/runtime_input.py +652 -0
- ksadk/conversations/runtime_invocation.py +395 -0
- ksadk/conversations/runtime_metadata.py +475 -0
- ksadk/conversations/runtime_observability.py +355 -0
- ksadk/conversations/runtime_payloads.py +272 -0
- ksadk/conversations/runtime_persistence.py +440 -0
- ksadk/conversations/runtime_preparation.py +418 -0
- ksadk/conversations/runtime_resume.py +816 -0
- ksadk/conversations/runtime_stream_events.py +902 -0
- ksadk/conversations/runtime_streaming.py +547 -0
- ksadk/conversations/semantic_summary.py +17 -5
- ksadk/conversations/session_title.py +6 -3
- ksadk/deployment/__init__.py +15 -16
- ksadk/deployment/agent_access.py +12 -23
- ksadk/deployment/base.py +52 -39
- ksadk/deployment/manager.py +63 -65
- ksadk/deployment/providers/serverless.py +326 -209
- ksadk/deployment/registry.py +38 -32
- ksadk/deployment/state.py +11 -11
- ksadk/deployment/ui_config.py +1 -1
- ksadk/detection/__init__.py +1 -1
- ksadk/detection/detector.py +146 -83
- ksadk/detection/mcp_detector.py +9 -6
- ksadk/events/__init__.py +25 -0
- ksadk/events/parser.py +191 -0
- ksadk/events/replay.py +37 -0
- ksadk/events/runtime_event.py +264 -0
- ksadk/events/store.py +317 -0
- ksadk/harness/__init__.py +34 -0
- ksadk/harness/app.py +278 -0
- ksadk/harness/config.py +181 -0
- ksadk/harness/reasoner.py +105 -0
- ksadk/harness/runner.py +211 -0
- ksadk/harness/sandbox.py +141 -0
- ksadk/harness/tools.py +222 -0
- ksadk/hermes_terminal.py +23 -12
- ksadk/identity/resolver.py +15 -9
- ksadk/ids.py +32 -0
- ksadk/knowledge_base/adk_tool.py +1 -1
- ksadk/knowledge_base/client.py +23 -29
- ksadk/managed_runtime.py +188 -0
- ksadk/markdown.py +5 -2
- ksadk/mcp_runtime/__init__.py +9 -6
- ksadk/memory/__init__.py +1 -1
- ksadk/memory/adk/__init__.py +2 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
- ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
- ksadk/memory/adk/backends/sdk_ltm_backend.py +26 -33
- ksadk/memory/adk/long_term_memory.py +31 -35
- ksadk/memory/adk/resilient_session_service.py +267 -0
- ksadk/memory/adk/short_term_memory.py +33 -27
- ksadk/memory/adk_tool.py +3 -2
- ksadk/memory/backends/base.py +19 -19
- ksadk/memory/backends/memory.py +24 -25
- ksadk/memory/backends/redis.py +31 -30
- ksadk/memory/langchain_tool.py +2 -1
- ksadk/memory/manager.py +52 -47
- ksadk/memory/service.py +10 -8
- ksadk/model_policy.py +15 -7
- ksadk/model_proxy/__init__.py +51 -0
- ksadk/model_proxy/bootstrap.py +80 -0
- ksadk/model_proxy/cache.py +165 -0
- ksadk/model_proxy/config.py +45 -0
- ksadk/model_proxy/demo_webui.py +115 -0
- ksadk/model_proxy/detect.py +145 -0
- ksadk/model_proxy/gate.py +68 -0
- ksadk/model_proxy/namespace.py +173 -0
- ksadk/model_proxy/protocol_proxy.py +22 -0
- ksadk/model_proxy/server.py +300 -0
- ksadk/model_proxy/transform.py +691 -0
- ksadk/openclaw_gateway.py +29 -14
- ksadk/runners/adk_runner.py +1248 -164
- ksadk/runners/base_runner.py +46 -29
- ksadk/runners/codex_runner.py +132 -0
- ksadk/runners/deepagents_runner.py +0 -1
- ksadk/runners/factory.py +17 -5
- ksadk/runners/langchain_runner.py +29 -530
- ksadk/runners/langgraph_runner.py +490 -95
- ksadk/runners/patch_langchain.py +16 -16
- ksadk/runners/remote_runner.py +264 -38
- ksadk/runners/usage_accumulator.py +1 -0
- ksadk/runners/utils/langfuse.py +3 -3
- ksadk/runners/utils/loader.py +7 -7
- ksadk/runtime/__init__.py +41 -0
- ksadk/runtime/adapter.py +345 -0
- ksadk/runtime/framework_adapters.py +124 -0
- ksadk/runtime/preprocessing.py +118 -0
- ksadk/runtime/runner_adapter.py +873 -0
- ksadk/runtime_context.py +18 -3
- ksadk/runtime_state.py +0 -1
- ksadk/sandbox/__init__.py +4 -1
- ksadk/sandbox/backends/__init__.py +4 -1
- ksadk/sandbox/backends/e2b.py +13 -5
- ksadk/sandbox/backends/local_process.py +11 -3
- ksadk/sandbox/base.py +10 -15
- ksadk/sandbox/factory.py +14 -4
- ksadk/sandbox/registry.py +57 -6
- ksadk/server/api_models.py +15 -5
- ksadk/server/app.py +455 -3743
- ksadk/server/composition.py +21 -0
- ksadk/server/factory.py +624 -0
- ksadk/server/routes/__init__.py +1 -0
- ksadk/server/routes/checkpoint_resolution.py +116 -0
- ksadk/server/routes/common.py +713 -0
- ksadk/server/routes/control.py +327 -0
- ksadk/server/routes/dependencies.py +98 -0
- ksadk/server/routes/misc.py +448 -0
- ksadk/server/routes/models.py +443 -0
- ksadk/server/routes/openai_compat.py +207 -0
- ksadk/server/routes/projection.py +795 -0
- ksadk/server/routes/routers.py +52 -0
- ksadk/server/routes/run.py +726 -0
- ksadk/server/routes/sessions.py +649 -0
- ksadk/server/routes/streaming.py +223 -0
- ksadk/server/routes/workspace.py +381 -0
- ksadk/server/static/assets/{ArtifactsPanel-C0meuOdi.js → ArtifactsPanel-Cjnqt-8W.js} +1 -1
- ksadk/server/static/assets/CodeBlock-BqyqZfuB.js +9 -0
- ksadk/server/static/assets/MathMessageMarkdown-BpH7jiFM.js +8 -0
- ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +303 -0
- ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +1 -0
- ksadk/server/static/assets/{arc-bj2rZnP5.js → arc-CFKwSk_B.js} +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-2u1ltDIj.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-GPEHLZMM-D6fvdK20.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-AAUBKEIU-BK82L5n5.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
- ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
- ksadk/server/static/assets/{chunk-2J33WTMH-DLVdkeyQ.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
- ksadk/server/static/assets/{chunk-4BX2VUAB-BbwsYcbW.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
- ksadk/server/static/assets/{chunk-55IACEB6-DOairxzw.js → chunk-55IACEB6-BK8paA20.js} +1 -1
- ksadk/server/static/assets/{chunk-727SXJPM-hCO487gs.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
- ksadk/server/static/assets/{chunk-AQP2D5EJ-XU8vDOuG.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
- ksadk/server/static/assets/{chunk-FMBD7UC4-PrapyfMu.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-CboKhTZ3.js → chunk-ND2GUHAM-D98vPd-E.js} +1 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-_N8bJ-EU.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
- ksadk/server/static/assets/{dagre-BM42HDAG-FoDlo5J2.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
- ksadk/server/static/assets/{diagram-2AECGRRQ-BBp6ebBw.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
- ksadk/server/static/assets/{diagram-5GNKFQAL-CqOvG1PR.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
- ksadk/server/static/assets/{diagram-KO2AKTUF-O9T3t6ba.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
- ksadk/server/static/assets/{diagram-LMA3HP47-CGG8ekBO.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
- ksadk/server/static/assets/{diagram-OG6HWLK6-4BmH1EEg.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
- ksadk/server/static/assets/{dist-CiZHB6ZW.js → dist-6CVKuTAe.js} +1 -1
- ksadk/server/static/assets/{dist-BMKM66B4.js → dist-BUHRxvDG.js} +1 -1
- ksadk/server/static/assets/{dist-CZ6C3x8B.js → dist-BZ9n9AjO.js} +1 -1
- ksadk/server/static/assets/{dist-DbaH3p5h.js → dist-BibcG1V9.js} +1 -1
- ksadk/server/static/assets/{dist-BVzcWa6D.js → dist-Bs-jQcRX.js} +1 -1
- ksadk/server/static/assets/{dist-D99wf6_X.js → dist-C2xjE7cO.js} +1 -1
- ksadk/server/static/assets/{dist-DLMjL01k.js → dist-C7-3jInC.js} +1 -1
- ksadk/server/static/assets/{dist-DI-oVAXw.js → dist-C99THtIA.js} +1 -1
- ksadk/server/static/assets/{dist-BkJjNih3.js → dist-CGcp2ex8.js} +1 -1
- ksadk/server/static/assets/{dist-D5JGcxBx.js → dist-CVQRZjPu.js} +1 -1
- ksadk/server/static/assets/{dist-CWiTDN_V.js → dist-Cf23NY4L.js} +1 -1
- ksadk/server/static/assets/{dist-DLeM1OCK.js → dist-CgF3fj15.js} +1 -1
- ksadk/server/static/assets/{dist-BmuTWcm7.js → dist-Cwt7koer.js} +1 -1
- ksadk/server/static/assets/{dist-C2DKRFVK.js → dist-D82mtD2s.js} +1 -1
- ksadk/server/static/assets/{dist-B67uF2uK.js → dist-DF06ORQo.js} +1 -1
- ksadk/server/static/assets/{dist-Cwhk4lwI.js → dist-DHn6nBzr.js} +1 -1
- ksadk/server/static/assets/{dist-S4klkK5W.js → dist-Djgl2wVZ.js} +1 -1
- ksadk/server/static/assets/{dist-DCpZrjaK.js → dist-DlzCRy0S.js} +1 -1
- ksadk/server/static/assets/{dist-Cp1_jKwf.js → dist-DqNnNFrw.js} +1 -1
- ksadk/server/static/assets/{dist-6kSZvCZh.js → dist-DyvYLMW9.js} +1 -1
- ksadk/server/static/assets/{dist-DM0rEQPk.js → dist-eoW6tZXA.js} +1 -1
- ksadk/server/static/assets/{dist-Bk1l05Ni.js → dist-ivOya1LD.js} +1 -1
- ksadk/server/static/assets/{erDiagram-TEJ5UH35-oJhaci7S.js → erDiagram-TEJ5UH35-CnfRcY2M.js} +1 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +1 -0
- ksadk/server/static/assets/{flowDiagram-I6XJVG4X-DtTmUan-.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
- ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BSsb_eij.js → ganttDiagram-6RSMTGT7-DpCoB2g9.js} +1 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-BR5s6Pws.js → gitGraphDiagram-PVQCEYII-9IKVFBVT.js} +1 -1
- ksadk/server/static/assets/index-QSwCHuB8.js +177 -0
- ksadk/server/static/assets/index-jOPmTOZH.css +2 -0
- ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +1 -0
- ksadk/server/static/assets/{infoDiagram-5YYISTIA-DYPc5cvh.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-MoBdALDg.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-DgLiQT8l.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-CsFx112F.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
- ksadk/server/static/assets/{katex-DRsiwpM2.js → katex-DATssjp1.js} +1 -1
- ksadk/server/static/assets/{linear-TNRQ5Orv.js → linear-CvMBw9cA.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-D2eMVOgo.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-BtByYFhL.js → mindmap-definition-RKZ34NQL-CSAQxEAe.js} +1 -1
- ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +1 -0
- ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPnuCB8c.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-miFpCPv4.js → quadrantDiagram-W4KKPZXB-CxIR4JXI.js} +1 -1
- ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +1 -0
- ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-mjB_yYkW.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-D7hSlZZZ.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-D9Cx6hC3.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-AJRCARHV-D_zrg47o.js → stateDiagram-AJRCARHV-DGpd9yFG.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +1 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-CwSec0Wa.js → timeline-definition-PNZ67QCA-jqoYs3jQ.js} +1 -1
- ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +1 -0
- ksadk/server/static/assets/{vennDiagram-CIIHVFJN-agCVobiF.js → vennDiagram-CIIHVFJN-l3Z7-wbP.js} +1 -1
- ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-TmrlhWNt.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-DV71V-ah.js → xychartDiagram-2RQKCTM6-SkiH8GsK.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/server/terminal_sessions.py +111 -58
- ksadk/sessions/__init__.py +86 -40
- ksadk/sessions/base.py +31 -11
- ksadk/sessions/continuity.py +28 -5
- ksadk/sessions/in_memory.py +46 -2
- ksadk/sessions/local_service.py +148 -17
- ksadk/sessions/postgres_service.py +216 -35
- ksadk/sessions/resilience.py +52 -0
- ksadk/sessions/resilient.py +388 -0
- ksadk/skills/models.py +17 -5
- ksadk/skills/package_store.py +5 -2
- ksadk/skills/runtime/agent.py +20 -5
- ksadk/skills/runtime/artifacts.py +1 -5
- ksadk/skills/runtime/backends/e2b.py +10 -4
- ksadk/skills/runtime/backends/local.py +15 -3
- ksadk/skills/runtime/base.py +1 -2
- ksadk/skills/runtime/executor.py +10 -3
- ksadk/skills/runtime/factory.py +4 -2
- ksadk/skills/runtime/loader.py +1 -2
- ksadk/skills/runtime/registry.py +15 -10
- ksadk/skills/runtime/request.py +3 -1
- ksadk/skills/service_client.py +38 -17
- ksadk/skills/tool_defs.py +15 -10
- ksadk/terminal_exec_policy.py +3 -4
- ksadk/tools/gateway.py +92 -15
- ksadk/tools/result_budget.py +12 -4
- ksadk/toolsets/__init__.py +136 -36
- ksadk/toolsets/platform.py +21 -5
- ksadk/toolsets/sandbox.py +82 -16
- ksadk/toolsets/skills.py +200 -39
- ksadk/toolsets/web.py +123 -26
- ksadk/toolsets/workspace.py +264 -114
- ksadk/tracing/exporters/inmemory_exporter.py +62 -51
- ksadk/tracing/exporters/langfuse_exporter.py +91 -76
- ksadk/tracing/setup.py +132 -53
- ksadk/tracing/span_utils.py +5 -2
- ksadk/tui/__init__.py +22 -7
- ksadk/tui/loop.py +1925 -0
- ksadk/tui/stream_render.py +69 -0
- ksadk/ui_config.py +5 -3
- ksadk/version.py +1 -1
- ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
- agentengine_sdk_python-0.6.9.dist-info/RECORD +0 -487
- ksadk/server/static/assets/CodeBlock-BbI4HPtJ.js +0 -9
- ksadk/server/static/assets/MathMessageMarkdown-DMLQYoLp.js +0 -8
- ksadk/server/static/assets/MermaidBlock-DCR8WZ17.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-CKUvWhUz.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +0 -1
- ksadk/server/static/assets/channel-BSSOwFIb.js +0 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +0 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +0 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +0 -1
- ksadk/server/static/assets/index-BH7-pC0B.js +0 -165
- ksadk/server/static/assets/index-DAURbCsP.css +0 -1
- ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +0 -1
- ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +0 -1
- ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +0 -1
- ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +0 -1
- ksadk/tui/app.py +0 -400
- ksadk/tui/clipboard.py +0 -100
- ksadk/tui/widgets/__init__.py +0 -22
- ksadk/tui/widgets/assistant.py +0 -89
- ksadk/tui/widgets/base.py +0 -54
- ksadk/tui/widgets/chat_input.py +0 -246
- ksadk/tui/widgets/history.py +0 -156
- ksadk/tui/widgets/system.py +0 -87
- ksadk/tui/widgets/thinking.py +0 -110
- ksadk/tui/widgets/tool_call.py +0 -161
- ksadk/tui/widgets/user.py +0 -37
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -4,24 +4,26 @@ LangGraphRunner - LangGraph 框架运行时
|
|
|
4
4
|
直接透传 LangGraph 原生能力,最小化封装
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import base64
|
|
8
|
+
import inspect
|
|
7
9
|
import os
|
|
8
|
-
import uuid
|
|
9
10
|
import re
|
|
11
|
+
import uuid
|
|
10
12
|
from typing import Any, AsyncIterator, Dict, Mapping
|
|
11
|
-
import base64
|
|
12
|
-
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
from ksadk.runners.base_runner import BaseRunner
|
|
15
|
-
from ksadk.sessions.continuity import LangGraphSessionAdapter
|
|
16
|
-
from ksadk.runners.utils import get_langfuse_callbacks, get_langfuse_metadata, load_agent_module
|
|
17
14
|
from langgraph.types import Command
|
|
15
|
+
|
|
18
16
|
from ksadk.conversations.attachments import classify_attachment_kind, read_attachment_uri_bytes
|
|
19
17
|
from ksadk.conversations.reasoning_markup import ReasoningMarkupParser, strip_reasoning_markup
|
|
18
|
+
from ksadk.runners.base_runner import BaseRunner
|
|
19
|
+
from ksadk.runners.usage_accumulator import accumulate_usage
|
|
20
|
+
from ksadk.runners.utils import get_langfuse_callbacks, get_langfuse_metadata, load_agent_module
|
|
21
|
+
from ksadk.sessions.continuity import LangGraphSessionAdapter
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
class LangGraphRunner(BaseRunner):
|
|
23
25
|
"""LangGraph 框架运行时
|
|
24
|
-
|
|
26
|
+
|
|
25
27
|
透传原生 LangGraph 功能,支持任意 State 格式
|
|
26
28
|
"""
|
|
27
29
|
|
|
@@ -39,7 +41,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
39
41
|
self._loaded_model_name = self.normalize_requested_model(
|
|
40
42
|
os.getenv("OPENAI_MODEL_NAME") or os.getenv("MODEL_NAME")
|
|
41
43
|
)
|
|
42
|
-
|
|
44
|
+
|
|
43
45
|
if not hasattr(self._agent, "invoke"):
|
|
44
46
|
raise TypeError("加载的对象不是有效的 LangGraph CompiledGraph")
|
|
45
47
|
|
|
@@ -56,10 +58,10 @@ class LangGraphRunner(BaseRunner):
|
|
|
56
58
|
|
|
57
59
|
def describe_checkpoint_capability(self) -> dict[str, Any]:
|
|
58
60
|
agent = getattr(self, "_agent", None)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if
|
|
61
|
+
checkpointer = getattr(agent, "checkpointer", None)
|
|
62
|
+
if checkpointer is None:
|
|
63
|
+
checkpointer = getattr(agent, "_checkpointer", None)
|
|
64
|
+
if checkpointer is None:
|
|
63
65
|
return {
|
|
64
66
|
"Supported": False,
|
|
65
67
|
"Backend": "none",
|
|
@@ -69,7 +71,16 @@ class LangGraphRunner(BaseRunner):
|
|
|
69
71
|
"Reason": "LangGraph graph has no configured checkpointer",
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
checkpointer_type = type(checkpointer)
|
|
75
|
+
type_name = f"{checkpointer_type.__module__}.{checkpointer_type.__name__}".lower()
|
|
76
|
+
if "memory" in type_name or "inmemory" in type_name:
|
|
77
|
+
backend = "memory"
|
|
78
|
+
elif "sqlite" in type_name:
|
|
79
|
+
backend = "sqlite"
|
|
80
|
+
elif "postgres" in type_name:
|
|
81
|
+
backend = "postgres"
|
|
82
|
+
else:
|
|
83
|
+
backend = str(os.getenv("KSADK_CHECKPOINT_BACKEND") or "").strip().lower()
|
|
73
84
|
if backend == "local":
|
|
74
85
|
backend = "sqlite"
|
|
75
86
|
if not backend:
|
|
@@ -86,7 +97,9 @@ class LangGraphRunner(BaseRunner):
|
|
|
86
97
|
scope = "pod_local"
|
|
87
98
|
durable = True
|
|
88
99
|
shared = False
|
|
89
|
-
reason =
|
|
100
|
+
reason = (
|
|
101
|
+
"SQLite checkpoint is durable for local web debugging but is not shared across pods"
|
|
102
|
+
)
|
|
90
103
|
elif backend in {"memory", "inmemory"}:
|
|
91
104
|
backend = "memory"
|
|
92
105
|
scope = "process_local"
|
|
@@ -108,25 +121,23 @@ class LangGraphRunner(BaseRunner):
|
|
|
108
121
|
capabilities = super().get_runtime_capabilities()
|
|
109
122
|
capabilities["SessionContinuity"] = {
|
|
110
123
|
"Supported": True,
|
|
111
|
-
"Type":
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"Level": "runtime"
|
|
115
|
-
if capabilities["Checkpoint"].get("Supported")
|
|
116
|
-
else "semantic",
|
|
124
|
+
"Type": (
|
|
125
|
+
"checkpoint" if capabilities["Checkpoint"].get("Supported") else "semantic_replay"
|
|
126
|
+
),
|
|
127
|
+
"Level": "runtime" if capabilities["Checkpoint"].get("Supported") else "semantic",
|
|
117
128
|
"Reason": "",
|
|
118
129
|
}
|
|
119
130
|
return capabilities
|
|
120
131
|
|
|
121
132
|
def _get_config(self, session_id: str) -> dict:
|
|
122
133
|
"""获取运行配置"""
|
|
123
|
-
config = {"configurable": {"thread_id": session_id}}
|
|
124
|
-
|
|
134
|
+
config: dict[str, Any] = {"configurable": {"thread_id": session_id}}
|
|
135
|
+
|
|
125
136
|
langfuse_callbacks = get_langfuse_callbacks()
|
|
126
137
|
if langfuse_callbacks:
|
|
127
138
|
config["callbacks"] = langfuse_callbacks
|
|
128
139
|
config["metadata"] = get_langfuse_metadata(session_id)
|
|
129
|
-
|
|
140
|
+
|
|
130
141
|
return config
|
|
131
142
|
|
|
132
143
|
@staticmethod
|
|
@@ -153,7 +164,9 @@ class LangGraphRunner(BaseRunner):
|
|
|
153
164
|
|
|
154
165
|
thread_id = str(checkpoint_ref.get("thread_id") or session_id or "").strip()
|
|
155
166
|
if not thread_id:
|
|
156
|
-
raise ValueError(
|
|
167
|
+
raise ValueError(
|
|
168
|
+
"checkpoint_resume requires session_id or framework_ref.langgraph.thread_id"
|
|
169
|
+
)
|
|
157
170
|
|
|
158
171
|
next_config = dict(config)
|
|
159
172
|
configurable = dict(next_config.get("configurable") or {})
|
|
@@ -163,6 +176,18 @@ class LangGraphRunner(BaseRunner):
|
|
|
163
176
|
next_config["configurable"] = configurable
|
|
164
177
|
return next_config
|
|
165
178
|
|
|
179
|
+
@staticmethod
|
|
180
|
+
def _checkpoint_resume_input(
|
|
181
|
+
value: Any,
|
|
182
|
+
*,
|
|
183
|
+
payload_provided: bool,
|
|
184
|
+
interrupt_id: str,
|
|
185
|
+
) -> Command | None:
|
|
186
|
+
if not payload_provided:
|
|
187
|
+
return None
|
|
188
|
+
resume_value = {interrupt_id: value} if interrupt_id else value
|
|
189
|
+
return Command(resume=resume_value)
|
|
190
|
+
|
|
166
191
|
@staticmethod
|
|
167
192
|
def _checkpoint_ref_from_state(state: Any) -> dict[str, Any]:
|
|
168
193
|
state_config = None
|
|
@@ -179,17 +204,15 @@ class LangGraphRunner(BaseRunner):
|
|
|
179
204
|
checkpoint_id = str(configurable.get("checkpoint_id") or "").strip()
|
|
180
205
|
if not thread_id or not checkpoint_id:
|
|
181
206
|
return {}
|
|
182
|
-
next_nodes_raw =
|
|
207
|
+
next_nodes_raw = (
|
|
208
|
+
state.get("next") if isinstance(state, dict) else getattr(state, "next", None)
|
|
209
|
+
)
|
|
183
210
|
next_nodes: list[str] = []
|
|
184
211
|
if isinstance(next_nodes_raw, str):
|
|
185
212
|
if next_nodes_raw.strip():
|
|
186
213
|
next_nodes = [next_nodes_raw.strip()]
|
|
187
214
|
elif isinstance(next_nodes_raw, (list, tuple, set)):
|
|
188
|
-
next_nodes = [
|
|
189
|
-
str(item).strip()
|
|
190
|
-
for item in next_nodes_raw
|
|
191
|
-
if str(item or "").strip()
|
|
192
|
-
]
|
|
215
|
+
next_nodes = [str(item).strip() for item in next_nodes_raw if str(item or "").strip()]
|
|
193
216
|
return {
|
|
194
217
|
"langgraph": {
|
|
195
218
|
"thread_id": thread_id,
|
|
@@ -259,11 +282,24 @@ class LangGraphRunner(BaseRunner):
|
|
|
259
282
|
return self._extract_usage(values)
|
|
260
283
|
return {}
|
|
261
284
|
|
|
285
|
+
@staticmethod
|
|
286
|
+
def _latest_checkpoint_config(config: dict[str, Any]) -> dict[str, Any]:
|
|
287
|
+
"""Select the newest state in a resumed thread, not the source checkpoint."""
|
|
288
|
+
latest_config = dict(config)
|
|
289
|
+
configurable = dict(latest_config.get("configurable") or {})
|
|
290
|
+
configurable.pop("checkpoint_id", None)
|
|
291
|
+
latest_config["configurable"] = configurable
|
|
292
|
+
return latest_config
|
|
293
|
+
|
|
262
294
|
@staticmethod
|
|
263
295
|
def _ambient_context_text(payload: Dict[str, Any]) -> str:
|
|
264
296
|
sections: list[str] = []
|
|
265
297
|
kb_context = payload.get("kb_context") or {}
|
|
266
|
-
kb_text =
|
|
298
|
+
kb_text = (
|
|
299
|
+
str(kb_context.get("formatted_text") or "").strip()
|
|
300
|
+
if isinstance(kb_context, dict)
|
|
301
|
+
else ""
|
|
302
|
+
)
|
|
267
303
|
if kb_text:
|
|
268
304
|
sections.append(f"Knowledge base context:\n{kb_text}")
|
|
269
305
|
|
|
@@ -315,6 +351,14 @@ class LangGraphRunner(BaseRunner):
|
|
|
315
351
|
prepared = prepare_state(dict(normalized_payload), session_context)
|
|
316
352
|
if not isinstance(prepared, dict):
|
|
317
353
|
raise TypeError("ksadk_prepare_state(payload, session_context) must return a dict")
|
|
354
|
+
# A custom state hook owns application state, but must not accidentally
|
|
355
|
+
# erase protocol namespaces that framework middleware consumes. In
|
|
356
|
+
# particular CopilotKit reads ``ag-ui`` to decide whether to inject the
|
|
357
|
+
# official A2UI tool, and reads ``copilotkit.actions`` for frontend
|
|
358
|
+
# actions. Keep these namespaces narrow and transport-owned.
|
|
359
|
+
for namespace in ("ag-ui", "copilotkit"):
|
|
360
|
+
if namespace in normalized_payload:
|
|
361
|
+
prepared.setdefault(namespace, normalized_payload[namespace])
|
|
318
362
|
return prepared
|
|
319
363
|
|
|
320
364
|
def _to_state(self, payload: Dict[str, Any], history: list) -> Dict[str, Any]:
|
|
@@ -328,7 +372,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
328
372
|
if "input" in normalized_payload and "messages" not in normalized_payload:
|
|
329
373
|
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
|
|
330
374
|
|
|
331
|
-
messages = []
|
|
375
|
+
messages: list[Any] = []
|
|
332
376
|
if system_text:
|
|
333
377
|
messages.append(SystemMessage(content=system_text))
|
|
334
378
|
for msg in history:
|
|
@@ -432,11 +476,11 @@ class LangGraphRunner(BaseRunner):
|
|
|
432
476
|
)
|
|
433
477
|
continue
|
|
434
478
|
|
|
435
|
-
|
|
436
|
-
if not
|
|
479
|
+
attachment_uri = attachment.get("file_uri")
|
|
480
|
+
if not attachment_uri:
|
|
437
481
|
continue
|
|
438
482
|
|
|
439
|
-
raw = read_attachment_uri_bytes(
|
|
483
|
+
raw = read_attachment_uri_bytes(str(attachment_uri))
|
|
440
484
|
if not raw:
|
|
441
485
|
continue
|
|
442
486
|
|
|
@@ -479,22 +523,72 @@ class LangGraphRunner(BaseRunner):
|
|
|
479
523
|
)
|
|
480
524
|
return self._agent.invoke(payload, **kwargs)
|
|
481
525
|
|
|
526
|
+
async def _invoke_from_stream_events(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
527
|
+
chunks: list[dict[str, Any]] = []
|
|
528
|
+
accumulated_text = ""
|
|
529
|
+
final_chunk: dict[str, Any] | None = None
|
|
530
|
+
metadata: dict[str, Any] = {}
|
|
531
|
+
|
|
532
|
+
async for chunk in self.stream(input_data):
|
|
533
|
+
chunks.append(dict(chunk))
|
|
534
|
+
chunk_type = chunk.get("type")
|
|
535
|
+
if chunk_type == "text":
|
|
536
|
+
accumulated_text += str(chunk.get("delta") or "")
|
|
537
|
+
elif chunk_type == "final":
|
|
538
|
+
final_chunk = dict(chunk)
|
|
539
|
+
elif chunk_type == "checkpoint":
|
|
540
|
+
chunk_metadata = chunk.get("metadata")
|
|
541
|
+
if isinstance(chunk_metadata, Mapping):
|
|
542
|
+
metadata.update(dict(chunk_metadata))
|
|
543
|
+
elif chunk_type == "interrupt":
|
|
544
|
+
return {
|
|
545
|
+
"type": "interrupt",
|
|
546
|
+
"interrupt_info": chunk.get("interrupt_info"),
|
|
547
|
+
"session_id": chunk.get("session_id") or input_data.get("session_id"),
|
|
548
|
+
"output": (
|
|
549
|
+
chunk.get("interrupt_info", {}).get("message", "需要用户确认")
|
|
550
|
+
if isinstance(chunk.get("interrupt_info"), Mapping)
|
|
551
|
+
else "需要用户确认"
|
|
552
|
+
),
|
|
553
|
+
"raw": {"chunks": chunks},
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if final_chunk:
|
|
557
|
+
output_text = str(final_chunk.get("output") or accumulated_text)
|
|
558
|
+
final_metadata = final_chunk.get("metadata")
|
|
559
|
+
if isinstance(final_metadata, Mapping):
|
|
560
|
+
metadata = {**dict(final_metadata), **metadata}
|
|
561
|
+
result: dict[str, Any] = {"output": output_text, "raw": {"chunks": chunks}}
|
|
562
|
+
usage = final_chunk.get("usage")
|
|
563
|
+
if isinstance(usage, Mapping) and usage:
|
|
564
|
+
result["usage"] = dict(usage)
|
|
565
|
+
if metadata:
|
|
566
|
+
result["metadata"] = metadata
|
|
567
|
+
return result
|
|
568
|
+
|
|
569
|
+
return {"output": accumulated_text, "raw": {"chunks": chunks}}
|
|
570
|
+
|
|
482
571
|
async def invoke(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
483
572
|
"""调用 LangGraph 图
|
|
484
|
-
|
|
573
|
+
|
|
485
574
|
支持两种输入格式:
|
|
486
575
|
1. 简化格式: {"input": "hello"} - 自动转换为 messages
|
|
487
576
|
2. 原生格式: {"messages": [...]} 或自定义 State - 直接透传
|
|
488
577
|
"""
|
|
489
578
|
payload = dict(input_data)
|
|
579
|
+
force_graph_invoke = bool(payload.pop("_ksadk_force_graph_invoke", False))
|
|
580
|
+
if not force_graph_invoke and hasattr(self._agent, "astream_events"):
|
|
581
|
+
return await self._invoke_from_stream_events(payload)
|
|
582
|
+
|
|
490
583
|
session_id = payload.pop("session_id", None) or str(uuid.uuid4())[:8]
|
|
491
584
|
is_resume = payload.pop("resume", False)
|
|
492
585
|
is_checkpoint_resume = bool(payload.pop("checkpoint_resume", False))
|
|
586
|
+
resume_payload_provided = bool(payload.pop("resume_payload_provided", False))
|
|
587
|
+
resume_interrupt_id = str(payload.pop("resume_interrupt_id", "") or "")
|
|
588
|
+
resume_value = payload.get("input")
|
|
493
589
|
checkpoint_ref = self._extract_langgraph_checkpoint_ref(payload)
|
|
494
590
|
history = payload.pop("history", [])
|
|
495
591
|
native_context = self.build_native_context(payload.get("platform_context"))
|
|
496
|
-
normalized_payload = self._strip_platform_context_fields(payload)
|
|
497
|
-
|
|
498
592
|
config = self._get_config(session_id)
|
|
499
593
|
if is_checkpoint_resume:
|
|
500
594
|
config = self._apply_checkpoint_resume_config(
|
|
@@ -502,24 +596,29 @@ class LangGraphRunner(BaseRunner):
|
|
|
502
596
|
session_id=session_id,
|
|
503
597
|
checkpoint_ref=checkpoint_ref,
|
|
504
598
|
)
|
|
505
|
-
|
|
599
|
+
|
|
506
600
|
# 判断输入格式 / resume
|
|
507
601
|
if is_checkpoint_resume:
|
|
508
|
-
state =
|
|
509
|
-
elif self._has_prepare_state_hook():
|
|
510
|
-
state = self._prepare_state_with_hook(payload, session_id, history, is_resume=is_resume)
|
|
602
|
+
state = resume_value
|
|
511
603
|
elif is_resume:
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
604
|
+
# ``Command(resume=...)`` is delivered to the graph's suspended
|
|
605
|
+
# interrupt. A custom prepare-state hook is for new user input;
|
|
606
|
+
# applying it here can rewrite an approval decision into ordinary
|
|
607
|
+
# graph state and turn an approved HITL action into a rejection.
|
|
608
|
+
state = resume_value
|
|
609
|
+
elif self._has_prepare_state_hook():
|
|
610
|
+
state = self._prepare_state_with_hook(payload, session_id, history)
|
|
516
611
|
else:
|
|
517
612
|
state = self._to_state(payload, history)
|
|
518
613
|
|
|
519
614
|
try:
|
|
520
615
|
if is_checkpoint_resume:
|
|
521
616
|
result = await self._invoke_graph(
|
|
522
|
-
|
|
617
|
+
self._checkpoint_resume_input(
|
|
618
|
+
state,
|
|
619
|
+
payload_provided=resume_payload_provided,
|
|
620
|
+
interrupt_id=resume_interrupt_id,
|
|
621
|
+
),
|
|
523
622
|
config=config,
|
|
524
623
|
context=native_context,
|
|
525
624
|
)
|
|
@@ -547,7 +646,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
547
646
|
if metadata:
|
|
548
647
|
output["metadata"] = {**(output.get("metadata") or {}), **metadata}
|
|
549
648
|
return output
|
|
550
|
-
|
|
649
|
+
|
|
551
650
|
except Exception as e:
|
|
552
651
|
if "Interrupt" in type(e).__name__:
|
|
553
652
|
interrupt_info = self._get_interrupt_info(self._agent.get_state(config))
|
|
@@ -555,7 +654,11 @@ class LangGraphRunner(BaseRunner):
|
|
|
555
654
|
"type": "interrupt",
|
|
556
655
|
"interrupt_info": interrupt_info,
|
|
557
656
|
"session_id": session_id,
|
|
558
|
-
"output":
|
|
657
|
+
"output": (
|
|
658
|
+
interrupt_info.get("message", "需要用户确认")
|
|
659
|
+
if isinstance(interrupt_info, dict)
|
|
660
|
+
else "需要用户确认"
|
|
661
|
+
),
|
|
559
662
|
}
|
|
560
663
|
raise
|
|
561
664
|
|
|
@@ -564,16 +667,21 @@ class LangGraphRunner(BaseRunner):
|
|
|
564
667
|
if isinstance(result, dict):
|
|
565
668
|
# 自定义 output 字段是业务显式出参,优先于内部 messages state。
|
|
566
669
|
if "output" in result:
|
|
567
|
-
return result["output"]
|
|
670
|
+
return str(result["output"])
|
|
568
671
|
# LangGraph 示例常用 answer 作为最终业务回答字段。
|
|
569
672
|
if "answer" in result:
|
|
570
|
-
return result["answer"]
|
|
673
|
+
return str(result["answer"])
|
|
571
674
|
# 标准 messages 格式
|
|
572
675
|
if "messages" in result:
|
|
573
676
|
messages = result["messages"]
|
|
574
677
|
if messages:
|
|
575
678
|
last = messages[-1]
|
|
576
|
-
|
|
679
|
+
content = (
|
|
680
|
+
last.get("content", str(last))
|
|
681
|
+
if isinstance(last, dict)
|
|
682
|
+
else getattr(last, "content", str(last))
|
|
683
|
+
)
|
|
684
|
+
return str(content)
|
|
577
685
|
return str(result) if result else ""
|
|
578
686
|
|
|
579
687
|
def _get_interrupt_info(self, state) -> dict:
|
|
@@ -583,12 +691,18 @@ class LangGraphRunner(BaseRunner):
|
|
|
583
691
|
if hasattr(task, "interrupts") and task.interrupts:
|
|
584
692
|
for intr in task.interrupts:
|
|
585
693
|
if hasattr(intr, "value"):
|
|
586
|
-
|
|
694
|
+
value = intr.value
|
|
695
|
+
info = dict(value) if isinstance(value, Mapping) else {"value": value}
|
|
696
|
+
interrupt_id = str(getattr(intr, "id", "") or "")
|
|
697
|
+
if interrupt_id:
|
|
698
|
+
info.setdefault("approval_request_id", interrupt_id)
|
|
699
|
+
return info
|
|
587
700
|
return {}
|
|
588
701
|
|
|
589
702
|
async def _stream_checkpoint_resume_updates(
|
|
590
703
|
self,
|
|
591
704
|
*,
|
|
705
|
+
stream_input: Any,
|
|
592
706
|
config: dict[str, Any],
|
|
593
707
|
context: dict[str, Any] | None,
|
|
594
708
|
) -> AsyncIterator[Dict[str, Any]]:
|
|
@@ -605,11 +719,17 @@ class LangGraphRunner(BaseRunner):
|
|
|
605
719
|
|
|
606
720
|
latest_output: Any = None
|
|
607
721
|
emitted_update = False
|
|
608
|
-
|
|
722
|
+
tool_names: dict[str, str] = {}
|
|
723
|
+
async for update in self._agent.astream(stream_input, **kwargs):
|
|
609
724
|
emitted_update = True
|
|
610
725
|
latest_output = update
|
|
611
726
|
if isinstance(update, Mapping):
|
|
612
727
|
for node_name, node_output in update.items():
|
|
728
|
+
for tool_event in self._tool_events_from_graph_update(
|
|
729
|
+
node_output,
|
|
730
|
+
tool_names=tool_names,
|
|
731
|
+
):
|
|
732
|
+
yield tool_event
|
|
613
733
|
yield {
|
|
614
734
|
"type": "graph_update",
|
|
615
735
|
"node": str(node_name),
|
|
@@ -618,14 +738,15 @@ class LangGraphRunner(BaseRunner):
|
|
|
618
738
|
else:
|
|
619
739
|
yield {"type": "graph_update", "output": update}
|
|
620
740
|
|
|
621
|
-
|
|
741
|
+
latest_config = self._latest_checkpoint_config(config)
|
|
742
|
+
state_usage = await self._latest_state_usage(latest_config)
|
|
622
743
|
state_output = ""
|
|
623
744
|
try:
|
|
624
745
|
state = None
|
|
625
746
|
if callable(getattr(self._agent, "aget_state", None)):
|
|
626
|
-
state = await self._agent.aget_state(
|
|
747
|
+
state = await self._agent.aget_state(latest_config)
|
|
627
748
|
elif callable(getattr(self._agent, "get_state", None)):
|
|
628
|
-
state = self._agent.get_state(
|
|
749
|
+
state = self._agent.get_state(latest_config)
|
|
629
750
|
values = getattr(state, "values", None)
|
|
630
751
|
if values is not None:
|
|
631
752
|
state_output = self._extract_output(values)
|
|
@@ -639,21 +760,93 @@ class LangGraphRunner(BaseRunner):
|
|
|
639
760
|
**({"resume_noop": True} if not emitted_update else {}),
|
|
640
761
|
}
|
|
641
762
|
|
|
642
|
-
metadata = await self._latest_checkpoint_metadata(
|
|
763
|
+
metadata = await self._latest_checkpoint_metadata(latest_config)
|
|
643
764
|
if metadata:
|
|
644
765
|
yield {"type": "checkpoint", "metadata": metadata}
|
|
645
766
|
|
|
767
|
+
@staticmethod
|
|
768
|
+
def _tool_events_from_graph_update(
|
|
769
|
+
node_output: Any,
|
|
770
|
+
*,
|
|
771
|
+
tool_names: dict[str, str],
|
|
772
|
+
) -> list[dict[str, Any]]:
|
|
773
|
+
"""Preserve LangGraph update-mode tool messages as structured runner events."""
|
|
774
|
+
|
|
775
|
+
if isinstance(node_output, Mapping):
|
|
776
|
+
messages = node_output.get("messages")
|
|
777
|
+
else:
|
|
778
|
+
messages = getattr(node_output, "messages", None)
|
|
779
|
+
if not isinstance(messages, (list, tuple)):
|
|
780
|
+
return []
|
|
781
|
+
|
|
782
|
+
events: list[dict[str, Any]] = []
|
|
783
|
+
for message in messages:
|
|
784
|
+
if isinstance(message, Mapping):
|
|
785
|
+
tool_calls = message.get("tool_calls")
|
|
786
|
+
tool_call_id = message.get("tool_call_id")
|
|
787
|
+
message_name = message.get("name")
|
|
788
|
+
message_content = message.get("content")
|
|
789
|
+
message_status = message.get("status")
|
|
790
|
+
else:
|
|
791
|
+
tool_calls = getattr(message, "tool_calls", None)
|
|
792
|
+
tool_call_id = getattr(message, "tool_call_id", None)
|
|
793
|
+
message_name = getattr(message, "name", None)
|
|
794
|
+
message_content = getattr(message, "content", None)
|
|
795
|
+
message_status = getattr(message, "status", None)
|
|
796
|
+
|
|
797
|
+
if isinstance(tool_calls, (list, tuple)):
|
|
798
|
+
for tool_call in tool_calls:
|
|
799
|
+
if isinstance(tool_call, Mapping):
|
|
800
|
+
call_id = str(tool_call.get("id") or tool_call.get("tool_call_id") or "")
|
|
801
|
+
name = str(tool_call.get("name") or "tool")
|
|
802
|
+
args = tool_call.get("args")
|
|
803
|
+
else:
|
|
804
|
+
call_id = str(
|
|
805
|
+
getattr(tool_call, "id", None)
|
|
806
|
+
or getattr(tool_call, "tool_call_id", None)
|
|
807
|
+
or ""
|
|
808
|
+
)
|
|
809
|
+
name = str(getattr(tool_call, "name", None) or "tool")
|
|
810
|
+
args = getattr(tool_call, "args", None)
|
|
811
|
+
if call_id:
|
|
812
|
+
tool_names[call_id] = name
|
|
813
|
+
events.append(
|
|
814
|
+
{
|
|
815
|
+
"type": "tool_call",
|
|
816
|
+
"tool_call_id": call_id or name,
|
|
817
|
+
"tool_name": name,
|
|
818
|
+
"tool_args": args,
|
|
819
|
+
}
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
if tool_call_id:
|
|
823
|
+
call_id = str(tool_call_id)
|
|
824
|
+
name = str(message_name or tool_names.get(call_id) or "tool")
|
|
825
|
+
status = str(message_status or "").lower()
|
|
826
|
+
events.append(
|
|
827
|
+
{
|
|
828
|
+
"type": "tool_result",
|
|
829
|
+
"tool_call_id": call_id,
|
|
830
|
+
"tool_name": name,
|
|
831
|
+
"tool_output": message_content,
|
|
832
|
+
"error": message_content if status in {"error", "failed"} else None,
|
|
833
|
+
}
|
|
834
|
+
)
|
|
835
|
+
return events
|
|
836
|
+
|
|
646
837
|
async def stream(self, input_data: Dict[str, Any]) -> AsyncIterator[Dict[str, Any]]:
|
|
647
838
|
"""流式调用 LangGraph 图"""
|
|
648
839
|
payload = dict(input_data)
|
|
840
|
+
payload.pop("_ksadk_force_graph_invoke", None)
|
|
649
841
|
session_id = payload.pop("session_id", None) or str(uuid.uuid4())[:8]
|
|
650
842
|
history = payload.pop("history", [])
|
|
651
843
|
is_resume = payload.pop("resume", False)
|
|
652
844
|
is_checkpoint_resume = bool(payload.pop("checkpoint_resume", False))
|
|
845
|
+
resume_payload_provided = bool(payload.pop("resume_payload_provided", False))
|
|
846
|
+
resume_interrupt_id = str(payload.pop("resume_interrupt_id", "") or "")
|
|
847
|
+
resume_value = payload.get("input")
|
|
653
848
|
checkpoint_ref = self._extract_langgraph_checkpoint_ref(payload)
|
|
654
849
|
native_context = self.build_native_context(payload.get("platform_context"))
|
|
655
|
-
normalized_payload = self._strip_platform_context_fields(payload)
|
|
656
|
-
|
|
657
850
|
invoke_payload = dict(payload)
|
|
658
851
|
invoke_payload["session_id"] = session_id
|
|
659
852
|
if history:
|
|
@@ -662,7 +855,9 @@ class LangGraphRunner(BaseRunner):
|
|
|
662
855
|
invoke_payload["resume"] = True
|
|
663
856
|
if is_checkpoint_resume:
|
|
664
857
|
invoke_payload["checkpoint_resume"] = True
|
|
665
|
-
|
|
858
|
+
invoke_payload["resume_payload_provided"] = resume_payload_provided
|
|
859
|
+
invoke_payload["resume_interrupt_id"] = resume_interrupt_id
|
|
860
|
+
|
|
666
861
|
config = self._get_config(session_id)
|
|
667
862
|
if is_checkpoint_resume:
|
|
668
863
|
config = self._apply_checkpoint_resume_config(
|
|
@@ -672,14 +867,13 @@ class LangGraphRunner(BaseRunner):
|
|
|
672
867
|
)
|
|
673
868
|
|
|
674
869
|
if is_checkpoint_resume:
|
|
675
|
-
state =
|
|
676
|
-
elif self._has_prepare_state_hook():
|
|
677
|
-
state = self._prepare_state_with_hook(payload, session_id, history, is_resume=is_resume)
|
|
870
|
+
state = resume_value
|
|
678
871
|
elif is_resume:
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
872
|
+
# Keep the interrupt value intact for ``Command(resume=...)``;
|
|
873
|
+
# prepare-state hooks only shape fresh user turns.
|
|
874
|
+
state = resume_value
|
|
875
|
+
elif self._has_prepare_state_hook():
|
|
876
|
+
state = self._prepare_state_with_hook(payload, session_id, history)
|
|
683
877
|
else:
|
|
684
878
|
state = self._to_state(payload, history)
|
|
685
879
|
|
|
@@ -689,10 +883,60 @@ class LangGraphRunner(BaseRunner):
|
|
|
689
883
|
emitted_non_text_event = False
|
|
690
884
|
final_output_text = ""
|
|
691
885
|
final_output_usage: dict[str, Any] = {}
|
|
886
|
+
final_output_last_usage: dict[str, Any] = {}
|
|
887
|
+
model_run_usages: dict[str, dict[str, Any]] = {}
|
|
888
|
+
model_run_order: list[str] = []
|
|
889
|
+
stream_usage_run_keys: set[str] = set()
|
|
890
|
+
latest_stream_usage: dict[str, Any] = {}
|
|
891
|
+
|
|
892
|
+
def model_run_key(
|
|
893
|
+
event: Mapping[str, Any],
|
|
894
|
+
*,
|
|
895
|
+
fallback_key: str | None = None,
|
|
896
|
+
) -> str:
|
|
897
|
+
raw_run_id = event.get("run_id")
|
|
898
|
+
return (
|
|
899
|
+
str(raw_run_id)
|
|
900
|
+
if raw_run_id
|
|
901
|
+
else fallback_key or f"model-event-{len(model_run_order)}"
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
def record_model_usage(
|
|
905
|
+
event: Mapping[str, Any],
|
|
906
|
+
usage: dict[str, Any],
|
|
907
|
+
*,
|
|
908
|
+
fallback_key: str | None = None,
|
|
909
|
+
) -> None:
|
|
910
|
+
if not usage:
|
|
911
|
+
return
|
|
912
|
+
run_key = model_run_key(event, fallback_key=fallback_key)
|
|
913
|
+
if run_key not in model_run_usages:
|
|
914
|
+
model_run_order.append(run_key)
|
|
915
|
+
model_run_usages[run_key] = dict(usage)
|
|
916
|
+
|
|
917
|
+
def accumulated_model_usage() -> dict[str, Any]:
|
|
918
|
+
if len(model_run_order) == 1:
|
|
919
|
+
return dict(model_run_usages.get(model_run_order[0]) or {})
|
|
920
|
+
usage: dict[str, Any] = {}
|
|
921
|
+
for run_key in model_run_order:
|
|
922
|
+
usage = accumulate_usage(usage, model_run_usages.get(run_key) or {})
|
|
923
|
+
return usage
|
|
924
|
+
|
|
925
|
+
def latest_model_usage() -> dict[str, Any]:
|
|
926
|
+
for run_key in reversed(model_run_order):
|
|
927
|
+
usage = model_run_usages.get(run_key)
|
|
928
|
+
if usage:
|
|
929
|
+
return dict(usage)
|
|
930
|
+
return {}
|
|
692
931
|
|
|
693
932
|
if is_checkpoint_resume and callable(getattr(self._agent, "astream", None)):
|
|
694
933
|
try:
|
|
695
934
|
async for chunk in self._stream_checkpoint_resume_updates(
|
|
935
|
+
stream_input=self._checkpoint_resume_input(
|
|
936
|
+
state,
|
|
937
|
+
payload_provided=resume_payload_provided,
|
|
938
|
+
interrupt_id=resume_interrupt_id,
|
|
939
|
+
),
|
|
696
940
|
config=config,
|
|
697
941
|
context=native_context,
|
|
698
942
|
):
|
|
@@ -720,9 +964,23 @@ class LangGraphRunner(BaseRunner):
|
|
|
720
964
|
return
|
|
721
965
|
|
|
722
966
|
try:
|
|
723
|
-
stream_input =
|
|
967
|
+
stream_input = (
|
|
968
|
+
self._checkpoint_resume_input(
|
|
969
|
+
state,
|
|
970
|
+
payload_provided=resume_payload_provided,
|
|
971
|
+
interrupt_id=resume_interrupt_id,
|
|
972
|
+
)
|
|
973
|
+
if is_checkpoint_resume
|
|
974
|
+
else (Command(resume=state) if is_resume else state)
|
|
975
|
+
)
|
|
976
|
+
# stream_mode 含 "custom" 才会产生 on_custom_stream 事件(custom writer);
|
|
977
|
+
# 保留默认 "values" 以兼容既有 on_chain_end/graph_update 消费。
|
|
724
978
|
stream_kwargs = {"version": "v2", "config": config}
|
|
725
|
-
if
|
|
979
|
+
if self._callable_accepts_keyword(self._agent.astream_events, "stream_mode"):
|
|
980
|
+
stream_kwargs["stream_mode"] = ["values", "custom"]
|
|
981
|
+
if native_context and self._callable_accepts_keyword(
|
|
982
|
+
self._agent.astream_events, "context"
|
|
983
|
+
):
|
|
726
984
|
stream_kwargs["context"] = native_context
|
|
727
985
|
async for event in self._agent.astream_events(stream_input, **stream_kwargs):
|
|
728
986
|
event_kind = event.get("event", "")
|
|
@@ -731,12 +989,25 @@ class LangGraphRunner(BaseRunner):
|
|
|
731
989
|
chunk = event.get("data", {}).get("chunk")
|
|
732
990
|
if not chunk:
|
|
733
991
|
continue
|
|
992
|
+
chunk_usage = self._extract_usage(chunk)
|
|
993
|
+
if chunk_usage:
|
|
994
|
+
# Some LangChain providers attach cumulative usage to
|
|
995
|
+
# every stream chunk, and LangChain may then sum those
|
|
996
|
+
# cumulative snapshots into an inflated
|
|
997
|
+
# on_chat_model_end usage. For a concrete model run,
|
|
998
|
+
# keep the latest stream snapshot and ignore the later
|
|
999
|
+
# end usage for that same run_id.
|
|
1000
|
+
latest_stream_usage = dict(chunk_usage)
|
|
1001
|
+
if event.get("run_id"):
|
|
1002
|
+
run_key = model_run_key(event)
|
|
1003
|
+
stream_usage_run_keys.add(run_key)
|
|
1004
|
+
record_model_usage(event, latest_stream_usage)
|
|
734
1005
|
|
|
735
1006
|
# 推理内容
|
|
736
1007
|
reasoning = getattr(chunk, "reasoning_content", None)
|
|
737
1008
|
if not reasoning and hasattr(chunk, "additional_kwargs"):
|
|
738
1009
|
reasoning = chunk.additional_kwargs.get("reasoning_content")
|
|
739
|
-
|
|
1010
|
+
|
|
740
1011
|
if reasoning:
|
|
741
1012
|
accumulated_reasoning += reasoning
|
|
742
1013
|
yield {"delta": reasoning, "type": "thinking"}
|
|
@@ -746,9 +1017,9 @@ class LangGraphRunner(BaseRunner):
|
|
|
746
1017
|
content = self._filter_tool_tags(chunk.content)
|
|
747
1018
|
if isinstance(content, str):
|
|
748
1019
|
if accumulated_reasoning and content.startswith(accumulated_reasoning):
|
|
749
|
-
content = content[len(accumulated_reasoning):]
|
|
1020
|
+
content = content[len(accumulated_reasoning) :]
|
|
750
1021
|
elif reasoning and content.startswith(reasoning):
|
|
751
|
-
content = content[len(reasoning):]
|
|
1022
|
+
content = content[len(reasoning) :]
|
|
752
1023
|
if content:
|
|
753
1024
|
for part in inline_reasoning_parser.feed(content):
|
|
754
1025
|
if not part.text or not part.text.strip():
|
|
@@ -760,6 +1031,68 @@ class LangGraphRunner(BaseRunner):
|
|
|
760
1031
|
accumulated_text += part.text
|
|
761
1032
|
yield {"delta": part.text, "type": "text"}
|
|
762
1033
|
|
|
1034
|
+
elif event_kind == "on_chat_model_end":
|
|
1035
|
+
data = event.get("data") or {}
|
|
1036
|
+
output = data.get("output") if isinstance(data, Mapping) else None
|
|
1037
|
+
usage = self._extract_usage(output) or self._extract_usage(data)
|
|
1038
|
+
last_usage = self._extract_last_usage(output) or self._extract_last_usage(data)
|
|
1039
|
+
run_key = model_run_key(event)
|
|
1040
|
+
if run_key not in stream_usage_run_keys:
|
|
1041
|
+
record_model_usage(event, last_usage or usage)
|
|
1042
|
+
|
|
1043
|
+
elif event_kind == "on_chain_stream":
|
|
1044
|
+
# node 内 get_stream_writer() 写入的自定义数据,经 stream_mode 含
|
|
1045
|
+
# "custom" 时,astream_events 包成 on_chain_stream,chunk 为
|
|
1046
|
+
# (mode, value) tuple:("custom", value) 是 writer 透传内容,
|
|
1047
|
+
# ("values", state) 是 state 快照(忽略,终态走 on_chain_end)。
|
|
1048
|
+
# 编排方常用 custom writer 把"调远端 agent/子图"的流式增量透传出来。
|
|
1049
|
+
chunk = event.get("data", {}).get("chunk")
|
|
1050
|
+
if not (
|
|
1051
|
+
isinstance(chunk, tuple) and len(chunk) == 2 and chunk[0] == "custom"
|
|
1052
|
+
):
|
|
1053
|
+
continue
|
|
1054
|
+
data = chunk[1]
|
|
1055
|
+
if isinstance(data, str):
|
|
1056
|
+
accumulated_text += data
|
|
1057
|
+
yield {"delta": data, "type": "text"}
|
|
1058
|
+
continue
|
|
1059
|
+
if isinstance(data, Mapping):
|
|
1060
|
+
custom_type = str(data.get("type") or "text")
|
|
1061
|
+
if custom_type in ("tool_call", "tool_result"):
|
|
1062
|
+
# 结构化工具事件:透传完整 payload(tool_name/tool_args/
|
|
1063
|
+
# tool_output 等),不计入正文,供 UI 渲染工具卡片。
|
|
1064
|
+
out = {"type": custom_type}
|
|
1065
|
+
out.update({k: v for k, v in data.items() if k != "type"})
|
|
1066
|
+
yield out
|
|
1067
|
+
continue
|
|
1068
|
+
custom_delta = ""
|
|
1069
|
+
for key in ("delta", "text", "content", "output", "data"):
|
|
1070
|
+
value = data.get(key)
|
|
1071
|
+
if isinstance(value, str) and value:
|
|
1072
|
+
custom_delta = value
|
|
1073
|
+
break
|
|
1074
|
+
if not custom_delta:
|
|
1075
|
+
continue
|
|
1076
|
+
replace = bool(data.get("replace"))
|
|
1077
|
+
if custom_type == "thinking":
|
|
1078
|
+
accumulated_reasoning = (
|
|
1079
|
+
custom_delta
|
|
1080
|
+
if replace
|
|
1081
|
+
else accumulated_reasoning + custom_delta
|
|
1082
|
+
)
|
|
1083
|
+
else:
|
|
1084
|
+
accumulated_text = (
|
|
1085
|
+
custom_delta if replace else accumulated_text + custom_delta
|
|
1086
|
+
)
|
|
1087
|
+
out = {"delta": custom_delta, "type": custom_type}
|
|
1088
|
+
if replace:
|
|
1089
|
+
out["replace"] = True
|
|
1090
|
+
yield out
|
|
1091
|
+
continue
|
|
1092
|
+
if data is not None:
|
|
1093
|
+
accumulated_text += str(data)
|
|
1094
|
+
yield {"delta": str(data), "type": "text"}
|
|
1095
|
+
|
|
763
1096
|
elif event_kind == "on_tool_start":
|
|
764
1097
|
emitted_non_text_event = True
|
|
765
1098
|
yield {
|
|
@@ -768,23 +1101,35 @@ class LangGraphRunner(BaseRunner):
|
|
|
768
1101
|
"tool_args": event.get("data", {}).get("input", {}),
|
|
769
1102
|
"run_id": event.get("run_id"),
|
|
770
1103
|
}
|
|
771
|
-
|
|
1104
|
+
|
|
772
1105
|
elif event_kind == "on_tool_end":
|
|
773
1106
|
emitted_non_text_event = True
|
|
774
1107
|
tool_output = event.get("data", {}).get("output", "")
|
|
1108
|
+
# LangGraph returns a ToolMessage here for normal tools.
|
|
1109
|
+
# Preserve its content instead of serializing the repr,
|
|
1110
|
+
# otherwise structured output such as A2UI envelopes becomes
|
|
1111
|
+
# unparsable. Keep the callback run_id below: it is paired
|
|
1112
|
+
# with the preceding ``on_tool_start`` event on this stream.
|
|
1113
|
+
normalized_output = getattr(tool_output, "content", tool_output)
|
|
1114
|
+
if isinstance(tool_output, Mapping) and "content" in tool_output:
|
|
1115
|
+
normalized_output = tool_output["content"]
|
|
775
1116
|
yield {
|
|
776
1117
|
"type": "tool_result",
|
|
777
1118
|
"tool_name": event.get("name", "unknown"),
|
|
778
1119
|
"tool_args": event.get("data", {}).get("input", {}),
|
|
779
|
-
"tool_output":
|
|
1120
|
+
"tool_output": normalized_output,
|
|
780
1121
|
"run_id": event.get("run_id"),
|
|
781
1122
|
}
|
|
782
|
-
|
|
1123
|
+
|
|
783
1124
|
elif event_kind == "on_chain_end":
|
|
784
1125
|
output = event.get("data", {}).get("output", {})
|
|
785
1126
|
if isinstance(output, dict) and "__interrupt__" in output:
|
|
786
1127
|
emitted_non_text_event = True
|
|
787
|
-
yield {
|
|
1128
|
+
yield {
|
|
1129
|
+
"type": "interrupt",
|
|
1130
|
+
"interrupt_info": output["__interrupt__"],
|
|
1131
|
+
"session_id": session_id,
|
|
1132
|
+
}
|
|
788
1133
|
return
|
|
789
1134
|
extracted_output = self._extract_output(output)
|
|
790
1135
|
if extracted_output:
|
|
@@ -794,10 +1139,41 @@ class LangGraphRunner(BaseRunner):
|
|
|
794
1139
|
|
|
795
1140
|
except Exception as e:
|
|
796
1141
|
if "Interrupt" in type(e).__name__:
|
|
797
|
-
yield {
|
|
1142
|
+
yield {
|
|
1143
|
+
"type": "interrupt",
|
|
1144
|
+
"interrupt_info": self._get_interrupt_info(self._agent.get_state(config)),
|
|
1145
|
+
"session_id": session_id,
|
|
1146
|
+
}
|
|
798
1147
|
return
|
|
799
1148
|
raise
|
|
800
1149
|
|
|
1150
|
+
# goal-18(ksadk-web 人机交互):图因审批门(HITL)在流式中静默暂停时,
|
|
1151
|
+
# 这里把审批详情(action_requests)作为 approval 事件冒出,供 UI 渲染审批卡。
|
|
1152
|
+
# 此前流式路径只在 checkpoint 标 resumable,UI 拿不到"该批哪个工具/什么参数/允许哪些决定"。
|
|
1153
|
+
# 注:get_state 在部分 agent 上是 async,统一按 awaitable 处理;取不到则跳过,不破坏事件流。
|
|
1154
|
+
pending_approval = None
|
|
1155
|
+
try:
|
|
1156
|
+
_get_state = getattr(self._agent, "aget_state", None) or getattr(
|
|
1157
|
+
self._agent, "get_state", None
|
|
1158
|
+
)
|
|
1159
|
+
if _get_state is not None:
|
|
1160
|
+
_maybe_state = _get_state(config)
|
|
1161
|
+
if inspect.isawaitable(_maybe_state):
|
|
1162
|
+
_maybe_state = await _maybe_state
|
|
1163
|
+
pending_approval = self._get_interrupt_info(_maybe_state)
|
|
1164
|
+
except Exception:
|
|
1165
|
+
pending_approval = None
|
|
1166
|
+
if pending_approval:
|
|
1167
|
+
yield {
|
|
1168
|
+
"type": "approval",
|
|
1169
|
+
"interrupt_info": pending_approval,
|
|
1170
|
+
"session_id": session_id,
|
|
1171
|
+
}
|
|
1172
|
+
metadata = await self._latest_checkpoint_metadata(config)
|
|
1173
|
+
if metadata:
|
|
1174
|
+
yield {"type": "checkpoint", "metadata": metadata}
|
|
1175
|
+
return
|
|
1176
|
+
|
|
801
1177
|
for part in inline_reasoning_parser.flush():
|
|
802
1178
|
if not part.text or not part.text.strip():
|
|
803
1179
|
continue
|
|
@@ -811,32 +1187,51 @@ class LangGraphRunner(BaseRunner):
|
|
|
811
1187
|
if not accumulated_text:
|
|
812
1188
|
if final_output_text:
|
|
813
1189
|
final_chunk = {"output": final_output_text, "type": "final"}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1190
|
+
usage = accumulated_model_usage() or final_output_usage or latest_stream_usage
|
|
1191
|
+
last_usage = (
|
|
1192
|
+
latest_model_usage() or final_output_last_usage or latest_stream_usage or usage
|
|
1193
|
+
)
|
|
1194
|
+
if usage:
|
|
1195
|
+
final_chunk["usage"] = usage
|
|
1196
|
+
if last_usage:
|
|
1197
|
+
final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
|
|
818
1198
|
yield final_chunk
|
|
819
1199
|
elif not emitted_non_text_event:
|
|
820
|
-
result = await self.invoke(invoke_payload)
|
|
821
|
-
|
|
1200
|
+
result = await self.invoke({**invoke_payload, "_ksadk_force_graph_invoke": True})
|
|
1201
|
+
fallback_chunk: dict[str, Any] = {
|
|
1202
|
+
"output": result.get("output", ""),
|
|
1203
|
+
"type": "final",
|
|
1204
|
+
}
|
|
822
1205
|
usage = self._extract_usage(result)
|
|
823
1206
|
if usage:
|
|
824
|
-
|
|
1207
|
+
fallback_chunk["usage"] = usage
|
|
825
1208
|
last_usage = self._extract_last_usage(result)
|
|
826
1209
|
if last_usage:
|
|
827
|
-
|
|
828
|
-
yield
|
|
829
|
-
|
|
830
|
-
if isinstance(
|
|
831
|
-
yield {"type": "checkpoint", "metadata":
|
|
1210
|
+
fallback_chunk.setdefault("metadata", {})["last_usage"] = last_usage
|
|
1211
|
+
yield fallback_chunk
|
|
1212
|
+
checkpoint_metadata = result.get("metadata") if isinstance(result, dict) else None
|
|
1213
|
+
if isinstance(checkpoint_metadata, dict) and checkpoint_metadata.get("agentengine"):
|
|
1214
|
+
yield {"type": "checkpoint", "metadata": checkpoint_metadata}
|
|
832
1215
|
return
|
|
833
1216
|
else:
|
|
834
1217
|
final_chunk = {"output": accumulated_text, "type": "final"}
|
|
835
|
-
|
|
1218
|
+
state_usage = await self._latest_state_usage(config)
|
|
1219
|
+
usage = (
|
|
1220
|
+
accumulated_model_usage()
|
|
1221
|
+
or state_usage
|
|
1222
|
+
or final_output_usage
|
|
1223
|
+
or latest_stream_usage
|
|
1224
|
+
)
|
|
836
1225
|
if usage:
|
|
837
1226
|
final_chunk["usage"] = usage
|
|
838
|
-
|
|
839
|
-
|
|
1227
|
+
last_usage = (
|
|
1228
|
+
latest_model_usage()
|
|
1229
|
+
or state_usage
|
|
1230
|
+
or final_output_last_usage
|
|
1231
|
+
or latest_stream_usage
|
|
1232
|
+
or usage
|
|
1233
|
+
)
|
|
1234
|
+
final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
|
|
840
1235
|
yield final_chunk
|
|
841
1236
|
|
|
842
1237
|
metadata = await self._latest_checkpoint_metadata(config)
|
|
@@ -847,6 +1242,6 @@ class LangGraphRunner(BaseRunner):
|
|
|
847
1242
|
"""过滤 <tool_call> 标签"""
|
|
848
1243
|
if not isinstance(content, str):
|
|
849
1244
|
return content
|
|
850
|
-
content = re.sub(r
|
|
851
|
-
content = re.sub(r
|
|
1245
|
+
content = re.sub(r"<tool_call>.*?</tool_call>", "", content, flags=re.DOTALL)
|
|
1246
|
+
content = re.sub(r"</?(?:tool_call|arg_key|arg_value)>", "", content)
|
|
852
1247
|
return content
|