agentengine-sdk-python 0.6.2__py3-none-any.whl → 0.6.9__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/METADATA +179 -0
- agentengine_sdk_python-0.6.9.dist-info/RECORD +487 -0
- {agentengine_sdk_python-0.6.2.dist-info → agentengine_sdk_python-0.6.9.dist-info}/WHEEL +1 -1
- agentengine_sdk_python-0.6.9.dist-info/entry_points.txt +3 -0
- agentengine_sdk_python-0.6.9.dist-info/licenses/LICENSE +175 -0
- agentengine_sdk_python-0.6.9.dist-info/top_level.txt +2 -0
- ksadk/__init__.py +13 -0
- ksadk/__main__.py +4 -0
- ksadk/a2a/__init__.py +15 -0
- ksadk/a2a/card_builder.py +64 -0
- ksadk/a2a/client.py +309 -0
- ksadk/a2a/executor.py +201 -0
- ksadk/a2a/server.py +79 -0
- ksadk/agents/__init__.py +20 -0
- ksadk/agents/base.py +370 -0
- ksadk/agents/context.py +38 -0
- ksadk/agents/event.py +35 -0
- ksadk/agents/loop_agent.py +71 -0
- ksadk/agents/parallel_agent.py +109 -0
- ksadk/agents/runner_adapter.py +24 -0
- ksadk/agents/sequential_agent.py +34 -0
- ksadk/api/__init__.py +3 -0
- ksadk/api/client.py +2227 -0
- ksadk/builders/__init__.py +20 -0
- ksadk/builders/base.py +66 -0
- ksadk/builders/code_builder.py +1990 -0
- ksadk/builders/container_builder.py +786 -0
- ksadk/builders/framework_requirements.py +59 -0
- ksadk/builders/ks3_uploader.py +389 -0
- ksadk/builders/mcp_builder.py +476 -0
- ksadk/builders/requirements_utils.py +75 -0
- ksadk/cli/__init__.py +531 -0
- ksadk/cli/__main__.py +4 -0
- ksadk/cli/agent_ref.py +204 -0
- ksadk/cli/cmd_a2a.py +167 -0
- ksadk/cli/cmd_agent.py +201 -0
- ksadk/cli/cmd_build.py +270 -0
- ksadk/cli/cmd_completion.py +304 -0
- ksadk/cli/cmd_config.py +758 -0
- ksadk/cli/cmd_create.py +2277 -0
- ksadk/cli/cmd_dashboard.py +900 -0
- ksadk/cli/cmd_deploy.py +704 -0
- ksadk/cli/cmd_destroy.py +345 -0
- ksadk/cli/cmd_files.py +1227 -0
- ksadk/cli/cmd_hermes.py +1259 -0
- ksadk/cli/cmd_invoke.py +1336 -0
- ksadk/cli/cmd_launch.py +564 -0
- ksadk/cli/cmd_mcp.py +1174 -0
- ksadk/cli/cmd_model.py +302 -0
- ksadk/cli/cmd_openclaw.py +3918 -0
- ksadk/cli/cmd_run.py +232 -0
- ksadk/cli/cmd_status.py +588 -0
- ksadk/cli/cmd_version.py +522 -0
- ksadk/cli/cmd_web.py +288 -0
- ksadk/cli/deploy_utils.py +87 -0
- ksadk/cli/dry_run.py +214 -0
- ksadk/cli/env_options.py +82 -0
- ksadk/cli/error_utils.py +525 -0
- ksadk/cli/global_options.py +31 -0
- ksadk/cli/local_runtime.py +97 -0
- ksadk/cli/model_catalog.py +136 -0
- ksadk/cli/network_options.py +410 -0
- ksadk/cli/resource_common.py +621 -0
- ksadk/cli/storage.py +84 -0
- ksadk/cli/ui.py +431 -0
- ksadk/cli/workflow_common.py +552 -0
- ksadk/common/__init__.py +14 -0
- ksadk/common/aicp_env.py +128 -0
- ksadk/common/auth.py +190 -0
- ksadk/common/constants.py +84 -0
- ksadk/common/llm_utils.py +61 -0
- ksadk/configs/__init__.py +61 -0
- ksadk/configs/env_registry.py +342 -0
- ksadk/configs/global_config.py +200 -0
- ksadk/configs/settings.py +706 -0
- ksadk/conversations/__init__.py +95 -0
- ksadk/conversations/attachment_storage.py +379 -0
- ksadk/conversations/attachments.py +774 -0
- ksadk/conversations/compaction_pipeline.py +387 -0
- ksadk/conversations/compaction_prompt.py +128 -0
- ksadk/conversations/context.py +303 -0
- ksadk/conversations/model_context.py +360 -0
- ksadk/conversations/model_options.py +136 -0
- ksadk/conversations/normalize.py +446 -0
- ksadk/conversations/reasoning_markup.py +91 -0
- ksadk/conversations/run_kinds.py +61 -0
- ksadk/conversations/run_status.py +53 -0
- ksadk/conversations/runtime.py +5208 -0
- ksadk/conversations/semantic_summary.py +359 -0
- ksadk/conversations/session_title.py +254 -0
- ksadk/deployment/__init__.py +83 -0
- ksadk/deployment/agent_access.py +226 -0
- ksadk/deployment/base.py +229 -0
- ksadk/deployment/manager.py +332 -0
- ksadk/deployment/providers/__init__.py +10 -0
- ksadk/deployment/providers/serverless.py +1105 -0
- ksadk/deployment/registry.py +120 -0
- ksadk/deployment/state.py +78 -0
- ksadk/deployment/ui_config.py +21 -0
- ksadk/detection/__init__.py +7 -0
- ksadk/detection/detector.py +431 -0
- ksadk/detection/mcp_detector.py +250 -0
- ksadk/hermes_terminal.py +467 -0
- ksadk/identity/__init__.py +17 -0
- ksadk/identity/resolver.py +385 -0
- ksadk/knowledge_base/__init__.py +62 -0
- ksadk/knowledge_base/adk_tool.py +47 -0
- ksadk/knowledge_base/client.py +297 -0
- ksadk/knowledge_base/langchain_tool.py +55 -0
- ksadk/knowledge_base/service.py +66 -0
- ksadk/knowledge_base/tool.py +50 -0
- ksadk/markdown.py +169 -0
- ksadk/mcp_runtime/__init__.py +231 -0
- ksadk/memory/__init__.py +57 -0
- ksadk/memory/adk/__init__.py +51 -0
- ksadk/memory/adk/backends/__init__.py +5 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
- ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
- ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
- ksadk/memory/adk/long_term_memory.py +273 -0
- ksadk/memory/adk/short_term_memory.py +289 -0
- ksadk/memory/adk_tool.py +31 -0
- ksadk/memory/backends/__init__.py +6 -0
- ksadk/memory/backends/base.py +103 -0
- ksadk/memory/backends/memory.py +112 -0
- ksadk/memory/backends/redis.py +146 -0
- ksadk/memory/langchain_tool.py +39 -0
- ksadk/memory/ltm_backend_factory.py +24 -0
- ksadk/memory/manager.py +215 -0
- ksadk/memory/service.py +172 -0
- ksadk/memory/tool.py +96 -0
- ksadk/model_policy.py +223 -0
- ksadk/openclaw_gateway.py +378 -0
- ksadk/runners/__init__.py +8 -0
- ksadk/runners/adk_runner.py +1135 -0
- ksadk/runners/base_runner.py +305 -0
- ksadk/runners/deepagents_runner.py +13 -0
- ksadk/runners/factory.py +73 -0
- ksadk/runners/langchain_runner.py +539 -0
- ksadk/runners/langgraph_runner.py +852 -0
- ksadk/runners/patch_langchain.py +187 -0
- ksadk/runners/remote_runner.py +667 -0
- ksadk/runners/usage_accumulator.py +38 -0
- ksadk/runners/utils/__init__.py +19 -0
- ksadk/runners/utils/langfuse.py +230 -0
- ksadk/runners/utils/loader.py +62 -0
- ksadk/runtime_context.py +166 -0
- ksadk/runtime_state.py +25 -0
- ksadk/sandbox/__init__.py +28 -0
- ksadk/sandbox/backends/__init__.py +9 -0
- ksadk/sandbox/backends/e2b.py +154 -0
- ksadk/sandbox/backends/local_process.py +131 -0
- ksadk/sandbox/base.py +101 -0
- ksadk/sandbox/factory.py +53 -0
- ksadk/sandbox/registry.py +187 -0
- ksadk/server/__init__.py +7 -0
- ksadk/server/api_models.py +73 -0
- ksadk/server/app.py +3789 -0
- ksadk/server/static/assets/ArtifactsPanel-C0meuOdi.js +1 -0
- ksadk/server/static/assets/CodeBlock-BbI4HPtJ.js +9 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- ksadk/server/static/assets/MathMessageMarkdown-DMLQYoLp.js +8 -0
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
- ksadk/server/static/assets/MermaidBlock-DCR8WZ17.js +303 -0
- ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
- ksadk/server/static/assets/NativeTerminalPanel-CKUvWhUz.js +1 -0
- ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
- ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
- ksadk/server/static/assets/arc-bj2rZnP5.js +1 -0
- ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +1 -0
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-2u1ltDIj.js +36 -0
- ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
- ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
- ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-D6fvdK20.js +132 -0
- ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-BK82L5n5.js +10 -0
- ksadk/server/static/assets/channel-BSSOwFIb.js +1 -0
- ksadk/server/static/assets/chunk-2J33WTMH-DLVdkeyQ.js +1 -0
- ksadk/server/static/assets/chunk-4BX2VUAB-BbwsYcbW.js +1 -0
- ksadk/server/static/assets/chunk-55IACEB6-DOairxzw.js +1 -0
- ksadk/server/static/assets/chunk-727SXJPM-hCO487gs.js +206 -0
- ksadk/server/static/assets/chunk-AQP2D5EJ-XU8vDOuG.js +231 -0
- ksadk/server/static/assets/chunk-FMBD7UC4-PrapyfMu.js +15 -0
- ksadk/server/static/assets/chunk-ND2GUHAM-CboKhTZ3.js +1 -0
- ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +1 -0
- ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
- ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
- ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
- ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
- ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
- ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
- ksadk/server/static/assets/cose-bilkent-S5V4N54A-_N8bJ-EU.js +1 -0
- ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
- ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
- ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
- ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
- ksadk/server/static/assets/dagre-BM42HDAG-FoDlo5J2.js +4 -0
- ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
- ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
- ksadk/server/static/assets/diagram-2AECGRRQ-BBp6ebBw.js +43 -0
- ksadk/server/static/assets/diagram-5GNKFQAL-CqOvG1PR.js +10 -0
- ksadk/server/static/assets/diagram-KO2AKTUF-O9T3t6ba.js +3 -0
- ksadk/server/static/assets/diagram-LMA3HP47-CGG8ekBO.js +24 -0
- ksadk/server/static/assets/diagram-OG6HWLK6-4BmH1EEg.js +24 -0
- ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
- ksadk/server/static/assets/dist-6kSZvCZh.js +1 -0
- ksadk/server/static/assets/dist-B67uF2uK.js +1 -0
- ksadk/server/static/assets/dist-BMKM66B4.js +1 -0
- ksadk/server/static/assets/dist-BVzcWa6D.js +1 -0
- ksadk/server/static/assets/dist-Bk1l05Ni.js +1 -0
- ksadk/server/static/assets/dist-BkJjNih3.js +1 -0
- ksadk/server/static/assets/dist-BmuTWcm7.js +9 -0
- ksadk/server/static/assets/dist-C2DKRFVK.js +1 -0
- ksadk/server/static/assets/dist-CWiTDN_V.js +6 -0
- ksadk/server/static/assets/dist-CZ6C3x8B.js +23 -0
- ksadk/server/static/assets/dist-CiZHB6ZW.js +1 -0
- ksadk/server/static/assets/dist-Cp1_jKwf.js +1 -0
- ksadk/server/static/assets/dist-Cwhk4lwI.js +2 -0
- ksadk/server/static/assets/dist-D5JGcxBx.js +11 -0
- ksadk/server/static/assets/dist-D99wf6_X.js +1 -0
- ksadk/server/static/assets/dist-DCpZrjaK.js +1 -0
- ksadk/server/static/assets/dist-DI-oVAXw.js +1 -0
- ksadk/server/static/assets/dist-DLMjL01k.js +1 -0
- ksadk/server/static/assets/dist-DLeM1OCK.js +1 -0
- ksadk/server/static/assets/dist-DM0rEQPk.js +1 -0
- ksadk/server/static/assets/dist-DbaH3p5h.js +1 -0
- ksadk/server/static/assets/dist-S4klkK5W.js +1 -0
- ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
- ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
- ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
- ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
- ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
- ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
- ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
- ksadk/server/static/assets/erDiagram-TEJ5UH35-oJhaci7S.js +85 -0
- ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
- ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +1 -0
- ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
- ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-DtTmUan-.js +162 -0
- ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
- ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BSsb_eij.js +292 -0
- ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
- ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-BR5s6Pws.js +106 -0
- ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
- ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
- ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
- ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
- ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
- ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
- ksadk/server/static/assets/index-BH7-pC0B.js +165 -0
- ksadk/server/static/assets/index-DAURbCsP.css +1 -0
- ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +1 -0
- ksadk/server/static/assets/infoDiagram-5YYISTIA-DYPc5cvh.js +2 -0
- ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
- ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-MoBdALDg.js +70 -0
- ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
- ksadk/server/static/assets/journeyDiagram-JHISSGLW-DgLiQT8l.js +139 -0
- ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
- ksadk/server/static/assets/kanban-definition-UN3LZRKU-CsFx112F.js +89 -0
- ksadk/server/static/assets/katex-DRsiwpM2.js +265 -0
- ksadk/server/static/assets/linear-TNRQ5Orv.js +1 -0
- ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
- ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
- ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
- ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
- ksadk/server/static/assets/mermaid-parser.core-D2eMVOgo.js +161 -0
- ksadk/server/static/assets/mindmap-definition-RKZ34NQL-BtByYFhL.js +96 -0
- ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
- ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
- ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
- ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
- ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
- ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
- ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
- ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
- ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
- ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
- ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
- ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +1 -0
- ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
- ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +1 -0
- ksadk/server/static/assets/pieDiagram-4H26LBE5-DPnuCB8c.js +30 -0
- ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
- ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
- ksadk/server/static/assets/properties-C357ku5U.js +1 -0
- ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
- ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
- ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
- ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
- ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
- ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-miFpCPv4.js +7 -0
- ksadk/server/static/assets/r-DADKenSm.js +1 -0
- ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +1 -0
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-mjB_yYkW.js +84 -0
- ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
- ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
- ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-D7hSlZZZ.js +40 -0
- ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
- ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
- ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-D9Cx6hC3.js +162 -0
- ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
- ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
- ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
- ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
- ksadk/server/static/assets/solr-BBopId9w.js +1 -0
- ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
- ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
- ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
- ksadk/server/static/assets/stateDiagram-AJRCARHV-D_zrg47o.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +1 -0
- ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
- ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
- ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
- ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
- ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
- ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
- ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
- ksadk/server/static/assets/timeline-definition-PNZ67QCA-CwSec0Wa.js +120 -0
- ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
- ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +1 -0
- ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
- ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
- ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
- ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
- ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
- ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
- ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-agCVobiF.js +34 -0
- ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
- ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
- ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-TmrlhWNt.js +78 -0
- ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
- ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
- ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-DV71V-ah.js +7 -0
- ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
- ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
- ksadk/server/static/favicon.svg +1 -0
- ksadk/server/static/icons.svg +24 -0
- ksadk/server/static/index.html +14 -0
- ksadk/server/terminal_sessions.py +551 -0
- ksadk/sessions/__init__.py +283 -0
- ksadk/sessions/base.py +354 -0
- ksadk/sessions/continuity.py +313 -0
- ksadk/sessions/errors.py +7 -0
- ksadk/sessions/in_memory.py +273 -0
- ksadk/sessions/local_service.py +949 -0
- ksadk/sessions/postgres_service.py +838 -0
- ksadk/sessions/sqlite_service.py +13 -0
- ksadk/skills/__init__.py +9 -0
- ksadk/skills/loader.py +46 -0
- ksadk/skills/models.py +120 -0
- ksadk/skills/package_store.py +104 -0
- ksadk/skills/runtime/__init__.py +17 -0
- ksadk/skills/runtime/agent.py +183 -0
- ksadk/skills/runtime/artifacts.py +39 -0
- ksadk/skills/runtime/backends/__init__.py +1 -0
- ksadk/skills/runtime/backends/disabled.py +8 -0
- ksadk/skills/runtime/backends/e2b.py +168 -0
- ksadk/skills/runtime/backends/local.py +95 -0
- ksadk/skills/runtime/base.py +103 -0
- ksadk/skills/runtime/executor.py +219 -0
- ksadk/skills/runtime/factory.py +37 -0
- ksadk/skills/runtime/loader.py +131 -0
- ksadk/skills/runtime/registry.py +150 -0
- ksadk/skills/runtime/request.py +67 -0
- ksadk/skills/service_client.py +239 -0
- ksadk/skills/service_env.py +58 -0
- ksadk/skills/tool_defs.py +221 -0
- ksadk/terminal_client.py +69 -0
- ksadk/terminal_exec_policy.py +229 -0
- ksadk/tools/__init__.py +17 -0
- ksadk/tools/gateway.py +230 -0
- ksadk/tools/result_budget.py +154 -0
- ksadk/toolsets/__init__.py +852 -0
- ksadk/toolsets/_langchain.py +11 -0
- ksadk/toolsets/platform.py +93 -0
- ksadk/toolsets/sandbox.py +310 -0
- ksadk/toolsets/skills.py +217 -0
- ksadk/toolsets/web.py +266 -0
- ksadk/toolsets/workspace.py +878 -0
- ksadk/toolsets/workspace_state.py +44 -0
- ksadk/tracing/__init__.py +37 -0
- ksadk/tracing/exporters/__init__.py +1 -0
- ksadk/tracing/exporters/inmemory_exporter.py +116 -0
- ksadk/tracing/exporters/langfuse_exporter.py +445 -0
- ksadk/tracing/setup.py +829 -0
- ksadk/tracing/span_utils.py +114 -0
- ksadk/tui/__init__.py +12 -0
- ksadk/tui/app.py +400 -0
- ksadk/tui/clipboard.py +100 -0
- ksadk/tui/widgets/__init__.py +22 -0
- ksadk/tui/widgets/assistant.py +89 -0
- ksadk/tui/widgets/base.py +54 -0
- ksadk/tui/widgets/chat_input.py +246 -0
- ksadk/tui/widgets/history.py +156 -0
- ksadk/tui/widgets/system.py +87 -0
- ksadk/tui/widgets/thinking.py +110 -0
- ksadk/tui/widgets/tool_call.py +161 -0
- ksadk/tui/widgets/user.py +37 -0
- ksadk/ui_config.py +168 -0
- ksadk/version.py +4 -0
- ksadk_runtime_common/__init__.py +20 -0
- ksadk_runtime_common/memory_backend/__init__.py +29 -0
- ksadk_runtime_common/memory_backend/manifest.py +79 -0
- ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
- ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
- ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
- ksadk_runtime_common/memory_backend/registry.py +56 -0
- ksadk_runtime_common/memory_backend/render.py +71 -0
- ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
- ksadk_runtime_common/workspace_files/__init__.py +29 -0
- ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
- ksadk_runtime_common/workspace_files/constants.py +9 -0
- ksadk_runtime_common/workspace_files/path_utils.py +71 -0
- ksadk_runtime_common/workspace_files/preview.py +96 -0
- ksadk_runtime_common/workspace_files/router.py +254 -0
- agentengine_sdk_python-0.6.2.dist-info/METADATA +0 -46
- agentengine_sdk_python-0.6.2.dist-info/RECORD +0 -4
- agentengine_sdk_python-0.6.2.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,1135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ADKRunner - Google ADK 框架运行时
|
|
3
|
+
|
|
4
|
+
参考 adk-python 原生实现,缓存 Runner 和 SessionService。
|
|
5
|
+
支持通过环境变量配置记忆体 (ShortTermMemory / LongTermMemory)。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import base64
|
|
9
|
+
import inspect
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, AsyncIterator, Dict, Mapping, Optional
|
|
15
|
+
|
|
16
|
+
from opentelemetry import trace
|
|
17
|
+
|
|
18
|
+
from ksadk.conversations.attachments import classify_attachment_kind, read_attachment_uri_bytes
|
|
19
|
+
from ksadk.conversations.model_context import supports_native_image_input
|
|
20
|
+
from ksadk.runners.base_runner import BaseRunner
|
|
21
|
+
from ksadk.runners.usage_accumulator import accumulate_usage
|
|
22
|
+
from ksadk.sessions.continuity import ADKSessionAdapter
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
tracer = trace.get_tracer(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ADKRunner(BaseRunner):
|
|
30
|
+
"""ADK 框架运行时"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, detection_result: Any, project_dir: str):
|
|
33
|
+
super().__init__(detection_result, project_dir)
|
|
34
|
+
self._runner = None
|
|
35
|
+
self._session_service = None
|
|
36
|
+
# Map external session_ids (e.g. from run_interactive or web) to ADK internal session IDs
|
|
37
|
+
self._session_map: Dict[str, str] = {}
|
|
38
|
+
# Fallback default session
|
|
39
|
+
self._default_session_id: Optional[str] = None
|
|
40
|
+
# Memory integration
|
|
41
|
+
self._short_term_memory = None
|
|
42
|
+
self._long_term_memory = None
|
|
43
|
+
# Knowledge base integration
|
|
44
|
+
self._knowledge_base = None
|
|
45
|
+
# Keep runtime toolsets alive for the lifetime of the runner.
|
|
46
|
+
self._runtime_toolsets: list[Any] = []
|
|
47
|
+
|
|
48
|
+
async def close(self) -> None:
|
|
49
|
+
"""Close runtime toolsets owned by this runner."""
|
|
50
|
+
toolsets = list(self._runtime_toolsets)
|
|
51
|
+
self._runtime_toolsets.clear()
|
|
52
|
+
for toolset in toolsets:
|
|
53
|
+
close = getattr(toolset, "aclose", None) or getattr(toolset, "close", None)
|
|
54
|
+
if not callable(close):
|
|
55
|
+
continue
|
|
56
|
+
try:
|
|
57
|
+
result = close()
|
|
58
|
+
if inspect.isawaitable(result):
|
|
59
|
+
await result
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
logger.warning("Failed to close runtime toolset %r: %s", toolset, exc)
|
|
62
|
+
|
|
63
|
+
def _apply_json_patch(self):
|
|
64
|
+
"""Monkey patch google.adk.models.lite_llm to handle invalid JSON safely"""
|
|
65
|
+
try:
|
|
66
|
+
import json
|
|
67
|
+
|
|
68
|
+
import google.adk.models.lite_llm as adk_lite_llm
|
|
69
|
+
|
|
70
|
+
# Create a proxy for the json module
|
|
71
|
+
class RobustJson:
|
|
72
|
+
def __getattr__(self, name):
|
|
73
|
+
return getattr(json, name)
|
|
74
|
+
|
|
75
|
+
def loads(self, s, **kwargs):
|
|
76
|
+
result = {}
|
|
77
|
+
try:
|
|
78
|
+
result = json.loads(s, **kwargs)
|
|
79
|
+
except json.JSONDecodeError:
|
|
80
|
+
# Try json_repair if available
|
|
81
|
+
try:
|
|
82
|
+
import json_repair
|
|
83
|
+
|
|
84
|
+
result = json_repair.loads(s)
|
|
85
|
+
except ImportError:
|
|
86
|
+
# Fallback: return empty dict to prevent crash
|
|
87
|
+
print(
|
|
88
|
+
f"\n⚠️ [KSADK] Warning: Captured invalid JSON from LLM: {s[:50]}..."
|
|
89
|
+
)
|
|
90
|
+
result = {}
|
|
91
|
+
|
|
92
|
+
# Ensure result is a dict (Google GenAI FunctionCall requires dict args)
|
|
93
|
+
if not isinstance(result, dict):
|
|
94
|
+
return {}
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
# Replace the 'json' module reference INSIDE lite_llm module
|
|
98
|
+
# This is safer than patching json.loads globally
|
|
99
|
+
adk_lite_llm.json = RobustJson()
|
|
100
|
+
|
|
101
|
+
except ImportError:
|
|
102
|
+
pass # ADK not installed
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
def _init_short_term_memory(self):
|
|
107
|
+
"""从环境变量初始化短期记忆
|
|
108
|
+
|
|
109
|
+
环境变量:
|
|
110
|
+
KSADK_ADK_SESSION_BACKEND / PATH / URL: ADK 专用 session 配置
|
|
111
|
+
KSADK_STM_BACKEND / PATH / URL: 旧平台级 STM 配置
|
|
112
|
+
KSADK_SESSION_BACKEND / DSN: 统一 session 配置 fallback
|
|
113
|
+
"""
|
|
114
|
+
configured_names = (
|
|
115
|
+
"KSADK_ADK_SESSION_BACKEND",
|
|
116
|
+
"KSADK_ADK_SESSION_PATH",
|
|
117
|
+
"KSADK_ADK_SESSION_URL",
|
|
118
|
+
"KSADK_STM_BACKEND",
|
|
119
|
+
"KSADK_STM_PATH",
|
|
120
|
+
"KSADK_STM_URL",
|
|
121
|
+
"KSADK_STM_DB_PATH",
|
|
122
|
+
"KSADK_STM_DB_URL",
|
|
123
|
+
"KSADK_SESSION_BACKEND",
|
|
124
|
+
"KSADK_SESSION_DSN",
|
|
125
|
+
)
|
|
126
|
+
if not any(str(os.environ.get(name, "")).strip() for name in configured_names):
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
from ksadk.memory.adk import ShortTermMemory
|
|
131
|
+
|
|
132
|
+
stm = ShortTermMemory.from_env()
|
|
133
|
+
logger.info(
|
|
134
|
+
"ShortTermMemory initialized: backend=%s path=%s",
|
|
135
|
+
stm.backend,
|
|
136
|
+
stm.local_database_path,
|
|
137
|
+
)
|
|
138
|
+
return stm
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.warning(f"Failed to init ShortTermMemory: {e}. Using default.")
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def get_session_adapter(self):
|
|
144
|
+
return ADKSessionAdapter()
|
|
145
|
+
|
|
146
|
+
def describe_checkpoint_capability(self) -> dict[str, Any]:
|
|
147
|
+
return {
|
|
148
|
+
"Supported": False,
|
|
149
|
+
"Backend": "none",
|
|
150
|
+
"Scope": "unknown",
|
|
151
|
+
"Durable": False,
|
|
152
|
+
"SharedAcrossPods": False,
|
|
153
|
+
"ResumeMode": "forward_only",
|
|
154
|
+
"Reason": "ADK native session can continue conversation context, but KSADK does not expose ADK framework checkpoint restore points",
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
def get_runtime_capabilities(self) -> dict[str, Any]:
|
|
158
|
+
capabilities = super().get_runtime_capabilities()
|
|
159
|
+
has_native_session = bool(getattr(self, "_short_term_memory", None)) or any(
|
|
160
|
+
str(os.getenv(name) or "").strip()
|
|
161
|
+
for name in (
|
|
162
|
+
"KSADK_ADK_SESSION_BACKEND",
|
|
163
|
+
"KSADK_ADK_SESSION_PATH",
|
|
164
|
+
"KSADK_ADK_SESSION_URL",
|
|
165
|
+
"KSADK_STM_BACKEND",
|
|
166
|
+
"KSADK_STM_PATH",
|
|
167
|
+
"KSADK_STM_URL",
|
|
168
|
+
"KSADK_STM_DB_PATH",
|
|
169
|
+
"KSADK_STM_DB_URL",
|
|
170
|
+
"KSADK_SESSION_BACKEND",
|
|
171
|
+
"KSADK_SESSION_DSN",
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
capabilities["SessionContinuity"] = {
|
|
175
|
+
"Supported": True,
|
|
176
|
+
"Type": "native_session" if has_native_session else "semantic_replay",
|
|
177
|
+
"Level": "semantic",
|
|
178
|
+
"Reason": "ADK native session can continue conversation context"
|
|
179
|
+
if has_native_session
|
|
180
|
+
else "conversation transcript can be replayed",
|
|
181
|
+
}
|
|
182
|
+
capabilities["ResumeRun"]["Reason"] = capabilities["Checkpoint"]["Reason"]
|
|
183
|
+
return capabilities
|
|
184
|
+
|
|
185
|
+
def _init_long_term_memory(self):
|
|
186
|
+
"""从环境变量初始化长期记忆
|
|
187
|
+
|
|
188
|
+
环境变量:
|
|
189
|
+
KSADK_LTM_BACKEND: local / http / sdk
|
|
190
|
+
KSADK_LTM_HTTP_URL: HTTP 记忆服务地址
|
|
191
|
+
KSADK_LTM_HTTP_TOKEN: 认证 Token
|
|
192
|
+
KSADK_LTM_ACCESS_KEY: SDK AK (fallback to KSYUN_ACCESS_KEY)
|
|
193
|
+
KSADK_LTM_SECRET_KEY: SDK SK (fallback to KSYUN_SECRET_KEY)
|
|
194
|
+
KSADK_LTM_TOP_K: 检索数量
|
|
195
|
+
"""
|
|
196
|
+
backend = os.environ.get("KSADK_LTM_BACKEND", "")
|
|
197
|
+
if not backend:
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
from ksadk.memory.adk import LongTermMemory
|
|
202
|
+
|
|
203
|
+
agent_name = self._agent.name if self._agent else "default"
|
|
204
|
+
ltm = LongTermMemory.from_env(app_name=agent_name)
|
|
205
|
+
logger.info(
|
|
206
|
+
f"LongTermMemory initialized: backend={backend}, "
|
|
207
|
+
f"app_name={agent_name}"
|
|
208
|
+
)
|
|
209
|
+
return ltm
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.warning(f"Failed to init LongTermMemory: {e}.")
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
def _init_knowledge_base(self):
|
|
215
|
+
"""从环境变量初始化知识库
|
|
216
|
+
|
|
217
|
+
环境变量:
|
|
218
|
+
KSADK_KB_DATASET_ID: 知识库 ID (必填,存在即启用)
|
|
219
|
+
KSADK_KB_ACCESS_KEY: AK (可选)
|
|
220
|
+
KSADK_KB_SECRET_KEY: SK (可选)
|
|
221
|
+
KSADK_KB_REGION: 区域 (默认 cn-beijing-6)
|
|
222
|
+
KSADK_KB_TOP_K: 返回结果数 (默认 5)
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
from ksadk.knowledge_base.client import KnowledgeBaseClient
|
|
226
|
+
|
|
227
|
+
if not KnowledgeBaseClient.is_configured():
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
kb = KnowledgeBaseClient.from_env()
|
|
231
|
+
logger.info(
|
|
232
|
+
f"KnowledgeBase initialized: dataset_id={kb.dataset_id}, "
|
|
233
|
+
f"region={kb.region}"
|
|
234
|
+
)
|
|
235
|
+
return kb
|
|
236
|
+
except ImportError:
|
|
237
|
+
logger.warning(
|
|
238
|
+
"kingsoftcloud-sdk-python not installed, "
|
|
239
|
+
"knowledge base disabled. "
|
|
240
|
+
"Install with: pip install kingsoftcloud-sdk-python"
|
|
241
|
+
)
|
|
242
|
+
return None
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.warning(f"Failed to init KnowledgeBase: {e}.")
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
def _inject_search_knowledge_tool(self):
|
|
248
|
+
"""自动注入 search_knowledge_base 工具到 Agent"""
|
|
249
|
+
try:
|
|
250
|
+
from ksadk.knowledge_base.adk_tool import search_knowledge_base
|
|
251
|
+
|
|
252
|
+
added = self._append_tools_by_name([search_knowledge_base])
|
|
253
|
+
if added:
|
|
254
|
+
logger.info(
|
|
255
|
+
"Injected 'search_knowledge_base' tool into agent "
|
|
256
|
+
f"(total tools: {len(self._agent.tools)})"
|
|
257
|
+
)
|
|
258
|
+
else:
|
|
259
|
+
logger.debug("Agent already has 'search_knowledge_base' tool")
|
|
260
|
+
except ImportError as e:
|
|
261
|
+
logger.warning(f"Failed to import knowledge base tool: {e}")
|
|
262
|
+
except Exception as e:
|
|
263
|
+
logger.warning(f"Failed to inject search_knowledge_base tool: {e}")
|
|
264
|
+
|
|
265
|
+
def _inject_load_memory_tool(self):
|
|
266
|
+
"""自动注入 load_memory 工具到 Agent"""
|
|
267
|
+
try:
|
|
268
|
+
from google.adk.tools import load_memory
|
|
269
|
+
|
|
270
|
+
added = self._append_tools_by_name([load_memory])
|
|
271
|
+
if added:
|
|
272
|
+
logger.info(
|
|
273
|
+
"Injected 'load_memory' tool into agent "
|
|
274
|
+
f"(total tools: {len(self._agent.tools)})"
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
logger.debug("Agent already has 'load_memory' tool")
|
|
278
|
+
except ImportError:
|
|
279
|
+
logger.warning(
|
|
280
|
+
"google.adk.tools.load_memory not available. "
|
|
281
|
+
"Ensure google-adk >= 1.0.0 is installed."
|
|
282
|
+
)
|
|
283
|
+
except Exception as e:
|
|
284
|
+
logger.warning(f"Failed to inject load_memory tool: {e}")
|
|
285
|
+
|
|
286
|
+
def _inject_save_memory_tool(self):
|
|
287
|
+
"""自动注入 save_memory 工具到 Agent"""
|
|
288
|
+
try:
|
|
289
|
+
from ksadk.memory.adk_tool import create_adk_tool
|
|
290
|
+
|
|
291
|
+
save_memory_tool = create_adk_tool()
|
|
292
|
+
added = self._append_tools_by_name([save_memory_tool])
|
|
293
|
+
if added:
|
|
294
|
+
logger.info(
|
|
295
|
+
"Injected 'save_memory' tool into agent "
|
|
296
|
+
f"(total tools: {len(self._agent.tools)})"
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
logger.debug("Agent already has 'save_memory' tool")
|
|
300
|
+
except ImportError as e:
|
|
301
|
+
logger.warning(f"Failed to import save_memory tool: {e}")
|
|
302
|
+
except Exception as e:
|
|
303
|
+
logger.warning(f"Failed to inject save_memory tool: {e}")
|
|
304
|
+
|
|
305
|
+
def _inject_skill_runtime_tools(self):
|
|
306
|
+
"""Inject Skill Runtime tools when skills are configured for sandbox mode."""
|
|
307
|
+
mode = self._resolve_skills_mode()
|
|
308
|
+
if mode == "local":
|
|
309
|
+
self._inject_local_skill_tools()
|
|
310
|
+
return
|
|
311
|
+
if mode != "sandbox":
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
from ksadk.skills.runtime import create_skill_runtime_backend
|
|
316
|
+
from ksadk.skills.tool_defs import (
|
|
317
|
+
build_execute_skills_tool,
|
|
318
|
+
build_skill_manifest_instruction,
|
|
319
|
+
load_remote_skill_manifests,
|
|
320
|
+
resolve_skill_space_ids,
|
|
321
|
+
resolve_user_skill_space_ids,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
skill_space_ids = resolve_skill_space_ids()
|
|
325
|
+
backend = create_skill_runtime_backend()
|
|
326
|
+
execute_skills = build_execute_skills_tool(
|
|
327
|
+
backend=backend,
|
|
328
|
+
skill_space_ids=resolve_user_skill_space_ids(),
|
|
329
|
+
session_id=getattr(self._agent, "name", None) or self.detection_result.name,
|
|
330
|
+
)
|
|
331
|
+
added = self._append_tools_by_name([execute_skills])
|
|
332
|
+
try:
|
|
333
|
+
manifest_instruction = build_skill_manifest_instruction(
|
|
334
|
+
load_remote_skill_manifests(skill_space_ids)
|
|
335
|
+
)
|
|
336
|
+
if manifest_instruction:
|
|
337
|
+
self._append_agent_instruction(manifest_instruction)
|
|
338
|
+
except Exception as exc:
|
|
339
|
+
logger.warning("Failed to inject remote Skill manifest: %s", exc)
|
|
340
|
+
if added:
|
|
341
|
+
logger.info("Injected Skill Runtime tools into agent (added: %s)", ", ".join(added))
|
|
342
|
+
else:
|
|
343
|
+
logger.debug("Skill Runtime tools already present")
|
|
344
|
+
except Exception as exc:
|
|
345
|
+
logger.warning("Failed to inject Skill Runtime tools: %s", exc)
|
|
346
|
+
|
|
347
|
+
def _append_agent_instruction(self, extra_instruction: str) -> None:
|
|
348
|
+
if not extra_instruction or not hasattr(self._agent, "instruction"):
|
|
349
|
+
return
|
|
350
|
+
current = str(getattr(self._agent, "instruction") or "")
|
|
351
|
+
if extra_instruction in current:
|
|
352
|
+
return
|
|
353
|
+
self._agent.instruction = f"{current.rstrip()}\n{extra_instruction}".strip()
|
|
354
|
+
|
|
355
|
+
def _inject_local_skill_tools(self):
|
|
356
|
+
try:
|
|
357
|
+
from ksadk.skills.loader import load_local_skill
|
|
358
|
+
from ksadk.skills.tool_defs import build_skills_tool
|
|
359
|
+
|
|
360
|
+
skills_dir = Path(
|
|
361
|
+
os.environ.get("KSADK_LOCAL_SKILLS_DIR")
|
|
362
|
+
or os.environ.get("KSADK_SKILL_CACHE_DIR")
|
|
363
|
+
or Path(self.project_dir) / "skills"
|
|
364
|
+
)
|
|
365
|
+
if not skills_dir.exists():
|
|
366
|
+
logger.info("ADKRunner: local skills directory does not exist: %s", skills_dir)
|
|
367
|
+
return
|
|
368
|
+
skills = [
|
|
369
|
+
load_local_skill(path)
|
|
370
|
+
for path in sorted(skills_dir.iterdir())
|
|
371
|
+
if path.is_dir() and (path / "SKILL.md").exists()
|
|
372
|
+
]
|
|
373
|
+
if not skills:
|
|
374
|
+
return
|
|
375
|
+
tool = build_skills_tool(skills)
|
|
376
|
+
added = self._append_tools_by_name([tool])
|
|
377
|
+
if added:
|
|
378
|
+
logger.info("Injected local Skill tools into agent (added: %s)", ", ".join(added))
|
|
379
|
+
except Exception as exc:
|
|
380
|
+
logger.warning("Failed to inject local Skill tools: %s", exc)
|
|
381
|
+
|
|
382
|
+
def _resolve_skills_mode(self) -> str:
|
|
383
|
+
mode = os.environ.get("KSADK_SKILLS_MODE", "auto").strip().lower()
|
|
384
|
+
if mode != "auto":
|
|
385
|
+
return mode
|
|
386
|
+
runtime_backend = os.environ.get("KSADK_SKILL_RUNTIME_BACKEND")
|
|
387
|
+
if runtime_backend is not None:
|
|
388
|
+
backend = runtime_backend.strip().lower()
|
|
389
|
+
return "sandbox" if backend and backend not in {"disabled", "none", "off"} else "auto"
|
|
390
|
+
|
|
391
|
+
backend = (os.environ.get("KSADK_SANDBOX_BACKEND") or "").strip().lower()
|
|
392
|
+
if backend and backend not in {"disabled", "none", "off"}:
|
|
393
|
+
return "sandbox"
|
|
394
|
+
if os.environ.get("KSADK_SANDBOX_TEMPLATE_ID") or os.environ.get("KSADK_SKILL_RUNTIME_TEMPLATE_ID"):
|
|
395
|
+
return "sandbox"
|
|
396
|
+
skills_dir = Path(
|
|
397
|
+
os.environ.get("KSADK_LOCAL_SKILLS_DIR")
|
|
398
|
+
or os.environ.get("KSADK_SKILL_CACHE_DIR")
|
|
399
|
+
or Path(self.project_dir) / "skills"
|
|
400
|
+
)
|
|
401
|
+
if self._has_local_skills(skills_dir):
|
|
402
|
+
return "local"
|
|
403
|
+
return "auto"
|
|
404
|
+
|
|
405
|
+
@staticmethod
|
|
406
|
+
def _has_local_skills(skills_dir: Path) -> bool:
|
|
407
|
+
if not skills_dir.exists():
|
|
408
|
+
return False
|
|
409
|
+
return any(path.is_dir() and (path / "SKILL.md").exists() for path in skills_dir.iterdir())
|
|
410
|
+
|
|
411
|
+
def _inject_mcp_toolsets(self):
|
|
412
|
+
"""默认注入远端 MCP toolset。"""
|
|
413
|
+
try:
|
|
414
|
+
from ksadk.mcp_runtime import (
|
|
415
|
+
MCP_TOOLSET_KEY_ATTR,
|
|
416
|
+
load_mcp_toolsets_from_env,
|
|
417
|
+
mcp_tools_enabled,
|
|
418
|
+
register_mcp_toolset_descriptors,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if not mcp_tools_enabled():
|
|
422
|
+
logger.info("ADKRunner: MCP tools disabled via KSADK_ENABLE_MCP_TOOLS=0")
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
toolsets = load_mcp_toolsets_from_env()
|
|
426
|
+
if not toolsets:
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
added = self._append_toolsets_by_key(
|
|
430
|
+
toolsets,
|
|
431
|
+
key_attr=MCP_TOOLSET_KEY_ATTR,
|
|
432
|
+
)
|
|
433
|
+
if not added:
|
|
434
|
+
logger.debug("ADKRunner: MCP toolsets already present")
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
for toolset in toolsets:
|
|
438
|
+
key = getattr(toolset, MCP_TOOLSET_KEY_ATTR, None)
|
|
439
|
+
if key in added:
|
|
440
|
+
self._runtime_toolsets.append(toolset)
|
|
441
|
+
register_mcp_toolset_descriptors(toolset)
|
|
442
|
+
logger.info("Injected MCP toolsets into agent (added: %s)", ", ".join(added))
|
|
443
|
+
except ImportError as exc:
|
|
444
|
+
logger.warning(f"Failed to import MCP runtime helpers: {exc}")
|
|
445
|
+
except Exception as exc:
|
|
446
|
+
logger.warning(f"Failed to inject MCP toolsets: {exc}")
|
|
447
|
+
|
|
448
|
+
def _inject_builtin_tools(self):
|
|
449
|
+
"""Inject ksadk built-in tools according to the runtime profile."""
|
|
450
|
+
try:
|
|
451
|
+
from ksadk.toolsets import builtin_tools_for_runtime, builtin_tools_mode
|
|
452
|
+
|
|
453
|
+
mode = builtin_tools_mode(default="off")
|
|
454
|
+
if mode == "off":
|
|
455
|
+
return
|
|
456
|
+
tools = builtin_tools_for_runtime(mode=mode)
|
|
457
|
+
if not tools:
|
|
458
|
+
return
|
|
459
|
+
added = self._append_tools_by_name(tools)
|
|
460
|
+
if added:
|
|
461
|
+
logger.info("Injected ksadk built-in tools into agent (added: %s)", ", ".join(added))
|
|
462
|
+
else:
|
|
463
|
+
logger.debug("ksadk built-in tools already present")
|
|
464
|
+
except Exception as exc:
|
|
465
|
+
logger.warning("Failed to inject ksadk built-in tools: %s", exc)
|
|
466
|
+
|
|
467
|
+
def inject_deferred_tools_for_request(self, tool_names: list[str] | tuple[str, ...]) -> list[str]:
|
|
468
|
+
"""Append direct built-in tools selected by deferred tool search."""
|
|
469
|
+
names = [str(name or "").strip() for name in tool_names or [] if str(name or "").strip()]
|
|
470
|
+
if not names:
|
|
471
|
+
return []
|
|
472
|
+
try:
|
|
473
|
+
from ksadk.toolsets import get_agentengine_tools
|
|
474
|
+
|
|
475
|
+
tools = get_agentengine_tools(include=names, profile="coding", mode="direct")
|
|
476
|
+
added = self._append_tools_by_name(tools)
|
|
477
|
+
if added:
|
|
478
|
+
logger.info("Injected deferred ksadk tools for request (added: %s)", ", ".join(added))
|
|
479
|
+
return added
|
|
480
|
+
except Exception as exc:
|
|
481
|
+
logger.warning("Failed to inject deferred ksadk tools for request: %s", exc)
|
|
482
|
+
return []
|
|
483
|
+
|
|
484
|
+
@staticmethod
|
|
485
|
+
def _tool_name(tool: Any) -> str:
|
|
486
|
+
return getattr(tool, "name", None) or getattr(tool, "__name__", "")
|
|
487
|
+
|
|
488
|
+
def _append_tools_by_name(self, tools: list[Any]) -> list[str]:
|
|
489
|
+
if not hasattr(self._agent, "tools"):
|
|
490
|
+
logger.warning("Agent has no 'tools' attribute, cannot inject runtime tools")
|
|
491
|
+
return []
|
|
492
|
+
|
|
493
|
+
if self._agent.tools is None:
|
|
494
|
+
self._agent.tools = []
|
|
495
|
+
|
|
496
|
+
existing_names = {
|
|
497
|
+
self._tool_name(tool)
|
|
498
|
+
for tool in self._agent.tools
|
|
499
|
+
if self._tool_name(tool)
|
|
500
|
+
}
|
|
501
|
+
added_names: list[str] = []
|
|
502
|
+
for tool in tools:
|
|
503
|
+
tool_name = self._tool_name(tool)
|
|
504
|
+
if tool_name and tool_name in existing_names:
|
|
505
|
+
continue
|
|
506
|
+
self._agent.tools.append(tool)
|
|
507
|
+
if tool_name:
|
|
508
|
+
existing_names.add(tool_name)
|
|
509
|
+
added_names.append(tool_name)
|
|
510
|
+
return added_names
|
|
511
|
+
|
|
512
|
+
def _append_toolsets_by_key(self, toolsets: list[Any], *, key_attr: str) -> list[str]:
|
|
513
|
+
if not hasattr(self._agent, "tools"):
|
|
514
|
+
logger.warning("Agent has no 'tools' attribute, cannot inject MCP toolsets")
|
|
515
|
+
return []
|
|
516
|
+
|
|
517
|
+
if self._agent.tools is None:
|
|
518
|
+
self._agent.tools = []
|
|
519
|
+
|
|
520
|
+
existing_keys = {
|
|
521
|
+
getattr(tool, key_attr)
|
|
522
|
+
for tool in self._agent.tools
|
|
523
|
+
if getattr(tool, key_attr, None)
|
|
524
|
+
}
|
|
525
|
+
added_keys: list[str] = []
|
|
526
|
+
for toolset in toolsets:
|
|
527
|
+
key = getattr(toolset, key_attr, None)
|
|
528
|
+
if key and key in existing_keys:
|
|
529
|
+
continue
|
|
530
|
+
self._agent.tools.append(toolset)
|
|
531
|
+
if key:
|
|
532
|
+
existing_keys.add(key)
|
|
533
|
+
added_keys.append(key)
|
|
534
|
+
return added_keys
|
|
535
|
+
|
|
536
|
+
def load_agent(self) -> None:
|
|
537
|
+
"""加载 ADK Agent"""
|
|
538
|
+
import warnings
|
|
539
|
+
|
|
540
|
+
warnings.filterwarnings("ignore", category=UserWarning, module="pydantic.main")
|
|
541
|
+
|
|
542
|
+
self._apply_json_patch()
|
|
543
|
+
|
|
544
|
+
# 添加项目目录到 Python 路径
|
|
545
|
+
project_path = Path(self.project_dir).resolve()
|
|
546
|
+
if str(project_path) not in sys.path:
|
|
547
|
+
sys.path.insert(0, str(project_path))
|
|
548
|
+
|
|
549
|
+
# 确定模块名: 从 entry_point 获取
|
|
550
|
+
# (e.g. "smart_assistant_adk/agent.py" -> "smart_assistant_adk.agent")
|
|
551
|
+
entry_point = self.detection_result.entry_point
|
|
552
|
+
if entry_point.endswith(".py"):
|
|
553
|
+
module_name = entry_point[:-3] # 移除 .py 后缀
|
|
554
|
+
else:
|
|
555
|
+
module_name = entry_point
|
|
556
|
+
|
|
557
|
+
# 转换路径为模块路径 (e.g., "subdir/agent" -> "subdir.agent")
|
|
558
|
+
module_name = module_name.replace("/", ".").replace("\\", ".")
|
|
559
|
+
|
|
560
|
+
try:
|
|
561
|
+
module = __import__(module_name, fromlist=[self.detection_result.agent_variable])
|
|
562
|
+
self._agent = getattr(module, self.detection_result.agent_variable)
|
|
563
|
+
|
|
564
|
+
# Inject safety instruction for DeepSeek/LLMs to prevent empty tool names
|
|
565
|
+
if hasattr(self._agent, "instruction"):
|
|
566
|
+
safety_prompt = "\nIMPORTANT: Do NOT output tool calls with empty names."
|
|
567
|
+
if self._agent.instruction:
|
|
568
|
+
self._agent.instruction += safety_prompt
|
|
569
|
+
else:
|
|
570
|
+
self._agent.instruction = safety_prompt
|
|
571
|
+
|
|
572
|
+
except ImportError as e:
|
|
573
|
+
raise ImportError(f"无法导入模块 {module_name}: {e}")
|
|
574
|
+
except AttributeError:
|
|
575
|
+
raise AttributeError(
|
|
576
|
+
f"模块 {module_name} 中未找到 {self.detection_result.agent_variable}"
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# 验证是否为 ADK Agent
|
|
580
|
+
if not hasattr(self._agent, "name"):
|
|
581
|
+
raise TypeError("加载的对象不是有效的 ADK Agent")
|
|
582
|
+
|
|
583
|
+
# 初始化记忆体 (从环境变量读取配置)
|
|
584
|
+
self._short_term_memory = self._init_short_term_memory()
|
|
585
|
+
self._long_term_memory = self._init_long_term_memory()
|
|
586
|
+
|
|
587
|
+
# 初始化知识库 (从环境变量读取配置)
|
|
588
|
+
self._knowledge_base = self._init_knowledge_base()
|
|
589
|
+
if self._knowledge_base:
|
|
590
|
+
self._inject_search_knowledge_tool()
|
|
591
|
+
|
|
592
|
+
# 初始化 SessionService
|
|
593
|
+
from google.adk.runners import Runner
|
|
594
|
+
from google.adk.sessions import InMemorySessionService
|
|
595
|
+
|
|
596
|
+
if self._short_term_memory:
|
|
597
|
+
self._session_service = self._short_term_memory.session_service
|
|
598
|
+
logger.info("ADKRunner: using ShortTermMemory session service")
|
|
599
|
+
else:
|
|
600
|
+
self._session_service = InMemorySessionService()
|
|
601
|
+
|
|
602
|
+
# 如果配置了长期记忆,自动注入 load_memory 工具到 agent
|
|
603
|
+
if self._long_term_memory:
|
|
604
|
+
self._inject_load_memory_tool()
|
|
605
|
+
self._inject_save_memory_tool()
|
|
606
|
+
|
|
607
|
+
self._inject_skill_runtime_tools()
|
|
608
|
+
self._inject_builtin_tools()
|
|
609
|
+
self._inject_mcp_toolsets()
|
|
610
|
+
|
|
611
|
+
# 初始化 Runner (传入 memory_service)
|
|
612
|
+
runner_kwargs = dict(
|
|
613
|
+
agent=self._agent,
|
|
614
|
+
session_service=self._session_service,
|
|
615
|
+
app_name=self._agent.name,
|
|
616
|
+
)
|
|
617
|
+
if self._long_term_memory:
|
|
618
|
+
runner_kwargs["memory_service"] = self._long_term_memory
|
|
619
|
+
logger.info("ADKRunner: LongTermMemory injected as memory_service")
|
|
620
|
+
|
|
621
|
+
self._runner = Runner(**runner_kwargs)
|
|
622
|
+
self._default_model_name = self.normalize_requested_model(
|
|
623
|
+
os.getenv("OPENAI_MODEL_NAME") or os.getenv("MODEL_NAME")
|
|
624
|
+
)
|
|
625
|
+
self._default_model_reference = self._discover_model_reference(self._agent)
|
|
626
|
+
self._active_model_name = (
|
|
627
|
+
self._default_model_reference
|
|
628
|
+
or self._default_model_name
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
def _discover_model_reference(self, agent: Any) -> Optional[str]:
|
|
632
|
+
visited: set[int] = set()
|
|
633
|
+
|
|
634
|
+
def _visit(node: Any) -> Optional[str]:
|
|
635
|
+
if node is None:
|
|
636
|
+
return None
|
|
637
|
+
node_id = id(node)
|
|
638
|
+
if node_id in visited:
|
|
639
|
+
return None
|
|
640
|
+
visited.add(node_id)
|
|
641
|
+
|
|
642
|
+
current_model = getattr(node, "model", None)
|
|
643
|
+
if hasattr(current_model, "model"):
|
|
644
|
+
candidate = str(getattr(current_model, "model", None) or "").strip()
|
|
645
|
+
if candidate:
|
|
646
|
+
return candidate
|
|
647
|
+
elif isinstance(current_model, str):
|
|
648
|
+
candidate = current_model.strip()
|
|
649
|
+
if candidate:
|
|
650
|
+
return candidate
|
|
651
|
+
|
|
652
|
+
for child in getattr(node, "sub_agents", []) or []:
|
|
653
|
+
discovered = _visit(child)
|
|
654
|
+
if discovered:
|
|
655
|
+
return discovered
|
|
656
|
+
return None
|
|
657
|
+
|
|
658
|
+
return _visit(agent)
|
|
659
|
+
|
|
660
|
+
@staticmethod
|
|
661
|
+
def _resolve_model_reference(existing_model: Any, requested_model: str) -> str:
|
|
662
|
+
existing = str(existing_model or "").strip()
|
|
663
|
+
requested = requested_model.strip()
|
|
664
|
+
if "/" in requested:
|
|
665
|
+
return requested
|
|
666
|
+
if "/" in existing:
|
|
667
|
+
provider_prefix = existing.split("/", 1)[0]
|
|
668
|
+
return f"{provider_prefix}/{requested}"
|
|
669
|
+
return requested
|
|
670
|
+
|
|
671
|
+
def _apply_model_to_agent_tree(self, agent: Any, requested_model: str) -> None:
|
|
672
|
+
visited: set[int] = set()
|
|
673
|
+
|
|
674
|
+
def _visit(node: Any) -> None:
|
|
675
|
+
if node is None:
|
|
676
|
+
return
|
|
677
|
+
node_id = id(node)
|
|
678
|
+
if node_id in visited:
|
|
679
|
+
return
|
|
680
|
+
visited.add(node_id)
|
|
681
|
+
|
|
682
|
+
current_model = getattr(node, "model", None)
|
|
683
|
+
if hasattr(current_model, "model"):
|
|
684
|
+
current_reference = getattr(current_model, "model", None)
|
|
685
|
+
next_reference = self._resolve_model_reference(current_reference, requested_model)
|
|
686
|
+
if current_reference != next_reference:
|
|
687
|
+
setattr(current_model, "model", next_reference)
|
|
688
|
+
elif isinstance(current_model, str):
|
|
689
|
+
next_reference = self._resolve_model_reference(current_model, requested_model)
|
|
690
|
+
if current_model != next_reference:
|
|
691
|
+
setattr(node, "model", next_reference)
|
|
692
|
+
|
|
693
|
+
for child in getattr(node, "sub_agents", []) or []:
|
|
694
|
+
_visit(child)
|
|
695
|
+
|
|
696
|
+
_visit(agent)
|
|
697
|
+
|
|
698
|
+
def prepare_for_request(self, model: str | None) -> None:
|
|
699
|
+
normalized = self.sync_process_model_env(model)
|
|
700
|
+
if normalized is None:
|
|
701
|
+
default_model_name = (
|
|
702
|
+
getattr(self, "_default_model_name", None)
|
|
703
|
+
or self.normalize_requested_model(
|
|
704
|
+
os.getenv("OPENAI_MODEL_NAME") or os.getenv("MODEL_NAME")
|
|
705
|
+
)
|
|
706
|
+
)
|
|
707
|
+
if default_model_name:
|
|
708
|
+
self.sync_process_model_env(default_model_name)
|
|
709
|
+
target_reference = (
|
|
710
|
+
getattr(self, "_default_model_reference", None)
|
|
711
|
+
or default_model_name
|
|
712
|
+
)
|
|
713
|
+
if target_reference and self._agent is not None:
|
|
714
|
+
current_reference = self._discover_model_reference(self._agent)
|
|
715
|
+
if current_reference != target_reference:
|
|
716
|
+
self._apply_model_to_agent_tree(self._agent, target_reference)
|
|
717
|
+
self._active_model_name = target_reference
|
|
718
|
+
return
|
|
719
|
+
target_reference = (
|
|
720
|
+
self._resolve_model_reference(
|
|
721
|
+
self._discover_model_reference(self._agent)
|
|
722
|
+
or getattr(self, "_default_model_reference", None)
|
|
723
|
+
or normalized,
|
|
724
|
+
normalized,
|
|
725
|
+
)
|
|
726
|
+
if self._agent is not None
|
|
727
|
+
else normalized
|
|
728
|
+
)
|
|
729
|
+
if target_reference == getattr(self, "_active_model_name", None):
|
|
730
|
+
return
|
|
731
|
+
if self._agent is not None:
|
|
732
|
+
self._apply_model_to_agent_tree(self._agent, normalized)
|
|
733
|
+
self._active_model_name = self._discover_model_reference(self._agent) or target_reference
|
|
734
|
+
return
|
|
735
|
+
self._active_model_name = target_reference
|
|
736
|
+
|
|
737
|
+
def _prepare_trace_metadata(self, session_id: str):
|
|
738
|
+
"""准备 Trace 元数据 (Tags, UserID, etc.)"""
|
|
739
|
+
from ksadk.tracing.span_utils import prepare_trace_metadata
|
|
740
|
+
return prepare_trace_metadata(
|
|
741
|
+
detection_result=getattr(self, "detection_result", None)
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
async def _ensure_session(self, external_session_id: str = None) -> str:
|
|
745
|
+
"""Get or create ADK session ID based on external ID
|
|
746
|
+
|
|
747
|
+
When ShortTermMemory is configured, uses its create_session method
|
|
748
|
+
which supports session retrieval (if session_id already exists).
|
|
749
|
+
"""
|
|
750
|
+
# Case 1: External ID provided
|
|
751
|
+
if external_session_id:
|
|
752
|
+
if external_session_id in self._session_map:
|
|
753
|
+
return self._session_map[external_session_id]
|
|
754
|
+
|
|
755
|
+
# Create new ADK session and map it
|
|
756
|
+
if self._short_term_memory:
|
|
757
|
+
session = await self._short_term_memory.create_session(
|
|
758
|
+
app_name=self._agent.name,
|
|
759
|
+
user_id="ksadk_user",
|
|
760
|
+
session_id=external_session_id,
|
|
761
|
+
)
|
|
762
|
+
else:
|
|
763
|
+
session = await self._session_service.create_session(
|
|
764
|
+
app_name=self._agent.name, user_id="ksadk_user"
|
|
765
|
+
)
|
|
766
|
+
self._session_map[external_session_id] = session.id
|
|
767
|
+
return session.id
|
|
768
|
+
|
|
769
|
+
# Case 2: No external ID (use default singleton)
|
|
770
|
+
if self._default_session_id is None:
|
|
771
|
+
if self._short_term_memory:
|
|
772
|
+
session = await self._short_term_memory.create_session(
|
|
773
|
+
app_name=self._agent.name,
|
|
774
|
+
user_id="ksadk_user",
|
|
775
|
+
)
|
|
776
|
+
else:
|
|
777
|
+
session = await self._session_service.create_session(
|
|
778
|
+
app_name=self._agent.name, user_id="ksadk_user"
|
|
779
|
+
)
|
|
780
|
+
self._default_session_id = session.id
|
|
781
|
+
return self._default_session_id
|
|
782
|
+
|
|
783
|
+
async def save_session_to_long_term_memory(
|
|
784
|
+
self, session_id: str, user_id: str = "ksadk_user"
|
|
785
|
+
) -> bool:
|
|
786
|
+
"""将指定 session 保存到长期记忆
|
|
787
|
+
|
|
788
|
+
Args:
|
|
789
|
+
session_id: ADK 内部 session ID
|
|
790
|
+
user_id: 用户 ID
|
|
791
|
+
|
|
792
|
+
Returns:
|
|
793
|
+
是否保存成功
|
|
794
|
+
"""
|
|
795
|
+
if not self._long_term_memory:
|
|
796
|
+
logger.warning("LongTermMemory not configured, cannot save session.")
|
|
797
|
+
return False
|
|
798
|
+
|
|
799
|
+
try:
|
|
800
|
+
session = await self._session_service.get_session(
|
|
801
|
+
app_name=self._agent.name,
|
|
802
|
+
user_id=user_id,
|
|
803
|
+
session_id=session_id,
|
|
804
|
+
)
|
|
805
|
+
if not session:
|
|
806
|
+
logger.error(f"Session {session_id} not found, cannot save.")
|
|
807
|
+
return False
|
|
808
|
+
|
|
809
|
+
await self._long_term_memory.add_session_to_memory(session)
|
|
810
|
+
logger.info(f"Session {session_id} saved to long-term memory.")
|
|
811
|
+
return True
|
|
812
|
+
except Exception as e:
|
|
813
|
+
logger.error(f"Error saving session to long-term memory: {e}")
|
|
814
|
+
return False
|
|
815
|
+
|
|
816
|
+
def _build_adk_content(
|
|
817
|
+
self,
|
|
818
|
+
text: str,
|
|
819
|
+
attachments: list[Dict[str, Any]],
|
|
820
|
+
*,
|
|
821
|
+
model_metadata: Dict[str, Any] | None = None,
|
|
822
|
+
) -> "types.Content":
|
|
823
|
+
from google.genai import types
|
|
824
|
+
parts = []
|
|
825
|
+
if text:
|
|
826
|
+
parts.append(types.Part(text=text))
|
|
827
|
+
skipped_images: list[str] = []
|
|
828
|
+
image_input_supported = supports_native_image_input(model_metadata)
|
|
829
|
+
for att in attachments:
|
|
830
|
+
mime_type = att.get("mime_type", "application/octet-stream")
|
|
831
|
+
display_name = att.get("display_name", "")
|
|
832
|
+
if classify_attachment_kind(str(mime_type), str(display_name)) == "image" and not image_input_supported:
|
|
833
|
+
skipped_images.append(str(display_name or "未命名图片"))
|
|
834
|
+
continue
|
|
835
|
+
|
|
836
|
+
data: Optional[bytes] = None
|
|
837
|
+
|
|
838
|
+
inline_data = att.get("data")
|
|
839
|
+
if att.get("transport") == "inline" and inline_data:
|
|
840
|
+
try:
|
|
841
|
+
data = base64.b64decode(str(inline_data).strip() + "===")
|
|
842
|
+
except Exception as e:
|
|
843
|
+
logger.warning(f"Failed to decode inline attachment {att.get('display_name', 'uploaded_file')}: {e}")
|
|
844
|
+
|
|
845
|
+
if data is None:
|
|
846
|
+
file_uri = att.get("file_uri")
|
|
847
|
+
if file_uri:
|
|
848
|
+
data = read_attachment_uri_bytes(file_uri)
|
|
849
|
+
if data is None:
|
|
850
|
+
logger.warning("Failed to load stored attachment %s", file_uri)
|
|
851
|
+
|
|
852
|
+
if data is None:
|
|
853
|
+
file_uri = att.get("file_uri", "")
|
|
854
|
+
if file_uri.startswith("local:"):
|
|
855
|
+
logger.warning(
|
|
856
|
+
"Ignoring direct local attachment reference %s; only resolved storage paths are allowed.",
|
|
857
|
+
file_uri,
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
if data is not None:
|
|
861
|
+
parts.append(types.Part.from_bytes(data=data, mime_type=mime_type))
|
|
862
|
+
|
|
863
|
+
if skipped_images:
|
|
864
|
+
image_list = "、".join(skipped_images)
|
|
865
|
+
parts.append(
|
|
866
|
+
types.Part(
|
|
867
|
+
text=(
|
|
868
|
+
"系统提示:当前模型不支持图片输入,"
|
|
869
|
+
f"无法直接分析图片附件({image_list})。"
|
|
870
|
+
"请切换到支持视觉的模型后重试。"
|
|
871
|
+
)
|
|
872
|
+
)
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
# If no parts were found at all (e.g. empty message), fallback to prevent crash
|
|
876
|
+
if not parts:
|
|
877
|
+
parts.append(types.Part(text="[empty message]"))
|
|
878
|
+
|
|
879
|
+
return types.Content(role="user", parts=parts)
|
|
880
|
+
|
|
881
|
+
def _build_state_delta(self, input_data: Dict[str, Any]) -> dict[str, Any]:
|
|
882
|
+
state_delta: dict[str, Any] = {}
|
|
883
|
+
for key in (
|
|
884
|
+
"input_parts",
|
|
885
|
+
"attachments",
|
|
886
|
+
"attachment_results",
|
|
887
|
+
"current_attachments",
|
|
888
|
+
"current_attachment_results",
|
|
889
|
+
"has_current_files",
|
|
890
|
+
):
|
|
891
|
+
if key in input_data:
|
|
892
|
+
state_delta[key] = input_data.get(key)
|
|
893
|
+
return state_delta
|
|
894
|
+
|
|
895
|
+
@staticmethod
|
|
896
|
+
def _normalize_usage_metadata(usage_metadata: Any) -> dict[str, Any]:
|
|
897
|
+
if usage_metadata is None:
|
|
898
|
+
return {}
|
|
899
|
+
if hasattr(usage_metadata, "model_dump"):
|
|
900
|
+
try:
|
|
901
|
+
usage_metadata = usage_metadata.model_dump(exclude_none=True)
|
|
902
|
+
except Exception:
|
|
903
|
+
usage_metadata = None
|
|
904
|
+
elif hasattr(usage_metadata, "dict"):
|
|
905
|
+
try:
|
|
906
|
+
usage_metadata = usage_metadata.dict()
|
|
907
|
+
except Exception:
|
|
908
|
+
usage_metadata = None
|
|
909
|
+
if not isinstance(usage_metadata, Mapping):
|
|
910
|
+
return {}
|
|
911
|
+
|
|
912
|
+
reasoning_tokens = usage_metadata.get("thoughts_token_count")
|
|
913
|
+
output_token_details = {}
|
|
914
|
+
if reasoning_tokens is not None:
|
|
915
|
+
try:
|
|
916
|
+
output_token_details["reasoning"] = int(reasoning_tokens)
|
|
917
|
+
except (TypeError, ValueError):
|
|
918
|
+
pass
|
|
919
|
+
|
|
920
|
+
input_token_details: dict[str, Any] = {}
|
|
921
|
+
cached_tokens = usage_metadata.get("cached_content_token_count")
|
|
922
|
+
if cached_tokens is not None:
|
|
923
|
+
try:
|
|
924
|
+
input_token_details["cached"] = int(cached_tokens)
|
|
925
|
+
except (TypeError, ValueError):
|
|
926
|
+
pass
|
|
927
|
+
tool_use_tokens = usage_metadata.get("tool_use_prompt_token_count")
|
|
928
|
+
if tool_use_tokens is not None:
|
|
929
|
+
try:
|
|
930
|
+
input_token_details["tool_use"] = int(tool_use_tokens)
|
|
931
|
+
except (TypeError, ValueError):
|
|
932
|
+
pass
|
|
933
|
+
|
|
934
|
+
if "input_tokens" in usage_metadata or "output_tokens" in usage_metadata:
|
|
935
|
+
input_tokens = int(usage_metadata.get("input_tokens") or 0)
|
|
936
|
+
output_tokens = int(usage_metadata.get("output_tokens") or 0)
|
|
937
|
+
total_tokens = int(usage_metadata.get("total_tokens") or (input_tokens + output_tokens))
|
|
938
|
+
direct_input_details = usage_metadata.get("input_token_details")
|
|
939
|
+
if isinstance(direct_input_details, Mapping):
|
|
940
|
+
input_token_details.update(dict(direct_input_details))
|
|
941
|
+
normalized = {
|
|
942
|
+
"input_tokens": input_tokens,
|
|
943
|
+
"output_tokens": output_tokens,
|
|
944
|
+
"total_tokens": total_tokens,
|
|
945
|
+
"input_token_details": input_token_details,
|
|
946
|
+
"output_token_details": output_token_details,
|
|
947
|
+
}
|
|
948
|
+
direct_output_details = usage_metadata.get("output_token_details")
|
|
949
|
+
if isinstance(direct_output_details, Mapping):
|
|
950
|
+
normalized["output_token_details"] = dict(direct_output_details)
|
|
951
|
+
return normalized
|
|
952
|
+
|
|
953
|
+
input_tokens = int(usage_metadata.get("prompt_token_count") or 0)
|
|
954
|
+
output_tokens = int(usage_metadata.get("candidates_token_count") or 0)
|
|
955
|
+
total_tokens = int(usage_metadata.get("total_token_count") or (input_tokens + output_tokens))
|
|
956
|
+
if not (input_tokens or output_tokens or total_tokens or input_token_details or output_token_details):
|
|
957
|
+
return {}
|
|
958
|
+
return {
|
|
959
|
+
"input_tokens": input_tokens,
|
|
960
|
+
"output_tokens": output_tokens,
|
|
961
|
+
"total_tokens": total_tokens,
|
|
962
|
+
"input_token_details": input_token_details,
|
|
963
|
+
"output_token_details": output_token_details,
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
@classmethod
|
|
967
|
+
def _extract_event_usage(cls, event: Any) -> dict[str, Any]:
|
|
968
|
+
return cls._normalize_usage_metadata(getattr(event, "usage_metadata", None))
|
|
969
|
+
|
|
970
|
+
async def invoke(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
971
|
+
"""调用 ADK Agent"""
|
|
972
|
+
from google.genai import types
|
|
973
|
+
|
|
974
|
+
user_input = input_data.get("input", "")
|
|
975
|
+
instructions = str(input_data.get("instructions") or "").strip()
|
|
976
|
+
if instructions:
|
|
977
|
+
user_input = f"{instructions}\n\nCurrent user input:\n{user_input or '[empty message]'}"
|
|
978
|
+
|
|
979
|
+
# 1. 准备 Metadata (提前以此获取 Agent Name)
|
|
980
|
+
_, _, _, agent_name = self._prepare_trace_metadata(None)
|
|
981
|
+
trace_name = agent_name or "adk.invoke"
|
|
982
|
+
|
|
983
|
+
with tracer.start_as_current_span(trace_name) as span:
|
|
984
|
+
# Set input.value for Langfuse top-level input display
|
|
985
|
+
span.set_attribute("input.value", user_input)
|
|
986
|
+
span.set_attribute("user.input", user_input[:200])
|
|
987
|
+
|
|
988
|
+
# Use external session ID if provided
|
|
989
|
+
req_session_id = input_data.get("session_id")
|
|
990
|
+
session_id = await self._ensure_session(req_session_id)
|
|
991
|
+
|
|
992
|
+
# 准备 Metadata 并设置 Span Attributes
|
|
993
|
+
# Langfuse Exporter 会读取这些 span attributes
|
|
994
|
+
agent_user_id, tags, _, _ = self._prepare_trace_metadata(session_id)
|
|
995
|
+
|
|
996
|
+
span.set_attribute("langfuse.session_id", session_id)
|
|
997
|
+
if agent_user_id:
|
|
998
|
+
span.set_attribute("langfuse.user_id", agent_user_id)
|
|
999
|
+
if tags:
|
|
1000
|
+
span.set_attribute("langfuse.tags", ",".join(tags))
|
|
1001
|
+
|
|
1002
|
+
new_message = self._build_adk_content(
|
|
1003
|
+
user_input,
|
|
1004
|
+
input_data.get("attachments", []),
|
|
1005
|
+
model_metadata=input_data.get("model_metadata"),
|
|
1006
|
+
)
|
|
1007
|
+
state_delta = self._build_state_delta(input_data)
|
|
1008
|
+
|
|
1009
|
+
final_response = ""
|
|
1010
|
+
|
|
1011
|
+
events_list = []
|
|
1012
|
+
usage: dict[str, Any] = {}
|
|
1013
|
+
last_usage: dict[str, Any] = {}
|
|
1014
|
+
async for event in self._runner.run_async(
|
|
1015
|
+
session_id=session_id,
|
|
1016
|
+
user_id="ksadk_user",
|
|
1017
|
+
new_message=new_message,
|
|
1018
|
+
state_delta=state_delta or None,
|
|
1019
|
+
):
|
|
1020
|
+
events_list.append(event)
|
|
1021
|
+
event_usage = self._extract_event_usage(event)
|
|
1022
|
+
if event_usage:
|
|
1023
|
+
usage = accumulate_usage(usage, event_usage)
|
|
1024
|
+
last_usage = event_usage # 保留最后一个非空(窗口占用=最后一次 input)
|
|
1025
|
+
if hasattr(event, "content") and event.content:
|
|
1026
|
+
if hasattr(event.content, "parts"):
|
|
1027
|
+
for part in event.content.parts:
|
|
1028
|
+
# 过滤掉思考内容 (thought=True),只保留最终答案
|
|
1029
|
+
is_thought = getattr(part, "thought", False)
|
|
1030
|
+
if hasattr(part, "text") and part.text and not is_thought:
|
|
1031
|
+
final_response = part.text
|
|
1032
|
+
|
|
1033
|
+
# Set output.value for Langfuse top-level output display
|
|
1034
|
+
span.set_attribute("output.value", final_response[:5000] if final_response else "")
|
|
1035
|
+
span.set_attribute("agent.output", final_response[:500] if final_response else "")
|
|
1036
|
+
result = {"output": final_response, "events": events_list}
|
|
1037
|
+
if usage:
|
|
1038
|
+
result["usage"] = usage
|
|
1039
|
+
# last_usage = 最后一次 LLM 调用快照(input_tokens = 当前上下文窗口占用)
|
|
1040
|
+
result.setdefault("metadata", {})["last_usage"] = last_usage
|
|
1041
|
+
return result
|
|
1042
|
+
|
|
1043
|
+
async def stream(self, input_data: Dict[str, Any]) -> AsyncIterator[Dict[str, Any]]:
|
|
1044
|
+
"""流式调用 ADK Agent
|
|
1045
|
+
|
|
1046
|
+
使用 StreamingMode.SSE 启用真正的流式 token 输出
|
|
1047
|
+
"""
|
|
1048
|
+
from google.adk.agents.run_config import RunConfig, StreamingMode
|
|
1049
|
+
from google.genai import types
|
|
1050
|
+
|
|
1051
|
+
user_input = input_data.get("input", "")
|
|
1052
|
+
instructions = str(input_data.get("instructions") or "").strip()
|
|
1053
|
+
if instructions:
|
|
1054
|
+
user_input = f"{instructions}\n\nCurrent user input:\n{user_input or '[empty message]'}"
|
|
1055
|
+
|
|
1056
|
+
# 1. 准备 Metadata (提前以此获取 Agent Name)
|
|
1057
|
+
_, _, _, agent_name = self._prepare_trace_metadata(None)
|
|
1058
|
+
trace_name = agent_name or "adk.stream"
|
|
1059
|
+
|
|
1060
|
+
with tracer.start_as_current_span(trace_name) as span:
|
|
1061
|
+
# Set input.value for Langfuse top-level input display
|
|
1062
|
+
span.set_attribute("input.value", user_input)
|
|
1063
|
+
span.set_attribute("user.input", user_input[:200])
|
|
1064
|
+
|
|
1065
|
+
# Use external session ID if provided
|
|
1066
|
+
req_session_id = input_data.get("session_id")
|
|
1067
|
+
session_id = await self._ensure_session(req_session_id)
|
|
1068
|
+
|
|
1069
|
+
# 准备 Metadata 并设置 Span Attributes
|
|
1070
|
+
agent_user_id, tags, _, _ = self._prepare_trace_metadata(session_id)
|
|
1071
|
+
|
|
1072
|
+
span.set_attribute("langfuse.session_id", session_id)
|
|
1073
|
+
if agent_user_id:
|
|
1074
|
+
span.set_attribute("langfuse.user_id", agent_user_id)
|
|
1075
|
+
if tags:
|
|
1076
|
+
span.set_attribute("langfuse.tags", ",".join(tags))
|
|
1077
|
+
|
|
1078
|
+
new_message = self._build_adk_content(
|
|
1079
|
+
user_input,
|
|
1080
|
+
input_data.get("attachments", []),
|
|
1081
|
+
model_metadata=input_data.get("model_metadata"),
|
|
1082
|
+
)
|
|
1083
|
+
state_delta = self._build_state_delta(input_data)
|
|
1084
|
+
|
|
1085
|
+
accumulated_text = ""
|
|
1086
|
+
usage: dict[str, Any] = {}
|
|
1087
|
+
last_usage: dict[str, Any] = {}
|
|
1088
|
+
|
|
1089
|
+
# 使用 StreamingMode.SSE 启用真正的流式输出
|
|
1090
|
+
run_config = RunConfig(streaming_mode=StreamingMode.SSE)
|
|
1091
|
+
|
|
1092
|
+
async for event in self._runner.run_async(
|
|
1093
|
+
session_id=session_id,
|
|
1094
|
+
user_id="ksadk_user",
|
|
1095
|
+
new_message=new_message,
|
|
1096
|
+
state_delta=state_delta or None,
|
|
1097
|
+
run_config=run_config,
|
|
1098
|
+
):
|
|
1099
|
+
event_usage = self._extract_event_usage(event)
|
|
1100
|
+
if event_usage:
|
|
1101
|
+
usage = accumulate_usage(usage, event_usage)
|
|
1102
|
+
last_usage = event_usage # 保留最后一个非空
|
|
1103
|
+
# Only yield text delta if event is partial to avoid duplication of final summary
|
|
1104
|
+
if hasattr(event, "content") and event.content and getattr(event, "partial", False):
|
|
1105
|
+
if hasattr(event.content, "parts"):
|
|
1106
|
+
for part in event.content.parts:
|
|
1107
|
+
if hasattr(part, "text") and part.text:
|
|
1108
|
+
is_thought = getattr(part, "thought", False)
|
|
1109
|
+
accumulated_text += part.text
|
|
1110
|
+
# 标记思考内容,前端可以选择是否展示
|
|
1111
|
+
yield {
|
|
1112
|
+
"delta": part.text,
|
|
1113
|
+
"type": "thinking" if is_thought else "text",
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
# 处理工具调用事件
|
|
1117
|
+
if hasattr(event, "actions") and event.actions:
|
|
1118
|
+
tool_calls = getattr(event.actions, "tool_calls", None)
|
|
1119
|
+
if tool_calls:
|
|
1120
|
+
for tool_call in tool_calls:
|
|
1121
|
+
yield {
|
|
1122
|
+
"type": "tool_call",
|
|
1123
|
+
"tool_name": getattr(tool_call, "name", "unknown"),
|
|
1124
|
+
"tool_args": getattr(tool_call, "input", {}),
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
# Set output.value for Langfuse top-level output display
|
|
1128
|
+
span.set_attribute("output.value", accumulated_text[:5000] if accumulated_text else "")
|
|
1129
|
+
span.set_attribute("agent.output", accumulated_text[:500])
|
|
1130
|
+
final_chunk: dict[str, Any] = {"output": accumulated_text, "type": "final"}
|
|
1131
|
+
if usage:
|
|
1132
|
+
final_chunk["usage"] = usage
|
|
1133
|
+
# last_usage = 最后一次 LLM 调用快照(input_tokens = 当前上下文窗口占用)
|
|
1134
|
+
final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
|
|
1135
|
+
yield final_chunk
|