solace-agent-mesh 1.0.2__py3-none-any.whl → 1.0.5__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/__init__.py +0 -5
- solace_agent_mesh/agent/adk/filesystem_artifact_service.py +32 -39
- solace_agent_mesh/agent/adk/services.py +140 -59
- solace_agent_mesh/agent/protocol/event_handlers.py +70 -116
- solace_agent_mesh/agent/sac/app.py +12 -2
- solace_agent_mesh/agent/sac/component.py +37 -1
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +8 -1
- solace_agent_mesh/agent/tools/peer_agent_tool.py +92 -84
- solace_agent_mesh/agent/utils/artifact_helpers.py +148 -14
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/{04989206.674a8007.js → 04989206.da8246cd.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/1023fc19.8e6d174c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{3d406171.f722eaf5.js → 3d406171.9b081d5f.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/75384d09.c3991823.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9eff14a2.036c35ea.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{aba87c2f.d3e2dcc3.js → aba87c2f.a6b84da6.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/{ae4415af.8e279b5d.js → ae4415af.96189a93.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/b7006a3a.38c0cf3d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/bb2ef573.56931473.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{cc969b05.954186d4.js → cc969b05.bd3e0d6c.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.5aff74ab.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{f897a61a.f8c53b0f.js → f897a61a.862b0514.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/main.946fa17b.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.aa687c82.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +8 -6
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/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/quick-start/index.html +9 -9
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +140 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
- solace_agent_mesh/assets/docs/img/solace-logo-text.svg +18 -0
- solace_agent_mesh/assets/docs/lunr-index-1755275703209.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1755275703209.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/init_cmd/orchestrator_step.py +1 -1
- solace_agent_mesh/cli/commands/run_cmd.py +46 -3
- solace_agent_mesh/cli/main.py +1 -3
- solace_agent_mesh/client/webui/frontend/static/assets/main-BCpII1-0.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-DzKPMTRs.js +673 -0
- solace_agent_mesh/client/webui/frontend/static/index.html +2 -2
- solace_agent_mesh/common/exceptions.py +25 -0
- solace_agent_mesh/common/utils/initializer.py +4 -3
- solace_agent_mesh/common/utils/message_utils.py +79 -0
- solace_agent_mesh/config_portal/backend/common.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-_7yox_eh.js → _index-xSu2leR8.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-e5c3acfe.js → manifest-950eb3be.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
- solace_agent_mesh/gateway/base/component.py +15 -2
- solace_agent_mesh/gateway/http_sse/sse_manager.py +23 -1
- solace_agent_mesh/templates/plugin_agent_config_template.yaml +1 -1
- {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.5.dist-info}/METADATA +66 -49
- {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.5.dist-info}/RECORD +86 -82
- solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/9eff14a2.1bf8f61c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.d5133813.js +0 -1
- solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
- solace_agent_mesh/assets/docs/lunr-index-1754075282978.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1754075282978.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-D11Lmy9p.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-Gfk3BYn5.js +0 -663
- /solace_agent_mesh/assets/docs/assets/js/{main.c6286d7c.js.LICENSE.txt → main.946fa17b.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.5.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.5.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -246,8 +246,8 @@ class SamAgentApp(App):
|
|
|
246
246
|
"type": "string",
|
|
247
247
|
"required": False,
|
|
248
248
|
"default": "namespace",
|
|
249
|
-
"enum": ["namespace", "app"
|
|
250
|
-
"description": "
|
|
249
|
+
"enum": ["namespace", "app"],
|
|
250
|
+
"description": "Process-wide scope for all artifact services. 'namespace' (default): shared by all components in the namespace. 'app': isolated by agent/gateway name. This setting must be consistent for all components in the same process.",
|
|
251
251
|
},
|
|
252
252
|
"artifact_scope_value": {
|
|
253
253
|
"type": "string",
|
|
@@ -340,6 +340,13 @@ class SamAgentApp(App):
|
|
|
340
340
|
"default": 0,
|
|
341
341
|
"description": "Minimum size in bytes for accumulated text from LLM stream before sending a status update. If 0 or less, batching is disabled and updates are sent per chunk. Final LLM chunks are always sent regardless of this threshold.",
|
|
342
342
|
},
|
|
343
|
+
{
|
|
344
|
+
"name": "max_message_size_bytes",
|
|
345
|
+
"required": False,
|
|
346
|
+
"type": "integer",
|
|
347
|
+
"default": 10_000_000,
|
|
348
|
+
"description": "Maximum allowed message size in bytes before rejecting publication to prevent broker disconnections. Default: 10MB",
|
|
349
|
+
},
|
|
343
350
|
{
|
|
344
351
|
"name": "enable_artifact_content_instruction",
|
|
345
352
|
"required": False,
|
|
@@ -551,6 +558,7 @@ class SamAgentApp(App):
|
|
|
551
558
|
app_config = app_info.get("app_config", {})
|
|
552
559
|
namespace = app_config.get("namespace")
|
|
553
560
|
agent_name = app_config.get("agent_name")
|
|
561
|
+
broker_request_response = app_info.get("broker_request_response")
|
|
554
562
|
|
|
555
563
|
if not namespace or not isinstance(namespace, str):
|
|
556
564
|
raise ValueError(
|
|
@@ -619,6 +627,8 @@ class SamAgentApp(App):
|
|
|
619
627
|
"component_config": {},
|
|
620
628
|
"subscriptions": generated_subs,
|
|
621
629
|
}
|
|
630
|
+
if broker_request_response:
|
|
631
|
+
component_definition["broker_request_response"] = broker_request_response
|
|
622
632
|
|
|
623
633
|
app_info["components"] = [component_definition]
|
|
624
634
|
log.debug("Replaced 'components' in app_info with programmatic definition.")
|
|
@@ -67,7 +67,9 @@ from ...common.a2a_protocol import (
|
|
|
67
67
|
get_gateway_status_topic,
|
|
68
68
|
)
|
|
69
69
|
from ...agent.utils.config_parser import resolve_instruction_provider
|
|
70
|
-
from ...agent.utils.artifact_helpers import
|
|
70
|
+
from ...agent.utils.artifact_helpers import (
|
|
71
|
+
get_latest_artifact_version,
|
|
72
|
+
)
|
|
71
73
|
from ...agent.adk.services import (
|
|
72
74
|
initialize_session_service,
|
|
73
75
|
initialize_artifact_service,
|
|
@@ -92,6 +94,8 @@ from ...agent.adk.invocation_monitor import InvocationMonitor
|
|
|
92
94
|
from ...common.middleware.registry import MiddlewareRegistry
|
|
93
95
|
from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT
|
|
94
96
|
from ...agent.tools.registry import tool_registry
|
|
97
|
+
from ...common.utils.message_utils import validate_message_size
|
|
98
|
+
from ...common.exceptions import MessageSizeExceededError
|
|
95
99
|
|
|
96
100
|
if TYPE_CHECKING:
|
|
97
101
|
from .task_execution_context import TaskExecutionContext
|
|
@@ -232,6 +236,11 @@ class SamAgentComponent(ComponentBase):
|
|
|
232
236
|
raise ValueError(
|
|
233
237
|
"Internal Error: Inter-agent comms config missing after validation."
|
|
234
238
|
)
|
|
239
|
+
|
|
240
|
+
self.max_message_size_bytes = self.get_config(
|
|
241
|
+
"max_message_size_bytes", 10_000_000
|
|
242
|
+
)
|
|
243
|
+
|
|
235
244
|
log.info("%s Configuration retrieved successfully.", self.log_identifier)
|
|
236
245
|
except Exception as e:
|
|
237
246
|
log.error(
|
|
@@ -394,6 +403,7 @@ class SamAgentComponent(ComponentBase):
|
|
|
394
403
|
self.session_service = initialize_session_service(self)
|
|
395
404
|
self.artifact_service = initialize_artifact_service(self)
|
|
396
405
|
self.memory_service = initialize_memory_service(self)
|
|
406
|
+
|
|
397
407
|
log.info(
|
|
398
408
|
"%s Synchronous ADK services initialized.", self.log_identifier
|
|
399
409
|
)
|
|
@@ -2968,6 +2978,29 @@ class SamAgentComponent(ComponentBase):
|
|
|
2968
2978
|
):
|
|
2969
2979
|
"""Helper to publish A2A messages via the SAC App."""
|
|
2970
2980
|
try:
|
|
2981
|
+
max_size_bytes = self.max_message_size_bytes
|
|
2982
|
+
|
|
2983
|
+
# Validate message size
|
|
2984
|
+
is_valid, actual_size = validate_message_size(
|
|
2985
|
+
payload, max_size_bytes, self.log_identifier
|
|
2986
|
+
)
|
|
2987
|
+
|
|
2988
|
+
if not is_valid:
|
|
2989
|
+
error_msg = (
|
|
2990
|
+
f"Message size validation failed: payload size ({actual_size} bytes) "
|
|
2991
|
+
f"exceeds maximum allowed size ({max_size_bytes} bytes)"
|
|
2992
|
+
)
|
|
2993
|
+
log.error("%s %s", self.log_identifier, error_msg)
|
|
2994
|
+
raise MessageSizeExceededError(actual_size, max_size_bytes, error_msg)
|
|
2995
|
+
|
|
2996
|
+
# Debug logging to show message size when publishing
|
|
2997
|
+
log.debug(
|
|
2998
|
+
"%s Publishing message to topic %s (size: %d bytes)",
|
|
2999
|
+
self.log_identifier,
|
|
3000
|
+
topic,
|
|
3001
|
+
actual_size,
|
|
3002
|
+
)
|
|
3003
|
+
|
|
2971
3004
|
app = self.get_app()
|
|
2972
3005
|
if app:
|
|
2973
3006
|
if self.invocation_monitor:
|
|
@@ -2985,6 +3018,9 @@ class SamAgentComponent(ComponentBase):
|
|
|
2985
3018
|
"%s Cannot publish message: Not running within a SAC App context.",
|
|
2986
3019
|
self.log_identifier,
|
|
2987
3020
|
)
|
|
3021
|
+
except MessageSizeExceededError:
|
|
3022
|
+
# Re-raise MessageSizeExceededError without wrapping
|
|
3023
|
+
raise
|
|
2988
3024
|
except Exception as e:
|
|
2989
3025
|
log.exception(
|
|
2990
3026
|
"%s Failed to publish A2A message to topic %s: %s",
|
|
@@ -1888,12 +1888,19 @@ async def delete_artifact(
|
|
|
1888
1888
|
"ArtifactService does not support deleting artifacts."
|
|
1889
1889
|
)
|
|
1890
1890
|
|
|
1891
|
+
if version is not None:
|
|
1892
|
+
log.warning(
|
|
1893
|
+
"%s Deleting a specific version (%s) is not supported by the current artifact service interface. "
|
|
1894
|
+
"All versions of the artifact will be deleted.",
|
|
1895
|
+
log_identifier,
|
|
1896
|
+
version,
|
|
1897
|
+
)
|
|
1898
|
+
|
|
1891
1899
|
await artifact_service.delete_artifact(
|
|
1892
1900
|
app_name=app_name,
|
|
1893
1901
|
user_id=user_id,
|
|
1894
1902
|
session_id=session_id,
|
|
1895
1903
|
filename=filename,
|
|
1896
|
-
version=version,
|
|
1897
1904
|
)
|
|
1898
1905
|
|
|
1899
1906
|
log.info(
|
|
@@ -7,6 +7,7 @@ import uuid
|
|
|
7
7
|
|
|
8
8
|
from google.adk.tools import BaseTool, ToolContext
|
|
9
9
|
from google.genai import types as adk_types
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
10
11
|
from solace_ai_connector.common.log import log
|
|
11
12
|
|
|
12
13
|
from ...common.types import (
|
|
@@ -16,6 +17,17 @@ from ...common.types import (
|
|
|
16
17
|
AgentCard,
|
|
17
18
|
)
|
|
18
19
|
from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT
|
|
20
|
+
from ...common.exceptions import MessageSizeExceededError
|
|
21
|
+
|
|
22
|
+
class ArtifactIdentifier(BaseModel):
|
|
23
|
+
"""Identifies a specific version of an artifact."""
|
|
24
|
+
|
|
25
|
+
filename: str = Field(..., description="The filename of the artifact.")
|
|
26
|
+
version: Union[str, int] = Field(
|
|
27
|
+
"latest",
|
|
28
|
+
description="The version of the artifact (e.g., 'latest' or a number).",
|
|
29
|
+
)
|
|
30
|
+
|
|
19
31
|
|
|
20
32
|
PEER_TOOL_PREFIX = "peer_"
|
|
21
33
|
CORRELATION_DATA_PREFIX = "a2a_subtask_"
|
|
@@ -24,8 +36,14 @@ CORRELATION_DATA_PREFIX = "a2a_subtask_"
|
|
|
24
36
|
class PeerAgentTool(BaseTool):
|
|
25
37
|
"""
|
|
26
38
|
An ADK Tool that represents a discovered peer agent and handles task delegation
|
|
27
|
-
via the A2A protocol over Solace.
|
|
28
|
-
|
|
39
|
+
via the A2A protocol over Solace.
|
|
40
|
+
|
|
41
|
+
This tool is long-running and operates in a "fire-and-forget" manner. It sends a
|
|
42
|
+
task to a peer agent, including a `task_description` and an optional list of
|
|
43
|
+
`artifacts` to provide context. The artifact identifiers are passed in the
|
|
44
|
+
A2A message metadata, and the peer agent is responsible for fetching them.
|
|
45
|
+
|
|
46
|
+
The response from the peer is handled asynchronously by the agent's event handlers.
|
|
29
47
|
"""
|
|
30
48
|
|
|
31
49
|
is_long_running = True
|
|
@@ -85,10 +103,24 @@ class PeerAgentTool(BaseTool):
|
|
|
85
103
|
type=adk_types.Type.STRING,
|
|
86
104
|
description="The original user query or relevant context.",
|
|
87
105
|
),
|
|
88
|
-
"
|
|
106
|
+
"artifacts": adk_types.Schema(
|
|
89
107
|
type=adk_types.Type.ARRAY,
|
|
90
|
-
items=adk_types.Schema(
|
|
91
|
-
|
|
108
|
+
items=adk_types.Schema(
|
|
109
|
+
type=adk_types.Type.OBJECT,
|
|
110
|
+
properties={
|
|
111
|
+
"filename": adk_types.Schema(
|
|
112
|
+
type=adk_types.Type.STRING,
|
|
113
|
+
description="The filename of the artifact.",
|
|
114
|
+
),
|
|
115
|
+
"version": adk_types.Schema(
|
|
116
|
+
type=adk_types.Type.STRING,
|
|
117
|
+
description="The version of the artifact (e.g., 'latest' or a number). Defaults to 'latest'.",
|
|
118
|
+
nullable=True,
|
|
119
|
+
),
|
|
120
|
+
},
|
|
121
|
+
required=["filename"],
|
|
122
|
+
),
|
|
123
|
+
description="A list of artifacts to provide as context to the peer agent.",
|
|
92
124
|
nullable=True,
|
|
93
125
|
),
|
|
94
126
|
},
|
|
@@ -106,11 +138,11 @@ class PeerAgentTool(BaseTool):
|
|
|
106
138
|
parameters=parameters_schema,
|
|
107
139
|
)
|
|
108
140
|
|
|
109
|
-
|
|
141
|
+
def _prepare_a2a_parts(
|
|
110
142
|
self, args: Dict[str, Any], tool_context: ToolContext
|
|
111
|
-
) -> List[
|
|
143
|
+
) -> List[TextPart]:
|
|
112
144
|
"""
|
|
113
|
-
Prepares the A2A message parts from tool arguments
|
|
145
|
+
Prepares the A2A message parts from tool arguments.
|
|
114
146
|
"""
|
|
115
147
|
task_description = args.get("task_description", "No description provided.")
|
|
116
148
|
calling_agent_name = self.host_component.agent_name or "Unknown Agent"
|
|
@@ -118,73 +150,16 @@ class PeerAgentTool(BaseTool):
|
|
|
118
150
|
# Create the multi-agent context message
|
|
119
151
|
context_message = (
|
|
120
152
|
f"You are part of a multi-agent AI platform. The task below is being sent to you by agent '{calling_agent_name}'. "
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
153
|
+
"You must perform this task to the best of your abilities. All artifacts that you create will automatically be "
|
|
154
|
+
"returned to the calling agent, but you must provide context and description for which artifacts are important "
|
|
155
|
+
"and how they should be used. Note that the calling agent will not see any of your history - only the text "
|
|
156
|
+
"that you respond with.\n\n"
|
|
157
|
+
"Note that if the request has not provided the information you need to do your job, you must ask for it. "
|
|
158
|
+
"You must not 'make up' data unless specifically instructed to do so.\n\n"
|
|
125
159
|
f"Now please execute this task that was given to you:\n\n{task_description}"
|
|
126
160
|
)
|
|
127
161
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
artifact_names = args.get("data_artifacts", [])
|
|
131
|
-
if not isinstance(artifact_names, list):
|
|
132
|
-
log.warning(
|
|
133
|
-
"%s 'data_artifacts' argument is not a list, ignoring. Value: %s",
|
|
134
|
-
self.log_identifier,
|
|
135
|
-
artifact_names,
|
|
136
|
-
)
|
|
137
|
-
artifact_names = []
|
|
138
|
-
|
|
139
|
-
if artifact_names:
|
|
140
|
-
log.debug(
|
|
141
|
-
"%s Preparing to include %d artifacts in peer request.",
|
|
142
|
-
self.log_identifier,
|
|
143
|
-
len(artifact_names),
|
|
144
|
-
)
|
|
145
|
-
a2a_context = tool_context.state.get("a2a_context", {})
|
|
146
|
-
for artifact_name in artifact_names:
|
|
147
|
-
try:
|
|
148
|
-
loaded_part = (
|
|
149
|
-
await self.host_component.artifact_service.load_artifact(
|
|
150
|
-
app_name=tool_context._invocation_context.app_name,
|
|
151
|
-
user_id=tool_context._invocation_context.user_id,
|
|
152
|
-
session_id=tool_context._invocation_context.session.id,
|
|
153
|
-
filename=artifact_name,
|
|
154
|
-
version=None,
|
|
155
|
-
)
|
|
156
|
-
)
|
|
157
|
-
if loaded_part:
|
|
158
|
-
a2a_file_part = await self.host_component._translate_adk_part_to_a2a_filepart(
|
|
159
|
-
loaded_part, artifact_name, a2a_context
|
|
160
|
-
)
|
|
161
|
-
if a2a_file_part:
|
|
162
|
-
a2a_message_parts.append(a2a_file_part)
|
|
163
|
-
log.info(
|
|
164
|
-
"%s Included latest artifact '%s' in peer request.",
|
|
165
|
-
self.log_identifier,
|
|
166
|
-
artifact_name,
|
|
167
|
-
)
|
|
168
|
-
else:
|
|
169
|
-
log.warning(
|
|
170
|
-
"%s Failed to translate loaded artifact '%s' to A2A part. Skipping.",
|
|
171
|
-
self.log_identifier,
|
|
172
|
-
artifact_name,
|
|
173
|
-
)
|
|
174
|
-
else:
|
|
175
|
-
log.warning(
|
|
176
|
-
"%s Could not load artifact '%s' to include in peer request. Skipping.",
|
|
177
|
-
self.log_identifier,
|
|
178
|
-
artifact_name,
|
|
179
|
-
)
|
|
180
|
-
except Exception as e:
|
|
181
|
-
log.exception(
|
|
182
|
-
"%s Error loading artifact '%s' for peer request: %s",
|
|
183
|
-
self.log_identifier,
|
|
184
|
-
artifact_name,
|
|
185
|
-
e,
|
|
186
|
-
)
|
|
187
|
-
return a2a_message_parts
|
|
162
|
+
return [TextPart(text=context_message)]
|
|
188
163
|
|
|
189
164
|
async def run_async(
|
|
190
165
|
self, *, args: Dict[str, Any], tool_context: ToolContext
|
|
@@ -236,8 +211,29 @@ class PeerAgentTool(BaseTool):
|
|
|
236
211
|
task_context_obj.parallel_tool_calls.get(invocation_id),
|
|
237
212
|
)
|
|
238
213
|
|
|
239
|
-
a2a_message_parts =
|
|
240
|
-
|
|
214
|
+
a2a_message_parts = self._prepare_a2a_parts(args, tool_context)
|
|
215
|
+
a2a_metadata = {}
|
|
216
|
+
raw_artifacts = args.get("artifacts", [])
|
|
217
|
+
if raw_artifacts and isinstance(raw_artifacts, list):
|
|
218
|
+
try:
|
|
219
|
+
# The ADK gives us a list of dicts, not Pydantic models
|
|
220
|
+
# so we can use them directly.
|
|
221
|
+
a2a_metadata["invoked_with_artifacts"] = raw_artifacts
|
|
222
|
+
log.debug(
|
|
223
|
+
"%s Included %d artifact identifiers in A2A message metadata.",
|
|
224
|
+
log_identifier,
|
|
225
|
+
len(raw_artifacts),
|
|
226
|
+
)
|
|
227
|
+
except Exception as e:
|
|
228
|
+
log.warning(
|
|
229
|
+
"%s Failed to serialize artifact identifiers: %s. Proceeding without them.",
|
|
230
|
+
log_identifier,
|
|
231
|
+
e,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
a2a_message = A2AMessage(
|
|
235
|
+
role="user", parts=a2a_message_parts, metadata=a2a_metadata
|
|
236
|
+
)
|
|
241
237
|
|
|
242
238
|
correlation_data = {
|
|
243
239
|
"adk_function_call_id": tool_context.function_call_id,
|
|
@@ -259,16 +255,28 @@ class PeerAgentTool(BaseTool):
|
|
|
259
255
|
component=self.host_component,
|
|
260
256
|
)
|
|
261
257
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
258
|
+
try:
|
|
259
|
+
self.host_component.submit_a2a_task(
|
|
260
|
+
target_agent_name=self.target_agent_name,
|
|
261
|
+
a2a_message=a2a_message,
|
|
262
|
+
original_session_id=original_session_id,
|
|
263
|
+
main_logical_task_id=main_logical_task_id,
|
|
264
|
+
user_id=user_id,
|
|
265
|
+
user_config=user_config,
|
|
266
|
+
sub_task_id=sub_task_id,
|
|
267
|
+
function_call_id=tool_context.function_call_id,
|
|
268
|
+
)
|
|
269
|
+
except MessageSizeExceededError as e:
|
|
270
|
+
log.error(
|
|
271
|
+
"%s Message size exceeded for peer agent request: %s",
|
|
272
|
+
log_identifier,
|
|
273
|
+
e,
|
|
274
|
+
)
|
|
275
|
+
return {
|
|
276
|
+
"status": "error",
|
|
277
|
+
"message": f"Error: {str(e)}. Message size exceeded for peer agent request.",
|
|
278
|
+
}
|
|
279
|
+
|
|
272
280
|
log.info(
|
|
273
281
|
"%s Registered active peer sub-task %s for main task %s.",
|
|
274
282
|
log_identifier,
|
|
@@ -8,17 +8,21 @@ import json
|
|
|
8
8
|
import csv
|
|
9
9
|
import io
|
|
10
10
|
import inspect
|
|
11
|
-
import datetime
|
|
12
11
|
import os
|
|
12
|
+
import yaml
|
|
13
|
+
import traceback
|
|
14
|
+
from datetime import datetime, timezone
|
|
13
15
|
from typing import Any, Dict, Optional, Tuple, List, Union, TYPE_CHECKING
|
|
14
|
-
from datetime import timezone
|
|
15
16
|
from google.adk.artifacts import BaseArtifactService
|
|
16
17
|
from google.genai import types as adk_types
|
|
17
18
|
from solace_ai_connector.common.log import log
|
|
19
|
+
from ...common.types import ArtifactInfo
|
|
18
20
|
from ...common.utils.mime_helpers import is_text_based_mime_type, is_text_based_file
|
|
21
|
+
from ...agent.utils.context_helpers import get_original_session_id
|
|
19
22
|
|
|
20
23
|
if TYPE_CHECKING:
|
|
21
24
|
from google.adk.tools import ToolContext
|
|
25
|
+
from ...agent.sac.component import SamAgentComponent
|
|
22
26
|
|
|
23
27
|
METADATA_SUFFIX = ".metadata.json"
|
|
24
28
|
DEFAULT_SCHEMA_MAX_KEYS = 20
|
|
@@ -155,13 +159,9 @@ def _infer_schema(
|
|
|
155
159
|
"text/x-yaml",
|
|
156
160
|
]:
|
|
157
161
|
try:
|
|
158
|
-
import yaml
|
|
159
|
-
|
|
160
162
|
data = yaml.safe_load(content_bytes)
|
|
161
163
|
schema_info["structure"] = _inspect_structure(data, depth, max_keys)
|
|
162
164
|
schema_info["inferred"] = True
|
|
163
|
-
except ImportError:
|
|
164
|
-
schema_info["error"] = "YAML inference skipped: PyYAML not installed."
|
|
165
165
|
except (yaml.YAMLError, UnicodeDecodeError) as e:
|
|
166
166
|
schema_info["error"] = f"YAML structure inference failed: {e}"
|
|
167
167
|
except Exception as e:
|
|
@@ -188,7 +188,7 @@ async def save_artifact_with_metadata(
|
|
|
188
188
|
content_bytes: bytes,
|
|
189
189
|
mime_type: str,
|
|
190
190
|
metadata_dict: Dict[str, Any],
|
|
191
|
-
timestamp: datetime
|
|
191
|
+
timestamp: datetime,
|
|
192
192
|
explicit_schema: Optional[Dict] = None,
|
|
193
193
|
schema_inference_depth: int = 2,
|
|
194
194
|
schema_max_keys: int = DEFAULT_SCHEMA_MAX_KEYS,
|
|
@@ -399,6 +399,143 @@ def format_metadata_for_llm(metadata: Dict[str, Any]) -> str:
|
|
|
399
399
|
return "\n".join(lines)
|
|
400
400
|
|
|
401
401
|
|
|
402
|
+
async def generate_artifact_metadata_summary(
|
|
403
|
+
component: "SamAgentComponent",
|
|
404
|
+
artifact_identifiers: List[Dict[str, Any]],
|
|
405
|
+
user_id: str,
|
|
406
|
+
session_id: str,
|
|
407
|
+
app_name: str,
|
|
408
|
+
header_text: Optional[str] = None,
|
|
409
|
+
) -> str:
|
|
410
|
+
"""
|
|
411
|
+
Loads metadata for a list of artifacts and formats it into a human-readable
|
|
412
|
+
YAML summary string, suitable for LLM context.
|
|
413
|
+
"""
|
|
414
|
+
if not artifact_identifiers:
|
|
415
|
+
return ""
|
|
416
|
+
|
|
417
|
+
log_identifier = f"{component.log_identifier}[ArtifactSummary]"
|
|
418
|
+
summary_parts = []
|
|
419
|
+
if header_text:
|
|
420
|
+
summary_parts.append(header_text)
|
|
421
|
+
|
|
422
|
+
if not (component.artifact_service and user_id and session_id):
|
|
423
|
+
log.warning(
|
|
424
|
+
"%s Cannot load artifact metadata: missing artifact_service or context.",
|
|
425
|
+
log_identifier,
|
|
426
|
+
)
|
|
427
|
+
for artifact_ref in artifact_identifiers:
|
|
428
|
+
filename = artifact_ref.get("filename", "unknown")
|
|
429
|
+
version = artifact_ref.get("version", "latest")
|
|
430
|
+
summary_parts.append(
|
|
431
|
+
f"---\nArtifact: '{filename}' (version: {version})\nError: Could not load metadata. Host component context missing."
|
|
432
|
+
)
|
|
433
|
+
return "\n\n".join(summary_parts)
|
|
434
|
+
|
|
435
|
+
for artifact_ref in artifact_identifiers:
|
|
436
|
+
filename = artifact_ref.get("filename")
|
|
437
|
+
version = artifact_ref.get("version", "latest")
|
|
438
|
+
if not filename:
|
|
439
|
+
log.warning(
|
|
440
|
+
"%s Skipping artifact with no filename in identifier: %s",
|
|
441
|
+
log_identifier,
|
|
442
|
+
artifact_ref,
|
|
443
|
+
)
|
|
444
|
+
continue
|
|
445
|
+
|
|
446
|
+
try:
|
|
447
|
+
metadata_result = await load_artifact_content_or_metadata(
|
|
448
|
+
artifact_service=component.artifact_service,
|
|
449
|
+
app_name=app_name,
|
|
450
|
+
user_id=user_id,
|
|
451
|
+
session_id=get_original_session_id(session_id),
|
|
452
|
+
filename=filename,
|
|
453
|
+
version=version,
|
|
454
|
+
load_metadata_only=True,
|
|
455
|
+
)
|
|
456
|
+
if metadata_result.get("status") == "success":
|
|
457
|
+
metadata = metadata_result.get("metadata", {})
|
|
458
|
+
resolved_version = metadata_result.get("version", version)
|
|
459
|
+
artifact_header = (
|
|
460
|
+
f"Artifact: '{filename}' (version: {resolved_version})"
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
# Remove redundant fields before dumping to YAML
|
|
464
|
+
metadata.pop("filename", None)
|
|
465
|
+
metadata.pop("version", None)
|
|
466
|
+
|
|
467
|
+
TRUNCATION_LIMIT_BYTES = 1024
|
|
468
|
+
TRUNCATION_MESSAGE = "\n... [truncated] ..."
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
formatted_metadata_str = yaml.safe_dump(
|
|
472
|
+
metadata,
|
|
473
|
+
default_flow_style=False,
|
|
474
|
+
sort_keys=False,
|
|
475
|
+
allow_unicode=True,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
if (
|
|
479
|
+
len(formatted_metadata_str.encode("utf-8"))
|
|
480
|
+
> TRUNCATION_LIMIT_BYTES
|
|
481
|
+
):
|
|
482
|
+
cutoff = TRUNCATION_LIMIT_BYTES - len(
|
|
483
|
+
TRUNCATION_MESSAGE.encode("utf-8")
|
|
484
|
+
)
|
|
485
|
+
# Ensure we don't cut in the middle of a multi-byte character
|
|
486
|
+
encoded_str = formatted_metadata_str.encode("utf-8")
|
|
487
|
+
if cutoff > 0:
|
|
488
|
+
truncated_encoded = encoded_str[:cutoff]
|
|
489
|
+
formatted_metadata_str = (
|
|
490
|
+
truncated_encoded.decode("utf-8", "ignore")
|
|
491
|
+
+ TRUNCATION_MESSAGE
|
|
492
|
+
)
|
|
493
|
+
else:
|
|
494
|
+
formatted_metadata_str = TRUNCATION_MESSAGE
|
|
495
|
+
|
|
496
|
+
summary_parts.append(
|
|
497
|
+
f"---\n{artifact_header}\n{formatted_metadata_str}"
|
|
498
|
+
)
|
|
499
|
+
except Exception as e_format:
|
|
500
|
+
log.error(
|
|
501
|
+
"%s Error formatting metadata for %s v%s: %s",
|
|
502
|
+
log_identifier,
|
|
503
|
+
filename,
|
|
504
|
+
version,
|
|
505
|
+
e_format,
|
|
506
|
+
)
|
|
507
|
+
summary_parts.append(
|
|
508
|
+
f"---\n{artifact_header}\nError: Could not format metadata."
|
|
509
|
+
)
|
|
510
|
+
else:
|
|
511
|
+
error_message = metadata_result.get(
|
|
512
|
+
"message", "Could not load metadata."
|
|
513
|
+
)
|
|
514
|
+
log.warning(
|
|
515
|
+
"%s Failed to load metadata for %s v%s: %s",
|
|
516
|
+
log_identifier,
|
|
517
|
+
filename,
|
|
518
|
+
version,
|
|
519
|
+
error_message,
|
|
520
|
+
)
|
|
521
|
+
artifact_header = f"Artifact: '{filename}' (version: {version})"
|
|
522
|
+
summary_parts.append(f"---\n{artifact_header}\nError: {error_message}")
|
|
523
|
+
except Exception as e_meta:
|
|
524
|
+
log.error(
|
|
525
|
+
"%s Unexpected error loading metadata for %s v%s: %s",
|
|
526
|
+
log_identifier,
|
|
527
|
+
filename,
|
|
528
|
+
version,
|
|
529
|
+
e_meta,
|
|
530
|
+
)
|
|
531
|
+
artifact_header = f"Artifact: '{filename}' (version: {version})"
|
|
532
|
+
summary_parts.append(
|
|
533
|
+
f"---\n{artifact_header}\nError: An unexpected error occurred while loading metadata."
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
return "\n\n".join(summary_parts)
|
|
537
|
+
|
|
538
|
+
|
|
402
539
|
def decode_and_get_bytes(
|
|
403
540
|
content_str: str, mime_type: str, log_identifier: str
|
|
404
541
|
) -> Tuple[bytes, str]:
|
|
@@ -441,12 +578,6 @@ def decode_and_get_bytes(
|
|
|
441
578
|
return file_bytes, final_mime_type
|
|
442
579
|
|
|
443
580
|
|
|
444
|
-
from google.adk.artifacts import BaseArtifactService
|
|
445
|
-
from datetime import datetime, timezone
|
|
446
|
-
import traceback
|
|
447
|
-
from ...common.types import ArtifactInfo
|
|
448
|
-
|
|
449
|
-
|
|
450
581
|
async def get_latest_artifact_version(
|
|
451
582
|
artifact_service: BaseArtifactService,
|
|
452
583
|
app_name: str,
|
|
@@ -476,7 +607,10 @@ async def get_latest_artifact_version(
|
|
|
476
607
|
return None
|
|
477
608
|
|
|
478
609
|
versions = await artifact_service.list_versions(
|
|
479
|
-
app_name=app_name,
|
|
610
|
+
app_name=app_name,
|
|
611
|
+
user_id=user_id,
|
|
612
|
+
session_id=session_id,
|
|
613
|
+
filename=filename,
|
|
480
614
|
)
|
|
481
615
|
if not versions:
|
|
482
616
|
log.debug("%s No versions found for artifact.", log_identifier)
|