solace-agent-mesh 1.5.1__py3-none-any.whl → 1.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/callbacks.py +0 -5
- solace_agent_mesh/agent/adk/models/lite_llm.py +123 -8
- solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +245 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +40 -1
- 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/app.py +55 -0
- solace_agent_mesh/agent/proxies/a2a/component.py +1115 -0
- solace_agent_mesh/agent/proxies/a2a/config.py +140 -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 +99 -0
- solace_agent_mesh/agent/proxies/base/component.py +619 -0
- solace_agent_mesh/agent/proxies/base/config.py +85 -0
- solace_agent_mesh/agent/proxies/base/proxy_task_context.py +17 -0
- solace_agent_mesh/agent/sac/app.py +9 -3
- solace_agent_mesh/agent/sac/component.py +160 -8
- solace_agent_mesh/agent/tools/audio_tools.py +125 -8
- solace_agent_mesh/agent/tools/web_tools.py +10 -5
- solace_agent_mesh/agent/utils/artifact_helpers.py +141 -3
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{ad71b5ed.60668e9e.js → ad71b5ed.af3ecfd1.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{da0b5bad.9d369087.js → da0b5bad.d08a9466.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js → main.20feee82.js} +2 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.0d198646.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +15 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +262 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +31 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +6 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +6 -5
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +100 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
- solace_agent_mesh/assets/docs/lunr-index-1761165361160.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1761165361160.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +2 -69
- solace_agent_mesh/cli/commands/eval_cmd.py +11 -49
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +0 -5
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +10 -12
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +9 -61
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +9 -49
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +1 -2
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DwrxZE0E.js → authCallback-BTf6dqwp.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-DarGQzyw.js → client-CaY59VuC.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BGTaW0uv.js +342 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{vendor-BKIeiHj_.js → vendor-BEmvJSYz.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
- solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
- solace_agent_mesh/common/a2a/__init__.py +24 -0
- solace_agent_mesh/common/a2a/artifact.py +39 -0
- solace_agent_mesh/common/a2a/events.py +29 -0
- solace_agent_mesh/common/a2a/message.py +68 -0
- solace_agent_mesh/common/a2a/protocol.py +73 -1
- solace_agent_mesh/common/agent_registry.py +83 -3
- solace_agent_mesh/common/constants.py +3 -1
- solace_agent_mesh/common/utils/pydantic_utils.py +12 -0
- solace_agent_mesh/config_portal/backend/common.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +98 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-44d62be6.js → manifest-61038fc6.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
- solace_agent_mesh/evaluation/evaluator.py +128 -104
- solace_agent_mesh/evaluation/message_organizer.py +116 -110
- solace_agent_mesh/evaluation/report_data_processor.py +84 -86
- solace_agent_mesh/evaluation/report_generator.py +73 -79
- solace_agent_mesh/evaluation/run.py +421 -235
- 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 +111 -232
- solace_agent_mesh/evaluation/summary_builder.py +227 -117
- solace_agent_mesh/gateway/base/app.py +1 -1
- solace_agent_mesh/gateway/base/component.py +8 -1
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
- solace_agent_mesh/gateway/http_sse/component.py +98 -2
- solace_agent_mesh/gateway/http_sse/dependencies.py +4 -4
- solace_agent_mesh/gateway/http_sse/main.py +2 -1
- solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +12 -13
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +15 -18
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +25 -18
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +30 -26
- solace_agent_mesh/gateway/http_sse/repository/task_repository.py +35 -44
- solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +4 -3
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +95 -203
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +4 -3
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +2 -2
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +33 -41
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +17 -11
- solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +4 -4
- solace_agent_mesh/gateway/http_sse/services/feedback_service.py +51 -43
- solace_agent_mesh/gateway/http_sse/services/session_service.py +20 -20
- solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +8 -8
- solace_agent_mesh/gateway/http_sse/shared/base_repository.py +45 -71
- solace_agent_mesh/gateway/http_sse/shared/types.py +0 -18
- solace_agent_mesh/templates/gateway_config_template.yaml +0 -5
- solace_agent_mesh/templates/logging_config_template.ini +10 -6
- solace_agent_mesh/templates/plugin_gateway_config_template.yaml +0 -3
- solace_agent_mesh/templates/shared_config.yaml +40 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/METADATA +47 -21
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/RECORD +162 -141
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.e49689dd.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.39d5851d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/71da7b71.804d6567.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/77cf947d.64c9bd6c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/9e9d0a82.dd810042.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/db924877.cbc66f02.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/de915948.139b4b9c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.582a78ca.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.5766a13d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.9c0297a6.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/runtime~main.18dc45dd.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1760121512891.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1760121512891.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-2nd1gbaH.js +0 -339
- solace_agent_mesh/client/webui/frontend/static/assets/main-DoKXctCM.css +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-BNuqpWDc.js +0 -98
- solace_agent_mesh/evaluation/config_loader.py +0 -657
- solace_agent_mesh/evaluation/test_case_loader.py +0 -714
- /solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js.LICENSE.txt → main.20feee82.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic configuration models for proxy applications.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Literal, Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import Field, model_validator
|
|
8
|
+
|
|
9
|
+
from ....common.utils.pydantic_utils import SamConfigBase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ArtifactServiceConfig(SamConfigBase):
|
|
13
|
+
"""Configuration for the shared Artifact Service."""
|
|
14
|
+
|
|
15
|
+
type: str = Field(
|
|
16
|
+
..., description="Service type (e.g., 'memory', 'gcs', 'filesystem')."
|
|
17
|
+
)
|
|
18
|
+
base_path: Optional[str] = Field(
|
|
19
|
+
default=None,
|
|
20
|
+
description="Base directory path (required for type 'filesystem').",
|
|
21
|
+
)
|
|
22
|
+
bucket_name: Optional[str] = Field(
|
|
23
|
+
default=None, description="GCS bucket name (required for type 'gcs')."
|
|
24
|
+
)
|
|
25
|
+
artifact_scope: Literal["namespace", "app", "custom"] = Field(
|
|
26
|
+
default="namespace", description="Process-wide scope for all artifact services."
|
|
27
|
+
)
|
|
28
|
+
artifact_scope_value: Optional[str] = Field(
|
|
29
|
+
default=None,
|
|
30
|
+
description="Custom identifier for artifact scope (required if artifact_scope is 'custom').",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@model_validator(mode="after")
|
|
34
|
+
def check_artifact_scope(self) -> "ArtifactServiceConfig":
|
|
35
|
+
if self.artifact_scope == "custom" and not self.artifact_scope_value:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
"'artifact_scope_value' is required when 'artifact_scope' is 'custom'."
|
|
38
|
+
)
|
|
39
|
+
if self.artifact_scope != "custom" and self.artifact_scope_value:
|
|
40
|
+
from solace_ai_connector.common.log import log
|
|
41
|
+
log.warning(
|
|
42
|
+
"Configuration Warning: 'artifact_scope_value' is ignored when 'artifact_scope' is not 'custom'."
|
|
43
|
+
)
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ProxiedAgentConfig(SamConfigBase):
|
|
48
|
+
"""Base configuration for a proxied agent."""
|
|
49
|
+
|
|
50
|
+
name: str = Field(
|
|
51
|
+
...,
|
|
52
|
+
description="The name the agent will have on the Solace mesh.",
|
|
53
|
+
)
|
|
54
|
+
request_timeout_seconds: Optional[int] = Field(
|
|
55
|
+
default=None,
|
|
56
|
+
description="Optional timeout override for this specific agent.",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class BaseProxyAppConfig(SamConfigBase):
|
|
61
|
+
"""Base configuration for all proxy applications."""
|
|
62
|
+
|
|
63
|
+
namespace: str = Field(
|
|
64
|
+
...,
|
|
65
|
+
description="Absolute topic prefix for A2A communication (e.g., 'myorg/dev').",
|
|
66
|
+
)
|
|
67
|
+
proxied_agents: List[ProxiedAgentConfig] = Field(
|
|
68
|
+
...,
|
|
69
|
+
min_length=1,
|
|
70
|
+
description="A list of downstream agents to be proxied.",
|
|
71
|
+
)
|
|
72
|
+
artifact_service: ArtifactServiceConfig = Field(
|
|
73
|
+
default_factory=lambda: ArtifactServiceConfig(type="memory"),
|
|
74
|
+
description="Configuration for the shared Artifact Service.",
|
|
75
|
+
)
|
|
76
|
+
discovery_interval_seconds: int = Field(
|
|
77
|
+
default=60,
|
|
78
|
+
ge=0,
|
|
79
|
+
description="Interval (seconds) to re-fetch agent cards. <= 0 disables periodic discovery.",
|
|
80
|
+
)
|
|
81
|
+
default_request_timeout_seconds: int = Field(
|
|
82
|
+
default=300,
|
|
83
|
+
gt=0,
|
|
84
|
+
description="Default timeout in seconds for requests to downstream agents.",
|
|
85
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Encapsulates the runtime state for a single, in-flight proxied agent task.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ProxyTaskContext:
|
|
11
|
+
"""
|
|
12
|
+
A class to hold all runtime state and control mechanisms for a single proxied agent task.
|
|
13
|
+
This object is created when a task is initiated and destroyed when it completes.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
task_id: str
|
|
17
|
+
a2a_context: Dict[str, Any]
|
|
@@ -23,7 +23,7 @@ from ...common.a2a import (
|
|
|
23
23
|
get_agent_status_subscription_topic,
|
|
24
24
|
get_sam_events_subscription_topic,
|
|
25
25
|
)
|
|
26
|
-
from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT, TEXT_ARTIFACT_CONTEXT_MAX_LENGTH_CAPACITY, TEXT_ARTIFACT_CONTEXT_DEFAULT_LENGTH
|
|
26
|
+
from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT, TEXT_ARTIFACT_CONTEXT_MAX_LENGTH_CAPACITY, TEXT_ARTIFACT_CONTEXT_DEFAULT_LENGTH, HEALTH_CHECK_TTL_SECONDS, HEALTH_CHECK_INTERVAL_SECONDS
|
|
27
27
|
from ...agent.sac.component import SamAgentComponent
|
|
28
28
|
from ...agent.utils.artifact_helpers import DEFAULT_SCHEMA_MAX_KEYS
|
|
29
29
|
from ...common.utils.pydantic_utils import SamConfigBase
|
|
@@ -79,6 +79,12 @@ class AgentDiscoveryConfig(SamConfigBase):
|
|
|
79
79
|
enabled: bool = Field(
|
|
80
80
|
default=True, description="Enable discovery and instruction injection."
|
|
81
81
|
)
|
|
82
|
+
health_check_ttl_seconds: int = Field(
|
|
83
|
+
default=HEALTH_CHECK_TTL_SECONDS, description="Time-to-live in seconds after which an unresponsive agent is de-registered."
|
|
84
|
+
)
|
|
85
|
+
health_check_interval_seconds: int = Field(
|
|
86
|
+
default=HEALTH_CHECK_INTERVAL_SECONDS, description="Interval in seconds between health checks."
|
|
87
|
+
)
|
|
82
88
|
|
|
83
89
|
|
|
84
90
|
class InterAgentCommunicationConfig(SamConfigBase):
|
|
@@ -452,8 +458,8 @@ class SamAgentApp(App):
|
|
|
452
458
|
broker_config["queue_name"] = generated_queue_name
|
|
453
459
|
log.debug("Injected generated broker.queue_name: %s", generated_queue_name)
|
|
454
460
|
|
|
455
|
-
broker_config["temporary_queue"] = True
|
|
456
|
-
log.debug("Set broker_config.temporary_queue =
|
|
461
|
+
broker_config["temporary_queue"] = app_info.get("broker", {}).get("temporary_queue", True)
|
|
462
|
+
log.debug("Set broker_config.temporary_queue = %s", broker_config["temporary_queue"])
|
|
457
463
|
|
|
458
464
|
super().__init__(app_info, **kwargs)
|
|
459
465
|
log.debug("%s Agent initialization complete.", agent_name)
|
|
@@ -8,10 +8,8 @@ import asyncio
|
|
|
8
8
|
import functools
|
|
9
9
|
import threading
|
|
10
10
|
import concurrent.futures
|
|
11
|
-
import uuid
|
|
12
11
|
import fnmatch
|
|
13
|
-
import
|
|
14
|
-
from datetime import datetime, timezone
|
|
12
|
+
import time
|
|
15
13
|
import json
|
|
16
14
|
from solace_ai_connector.common.message import (
|
|
17
15
|
Message as SolaceMessage,
|
|
@@ -73,9 +71,10 @@ from ...agent.tools.peer_agent_tool import (
|
|
|
73
71
|
PEER_TOOL_PREFIX,
|
|
74
72
|
)
|
|
75
73
|
from ...common.middleware.registry import MiddlewareRegistry
|
|
76
|
-
from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT
|
|
74
|
+
from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT, HEALTH_CHECK_TTL_SECONDS, HEALTH_CHECK_INTERVAL_SECONDS
|
|
77
75
|
from ...agent.tools.registry import tool_registry
|
|
78
76
|
from ...common.sac.sam_component_base import SamComponentBase
|
|
77
|
+
from ...common.agent_registry import AgentRegistry
|
|
79
78
|
|
|
80
79
|
log = logging.getLogger(__name__)
|
|
81
80
|
|
|
@@ -113,6 +112,7 @@ class SamAgentComponent(SamComponentBase):
|
|
|
113
112
|
|
|
114
113
|
CORRELATION_DATA_PREFIX = CORRELATION_DATA_PREFIX
|
|
115
114
|
HOST_COMPONENT_VERSION = "1.0.0-alpha"
|
|
115
|
+
HEALTH_CHECK_TIMER_ID = "agent_health_check"
|
|
116
116
|
|
|
117
117
|
def __init__(self, **kwargs):
|
|
118
118
|
"""
|
|
@@ -129,6 +129,9 @@ class SamAgentComponent(SamComponentBase):
|
|
|
129
129
|
super().__init__(info, **kwargs)
|
|
130
130
|
self.agent_name = self.get_config("agent_name")
|
|
131
131
|
log.info("%s Initializing A2A ADK Host Component...", self.log_identifier)
|
|
132
|
+
|
|
133
|
+
# Initialize the agent registry for health tracking
|
|
134
|
+
self.agent_registry = AgentRegistry()
|
|
132
135
|
try:
|
|
133
136
|
self.namespace = self.get_config("namespace")
|
|
134
137
|
if not self.namespace:
|
|
@@ -237,7 +240,7 @@ class SamAgentComponent(SamComponentBase):
|
|
|
237
240
|
self.adk_agent: LlmAgent = None
|
|
238
241
|
self.runner: Runner = None
|
|
239
242
|
self.agent_card_tool_manifest: List[Dict[str, Any]] = []
|
|
240
|
-
self.peer_agents: Dict[str, Any] = {}
|
|
243
|
+
self.peer_agents: Dict[str, Any] = {} # Keep for backward compatibility
|
|
241
244
|
self._card_publish_timer_id: str = f"publish_card_{self.agent_name}"
|
|
242
245
|
self._async_init_future = None
|
|
243
246
|
self.peer_response_queues: Dict[str, asyncio.Queue] = {}
|
|
@@ -414,6 +417,26 @@ class SamAgentComponent(SamComponentBase):
|
|
|
414
417
|
"%s Agent card publishing interval not configured or invalid, card will not be published periodically.",
|
|
415
418
|
self.log_identifier,
|
|
416
419
|
)
|
|
420
|
+
|
|
421
|
+
# Set up health check timer if enabled
|
|
422
|
+
health_check_interval_seconds = self.agent_discovery_config.get("health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
|
|
423
|
+
if health_check_interval_seconds > 0:
|
|
424
|
+
log.info(
|
|
425
|
+
"%s Scheduling agent health check every %d seconds.",
|
|
426
|
+
self.log_identifier,
|
|
427
|
+
health_check_interval_seconds,
|
|
428
|
+
)
|
|
429
|
+
self.add_timer(
|
|
430
|
+
delay_ms=health_check_interval_seconds * 1000,
|
|
431
|
+
timer_id=self.HEALTH_CHECK_TIMER_ID,
|
|
432
|
+
interval_ms=health_check_interval_seconds * 1000,
|
|
433
|
+
)
|
|
434
|
+
else:
|
|
435
|
+
log.warning(
|
|
436
|
+
"%s Agent health check interval not configured or invalid, health checks will not run periodically.",
|
|
437
|
+
self.log_identifier,
|
|
438
|
+
)
|
|
439
|
+
|
|
417
440
|
log.info(
|
|
418
441
|
"%s Initialization complete for agent: %s",
|
|
419
442
|
self.log_identifier,
|
|
@@ -488,10 +511,14 @@ class SamAgentComponent(SamComponentBase):
|
|
|
488
511
|
)
|
|
489
512
|
|
|
490
513
|
def handle_timer_event(self, timer_data: Dict[str, Any]):
|
|
491
|
-
"""Handles timer events
|
|
514
|
+
"""Handles timer events for agent card publishing and health checks."""
|
|
492
515
|
log.debug("%s Received timer event: %s", self.log_identifier, timer_data)
|
|
493
|
-
|
|
516
|
+
timer_id = timer_data.get("timer_id")
|
|
517
|
+
|
|
518
|
+
if timer_id == self._card_publish_timer_id:
|
|
494
519
|
publish_agent_card(self)
|
|
520
|
+
elif timer_id == self.HEALTH_CHECK_TIMER_ID:
|
|
521
|
+
self._check_agent_health()
|
|
495
522
|
|
|
496
523
|
async def handle_cache_expiry_event(self, cache_data: Dict[str, Any]):
|
|
497
524
|
"""
|
|
@@ -866,7 +893,8 @@ class SamAgentComponent(SamComponentBase):
|
|
|
866
893
|
peer_tools_to_add = []
|
|
867
894
|
allowed_peer_descriptions = []
|
|
868
895
|
|
|
869
|
-
|
|
896
|
+
# Sort peer agents alphabetically to ensure consistent tool ordering for prompt caching
|
|
897
|
+
for peer_name, agent_card in sorted(self.peer_agents.items()):
|
|
870
898
|
if not isinstance(agent_card, AgentCard) or peer_name == self_name:
|
|
871
899
|
continue
|
|
872
900
|
|
|
@@ -2991,6 +3019,7 @@ class SamAgentComponent(SamComponentBase):
|
|
|
2991
3019
|
"""Clean up resources on component shutdown."""
|
|
2992
3020
|
log.info("%s Cleaning up A2A ADK Host Component.", self.log_identifier)
|
|
2993
3021
|
self.cancel_timer(self._card_publish_timer_id)
|
|
3022
|
+
self.cancel_timer(self.HEALTH_CHECK_TIMER_ID)
|
|
2994
3023
|
|
|
2995
3024
|
cleanup_func_details = self.get_config("agent_cleanup_function")
|
|
2996
3025
|
|
|
@@ -3151,6 +3180,129 @@ class SamAgentComponent(SamComponentBase):
|
|
|
3151
3180
|
For now, using the agent name, but could be made more robust (e.g., hostname + agent name).
|
|
3152
3181
|
"""
|
|
3153
3182
|
return self.agent_name
|
|
3183
|
+
|
|
3184
|
+
def _check_agent_health(self):
|
|
3185
|
+
"""
|
|
3186
|
+
Checks the health of peer agents and de-registers unresponsive ones.
|
|
3187
|
+
This is called periodically by the health check timer.
|
|
3188
|
+
Uses TTL-based expiration to determine if an agent is unresponsive.
|
|
3189
|
+
"""
|
|
3190
|
+
|
|
3191
|
+
log.debug("%s Performing agent health check...", self.log_identifier)
|
|
3192
|
+
|
|
3193
|
+
ttl_seconds = self.agent_discovery_config.get("health_check_ttl_seconds", HEALTH_CHECK_TTL_SECONDS)
|
|
3194
|
+
health_check_interval = self.agent_discovery_config.get("health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
|
|
3195
|
+
|
|
3196
|
+
log.debug(
|
|
3197
|
+
"%s Health check configuration: interval=%d seconds, TTL=%d seconds",
|
|
3198
|
+
self.log_identifier,
|
|
3199
|
+
health_check_interval,
|
|
3200
|
+
ttl_seconds
|
|
3201
|
+
)
|
|
3202
|
+
|
|
3203
|
+
# Validate configuration values
|
|
3204
|
+
if ttl_seconds <= 0 or health_check_interval <= 0 or ttl_seconds < health_check_interval:
|
|
3205
|
+
log.error(
|
|
3206
|
+
"%s agent_health_check_ttl_seconds (%d) and agent_health_check_interval_seconds (%d) must be positive and TTL must be greater than interval.",
|
|
3207
|
+
self.log_identifier,
|
|
3208
|
+
ttl_seconds,
|
|
3209
|
+
health_check_interval
|
|
3210
|
+
)
|
|
3211
|
+
raise ValueError(f"Invalid health check configuration. agent_health_check_ttl_seconds ({ttl_seconds}) and agent_health_check_interval_seconds ({health_check_interval}) must be positive and TTL must be greater than interval.")
|
|
3212
|
+
|
|
3213
|
+
# Get all agent names from the registry
|
|
3214
|
+
agent_names = self.agent_registry.get_agent_names()
|
|
3215
|
+
total_agents = len(agent_names)
|
|
3216
|
+
agents_to_deregister = []
|
|
3217
|
+
|
|
3218
|
+
log.debug("%s Checking health of %d peer agents", self.log_identifier, total_agents)
|
|
3219
|
+
|
|
3220
|
+
for agent_name in agent_names:
|
|
3221
|
+
# Skip our own agent
|
|
3222
|
+
if agent_name == self.agent_name:
|
|
3223
|
+
continue
|
|
3224
|
+
|
|
3225
|
+
# Check if the agent's TTL has expired
|
|
3226
|
+
is_expired, time_since_last_seen = self.agent_registry.check_ttl_expired(agent_name, ttl_seconds)
|
|
3227
|
+
|
|
3228
|
+
if is_expired:
|
|
3229
|
+
log.warning(
|
|
3230
|
+
"%s Agent '%s' TTL has expired. De-registering. Time since last seen: %d seconds (TTL: %d seconds)",
|
|
3231
|
+
self.log_identifier,
|
|
3232
|
+
agent_name,
|
|
3233
|
+
time_since_last_seen,
|
|
3234
|
+
ttl_seconds
|
|
3235
|
+
)
|
|
3236
|
+
agents_to_deregister.append(agent_name)
|
|
3237
|
+
|
|
3238
|
+
# De-register unresponsive agents
|
|
3239
|
+
for agent_name in agents_to_deregister:
|
|
3240
|
+
self._deregister_agent(agent_name)
|
|
3241
|
+
|
|
3242
|
+
log.debug(
|
|
3243
|
+
"%s Agent health check completed. Total agents: %d, De-registered: %d",
|
|
3244
|
+
self.log_identifier,
|
|
3245
|
+
total_agents,
|
|
3246
|
+
len(agents_to_deregister)
|
|
3247
|
+
)
|
|
3248
|
+
|
|
3249
|
+
def _deregister_agent(self, agent_name: str):
|
|
3250
|
+
"""
|
|
3251
|
+
De-registers an agent from the registry and publishes a de-registration event.
|
|
3252
|
+
"""
|
|
3253
|
+
# Remove from registry
|
|
3254
|
+
registry_removed = self.agent_registry.remove_agent(agent_name)
|
|
3255
|
+
|
|
3256
|
+
# Always remove from peer_agents regardless of registry result
|
|
3257
|
+
peer_removed = False
|
|
3258
|
+
if agent_name in self.peer_agents:
|
|
3259
|
+
del self.peer_agents[agent_name]
|
|
3260
|
+
peer_removed = True
|
|
3261
|
+
log.info(
|
|
3262
|
+
"%s Removed agent '%s' from peer_agents dictionary",
|
|
3263
|
+
self.log_identifier,
|
|
3264
|
+
agent_name
|
|
3265
|
+
)
|
|
3266
|
+
|
|
3267
|
+
# Publish de-registration event if agent was in either data structure
|
|
3268
|
+
if registry_removed or peer_removed:
|
|
3269
|
+
try:
|
|
3270
|
+
# Create a de-registration event topic
|
|
3271
|
+
namespace = self.get_config("namespace")
|
|
3272
|
+
deregistration_topic = f"{namespace}/a2a/events/agent/deregistered"
|
|
3273
|
+
|
|
3274
|
+
current_time = time.time()
|
|
3275
|
+
|
|
3276
|
+
# Create the payload
|
|
3277
|
+
deregistration_payload = {
|
|
3278
|
+
"event_type": "agent.deregistered",
|
|
3279
|
+
"agent_name": agent_name,
|
|
3280
|
+
"reason": "health_check_failure",
|
|
3281
|
+
"metadata": {
|
|
3282
|
+
"timestamp": current_time,
|
|
3283
|
+
"deregistered_by": self.agent_name
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
# Publish the event
|
|
3288
|
+
self.publish_a2a_message(
|
|
3289
|
+
payload=deregistration_payload,
|
|
3290
|
+
topic=deregistration_topic
|
|
3291
|
+
)
|
|
3292
|
+
|
|
3293
|
+
log.info(
|
|
3294
|
+
"%s Published de-registration event for agent '%s' to topic '%s'",
|
|
3295
|
+
self.log_identifier,
|
|
3296
|
+
agent_name,
|
|
3297
|
+
deregistration_topic
|
|
3298
|
+
)
|
|
3299
|
+
except Exception as e:
|
|
3300
|
+
log.error(
|
|
3301
|
+
"%s Failed to publish de-registration event for agent '%s': %s",
|
|
3302
|
+
self.log_identifier,
|
|
3303
|
+
agent_name,
|
|
3304
|
+
e
|
|
3305
|
+
)
|
|
3154
3306
|
|
|
3155
3307
|
async def _resolve_early_embeds_and_handle_signals(
|
|
3156
3308
|
self, raw_text: str, a2a_context: Dict
|
|
@@ -25,6 +25,7 @@ from pydub import AudioSegment
|
|
|
25
25
|
from ...agent.utils.artifact_helpers import (
|
|
26
26
|
load_artifact_content_or_metadata,
|
|
27
27
|
save_artifact_with_metadata,
|
|
28
|
+
ensure_correct_extension,
|
|
28
29
|
DEFAULT_SCHEMA_MAX_KEYS,
|
|
29
30
|
)
|
|
30
31
|
from ...agent.utils.context_helpers import get_original_session_id
|
|
@@ -1210,14 +1211,18 @@ async def concatenate_audio(
|
|
|
1210
1211
|
|
|
1211
1212
|
async def transcribe_audio(
|
|
1212
1213
|
audio_filename: str,
|
|
1214
|
+
output_filename: Optional[str] = None,
|
|
1215
|
+
description: Optional[str] = None,
|
|
1213
1216
|
tool_context: ToolContext = None,
|
|
1214
1217
|
tool_config: Optional[Dict[str, Any]] = None,
|
|
1215
1218
|
) -> Dict[str, Any]:
|
|
1216
1219
|
"""
|
|
1217
|
-
Transcribes an audio recording
|
|
1220
|
+
Transcribes an audio recording and saves the transcription as a text artifact.
|
|
1218
1221
|
|
|
1219
1222
|
Args:
|
|
1220
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.
|
|
1221
1226
|
tool_context: The context provided by the ADK framework.
|
|
1222
1227
|
tool_config: Configuration dictionary containing model, api_base, api_key.
|
|
1223
1228
|
|
|
@@ -1225,9 +1230,10 @@ async def transcribe_audio(
|
|
|
1225
1230
|
A dictionary containing:
|
|
1226
1231
|
- "status": "success" or "error".
|
|
1227
1232
|
- "message": A descriptive message about the outcome.
|
|
1228
|
-
- "
|
|
1229
|
-
- "
|
|
1230
|
-
- "
|
|
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.
|
|
1231
1237
|
"""
|
|
1232
1238
|
log_identifier = f"[AudioTools:transcribe_audio:{audio_filename}]"
|
|
1233
1239
|
if not tool_context:
|
|
@@ -1340,8 +1346,28 @@ async def transcribe_audio(
|
|
|
1340
1346
|
)
|
|
1341
1347
|
|
|
1342
1348
|
audio_bytes = audio_artifact_part.inline_data.data
|
|
1349
|
+
audio_mime_type = audio_artifact_part.inline_data.mime_type or "application/octet-stream"
|
|
1343
1350
|
log.debug(f"{log_identifier} Loaded audio artifact: {len(audio_bytes)} bytes")
|
|
1344
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
|
+
|
|
1345
1371
|
temp_file_path = None
|
|
1346
1372
|
try:
|
|
1347
1373
|
file_ext = os.path.splitext(filename_base_for_load)[1]
|
|
@@ -1383,12 +1409,93 @@ async def transcribe_audio(
|
|
|
1383
1409
|
f"{log_identifier} Audio transcribed successfully. Transcription length: {len(transcription)} characters"
|
|
1384
1410
|
)
|
|
1385
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
|
+
|
|
1386
1491
|
return {
|
|
1387
1492
|
"status": "success",
|
|
1388
|
-
"message": "Audio transcribed successfully",
|
|
1389
|
-
"
|
|
1493
|
+
"message": "Audio transcribed and saved successfully",
|
|
1494
|
+
"output_filename": final_filename,
|
|
1495
|
+
"output_version": save_result["data_version"],
|
|
1390
1496
|
"audio_filename": filename_base_for_load,
|
|
1391
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."
|
|
1392
1499
|
}
|
|
1393
1500
|
|
|
1394
1501
|
finally:
|
|
@@ -1600,9 +1707,9 @@ concatenate_audio_tool_def = BuiltinTool(
|
|
|
1600
1707
|
transcribe_audio_tool_def = BuiltinTool(
|
|
1601
1708
|
name="transcribe_audio",
|
|
1602
1709
|
implementation=transcribe_audio,
|
|
1603
|
-
description="Transcribes an audio recording
|
|
1710
|
+
description="Transcribes an audio recording and saves the transcription as a text artifact.",
|
|
1604
1711
|
category="audio",
|
|
1605
|
-
required_scopes=["tool:audio:transcribe"],
|
|
1712
|
+
required_scopes=["tool:audio:transcribe", "tool:artifact:create"],
|
|
1606
1713
|
parameters=adk_types.Schema(
|
|
1607
1714
|
type=adk_types.Type.OBJECT,
|
|
1608
1715
|
properties={
|
|
@@ -1610,6 +1717,16 @@ transcribe_audio_tool_def = BuiltinTool(
|
|
|
1610
1717
|
type=adk_types.Type.STRING,
|
|
1611
1718
|
description="The filename (and optional :version) of the input audio artifact.",
|
|
1612
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
|
+
),
|
|
1613
1730
|
},
|
|
1614
1731
|
required=["audio_filename"],
|
|
1615
1732
|
),
|
|
@@ -92,7 +92,12 @@ async def web_request(
|
|
|
92
92
|
log.error(f"{log_identifier} ToolContext is missing.")
|
|
93
93
|
return {"status": "error", "message": "ToolContext is missing."}
|
|
94
94
|
|
|
95
|
-
if
|
|
95
|
+
# Check if loopback URLs are allowed (for testing)
|
|
96
|
+
allow_loopback = False
|
|
97
|
+
if tool_config:
|
|
98
|
+
allow_loopback = tool_config.get("allow_loopback", False)
|
|
99
|
+
|
|
100
|
+
if not allow_loopback and not _is_safe_url(url):
|
|
96
101
|
log.error(f"{log_identifier} URL is not safe to request: {url}")
|
|
97
102
|
return {"status": "error", "message": "URL is not safe to request."}
|
|
98
103
|
|
|
@@ -150,10 +155,9 @@ async def web_request(
|
|
|
150
155
|
)
|
|
151
156
|
|
|
152
157
|
response_content_bytes = response.content
|
|
153
|
-
response_headers = dict(response.headers)
|
|
154
158
|
response_status_code = response.status_code
|
|
155
159
|
original_content_type = (
|
|
156
|
-
|
|
160
|
+
response.headers.get("content-type", "application/octet-stream")
|
|
157
161
|
.split(";")[0]
|
|
158
162
|
.strip()
|
|
159
163
|
)
|
|
@@ -238,7 +242,7 @@ async def web_request(
|
|
|
238
242
|
{k: v for k, v in headers.items() if k.lower() != "authorization"}
|
|
239
243
|
),
|
|
240
244
|
"response_status_code": response_status_code,
|
|
241
|
-
"response_headers": json.dumps(
|
|
245
|
+
"response_headers": json.dumps(dict(response.headers)),
|
|
242
246
|
"original_content_type": original_content_type,
|
|
243
247
|
"processed_content_type": processed_content_type,
|
|
244
248
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
@@ -285,7 +289,8 @@ async def web_request(
|
|
|
285
289
|
return {
|
|
286
290
|
"status": "success",
|
|
287
291
|
"message": f"Successfully fetched content from {url} (status: {response_status_code}). "
|
|
288
|
-
f"Saved as artifact '{final_artifact_filename}' v{save_result['data_version']}."
|
|
292
|
+
f"Saved as artifact '{final_artifact_filename}' v{save_result['data_version']}. "
|
|
293
|
+
f"Analyze the content of '{final_artifact_filename}' before providing a final answer to the user.",
|
|
289
294
|
"output_filename": final_artifact_filename,
|
|
290
295
|
"output_version": save_result["data_version"],
|
|
291
296
|
"response_status_code": response_status_code,
|