solace-agent-mesh 1.11.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- solace_agent_mesh/__init__.py +0 -0
- solace_agent_mesh/agent/__init__.py +0 -0
- solace_agent_mesh/agent/adk/__init__.py +0 -0
- solace_agent_mesh/agent/adk/adk_llm.txt +226 -0
- solace_agent_mesh/agent/adk/adk_llm_detail.txt +566 -0
- solace_agent_mesh/agent/adk/alembic/README +74 -0
- solace_agent_mesh/agent/adk/alembic/env.py +77 -0
- solace_agent_mesh/agent/adk/alembic/script.py.mako +28 -0
- solace_agent_mesh/agent/adk/alembic/versions/e2902798564d_adk_session_db_upgrade.py +52 -0
- solace_agent_mesh/agent/adk/alembic.ini +112 -0
- solace_agent_mesh/agent/adk/app_llm_agent.py +52 -0
- solace_agent_mesh/agent/adk/artifacts/__init__.py +1 -0
- solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +171 -0
- solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +545 -0
- solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +609 -0
- solace_agent_mesh/agent/adk/callbacks.py +2318 -0
- solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +406 -0
- solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +415 -0
- solace_agent_mesh/agent/adk/mcp_content_processor.py +666 -0
- solace_agent_mesh/agent/adk/models/lite_llm.py +1026 -0
- solace_agent_mesh/agent/adk/models/models_llm.txt +189 -0
- solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +132 -0
- solace_agent_mesh/agent/adk/runner.py +390 -0
- solace_agent_mesh/agent/adk/schema_migration.py +88 -0
- solace_agent_mesh/agent/adk/services.py +468 -0
- solace_agent_mesh/agent/adk/setup.py +1325 -0
- solace_agent_mesh/agent/adk/stream_parser.py +415 -0
- solace_agent_mesh/agent/adk/tool_wrapper.py +165 -0
- solace_agent_mesh/agent/agent_llm.txt +369 -0
- solace_agent_mesh/agent/agent_llm_detail.txt +1702 -0
- solace_agent_mesh/agent/protocol/__init__.py +0 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +2041 -0
- solace_agent_mesh/agent/protocol/protocol_llm.txt +81 -0
- solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +92 -0
- solace_agent_mesh/agent/proxies/__init__.py +0 -0
- solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
- solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +190 -0
- solace_agent_mesh/agent/proxies/a2a/app.py +56 -0
- solace_agent_mesh/agent/proxies/a2a/component.py +1585 -0
- solace_agent_mesh/agent/proxies/a2a/config.py +216 -0
- solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
- solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
- solace_agent_mesh/agent/proxies/base/app.py +100 -0
- solace_agent_mesh/agent/proxies/base/base_llm.txt +148 -0
- solace_agent_mesh/agent/proxies/base/component.py +816 -0
- solace_agent_mesh/agent/proxies/base/config.py +85 -0
- solace_agent_mesh/agent/proxies/base/proxy_task_context.py +19 -0
- solace_agent_mesh/agent/proxies/proxies_llm.txt +283 -0
- solace_agent_mesh/agent/sac/__init__.py +0 -0
- solace_agent_mesh/agent/sac/app.py +595 -0
- solace_agent_mesh/agent/sac/component.py +3668 -0
- solace_agent_mesh/agent/sac/patch_adk.py +103 -0
- solace_agent_mesh/agent/sac/sac_llm.txt +189 -0
- solace_agent_mesh/agent/sac/sac_llm_detail.txt +200 -0
- solace_agent_mesh/agent/sac/task_execution_context.py +415 -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 +58 -0
- solace_agent_mesh/agent/testing/testing_llm_detail.txt +68 -0
- solace_agent_mesh/agent/tools/__init__.py +16 -0
- solace_agent_mesh/agent/tools/audio_tools.py +1740 -0
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +2500 -0
- solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +244 -0
- solace_agent_mesh/agent/tools/dynamic_tool.py +396 -0
- solace_agent_mesh/agent/tools/general_agent_tools.py +572 -0
- solace_agent_mesh/agent/tools/image_tools.py +1185 -0
- solace_agent_mesh/agent/tools/peer_agent_tool.py +363 -0
- solace_agent_mesh/agent/tools/registry.py +38 -0
- solace_agent_mesh/agent/tools/test_tools.py +136 -0
- solace_agent_mesh/agent/tools/time_tools.py +126 -0
- solace_agent_mesh/agent/tools/tool_config_types.py +93 -0
- solace_agent_mesh/agent/tools/tool_definition.py +53 -0
- solace_agent_mesh/agent/tools/tools_llm.txt +276 -0
- solace_agent_mesh/agent/tools/tools_llm_detail.txt +275 -0
- solace_agent_mesh/agent/tools/web_tools.py +392 -0
- solace_agent_mesh/agent/utils/__init__.py +0 -0
- solace_agent_mesh/agent/utils/artifact_helpers.py +1353 -0
- solace_agent_mesh/agent/utils/config_parser.py +49 -0
- solace_agent_mesh/agent/utils/context_helpers.py +77 -0
- solace_agent_mesh/agent/utils/utils_llm.txt +152 -0
- solace_agent_mesh/agent/utils/utils_llm_detail.txt +149 -0
- solace_agent_mesh/assets/docs/404.html +16 -0
- solace_agent_mesh/assets/docs/assets/css/styles.8162edfb.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/sam-enterprise-credentials-b269f095349473118b2b33bdfcc40122.png +0 -0
- solace_agent_mesh/assets/docs/assets/js/032c2d61.f3d37824.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/05749d90.19ac4f35.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/0bcf40b7.c019ad46.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.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/15ba94aa.92fea363.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15e40e79.434bb30f.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/17896441.e612dfb4.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2131ec11.5c7a1f6e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js.LICENSE.txt +13 -0
- solace_agent_mesh/assets/docs/assets/js/2334.1cf50a20.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/240a0364.9ad94d1b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2e32b5e0.33f5d75b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/341393d4.0fac2613.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3624.0eaa1fd0.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/3a6c6137.f5940cfa.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ac1795d.28b7c67b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ff0015d.2ddc75c0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/41adc471.48b12a4e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.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/509e993c.a1fbf45a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/55b7b518.29d6e75d.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/5c2bd65f.90a87880.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6063ff4c.ef84f702.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/60702c0e.a8bdd79b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/66d4869e.b77431fc.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/6a520c9d.b6e3f2ce.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6aaedf65.7253541d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6d84eae0.fd23ba4a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6fdfefc7.99de744e.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/71da7b71.374b9d54.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/722f809d.965da774.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/742f027b.46c07808.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.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/8024126c.fa0e7186.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/81a99df0.2484b8d9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/82fbfb93.161823a5.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.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.5d015485.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/8591.5d015485.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.6c1dbf0c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/924ffdeb.975e428a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/945fb41e.6f4cdffd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/94e8668d.16083b3f.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/9bb13469.b2333011.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a94703ab.3e5fbcb3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ab9708a8.245ae0ef.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ad71b5ed.af3ecfd1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/c198a0dc.8f31f867.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/c93cbaa0.0e0d8baf.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cab03b5b.6a073091.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.07e170dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de5f4c65.e8241890.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e04b235d.52cb25ed.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e1b6eeb4.b1068f9b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e3d9abda.1476f570.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.4488e34c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.250993bf.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.7acf7ace.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/main.7acf7ace.js.LICENSE.txt +81 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.9e0813a2.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +154 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +99 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +90 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +107 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +166 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +101 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +219 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +92 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +29 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +55 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +110 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +182 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/prompts/index.html +147 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +345 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/speech/index.html +52 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +83 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +84 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +25 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +47 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +85 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +60 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/proxy_configuration/index.html +49 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +144 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +191 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +128 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +34 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +55 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +267 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +142 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +116 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +86 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +164 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +140 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +57 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +72 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +102 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/teams-integration/index.html +115 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +86 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +67 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +37 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +86 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/openapi-tools/index.html +324 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +247 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +184 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +75 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +85 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +41 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +290 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +78 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +25 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +78 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +160 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +142 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +100 -0
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +52 -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/sam-enterprise-credentials.png +0 -0
- solace_agent_mesh/assets/docs/img/solace-logo-text.svg +18 -0
- solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
- solace_agent_mesh/assets/docs/lunr-index-1765810064709.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -0
- solace_agent_mesh/assets/docs/search-doc-1765810064709.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 -0
- solace_agent_mesh/cli/commands/__init__.py +0 -0
- 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 +729 -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 +102 -0
- solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +114 -0
- solace_agent_mesh/cli/commands/docs_cmd.py +60 -0
- solace_agent_mesh/cli/commands/eval_cmd.py +46 -0
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +439 -0
- solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
- solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
- solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +238 -0
- solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +464 -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 +119 -0
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +215 -0
- solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +20 -0
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +137 -0
- solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +144 -0
- solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +306 -0
- solace_agent_mesh/cli/commands/plugin_cmd/install_cmd.py +283 -0
- solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +175 -0
- solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
- solace_agent_mesh/cli/commands/run_cmd.py +215 -0
- solace_agent_mesh/cli/main.py +52 -0
- solace_agent_mesh/cli/utils.py +262 -0
- solace_agent_mesh/client/webui/frontend/static/assets/authCallback-Dj3JtK42.js +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/client-ZKk9kEJ5.js +25 -0
- solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-BcUaNZ-Q.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-vjch4RYc.js +435 -0
- solace_agent_mesh/client/webui/frontend/static/assets/vendor-BNV4kZN0.js +535 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +15 -0
- solace_agent_mesh/client/webui/frontend/static/index.html +16 -0
- solace_agent_mesh/client/webui/frontend/static/mockServiceWorker.js +336 -0
- solace_agent_mesh/client/webui/frontend/static/ui-version.json +6 -0
- solace_agent_mesh/common/__init__.py +1 -0
- solace_agent_mesh/common/a2a/__init__.py +241 -0
- solace_agent_mesh/common/a2a/a2a_llm.txt +175 -0
- solace_agent_mesh/common/a2a/a2a_llm_detail.txt +193 -0
- solace_agent_mesh/common/a2a/artifact.py +368 -0
- solace_agent_mesh/common/a2a/events.py +213 -0
- solace_agent_mesh/common/a2a/message.py +375 -0
- solace_agent_mesh/common/a2a/protocol.py +689 -0
- solace_agent_mesh/common/a2a/task.py +127 -0
- solace_agent_mesh/common/a2a/translation.py +655 -0
- solace_agent_mesh/common/a2a/types.py +55 -0
- solace_agent_mesh/common/a2a_spec/a2a.json +2576 -0
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +445 -0
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +736 -0
- solace_agent_mesh/common/a2a_spec/schemas/agent_progress_update.json +18 -0
- solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +48 -0
- solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
- solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +41 -0
- solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +330 -0
- solace_agent_mesh/common/a2a_spec/schemas/tool_invocation_start.json +26 -0
- solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +48 -0
- solace_agent_mesh/common/agent_registry.py +122 -0
- solace_agent_mesh/common/common_llm.txt +230 -0
- solace_agent_mesh/common/common_llm_detail.txt +2562 -0
- solace_agent_mesh/common/constants.py +6 -0
- solace_agent_mesh/common/data_parts.py +150 -0
- solace_agent_mesh/common/exceptions.py +49 -0
- solace_agent_mesh/common/middleware/__init__.py +12 -0
- solace_agent_mesh/common/middleware/config_resolver.py +132 -0
- solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
- solace_agent_mesh/common/middleware/middleware_llm_detail.txt +185 -0
- solace_agent_mesh/common/middleware/registry.py +127 -0
- solace_agent_mesh/common/oauth/__init__.py +17 -0
- solace_agent_mesh/common/oauth/oauth_client.py +408 -0
- solace_agent_mesh/common/oauth/utils.py +50 -0
- solace_agent_mesh/common/sac/__init__.py +0 -0
- solace_agent_mesh/common/sac/sac_llm.txt +71 -0
- solace_agent_mesh/common/sac/sac_llm_detail.txt +82 -0
- solace_agent_mesh/common/sac/sam_component_base.py +730 -0
- solace_agent_mesh/common/sam_events/__init__.py +9 -0
- solace_agent_mesh/common/sam_events/event_service.py +208 -0
- solace_agent_mesh/common/sam_events/sam_events_llm.txt +104 -0
- solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +115 -0
- solace_agent_mesh/common/services/__init__.py +4 -0
- solace_agent_mesh/common/services/employee_service.py +164 -0
- solace_agent_mesh/common/services/identity_service.py +134 -0
- solace_agent_mesh/common/services/providers/__init__.py +4 -0
- solace_agent_mesh/common/services/providers/local_file_identity_service.py +151 -0
- solace_agent_mesh/common/services/providers/providers_llm.txt +81 -0
- solace_agent_mesh/common/services/services_llm.txt +368 -0
- solace_agent_mesh/common/services/services_llm_detail.txt +459 -0
- solace_agent_mesh/common/utils/__init__.py +7 -0
- solace_agent_mesh/common/utils/artifact_utils.py +31 -0
- solace_agent_mesh/common/utils/asyncio_macos_fix.py +88 -0
- solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
- solace_agent_mesh/common/utils/embeds/constants.py +56 -0
- solace_agent_mesh/common/utils/embeds/converter.py +447 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +220 -0
- solace_agent_mesh/common/utils/embeds/evaluators.py +395 -0
- solace_agent_mesh/common/utils/embeds/modifiers.py +793 -0
- solace_agent_mesh/common/utils/embeds/resolver.py +967 -0
- solace_agent_mesh/common/utils/embeds/types.py +23 -0
- solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
- solace_agent_mesh/common/utils/initializer.py +52 -0
- solace_agent_mesh/common/utils/log_formatters.py +64 -0
- solace_agent_mesh/common/utils/message_utils.py +80 -0
- solace_agent_mesh/common/utils/mime_helpers.py +172 -0
- solace_agent_mesh/common/utils/push_notification_auth.py +135 -0
- solace_agent_mesh/common/utils/pydantic_utils.py +159 -0
- solace_agent_mesh/common/utils/rbac_utils.py +69 -0
- solace_agent_mesh/common/utils/templates/__init__.py +8 -0
- solace_agent_mesh/common/utils/templates/liquid_renderer.py +210 -0
- solace_agent_mesh/common/utils/templates/template_resolver.py +161 -0
- solace_agent_mesh/common/utils/type_utils.py +28 -0
- solace_agent_mesh/common/utils/utils_llm.txt +335 -0
- solace_agent_mesh/common/utils/utils_llm_detail.txt +572 -0
- solace_agent_mesh/config_portal/__init__.py +0 -0
- solace_agent_mesh/config_portal/backend/__init__.py +0 -0
- solace_agent_mesh/config_portal/backend/common.py +77 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/__init__.py +0 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +24 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +166 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +521 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +217 -0
- solace_agent_mesh/config_portal/backend/server.py +644 -0
- solace_agent_mesh/config_portal/frontend/static/client/Solace_community_logo.png +0 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DiOiAjzL.js +103 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/components-Rk0n-9cK.js +140 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/entry.client-mvZjNKiz.js +19 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/index-DzNKzXrc.js +68 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-ba77705e.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-B17tZKK7.css +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-V2BeTIUc.js +10 -0
- solace_agent_mesh/config_portal/frontend/static/client/favicon.ico +0 -0
- solace_agent_mesh/config_portal/frontend/static/client/index.html +7 -0
- solace_agent_mesh/core_a2a/__init__.py +1 -0
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +90 -0
- solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +101 -0
- solace_agent_mesh/core_a2a/service.py +307 -0
- solace_agent_mesh/evaluation/__init__.py +0 -0
- solace_agent_mesh/evaluation/evaluator.py +691 -0
- solace_agent_mesh/evaluation/message_organizer.py +553 -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 +970 -0
- solace_agent_mesh/evaluation/report_generator.py +607 -0
- solace_agent_mesh/evaluation/run.py +954 -0
- solace_agent_mesh/evaluation/shared/__init__.py +92 -0
- solace_agent_mesh/evaluation/shared/constants.py +47 -0
- solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
- solace_agent_mesh/evaluation/shared/helpers.py +35 -0
- solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
- solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
- solace_agent_mesh/evaluation/subscriber.py +776 -0
- solace_agent_mesh/evaluation/summary_builder.py +880 -0
- solace_agent_mesh/gateway/__init__.py +0 -0
- solace_agent_mesh/gateway/adapter/__init__.py +1 -0
- solace_agent_mesh/gateway/adapter/base.py +143 -0
- solace_agent_mesh/gateway/adapter/types.py +221 -0
- solace_agent_mesh/gateway/base/__init__.py +1 -0
- solace_agent_mesh/gateway/base/app.py +345 -0
- solace_agent_mesh/gateway/base/base_llm.txt +226 -0
- solace_agent_mesh/gateway/base/base_llm_detail.txt +235 -0
- solace_agent_mesh/gateway/base/component.py +2030 -0
- solace_agent_mesh/gateway/base/task_context.py +75 -0
- solace_agent_mesh/gateway/gateway_llm.txt +369 -0
- solace_agent_mesh/gateway/gateway_llm_detail.txt +3885 -0
- solace_agent_mesh/gateway/generic/__init__.py +1 -0
- solace_agent_mesh/gateway/generic/app.py +50 -0
- solace_agent_mesh/gateway/generic/component.py +727 -0
- solace_agent_mesh/gateway/http_sse/__init__.py +0 -0
- solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +345 -0
- solace_agent_mesh/gateway/http_sse/alembic/env.py +87 -0
- solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20250910_d5b3f8f2e9a0_create_initial_database.py +58 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20250911_b1c2d3e4f5g6_add_database_indexes.py +83 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20250916_f6e7d8c9b0a1_convert_timestamps_to_epoch_and_align_columns.py +412 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251006_98882922fa59_add_tasks_events_feedback_chat_tasks.py +190 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +109 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251108_create_prompt_tables_with_sharing.py +154 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251115_add_parent_task_id.py +32 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251126_add_background_task_fields.py +47 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251202_add_versioned_fields_to_prompts.py +52 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +161 -0
- solace_agent_mesh/gateway/http_sse/alembic.ini +109 -0
- solace_agent_mesh/gateway/http_sse/app.py +351 -0
- solace_agent_mesh/gateway/http_sse/component.py +2360 -0
- solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +105 -0
- solace_agent_mesh/gateway/http_sse/components/task_logger_forwarder.py +109 -0
- solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +110 -0
- solace_agent_mesh/gateway/http_sse/dependencies.py +653 -0
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +299 -0
- solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +3278 -0
- solace_agent_mesh/gateway/http_sse/main.py +789 -0
- solace_agent_mesh/gateway/http_sse/repository/__init__.py +46 -0
- solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +102 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +11 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/chat_task.py +75 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +221 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/feedback.py +20 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/session.py +66 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +0 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/task.py +32 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/task_event.py +21 -0
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +125 -0
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +239 -0
- solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +34 -0
- solace_agent_mesh/gateway/http_sse/repository/models/base.py +7 -0
- solace_agent_mesh/gateway/http_sse/repository/models/chat_task_model.py +31 -0
- solace_agent_mesh/gateway/http_sse/repository/models/feedback_model.py +21 -0
- solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +257 -0
- solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
- solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
- solace_agent_mesh/gateway/http_sse/repository/models/prompt_model.py +159 -0
- solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +53 -0
- solace_agent_mesh/gateway/http_sse/repository/models/task_event_model.py +25 -0
- solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +39 -0
- solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
- solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
- solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +308 -0
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +268 -0
- solace_agent_mesh/gateway/http_sse/repository/task_repository.py +248 -0
- solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +74 -0
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +1137 -0
- solace_agent_mesh/gateway/http_sse/routers/auth.py +311 -0
- solace_agent_mesh/gateway/http_sse/routers/config.py +371 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/__init__.py +10 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +450 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/project_dto.py +69 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/prompt_dto.py +255 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +15 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +133 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +33 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/task_requests.py +58 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +18 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +42 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +31 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +123 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +33 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/task_responses.py +30 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/version_responses.py +31 -0
- solace_agent_mesh/gateway/http_sse/routers/feedback.py +168 -0
- solace_agent_mesh/gateway/http_sse/routers/people.py +38 -0
- solace_agent_mesh/gateway/http_sse/routers/projects.py +767 -0
- solace_agent_mesh/gateway/http_sse/routers/prompts.py +1415 -0
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +312 -0
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +634 -0
- solace_agent_mesh/gateway/http_sse/routers/speech.py +355 -0
- solace_agent_mesh/gateway/http_sse/routers/sse.py +230 -0
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +1089 -0
- solace_agent_mesh/gateway/http_sse/routers/users.py +83 -0
- solace_agent_mesh/gateway/http_sse/routers/version.py +343 -0
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +1220 -0
- solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/services/agent_card_service.py +71 -0
- solace_agent_mesh/gateway/http_sse/services/audio_service.py +1227 -0
- solace_agent_mesh/gateway/http_sse/services/background_task_monitor.py +186 -0
- solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +273 -0
- solace_agent_mesh/gateway/http_sse/services/feedback_service.py +250 -0
- solace_agent_mesh/gateway/http_sse/services/people_service.py +78 -0
- solace_agent_mesh/gateway/http_sse/services/project_service.py +930 -0
- solace_agent_mesh/gateway/http_sse/services/prompt_builder_assistant.py +303 -0
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +303 -0
- solace_agent_mesh/gateway/http_sse/services/session_service.py +702 -0
- solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +593 -0
- solace_agent_mesh/gateway/http_sse/services/task_service.py +119 -0
- solace_agent_mesh/gateway/http_sse/session_manager.py +219 -0
- solace_agent_mesh/gateway/http_sse/shared/__init__.py +146 -0
- solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
- solace_agent_mesh/gateway/http_sse/shared/base_repository.py +252 -0
- solace_agent_mesh/gateway/http_sse/shared/database_exceptions.py +274 -0
- solace_agent_mesh/gateway/http_sse/shared/database_helpers.py +43 -0
- solace_agent_mesh/gateway/http_sse/shared/enums.py +40 -0
- solace_agent_mesh/gateway/http_sse/shared/error_dto.py +107 -0
- solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +217 -0
- solace_agent_mesh/gateway/http_sse/shared/exceptions.py +192 -0
- solace_agent_mesh/gateway/http_sse/shared/pagination.py +138 -0
- solace_agent_mesh/gateway/http_sse/shared/response_utils.py +134 -0
- solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +319 -0
- solace_agent_mesh/gateway/http_sse/shared/timestamp_utils.py +97 -0
- solace_agent_mesh/gateway/http_sse/shared/types.py +50 -0
- solace_agent_mesh/gateway/http_sse/shared/utils.py +22 -0
- solace_agent_mesh/gateway/http_sse/sse_event_buffer.py +88 -0
- solace_agent_mesh/gateway/http_sse/sse_manager.py +491 -0
- solace_agent_mesh/gateway/http_sse/utils/__init__.py +1 -0
- solace_agent_mesh/gateway/http_sse/utils/artifact_copy_utils.py +370 -0
- solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +72 -0
- solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +47 -0
- solace_agent_mesh/llm.txt +228 -0
- solace_agent_mesh/llm_detail.txt +2835 -0
- solace_agent_mesh/services/__init__.py +0 -0
- solace_agent_mesh/services/platform/__init__.py +18 -0
- solace_agent_mesh/services/platform/alembic/env.py +85 -0
- solace_agent_mesh/services/platform/alembic/script.py.mako +28 -0
- solace_agent_mesh/services/platform/alembic.ini +109 -0
- solace_agent_mesh/services/platform/api/__init__.py +3 -0
- solace_agent_mesh/services/platform/api/dependencies.py +147 -0
- solace_agent_mesh/services/platform/api/main.py +280 -0
- solace_agent_mesh/services/platform/api/middleware.py +51 -0
- solace_agent_mesh/services/platform/api/routers/__init__.py +24 -0
- solace_agent_mesh/services/platform/app.py +114 -0
- solace_agent_mesh/services/platform/component.py +235 -0
- solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
- solace_agent_mesh/solace_agent_mesh_llm_detail.txt +8599 -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 +75 -0
- solace_agent_mesh/templates/gateway_component_template.py +484 -0
- solace_agent_mesh/templates/gateway_config_template.yaml +38 -0
- solace_agent_mesh/templates/logging_config_template.yaml +48 -0
- solace_agent_mesh/templates/main_orchestrator.yaml +66 -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 +60 -0
- solace_agent_mesh/templates/plugin_pyproject_template.toml +32 -0
- solace_agent_mesh/templates/plugin_readme_template.md +12 -0
- solace_agent_mesh/templates/plugin_tool_config_template.yaml +109 -0
- solace_agent_mesh/templates/plugin_tools_template.py +224 -0
- solace_agent_mesh/templates/shared_config.yaml +112 -0
- solace_agent_mesh/templates/templates_llm.txt +147 -0
- solace_agent_mesh/templates/webui.yaml +177 -0
- solace_agent_mesh-1.11.2.dist-info/METADATA +504 -0
- solace_agent_mesh-1.11.2.dist-info/RECORD +624 -0
- solace_agent_mesh-1.11.2.dist-info/WHEEL +4 -0
- solace_agent_mesh-1.11.2.dist-info/entry_points.txt +3 -0
- solace_agent_mesh-1.11.2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,1740 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Collection of Python tools for audio processing and text-to-speech generation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import asyncio
|
|
7
|
+
import inspect
|
|
8
|
+
import io
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import random
|
|
12
|
+
import tempfile
|
|
13
|
+
import uuid
|
|
14
|
+
import wave
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
|
|
20
|
+
from google import genai
|
|
21
|
+
from google.genai import types as adk_types
|
|
22
|
+
from google.adk.tools import ToolContext
|
|
23
|
+
from pydub import AudioSegment
|
|
24
|
+
|
|
25
|
+
from ...agent.utils.artifact_helpers import (
|
|
26
|
+
load_artifact_content_or_metadata,
|
|
27
|
+
save_artifact_with_metadata,
|
|
28
|
+
ensure_correct_extension,
|
|
29
|
+
DEFAULT_SCHEMA_MAX_KEYS,
|
|
30
|
+
)
|
|
31
|
+
from ...agent.utils.context_helpers import get_original_session_id
|
|
32
|
+
|
|
33
|
+
from .tool_definition import BuiltinTool
|
|
34
|
+
from .registry import tool_registry
|
|
35
|
+
|
|
36
|
+
log = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
VOICE_TONE_MAPPING = {
|
|
39
|
+
"bright": ["Zephyr", "Autonoe"],
|
|
40
|
+
"upbeat": ["Puck", "Laomedeia"],
|
|
41
|
+
"informative": ["Charon", "Rasalgethi"],
|
|
42
|
+
"firm": ["Kore", "Orus", "Alnilam"],
|
|
43
|
+
"excitable": ["Fenrir"],
|
|
44
|
+
"youthful": ["Leda"],
|
|
45
|
+
"breezy": ["Aoede"],
|
|
46
|
+
"easy-going": ["Callirrhoe", "Umbriel"],
|
|
47
|
+
"breathy": ["Enceladus"],
|
|
48
|
+
"clear": ["Iapetus", "Erinome"],
|
|
49
|
+
"smooth": ["Algieba", "Despina"],
|
|
50
|
+
"gravelly": ["Algenib"],
|
|
51
|
+
"soft": ["Achernar"],
|
|
52
|
+
"even": ["Schedar"],
|
|
53
|
+
"mature": ["Gacrux"],
|
|
54
|
+
"forward": ["Pulcherrima"],
|
|
55
|
+
"friendly": ["Achird"],
|
|
56
|
+
"casual": ["Zubenelgenubi"],
|
|
57
|
+
"gentle": ["Vindemiatrix"],
|
|
58
|
+
"lively": ["Sadachbia"],
|
|
59
|
+
"knowledgeable": ["Sadaltager"],
|
|
60
|
+
"warm": ["Sulafat"],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
GENDER_TO_VOICE_MAPPING = {
|
|
64
|
+
"male": [
|
|
65
|
+
"Puck",
|
|
66
|
+
"Charon",
|
|
67
|
+
"Orus",
|
|
68
|
+
"Enceladus",
|
|
69
|
+
"Iapetus",
|
|
70
|
+
"Algieba",
|
|
71
|
+
"Algenib",
|
|
72
|
+
"Alnilam",
|
|
73
|
+
"Schedar",
|
|
74
|
+
"Achird",
|
|
75
|
+
"Zubenelgenubi",
|
|
76
|
+
"Sadachbia",
|
|
77
|
+
"Sadaltager",
|
|
78
|
+
],
|
|
79
|
+
"female": [
|
|
80
|
+
"Zephyr",
|
|
81
|
+
"Kore",
|
|
82
|
+
"Leda",
|
|
83
|
+
"Aoede",
|
|
84
|
+
"Callirrhoe",
|
|
85
|
+
"Autonoe",
|
|
86
|
+
"Despina",
|
|
87
|
+
"Erinome",
|
|
88
|
+
"Laomedeia",
|
|
89
|
+
"Achernar",
|
|
90
|
+
"Gacrux",
|
|
91
|
+
"Pulcherrima",
|
|
92
|
+
"Vindemiatrix",
|
|
93
|
+
"Sulafat",
|
|
94
|
+
],
|
|
95
|
+
"neutral": [
|
|
96
|
+
"Fenrir",
|
|
97
|
+
"Umbriel",
|
|
98
|
+
"Rasalgethi",
|
|
99
|
+
],
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_all_voices_set = set(
|
|
103
|
+
voice for voices_in_tone in VOICE_TONE_MAPPING.values() for voice in voices_in_tone
|
|
104
|
+
)
|
|
105
|
+
_all_voices_set.update(
|
|
106
|
+
voice
|
|
107
|
+
for voices_in_gender in GENDER_TO_VOICE_MAPPING.values()
|
|
108
|
+
for voice in voices_in_gender
|
|
109
|
+
)
|
|
110
|
+
ALL_AVAILABLE_VOICES = list(_all_voices_set)
|
|
111
|
+
|
|
112
|
+
if not ALL_AVAILABLE_VOICES:
|
|
113
|
+
ALL_AVAILABLE_VOICES = [
|
|
114
|
+
"Kore",
|
|
115
|
+
"Puck",
|
|
116
|
+
"Zephyr",
|
|
117
|
+
"Charon",
|
|
118
|
+
"Rasalgethi",
|
|
119
|
+
"Alnilam",
|
|
120
|
+
"Fenrir",
|
|
121
|
+
"Leda",
|
|
122
|
+
"Aoede",
|
|
123
|
+
"Callirrhoe",
|
|
124
|
+
"Umbriel",
|
|
125
|
+
"Enceladus",
|
|
126
|
+
"Iapetus",
|
|
127
|
+
"Erinome",
|
|
128
|
+
"Algieba",
|
|
129
|
+
"Despina",
|
|
130
|
+
"Algenib",
|
|
131
|
+
"Achernar",
|
|
132
|
+
"Schedar",
|
|
133
|
+
"Gacrux",
|
|
134
|
+
"Pulcherrima",
|
|
135
|
+
"Achird",
|
|
136
|
+
"Zubenelgenubi",
|
|
137
|
+
"Vindemiatrix",
|
|
138
|
+
"Sadachbia",
|
|
139
|
+
"Sadaltager",
|
|
140
|
+
"Sulafat",
|
|
141
|
+
"Autonoe",
|
|
142
|
+
"Laomedeia",
|
|
143
|
+
"Orus",
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
SUPPORTED_LANGUAGES = {
|
|
148
|
+
"arabic": "ar-EG",
|
|
149
|
+
"arabic_egyptian": "ar-EG",
|
|
150
|
+
"german": "de-DE",
|
|
151
|
+
"english": "en-US",
|
|
152
|
+
"english_us": "en-US",
|
|
153
|
+
"english_india": "en-IN",
|
|
154
|
+
"spanish": "es-US",
|
|
155
|
+
"spanish_us": "es-US",
|
|
156
|
+
"french": "fr-FR",
|
|
157
|
+
"hindi": "hi-IN",
|
|
158
|
+
"indonesian": "id-ID",
|
|
159
|
+
"italian": "it-IT",
|
|
160
|
+
"japanese": "ja-JP",
|
|
161
|
+
"korean": "ko-KR",
|
|
162
|
+
"portuguese": "pt-BR",
|
|
163
|
+
"portuguese_brazil": "pt-BR",
|
|
164
|
+
"russian": "ru-RU",
|
|
165
|
+
"dutch": "nl-NL",
|
|
166
|
+
"polish": "pl-PL",
|
|
167
|
+
"thai": "th-TH",
|
|
168
|
+
"turkish": "tr-TR",
|
|
169
|
+
"vietnamese": "vi-VN",
|
|
170
|
+
"romanian": "ro-RO",
|
|
171
|
+
"ukrainian": "uk-UA",
|
|
172
|
+
"bengali": "bn-BD",
|
|
173
|
+
"marathi": "mr-IN",
|
|
174
|
+
"tamil": "ta-IN",
|
|
175
|
+
"telugu": "te-IN",
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
from typing import Set
|
|
179
|
+
|
|
180
|
+
DEFAULT_VOICE = "Kore"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _get_effective_tone_voices(
|
|
184
|
+
tone: Optional[str], voice_tone_mapping: Optional[Dict[str, List[str]]] = None
|
|
185
|
+
) -> Optional[List[str]]:
|
|
186
|
+
"""Helper to get voices for a tone, considering aliases."""
|
|
187
|
+
if not tone:
|
|
188
|
+
return None
|
|
189
|
+
mapping = voice_tone_mapping or VOICE_TONE_MAPPING
|
|
190
|
+
tone_lower = tone.lower().strip()
|
|
191
|
+
tone_aliases = {
|
|
192
|
+
"professional": "firm",
|
|
193
|
+
"business": "firm",
|
|
194
|
+
"corporate": "firm",
|
|
195
|
+
"cheerful": "upbeat",
|
|
196
|
+
"happy": "upbeat",
|
|
197
|
+
"energetic": "excitable",
|
|
198
|
+
"calm": "soft",
|
|
199
|
+
"relaxed": "easy-going",
|
|
200
|
+
"serious": "informative",
|
|
201
|
+
"educational": "knowledgeable",
|
|
202
|
+
"teaching": "knowledgeable",
|
|
203
|
+
"conversational": "casual",
|
|
204
|
+
"natural": "friendly",
|
|
205
|
+
"welcoming": "warm",
|
|
206
|
+
}
|
|
207
|
+
effective_tone = tone_aliases.get(tone_lower, tone_lower)
|
|
208
|
+
return mapping.get(effective_tone)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _get_gender_voices(
|
|
212
|
+
gender: Optional[str], gender_voice_mapping: Optional[Dict[str, List[str]]] = None
|
|
213
|
+
) -> Optional[List[str]]:
|
|
214
|
+
"""Helper to get voices for a gender."""
|
|
215
|
+
if not gender:
|
|
216
|
+
return None
|
|
217
|
+
mapping = gender_voice_mapping or GENDER_TO_VOICE_MAPPING
|
|
218
|
+
return mapping.get(gender.lower().strip())
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _get_voice_for_speaker(
|
|
222
|
+
gender: Optional[str],
|
|
223
|
+
tone: Optional[str],
|
|
224
|
+
used_voices_in_current_call: Set[str],
|
|
225
|
+
voice_tone_mapping: Optional[Dict[str, List[str]]] = None,
|
|
226
|
+
gender_voice_mapping: Optional[Dict[str, List[str]]] = None,
|
|
227
|
+
) -> str:
|
|
228
|
+
"""
|
|
229
|
+
Selects a voice based on desired gender and/or tone, prioritizing uniqueness.
|
|
230
|
+
|
|
231
|
+
Selection Priority:
|
|
232
|
+
1. Unique voice matching both specified gender and tone.
|
|
233
|
+
2. Unique voice matching specified gender (if tone match failed or tone not specified).
|
|
234
|
+
3. Unique voice matching specified tone (if gender match failed or gender not specified).
|
|
235
|
+
4. Any unique voice from the global pool.
|
|
236
|
+
5. If all unique options exhausted (reuse necessary):
|
|
237
|
+
a. Voice matching both specified gender and tone.
|
|
238
|
+
b. Voice matching specified gender.
|
|
239
|
+
c. Voice matching specified tone.
|
|
240
|
+
d. Any voice from the global pool.
|
|
241
|
+
6. Fallback to DEFAULT_VOICE ("Kore").
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
gender: The desired gender ("male", "female", "neutral").
|
|
245
|
+
tone: The desired tone (e.g., "friendly", "professional").
|
|
246
|
+
used_voices_in_current_call: A set of voice names already used.
|
|
247
|
+
voice_tone_mapping: Optional custom tone-to-voice mapping.
|
|
248
|
+
gender_voice_mapping: Optional custom gender-to-voice mapping.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
A voice name string.
|
|
252
|
+
"""
|
|
253
|
+
log_identifier_select = "[AudioTools:_get_voice_for_speaker]"
|
|
254
|
+
log.debug(
|
|
255
|
+
"%s Selecting voice for gender='%s', tone='%s', used_voices=%s",
|
|
256
|
+
log_identifier_select,
|
|
257
|
+
gender,
|
|
258
|
+
tone,
|
|
259
|
+
used_voices_in_current_call,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
candidate_pool = list(ALL_AVAILABLE_VOICES)
|
|
263
|
+
|
|
264
|
+
gender_specific_voices = None
|
|
265
|
+
if gender:
|
|
266
|
+
gender_specific_voices = _get_gender_voices(gender, gender_voice_mapping)
|
|
267
|
+
if gender_specific_voices:
|
|
268
|
+
candidate_pool = [v for v in candidate_pool if v in gender_specific_voices]
|
|
269
|
+
log.debug(
|
|
270
|
+
"%s Filtered by gender '%s'. Candidates: %s",
|
|
271
|
+
log_identifier_select,
|
|
272
|
+
gender,
|
|
273
|
+
candidate_pool,
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
log.warning(
|
|
277
|
+
"%s Gender '%s' not found in mapping or has no voices. Gender filter ignored for now.",
|
|
278
|
+
log_identifier_select,
|
|
279
|
+
gender,
|
|
280
|
+
)
|
|
281
|
+
gender_specific_voices = None
|
|
282
|
+
|
|
283
|
+
tone_specific_voices = None
|
|
284
|
+
if tone:
|
|
285
|
+
voices_for_tone = _get_effective_tone_voices(tone, voice_tone_mapping)
|
|
286
|
+
if voices_for_tone:
|
|
287
|
+
tone_specific_voices = voices_for_tone
|
|
288
|
+
candidate_pool = [v for v in candidate_pool if v in voices_for_tone]
|
|
289
|
+
log.debug(
|
|
290
|
+
"%s Filtered by tone '%s'. Candidates: %s",
|
|
291
|
+
log_identifier_select,
|
|
292
|
+
tone,
|
|
293
|
+
candidate_pool,
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
log.warning(
|
|
297
|
+
"%s Tone '%s' not found in mapping or has no voices. Tone filter ignored.",
|
|
298
|
+
log_identifier_select,
|
|
299
|
+
tone,
|
|
300
|
+
)
|
|
301
|
+
tone_specific_voices = None
|
|
302
|
+
|
|
303
|
+
available_unique_voices = [
|
|
304
|
+
v for v in candidate_pool if v not in used_voices_in_current_call
|
|
305
|
+
]
|
|
306
|
+
if available_unique_voices:
|
|
307
|
+
selected = random.choice(available_unique_voices)
|
|
308
|
+
log.info(
|
|
309
|
+
"%s Selected unique voice '%s' from gender/tone filtered pool.",
|
|
310
|
+
log_identifier_select,
|
|
311
|
+
selected,
|
|
312
|
+
)
|
|
313
|
+
return selected
|
|
314
|
+
|
|
315
|
+
log.debug(
|
|
316
|
+
"%s No unique voice in primary filtered pool. Trying fallbacks for uniqueness.",
|
|
317
|
+
log_identifier_select,
|
|
318
|
+
)
|
|
319
|
+
if gender_specific_voices:
|
|
320
|
+
available_gender_unique = [
|
|
321
|
+
v for v in gender_specific_voices if v not in used_voices_in_current_call
|
|
322
|
+
]
|
|
323
|
+
if available_gender_unique:
|
|
324
|
+
selected = random.choice(available_gender_unique)
|
|
325
|
+
log.info(
|
|
326
|
+
"%s Selected unique voice '%s' from gender-only pool (tone constraint relaxed).",
|
|
327
|
+
log_identifier_select,
|
|
328
|
+
selected,
|
|
329
|
+
)
|
|
330
|
+
return selected
|
|
331
|
+
if tone_specific_voices:
|
|
332
|
+
available_tone_unique = [
|
|
333
|
+
v for v in tone_specific_voices if v not in used_voices_in_current_call
|
|
334
|
+
]
|
|
335
|
+
if available_tone_unique:
|
|
336
|
+
selected = random.choice(available_tone_unique)
|
|
337
|
+
log.info(
|
|
338
|
+
"%s Selected unique voice '%s' from tone-only pool (gender constraint relaxed or not specified).",
|
|
339
|
+
log_identifier_select,
|
|
340
|
+
selected,
|
|
341
|
+
)
|
|
342
|
+
return selected
|
|
343
|
+
globally_available_unique = [
|
|
344
|
+
v for v in ALL_AVAILABLE_VOICES if v not in used_voices_in_current_call
|
|
345
|
+
]
|
|
346
|
+
if globally_available_unique:
|
|
347
|
+
selected = random.choice(globally_available_unique)
|
|
348
|
+
log.info(
|
|
349
|
+
"%s Selected unique voice '%s' from global pool (all constraints relaxed).",
|
|
350
|
+
log_identifier_select,
|
|
351
|
+
selected,
|
|
352
|
+
)
|
|
353
|
+
return selected
|
|
354
|
+
|
|
355
|
+
log.warning("%s All voices are used. Reusing a voice.", log_identifier_select)
|
|
356
|
+
if candidate_pool:
|
|
357
|
+
selected = random.choice(candidate_pool)
|
|
358
|
+
log.info(
|
|
359
|
+
"%s Reusing voice '%s' from gender/tone filtered pool.",
|
|
360
|
+
log_identifier_select,
|
|
361
|
+
selected,
|
|
362
|
+
)
|
|
363
|
+
return selected
|
|
364
|
+
if gender_specific_voices:
|
|
365
|
+
selected = random.choice(gender_specific_voices)
|
|
366
|
+
log.info(
|
|
367
|
+
"%s Reusing voice '%s' from gender-only pool.",
|
|
368
|
+
log_identifier_select,
|
|
369
|
+
selected,
|
|
370
|
+
)
|
|
371
|
+
return selected
|
|
372
|
+
if tone_specific_voices:
|
|
373
|
+
selected = random.choice(tone_specific_voices)
|
|
374
|
+
log.info(
|
|
375
|
+
"%s Reusing voice '%s' from tone-only pool.",
|
|
376
|
+
log_identifier_select,
|
|
377
|
+
selected,
|
|
378
|
+
)
|
|
379
|
+
return selected
|
|
380
|
+
if ALL_AVAILABLE_VOICES:
|
|
381
|
+
selected = random.choice(ALL_AVAILABLE_VOICES)
|
|
382
|
+
log.info(
|
|
383
|
+
"%s Reusing voice '%s' from global pool.", log_identifier_select, selected
|
|
384
|
+
)
|
|
385
|
+
return selected
|
|
386
|
+
|
|
387
|
+
log.error(
|
|
388
|
+
"%s No voices available in any mapping or pool. Using default '%s'.",
|
|
389
|
+
log_identifier_select,
|
|
390
|
+
DEFAULT_VOICE,
|
|
391
|
+
)
|
|
392
|
+
return DEFAULT_VOICE
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _get_language_code(language: str) -> str:
|
|
396
|
+
"""
|
|
397
|
+
Get BCP-47 language code from language name or code.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
language: Language name or BCP-47 code
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
BCP-47 language code, defaults to "en-US"
|
|
404
|
+
"""
|
|
405
|
+
if not language:
|
|
406
|
+
return "en-US"
|
|
407
|
+
|
|
408
|
+
language_lower = language.lower().strip()
|
|
409
|
+
|
|
410
|
+
if "-" in language_lower and len(language_lower) >= 5:
|
|
411
|
+
return language
|
|
412
|
+
|
|
413
|
+
if language_lower in SUPPORTED_LANGUAGES:
|
|
414
|
+
return SUPPORTED_LANGUAGES[language_lower]
|
|
415
|
+
|
|
416
|
+
log.warning(f"[AudioTools] Unknown language '{language}', using default 'en-US'")
|
|
417
|
+
return "en-US"
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _create_voice_config(voice_name: str) -> adk_types.VoiceConfig:
|
|
421
|
+
"""Create a VoiceConfig for single-voice TTS."""
|
|
422
|
+
return adk_types.VoiceConfig(
|
|
423
|
+
prebuilt_voice_config=adk_types.PrebuiltVoiceConfig(voice_name=voice_name)
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _create_multi_speaker_config(
|
|
428
|
+
speaker_configs: List[Dict[str, str]],
|
|
429
|
+
) -> adk_types.MultiSpeakerVoiceConfig:
|
|
430
|
+
"""Create a MultiSpeakerVoiceConfig for multi-speaker TTS."""
|
|
431
|
+
speaker_voice_configs = []
|
|
432
|
+
|
|
433
|
+
for config in speaker_configs:
|
|
434
|
+
speaker_name = config.get("name", "Speaker")
|
|
435
|
+
voice_name = config.get("voice", "Kore")
|
|
436
|
+
|
|
437
|
+
speaker_voice_config = adk_types.SpeakerVoiceConfig(
|
|
438
|
+
speaker=speaker_name, voice_config=_create_voice_config(voice_name)
|
|
439
|
+
)
|
|
440
|
+
speaker_voice_configs.append(speaker_voice_config)
|
|
441
|
+
|
|
442
|
+
return adk_types.MultiSpeakerVoiceConfig(
|
|
443
|
+
speaker_voice_configs=speaker_voice_configs
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
async def _generate_audio_with_gemini(
|
|
448
|
+
client: genai.Client,
|
|
449
|
+
prompt: str,
|
|
450
|
+
speech_config: adk_types.SpeechConfig,
|
|
451
|
+
model: str = "gemini-2.5-flash-preview-tts",
|
|
452
|
+
language: str = "en-US",
|
|
453
|
+
) -> bytes:
|
|
454
|
+
"""
|
|
455
|
+
Shared function for generating audio using Gemini API.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
client: Gemini client instance
|
|
459
|
+
prompt: Text prompt for TTS
|
|
460
|
+
speech_config: Speech configuration (single or multi-speaker)
|
|
461
|
+
model: Gemini model to use
|
|
462
|
+
language: BCP-47 language code
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Raw audio data as bytes
|
|
466
|
+
"""
|
|
467
|
+
config = adk_types.GenerateContentConfig(
|
|
468
|
+
response_modalities=["AUDIO"],
|
|
469
|
+
speech_config=speech_config,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
if hasattr(config, "language"):
|
|
473
|
+
config.language = language
|
|
474
|
+
|
|
475
|
+
response = await asyncio.to_thread(
|
|
476
|
+
client.models.generate_content, model=model, contents=prompt, config=config
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
if (
|
|
480
|
+
not response
|
|
481
|
+
or not response.candidates
|
|
482
|
+
or not response.candidates[0].content.parts
|
|
483
|
+
):
|
|
484
|
+
raise ValueError("Gemini API did not return valid audio data")
|
|
485
|
+
|
|
486
|
+
audio_data = response.candidates[0].content.parts[0].inline_data.data
|
|
487
|
+
if not audio_data:
|
|
488
|
+
raise ValueError("No audio data received from Gemini API")
|
|
489
|
+
|
|
490
|
+
return audio_data
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _create_wav_file(
|
|
494
|
+
filename: str,
|
|
495
|
+
pcm_data: bytes,
|
|
496
|
+
channels: int = 1,
|
|
497
|
+
rate: int = 24000,
|
|
498
|
+
sample_width: int = 2,
|
|
499
|
+
):
|
|
500
|
+
"""Create a proper WAV file from PCM data (based on working example)."""
|
|
501
|
+
with wave.open(filename, "wb") as wf:
|
|
502
|
+
wf.setnchannels(channels)
|
|
503
|
+
wf.setsampwidth(sample_width)
|
|
504
|
+
wf.setframerate(rate)
|
|
505
|
+
wf.writeframes(pcm_data)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
async def _convert_pcm_to_mp3(pcm_data: bytes) -> bytes:
|
|
509
|
+
"""
|
|
510
|
+
Shared function for converting raw PCM data to MP3 format.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
pcm_data: Raw PCM audio data from Gemini API
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
MP3 audio data as bytes
|
|
517
|
+
"""
|
|
518
|
+
wav_temp_path = None
|
|
519
|
+
mp3_temp_path = None
|
|
520
|
+
|
|
521
|
+
try:
|
|
522
|
+
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as wav_temp:
|
|
523
|
+
wav_temp_path = wav_temp.name
|
|
524
|
+
|
|
525
|
+
await asyncio.to_thread(_create_wav_file, wav_temp_path, pcm_data)
|
|
526
|
+
|
|
527
|
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as mp3_temp:
|
|
528
|
+
mp3_temp_path = mp3_temp.name
|
|
529
|
+
|
|
530
|
+
audio = await asyncio.to_thread(AudioSegment.from_wav, wav_temp_path)
|
|
531
|
+
await asyncio.to_thread(audio.export, mp3_temp_path, format="mp3")
|
|
532
|
+
|
|
533
|
+
with open(mp3_temp_path, "rb") as mp3_file:
|
|
534
|
+
mp3_data = mp3_file.read()
|
|
535
|
+
|
|
536
|
+
return mp3_data
|
|
537
|
+
|
|
538
|
+
finally:
|
|
539
|
+
if wav_temp_path:
|
|
540
|
+
try:
|
|
541
|
+
os.remove(wav_temp_path)
|
|
542
|
+
except OSError:
|
|
543
|
+
pass
|
|
544
|
+
if mp3_temp_path:
|
|
545
|
+
try:
|
|
546
|
+
os.remove(mp3_temp_path)
|
|
547
|
+
except OSError:
|
|
548
|
+
pass
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
async def _save_audio_artifact(
|
|
552
|
+
audio_data: bytes,
|
|
553
|
+
filename: str,
|
|
554
|
+
metadata: Dict[str, Any],
|
|
555
|
+
tool_context: ToolContext,
|
|
556
|
+
) -> Dict[str, Any]:
|
|
557
|
+
"""
|
|
558
|
+
Shared function for saving audio artifacts with metadata.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
audio_data: Audio data to save
|
|
562
|
+
filename: Filename for the artifact
|
|
563
|
+
metadata: Metadata dictionary
|
|
564
|
+
tool_context: ADK tool context
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
Save result dictionary
|
|
568
|
+
"""
|
|
569
|
+
inv_context = tool_context._invocation_context
|
|
570
|
+
app_name = inv_context.app_name
|
|
571
|
+
user_id = inv_context.user_id
|
|
572
|
+
session_id = get_original_session_id(inv_context)
|
|
573
|
+
artifact_service = inv_context.artifact_service
|
|
574
|
+
|
|
575
|
+
if not artifact_service:
|
|
576
|
+
raise ValueError("ArtifactService is not available in the context")
|
|
577
|
+
|
|
578
|
+
save_result = await save_artifact_with_metadata(
|
|
579
|
+
artifact_service=artifact_service,
|
|
580
|
+
app_name=app_name,
|
|
581
|
+
user_id=user_id,
|
|
582
|
+
session_id=session_id,
|
|
583
|
+
filename=filename,
|
|
584
|
+
content_bytes=audio_data,
|
|
585
|
+
mime_type="audio/mpeg",
|
|
586
|
+
metadata_dict=metadata,
|
|
587
|
+
timestamp=datetime.now(timezone.utc),
|
|
588
|
+
schema_max_keys=DEFAULT_SCHEMA_MAX_KEYS,
|
|
589
|
+
tool_context=tool_context,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
if save_result.get("status") == "error":
|
|
593
|
+
raise IOError(
|
|
594
|
+
f"Failed to save audio artifact: {save_result.get('message', 'Unknown error')}"
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
return save_result
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
async def select_voice(
|
|
601
|
+
gender: Optional[str] = None,
|
|
602
|
+
tone: Optional[str] = None,
|
|
603
|
+
exclude_voices: Optional[List[str]] = None,
|
|
604
|
+
tool_context: ToolContext = None,
|
|
605
|
+
tool_config: Optional[Dict[str, Any]] = None,
|
|
606
|
+
) -> Dict[str, Any]:
|
|
607
|
+
"""
|
|
608
|
+
Selects a suitable voice name based on criteria like gender and tone.
|
|
609
|
+
Use this to get a consistent voice name that can be passed to the `text_to_speech` tool for multiple calls.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
gender: Optional desired gender for the voice ("male", "female", "neutral").
|
|
613
|
+
tone: Optional tone preference (e.g., "friendly", "professional", "warm").
|
|
614
|
+
exclude_voices: Optional list of voice names to exclude from the selection.
|
|
615
|
+
tool_context: ADK tool context.
|
|
616
|
+
tool_config: Configuration including voice mappings.
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
Dictionary with status and the selected voice name.
|
|
620
|
+
"""
|
|
621
|
+
log_identifier = "[AudioTools:select_voice]"
|
|
622
|
+
log.info(
|
|
623
|
+
f"{log_identifier} Selecting voice for gender='{gender}', tone='{tone}', excluding='{exclude_voices}'"
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
try:
|
|
627
|
+
config = tool_config or {}
|
|
628
|
+
voice_tone_mapping = config.get("voice_tone_mapping", VOICE_TONE_MAPPING)
|
|
629
|
+
gender_voice_mapping = config.get(
|
|
630
|
+
"gender_voice_mapping", GENDER_TO_VOICE_MAPPING
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
used_voices = set(exclude_voices) if exclude_voices else set()
|
|
634
|
+
|
|
635
|
+
selected_voice = _get_voice_for_speaker(
|
|
636
|
+
gender=gender,
|
|
637
|
+
tone=tone,
|
|
638
|
+
used_voices_in_current_call=used_voices,
|
|
639
|
+
voice_tone_mapping=voice_tone_mapping,
|
|
640
|
+
gender_voice_mapping=gender_voice_mapping,
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
log.info(f"{log_identifier} Selected voice: {selected_voice}")
|
|
644
|
+
|
|
645
|
+
return {
|
|
646
|
+
"status": "success",
|
|
647
|
+
"message": f"Successfully selected voice '{selected_voice}'.",
|
|
648
|
+
"voice_name": selected_voice,
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
except Exception as e:
|
|
652
|
+
log.exception(f"{log_identifier} Unexpected error in select_voice: {e}")
|
|
653
|
+
return {"status": "error", "message": f"An unexpected error occurred: {e}"}
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
async def text_to_speech(
|
|
657
|
+
text: str,
|
|
658
|
+
output_filename: Optional[str] = None,
|
|
659
|
+
voice_name: Optional[str] = None,
|
|
660
|
+
gender: Optional[str] = None,
|
|
661
|
+
tone: Optional[str] = None,
|
|
662
|
+
language: Optional[str] = None,
|
|
663
|
+
tool_context: ToolContext = None,
|
|
664
|
+
tool_config: Optional[Dict[str, Any]] = None,
|
|
665
|
+
) -> Dict[str, Any]:
|
|
666
|
+
"""
|
|
667
|
+
Converts text to speech using Gemini TTS API and saves as MP3 artifact.
|
|
668
|
+
|
|
669
|
+
Args:
|
|
670
|
+
text: The text to convert to speech.
|
|
671
|
+
output_filename: Optional filename for the output MP3.
|
|
672
|
+
voice_name: Optional specific voice name (e.g., "Kore", "Puck"). Overrides gender and tone.
|
|
673
|
+
gender: Optional desired gender for the voice ("male", "female", "neutral").
|
|
674
|
+
Used if 'voice_name' is not provided.
|
|
675
|
+
tone: Optional tone preference (e.g., "friendly", "professional", "warm").
|
|
676
|
+
Used if 'voice_name' is not provided, considered after 'gender'.
|
|
677
|
+
language: Optional language code (e.g., "en-US", "fr-FR", "ja-JP").
|
|
678
|
+
tool_context: ADK tool context.
|
|
679
|
+
tool_config: Configuration including API key, model settings, and voice mappings.
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
Dictionary with status, output filename, version, and preview.
|
|
683
|
+
"""
|
|
684
|
+
log_identifier = "[AudioTools:text_to_speech]"
|
|
685
|
+
|
|
686
|
+
if not tool_context:
|
|
687
|
+
return {"status": "error", "message": "ToolContext is missing"}
|
|
688
|
+
|
|
689
|
+
if not text or not text.strip():
|
|
690
|
+
return {"status": "error", "message": "Text input is required"}
|
|
691
|
+
|
|
692
|
+
try:
|
|
693
|
+
log.info(f"{log_identifier} Processing TTS request for text: '{text[:50]}...'")
|
|
694
|
+
|
|
695
|
+
config = tool_config or {}
|
|
696
|
+
api_key = config.get("gemini_api_key")
|
|
697
|
+
model = config.get("model", "gemini-2.5-flash-preview-tts")
|
|
698
|
+
default_voice = config.get("voice_name", DEFAULT_VOICE)
|
|
699
|
+
default_language = config.get("language", "en-US")
|
|
700
|
+
voice_tone_mapping = config.get("voice_tone_mapping", VOICE_TONE_MAPPING)
|
|
701
|
+
gender_voice_mapping = config.get(
|
|
702
|
+
"gender_voice_mapping", GENDER_TO_VOICE_MAPPING
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
if not api_key:
|
|
706
|
+
return {
|
|
707
|
+
"status": "error",
|
|
708
|
+
"message": "GEMINI_API_KEY is required in tool configuration",
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
final_voice = voice_name
|
|
712
|
+
if not final_voice:
|
|
713
|
+
final_voice = _get_voice_for_speaker(
|
|
714
|
+
gender=gender,
|
|
715
|
+
tone=tone,
|
|
716
|
+
used_voices_in_current_call=set(),
|
|
717
|
+
voice_tone_mapping=voice_tone_mapping,
|
|
718
|
+
gender_voice_mapping=gender_voice_mapping,
|
|
719
|
+
)
|
|
720
|
+
log.info(
|
|
721
|
+
f"{log_identifier} Selected voice '{final_voice}' for gender='{gender}', tone='{tone}'"
|
|
722
|
+
)
|
|
723
|
+
else:
|
|
724
|
+
log.info(
|
|
725
|
+
f"{log_identifier} Using specified voice_name '{final_voice}'. Gender/tone selection skipped."
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
if not final_voice:
|
|
729
|
+
final_voice = default_voice
|
|
730
|
+
log.warning(
|
|
731
|
+
f"{log_identifier} Voice selection resulted in None, using default '{default_voice}'."
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
final_language = _get_language_code(language or default_language)
|
|
735
|
+
|
|
736
|
+
client = genai.Client(api_key=api_key)
|
|
737
|
+
|
|
738
|
+
voice_config = _create_voice_config(final_voice)
|
|
739
|
+
speech_config = adk_types.SpeechConfig(voice_config=voice_config)
|
|
740
|
+
|
|
741
|
+
log.info(
|
|
742
|
+
f"{log_identifier} Generating audio with voice '{final_voice}' and language '{final_language}'"
|
|
743
|
+
)
|
|
744
|
+
wav_data = await _generate_audio_with_gemini(
|
|
745
|
+
client=client,
|
|
746
|
+
prompt=f"Say in a clear voice: {text}",
|
|
747
|
+
speech_config=speech_config,
|
|
748
|
+
model=model,
|
|
749
|
+
language=final_language,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
log.info(f"{log_identifier} Converting audio to MP3 format")
|
|
753
|
+
mp3_data = await _convert_pcm_to_mp3(wav_data)
|
|
754
|
+
|
|
755
|
+
final_filename = output_filename
|
|
756
|
+
if not final_filename:
|
|
757
|
+
final_filename = f"tts_audio_{uuid.uuid4()}.mp3"
|
|
758
|
+
elif not final_filename.lower().endswith(".mp3"):
|
|
759
|
+
final_filename = f"{final_filename}.mp3"
|
|
760
|
+
|
|
761
|
+
metadata = {
|
|
762
|
+
"description": f"Text-to-speech audio generated from text input. Used voice: {final_voice}\nSource text: {text[:1500]}",
|
|
763
|
+
"source_text": text[:500],
|
|
764
|
+
"voice_name": final_voice,
|
|
765
|
+
"language": final_language,
|
|
766
|
+
"model": model,
|
|
767
|
+
"generation_tool": "text_to_speech",
|
|
768
|
+
"generation_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if voice_name:
|
|
772
|
+
metadata["requested_voice_name"] = voice_name
|
|
773
|
+
if gender:
|
|
774
|
+
metadata["requested_gender"] = gender
|
|
775
|
+
if tone:
|
|
776
|
+
metadata["requested_tone"] = tone
|
|
777
|
+
if language:
|
|
778
|
+
metadata["requested_language"] = language
|
|
779
|
+
|
|
780
|
+
log.info(f"{log_identifier} Saving audio artifact '{final_filename}'")
|
|
781
|
+
save_result = await _save_audio_artifact(
|
|
782
|
+
audio_data=mp3_data,
|
|
783
|
+
filename=final_filename,
|
|
784
|
+
metadata=metadata,
|
|
785
|
+
tool_context=tool_context,
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
log.info(
|
|
789
|
+
f"{log_identifier} Audio artifact saved successfully: {final_filename} v{save_result['data_version']}"
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
return {
|
|
793
|
+
"status": "success",
|
|
794
|
+
"message": f"Text-to-speech audio generated and saved successfully",
|
|
795
|
+
"output_filename": final_filename,
|
|
796
|
+
"output_version": save_result["data_version"],
|
|
797
|
+
"voice_used": final_voice,
|
|
798
|
+
"language_used": final_language,
|
|
799
|
+
"result_preview": f"Audio '{final_filename}' (v{save_result['data_version']}) created from text: \"{text[:100]}...\"",
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
except ValueError as ve:
|
|
803
|
+
log.error(f"{log_identifier} Value error: {ve}")
|
|
804
|
+
return {"status": "error", "message": str(ve)}
|
|
805
|
+
except IOError as ioe:
|
|
806
|
+
log.error(f"{log_identifier} IO error: {ioe}")
|
|
807
|
+
return {"status": "error", "message": str(ioe)}
|
|
808
|
+
except Exception as e:
|
|
809
|
+
log.exception(f"{log_identifier} Unexpected error in text_to_speech: {e}")
|
|
810
|
+
return {"status": "error", "message": f"An unexpected error occurred: {e}"}
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
async def multi_speaker_text_to_speech(
|
|
814
|
+
conversation_text: str,
|
|
815
|
+
output_filename: Optional[str] = None,
|
|
816
|
+
speaker_configs: Optional[List[Dict[str, str]]] = None,
|
|
817
|
+
language: Optional[str] = None,
|
|
818
|
+
tool_context: ToolContext = None,
|
|
819
|
+
tool_config: Optional[Dict[str, Any]] = None,
|
|
820
|
+
) -> Dict[str, Any]:
|
|
821
|
+
"""
|
|
822
|
+
Converts conversation text with speaker labels to speech using multiple voices.
|
|
823
|
+
|
|
824
|
+
Args:
|
|
825
|
+
conversation_text: Text with speaker labels (e.g., "Speaker1: Hello\\nSpeaker2: Hi there").
|
|
826
|
+
output_filename: Optional filename for the output MP3.
|
|
827
|
+
speaker_configs: Optional list of speaker configurations. Each item is a dictionary:
|
|
828
|
+
`{"name": "Speaker1", "voice": "Kore", "gender": "female", "tone": "firm"}`.
|
|
829
|
+
- `name` (str): The speaker's name as it appears in `conversation_text`.
|
|
830
|
+
- `voice` (Optional[str]): Specific voice name to use. Overrides gender/tone.
|
|
831
|
+
- `gender` (Optional[str]): Desired gender ("male", "female", "neutral").
|
|
832
|
+
- `tone` (Optional[str]): Desired tone (e.g., "friendly").
|
|
833
|
+
If only `gender` and/or `tone` are provided, a voice is selected.
|
|
834
|
+
If no config for a speaker in text, or if speaker_configs is empty,
|
|
835
|
+
default speakers from tool_config are used, or a default voice is assigned.
|
|
836
|
+
language: Optional language code (e.g., "en-US", "fr-FR", "ja-JP").
|
|
837
|
+
tool_context: ADK tool context.
|
|
838
|
+
tool_config: Configuration including API key, model, default speakers, and voice mappings.
|
|
839
|
+
|
|
840
|
+
Returns:
|
|
841
|
+
Dictionary with status, output filename, version, and preview.
|
|
842
|
+
"""
|
|
843
|
+
log_identifier = "[AudioTools:multi_speaker_text_to_speech]"
|
|
844
|
+
|
|
845
|
+
if not tool_context:
|
|
846
|
+
return {"status": "error", "message": "ToolContext is missing"}
|
|
847
|
+
|
|
848
|
+
if not conversation_text or not conversation_text.strip():
|
|
849
|
+
return {"status": "error", "message": "Conversation text input is required"}
|
|
850
|
+
|
|
851
|
+
try:
|
|
852
|
+
log.info(
|
|
853
|
+
f"{log_identifier} Processing multi-speaker TTS request for text: '{conversation_text[:50]}...'"
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
config = tool_config or {}
|
|
857
|
+
api_key = config.get("gemini_api_key")
|
|
858
|
+
model = config.get("model", "gemini-2.5-flash-preview-tts")
|
|
859
|
+
default_language = config.get("language", "en-US")
|
|
860
|
+
default_speakers = config.get(
|
|
861
|
+
"default_speakers",
|
|
862
|
+
[
|
|
863
|
+
{"name": "Speaker1", "voice": "Kore"},
|
|
864
|
+
{
|
|
865
|
+
"name": "Speaker2",
|
|
866
|
+
"voice": "Puck",
|
|
867
|
+
"gender": "male",
|
|
868
|
+
"tone": "upbeat",
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
"name": "Speaker3",
|
|
872
|
+
"voice": "Zephyr",
|
|
873
|
+
"gender": "female",
|
|
874
|
+
"tone": "bright",
|
|
875
|
+
},
|
|
876
|
+
],
|
|
877
|
+
)
|
|
878
|
+
voice_tone_mapping = config.get("voice_tone_mapping", VOICE_TONE_MAPPING)
|
|
879
|
+
gender_voice_mapping = config.get(
|
|
880
|
+
"gender_voice_mapping", GENDER_TO_VOICE_MAPPING
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
if not api_key:
|
|
884
|
+
return {
|
|
885
|
+
"status": "error",
|
|
886
|
+
"message": "GEMINI_API_KEY is required in tool configuration",
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
final_speaker_configs = []
|
|
890
|
+
used_voices_in_current_call: Set[str] = set()
|
|
891
|
+
|
|
892
|
+
configs_to_process = speaker_configs if speaker_configs else default_speakers
|
|
893
|
+
if not configs_to_process and conversation_text:
|
|
894
|
+
log.warning(
|
|
895
|
+
"%s No speaker_configs and no default_speakers. Creating a default speaker.",
|
|
896
|
+
log_identifier,
|
|
897
|
+
)
|
|
898
|
+
configs_to_process = [
|
|
899
|
+
{"name": "Speaker1", "gender": "neutral", "tone": "neutral"}
|
|
900
|
+
]
|
|
901
|
+
|
|
902
|
+
for i, config_item in enumerate(configs_to_process):
|
|
903
|
+
speaker_name = config_item.get("name", f"Speaker{i+1}")
|
|
904
|
+
voice_name_from_config = config_item.get("voice")
|
|
905
|
+
gender_from_config = config_item.get("gender")
|
|
906
|
+
tone_from_config = config_item.get("tone")
|
|
907
|
+
final_voice_for_speaker = None
|
|
908
|
+
|
|
909
|
+
if voice_name_from_config:
|
|
910
|
+
final_voice_for_speaker = voice_name_from_config
|
|
911
|
+
log.info(
|
|
912
|
+
f"{log_identifier} Using specified voice '{final_voice_for_speaker}' for speaker '{speaker_name}'."
|
|
913
|
+
)
|
|
914
|
+
else:
|
|
915
|
+
final_voice_for_speaker = _get_voice_for_speaker(
|
|
916
|
+
gender=gender_from_config,
|
|
917
|
+
tone=tone_from_config,
|
|
918
|
+
used_voices_in_current_call=used_voices_in_current_call,
|
|
919
|
+
voice_tone_mapping=voice_tone_mapping,
|
|
920
|
+
gender_voice_mapping=gender_voice_mapping,
|
|
921
|
+
)
|
|
922
|
+
log.info(
|
|
923
|
+
f"{log_identifier} Selected voice '{final_voice_for_speaker}' for speaker '{speaker_name}' (gender='{gender_from_config}', tone='{tone_from_config}')."
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
if not final_voice_for_speaker:
|
|
927
|
+
final_voice_for_speaker = DEFAULT_VOICE
|
|
928
|
+
log.warning(
|
|
929
|
+
f"{log_identifier} Voice selection for speaker '{speaker_name}' resulted in None, using default '{DEFAULT_VOICE}'."
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
final_speaker_configs.append(
|
|
933
|
+
{"name": speaker_name, "voice": final_voice_for_speaker}
|
|
934
|
+
)
|
|
935
|
+
if final_voice_for_speaker:
|
|
936
|
+
used_voices_in_current_call.add(final_voice_for_speaker)
|
|
937
|
+
|
|
938
|
+
final_language = _get_language_code(language or default_language)
|
|
939
|
+
|
|
940
|
+
client = genai.Client(api_key=api_key)
|
|
941
|
+
|
|
942
|
+
multi_speaker_config = _create_multi_speaker_config(final_speaker_configs)
|
|
943
|
+
speech_config = adk_types.SpeechConfig(
|
|
944
|
+
multi_speaker_voice_config=multi_speaker_config
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
log.info(
|
|
948
|
+
f"{log_identifier} Generating multi-speaker audio with {len(final_speaker_configs)} speakers and language '{final_language}'"
|
|
949
|
+
)
|
|
950
|
+
wav_data = await _generate_audio_with_gemini(
|
|
951
|
+
client=client,
|
|
952
|
+
prompt=f"TTS the following conversation: {conversation_text}",
|
|
953
|
+
speech_config=speech_config,
|
|
954
|
+
model=model,
|
|
955
|
+
language=final_language,
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
log.info(f"{log_identifier} Converting audio to MP3 format")
|
|
959
|
+
mp3_data = await _convert_pcm_to_mp3(wav_data)
|
|
960
|
+
|
|
961
|
+
final_filename = output_filename
|
|
962
|
+
if not final_filename:
|
|
963
|
+
final_filename = f"multi_speaker_tts_{uuid.uuid4()}.mp3"
|
|
964
|
+
elif not final_filename.lower().endswith(".mp3"):
|
|
965
|
+
final_filename = f"{final_filename}.mp3"
|
|
966
|
+
|
|
967
|
+
voice_info = ", ".join(
|
|
968
|
+
[f"{s['name']} ({s['voice']})" for s in final_speaker_configs]
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
metadata = {
|
|
972
|
+
"description": f"Multi-speaker text-to-speech audio generated from conversation. Voices used: {voice_info}\nSource text: {conversation_text[:1500]}...",
|
|
973
|
+
"source_text": conversation_text[:500],
|
|
974
|
+
"language": final_language,
|
|
975
|
+
"model": model,
|
|
976
|
+
"generation_tool": "multi_speaker_text_to_speech",
|
|
977
|
+
"generation_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
978
|
+
"speaker_count": len(final_speaker_configs),
|
|
979
|
+
"speakers_used_details": json.dumps(final_speaker_configs),
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if language:
|
|
983
|
+
metadata["requested_language"] = language
|
|
984
|
+
if speaker_configs:
|
|
985
|
+
metadata["requested_speaker_configs"] = json.dumps(speaker_configs)
|
|
986
|
+
|
|
987
|
+
log.info(f"{log_identifier} Saving audio artifact '{final_filename}'")
|
|
988
|
+
save_result = await _save_audio_artifact(
|
|
989
|
+
audio_data=mp3_data,
|
|
990
|
+
filename=final_filename,
|
|
991
|
+
metadata=metadata,
|
|
992
|
+
tool_context=tool_context,
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
log.info(
|
|
996
|
+
f"{log_identifier} Multi-speaker audio artifact saved successfully: {final_filename} v{save_result['data_version']}"
|
|
997
|
+
)
|
|
998
|
+
|
|
999
|
+
speaker_summary = ", ".join(
|
|
1000
|
+
[f"{s['name']} ({s['voice']})" for s in final_speaker_configs]
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
return {
|
|
1004
|
+
"status": "success",
|
|
1005
|
+
"message": f"Multi-speaker text-to-speech audio generated and saved successfully: Speakers: {speaker_summary}",
|
|
1006
|
+
"output_filename": final_filename,
|
|
1007
|
+
"output_version": save_result["data_version"],
|
|
1008
|
+
"speakers_used": speaker_summary,
|
|
1009
|
+
"language_used": final_language,
|
|
1010
|
+
"result_preview": f"Multi-speaker audio '{final_filename}' (v{save_result['data_version']}) created with {len(final_speaker_configs)} speakers from conversation: \"{conversation_text[:100]}...\"",
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
except ValueError as ve:
|
|
1014
|
+
log.error(f"{log_identifier} Value error: {ve}")
|
|
1015
|
+
return {"status": "error", "message": str(ve)}
|
|
1016
|
+
except IOError as ioe:
|
|
1017
|
+
log.error(f"{log_identifier} IO error: {ioe}")
|
|
1018
|
+
return {"status": "error", "message": str(ioe)}
|
|
1019
|
+
except Exception as e:
|
|
1020
|
+
log.exception(
|
|
1021
|
+
f"{log_identifier} Unexpected error in multi_speaker_text_to_speech: {e}"
|
|
1022
|
+
)
|
|
1023
|
+
return {"status": "error", "message": f"An unexpected error occurred: {e}"}
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def _is_supported_audio_format_for_transcription(filename: str) -> bool:
|
|
1027
|
+
"""Check if the audio format is supported for transcription."""
|
|
1028
|
+
ext = os.path.splitext(filename)[1].lower()
|
|
1029
|
+
supported_formats = {".wav", ".mp3"}
|
|
1030
|
+
return ext in supported_formats
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
def _get_audio_mime_type(filename: str) -> str:
|
|
1034
|
+
"""Get MIME type from audio file extension."""
|
|
1035
|
+
ext = os.path.splitext(filename)[1].lower()
|
|
1036
|
+
mime_mapping = {".wav": "audio/wav", ".mp3": "audio/mpeg"}
|
|
1037
|
+
return mime_mapping.get(ext, "audio/wav")
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
async def concatenate_audio(
|
|
1041
|
+
clips_to_join: List[Dict[str, Any]],
|
|
1042
|
+
output_filename: Optional[str] = None,
|
|
1043
|
+
tool_context: ToolContext = None,
|
|
1044
|
+
tool_config: Optional[Dict[str, Any]] = None,
|
|
1045
|
+
) -> Dict[str, Any]:
|
|
1046
|
+
"""
|
|
1047
|
+
Combines multiple audio artifacts in a specified order into a single audio file.
|
|
1048
|
+
Allows for custom pause durations between each clip.
|
|
1049
|
+
|
|
1050
|
+
Args:
|
|
1051
|
+
clips_to_join: An ordered list of clip objects to be joined. Each object should contain:
|
|
1052
|
+
- `filename` (str): The artifact filename of the audio clip (with optional :version).
|
|
1053
|
+
- `pause_after_ms` (Optional[int]): The duration of silence, in milliseconds,
|
|
1054
|
+
to insert *after* this clip. If omitted, a default pause will be used. The gap between
|
|
1055
|
+
two people speaking in a conversation is typically around 250ms.
|
|
1056
|
+
output_filename: Optional. The desired filename for the final combined audio artifact.
|
|
1057
|
+
tool_context: The context provided by the ADK framework.
|
|
1058
|
+
tool_config: Configuration dictionary.
|
|
1059
|
+
|
|
1060
|
+
Returns:
|
|
1061
|
+
A dictionary with status and output artifact details.
|
|
1062
|
+
"""
|
|
1063
|
+
log_identifier = "[AudioTools:concatenate_audio]"
|
|
1064
|
+
if not tool_context:
|
|
1065
|
+
return {"status": "error", "message": "ToolContext is missing"}
|
|
1066
|
+
if not clips_to_join:
|
|
1067
|
+
return {
|
|
1068
|
+
"status": "error",
|
|
1069
|
+
"message": "The 'clips_to_join' list cannot be empty.",
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
try:
|
|
1073
|
+
inv_context = tool_context._invocation_context
|
|
1074
|
+
app_name = inv_context.app_name
|
|
1075
|
+
user_id = inv_context.user_id
|
|
1076
|
+
session_id = get_original_session_id(inv_context)
|
|
1077
|
+
artifact_service = inv_context.artifact_service
|
|
1078
|
+
host_component = getattr(inv_context.agent, "host_component", None)
|
|
1079
|
+
|
|
1080
|
+
if not artifact_service:
|
|
1081
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
1082
|
+
|
|
1083
|
+
config = tool_config or {}
|
|
1084
|
+
default_pause_ms = config.get("default_pause_ms", 250)
|
|
1085
|
+
|
|
1086
|
+
combined_audio = None
|
|
1087
|
+
source_filenames = []
|
|
1088
|
+
|
|
1089
|
+
for i, clip_info in enumerate(clips_to_join):
|
|
1090
|
+
clip_filename_with_version = clip_info.get("filename")
|
|
1091
|
+
if not clip_filename_with_version:
|
|
1092
|
+
raise ValueError(
|
|
1093
|
+
f"Clip at index {i} is missing the required 'filename' key."
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
source_filenames.append(clip_filename_with_version)
|
|
1097
|
+
|
|
1098
|
+
parts = clip_filename_with_version.rsplit(":", 1)
|
|
1099
|
+
filename_base = parts[0]
|
|
1100
|
+
version_str = None
|
|
1101
|
+
if len(parts) > 1 and parts[1].isdigit():
|
|
1102
|
+
version_str = parts[1]
|
|
1103
|
+
version_to_load = int(version_str) if version_str else "latest"
|
|
1104
|
+
|
|
1105
|
+
load_result = await load_artifact_content_or_metadata(
|
|
1106
|
+
artifact_service=artifact_service,
|
|
1107
|
+
app_name=app_name,
|
|
1108
|
+
user_id=user_id,
|
|
1109
|
+
session_id=session_id,
|
|
1110
|
+
filename=filename_base,
|
|
1111
|
+
version=version_to_load,
|
|
1112
|
+
return_raw_bytes=True,
|
|
1113
|
+
component=host_component,
|
|
1114
|
+
log_identifier_prefix=f"{log_identifier}[LoadClip:{clip_filename_with_version}]",
|
|
1115
|
+
)
|
|
1116
|
+
|
|
1117
|
+
if load_result.get("status") != "success":
|
|
1118
|
+
raise FileNotFoundError(
|
|
1119
|
+
f"Failed to load audio clip '{clip_filename_with_version}': {load_result.get('message')}"
|
|
1120
|
+
)
|
|
1121
|
+
|
|
1122
|
+
audio_bytes = load_result.get("raw_bytes")
|
|
1123
|
+
mime_type = load_result.get("mime_type", "audio/mpeg")
|
|
1124
|
+
|
|
1125
|
+
audio_format = "mp3"
|
|
1126
|
+
if "wav" in mime_type:
|
|
1127
|
+
audio_format = "wav"
|
|
1128
|
+
elif "mpeg" in mime_type:
|
|
1129
|
+
audio_format = "mp3"
|
|
1130
|
+
|
|
1131
|
+
log.debug(
|
|
1132
|
+
f"{log_identifier} Loading clip '{clip_filename_with_version}' with format '{audio_format}'"
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
clip_segment = await asyncio.to_thread(
|
|
1136
|
+
AudioSegment.from_file, io.BytesIO(audio_bytes), format=audio_format
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
if combined_audio is None:
|
|
1140
|
+
combined_audio = clip_segment
|
|
1141
|
+
else:
|
|
1142
|
+
combined_audio += clip_segment
|
|
1143
|
+
|
|
1144
|
+
if i < len(clips_to_join) - 1:
|
|
1145
|
+
pause_ms = clip_info.get("pause_after_ms", default_pause_ms)
|
|
1146
|
+
if pause_ms > 0:
|
|
1147
|
+
pause_segment = AudioSegment.silent(duration=pause_ms)
|
|
1148
|
+
combined_audio += pause_segment
|
|
1149
|
+
log.debug(
|
|
1150
|
+
f"{log_identifier} Added {pause_ms}ms pause after '{clip_filename_with_version}'."
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
if combined_audio is None:
|
|
1154
|
+
return {
|
|
1155
|
+
"status": "error",
|
|
1156
|
+
"message": "No audio clips were successfully processed.",
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
output_buffer = io.BytesIO()
|
|
1160
|
+
await asyncio.to_thread(combined_audio.export, output_buffer, format="mp3")
|
|
1161
|
+
mp3_data = output_buffer.getvalue()
|
|
1162
|
+
|
|
1163
|
+
final_filename = output_filename
|
|
1164
|
+
if not final_filename:
|
|
1165
|
+
final_filename = f"concatenated_audio_{uuid.uuid4()}.mp3"
|
|
1166
|
+
elif not final_filename.lower().endswith(".mp3"):
|
|
1167
|
+
final_filename = f"{final_filename}.mp3"
|
|
1168
|
+
|
|
1169
|
+
metadata = {
|
|
1170
|
+
"description": f"Concatenated audio created from {len(clips_to_join)} clips.",
|
|
1171
|
+
"source_clips": source_filenames,
|
|
1172
|
+
"generation_tool": "concatenate_audio",
|
|
1173
|
+
"generation_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
log.info(
|
|
1177
|
+
f"{log_identifier} Saving concatenated audio artifact '{final_filename}'"
|
|
1178
|
+
)
|
|
1179
|
+
save_result = await _save_audio_artifact(
|
|
1180
|
+
audio_data=mp3_data,
|
|
1181
|
+
filename=final_filename,
|
|
1182
|
+
metadata=metadata,
|
|
1183
|
+
tool_context=tool_context,
|
|
1184
|
+
)
|
|
1185
|
+
|
|
1186
|
+
log.info(
|
|
1187
|
+
f"{log_identifier} Concatenated audio artifact saved successfully: {final_filename} v{save_result['data_version']}"
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
return {
|
|
1191
|
+
"status": "success",
|
|
1192
|
+
"message": f"Audio clips concatenated and saved successfully.",
|
|
1193
|
+
"output_filename": final_filename,
|
|
1194
|
+
"output_version": save_result["data_version"],
|
|
1195
|
+
"result_preview": f"Concatenated audio '{final_filename}' (v{save_result['data_version']}) created from {len(clips_to_join)} clips.",
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
except FileNotFoundError as e:
|
|
1199
|
+
log.warning(f"{log_identifier} File not found error: {e}")
|
|
1200
|
+
return {"status": "error", "message": str(e)}
|
|
1201
|
+
except ValueError as ve:
|
|
1202
|
+
log.error(f"{log_identifier} Value error: {ve}")
|
|
1203
|
+
return {"status": "error", "message": str(ve)}
|
|
1204
|
+
except IOError as ioe:
|
|
1205
|
+
log.error(f"{log_identifier} IO error: {ioe}")
|
|
1206
|
+
return {"status": "error", "message": str(ioe)}
|
|
1207
|
+
except Exception as e:
|
|
1208
|
+
log.exception(f"{log_identifier} Unexpected error in concatenate_audio: {e}")
|
|
1209
|
+
return {"status": "error", "message": f"An unexpected error occurred: {e}"}
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
async def transcribe_audio(
|
|
1213
|
+
audio_filename: str,
|
|
1214
|
+
output_filename: Optional[str] = None,
|
|
1215
|
+
description: Optional[str] = None,
|
|
1216
|
+
tool_context: ToolContext = None,
|
|
1217
|
+
tool_config: Optional[Dict[str, Any]] = None,
|
|
1218
|
+
) -> Dict[str, Any]:
|
|
1219
|
+
"""
|
|
1220
|
+
Transcribes an audio recording and saves the transcription as a text artifact.
|
|
1221
|
+
|
|
1222
|
+
Args:
|
|
1223
|
+
audio_filename: The filename (and optional :version) of the input audio artifact.
|
|
1224
|
+
output_filename: Optional filename for the transcription text file (without extension).
|
|
1225
|
+
description: Optional description of the transcription for metadata.
|
|
1226
|
+
tool_context: The context provided by the ADK framework.
|
|
1227
|
+
tool_config: Configuration dictionary containing model, api_base, api_key.
|
|
1228
|
+
|
|
1229
|
+
Returns:
|
|
1230
|
+
A dictionary containing:
|
|
1231
|
+
- "status": "success" or "error".
|
|
1232
|
+
- "message": A descriptive message about the outcome.
|
|
1233
|
+
- "output_filename": The name of the saved transcription artifact.
|
|
1234
|
+
- "output_version": The version of the saved transcription artifact.
|
|
1235
|
+
- "audio_filename": The name of the input audio artifact.
|
|
1236
|
+
- "audio_version": The version of the input audio artifact.
|
|
1237
|
+
"""
|
|
1238
|
+
log_identifier = f"[AudioTools:transcribe_audio:{audio_filename}]"
|
|
1239
|
+
if not tool_context:
|
|
1240
|
+
log.error(f"{log_identifier} ToolContext is missing.")
|
|
1241
|
+
return {"status": "error", "message": "ToolContext is missing."}
|
|
1242
|
+
|
|
1243
|
+
try:
|
|
1244
|
+
inv_context = tool_context._invocation_context
|
|
1245
|
+
if not inv_context:
|
|
1246
|
+
raise ValueError("InvocationContext is not available.")
|
|
1247
|
+
|
|
1248
|
+
app_name = getattr(inv_context, "app_name", None)
|
|
1249
|
+
user_id = getattr(inv_context, "user_id", None)
|
|
1250
|
+
session_id = get_original_session_id(inv_context)
|
|
1251
|
+
artifact_service = getattr(inv_context, "artifact_service", None)
|
|
1252
|
+
|
|
1253
|
+
if not all([app_name, user_id, session_id, artifact_service]):
|
|
1254
|
+
missing_parts = [
|
|
1255
|
+
part
|
|
1256
|
+
for part, val in [
|
|
1257
|
+
("app_name", app_name),
|
|
1258
|
+
("user_id", user_id),
|
|
1259
|
+
("session_id", session_id),
|
|
1260
|
+
("artifact_service", artifact_service),
|
|
1261
|
+
]
|
|
1262
|
+
if not val
|
|
1263
|
+
]
|
|
1264
|
+
raise ValueError(
|
|
1265
|
+
f"Missing required context parts: {', '.join(missing_parts)}"
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1268
|
+
log.info(f"{log_identifier} Processing request for session {session_id}.")
|
|
1269
|
+
|
|
1270
|
+
current_tool_config = tool_config if tool_config is not None else {}
|
|
1271
|
+
|
|
1272
|
+
if not current_tool_config:
|
|
1273
|
+
log.warning(
|
|
1274
|
+
f"{log_identifier} Tool-specific configuration (tool_config) is empty."
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
model_name = current_tool_config.get("model")
|
|
1278
|
+
api_key = current_tool_config.get("api_key")
|
|
1279
|
+
api_base = current_tool_config.get("api_base")
|
|
1280
|
+
|
|
1281
|
+
if not model_name:
|
|
1282
|
+
raise ValueError("'model' configuration is missing in tool_config.")
|
|
1283
|
+
if not api_key:
|
|
1284
|
+
raise ValueError("'api_key' configuration is missing in tool_config.")
|
|
1285
|
+
if not api_base:
|
|
1286
|
+
raise ValueError("'api_base' configuration is missing in tool_config.")
|
|
1287
|
+
|
|
1288
|
+
log.debug(f"{log_identifier} Using model: {model_name}, API base: {api_base}")
|
|
1289
|
+
|
|
1290
|
+
parts = audio_filename.split(":", 1)
|
|
1291
|
+
filename_base_for_load = parts[0]
|
|
1292
|
+
version_str = parts[1] if len(parts) > 1 else None
|
|
1293
|
+
version_to_load = int(version_str) if version_str else None
|
|
1294
|
+
|
|
1295
|
+
if not _is_supported_audio_format_for_transcription(filename_base_for_load):
|
|
1296
|
+
raise ValueError(f"Unsupported audio format. Supported formats: .wav, .mp3")
|
|
1297
|
+
|
|
1298
|
+
if version_to_load is None:
|
|
1299
|
+
list_versions_method = getattr(artifact_service, "list_versions")
|
|
1300
|
+
if inspect.iscoroutinefunction(list_versions_method):
|
|
1301
|
+
versions = await list_versions_method(
|
|
1302
|
+
app_name=app_name,
|
|
1303
|
+
user_id=user_id,
|
|
1304
|
+
session_id=session_id,
|
|
1305
|
+
filename=filename_base_for_load,
|
|
1306
|
+
)
|
|
1307
|
+
else:
|
|
1308
|
+
versions = await asyncio.to_thread(
|
|
1309
|
+
list_versions_method,
|
|
1310
|
+
app_name=app_name,
|
|
1311
|
+
user_id=user_id,
|
|
1312
|
+
session_id=session_id,
|
|
1313
|
+
filename=filename_base_for_load,
|
|
1314
|
+
)
|
|
1315
|
+
if not versions:
|
|
1316
|
+
raise FileNotFoundError(
|
|
1317
|
+
f"Audio artifact '{filename_base_for_load}' not found."
|
|
1318
|
+
)
|
|
1319
|
+
version_to_load = max(versions)
|
|
1320
|
+
log.debug(
|
|
1321
|
+
f"{log_identifier} Using latest version for input: {version_to_load}"
|
|
1322
|
+
)
|
|
1323
|
+
|
|
1324
|
+
load_artifact_method = getattr(artifact_service, "load_artifact")
|
|
1325
|
+
if inspect.iscoroutinefunction(load_artifact_method):
|
|
1326
|
+
audio_artifact_part = await load_artifact_method(
|
|
1327
|
+
app_name=app_name,
|
|
1328
|
+
user_id=user_id,
|
|
1329
|
+
session_id=session_id,
|
|
1330
|
+
filename=filename_base_for_load,
|
|
1331
|
+
version=version_to_load,
|
|
1332
|
+
)
|
|
1333
|
+
else:
|
|
1334
|
+
audio_artifact_part = await asyncio.to_thread(
|
|
1335
|
+
load_artifact_method,
|
|
1336
|
+
app_name=app_name,
|
|
1337
|
+
user_id=user_id,
|
|
1338
|
+
session_id=session_id,
|
|
1339
|
+
filename=filename_base_for_load,
|
|
1340
|
+
version=version_to_load,
|
|
1341
|
+
)
|
|
1342
|
+
|
|
1343
|
+
if not audio_artifact_part or not audio_artifact_part.inline_data:
|
|
1344
|
+
raise FileNotFoundError(
|
|
1345
|
+
f"Content for audio artifact '{filename_base_for_load}' v{version_to_load} not found."
|
|
1346
|
+
)
|
|
1347
|
+
|
|
1348
|
+
audio_bytes = audio_artifact_part.inline_data.data
|
|
1349
|
+
audio_mime_type = audio_artifact_part.inline_data.mime_type or "application/octet-stream"
|
|
1350
|
+
log.debug(f"{log_identifier} Loaded audio artifact: {len(audio_bytes)} bytes")
|
|
1351
|
+
|
|
1352
|
+
# Load source audio metadata to copy description
|
|
1353
|
+
source_audio_metadata = {}
|
|
1354
|
+
try:
|
|
1355
|
+
metadata_result = await load_artifact_content_or_metadata(
|
|
1356
|
+
artifact_service=artifact_service,
|
|
1357
|
+
app_name=app_name,
|
|
1358
|
+
user_id=user_id,
|
|
1359
|
+
session_id=session_id,
|
|
1360
|
+
filename=filename_base_for_load,
|
|
1361
|
+
version=version_to_load,
|
|
1362
|
+
load_metadata_only=True,
|
|
1363
|
+
log_identifier_prefix=f"{log_identifier}[source_metadata]",
|
|
1364
|
+
)
|
|
1365
|
+
if metadata_result.get("status") == "success":
|
|
1366
|
+
source_audio_metadata = metadata_result.get("metadata", {})
|
|
1367
|
+
log.debug(f"{log_identifier} Loaded source audio metadata")
|
|
1368
|
+
except Exception as meta_err:
|
|
1369
|
+
log.warning(f"{log_identifier} Could not load source audio metadata: {meta_err}")
|
|
1370
|
+
|
|
1371
|
+
temp_file_path = None
|
|
1372
|
+
try:
|
|
1373
|
+
file_ext = os.path.splitext(filename_base_for_load)[1]
|
|
1374
|
+
|
|
1375
|
+
with tempfile.NamedTemporaryFile(
|
|
1376
|
+
suffix=file_ext, delete=False
|
|
1377
|
+
) as temp_file:
|
|
1378
|
+
temp_file_path = temp_file.name
|
|
1379
|
+
temp_file.write(audio_bytes)
|
|
1380
|
+
|
|
1381
|
+
log.debug(f"{log_identifier} Created temporary file: {temp_file_path}")
|
|
1382
|
+
|
|
1383
|
+
api_url = f"{api_base.rstrip('/')}/v1/audio/transcriptions"
|
|
1384
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
1385
|
+
|
|
1386
|
+
mime_type = _get_audio_mime_type(filename_base_for_load)
|
|
1387
|
+
|
|
1388
|
+
with open(temp_file_path, "rb") as audio_file:
|
|
1389
|
+
files = {
|
|
1390
|
+
"file": (filename_base_for_load, audio_file, mime_type),
|
|
1391
|
+
"model": (None, model_name),
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
log.debug(f"{log_identifier} Calling transcription API...")
|
|
1395
|
+
|
|
1396
|
+
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
1397
|
+
response = await client.post(api_url, headers=headers, files=files)
|
|
1398
|
+
response.raise_for_status()
|
|
1399
|
+
response_data = response.json()
|
|
1400
|
+
|
|
1401
|
+
log.debug(f"{log_identifier} Transcription API response received.")
|
|
1402
|
+
|
|
1403
|
+
if not response_data.get("text"):
|
|
1404
|
+
raise ValueError("API response does not contain transcription text.")
|
|
1405
|
+
|
|
1406
|
+
transcription = response_data["text"]
|
|
1407
|
+
|
|
1408
|
+
log.info(
|
|
1409
|
+
f"{log_identifier} Audio transcribed successfully. Transcription length: {len(transcription)} characters"
|
|
1410
|
+
)
|
|
1411
|
+
|
|
1412
|
+
# Determine output filename
|
|
1413
|
+
if output_filename:
|
|
1414
|
+
final_filename = ensure_correct_extension(output_filename, "txt")
|
|
1415
|
+
else:
|
|
1416
|
+
# Auto-generate from source audio filename
|
|
1417
|
+
base_name = os.path.splitext(filename_base_for_load)[0]
|
|
1418
|
+
final_filename = f"{base_name}_transcription.txt"
|
|
1419
|
+
|
|
1420
|
+
# Build comprehensive metadata
|
|
1421
|
+
transcription_word_count = len(transcription.split())
|
|
1422
|
+
transcription_char_count = len(transcription)
|
|
1423
|
+
|
|
1424
|
+
# Build description from multiple sources
|
|
1425
|
+
description_parts = []
|
|
1426
|
+
|
|
1427
|
+
# Add user-provided description
|
|
1428
|
+
if description:
|
|
1429
|
+
description_parts.append(description)
|
|
1430
|
+
|
|
1431
|
+
# Add source audio description if available
|
|
1432
|
+
source_description = source_audio_metadata.get("description")
|
|
1433
|
+
if source_description:
|
|
1434
|
+
description_parts.append(f"Source: {source_description}")
|
|
1435
|
+
|
|
1436
|
+
# Add source audio info
|
|
1437
|
+
description_parts.append(f"Transcribed from audio file '{filename_base_for_load}' (version {version_to_load}, {audio_mime_type})")
|
|
1438
|
+
|
|
1439
|
+
# Combine all description parts
|
|
1440
|
+
final_description = ". ".join(description_parts)
|
|
1441
|
+
|
|
1442
|
+
metadata = {
|
|
1443
|
+
"description": final_description,
|
|
1444
|
+
"source_audio_filename": filename_base_for_load,
|
|
1445
|
+
"source_audio_version": version_to_load,
|
|
1446
|
+
"source_audio_mime_type": audio_mime_type,
|
|
1447
|
+
"transcription_model": model_name,
|
|
1448
|
+
"transcription_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
1449
|
+
"transcription_word_count": transcription_word_count,
|
|
1450
|
+
"transcription_char_count": transcription_char_count,
|
|
1451
|
+
"generation_tool": "transcribe_audio",
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
# Copy source audio description separately for reference
|
|
1455
|
+
if source_description:
|
|
1456
|
+
metadata["source_audio_description"] = source_description
|
|
1457
|
+
|
|
1458
|
+
# Add user-provided description separately if provided
|
|
1459
|
+
if description:
|
|
1460
|
+
metadata["user_provided_description"] = description
|
|
1461
|
+
|
|
1462
|
+
# Save transcription as text artifact
|
|
1463
|
+
transcription_bytes = transcription.encode("utf-8")
|
|
1464
|
+
|
|
1465
|
+
save_result = await save_artifact_with_metadata(
|
|
1466
|
+
artifact_service=artifact_service,
|
|
1467
|
+
app_name=app_name,
|
|
1468
|
+
user_id=user_id,
|
|
1469
|
+
session_id=session_id,
|
|
1470
|
+
filename=final_filename,
|
|
1471
|
+
content_bytes=transcription_bytes,
|
|
1472
|
+
mime_type="text/plain",
|
|
1473
|
+
metadata_dict=metadata,
|
|
1474
|
+
timestamp=datetime.now(timezone.utc),
|
|
1475
|
+
schema_max_keys=DEFAULT_SCHEMA_MAX_KEYS,
|
|
1476
|
+
tool_context=tool_context,
|
|
1477
|
+
)
|
|
1478
|
+
|
|
1479
|
+
if save_result.get("status") != "success":
|
|
1480
|
+
error_msg = save_result.get("message", "Failed to save transcription artifact")
|
|
1481
|
+
log.error(f"{log_identifier} {error_msg}")
|
|
1482
|
+
return {
|
|
1483
|
+
"status": "error",
|
|
1484
|
+
"message": f"Transcription succeeded but failed to save as artifact: {error_msg}",
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
log.info(
|
|
1488
|
+
f"{log_identifier} Transcription saved to '{final_filename}' v{save_result['data_version']}"
|
|
1489
|
+
)
|
|
1490
|
+
|
|
1491
|
+
return {
|
|
1492
|
+
"status": "success",
|
|
1493
|
+
"message": "Audio transcribed and saved successfully",
|
|
1494
|
+
"output_filename": final_filename,
|
|
1495
|
+
"output_version": save_result["data_version"],
|
|
1496
|
+
"audio_filename": filename_base_for_load,
|
|
1497
|
+
"audio_version": version_to_load,
|
|
1498
|
+
"result_preview": f"Transcription saved to '{final_filename}' (v{save_result['data_version']}). Length: {transcription_char_count} characters, {transcription_word_count} words."
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
finally:
|
|
1502
|
+
if temp_file_path and os.path.exists(temp_file_path):
|
|
1503
|
+
try:
|
|
1504
|
+
os.remove(temp_file_path)
|
|
1505
|
+
log.debug(
|
|
1506
|
+
f"{log_identifier} Cleaned up temporary file: {temp_file_path}"
|
|
1507
|
+
)
|
|
1508
|
+
except OSError as e:
|
|
1509
|
+
log.warning(
|
|
1510
|
+
f"{log_identifier} Failed to clean up temporary file {temp_file_path}: {e}"
|
|
1511
|
+
)
|
|
1512
|
+
|
|
1513
|
+
except FileNotFoundError as e:
|
|
1514
|
+
log.warning(f"{log_identifier} File not found error: {e}")
|
|
1515
|
+
return {"status": "error", "message": str(e)}
|
|
1516
|
+
except ValueError as ve:
|
|
1517
|
+
log.error(f"{log_identifier} Value error: {ve}")
|
|
1518
|
+
return {"status": "error", "message": str(ve)}
|
|
1519
|
+
except httpx.HTTPStatusError as hse:
|
|
1520
|
+
log.error(
|
|
1521
|
+
f"{log_identifier} HTTP error calling transcription API: {hse.response.status_code} - {hse.response.text}"
|
|
1522
|
+
)
|
|
1523
|
+
return {"status": "error", "message": f"API error: {hse.response.status_code}"}
|
|
1524
|
+
except httpx.RequestError as re:
|
|
1525
|
+
log.error(f"{log_identifier} Request error calling transcription API: {re}")
|
|
1526
|
+
return {"status": "error", "message": f"Request error: {re}"}
|
|
1527
|
+
except json.JSONDecodeError as jde:
|
|
1528
|
+
log.error(f"{log_identifier} JSON decode error: {jde}")
|
|
1529
|
+
return {"status": "error", "message": "Invalid JSON response from API"}
|
|
1530
|
+
except Exception as e:
|
|
1531
|
+
log.exception(f"{log_identifier} Unexpected error in transcribe_audio: {e}")
|
|
1532
|
+
return {"status": "error", "message": f"An unexpected error occurred: {e}"}
|
|
1533
|
+
|
|
1534
|
+
|
|
1535
|
+
select_voice_tool_def = BuiltinTool(
|
|
1536
|
+
name="select_voice",
|
|
1537
|
+
implementation=select_voice,
|
|
1538
|
+
description="Selects a suitable voice name based on criteria like gender and tone. Use this to get a consistent voice name that can be passed to the `text_to_speech` tool for multiple calls.",
|
|
1539
|
+
category="audio",
|
|
1540
|
+
required_scopes=["tool:audio:tts"],
|
|
1541
|
+
parameters=adk_types.Schema(
|
|
1542
|
+
type=adk_types.Type.OBJECT,
|
|
1543
|
+
properties={
|
|
1544
|
+
"gender": adk_types.Schema(
|
|
1545
|
+
type=adk_types.Type.STRING,
|
|
1546
|
+
description="Optional desired gender for the voice ('male', 'female', 'neutral').",
|
|
1547
|
+
nullable=True,
|
|
1548
|
+
),
|
|
1549
|
+
"tone": adk_types.Schema(
|
|
1550
|
+
type=adk_types.Type.STRING,
|
|
1551
|
+
description="Optional tone preference (e.g., 'friendly', 'professional').",
|
|
1552
|
+
nullable=True,
|
|
1553
|
+
),
|
|
1554
|
+
"exclude_voices": adk_types.Schema(
|
|
1555
|
+
type=adk_types.Type.ARRAY,
|
|
1556
|
+
items=adk_types.Schema(type=adk_types.Type.STRING),
|
|
1557
|
+
description="Optional list of voice names to exclude from the selection.",
|
|
1558
|
+
nullable=True,
|
|
1559
|
+
),
|
|
1560
|
+
},
|
|
1561
|
+
required=[],
|
|
1562
|
+
),
|
|
1563
|
+
examples=[],
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1566
|
+
text_to_speech_tool_def = BuiltinTool(
|
|
1567
|
+
name="text_to_speech",
|
|
1568
|
+
implementation=text_to_speech,
|
|
1569
|
+
description="Converts text to speech using Gemini TTS API and saves as MP3 artifact.",
|
|
1570
|
+
category="audio",
|
|
1571
|
+
required_scopes=["tool:audio:tts"],
|
|
1572
|
+
parameters=adk_types.Schema(
|
|
1573
|
+
type=adk_types.Type.OBJECT,
|
|
1574
|
+
properties={
|
|
1575
|
+
"text": adk_types.Schema(
|
|
1576
|
+
type=adk_types.Type.STRING, description="The text to convert to speech."
|
|
1577
|
+
),
|
|
1578
|
+
"output_filename": adk_types.Schema(
|
|
1579
|
+
type=adk_types.Type.STRING,
|
|
1580
|
+
description="Optional filename for the output MP3.",
|
|
1581
|
+
nullable=True,
|
|
1582
|
+
),
|
|
1583
|
+
"voice_name": adk_types.Schema(
|
|
1584
|
+
type=adk_types.Type.STRING,
|
|
1585
|
+
description="Optional specific voice name (e.g., 'Kore', 'Puck'). Overrides gender and tone.",
|
|
1586
|
+
nullable=True,
|
|
1587
|
+
),
|
|
1588
|
+
"gender": adk_types.Schema(
|
|
1589
|
+
type=adk_types.Type.STRING,
|
|
1590
|
+
description="Optional desired gender for the voice ('male', 'female', 'neutral'). Used if 'voice_name' is not provided.",
|
|
1591
|
+
nullable=True,
|
|
1592
|
+
),
|
|
1593
|
+
"tone": adk_types.Schema(
|
|
1594
|
+
type=adk_types.Type.STRING,
|
|
1595
|
+
description="Optional tone preference (e.g., 'friendly', 'professional'). Used if 'voice_name' is not provided.",
|
|
1596
|
+
nullable=True,
|
|
1597
|
+
),
|
|
1598
|
+
"language": adk_types.Schema(
|
|
1599
|
+
type=adk_types.Type.STRING,
|
|
1600
|
+
description="Optional language code (e.g., 'en-US', 'fr-FR').",
|
|
1601
|
+
nullable=True,
|
|
1602
|
+
),
|
|
1603
|
+
},
|
|
1604
|
+
required=["text"],
|
|
1605
|
+
),
|
|
1606
|
+
examples=[],
|
|
1607
|
+
)
|
|
1608
|
+
|
|
1609
|
+
multi_speaker_text_to_speech_tool_def = BuiltinTool(
|
|
1610
|
+
name="multi_speaker_text_to_speech",
|
|
1611
|
+
implementation=multi_speaker_text_to_speech,
|
|
1612
|
+
description="Converts conversation text with speaker labels to speech using multiple voices.",
|
|
1613
|
+
category="audio",
|
|
1614
|
+
required_scopes=["tool:audio:tts"],
|
|
1615
|
+
parameters=adk_types.Schema(
|
|
1616
|
+
type=adk_types.Type.OBJECT,
|
|
1617
|
+
properties={
|
|
1618
|
+
"conversation_text": adk_types.Schema(
|
|
1619
|
+
type=adk_types.Type.STRING,
|
|
1620
|
+
description="Text with speaker labels (e.g., 'Speaker1: Hello\\nSpeaker2: Hi there').",
|
|
1621
|
+
),
|
|
1622
|
+
"output_filename": adk_types.Schema(
|
|
1623
|
+
type=adk_types.Type.STRING,
|
|
1624
|
+
description="Optional filename for the output MP3.",
|
|
1625
|
+
nullable=True,
|
|
1626
|
+
),
|
|
1627
|
+
"speaker_configs": adk_types.Schema(
|
|
1628
|
+
type=adk_types.Type.ARRAY,
|
|
1629
|
+
items=adk_types.Schema(
|
|
1630
|
+
type=adk_types.Type.OBJECT,
|
|
1631
|
+
description="Configuration for a single speaker.",
|
|
1632
|
+
properties={
|
|
1633
|
+
"name": adk_types.Schema(
|
|
1634
|
+
type=adk_types.Type.STRING,
|
|
1635
|
+
description="The speaker's name as it appears in conversation_text.",
|
|
1636
|
+
),
|
|
1637
|
+
"voice": adk_types.Schema(
|
|
1638
|
+
type=adk_types.Type.STRING,
|
|
1639
|
+
description="Specific voice name to use. Overrides gender/tone.",
|
|
1640
|
+
nullable=True,
|
|
1641
|
+
),
|
|
1642
|
+
"gender": adk_types.Schema(
|
|
1643
|
+
type=adk_types.Type.STRING,
|
|
1644
|
+
description="Desired gender ('male', 'female', 'neutral').",
|
|
1645
|
+
nullable=True,
|
|
1646
|
+
),
|
|
1647
|
+
"tone": adk_types.Schema(
|
|
1648
|
+
type=adk_types.Type.STRING,
|
|
1649
|
+
description="Desired tone (e.g., 'friendly').",
|
|
1650
|
+
nullable=True,
|
|
1651
|
+
),
|
|
1652
|
+
},
|
|
1653
|
+
),
|
|
1654
|
+
description="Optional list of speaker configurations.",
|
|
1655
|
+
nullable=True,
|
|
1656
|
+
),
|
|
1657
|
+
"language": adk_types.Schema(
|
|
1658
|
+
type=adk_types.Type.STRING,
|
|
1659
|
+
description="Optional language code (e.g., 'en-US', 'fr-FR').",
|
|
1660
|
+
nullable=True,
|
|
1661
|
+
),
|
|
1662
|
+
},
|
|
1663
|
+
required=["conversation_text"],
|
|
1664
|
+
),
|
|
1665
|
+
examples=[],
|
|
1666
|
+
)
|
|
1667
|
+
|
|
1668
|
+
concatenate_audio_tool_def = BuiltinTool(
|
|
1669
|
+
name="concatenate_audio",
|
|
1670
|
+
implementation=concatenate_audio,
|
|
1671
|
+
description="Combines multiple audio artifacts in a specified order into a single audio file. Allows for custom pause durations between each clip.",
|
|
1672
|
+
category="audio",
|
|
1673
|
+
required_scopes=["tool:audio:edit", "tool:artifact:create", "tool:artifact:load"],
|
|
1674
|
+
parameters=adk_types.Schema(
|
|
1675
|
+
type=adk_types.Type.OBJECT,
|
|
1676
|
+
properties={
|
|
1677
|
+
"clips_to_join": adk_types.Schema(
|
|
1678
|
+
type=adk_types.Type.ARRAY,
|
|
1679
|
+
description="An ordered list of clip objects to be joined.",
|
|
1680
|
+
items=adk_types.Schema(
|
|
1681
|
+
type=adk_types.Type.OBJECT,
|
|
1682
|
+
properties={
|
|
1683
|
+
"filename": adk_types.Schema(
|
|
1684
|
+
type=adk_types.Type.STRING,
|
|
1685
|
+
description="The artifact filename of the audio clip (with optional :version).",
|
|
1686
|
+
),
|
|
1687
|
+
"pause_after_ms": adk_types.Schema(
|
|
1688
|
+
type=adk_types.Type.INTEGER,
|
|
1689
|
+
description="Optional duration of silence in milliseconds to insert *after* this clip. Defaults to 500ms.",
|
|
1690
|
+
nullable=True,
|
|
1691
|
+
),
|
|
1692
|
+
},
|
|
1693
|
+
required=["filename"],
|
|
1694
|
+
),
|
|
1695
|
+
),
|
|
1696
|
+
"output_filename": adk_types.Schema(
|
|
1697
|
+
type=adk_types.Type.STRING,
|
|
1698
|
+
description="Optional. The desired filename for the final combined audio artifact.",
|
|
1699
|
+
nullable=True,
|
|
1700
|
+
),
|
|
1701
|
+
},
|
|
1702
|
+
required=["clips_to_join"],
|
|
1703
|
+
),
|
|
1704
|
+
examples=[],
|
|
1705
|
+
)
|
|
1706
|
+
|
|
1707
|
+
transcribe_audio_tool_def = BuiltinTool(
|
|
1708
|
+
name="transcribe_audio",
|
|
1709
|
+
implementation=transcribe_audio,
|
|
1710
|
+
description="Transcribes an audio recording and saves the transcription as a text artifact.",
|
|
1711
|
+
category="audio",
|
|
1712
|
+
required_scopes=["tool:audio:transcribe", "tool:artifact:create"],
|
|
1713
|
+
parameters=adk_types.Schema(
|
|
1714
|
+
type=adk_types.Type.OBJECT,
|
|
1715
|
+
properties={
|
|
1716
|
+
"audio_filename": adk_types.Schema(
|
|
1717
|
+
type=adk_types.Type.STRING,
|
|
1718
|
+
description="The filename (and optional :version) of the input audio artifact.",
|
|
1719
|
+
),
|
|
1720
|
+
"output_filename": adk_types.Schema(
|
|
1721
|
+
type=adk_types.Type.STRING,
|
|
1722
|
+
description="Optional filename for the transcription text file (without .txt extension). If not provided, will auto-generate from source audio filename.",
|
|
1723
|
+
nullable=True,
|
|
1724
|
+
),
|
|
1725
|
+
"description": adk_types.Schema(
|
|
1726
|
+
type=adk_types.Type.STRING,
|
|
1727
|
+
description="Optional description of the transcription for metadata (e.g., 'Transcription of customer support call about billing inquiry'). Will be combined with source audio description if available.",
|
|
1728
|
+
nullable=True,
|
|
1729
|
+
),
|
|
1730
|
+
},
|
|
1731
|
+
required=["audio_filename"],
|
|
1732
|
+
),
|
|
1733
|
+
examples=[],
|
|
1734
|
+
)
|
|
1735
|
+
|
|
1736
|
+
tool_registry.register(select_voice_tool_def)
|
|
1737
|
+
tool_registry.register(text_to_speech_tool_def)
|
|
1738
|
+
tool_registry.register(multi_speaker_text_to_speech_tool_def)
|
|
1739
|
+
tool_registry.register(concatenate_audio_tool_def)
|
|
1740
|
+
tool_registry.register(transcribe_audio_tool_def)
|