solace-agent-mesh 0.2.3__py3-none-any.whl → 1.0.1__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.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/adk_llm.txt +93 -0
- solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
- solace_agent_mesh/agent/adk/callbacks.py +1694 -0
- solace_agent_mesh/agent/adk/filesystem_artifact_service.py +381 -0
- solace_agent_mesh/agent/adk/invocation_monitor.py +295 -0
- solace_agent_mesh/agent/adk/models/lite_llm.py +872 -0
- solace_agent_mesh/agent/adk/models/models_llm.txt +94 -0
- solace_agent_mesh/agent/adk/runner.py +353 -0
- solace_agent_mesh/agent/adk/services.py +240 -0
- solace_agent_mesh/agent/adk/setup.py +751 -0
- solace_agent_mesh/agent/adk/stream_parser.py +214 -0
- solace_agent_mesh/agent/adk/tool_wrapper.py +139 -0
- solace_agent_mesh/agent/agent_llm.txt +41 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +1469 -0
- solace_agent_mesh/agent/protocol/protocol_llm.txt +21 -0
- solace_agent_mesh/agent/sac/app.py +640 -0
- solace_agent_mesh/agent/sac/component.py +3388 -0
- solace_agent_mesh/agent/sac/patch_adk.py +111 -0
- solace_agent_mesh/agent/sac/sac_llm.txt +105 -0
- solace_agent_mesh/agent/sac/task_execution_context.py +176 -0
- solace_agent_mesh/agent/testing/__init__.py +3 -0
- solace_agent_mesh/agent/testing/debug_utils.py +135 -0
- solace_agent_mesh/agent/testing/testing_llm.txt +90 -0
- solace_agent_mesh/agent/tools/__init__.py +14 -0
- solace_agent_mesh/agent/tools/audio_tools.py +1622 -0
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +1954 -0
- solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +238 -0
- solace_agent_mesh/agent/tools/general_agent_tools.py +569 -0
- solace_agent_mesh/agent/tools/image_tools.py +1184 -0
- solace_agent_mesh/agent/tools/peer_agent_tool.py +289 -0
- solace_agent_mesh/agent/tools/registry.py +36 -0
- solace_agent_mesh/agent/tools/test_tools.py +135 -0
- solace_agent_mesh/agent/tools/tool_definition.py +45 -0
- solace_agent_mesh/agent/tools/tools_llm.txt +104 -0
- solace_agent_mesh/agent/tools/web_tools.py +381 -0
- solace_agent_mesh/agent/utils/artifact_helpers.py +927 -0
- solace_agent_mesh/agent/utils/config_parser.py +47 -0
- solace_agent_mesh/agent/utils/context_helpers.py +60 -0
- solace_agent_mesh/agent/utils/utils_llm.txt +153 -0
- solace_agent_mesh/assets/docs/404.html +16 -0
- solace_agent_mesh/assets/docs/assets/css/styles.906a1503.css +1 -0
- solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
- solace_agent_mesh/assets/docs/assets/images/sac-flows-80d5b603c6aafd33e87945680ce0abf3.png +0 -0
- solace_agent_mesh/assets/docs/assets/images/sac_parts_of_a_component-cb3d0424b1d0c17734c5435cca6b4082.png +0 -0
- solace_agent_mesh/assets/docs/assets/js/04989206.674a8007.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/0e682baa.79f0ab22.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1523c6b4.91c7bc01.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
- solace_agent_mesh/assets/docs/assets/js/166ab619.7d97ccaf.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js.LICENSE.txt +7 -0
- solace_agent_mesh/assets/docs/assets/js/1c6e87d2.23bccffb.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/21ceee5f.614fa8dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2334.622a6395.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2a9cab12.8909df92.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/332e10b5.7a103f42.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3624.b524e433.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3d406171.f722eaf5.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/42b3f8d8.36090198.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/442a8107.5ba94b65.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4c2787c2.66ee00e9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5b4258a4.bda20761.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/768e31b0.a12673db.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/85387663.6bf41934.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js.LICENSE.txt +61 -0
- solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8731.49e930c2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/945fb41e.74d728aa.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9eff14a2.1bf8f61c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a3a92b25.26ca071f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/aba87c2f.d3e2dcc3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ae4415af.8e279b5d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/bac0be12.f50d9bac.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/c2c06897.63b76e9e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cc969b05.954186d4.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cd3d4052.ca6eed8c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ced92a13.fb92e7ca.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cee5d587.f5b73ca1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f897a61a.2c2e152c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/fbfa3e75.aca209c9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js.LICENSE.txt +81 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.d9520ae2.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +128 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +91 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +201 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +91 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +55 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +82 -0
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +60 -0
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +48 -0
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +17 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +45 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +76 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +150 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +267 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +136 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +116 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +80 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +164 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +57 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +72 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +102 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +99 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +90 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +107 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +152 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +103 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +170 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +200 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +69 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +59 -0
- solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
- solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
- solace_agent_mesh/assets/docs/img/logo.png +0 -0
- solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
- solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
- solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
- solace_agent_mesh/assets/docs/lunr-index-1753813536522.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -0
- solace_agent_mesh/assets/docs/search-doc-1753813536522.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -0
- solace_agent_mesh/assets/docs/sitemap.xml +1 -0
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
- solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +659 -0
- solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
- solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +93 -0
- solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +118 -0
- solace_agent_mesh/cli/commands/docs_cmd.py +57 -0
- solace_agent_mesh/cli/commands/eval_cmd.py +64 -0
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +404 -0
- solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
- solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +197 -0
- solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +387 -0
- solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
- solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +110 -0
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +183 -0
- solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +18 -0
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +372 -0
- solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +138 -0
- solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +309 -0
- solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +174 -0
- solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
- solace_agent_mesh/cli/commands/run_cmd.py +158 -0
- solace_agent_mesh/cli/main.py +17 -294
- solace_agent_mesh/cli/utils.py +135 -204
- solace_agent_mesh/client/webui/frontend/static/assets/authCallback-DvlO62me.js +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/client-bp6u3qVZ.js +49 -0
- solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-An0a5j5k.js +663 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-Bu5-4Bac.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +14 -0
- solace_agent_mesh/client/webui/frontend/static/index.html +15 -0
- solace_agent_mesh/common/__init__.py +1 -0
- solace_agent_mesh/common/a2a_protocol.py +564 -0
- solace_agent_mesh/common/agent_registry.py +42 -0
- solace_agent_mesh/common/client/__init__.py +4 -0
- solace_agent_mesh/common/client/card_resolver.py +21 -0
- solace_agent_mesh/common/client/client.py +85 -0
- solace_agent_mesh/common/client/client_llm.txt +133 -0
- solace_agent_mesh/common/common_llm.txt +144 -0
- solace_agent_mesh/common/constants.py +1 -14
- solace_agent_mesh/common/middleware/__init__.py +12 -0
- solace_agent_mesh/common/middleware/config_resolver.py +130 -0
- solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
- solace_agent_mesh/common/middleware/registry.py +125 -0
- solace_agent_mesh/common/server/__init__.py +4 -0
- solace_agent_mesh/common/server/server.py +122 -0
- solace_agent_mesh/common/server/server_llm.txt +169 -0
- solace_agent_mesh/common/server/task_manager.py +291 -0
- solace_agent_mesh/common/server/utils.py +28 -0
- solace_agent_mesh/common/services/__init__.py +4 -0
- solace_agent_mesh/common/services/employee_service.py +162 -0
- solace_agent_mesh/common/services/identity_service.py +129 -0
- solace_agent_mesh/common/services/providers/__init__.py +4 -0
- solace_agent_mesh/common/services/providers/local_file_identity_service.py +148 -0
- solace_agent_mesh/common/services/providers/providers_llm.txt +113 -0
- solace_agent_mesh/common/services/services_llm.txt +132 -0
- solace_agent_mesh/common/types.py +411 -0
- solace_agent_mesh/common/utils/__init__.py +7 -0
- solace_agent_mesh/common/utils/asyncio_macos_fix.py +86 -0
- solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
- solace_agent_mesh/common/utils/embeds/constants.py +55 -0
- solace_agent_mesh/common/utils/embeds/converter.py +452 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +124 -0
- solace_agent_mesh/common/utils/embeds/evaluators.py +394 -0
- solace_agent_mesh/common/utils/embeds/modifiers.py +816 -0
- solace_agent_mesh/common/utils/embeds/resolver.py +865 -0
- solace_agent_mesh/common/utils/embeds/types.py +14 -0
- solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
- solace_agent_mesh/common/utils/log_formatters.py +44 -0
- solace_agent_mesh/common/utils/mime_helpers.py +106 -0
- solace_agent_mesh/common/utils/push_notification_auth.py +134 -0
- solace_agent_mesh/common/utils/utils_llm.txt +67 -0
- solace_agent_mesh/config_portal/backend/common.py +66 -24
- solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +23 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +160 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +525 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +216 -0
- solace_agent_mesh/config_portal/backend/server.py +550 -140
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DNxCwAGB.js +48 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/components-B7lKcHVY.js +140 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-DX1misIU.js → entry.client-CEumGClk.js} +3 -3
- solace_agent_mesh/config_portal/frontend/static/client/assets/index-DSo1AH_7.js +68 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-d2b54a97.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{root-BApq5dPK.js → root-C4XmHinv.js} +2 -2
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
- solace_agent_mesh/core_a2a/__init__.py +1 -0
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +88 -0
- solace_agent_mesh/core_a2a/service.py +331 -0
- solace_agent_mesh/evaluation/config_loader.py +657 -0
- solace_agent_mesh/evaluation/evaluator.py +667 -0
- solace_agent_mesh/evaluation/message_organizer.py +568 -0
- solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
- solace_agent_mesh/evaluation/report/chart_section.html +141 -0
- solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
- solace_agent_mesh/evaluation/report/modal.html +59 -0
- solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
- solace_agent_mesh/evaluation/report/modal_script.js +296 -0
- solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
- solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
- solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
- solace_agent_mesh/evaluation/report/templates/header.html +340 -0
- solace_agent_mesh/evaluation/report_data_processor.py +972 -0
- solace_agent_mesh/evaluation/report_generator.py +613 -0
- solace_agent_mesh/evaluation/run.py +613 -0
- solace_agent_mesh/evaluation/subscriber.py +872 -0
- solace_agent_mesh/evaluation/summary_builder.py +775 -0
- solace_agent_mesh/evaluation/test_case_loader.py +714 -0
- solace_agent_mesh/gateway/base/__init__.py +1 -0
- solace_agent_mesh/gateway/base/app.py +266 -0
- solace_agent_mesh/gateway/base/base_llm.txt +119 -0
- solace_agent_mesh/gateway/base/component.py +1542 -0
- solace_agent_mesh/gateway/base/task_context.py +74 -0
- solace_agent_mesh/gateway/gateway_llm.txt +125 -0
- solace_agent_mesh/gateway/http_sse/app.py +190 -0
- solace_agent_mesh/gateway/http_sse/component.py +1602 -0
- solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +65 -0
- solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +108 -0
- solace_agent_mesh/gateway/http_sse/dependencies.py +316 -0
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +63 -0
- solace_agent_mesh/gateway/http_sse/main.py +442 -0
- solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/routers/agents.py +41 -0
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +821 -0
- solace_agent_mesh/gateway/http_sse/routers/auth.py +212 -0
- solace_agent_mesh/gateway/http_sse/routers/config.py +55 -0
- solace_agent_mesh/gateway/http_sse/routers/people.py +69 -0
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +37 -0
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +80 -0
- solace_agent_mesh/gateway/http_sse/routers/sse.py +138 -0
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +294 -0
- solace_agent_mesh/gateway/http_sse/routers/users.py +59 -0
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +1131 -0
- solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/services/agent_service.py +69 -0
- solace_agent_mesh/gateway/http_sse/services/people_service.py +158 -0
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +179 -0
- solace_agent_mesh/gateway/http_sse/services/task_service.py +121 -0
- solace_agent_mesh/gateway/http_sse/session_manager.py +187 -0
- solace_agent_mesh/gateway/http_sse/sse_manager.py +328 -0
- solace_agent_mesh/llm.txt +228 -0
- solace_agent_mesh/llm_detail.txt +2835 -0
- solace_agent_mesh/templates/agent_template.yaml +53 -0
- solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
- solace_agent_mesh/templates/gateway_app_template.py +73 -0
- solace_agent_mesh/templates/gateway_component_template.py +400 -0
- solace_agent_mesh/templates/gateway_config_template.yaml +43 -0
- solace_agent_mesh/templates/main_orchestrator.yaml +55 -0
- solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
- solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
- solace_agent_mesh/templates/plugin_custom_template.py +10 -0
- solace_agent_mesh/templates/plugin_gateway_config_template.yaml +63 -0
- solace_agent_mesh/templates/plugin_pyproject_template.toml +33 -0
- solace_agent_mesh/templates/plugin_readme_template.md +34 -0
- solace_agent_mesh/templates/plugin_tools_template.py +224 -0
- solace_agent_mesh/templates/shared_config.yaml +66 -0
- solace_agent_mesh/templates/templates_llm.txt +147 -0
- solace_agent_mesh/templates/webui.yaml +53 -0
- solace_agent_mesh-1.0.1.dist-info/METADATA +432 -0
- solace_agent_mesh-1.0.1.dist-info/RECORD +359 -0
- solace_agent_mesh-1.0.1.dist-info/entry_points.txt +3 -0
- {solace_agent_mesh-0.2.3.dist-info → solace_agent_mesh-1.0.1.dist-info}/licenses/LICENSE +1 -1
- solace_agent_mesh/agents/base_agent_component.py +0 -226
- solace_agent_mesh/agents/global/actions/agent_state_change.py +0 -54
- solace_agent_mesh/agents/global/actions/clear_history.py +0 -32
- solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +0 -160
- solace_agent_mesh/agents/global/actions/create_file.py +0 -70
- solace_agent_mesh/agents/global/actions/error_action.py +0 -45
- solace_agent_mesh/agents/global/actions/plantuml_diagram.py +0 -163
- solace_agent_mesh/agents/global/actions/plotly_graph.py +0 -152
- solace_agent_mesh/agents/global/actions/retrieve_file.py +0 -51
- solace_agent_mesh/agents/global/global_agent_component.py +0 -38
- solace_agent_mesh/agents/image_processing/actions/create_image.py +0 -75
- solace_agent_mesh/agents/image_processing/actions/describe_image.py +0 -115
- solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +0 -23
- solace_agent_mesh/agents/slack/__init__.py +0 -1
- solace_agent_mesh/agents/slack/actions/__init__.py +0 -1
- solace_agent_mesh/agents/slack/actions/post_message.py +0 -177
- solace_agent_mesh/agents/slack/slack_agent_component.py +0 -59
- solace_agent_mesh/agents/web_request/actions/do_image_search.py +0 -84
- solace_agent_mesh/agents/web_request/actions/do_news_search.py +0 -47
- solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +0 -34
- solace_agent_mesh/agents/web_request/actions/do_web_request.py +0 -135
- solace_agent_mesh/agents/web_request/actions/download_file.py +0 -69
- solace_agent_mesh/agents/web_request/web_request_agent_component.py +0 -33
- solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +0 -1
- solace_agent_mesh/assets/web-visualizer/assets/index-DnDr1pnu.js +0 -109
- solace_agent_mesh/assets/web-visualizer/index.html +0 -14
- solace_agent_mesh/assets/web-visualizer/vite.svg +0 -1
- solace_agent_mesh/cli/commands/add/__init__.py +0 -3
- solace_agent_mesh/cli/commands/add/add.py +0 -88
- solace_agent_mesh/cli/commands/add/agent.py +0 -110
- solace_agent_mesh/cli/commands/add/copy_from_plugin.py +0 -92
- solace_agent_mesh/cli/commands/add/gateway.py +0 -374
- solace_agent_mesh/cli/commands/build.py +0 -670
- solace_agent_mesh/cli/commands/chat/__init__.py +0 -3
- solace_agent_mesh/cli/commands/chat/chat.py +0 -361
- solace_agent_mesh/cli/commands/config.py +0 -29
- solace_agent_mesh/cli/commands/init/__init__.py +0 -3
- solace_agent_mesh/cli/commands/init/ai_provider_step.py +0 -93
- solace_agent_mesh/cli/commands/init/broker_step.py +0 -99
- solace_agent_mesh/cli/commands/init/builtin_agent_step.py +0 -83
- solace_agent_mesh/cli/commands/init/check_if_already_done.py +0 -13
- solace_agent_mesh/cli/commands/init/create_config_file_step.py +0 -65
- solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +0 -147
- solace_agent_mesh/cli/commands/init/file_service_step.py +0 -73
- solace_agent_mesh/cli/commands/init/init.py +0 -92
- solace_agent_mesh/cli/commands/init/project_structure_step.py +0 -16
- solace_agent_mesh/cli/commands/init/web_init_step.py +0 -32
- solace_agent_mesh/cli/commands/plugin/__init__.py +0 -3
- solace_agent_mesh/cli/commands/plugin/add.py +0 -100
- solace_agent_mesh/cli/commands/plugin/build.py +0 -268
- solace_agent_mesh/cli/commands/plugin/create.py +0 -117
- solace_agent_mesh/cli/commands/plugin/plugin.py +0 -124
- solace_agent_mesh/cli/commands/plugin/remove.py +0 -73
- solace_agent_mesh/cli/commands/run.py +0 -68
- solace_agent_mesh/cli/commands/visualizer.py +0 -138
- solace_agent_mesh/cli/config.py +0 -85
- solace_agent_mesh/common/action.py +0 -91
- solace_agent_mesh/common/action_list.py +0 -37
- solace_agent_mesh/common/action_response.py +0 -340
- solace_agent_mesh/common/mysql_database.py +0 -40
- solace_agent_mesh/common/postgres_database.py +0 -85
- solace_agent_mesh/common/prompt_templates.py +0 -28
- solace_agent_mesh/common/stimulus_utils.py +0 -152
- solace_agent_mesh/common/time.py +0 -24
- solace_agent_mesh/common/utils.py +0 -712
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DMmCawWe.js +0 -42
- solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +0 -191
- solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +0 -17
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-dd988f05.js +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-DX4gQ516.css +0 -1
- solace_agent_mesh/configs/agent_global.yaml +0 -74
- solace_agent_mesh/configs/agent_image_processing.yaml +0 -82
- solace_agent_mesh/configs/agent_slack.yaml +0 -64
- solace_agent_mesh/configs/agent_web_request.yaml +0 -75
- solace_agent_mesh/configs/conversation_to_file.yaml +0 -56
- solace_agent_mesh/configs/error_catcher.yaml +0 -56
- solace_agent_mesh/configs/monitor.yaml +0 -0
- solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +0 -109
- solace_agent_mesh/configs/monitor_user_feedback.yaml +0 -58
- solace_agent_mesh/configs/orchestrator.yaml +0 -241
- solace_agent_mesh/configs/service_embedding.yaml +0 -81
- solace_agent_mesh/configs/service_llm.yaml +0 -265
- solace_agent_mesh/configs/visualize_websocket.yaml +0 -55
- solace_agent_mesh/gateway/components/gateway_base.py +0 -47
- solace_agent_mesh/gateway/components/gateway_input.py +0 -278
- solace_agent_mesh/gateway/components/gateway_output.py +0 -298
- solace_agent_mesh/gateway/identity/bamboohr_identity.py +0 -18
- solace_agent_mesh/gateway/identity/identity_base.py +0 -10
- solace_agent_mesh/gateway/identity/identity_provider.py +0 -60
- solace_agent_mesh/gateway/identity/no_identity.py +0 -9
- solace_agent_mesh/gateway/identity/passthru_identity.py +0 -9
- solace_agent_mesh/monitors/base_monitor_component.py +0 -26
- solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +0 -75
- solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +0 -560
- solace_agent_mesh/orchestrator/__init__.py +0 -0
- solace_agent_mesh/orchestrator/action_manager.py +0 -237
- solace_agent_mesh/orchestrator/components/__init__.py +0 -0
- solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +0 -58
- solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +0 -179
- solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +0 -107
- solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +0 -527
- solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +0 -260
- solace_agent_mesh/orchestrator/orchestrator_main.py +0 -172
- solace_agent_mesh/orchestrator/orchestrator_prompt.py +0 -539
- solace_agent_mesh/services/__init__.py +0 -0
- solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +0 -56
- solace_agent_mesh/services/bamboo_hr_service/__init__.py +0 -3
- solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +0 -182
- solace_agent_mesh/services/common/__init__.py +0 -4
- solace_agent_mesh/services/common/auto_expiry.py +0 -45
- solace_agent_mesh/services/common/singleton.py +0 -18
- solace_agent_mesh/services/file_service/__init__.py +0 -14
- solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
- solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +0 -149
- solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +0 -162
- solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +0 -64
- solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +0 -106
- solace_agent_mesh/services/file_service/file_service.py +0 -437
- solace_agent_mesh/services/file_service/file_service_constants.py +0 -54
- solace_agent_mesh/services/file_service/file_transformations.py +0 -141
- solace_agent_mesh/services/file_service/file_utils.py +0 -324
- solace_agent_mesh/services/file_service/transformers/__init__.py +0 -5
- solace_agent_mesh/services/history_service/__init__.py +0 -3
- solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
- solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +0 -54
- solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +0 -74
- solace_agent_mesh/services/history_service/history_providers/index.py +0 -40
- solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +0 -33
- solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +0 -66
- solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +0 -66
- solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +0 -93
- solace_agent_mesh/services/history_service/history_service.py +0 -413
- solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
- solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +0 -399
- solace_agent_mesh/services/llm_service/components/llm_request_component.py +0 -340
- solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +0 -152
- solace_agent_mesh/services/middleware_service/__init__.py +0 -0
- solace_agent_mesh/services/middleware_service/middleware_service.py +0 -20
- solace_agent_mesh/templates/action.py +0 -38
- solace_agent_mesh/templates/agent.py +0 -29
- solace_agent_mesh/templates/agent.yaml +0 -70
- solace_agent_mesh/templates/gateway-config-template.yaml +0 -6
- solace_agent_mesh/templates/gateway-default-config.yaml +0 -28
- solace_agent_mesh/templates/gateway-flows.yaml +0 -78
- solace_agent_mesh/templates/gateway-header.yaml +0 -16
- solace_agent_mesh/templates/gateway_base.py +0 -15
- solace_agent_mesh/templates/gateway_input.py +0 -98
- solace_agent_mesh/templates/gateway_output.py +0 -71
- solace_agent_mesh/templates/plugin-gateway-default-config.yaml +0 -29
- solace_agent_mesh/templates/plugin-pyproject.toml +0 -30
- solace_agent_mesh/templates/rest-api-default-config.yaml +0 -31
- solace_agent_mesh/templates/rest-api-flows.yaml +0 -81
- solace_agent_mesh/templates/slack-default-config.yaml +0 -16
- solace_agent_mesh/templates/slack-flows.yaml +0 -81
- solace_agent_mesh/templates/solace-agent-mesh-default.yaml +0 -86
- solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +0 -8
- solace_agent_mesh/templates/web-default-config.yaml +0 -10
- solace_agent_mesh/templates/web-flows.yaml +0 -76
- solace_agent_mesh/tools/__init__.py +0 -0
- solace_agent_mesh/tools/components/__init__.py +0 -0
- solace_agent_mesh/tools/components/conversation_formatter.py +0 -111
- solace_agent_mesh/tools/components/file_resolver_component.py +0 -58
- solace_agent_mesh/tools/config/runtime_config.py +0 -26
- solace_agent_mesh-0.2.3.dist-info/METADATA +0 -172
- solace_agent_mesh-0.2.3.dist-info/RECORD +0 -193
- solace_agent_mesh-0.2.3.dist-info/entry_points.txt +0 -3
- /solace_agent_mesh/{agents → agent}/__init__.py +0 -0
- /solace_agent_mesh/{agents/global → agent/adk}/__init__.py +0 -0
- /solace_agent_mesh/{agents/global/actions → agent/protocol}/__init__.py +0 -0
- /solace_agent_mesh/{agents/image_processing → agent/sac}/__init__.py +0 -0
- /solace_agent_mesh/{agents/image_processing/actions → agent/utils}/__init__.py +0 -0
- /solace_agent_mesh/{agents/web_request → config_portal/backend/plugin_catalog}/__init__.py +0 -0
- /solace_agent_mesh/{agents/web_request/actions → evaluation}/__init__.py +0 -0
- /solace_agent_mesh/gateway/{components → http_sse}/__init__.py +0 -0
- {solace_agent_mesh-0.2.3.dist-info → solace_agent_mesh-1.0.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,1954 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Built-in ADK Tools for Artifact Management within the A2A Host.
|
|
3
|
+
These tools interact with the ADK ArtifactService via the ToolContext and
|
|
4
|
+
use state_delta for signaling artifact return requests to the host component.
|
|
5
|
+
Metadata handling is integrated via artifact_helpers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import uuid
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import fnmatch
|
|
12
|
+
from typing import Any, Dict, Optional, Union, TYPE_CHECKING
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from google.adk.tools import ToolContext
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from google.adk.agents.invocation_context import InvocationContext
|
|
18
|
+
from google.genai import types as adk_types
|
|
19
|
+
from solace_ai_connector.common.log import log
|
|
20
|
+
from .tool_definition import BuiltinTool
|
|
21
|
+
from .registry import tool_registry
|
|
22
|
+
from ...agent.utils.artifact_helpers import (
|
|
23
|
+
save_artifact_with_metadata,
|
|
24
|
+
decode_and_get_bytes,
|
|
25
|
+
load_artifact_content_or_metadata,
|
|
26
|
+
is_filename_safe,
|
|
27
|
+
METADATA_SUFFIX,
|
|
28
|
+
DEFAULT_SCHEMA_MAX_KEYS,
|
|
29
|
+
)
|
|
30
|
+
from ...common.utils.embeds import (
|
|
31
|
+
evaluate_embed,
|
|
32
|
+
EMBED_REGEX,
|
|
33
|
+
EMBED_CHAIN_DELIMITER,
|
|
34
|
+
)
|
|
35
|
+
from ...agent.utils.context_helpers import get_original_session_id
|
|
36
|
+
from ...agent.adk.models.lite_llm import LiteLlm
|
|
37
|
+
from google.adk.models import LlmRequest
|
|
38
|
+
from google.adk.models.registry import LLMRegistry
|
|
39
|
+
from ...common.utils.mime_helpers import is_text_based_file
|
|
40
|
+
|
|
41
|
+
async def _internal_create_artifact(
|
|
42
|
+
filename: str,
|
|
43
|
+
content: str,
|
|
44
|
+
mime_type: str,
|
|
45
|
+
tool_context: ToolContext = None,
|
|
46
|
+
description: Optional[str] = None,
|
|
47
|
+
metadata_json: Optional[str] = None,
|
|
48
|
+
schema_max_keys: Optional[int] = None,
|
|
49
|
+
) -> Dict[str, Any]:
|
|
50
|
+
"""
|
|
51
|
+
Internal helper to create an artifact with its first chunk of content and metadata.
|
|
52
|
+
This function is not intended to be called directly by the LLM.
|
|
53
|
+
It is used by callbacks that process fenced artifact blocks.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
filename: The desired name for the artifact.
|
|
57
|
+
content: The first chunk of the artifact content, as a string.
|
|
58
|
+
If the mime_type suggests binary data, this string is expected
|
|
59
|
+
to be base64 encoded.
|
|
60
|
+
mime_type: The MIME type of the content.
|
|
61
|
+
tool_context: The ADK ToolContext, required for accessing services.
|
|
62
|
+
description (str, optional): A description for the artifact.
|
|
63
|
+
metadata_json (str, optional): A JSON string of additional metadata.
|
|
64
|
+
schema_max_keys (int, optional): Max keys for schema inference.
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
A dictionary indicating the result, returned by save_artifact_with_metadata.
|
|
69
|
+
"""
|
|
70
|
+
if not tool_context:
|
|
71
|
+
return {
|
|
72
|
+
"status": "error",
|
|
73
|
+
"filename": filename,
|
|
74
|
+
"message": "ToolContext is missing, cannot save artifact.",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if not is_filename_safe(filename):
|
|
78
|
+
return {
|
|
79
|
+
"status": "error",
|
|
80
|
+
"filename": filename,
|
|
81
|
+
"message": "Filename is invalid or contains disallowed characters (e.g., '/', '..').",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
log_identifier = f"[BuiltinArtifactTool:_internal_create_artifact:{filename}]"
|
|
85
|
+
|
|
86
|
+
final_metadata = {}
|
|
87
|
+
if description:
|
|
88
|
+
final_metadata["description"] = description
|
|
89
|
+
if metadata_json:
|
|
90
|
+
try:
|
|
91
|
+
final_metadata.update(json.loads(metadata_json))
|
|
92
|
+
except (json.JSONDecodeError, TypeError):
|
|
93
|
+
log.warning(
|
|
94
|
+
"%s Invalid JSON in metadata_json attribute: %s",
|
|
95
|
+
log_identifier,
|
|
96
|
+
metadata_json,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
final_metadata["metadata_parsing_error"] = (
|
|
100
|
+
f"Invalid JSON provided: {metadata_json}"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
log.debug("%s Processing request with metadata: %s", log_identifier, final_metadata)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
inv_context = tool_context._invocation_context
|
|
107
|
+
artifact_bytes, final_mime_type = decode_and_get_bytes(
|
|
108
|
+
content, mime_type, log_identifier
|
|
109
|
+
)
|
|
110
|
+
max_keys_to_use = (
|
|
111
|
+
schema_max_keys if schema_max_keys is not None else DEFAULT_SCHEMA_MAX_KEYS
|
|
112
|
+
)
|
|
113
|
+
if schema_max_keys is not None:
|
|
114
|
+
log.debug(
|
|
115
|
+
"%s Using schema_max_keys provided by LLM: %d",
|
|
116
|
+
log_identifier,
|
|
117
|
+
schema_max_keys,
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
log.debug(
|
|
121
|
+
"%s Using default schema_max_keys: %d",
|
|
122
|
+
log_identifier,
|
|
123
|
+
DEFAULT_SCHEMA_MAX_KEYS,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
artifact_service = inv_context.artifact_service
|
|
127
|
+
if not artifact_service:
|
|
128
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
129
|
+
session_last_update_time = inv_context.session.last_update_time
|
|
130
|
+
timestamp_for_artifact: datetime
|
|
131
|
+
if isinstance(session_last_update_time, datetime):
|
|
132
|
+
timestamp_for_artifact = session_last_update_time
|
|
133
|
+
elif isinstance(session_last_update_time, (int, float)):
|
|
134
|
+
log.debug(
|
|
135
|
+
"%s Converting numeric session.last_update_time (%s) to datetime.",
|
|
136
|
+
log_identifier,
|
|
137
|
+
session_last_update_time,
|
|
138
|
+
)
|
|
139
|
+
try:
|
|
140
|
+
timestamp_for_artifact = datetime.fromtimestamp(
|
|
141
|
+
session_last_update_time, timezone.utc
|
|
142
|
+
)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
log.warning(
|
|
145
|
+
"%s Failed to convert numeric timestamp %s to datetime: %s. Using current time.",
|
|
146
|
+
log_identifier,
|
|
147
|
+
session_last_update_time,
|
|
148
|
+
e,
|
|
149
|
+
)
|
|
150
|
+
timestamp_for_artifact = datetime.now(timezone.utc)
|
|
151
|
+
else:
|
|
152
|
+
if session_last_update_time is not None:
|
|
153
|
+
log.warning(
|
|
154
|
+
"%s Unexpected type for session.last_update_time: %s. Using current time.",
|
|
155
|
+
log_identifier,
|
|
156
|
+
type(session_last_update_time),
|
|
157
|
+
)
|
|
158
|
+
timestamp_for_artifact = datetime.now(timezone.utc)
|
|
159
|
+
result = await save_artifact_with_metadata(
|
|
160
|
+
artifact_service=artifact_service,
|
|
161
|
+
app_name=inv_context.app_name,
|
|
162
|
+
user_id=inv_context.user_id,
|
|
163
|
+
session_id=get_original_session_id(inv_context),
|
|
164
|
+
filename=filename,
|
|
165
|
+
content_bytes=artifact_bytes,
|
|
166
|
+
mime_type=final_mime_type,
|
|
167
|
+
metadata_dict=final_metadata,
|
|
168
|
+
timestamp=timestamp_for_artifact,
|
|
169
|
+
schema_max_keys=max_keys_to_use,
|
|
170
|
+
tool_context=tool_context,
|
|
171
|
+
)
|
|
172
|
+
log.info(
|
|
173
|
+
"%s Result from save_artifact_with_metadata: %s", log_identifier, result
|
|
174
|
+
)
|
|
175
|
+
return result
|
|
176
|
+
except Exception as e:
|
|
177
|
+
log.exception(
|
|
178
|
+
"%s Error creating artifact '%s': %s", log_identifier, filename, e
|
|
179
|
+
)
|
|
180
|
+
return {
|
|
181
|
+
"status": "error",
|
|
182
|
+
"filename": filename,
|
|
183
|
+
"message": f"Failed to create artifact: {e}",
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
async def list_artifacts(tool_context: ToolContext = None) -> Dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Lists all available data artifact filenames and their versions for the current session.
|
|
190
|
+
Includes a summary of the latest version's metadata for each artifact.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
tool_context: The context provided by the ADK framework.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
A dictionary containing the list of artifacts with metadata summaries or an error.
|
|
197
|
+
"""
|
|
198
|
+
if not tool_context:
|
|
199
|
+
return {"status": "error", "message": "ToolContext is missing."}
|
|
200
|
+
log_identifier = "[BuiltinArtifactTool:list_artifacts]"
|
|
201
|
+
log.debug("%s Processing request.", log_identifier)
|
|
202
|
+
try:
|
|
203
|
+
artifact_service = tool_context._invocation_context.artifact_service
|
|
204
|
+
if not artifact_service:
|
|
205
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
206
|
+
app_name = tool_context._invocation_context.app_name
|
|
207
|
+
user_id = tool_context._invocation_context.user_id
|
|
208
|
+
session_id = get_original_session_id(tool_context._invocation_context)
|
|
209
|
+
list_keys_method = getattr(artifact_service, "list_artifact_keys")
|
|
210
|
+
all_keys = await list_keys_method(
|
|
211
|
+
app_name=app_name, user_id=user_id, session_id=session_id
|
|
212
|
+
)
|
|
213
|
+
response_files = []
|
|
214
|
+
processed_data_files = set()
|
|
215
|
+
for key in all_keys:
|
|
216
|
+
if key.endswith(METADATA_SUFFIX):
|
|
217
|
+
continue # Skip metadata files initially
|
|
218
|
+
|
|
219
|
+
if key in processed_data_files:
|
|
220
|
+
continue # Already processed this data file
|
|
221
|
+
|
|
222
|
+
filename = key
|
|
223
|
+
metadata_summary = None
|
|
224
|
+
versions = []
|
|
225
|
+
try:
|
|
226
|
+
versions = await artifact_service.list_versions(
|
|
227
|
+
app_name=app_name,
|
|
228
|
+
user_id=user_id,
|
|
229
|
+
session_id=session_id,
|
|
230
|
+
filename=filename,
|
|
231
|
+
)
|
|
232
|
+
if not versions:
|
|
233
|
+
log.warning(
|
|
234
|
+
"%s Found artifact key '%s' but no versions listed. Skipping.",
|
|
235
|
+
log_identifier,
|
|
236
|
+
filename,
|
|
237
|
+
)
|
|
238
|
+
continue
|
|
239
|
+
latest_version = max(versions)
|
|
240
|
+
metadata_filename = f"{filename}{METADATA_SUFFIX}"
|
|
241
|
+
if metadata_filename in all_keys:
|
|
242
|
+
try:
|
|
243
|
+
metadata_part = await artifact_service.load_artifact(
|
|
244
|
+
app_name=app_name,
|
|
245
|
+
user_id=user_id,
|
|
246
|
+
session_id=session_id,
|
|
247
|
+
filename=metadata_filename,
|
|
248
|
+
version=latest_version,
|
|
249
|
+
)
|
|
250
|
+
if metadata_part and metadata_part.inline_data:
|
|
251
|
+
try:
|
|
252
|
+
metadata_dict = json.loads(
|
|
253
|
+
metadata_part.inline_data.data.decode("utf-8")
|
|
254
|
+
)
|
|
255
|
+
schema = metadata_dict.get("schema", {})
|
|
256
|
+
metadata_summary = {
|
|
257
|
+
"description": metadata_dict.get("description"),
|
|
258
|
+
"source": metadata_dict.get("source"),
|
|
259
|
+
"type": metadata_dict.get("mime_type"),
|
|
260
|
+
"size": metadata_dict.get("size_bytes"),
|
|
261
|
+
"schema_type": schema.get(
|
|
262
|
+
"type", metadata_dict.get("mime_type")
|
|
263
|
+
),
|
|
264
|
+
"schema_inferred": schema.get("inferred"),
|
|
265
|
+
}
|
|
266
|
+
metadata_summary = {
|
|
267
|
+
k: v
|
|
268
|
+
for k, v in metadata_summary.items()
|
|
269
|
+
if v is not None
|
|
270
|
+
}
|
|
271
|
+
log.debug(
|
|
272
|
+
"%s Loaded metadata summary for '%s' v%d.",
|
|
273
|
+
log_identifier,
|
|
274
|
+
filename,
|
|
275
|
+
latest_version,
|
|
276
|
+
)
|
|
277
|
+
except json.JSONDecodeError as json_err:
|
|
278
|
+
log.warning(
|
|
279
|
+
"%s Failed to parse metadata JSON for '%s' v%d: %s",
|
|
280
|
+
log_identifier,
|
|
281
|
+
metadata_filename,
|
|
282
|
+
latest_version,
|
|
283
|
+
json_err,
|
|
284
|
+
)
|
|
285
|
+
metadata_summary = {"error": "Failed to parse metadata"}
|
|
286
|
+
except Exception as fmt_err:
|
|
287
|
+
log.warning(
|
|
288
|
+
"%s Failed to format metadata summary for '%s' v%d: %s",
|
|
289
|
+
log_identifier,
|
|
290
|
+
metadata_filename,
|
|
291
|
+
latest_version,
|
|
292
|
+
fmt_err,
|
|
293
|
+
)
|
|
294
|
+
metadata_summary = {
|
|
295
|
+
"error": "Failed to format metadata"
|
|
296
|
+
}
|
|
297
|
+
else:
|
|
298
|
+
log.warning(
|
|
299
|
+
"%s Metadata file '%s' v%d found but empty or unreadable.",
|
|
300
|
+
log_identifier,
|
|
301
|
+
metadata_filename,
|
|
302
|
+
latest_version,
|
|
303
|
+
)
|
|
304
|
+
metadata_summary = {
|
|
305
|
+
"error": "Metadata file empty or unreadable"
|
|
306
|
+
}
|
|
307
|
+
except Exception as load_err:
|
|
308
|
+
log.warning(
|
|
309
|
+
"%s Failed to load metadata file '%s' v%d: %s",
|
|
310
|
+
log_identifier,
|
|
311
|
+
metadata_filename,
|
|
312
|
+
latest_version,
|
|
313
|
+
load_err,
|
|
314
|
+
)
|
|
315
|
+
metadata_summary = {
|
|
316
|
+
"error": f"Failed to load metadata: {load_err}"
|
|
317
|
+
}
|
|
318
|
+
else:
|
|
319
|
+
log.debug(
|
|
320
|
+
"%s No companion metadata file found for '%s'.",
|
|
321
|
+
log_identifier,
|
|
322
|
+
filename,
|
|
323
|
+
)
|
|
324
|
+
metadata_summary = {"info": "No metadata file found"}
|
|
325
|
+
except Exception as version_err:
|
|
326
|
+
log.warning(
|
|
327
|
+
"%s Failed to list versions or process metadata for file '%s': %s. Skipping file.",
|
|
328
|
+
log_identifier,
|
|
329
|
+
filename,
|
|
330
|
+
version_err,
|
|
331
|
+
)
|
|
332
|
+
continue
|
|
333
|
+
response_files.append(
|
|
334
|
+
{
|
|
335
|
+
"filename": filename,
|
|
336
|
+
"versions": versions,
|
|
337
|
+
"metadata_summary": metadata_summary,
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
processed_data_files.add(filename)
|
|
341
|
+
log.info(
|
|
342
|
+
"%s Found %d data artifacts for session %s.",
|
|
343
|
+
log_identifier,
|
|
344
|
+
len(response_files),
|
|
345
|
+
session_id,
|
|
346
|
+
)
|
|
347
|
+
return {"status": "success", "artifacts": response_files}
|
|
348
|
+
except Exception as e:
|
|
349
|
+
log.exception("%s Error listing artifacts: %s", log_identifier, e)
|
|
350
|
+
return {"status": "error", "message": f"Failed to list artifacts: {e}"}
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
async def load_artifact(
|
|
354
|
+
filename: str,
|
|
355
|
+
version: int,
|
|
356
|
+
load_metadata_only: bool = False,
|
|
357
|
+
max_content_length: Optional[int] = None,
|
|
358
|
+
tool_context: ToolContext = None,
|
|
359
|
+
) -> Dict[str, Any]:
|
|
360
|
+
"""
|
|
361
|
+
Loads the content or metadata of a specific artifact version.
|
|
362
|
+
Early-stage embeds in the filename argument are resolved.
|
|
363
|
+
|
|
364
|
+
If load_metadata_only is True, loads the full metadata dictionary.
|
|
365
|
+
Otherwise, loads text content (potentially truncated) or binary metadata summary.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
filename: The name of the artifact to load. May contain embeds.
|
|
369
|
+
version: The specific version number to load. Must be explicitly provided.
|
|
370
|
+
load_metadata_only (bool): If True, load only the metadata JSON. Default False.
|
|
371
|
+
max_content_length (Optional[int]): Maximum character length for text content.
|
|
372
|
+
If None, uses app configuration. Range: 100-100,000.
|
|
373
|
+
tool_context: The context provided by the ADK framework.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
A dictionary containing the artifact details and content/metadata or an error.
|
|
377
|
+
"""
|
|
378
|
+
if not tool_context:
|
|
379
|
+
return {
|
|
380
|
+
"status": "error",
|
|
381
|
+
"filename": filename,
|
|
382
|
+
"version": version,
|
|
383
|
+
"message": "ToolContext is missing.",
|
|
384
|
+
}
|
|
385
|
+
log_identifier = f"[BuiltinArtifactTool:load_artifact:{filename}:{version}]"
|
|
386
|
+
log.debug(
|
|
387
|
+
"%s Processing request (load_metadata_only=%s).",
|
|
388
|
+
log_identifier,
|
|
389
|
+
load_metadata_only,
|
|
390
|
+
)
|
|
391
|
+
if version is None:
|
|
392
|
+
version = "latest"
|
|
393
|
+
try:
|
|
394
|
+
artifact_service = tool_context._invocation_context.artifact_service
|
|
395
|
+
if not artifact_service:
|
|
396
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
397
|
+
app_name = tool_context._invocation_context.app_name
|
|
398
|
+
user_id = tool_context._invocation_context.user_id
|
|
399
|
+
session_id = get_original_session_id(tool_context._invocation_context)
|
|
400
|
+
agent = getattr(tool_context._invocation_context, "agent", None)
|
|
401
|
+
host_component = getattr(agent, "host_component", None) if agent else None
|
|
402
|
+
result = await load_artifact_content_or_metadata(
|
|
403
|
+
artifact_service=artifact_service,
|
|
404
|
+
app_name=app_name,
|
|
405
|
+
user_id=user_id,
|
|
406
|
+
session_id=session_id,
|
|
407
|
+
filename=filename,
|
|
408
|
+
version=version,
|
|
409
|
+
load_metadata_only=load_metadata_only,
|
|
410
|
+
max_content_length=max_content_length,
|
|
411
|
+
component=host_component,
|
|
412
|
+
log_identifier_prefix="[BuiltinArtifactTool:load_artifact]",
|
|
413
|
+
)
|
|
414
|
+
return result
|
|
415
|
+
except FileNotFoundError as fnf_err:
|
|
416
|
+
log.warning(
|
|
417
|
+
"%s Artifact not found (reported by helper): %s", log_identifier, fnf_err
|
|
418
|
+
)
|
|
419
|
+
return {
|
|
420
|
+
"status": "error",
|
|
421
|
+
"filename": filename,
|
|
422
|
+
"version": version,
|
|
423
|
+
"message": str(fnf_err),
|
|
424
|
+
}
|
|
425
|
+
except ValueError as val_err:
|
|
426
|
+
log.warning(
|
|
427
|
+
"%s Value error during load (reported by helper): %s",
|
|
428
|
+
log_identifier,
|
|
429
|
+
val_err,
|
|
430
|
+
)
|
|
431
|
+
return {
|
|
432
|
+
"status": "error",
|
|
433
|
+
"filename": filename,
|
|
434
|
+
"version": version,
|
|
435
|
+
"message": str(val_err),
|
|
436
|
+
}
|
|
437
|
+
except Exception as e:
|
|
438
|
+
log.exception(
|
|
439
|
+
"%s Unexpected error in load_artifact tool: %s", log_identifier, e
|
|
440
|
+
)
|
|
441
|
+
return {
|
|
442
|
+
"status": "error",
|
|
443
|
+
"filename": filename,
|
|
444
|
+
"version": version,
|
|
445
|
+
"message": f"Unexpected error processing load request: {e}",
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
async def signal_artifact_for_return(
|
|
450
|
+
filename: str,
|
|
451
|
+
version: int,
|
|
452
|
+
tool_context: ToolContext = None,
|
|
453
|
+
) -> Dict[str, Any]:
|
|
454
|
+
"""
|
|
455
|
+
Signals that a specific version of an artifact should be returned to the
|
|
456
|
+
original caller as part of the final task result.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
filename: The name of the artifact to return. May contain embeds.
|
|
460
|
+
version: The specific version number to return. Must be explicitly provided.
|
|
461
|
+
"""
|
|
462
|
+
if not tool_context:
|
|
463
|
+
return {
|
|
464
|
+
"status": "error",
|
|
465
|
+
"filename": filename,
|
|
466
|
+
"version": version,
|
|
467
|
+
"message": "ToolContext is missing.",
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
log_identifier = (
|
|
471
|
+
f"[BuiltinArtifactTool:signal_artifact_for_return:{filename}:{version}]"
|
|
472
|
+
)
|
|
473
|
+
log.debug("%s Processing request after potential embed resolution.", log_identifier)
|
|
474
|
+
|
|
475
|
+
if version is None:
|
|
476
|
+
return {
|
|
477
|
+
"status": "error",
|
|
478
|
+
"filename": filename,
|
|
479
|
+
"version": None,
|
|
480
|
+
"message": "Version parameter is required. Use list_artifacts() to find available versions.",
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
try:
|
|
484
|
+
inv_context = tool_context._invocation_context
|
|
485
|
+
artifact_service = inv_context.artifact_service
|
|
486
|
+
host_component = getattr(inv_context.agent, "host_component", None)
|
|
487
|
+
|
|
488
|
+
if not artifact_service:
|
|
489
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
490
|
+
if not host_component:
|
|
491
|
+
raise ValueError("Host component is not available.")
|
|
492
|
+
|
|
493
|
+
app_name = inv_context.app_name
|
|
494
|
+
user_id = inv_context.user_id
|
|
495
|
+
session_id = get_original_session_id(inv_context)
|
|
496
|
+
|
|
497
|
+
versions = await artifact_service.list_versions(
|
|
498
|
+
app_name=app_name,
|
|
499
|
+
user_id=user_id,
|
|
500
|
+
session_id=session_id,
|
|
501
|
+
filename=filename,
|
|
502
|
+
)
|
|
503
|
+
if version not in versions:
|
|
504
|
+
raise FileNotFoundError(
|
|
505
|
+
f"Artifact '{filename}' version {version} not found."
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
a2a_context = tool_context.state.get("a2a_context", {})
|
|
509
|
+
logical_task_id = a2a_context.get("logical_task_id")
|
|
510
|
+
if not logical_task_id:
|
|
511
|
+
raise ValueError("Could not determine logical_task_id for signaling.")
|
|
512
|
+
|
|
513
|
+
with host_component.active_tasks_lock:
|
|
514
|
+
task_execution_context = host_component.active_tasks.get(logical_task_id)
|
|
515
|
+
|
|
516
|
+
if not task_execution_context:
|
|
517
|
+
raise ValueError(
|
|
518
|
+
f"TaskExecutionContext not found for task {logical_task_id}."
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
signal_data = {"filename": filename, "version": version}
|
|
522
|
+
task_execution_context.add_artifact_signal(signal_data)
|
|
523
|
+
|
|
524
|
+
log.info(
|
|
525
|
+
"%s Added artifact signal to TaskExecutionContext for task %s.",
|
|
526
|
+
log_identifier,
|
|
527
|
+
logical_task_id,
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
# Also add a placeholder to state_delta. This acts as a trigger
|
|
531
|
+
# for the host component to check the cache at the end of the turn.
|
|
532
|
+
# The key is unique to avoid collisions, but the content is just a placeholder.
|
|
533
|
+
trigger_key = f"temp:a2a_return_artifact:{uuid.uuid4().hex}"
|
|
534
|
+
tool_context.actions.state_delta[trigger_key] = {"triggered": True}
|
|
535
|
+
log.debug(
|
|
536
|
+
"%s Set state_delta trigger key '%s' to ensure signal processing.",
|
|
537
|
+
log_identifier,
|
|
538
|
+
trigger_key,
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
"status": "success",
|
|
543
|
+
"message": f"Artifact '{filename}' (version {version}) has been signaled for return.",
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
except FileNotFoundError as fnf_err:
|
|
547
|
+
log.warning("%s Artifact not found: %s", log_identifier, fnf_err)
|
|
548
|
+
return {
|
|
549
|
+
"status": "error",
|
|
550
|
+
"filename": filename,
|
|
551
|
+
"version": version,
|
|
552
|
+
"message": str(fnf_err),
|
|
553
|
+
}
|
|
554
|
+
except Exception as e:
|
|
555
|
+
log.exception("%s Error signaling artifact for return: %s", log_identifier, e)
|
|
556
|
+
return {
|
|
557
|
+
"status": "error",
|
|
558
|
+
"filename": filename,
|
|
559
|
+
"version": version,
|
|
560
|
+
"message": f"Failed to signal artifact for return: {e}",
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
async def apply_embed_and_create_artifact(
|
|
565
|
+
output_filename: str,
|
|
566
|
+
embed_directive: str,
|
|
567
|
+
output_metadata: Optional[Dict[str, Any]] = None,
|
|
568
|
+
tool_context: ToolContext = None,
|
|
569
|
+
) -> Dict[str, Any]:
|
|
570
|
+
"""
|
|
571
|
+
Resolves an 'artifact_content' embed directive (including modifiers and formatting)
|
|
572
|
+
and saves the resulting content as a new artifact. The entire embed directive
|
|
573
|
+
must be provided as a string as the embed_directive argument.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
output_filename: The desired name for the new artifact.
|
|
577
|
+
embed_directive: The full '«artifact_content:...>>>...>>>format:...»' string.
|
|
578
|
+
output_metadata (dict, optional): Metadata for the new artifact.
|
|
579
|
+
tool_context: The context provided by the ADK framework.
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
A dictionary indicating the result, including the new filename and version.
|
|
583
|
+
"""
|
|
584
|
+
if not tool_context:
|
|
585
|
+
return {"status": "error", "message": "ToolContext is missing."}
|
|
586
|
+
|
|
587
|
+
log_identifier = f"[BuiltinArtifactTool:apply_embed:{output_filename}]"
|
|
588
|
+
log.info(
|
|
589
|
+
"%s Processing request with directive: %s", log_identifier, embed_directive
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
match = EMBED_REGEX.fullmatch(embed_directive)
|
|
593
|
+
if not match:
|
|
594
|
+
return {
|
|
595
|
+
"status": "error",
|
|
596
|
+
"message": f"Invalid embed directive format: {embed_directive}",
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
embed_type = match.group(1)
|
|
600
|
+
expression = match.group(2)
|
|
601
|
+
format_spec = match.group(3)
|
|
602
|
+
|
|
603
|
+
if embed_type != "artifact_content":
|
|
604
|
+
return {
|
|
605
|
+
"status": "error",
|
|
606
|
+
"message": f"This tool only supports 'artifact_content' embeds, got '{embed_type}'.",
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
try:
|
|
610
|
+
inv_context = tool_context._invocation_context
|
|
611
|
+
artifact_service = inv_context.artifact_service
|
|
612
|
+
if not artifact_service:
|
|
613
|
+
raise ValueError("ArtifactService not available.")
|
|
614
|
+
|
|
615
|
+
host_component = getattr(inv_context.agent, "host_component", None)
|
|
616
|
+
if not host_component:
|
|
617
|
+
log.warning(
|
|
618
|
+
"%s Could not access host component config for limits. Proceeding without them.",
|
|
619
|
+
log_identifier,
|
|
620
|
+
)
|
|
621
|
+
embed_config = {}
|
|
622
|
+
else:
|
|
623
|
+
embed_config = {
|
|
624
|
+
"gateway_artifact_content_limit_bytes": host_component.get_config(
|
|
625
|
+
"gateway_artifact_content_limit_bytes", -1
|
|
626
|
+
),
|
|
627
|
+
"gateway_recursive_embed_depth": host_component.get_config(
|
|
628
|
+
"gateway_recursive_embed_depth", 3
|
|
629
|
+
),
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
gateway_context = {
|
|
633
|
+
"artifact_service": artifact_service,
|
|
634
|
+
"session_context": {
|
|
635
|
+
"app_name": inv_context.app_name,
|
|
636
|
+
"user_id": inv_context.user_id,
|
|
637
|
+
"session_id": get_original_session_id(inv_context),
|
|
638
|
+
},
|
|
639
|
+
}
|
|
640
|
+
except Exception as ctx_err:
|
|
641
|
+
log.error(
|
|
642
|
+
"%s Failed to prepare context/config for embed evaluation: %s",
|
|
643
|
+
log_identifier,
|
|
644
|
+
ctx_err,
|
|
645
|
+
)
|
|
646
|
+
return {
|
|
647
|
+
"status": "error",
|
|
648
|
+
"message": f"Internal error preparing context: {ctx_err}",
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
resolved_content_str, error_msg_from_eval, _ = await evaluate_embed(
|
|
652
|
+
embed_type=embed_type,
|
|
653
|
+
expression=expression,
|
|
654
|
+
format_spec=format_spec,
|
|
655
|
+
context=gateway_context,
|
|
656
|
+
log_identifier=log_identifier,
|
|
657
|
+
config=embed_config,
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
if error_msg_from_eval or (
|
|
661
|
+
resolved_content_str and resolved_content_str.startswith("[Error:")
|
|
662
|
+
):
|
|
663
|
+
error_to_report = error_msg_from_eval or resolved_content_str
|
|
664
|
+
log.error("%s Embed resolution failed: %s", log_identifier, error_to_report)
|
|
665
|
+
return {
|
|
666
|
+
"status": "error",
|
|
667
|
+
"message": f"Embed resolution failed: {error_to_report}",
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
output_mime_type = "text/plain"
|
|
671
|
+
final_format = None
|
|
672
|
+
chain_parts = expression.split(EMBED_CHAIN_DELIMITER)
|
|
673
|
+
if len(chain_parts) > 1:
|
|
674
|
+
last_part = chain_parts[-1].strip()
|
|
675
|
+
format_match = re.match(r"format:(.*)", last_part, re.DOTALL)
|
|
676
|
+
if format_match:
|
|
677
|
+
final_format = format_match.group(1).strip().lower()
|
|
678
|
+
elif format_spec:
|
|
679
|
+
final_format = format_spec.strip().lower()
|
|
680
|
+
|
|
681
|
+
if final_format:
|
|
682
|
+
if final_format == "html":
|
|
683
|
+
output_mime_type = "text/html"
|
|
684
|
+
elif final_format == "json" or final_format == "json_pretty":
|
|
685
|
+
output_mime_type = "application/json"
|
|
686
|
+
elif final_format == "csv":
|
|
687
|
+
output_mime_type = "text/csv"
|
|
688
|
+
elif final_format == "datauri":
|
|
689
|
+
output_mime_type = "text/plain"
|
|
690
|
+
log.warning(
|
|
691
|
+
"%s Embed resolved to data URI; saving new artifact as text/plain.",
|
|
692
|
+
log_identifier,
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
log.debug("%s Determined output MIME type as: %s", log_identifier, output_mime_type)
|
|
696
|
+
|
|
697
|
+
try:
|
|
698
|
+
resolved_bytes = resolved_content_str.encode("utf-8")
|
|
699
|
+
inv_context = tool_context._invocation_context
|
|
700
|
+
artifact_service = inv_context.artifact_service
|
|
701
|
+
if not artifact_service:
|
|
702
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
703
|
+
|
|
704
|
+
save_result = await save_artifact_with_metadata(
|
|
705
|
+
artifact_service=artifact_service,
|
|
706
|
+
app_name=inv_context.app_name,
|
|
707
|
+
user_id=inv_context.user_id,
|
|
708
|
+
session_id=get_original_session_id(inv_context),
|
|
709
|
+
filename=output_filename,
|
|
710
|
+
content_bytes=resolved_bytes,
|
|
711
|
+
mime_type=output_mime_type,
|
|
712
|
+
metadata_dict=(
|
|
713
|
+
lambda base_meta, user_meta: (
|
|
714
|
+
base_meta.update(user_meta or {}),
|
|
715
|
+
base_meta,
|
|
716
|
+
)[1]
|
|
717
|
+
)({"source_directive": embed_directive}, output_metadata),
|
|
718
|
+
timestamp=inv_context.session.last_update_time
|
|
719
|
+
or datetime.now(timezone.utc),
|
|
720
|
+
schema_max_keys=(
|
|
721
|
+
host_component.get_config("schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS)
|
|
722
|
+
if host_component
|
|
723
|
+
else DEFAULT_SCHEMA_MAX_KEYS
|
|
724
|
+
),
|
|
725
|
+
tool_context=tool_context,
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
log.info(
|
|
729
|
+
"%s Successfully applied embed and saved new artifact '%s' (v%s).",
|
|
730
|
+
log_identifier,
|
|
731
|
+
output_filename,
|
|
732
|
+
save_result.get("data_version"),
|
|
733
|
+
)
|
|
734
|
+
return {
|
|
735
|
+
"status": "success",
|
|
736
|
+
"output_filename": output_filename,
|
|
737
|
+
"output_version": save_result.get("data_version"),
|
|
738
|
+
"output_mime_type": output_mime_type,
|
|
739
|
+
"message": f"Successfully created artifact '{output_filename}' v{save_result.get('data_version')} from embed directive.",
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
except Exception as save_err:
|
|
743
|
+
log.exception(
|
|
744
|
+
"%s Failed to save resolved content as artifact '%s': %s",
|
|
745
|
+
log_identifier,
|
|
746
|
+
output_filename,
|
|
747
|
+
save_err,
|
|
748
|
+
)
|
|
749
|
+
return {
|
|
750
|
+
"status": "error",
|
|
751
|
+
"message": f"Failed to save new artifact: {save_err}",
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
async def extract_content_from_artifact(
|
|
756
|
+
filename: str,
|
|
757
|
+
extraction_goal: str,
|
|
758
|
+
version: Optional[str] = "latest",
|
|
759
|
+
output_filename_base: Optional[str] = None,
|
|
760
|
+
tool_context: ToolContext = None,
|
|
761
|
+
) -> Dict[str, Any]:
|
|
762
|
+
"""
|
|
763
|
+
Loads an existing artifact, uses an internal LLM to process its content
|
|
764
|
+
based on an "extraction_goal," and manages the output by returning it
|
|
765
|
+
or saving it as a new artifact.
|
|
766
|
+
|
|
767
|
+
The tool's description for the LLM might dynamically update based on
|
|
768
|
+
the 'supported_binary_mime_types' configuration of the agent, indicating
|
|
769
|
+
which binary types it can attempt to process.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
filename (str): Name of the source artifact. May contain embeds.
|
|
773
|
+
extraction_goal (str): Natural language instruction for the LLM on what
|
|
774
|
+
to extract or how to transform the content.
|
|
775
|
+
May contain embeds.
|
|
776
|
+
version (Optional[Union[int, str]]): Version of the source artifact.
|
|
777
|
+
Can be an integer or "latest".
|
|
778
|
+
Defaults to "latest". May contain embeds.
|
|
779
|
+
output_filename_base (Optional[str]): Optional base name for the new
|
|
780
|
+
artifact if the extracted content
|
|
781
|
+
is saved. May contain embeds.
|
|
782
|
+
tool_context (ToolContext): Provided by the ADK framework.
|
|
783
|
+
|
|
784
|
+
Returns:
|
|
785
|
+
Dict[str, Any]: A dictionary containing the status of the operation,
|
|
786
|
+
a message for the LLM, and potentially the extracted
|
|
787
|
+
data or details of a newly saved artifact.
|
|
788
|
+
Refer to the design document for specific response structures.
|
|
789
|
+
"""
|
|
790
|
+
log_identifier = f"[BuiltinArtifactTool:extract_content:{filename}:{version}]"
|
|
791
|
+
log.debug(
|
|
792
|
+
"%s Processing request. Goal: '%s', Output base: '%s'",
|
|
793
|
+
log_identifier,
|
|
794
|
+
extraction_goal,
|
|
795
|
+
output_filename_base,
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
if not tool_context:
|
|
799
|
+
return {
|
|
800
|
+
"status": "error_tool_context_missing",
|
|
801
|
+
"message_to_llm": "Tool execution failed: ToolContext is missing.",
|
|
802
|
+
"filename": filename,
|
|
803
|
+
"version_requested": str(version),
|
|
804
|
+
}
|
|
805
|
+
if not filename:
|
|
806
|
+
return {
|
|
807
|
+
"status": "error_missing_filename",
|
|
808
|
+
"message_to_llm": "Tool execution failed: 'filename' parameter is required.",
|
|
809
|
+
"version_requested": str(version),
|
|
810
|
+
}
|
|
811
|
+
if not extraction_goal:
|
|
812
|
+
return {
|
|
813
|
+
"status": "error_missing_extraction_goal",
|
|
814
|
+
"message_to_llm": "Tool execution failed: 'extraction_goal' parameter is required.",
|
|
815
|
+
"filename": filename,
|
|
816
|
+
"version_requested": str(version),
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
inv_context = tool_context._invocation_context
|
|
820
|
+
host_component = getattr(inv_context.agent, "host_component", None)
|
|
821
|
+
if not host_component:
|
|
822
|
+
log.error(
|
|
823
|
+
"%s Host component not found on agent. Cannot retrieve config.",
|
|
824
|
+
log_identifier,
|
|
825
|
+
)
|
|
826
|
+
return {
|
|
827
|
+
"status": "error_internal_configuration",
|
|
828
|
+
"message_to_llm": "Tool configuration error: Host component not accessible.",
|
|
829
|
+
"filename": filename,
|
|
830
|
+
"version_requested": str(version),
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
try:
|
|
834
|
+
save_threshold = host_component.get_config(
|
|
835
|
+
"tool_output_save_threshold_bytes", 2048
|
|
836
|
+
)
|
|
837
|
+
llm_max_bytes = host_component.get_config(
|
|
838
|
+
"tool_output_llm_return_max_bytes", 4096
|
|
839
|
+
)
|
|
840
|
+
extraction_config = host_component.get_config(
|
|
841
|
+
"extract_content_from_artifact_config", {}
|
|
842
|
+
)
|
|
843
|
+
supported_binary_mime_types = extraction_config.get(
|
|
844
|
+
"supported_binary_mime_types", []
|
|
845
|
+
)
|
|
846
|
+
model_config_for_extraction = extraction_config.get("model")
|
|
847
|
+
except Exception as e:
|
|
848
|
+
log.exception("%s Error retrieving tool configuration: %s", log_identifier, e)
|
|
849
|
+
return {
|
|
850
|
+
"status": "error_internal_configuration",
|
|
851
|
+
"message_to_llm": f"Tool configuration error: {e}",
|
|
852
|
+
"filename": filename,
|
|
853
|
+
"version_requested": str(version),
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
source_artifact_data = None
|
|
857
|
+
processed_version: Union[int, str]
|
|
858
|
+
|
|
859
|
+
if version is None or (
|
|
860
|
+
isinstance(version, str) and version.strip().lower() == "latest"
|
|
861
|
+
):
|
|
862
|
+
processed_version = "latest"
|
|
863
|
+
else:
|
|
864
|
+
try:
|
|
865
|
+
processed_version = int(version)
|
|
866
|
+
except ValueError:
|
|
867
|
+
log.warning(
|
|
868
|
+
"%s Invalid version string: '%s'. Must be an integer or 'latest'.",
|
|
869
|
+
log_identifier,
|
|
870
|
+
version,
|
|
871
|
+
)
|
|
872
|
+
return {
|
|
873
|
+
"status": "error_invalid_version_format",
|
|
874
|
+
"message_to_llm": f"Invalid version format '{version}'. Version must be an integer or 'latest'.",
|
|
875
|
+
"filename": filename,
|
|
876
|
+
"version_requested": str(version),
|
|
877
|
+
}
|
|
878
|
+
try:
|
|
879
|
+
log.debug(
|
|
880
|
+
"%s Loading source artifact '%s' version '%s' (processed as: %s)",
|
|
881
|
+
log_identifier,
|
|
882
|
+
filename,
|
|
883
|
+
version,
|
|
884
|
+
processed_version,
|
|
885
|
+
)
|
|
886
|
+
source_artifact_data = await load_artifact_content_or_metadata(
|
|
887
|
+
artifact_service=inv_context.artifact_service,
|
|
888
|
+
app_name=inv_context.app_name,
|
|
889
|
+
user_id=inv_context.user_id,
|
|
890
|
+
session_id=get_original_session_id(inv_context),
|
|
891
|
+
filename=filename,
|
|
892
|
+
version=processed_version,
|
|
893
|
+
return_raw_bytes=True,
|
|
894
|
+
log_identifier_prefix=log_identifier,
|
|
895
|
+
)
|
|
896
|
+
if source_artifact_data.get("status") != "success":
|
|
897
|
+
raise FileNotFoundError(
|
|
898
|
+
source_artifact_data.get("message", "Failed to load artifact")
|
|
899
|
+
)
|
|
900
|
+
log.info(
|
|
901
|
+
"%s Successfully loaded source artifact '%s' version %s (actual: v%s)",
|
|
902
|
+
log_identifier,
|
|
903
|
+
filename,
|
|
904
|
+
version,
|
|
905
|
+
source_artifact_data.get("version"),
|
|
906
|
+
)
|
|
907
|
+
except FileNotFoundError as e:
|
|
908
|
+
log.warning("%s Source artifact not found: %s", log_identifier, e)
|
|
909
|
+
return {
|
|
910
|
+
"status": "error_artifact_not_found",
|
|
911
|
+
"message_to_llm": f"Could not extract content. Source artifact '{filename}' (version {version}) was not found: {e}",
|
|
912
|
+
"filename": filename,
|
|
913
|
+
"version_requested": str(version),
|
|
914
|
+
}
|
|
915
|
+
except Exception as e:
|
|
916
|
+
log.exception("%s Error loading source artifact: %s", log_identifier, e)
|
|
917
|
+
return {
|
|
918
|
+
"status": "error_loading_artifact",
|
|
919
|
+
"message_to_llm": f"Error loading source artifact '{filename}': {e}",
|
|
920
|
+
"filename": filename,
|
|
921
|
+
"version_requested": str(version),
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
source_artifact_content_bytes = source_artifact_data.get("raw_bytes")
|
|
925
|
+
source_mime_type = source_artifact_data.get("mime_type", "application/octet-stream")
|
|
926
|
+
actual_source_version = source_artifact_data.get("version", "unknown")
|
|
927
|
+
|
|
928
|
+
chosen_llm = None
|
|
929
|
+
try:
|
|
930
|
+
if model_config_for_extraction:
|
|
931
|
+
if isinstance(model_config_for_extraction, str):
|
|
932
|
+
chosen_llm = LLMRegistry.new_llm(model_config_for_extraction)
|
|
933
|
+
log.info(
|
|
934
|
+
"%s Using tool-specific LLM (string): %s",
|
|
935
|
+
log_identifier,
|
|
936
|
+
model_config_for_extraction,
|
|
937
|
+
)
|
|
938
|
+
elif isinstance(model_config_for_extraction, dict):
|
|
939
|
+
chosen_llm = LiteLlm(**model_config_for_extraction)
|
|
940
|
+
log.info(
|
|
941
|
+
"%s Using tool-specific LLM (dict): %s",
|
|
942
|
+
log_identifier,
|
|
943
|
+
model_config_for_extraction.get("model"),
|
|
944
|
+
)
|
|
945
|
+
else:
|
|
946
|
+
log.warning(
|
|
947
|
+
"%s Invalid 'model' config for extraction tool. Falling back to agent default.",
|
|
948
|
+
log_identifier,
|
|
949
|
+
)
|
|
950
|
+
chosen_llm = inv_context.agent.canonical_model
|
|
951
|
+
else:
|
|
952
|
+
chosen_llm = inv_context.agent.canonical_model
|
|
953
|
+
log.info(
|
|
954
|
+
"%s Using agent's default LLM: %s", log_identifier, chosen_llm.model
|
|
955
|
+
)
|
|
956
|
+
except Exception as e:
|
|
957
|
+
log.exception("%s Error initializing LLM for extraction: %s", log_identifier, e)
|
|
958
|
+
return {
|
|
959
|
+
"status": "error_internal_llm_setup",
|
|
960
|
+
"message_to_llm": f"Failed to set up LLM for extraction: {e}",
|
|
961
|
+
"filename": filename,
|
|
962
|
+
"version_requested": str(version),
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
llm_parts = []
|
|
966
|
+
is_binary_supported = False
|
|
967
|
+
|
|
968
|
+
normalized_source_mime_type = source_mime_type.lower() if source_mime_type else ""
|
|
969
|
+
|
|
970
|
+
is_text_based = is_text_based_file(
|
|
971
|
+
mime_type=normalized_source_mime_type,
|
|
972
|
+
content_bytes=source_artifact_content_bytes,
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
if is_text_based:
|
|
976
|
+
try:
|
|
977
|
+
artifact_text_content = source_artifact_content_bytes.decode("utf-8")
|
|
978
|
+
llm_parts.append(
|
|
979
|
+
adk_types.Part(
|
|
980
|
+
text=f"Artifact Content (MIME type: {source_mime_type}):\n```\n{artifact_text_content}\n```"
|
|
981
|
+
)
|
|
982
|
+
)
|
|
983
|
+
log.debug("%s Prepared text content for LLM.", log_identifier)
|
|
984
|
+
except UnicodeDecodeError as e:
|
|
985
|
+
log.warning(
|
|
986
|
+
"%s Failed to decode text artifact as UTF-8: %s. Treating as opaque binary.",
|
|
987
|
+
log_identifier,
|
|
988
|
+
e,
|
|
989
|
+
)
|
|
990
|
+
llm_parts.append(
|
|
991
|
+
adk_types.Part(
|
|
992
|
+
text=f"The artifact '{filename}' is a binary file of type '{source_mime_type}' and could not be decoded as text."
|
|
993
|
+
)
|
|
994
|
+
)
|
|
995
|
+
else: # Binary
|
|
996
|
+
for supported_pattern in supported_binary_mime_types:
|
|
997
|
+
if fnmatch.fnmatch(source_mime_type, supported_pattern):
|
|
998
|
+
is_binary_supported = True
|
|
999
|
+
break
|
|
1000
|
+
if is_binary_supported:
|
|
1001
|
+
llm_parts.append(
|
|
1002
|
+
adk_types.Part(
|
|
1003
|
+
inline_data=adk_types.Blob(
|
|
1004
|
+
mime_type=source_mime_type, data=source_artifact_content_bytes
|
|
1005
|
+
)
|
|
1006
|
+
)
|
|
1007
|
+
)
|
|
1008
|
+
llm_parts.append(
|
|
1009
|
+
adk_types.Part(
|
|
1010
|
+
text=f"The above is the content of artifact '{filename}' (MIME type: {source_mime_type})."
|
|
1011
|
+
)
|
|
1012
|
+
)
|
|
1013
|
+
log.debug(
|
|
1014
|
+
"%s Prepared supported binary content (MIME: %s) for LLM.",
|
|
1015
|
+
log_identifier,
|
|
1016
|
+
source_mime_type,
|
|
1017
|
+
)
|
|
1018
|
+
else:
|
|
1019
|
+
llm_parts.append(
|
|
1020
|
+
adk_types.Part(
|
|
1021
|
+
text=f"The artifact '{filename}' is a binary file of type '{source_mime_type}'. Direct content processing is not supported by this tool's current configuration. Perform the extraction goal based on its filename and type if possible, or state that the content cannot be analyzed."
|
|
1022
|
+
)
|
|
1023
|
+
)
|
|
1024
|
+
log.debug(
|
|
1025
|
+
"%s Prepared message for unsupported binary content (MIME: %s) for LLM.",
|
|
1026
|
+
log_identifier,
|
|
1027
|
+
source_mime_type,
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
internal_llm_contents = [
|
|
1031
|
+
adk_types.Content(
|
|
1032
|
+
role="user", parts=[adk_types.Part(text=extraction_goal)] + llm_parts
|
|
1033
|
+
)
|
|
1034
|
+
]
|
|
1035
|
+
internal_llm_request = LlmRequest(
|
|
1036
|
+
model=chosen_llm.model,
|
|
1037
|
+
contents=internal_llm_contents,
|
|
1038
|
+
config=adk_types.GenerateContentConfig(
|
|
1039
|
+
temperature=0.1,
|
|
1040
|
+
),
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
extracted_content_str = ""
|
|
1044
|
+
try:
|
|
1045
|
+
log.info(
|
|
1046
|
+
"%s Executing internal LLM call for extraction. Goal: %s",
|
|
1047
|
+
log_identifier,
|
|
1048
|
+
extraction_goal,
|
|
1049
|
+
)
|
|
1050
|
+
if hasattr(chosen_llm, "generate_content") and not hasattr(
|
|
1051
|
+
chosen_llm, "generate_content_async"
|
|
1052
|
+
):
|
|
1053
|
+
llm_response = chosen_llm.generate_content(request=internal_llm_request)
|
|
1054
|
+
if llm_response.parts:
|
|
1055
|
+
extracted_content_str = llm_response.parts[0].text or ""
|
|
1056
|
+
else:
|
|
1057
|
+
extracted_content_str = ""
|
|
1058
|
+
elif hasattr(chosen_llm, "generate_content_async"):
|
|
1059
|
+
log.debug(
|
|
1060
|
+
"%s Calling LLM's generate_content_async (non-streaming) for extraction.",
|
|
1061
|
+
log_identifier,
|
|
1062
|
+
)
|
|
1063
|
+
try:
|
|
1064
|
+
llm_response_obj = None
|
|
1065
|
+
async for response_event in chosen_llm.generate_content_async(
|
|
1066
|
+
internal_llm_request
|
|
1067
|
+
):
|
|
1068
|
+
llm_response_obj = response_event
|
|
1069
|
+
break
|
|
1070
|
+
if (
|
|
1071
|
+
llm_response_obj
|
|
1072
|
+
and hasattr(llm_response_obj, "text")
|
|
1073
|
+
and llm_response_obj.text
|
|
1074
|
+
):
|
|
1075
|
+
extracted_content_str = llm_response_obj.text
|
|
1076
|
+
elif (
|
|
1077
|
+
llm_response_obj
|
|
1078
|
+
and hasattr(llm_response_obj, "parts")
|
|
1079
|
+
and llm_response_obj.parts
|
|
1080
|
+
):
|
|
1081
|
+
extracted_content_str = "".join(
|
|
1082
|
+
[
|
|
1083
|
+
part.text
|
|
1084
|
+
for part in llm_response_obj.parts
|
|
1085
|
+
if hasattr(part, "text") and part.text
|
|
1086
|
+
]
|
|
1087
|
+
)
|
|
1088
|
+
elif (
|
|
1089
|
+
llm_response_obj
|
|
1090
|
+
and hasattr(llm_response_obj, "content")
|
|
1091
|
+
and hasattr(llm_response_obj.content, "parts")
|
|
1092
|
+
and llm_response_obj.content.parts
|
|
1093
|
+
):
|
|
1094
|
+
extracted_content_str = "".join(
|
|
1095
|
+
[
|
|
1096
|
+
part.text
|
|
1097
|
+
for part in llm_response_obj.content.parts
|
|
1098
|
+
if hasattr(part, "text") and part.text
|
|
1099
|
+
]
|
|
1100
|
+
)
|
|
1101
|
+
else:
|
|
1102
|
+
extracted_content_str = ""
|
|
1103
|
+
log.warning(
|
|
1104
|
+
"%s LLM response object or its text/parts were not found or empty after non-streaming call.",
|
|
1105
|
+
log_identifier,
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
except Exception as llm_async_err:
|
|
1109
|
+
log.exception(
|
|
1110
|
+
"%s Asynchronous LLM call for extraction failed: %s",
|
|
1111
|
+
log_identifier,
|
|
1112
|
+
llm_async_err,
|
|
1113
|
+
)
|
|
1114
|
+
extracted_content_str = (
|
|
1115
|
+
f"[ERROR: Asynchronous LLM call failed: {llm_async_err}]"
|
|
1116
|
+
)
|
|
1117
|
+
else:
|
|
1118
|
+
log.error(
|
|
1119
|
+
"%s LLM does not have a known generate_content or generate_content_async method. Extraction will be empty.",
|
|
1120
|
+
log_identifier,
|
|
1121
|
+
)
|
|
1122
|
+
extracted_content_str = "[ERROR: LLM method not found]"
|
|
1123
|
+
|
|
1124
|
+
log.info(
|
|
1125
|
+
"%s Internal LLM call completed. Extracted content length: %d chars",
|
|
1126
|
+
log_identifier,
|
|
1127
|
+
len(extracted_content_str),
|
|
1128
|
+
)
|
|
1129
|
+
if not extracted_content_str.strip():
|
|
1130
|
+
log.warning(
|
|
1131
|
+
"%s Internal LLM produced empty or whitespace-only content for extraction goal.",
|
|
1132
|
+
log_identifier,
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
except Exception as e:
|
|
1136
|
+
log.exception(
|
|
1137
|
+
"%s Internal LLM call for extraction failed: %s", log_identifier, e
|
|
1138
|
+
)
|
|
1139
|
+
return {
|
|
1140
|
+
"status": "error_extraction_failed",
|
|
1141
|
+
"message_to_llm": f"The LLM failed to process the artifact content for your goal '{extraction_goal}'. Error: {e}",
|
|
1142
|
+
"filename": filename,
|
|
1143
|
+
"version_requested": str(version),
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
extracted_content_bytes = extracted_content_str.encode("utf-8")
|
|
1147
|
+
extracted_content_size_bytes = len(extracted_content_bytes)
|
|
1148
|
+
output_mime_type = "text/plain"
|
|
1149
|
+
try:
|
|
1150
|
+
json.loads(extracted_content_str)
|
|
1151
|
+
output_mime_type = "application/json"
|
|
1152
|
+
log.debug(
|
|
1153
|
+
"%s Extracted content appears to be valid JSON. Setting output MIME to application/json.",
|
|
1154
|
+
log_identifier,
|
|
1155
|
+
)
|
|
1156
|
+
except json.JSONDecodeError:
|
|
1157
|
+
log.debug(
|
|
1158
|
+
"%s Extracted content is not JSON. Using output MIME text/plain.",
|
|
1159
|
+
log_identifier,
|
|
1160
|
+
)
|
|
1161
|
+
|
|
1162
|
+
response_for_llm_str = extracted_content_str
|
|
1163
|
+
saved_extracted_artifact_details = None
|
|
1164
|
+
final_status = "success"
|
|
1165
|
+
message_to_llm_parts = [
|
|
1166
|
+
f"Successfully extracted content from '{filename}' (v{actual_source_version}) based on your goal: '{extraction_goal}'."
|
|
1167
|
+
]
|
|
1168
|
+
was_saved = False
|
|
1169
|
+
was_truncated = False
|
|
1170
|
+
|
|
1171
|
+
if extracted_content_size_bytes > save_threshold:
|
|
1172
|
+
log.info(
|
|
1173
|
+
"%s Extracted content size (%d bytes) exceeds save threshold (%d bytes). Saving as new artifact.",
|
|
1174
|
+
log_identifier,
|
|
1175
|
+
extracted_content_size_bytes,
|
|
1176
|
+
save_threshold,
|
|
1177
|
+
)
|
|
1178
|
+
saved_extracted_artifact_details = await _save_extracted_artifact(
|
|
1179
|
+
tool_context,
|
|
1180
|
+
host_component,
|
|
1181
|
+
extracted_content_bytes,
|
|
1182
|
+
filename,
|
|
1183
|
+
actual_source_version,
|
|
1184
|
+
extraction_goal,
|
|
1185
|
+
output_filename_base,
|
|
1186
|
+
output_mime_type,
|
|
1187
|
+
)
|
|
1188
|
+
if saved_extracted_artifact_details.get("status") == "success":
|
|
1189
|
+
was_saved = True
|
|
1190
|
+
message_to_llm_parts.append(
|
|
1191
|
+
f"The full extracted content was saved as artifact '{saved_extracted_artifact_details.get('data_filename')}' "
|
|
1192
|
+
f"(version {saved_extracted_artifact_details.get('data_version')}). "
|
|
1193
|
+
f"You can retrieve it using 'load_artifact' or perform further extractions on it using 'extract_content_from_artifact' "
|
|
1194
|
+
f"with this new filename and version."
|
|
1195
|
+
)
|
|
1196
|
+
else:
|
|
1197
|
+
message_to_llm_parts.append(
|
|
1198
|
+
f"Attempted to save the large extracted content, but failed: {saved_extracted_artifact_details.get('message')}"
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
if len(extracted_content_str.encode("utf-8")) > llm_max_bytes:
|
|
1202
|
+
was_truncated = True
|
|
1203
|
+
log.info(
|
|
1204
|
+
"%s Original extracted content (%d bytes) exceeds LLM return max bytes (%d bytes). Truncating for LLM response.",
|
|
1205
|
+
log_identifier,
|
|
1206
|
+
len(extracted_content_str.encode("utf-8")),
|
|
1207
|
+
llm_max_bytes,
|
|
1208
|
+
)
|
|
1209
|
+
|
|
1210
|
+
if not was_saved:
|
|
1211
|
+
log.info(
|
|
1212
|
+
"%s Saving extracted content now because it needs truncation for LLM response and wasn't saved previously.",
|
|
1213
|
+
log_identifier,
|
|
1214
|
+
)
|
|
1215
|
+
saved_extracted_artifact_details = await _save_extracted_artifact(
|
|
1216
|
+
tool_context,
|
|
1217
|
+
host_component,
|
|
1218
|
+
extracted_content_bytes,
|
|
1219
|
+
filename,
|
|
1220
|
+
actual_source_version,
|
|
1221
|
+
extraction_goal,
|
|
1222
|
+
output_filename_base,
|
|
1223
|
+
output_mime_type,
|
|
1224
|
+
)
|
|
1225
|
+
if saved_extracted_artifact_details.get("status") == "success":
|
|
1226
|
+
was_saved = True
|
|
1227
|
+
message_to_llm_parts.append(
|
|
1228
|
+
f"The full extracted content (which is being truncated for this response) was saved as artifact "
|
|
1229
|
+
f"'{saved_extracted_artifact_details.get('data_filename')}' (version {saved_extracted_artifact_details.get('data_version')}). "
|
|
1230
|
+
f"You can retrieve the full content using 'load_artifact' or perform further extractions on it."
|
|
1231
|
+
)
|
|
1232
|
+
else:
|
|
1233
|
+
message_to_llm_parts.append(
|
|
1234
|
+
f"Attempted to save the extracted content before truncation, but failed: {saved_extracted_artifact_details.get('message')}"
|
|
1235
|
+
)
|
|
1236
|
+
|
|
1237
|
+
truncation_suffix = "... [Content truncated]"
|
|
1238
|
+
adjusted_max_bytes = llm_max_bytes - len(truncation_suffix.encode("utf-8"))
|
|
1239
|
+
if adjusted_max_bytes < 0:
|
|
1240
|
+
adjusted_max_bytes = 0
|
|
1241
|
+
|
|
1242
|
+
temp_response_bytes = extracted_content_str.encode("utf-8")
|
|
1243
|
+
truncated_bytes = temp_response_bytes[:adjusted_max_bytes]
|
|
1244
|
+
response_for_llm_str = (
|
|
1245
|
+
truncated_bytes.decode("utf-8", "ignore") + truncation_suffix
|
|
1246
|
+
)
|
|
1247
|
+
|
|
1248
|
+
message_to_llm_parts.append(
|
|
1249
|
+
"The extracted content provided in 'extracted_data_preview' has been truncated due to size limits. "
|
|
1250
|
+
"If saved, the full version is available in the specified artifact."
|
|
1251
|
+
)
|
|
1252
|
+
|
|
1253
|
+
if was_saved and was_truncated:
|
|
1254
|
+
final_status = "success_full_content_saved_preview_returned"
|
|
1255
|
+
elif was_saved:
|
|
1256
|
+
final_status = "success_full_content_saved_and_returned"
|
|
1257
|
+
elif was_truncated:
|
|
1258
|
+
final_status = "success_content_returned_truncated_and_saved"
|
|
1259
|
+
else:
|
|
1260
|
+
final_status = "success_content_returned"
|
|
1261
|
+
|
|
1262
|
+
final_response_dict = {
|
|
1263
|
+
"status": final_status,
|
|
1264
|
+
"message_to_llm": " ".join(list(dict.fromkeys(message_to_llm_parts))),
|
|
1265
|
+
"source_filename": filename,
|
|
1266
|
+
"source_version_processed": actual_source_version,
|
|
1267
|
+
"extraction_goal_used": extraction_goal,
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
if was_truncated:
|
|
1271
|
+
final_response_dict["extracted_data_preview"] = response_for_llm_str
|
|
1272
|
+
else:
|
|
1273
|
+
final_response_dict["extracted_data"] = response_for_llm_str
|
|
1274
|
+
|
|
1275
|
+
if (
|
|
1276
|
+
saved_extracted_artifact_details
|
|
1277
|
+
and saved_extracted_artifact_details.get("status") == "success"
|
|
1278
|
+
):
|
|
1279
|
+
final_response_dict["saved_extracted_artifact_details"] = (
|
|
1280
|
+
saved_extracted_artifact_details
|
|
1281
|
+
)
|
|
1282
|
+
elif saved_extracted_artifact_details:
|
|
1283
|
+
final_response_dict["saved_extracted_artifact_attempt_details"] = (
|
|
1284
|
+
saved_extracted_artifact_details
|
|
1285
|
+
)
|
|
1286
|
+
|
|
1287
|
+
log.info(
|
|
1288
|
+
"%s Tool execution finished. Final status: %s. Response preview: %s",
|
|
1289
|
+
log_identifier,
|
|
1290
|
+
final_status,
|
|
1291
|
+
final_response_dict,
|
|
1292
|
+
)
|
|
1293
|
+
return final_response_dict
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
async def append_to_artifact(
|
|
1297
|
+
filename: str,
|
|
1298
|
+
content_chunk: str,
|
|
1299
|
+
mime_type: str,
|
|
1300
|
+
tool_context: ToolContext = None,
|
|
1301
|
+
) -> Dict[str, Any]:
|
|
1302
|
+
"""
|
|
1303
|
+
Appends a chunk of content to an existing artifact. This operation will
|
|
1304
|
+
create a new version of the artifact. The content_chunk should be a string,
|
|
1305
|
+
potentially base64 encoded if it represents binary data (indicated by mime_type).
|
|
1306
|
+
The chunk size should be limited (e.g., max 3KB) by the LLM.
|
|
1307
|
+
|
|
1308
|
+
Args:
|
|
1309
|
+
filename: The name of the artifact to append to. May contain embeds.
|
|
1310
|
+
content_chunk: The chunk of content to append (max approx. 3KB).
|
|
1311
|
+
If mime_type suggests binary, this should be base64 encoded.
|
|
1312
|
+
May contain embeds.
|
|
1313
|
+
mime_type: The MIME type of the content_chunk. This helps determine if
|
|
1314
|
+
base64 decoding is needed for the chunk. The overall artifact's
|
|
1315
|
+
MIME type will be preserved from its latest version.
|
|
1316
|
+
May contain embeds.
|
|
1317
|
+
tool_context: The context provided by the ADK framework.
|
|
1318
|
+
|
|
1319
|
+
Returns:
|
|
1320
|
+
A dictionary indicating the result, including the new version of the artifact.
|
|
1321
|
+
"""
|
|
1322
|
+
if not tool_context:
|
|
1323
|
+
return {
|
|
1324
|
+
"status": "error",
|
|
1325
|
+
"filename": filename,
|
|
1326
|
+
"message": "ToolContext is missing, cannot append to artifact.",
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
log_identifier = f"[BuiltinArtifactTool:append_to_artifact:{filename}]"
|
|
1330
|
+
log.debug("%s Processing request to append chunk.", log_identifier)
|
|
1331
|
+
|
|
1332
|
+
try:
|
|
1333
|
+
inv_context = tool_context._invocation_context
|
|
1334
|
+
artifact_service = inv_context.artifact_service
|
|
1335
|
+
if not artifact_service:
|
|
1336
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
1337
|
+
|
|
1338
|
+
app_name = inv_context.app_name
|
|
1339
|
+
user_id = inv_context.user_id
|
|
1340
|
+
session_id = get_original_session_id(inv_context)
|
|
1341
|
+
host_component = getattr(inv_context.agent, "host_component", None)
|
|
1342
|
+
|
|
1343
|
+
log.debug(
|
|
1344
|
+
"%s Loading latest version of artifact '%s' content to append to.",
|
|
1345
|
+
log_identifier,
|
|
1346
|
+
filename,
|
|
1347
|
+
)
|
|
1348
|
+
content_load_result = await load_artifact_content_or_metadata(
|
|
1349
|
+
artifact_service=artifact_service,
|
|
1350
|
+
app_name=app_name,
|
|
1351
|
+
user_id=user_id,
|
|
1352
|
+
session_id=session_id,
|
|
1353
|
+
filename=filename,
|
|
1354
|
+
version="latest",
|
|
1355
|
+
load_metadata_only=False,
|
|
1356
|
+
return_raw_bytes=True,
|
|
1357
|
+
component=host_component,
|
|
1358
|
+
log_identifier_prefix=f"{log_identifier}[LoadOriginalContent]",
|
|
1359
|
+
)
|
|
1360
|
+
|
|
1361
|
+
if content_load_result.get("status") != "success":
|
|
1362
|
+
log.error(
|
|
1363
|
+
"%s Failed to load original artifact content '%s': %s",
|
|
1364
|
+
log_identifier,
|
|
1365
|
+
filename,
|
|
1366
|
+
content_load_result.get("message"),
|
|
1367
|
+
)
|
|
1368
|
+
return {
|
|
1369
|
+
"status": "error",
|
|
1370
|
+
"filename": filename,
|
|
1371
|
+
"message": f"Failed to load original artifact content to append to: {content_load_result.get('message')}",
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
original_artifact_bytes = content_load_result.get("raw_bytes", b"")
|
|
1375
|
+
original_mime_type = content_load_result.get(
|
|
1376
|
+
"mime_type", "application/octet-stream"
|
|
1377
|
+
)
|
|
1378
|
+
original_version_loaded = content_load_result.get("version", "unknown")
|
|
1379
|
+
log.info(
|
|
1380
|
+
"%s Loaded original artifact content '%s' v%s, type: %s, size: %d bytes.",
|
|
1381
|
+
log_identifier,
|
|
1382
|
+
filename,
|
|
1383
|
+
original_version_loaded,
|
|
1384
|
+
original_mime_type,
|
|
1385
|
+
len(original_artifact_bytes),
|
|
1386
|
+
)
|
|
1387
|
+
|
|
1388
|
+
log.debug(
|
|
1389
|
+
"%s Loading latest version of artifact '%s' metadata.",
|
|
1390
|
+
log_identifier,
|
|
1391
|
+
filename,
|
|
1392
|
+
)
|
|
1393
|
+
metadata_load_result = await load_artifact_content_or_metadata(
|
|
1394
|
+
artifact_service=artifact_service,
|
|
1395
|
+
app_name=app_name,
|
|
1396
|
+
user_id=user_id,
|
|
1397
|
+
session_id=session_id,
|
|
1398
|
+
filename=filename,
|
|
1399
|
+
version="latest",
|
|
1400
|
+
load_metadata_only=True,
|
|
1401
|
+
component=host_component,
|
|
1402
|
+
log_identifier_prefix=f"{log_identifier}[LoadOriginalMetadata]",
|
|
1403
|
+
)
|
|
1404
|
+
original_metadata_dict = {}
|
|
1405
|
+
if metadata_load_result.get("status") == "success":
|
|
1406
|
+
original_metadata_dict = metadata_load_result.get("metadata", {})
|
|
1407
|
+
log.info(
|
|
1408
|
+
"%s Loaded original artifact metadata for '%s' v%s.",
|
|
1409
|
+
log_identifier,
|
|
1410
|
+
filename,
|
|
1411
|
+
metadata_load_result.get("version", "unknown"),
|
|
1412
|
+
)
|
|
1413
|
+
else:
|
|
1414
|
+
log.warning(
|
|
1415
|
+
"%s Failed to load original artifact metadata for '%s': %s. Proceeding with minimal metadata.",
|
|
1416
|
+
log_identifier,
|
|
1417
|
+
filename,
|
|
1418
|
+
metadata_load_result.get("message"),
|
|
1419
|
+
)
|
|
1420
|
+
|
|
1421
|
+
chunk_bytes, _ = decode_and_get_bytes(
|
|
1422
|
+
content_chunk, mime_type, f"{log_identifier}[DecodeChunk]"
|
|
1423
|
+
)
|
|
1424
|
+
log.debug(
|
|
1425
|
+
"%s Decoded content_chunk (declared type: %s) to %d bytes.",
|
|
1426
|
+
log_identifier,
|
|
1427
|
+
mime_type,
|
|
1428
|
+
len(chunk_bytes),
|
|
1429
|
+
)
|
|
1430
|
+
|
|
1431
|
+
combined_bytes = original_artifact_bytes + chunk_bytes
|
|
1432
|
+
log.debug(
|
|
1433
|
+
"%s Appended chunk. New total size: %d bytes.",
|
|
1434
|
+
log_identifier,
|
|
1435
|
+
len(combined_bytes),
|
|
1436
|
+
)
|
|
1437
|
+
|
|
1438
|
+
new_metadata_for_save = {
|
|
1439
|
+
key: value
|
|
1440
|
+
for key, value in original_metadata_dict.items()
|
|
1441
|
+
if key
|
|
1442
|
+
not in [
|
|
1443
|
+
"filename",
|
|
1444
|
+
"mime_type",
|
|
1445
|
+
"size_bytes",
|
|
1446
|
+
"timestamp_utc",
|
|
1447
|
+
"schema",
|
|
1448
|
+
"version",
|
|
1449
|
+
]
|
|
1450
|
+
}
|
|
1451
|
+
new_metadata_for_save["appended_from_version"] = original_version_loaded
|
|
1452
|
+
new_metadata_for_save["appended_chunk_declared_mime_type"] = mime_type
|
|
1453
|
+
|
|
1454
|
+
schema_max_keys = (
|
|
1455
|
+
host_component.get_config("schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS)
|
|
1456
|
+
if host_component
|
|
1457
|
+
else DEFAULT_SCHEMA_MAX_KEYS
|
|
1458
|
+
)
|
|
1459
|
+
|
|
1460
|
+
save_result = await save_artifact_with_metadata(
|
|
1461
|
+
artifact_service=artifact_service,
|
|
1462
|
+
app_name=app_name,
|
|
1463
|
+
user_id=user_id,
|
|
1464
|
+
session_id=session_id,
|
|
1465
|
+
filename=filename,
|
|
1466
|
+
content_bytes=combined_bytes,
|
|
1467
|
+
mime_type=original_mime_type,
|
|
1468
|
+
metadata_dict=new_metadata_for_save,
|
|
1469
|
+
timestamp=datetime.now(timezone.utc),
|
|
1470
|
+
schema_max_keys=schema_max_keys,
|
|
1471
|
+
tool_context=tool_context,
|
|
1472
|
+
)
|
|
1473
|
+
|
|
1474
|
+
log.info(
|
|
1475
|
+
"%s Result from save_artifact_with_metadata after append: %s",
|
|
1476
|
+
log_identifier,
|
|
1477
|
+
save_result,
|
|
1478
|
+
)
|
|
1479
|
+
|
|
1480
|
+
if save_result.get("status") == "error":
|
|
1481
|
+
raise IOError(
|
|
1482
|
+
f"Failed to save appended artifact: {save_result.get('message', 'Unknown error')}"
|
|
1483
|
+
)
|
|
1484
|
+
|
|
1485
|
+
return {
|
|
1486
|
+
"status": "success",
|
|
1487
|
+
"filename": filename,
|
|
1488
|
+
"new_version": save_result.get("data_version"),
|
|
1489
|
+
"total_size_bytes": len(combined_bytes),
|
|
1490
|
+
"message": f"Chunk appended to '{filename}'. New version is {save_result.get('data_version')} with total size {len(combined_bytes)} bytes.",
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
except FileNotFoundError as e:
|
|
1494
|
+
log.warning("%s Original artifact not found for append: %s", log_identifier, e)
|
|
1495
|
+
return {
|
|
1496
|
+
"status": "error",
|
|
1497
|
+
"filename": filename,
|
|
1498
|
+
"message": f"Original artifact '{filename}' not found: {e}",
|
|
1499
|
+
}
|
|
1500
|
+
except ValueError as e:
|
|
1501
|
+
log.warning("%s Value error during append: %s", log_identifier, e)
|
|
1502
|
+
return {"status": "error", "filename": filename, "message": str(e)}
|
|
1503
|
+
except IOError as e:
|
|
1504
|
+
log.warning("%s IO error during append: %s", log_identifier, e)
|
|
1505
|
+
return {"status": "error", "filename": filename, "message": str(e)}
|
|
1506
|
+
except Exception as e:
|
|
1507
|
+
log.exception(
|
|
1508
|
+
"%s Unexpected error appending to artifact '%s': %s",
|
|
1509
|
+
log_identifier,
|
|
1510
|
+
filename,
|
|
1511
|
+
e,
|
|
1512
|
+
)
|
|
1513
|
+
return {
|
|
1514
|
+
"status": "error",
|
|
1515
|
+
"filename": filename,
|
|
1516
|
+
"message": f"Failed to append to artifact: {e}",
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
|
|
1520
|
+
async def _save_extracted_artifact(
|
|
1521
|
+
tool_context: ToolContext,
|
|
1522
|
+
host_component: Any,
|
|
1523
|
+
extracted_content_bytes: bytes,
|
|
1524
|
+
source_artifact_filename: str,
|
|
1525
|
+
source_artifact_version: Union[int, str],
|
|
1526
|
+
extraction_goal: str,
|
|
1527
|
+
output_filename_base: Optional[str],
|
|
1528
|
+
output_mime_type: str,
|
|
1529
|
+
) -> Dict[str, Any]:
|
|
1530
|
+
"""
|
|
1531
|
+
Saves the extracted content as a new artifact with comprehensive metadata.
|
|
1532
|
+
|
|
1533
|
+
Args:
|
|
1534
|
+
tool_context: The ADK ToolContext.
|
|
1535
|
+
host_component: The A2A_ADK_HostComponent instance for accessing config and services.
|
|
1536
|
+
extracted_content_bytes: The raw byte content of the extracted data.
|
|
1537
|
+
source_artifact_filename: The filename of the original artifact.
|
|
1538
|
+
source_artifact_version: The version of the original artifact.
|
|
1539
|
+
extraction_goal: The natural language goal used for extraction.
|
|
1540
|
+
output_filename_base: Optional base for the new artifact's filename.
|
|
1541
|
+
output_mime_type: The MIME type of the extracted content.
|
|
1542
|
+
|
|
1543
|
+
Returns:
|
|
1544
|
+
A dictionary containing details of the saved artifact, as returned by
|
|
1545
|
+
`save_artifact_with_metadata`.
|
|
1546
|
+
"""
|
|
1547
|
+
log_identifier = f"[BuiltinArtifactTool:_save_extracted_artifact]"
|
|
1548
|
+
log.debug("%s Saving extracted content...", log_identifier)
|
|
1549
|
+
|
|
1550
|
+
try:
|
|
1551
|
+
base_name = output_filename_base or f"{source_artifact_filename}_extracted"
|
|
1552
|
+
base_name_sanitized = re.sub(r'[<>:"/\\|?*\s]+', "_", base_name)
|
|
1553
|
+
base_name_sanitized = base_name_sanitized.strip("_")
|
|
1554
|
+
|
|
1555
|
+
suffix = uuid.uuid4().hex[:8]
|
|
1556
|
+
extension_map = {
|
|
1557
|
+
"text/plain": ".txt",
|
|
1558
|
+
"application/json": ".json",
|
|
1559
|
+
"text/csv": ".csv",
|
|
1560
|
+
"text/html": ".html",
|
|
1561
|
+
"image/png": ".png",
|
|
1562
|
+
"image/jpeg": ".jpg",
|
|
1563
|
+
"application/pdf": ".pdf",
|
|
1564
|
+
}
|
|
1565
|
+
ext = extension_map.get(output_mime_type.lower(), ".dat")
|
|
1566
|
+
filename = f"{base_name_sanitized}_{suffix}{ext}"
|
|
1567
|
+
log.debug("%s Generated output filename: %s", log_identifier, filename)
|
|
1568
|
+
|
|
1569
|
+
timestamp = datetime.now(timezone.utc)
|
|
1570
|
+
metadata_for_saving = {
|
|
1571
|
+
"description": f"Content extracted/transformed from artifact '{source_artifact_filename}' (version {source_artifact_version}) using goal: '{extraction_goal}'.",
|
|
1572
|
+
"source_artifact_filename": source_artifact_filename,
|
|
1573
|
+
"source_artifact_version": source_artifact_version,
|
|
1574
|
+
"extraction_goal_used": extraction_goal,
|
|
1575
|
+
}
|
|
1576
|
+
log.debug(
|
|
1577
|
+
"%s Prepared metadata for saving: %s", log_identifier, metadata_for_saving
|
|
1578
|
+
)
|
|
1579
|
+
|
|
1580
|
+
inv_context = tool_context._invocation_context
|
|
1581
|
+
artifact_service = inv_context.artifact_service
|
|
1582
|
+
if not artifact_service:
|
|
1583
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
1584
|
+
|
|
1585
|
+
app_name = inv_context.app_name
|
|
1586
|
+
user_id = inv_context.user_id
|
|
1587
|
+
session_id = get_original_session_id(inv_context)
|
|
1588
|
+
schema_max_keys = host_component.get_config(
|
|
1589
|
+
"schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS
|
|
1590
|
+
)
|
|
1591
|
+
|
|
1592
|
+
log.debug(
|
|
1593
|
+
"%s Calling save_artifact_with_metadata for '%s' (app: %s, user: %s, session: %s, schema_keys: %d)",
|
|
1594
|
+
log_identifier,
|
|
1595
|
+
filename,
|
|
1596
|
+
app_name,
|
|
1597
|
+
user_id,
|
|
1598
|
+
session_id,
|
|
1599
|
+
schema_max_keys,
|
|
1600
|
+
)
|
|
1601
|
+
|
|
1602
|
+
save_result = await save_artifact_with_metadata(
|
|
1603
|
+
artifact_service=artifact_service,
|
|
1604
|
+
app_name=app_name,
|
|
1605
|
+
user_id=user_id,
|
|
1606
|
+
session_id=session_id,
|
|
1607
|
+
filename=filename,
|
|
1608
|
+
content_bytes=extracted_content_bytes,
|
|
1609
|
+
mime_type=output_mime_type,
|
|
1610
|
+
metadata_dict=metadata_for_saving,
|
|
1611
|
+
timestamp=timestamp,
|
|
1612
|
+
schema_max_keys=schema_max_keys,
|
|
1613
|
+
tool_context=tool_context,
|
|
1614
|
+
)
|
|
1615
|
+
|
|
1616
|
+
log.info(
|
|
1617
|
+
"%s Extracted content saved as artifact '%s' (version %s). Result: %s",
|
|
1618
|
+
log_identifier,
|
|
1619
|
+
save_result.get("data_filename", filename),
|
|
1620
|
+
save_result.get("data_version", "N/A"),
|
|
1621
|
+
save_result.get("status"),
|
|
1622
|
+
)
|
|
1623
|
+
return save_result
|
|
1624
|
+
|
|
1625
|
+
except Exception as e:
|
|
1626
|
+
log.exception(
|
|
1627
|
+
"%s Error in _save_extracted_artifact for source '%s': %s",
|
|
1628
|
+
log_identifier,
|
|
1629
|
+
source_artifact_filename,
|
|
1630
|
+
e,
|
|
1631
|
+
)
|
|
1632
|
+
return {
|
|
1633
|
+
"status": "error",
|
|
1634
|
+
"data_filename": filename if "filename" in locals() else "unknown_filename",
|
|
1635
|
+
"message": f"Failed to save extracted content as artifact: {e}",
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
|
|
1639
|
+
async def _notify_artifact_save(
|
|
1640
|
+
filename: str,
|
|
1641
|
+
version: int,
|
|
1642
|
+
status: str,
|
|
1643
|
+
tool_context: ToolContext = None, # Keep tool_context for signature consistency
|
|
1644
|
+
) -> Dict[str, Any]:
|
|
1645
|
+
"""
|
|
1646
|
+
An internal tool used by the system to confirm that a fenced artifact block
|
|
1647
|
+
has been successfully saved. It performs no actions and simply returns its
|
|
1648
|
+
arguments to get the result into the ADK history.
|
|
1649
|
+
"""
|
|
1650
|
+
return {"filename": filename, "version": version, "status": status}
|
|
1651
|
+
|
|
1652
|
+
|
|
1653
|
+
_notify_artifact_save_tool_def = BuiltinTool(
|
|
1654
|
+
name="_notify_artifact_save",
|
|
1655
|
+
implementation=_notify_artifact_save,
|
|
1656
|
+
description="INTERNAL TOOL. This tool is used by the system to confirm that a fenced artifact block has been saved. You MUST NOT call this tool directly.",
|
|
1657
|
+
category="internal",
|
|
1658
|
+
required_scopes=[], # No scopes needed for an internal notification tool
|
|
1659
|
+
parameters=adk_types.Schema(
|
|
1660
|
+
type=adk_types.Type.OBJECT,
|
|
1661
|
+
properties={
|
|
1662
|
+
"filename": adk_types.Schema(
|
|
1663
|
+
type=adk_types.Type.STRING,
|
|
1664
|
+
description="The name of the artifact that was saved.",
|
|
1665
|
+
),
|
|
1666
|
+
"version": adk_types.Schema(
|
|
1667
|
+
type=adk_types.Type.INTEGER,
|
|
1668
|
+
description="The version number of the saved artifact.",
|
|
1669
|
+
),
|
|
1670
|
+
"status": adk_types.Schema(
|
|
1671
|
+
type=adk_types.Type.STRING,
|
|
1672
|
+
description="The status of the save operation.",
|
|
1673
|
+
),
|
|
1674
|
+
},
|
|
1675
|
+
required=["filename", "version", "status"],
|
|
1676
|
+
),
|
|
1677
|
+
examples=[],
|
|
1678
|
+
)
|
|
1679
|
+
|
|
1680
|
+
append_to_artifact_tool_def = BuiltinTool(
|
|
1681
|
+
name="append_to_artifact",
|
|
1682
|
+
implementation=append_to_artifact,
|
|
1683
|
+
description="Appends a chunk of content to an existing artifact. This operation will create a new version of the artifact. The content_chunk should be a string, potentially base64 encoded if it represents binary data (indicated by mime_type). The chunk size should be limited (e.g., max 3KB) by the LLM.",
|
|
1684
|
+
category="artifact_management",
|
|
1685
|
+
required_scopes=["tool:artifact:append"],
|
|
1686
|
+
parameters=adk_types.Schema(
|
|
1687
|
+
type=adk_types.Type.OBJECT,
|
|
1688
|
+
properties={
|
|
1689
|
+
"filename": adk_types.Schema(
|
|
1690
|
+
type=adk_types.Type.STRING,
|
|
1691
|
+
description="The name of the artifact to append to. May contain embeds.",
|
|
1692
|
+
),
|
|
1693
|
+
"content_chunk": adk_types.Schema(
|
|
1694
|
+
type=adk_types.Type.STRING,
|
|
1695
|
+
description="The chunk of content to append (max approx. 3KB). If mime_type suggests binary, this should be base64 encoded. May contain embeds.",
|
|
1696
|
+
),
|
|
1697
|
+
"mime_type": adk_types.Schema(
|
|
1698
|
+
type=adk_types.Type.STRING,
|
|
1699
|
+
description="The MIME type of the content_chunk. This helps determine if base64 decoding is needed for the chunk. The overall artifact's MIME type will be preserved from its latest version. May contain embeds.",
|
|
1700
|
+
),
|
|
1701
|
+
},
|
|
1702
|
+
required=["filename", "content_chunk", "mime_type"],
|
|
1703
|
+
),
|
|
1704
|
+
examples=[],
|
|
1705
|
+
)
|
|
1706
|
+
|
|
1707
|
+
list_artifacts_tool_def = BuiltinTool(
|
|
1708
|
+
name="list_artifacts",
|
|
1709
|
+
implementation=list_artifacts,
|
|
1710
|
+
description="Lists all available data artifact filenames and their versions for the current session. Includes a summary of the latest version's metadata for each artifact.",
|
|
1711
|
+
category="artifact_management",
|
|
1712
|
+
required_scopes=["tool:artifact:list"],
|
|
1713
|
+
parameters=adk_types.Schema(
|
|
1714
|
+
type=adk_types.Type.OBJECT,
|
|
1715
|
+
properties={},
|
|
1716
|
+
required=[],
|
|
1717
|
+
),
|
|
1718
|
+
examples=[],
|
|
1719
|
+
)
|
|
1720
|
+
|
|
1721
|
+
load_artifact_tool_def = BuiltinTool(
|
|
1722
|
+
name="load_artifact",
|
|
1723
|
+
implementation=load_artifact,
|
|
1724
|
+
description="Loads the content or metadata of a specific artifact version. If load_metadata_only is True, loads the full metadata dictionary. Otherwise, loads text content (potentially truncated) or a summary for binary types.",
|
|
1725
|
+
category="artifact_management",
|
|
1726
|
+
required_scopes=["tool:artifact:load"],
|
|
1727
|
+
parameters=adk_types.Schema(
|
|
1728
|
+
type=adk_types.Type.OBJECT,
|
|
1729
|
+
properties={
|
|
1730
|
+
"filename": adk_types.Schema(
|
|
1731
|
+
type=adk_types.Type.STRING,
|
|
1732
|
+
description="The name of the artifact to load. May contain embeds.",
|
|
1733
|
+
),
|
|
1734
|
+
"version": adk_types.Schema(
|
|
1735
|
+
type=adk_types.Type.INTEGER,
|
|
1736
|
+
description="The specific version number to load. Must be explicitly provided.",
|
|
1737
|
+
),
|
|
1738
|
+
"load_metadata_only": adk_types.Schema(
|
|
1739
|
+
type=adk_types.Type.BOOLEAN,
|
|
1740
|
+
description="If True, load only the metadata JSON. Default False.",
|
|
1741
|
+
nullable=True,
|
|
1742
|
+
),
|
|
1743
|
+
"max_content_length": adk_types.Schema(
|
|
1744
|
+
type=adk_types.Type.INTEGER,
|
|
1745
|
+
description="Optional. Maximum character length for text content. If None, uses app configuration. Range: 100-100,000.",
|
|
1746
|
+
nullable=True,
|
|
1747
|
+
),
|
|
1748
|
+
},
|
|
1749
|
+
required=["filename", "version"],
|
|
1750
|
+
),
|
|
1751
|
+
examples=[],
|
|
1752
|
+
)
|
|
1753
|
+
|
|
1754
|
+
signal_artifact_for_return_tool_def = BuiltinTool(
|
|
1755
|
+
name="signal_artifact_for_return",
|
|
1756
|
+
implementation=signal_artifact_for_return,
|
|
1757
|
+
description="Signals the host component to return a specific artifact version to the original caller of the task. This tool does not load the artifact content itself; it just flags it for return.",
|
|
1758
|
+
category="artifact_management",
|
|
1759
|
+
required_scopes=["tool:artifact:signal_return"],
|
|
1760
|
+
parameters=adk_types.Schema(
|
|
1761
|
+
type=adk_types.Type.OBJECT,
|
|
1762
|
+
properties={
|
|
1763
|
+
"filename": adk_types.Schema(
|
|
1764
|
+
type=adk_types.Type.STRING,
|
|
1765
|
+
description="The name of the artifact to return. May contain embeds.",
|
|
1766
|
+
),
|
|
1767
|
+
"version": adk_types.Schema(
|
|
1768
|
+
type=adk_types.Type.INTEGER,
|
|
1769
|
+
description="The specific version number to return. Use list_artifacts() first to find available versions.",
|
|
1770
|
+
),
|
|
1771
|
+
},
|
|
1772
|
+
required=["filename", "version"],
|
|
1773
|
+
),
|
|
1774
|
+
examples=[],
|
|
1775
|
+
)
|
|
1776
|
+
|
|
1777
|
+
apply_embed_and_create_artifact_tool_def = BuiltinTool(
|
|
1778
|
+
name="apply_embed_and_create_artifact",
|
|
1779
|
+
implementation=apply_embed_and_create_artifact,
|
|
1780
|
+
description="Resolves an 'artifact_content' embed directive (including modifiers and formatting) and saves the resulting content as a new artifact. The entire embed directive must be provided as a string.",
|
|
1781
|
+
category="artifact_management",
|
|
1782
|
+
required_scopes=["tool:artifact:create", "tool:artifact:load"],
|
|
1783
|
+
parameters=adk_types.Schema(
|
|
1784
|
+
type=adk_types.Type.OBJECT,
|
|
1785
|
+
properties={
|
|
1786
|
+
"output_filename": adk_types.Schema(
|
|
1787
|
+
type=adk_types.Type.STRING,
|
|
1788
|
+
description="The desired name for the new artifact.",
|
|
1789
|
+
),
|
|
1790
|
+
"embed_directive": adk_types.Schema(
|
|
1791
|
+
type=adk_types.Type.STRING,
|
|
1792
|
+
description="The full '«artifact_content:...>>>...>>>format:...»' string.",
|
|
1793
|
+
),
|
|
1794
|
+
"output_metadata": adk_types.Schema(
|
|
1795
|
+
type=adk_types.Type.OBJECT,
|
|
1796
|
+
description="Optional metadata for the new artifact.",
|
|
1797
|
+
nullable=True,
|
|
1798
|
+
),
|
|
1799
|
+
},
|
|
1800
|
+
required=["output_filename", "embed_directive"],
|
|
1801
|
+
),
|
|
1802
|
+
raw_string_args=["embed_directive"],
|
|
1803
|
+
examples=[],
|
|
1804
|
+
)
|
|
1805
|
+
|
|
1806
|
+
extract_content_from_artifact_tool_def = BuiltinTool(
|
|
1807
|
+
name="extract_content_from_artifact",
|
|
1808
|
+
implementation=extract_content_from_artifact,
|
|
1809
|
+
description="Loads an existing artifact, uses an internal LLM to process its content based on an 'extraction_goal,' and manages the output by returning it or saving it as a new artifact.",
|
|
1810
|
+
category="artifact_management",
|
|
1811
|
+
required_scopes=["tool:artifact:load", "tool:artifact:create"],
|
|
1812
|
+
parameters=adk_types.Schema(
|
|
1813
|
+
type=adk_types.Type.OBJECT,
|
|
1814
|
+
properties={
|
|
1815
|
+
"filename": adk_types.Schema(
|
|
1816
|
+
type=adk_types.Type.STRING,
|
|
1817
|
+
description="Name of the source artifact. May contain embeds.",
|
|
1818
|
+
),
|
|
1819
|
+
"extraction_goal": adk_types.Schema(
|
|
1820
|
+
type=adk_types.Type.STRING,
|
|
1821
|
+
description="Natural language instruction for the LLM on what to extract or how to transform the content. May contain embeds.",
|
|
1822
|
+
),
|
|
1823
|
+
"version": adk_types.Schema(
|
|
1824
|
+
type=adk_types.Type.STRING,
|
|
1825
|
+
description="Version of the source artifact. Can be an integer or 'latest'. Defaults to 'latest'. May contain embeds.",
|
|
1826
|
+
nullable=True,
|
|
1827
|
+
),
|
|
1828
|
+
"output_filename_base": adk_types.Schema(
|
|
1829
|
+
type=adk_types.Type.STRING,
|
|
1830
|
+
description="Optional base name for the new artifact if the extracted content is saved. May contain embeds.",
|
|
1831
|
+
nullable=True,
|
|
1832
|
+
),
|
|
1833
|
+
},
|
|
1834
|
+
required=["filename", "extraction_goal"],
|
|
1835
|
+
),
|
|
1836
|
+
examples=[],
|
|
1837
|
+
)
|
|
1838
|
+
|
|
1839
|
+
tool_registry.register(_notify_artifact_save_tool_def)
|
|
1840
|
+
tool_registry.register(append_to_artifact_tool_def)
|
|
1841
|
+
tool_registry.register(list_artifacts_tool_def)
|
|
1842
|
+
tool_registry.register(load_artifact_tool_def)
|
|
1843
|
+
tool_registry.register(signal_artifact_for_return_tool_def)
|
|
1844
|
+
tool_registry.register(apply_embed_and_create_artifact_tool_def)
|
|
1845
|
+
tool_registry.register(extract_content_from_artifact_tool_def)
|
|
1846
|
+
|
|
1847
|
+
|
|
1848
|
+
async def delete_artifact(
|
|
1849
|
+
filename: str,
|
|
1850
|
+
version: Optional[int] = None,
|
|
1851
|
+
tool_context: ToolContext = None,
|
|
1852
|
+
) -> Dict[str, Any]:
|
|
1853
|
+
"""
|
|
1854
|
+
Deletes a specific version of an artifact, or all versions if no version is specified.
|
|
1855
|
+
|
|
1856
|
+
Args:
|
|
1857
|
+
filename: The name of the artifact to delete.
|
|
1858
|
+
version: The specific version number to delete. If not provided, all versions will be deleted.
|
|
1859
|
+
tool_context: The context provided by the ADK framework.
|
|
1860
|
+
|
|
1861
|
+
Returns:
|
|
1862
|
+
A dictionary indicating the result of the deletion.
|
|
1863
|
+
"""
|
|
1864
|
+
if not tool_context:
|
|
1865
|
+
return {
|
|
1866
|
+
"status": "error",
|
|
1867
|
+
"filename": filename,
|
|
1868
|
+
"message": "ToolContext is missing, cannot delete artifact.",
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
log_identifier = (
|
|
1872
|
+
f"[BuiltinArtifactTool:delete_artifact:{filename}:{version or 'all'}]"
|
|
1873
|
+
)
|
|
1874
|
+
log.debug("%s Processing request.", log_identifier)
|
|
1875
|
+
|
|
1876
|
+
try:
|
|
1877
|
+
inv_context = tool_context._invocation_context
|
|
1878
|
+
artifact_service = inv_context.artifact_service
|
|
1879
|
+
if not artifact_service:
|
|
1880
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
1881
|
+
|
|
1882
|
+
app_name = inv_context.app_name
|
|
1883
|
+
user_id = inv_context.user_id
|
|
1884
|
+
session_id = get_original_session_id(inv_context)
|
|
1885
|
+
|
|
1886
|
+
if not hasattr(artifact_service, "delete_artifact"):
|
|
1887
|
+
raise NotImplementedError(
|
|
1888
|
+
"ArtifactService does not support deleting artifacts."
|
|
1889
|
+
)
|
|
1890
|
+
|
|
1891
|
+
await artifact_service.delete_artifact(
|
|
1892
|
+
app_name=app_name,
|
|
1893
|
+
user_id=user_id,
|
|
1894
|
+
session_id=session_id,
|
|
1895
|
+
filename=filename,
|
|
1896
|
+
version=version,
|
|
1897
|
+
)
|
|
1898
|
+
|
|
1899
|
+
log.info(
|
|
1900
|
+
"%s Successfully deleted artifact '%s' version '%s'.",
|
|
1901
|
+
log_identifier,
|
|
1902
|
+
filename,
|
|
1903
|
+
version or "all",
|
|
1904
|
+
)
|
|
1905
|
+
return {
|
|
1906
|
+
"status": "success",
|
|
1907
|
+
"filename": filename,
|
|
1908
|
+
"version": version or "all",
|
|
1909
|
+
"message": f"Artifact '{filename}' version '{version or 'all'}' deleted successfully.",
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
except FileNotFoundError as e:
|
|
1913
|
+
log.warning("%s Artifact not found for deletion: %s", log_identifier, e)
|
|
1914
|
+
return {
|
|
1915
|
+
"status": "error",
|
|
1916
|
+
"filename": filename,
|
|
1917
|
+
"message": f"Artifact '{filename}' not found.",
|
|
1918
|
+
}
|
|
1919
|
+
except Exception as e:
|
|
1920
|
+
log.exception(
|
|
1921
|
+
"%s Error deleting artifact '%s': %s", log_identifier, filename, e
|
|
1922
|
+
)
|
|
1923
|
+
return {
|
|
1924
|
+
"status": "error",
|
|
1925
|
+
"filename": filename,
|
|
1926
|
+
"message": f"Failed to delete artifact: {e}",
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
|
|
1930
|
+
delete_artifact_tool_def = BuiltinTool(
|
|
1931
|
+
name="delete_artifact",
|
|
1932
|
+
implementation=delete_artifact,
|
|
1933
|
+
description="Deletes a specific version of an artifact, or all versions if no version is specified.",
|
|
1934
|
+
category="artifact_management",
|
|
1935
|
+
required_scopes=["tool:artifact:delete"],
|
|
1936
|
+
parameters=adk_types.Schema(
|
|
1937
|
+
type=adk_types.Type.OBJECT,
|
|
1938
|
+
properties={
|
|
1939
|
+
"filename": adk_types.Schema(
|
|
1940
|
+
type=adk_types.Type.STRING,
|
|
1941
|
+
description="The name of the artifact to delete.",
|
|
1942
|
+
),
|
|
1943
|
+
"version": adk_types.Schema(
|
|
1944
|
+
type=adk_types.Type.INTEGER,
|
|
1945
|
+
description="The specific version number to delete. If not provided, all versions will be deleted.",
|
|
1946
|
+
nullable=True,
|
|
1947
|
+
),
|
|
1948
|
+
},
|
|
1949
|
+
required=["filename"],
|
|
1950
|
+
),
|
|
1951
|
+
examples=[],
|
|
1952
|
+
)
|
|
1953
|
+
|
|
1954
|
+
tool_registry.register(delete_artifact_tool_def)
|