solace-agent-mesh 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/__init__.py +0 -3
- solace_agent_mesh/agents/__init__.py +0 -0
- solace_agent_mesh/agents/base_agent_component.py +224 -0
- solace_agent_mesh/agents/global/__init__.py +0 -0
- solace_agent_mesh/agents/global/actions/__init__.py +0 -0
- solace_agent_mesh/agents/global/actions/agent_state_change.py +54 -0
- solace_agent_mesh/agents/global/actions/clear_history.py +32 -0
- solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +160 -0
- solace_agent_mesh/agents/global/actions/create_file.py +70 -0
- solace_agent_mesh/agents/global/actions/error_action.py +45 -0
- solace_agent_mesh/agents/global/actions/plantuml_diagram.py +93 -0
- solace_agent_mesh/agents/global/actions/plotly_graph.py +117 -0
- solace_agent_mesh/agents/global/actions/retrieve_file.py +51 -0
- solace_agent_mesh/agents/global/global_agent_component.py +38 -0
- solace_agent_mesh/agents/image_processing/__init__.py +0 -0
- solace_agent_mesh/agents/image_processing/actions/__init__.py +0 -0
- solace_agent_mesh/agents/image_processing/actions/create_image.py +75 -0
- solace_agent_mesh/agents/image_processing/actions/describe_image.py +115 -0
- solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +23 -0
- solace_agent_mesh/agents/slack/__init__.py +1 -0
- solace_agent_mesh/agents/slack/actions/__init__.py +1 -0
- solace_agent_mesh/agents/slack/actions/post_message.py +177 -0
- solace_agent_mesh/agents/slack/slack_agent_component.py +59 -0
- solace_agent_mesh/agents/web_request/__init__.py +0 -0
- solace_agent_mesh/agents/web_request/actions/__init__.py +0 -0
- solace_agent_mesh/agents/web_request/actions/do_image_search.py +84 -0
- solace_agent_mesh/agents/web_request/actions/do_news_search.py +47 -0
- solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +34 -0
- solace_agent_mesh/agents/web_request/actions/do_web_request.py +134 -0
- solace_agent_mesh/agents/web_request/actions/download_file.py +69 -0
- solace_agent_mesh/agents/web_request/web_request_agent_component.py +33 -0
- solace_agent_mesh/assets/web-visualizer/assets/index-C5awueeJ.js +109 -0
- solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +1 -0
- solace_agent_mesh/assets/web-visualizer/index.html +14 -0
- solace_agent_mesh/assets/web-visualizer/vite.svg +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/__init__.py +3 -0
- solace_agent_mesh/cli/commands/add/add.py +88 -0
- solace_agent_mesh/cli/commands/add/agent.py +110 -0
- solace_agent_mesh/cli/commands/add/copy_from_plugin.py +90 -0
- solace_agent_mesh/cli/commands/add/gateway.py +221 -0
- solace_agent_mesh/cli/commands/build.py +631 -0
- solace_agent_mesh/cli/commands/chat/__init__.py +3 -0
- solace_agent_mesh/cli/commands/chat/chat.py +361 -0
- solace_agent_mesh/cli/commands/config.py +29 -0
- solace_agent_mesh/cli/commands/init/__init__.py +3 -0
- solace_agent_mesh/cli/commands/init/ai_provider_step.py +76 -0
- solace_agent_mesh/cli/commands/init/broker_step.py +102 -0
- solace_agent_mesh/cli/commands/init/builtin_agent_step.py +88 -0
- solace_agent_mesh/cli/commands/init/check_if_already_done.py +13 -0
- solace_agent_mesh/cli/commands/init/create_config_file_step.py +52 -0
- solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +96 -0
- solace_agent_mesh/cli/commands/init/file_service_step.py +73 -0
- solace_agent_mesh/cli/commands/init/init.py +114 -0
- solace_agent_mesh/cli/commands/init/project_structure_step.py +45 -0
- solace_agent_mesh/cli/commands/init/rest_api_step.py +50 -0
- solace_agent_mesh/cli/commands/init/web_ui_step.py +40 -0
- solace_agent_mesh/cli/commands/plugin/__init__.py +3 -0
- solace_agent_mesh/cli/commands/plugin/add.py +98 -0
- solace_agent_mesh/cli/commands/plugin/build.py +217 -0
- solace_agent_mesh/cli/commands/plugin/create.py +117 -0
- solace_agent_mesh/cli/commands/plugin/plugin.py +109 -0
- solace_agent_mesh/cli/commands/plugin/remove.py +71 -0
- solace_agent_mesh/cli/commands/run.py +68 -0
- solace_agent_mesh/cli/commands/visualizer.py +138 -0
- solace_agent_mesh/cli/config.py +81 -0
- solace_agent_mesh/cli/main.py +306 -0
- solace_agent_mesh/cli/utils.py +246 -0
- solace_agent_mesh/common/__init__.py +0 -0
- solace_agent_mesh/common/action.py +91 -0
- solace_agent_mesh/common/action_list.py +37 -0
- solace_agent_mesh/common/action_response.py +327 -0
- solace_agent_mesh/common/constants.py +3 -0
- solace_agent_mesh/common/mysql_database.py +40 -0
- solace_agent_mesh/common/postgres_database.py +79 -0
- solace_agent_mesh/common/prompt_templates.py +30 -0
- solace_agent_mesh/common/prompt_templates_unused_delete.py +161 -0
- solace_agent_mesh/common/stimulus_utils.py +152 -0
- solace_agent_mesh/common/time.py +24 -0
- solace_agent_mesh/common/utils.py +638 -0
- solace_agent_mesh/configs/agent_global.yaml +74 -0
- solace_agent_mesh/configs/agent_image_processing.yaml +82 -0
- solace_agent_mesh/configs/agent_slack.yaml +64 -0
- solace_agent_mesh/configs/agent_web_request.yaml +75 -0
- solace_agent_mesh/configs/conversation_to_file.yaml +56 -0
- solace_agent_mesh/configs/error_catcher.yaml +56 -0
- solace_agent_mesh/configs/monitor.yaml +0 -0
- solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +106 -0
- solace_agent_mesh/configs/monitor_user_feedback.yaml +58 -0
- solace_agent_mesh/configs/orchestrator.yaml +241 -0
- solace_agent_mesh/configs/service_embedding.yaml +81 -0
- solace_agent_mesh/configs/service_llm.yaml +265 -0
- solace_agent_mesh/configs/visualize_websocket.yaml +55 -0
- solace_agent_mesh/gateway/__init__.py +0 -0
- solace_agent_mesh/gateway/components/__init__.py +0 -0
- solace_agent_mesh/gateway/components/gateway_base.py +41 -0
- solace_agent_mesh/gateway/components/gateway_input.py +265 -0
- solace_agent_mesh/gateway/components/gateway_output.py +289 -0
- solace_agent_mesh/gateway/identity/bamboohr_identity.py +18 -0
- solace_agent_mesh/gateway/identity/identity_base.py +10 -0
- solace_agent_mesh/gateway/identity/identity_provider.py +60 -0
- solace_agent_mesh/gateway/identity/no_identity.py +9 -0
- solace_agent_mesh/gateway/identity/passthru_identity.py +9 -0
- solace_agent_mesh/monitors/base_monitor_component.py +26 -0
- solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +75 -0
- solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +560 -0
- solace_agent_mesh/orchestrator/__init__.py +0 -0
- solace_agent_mesh/orchestrator/action_manager.py +225 -0
- solace_agent_mesh/orchestrator/components/__init__.py +0 -0
- solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +54 -0
- solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +179 -0
- solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +107 -0
- solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +477 -0
- solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +246 -0
- solace_agent_mesh/orchestrator/orchestrator_main.py +166 -0
- solace_agent_mesh/orchestrator/orchestrator_prompt.py +410 -0
- solace_agent_mesh/services/__init__.py +0 -0
- solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +56 -0
- solace_agent_mesh/services/bamboo_hr_service/__init__.py +3 -0
- solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +182 -0
- solace_agent_mesh/services/common/__init__.py +4 -0
- solace_agent_mesh/services/common/auto_expiry.py +45 -0
- solace_agent_mesh/services/common/singleton.py +18 -0
- solace_agent_mesh/services/file_service/__init__.py +14 -0
- solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
- solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +149 -0
- solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +162 -0
- solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +64 -0
- solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +106 -0
- solace_agent_mesh/services/file_service/file_service.py +432 -0
- solace_agent_mesh/services/file_service/file_service_constants.py +54 -0
- solace_agent_mesh/services/file_service/file_transformations.py +131 -0
- solace_agent_mesh/services/file_service/file_utils.py +322 -0
- solace_agent_mesh/services/file_service/transformers/__init__.py +5 -0
- solace_agent_mesh/services/history_service/__init__.py +3 -0
- solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
- solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +78 -0
- solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +167 -0
- solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +163 -0
- solace_agent_mesh/services/history_service/history_service.py +139 -0
- solace_agent_mesh/services/llm_service/components/llm_request_component.py +293 -0
- solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +152 -0
- solace_agent_mesh/services/middleware_service/__init__.py +0 -0
- solace_agent_mesh/services/middleware_service/middleware_service.py +20 -0
- solace_agent_mesh/templates/action.py +38 -0
- solace_agent_mesh/templates/agent.py +29 -0
- solace_agent_mesh/templates/agent.yaml +70 -0
- solace_agent_mesh/templates/gateway-config-template.yaml +6 -0
- solace_agent_mesh/templates/gateway-default-config.yaml +28 -0
- solace_agent_mesh/templates/gateway-flows.yaml +81 -0
- solace_agent_mesh/templates/gateway-header.yaml +16 -0
- solace_agent_mesh/templates/gateway_base.py +15 -0
- solace_agent_mesh/templates/gateway_input.py +98 -0
- solace_agent_mesh/templates/gateway_output.py +71 -0
- solace_agent_mesh/templates/plugin-pyproject.toml +30 -0
- solace_agent_mesh/templates/rest-api-default-config.yaml +24 -0
- solace_agent_mesh/templates/rest-api-flows.yaml +80 -0
- solace_agent_mesh/templates/slack-default-config.yaml +9 -0
- solace_agent_mesh/templates/slack-flows.yaml +90 -0
- solace_agent_mesh/templates/solace-agent-mesh-default.yaml +77 -0
- solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +8 -0
- solace_agent_mesh/templates/web-default-config.yaml +5 -0
- solace_agent_mesh/templates/web-flows.yaml +86 -0
- solace_agent_mesh/tools/__init__.py +0 -0
- solace_agent_mesh/tools/components/__init__.py +0 -0
- solace_agent_mesh/tools/components/conversation_formatter.py +111 -0
- solace_agent_mesh/tools/components/file_resolver_component.py +58 -0
- solace_agent_mesh/tools/config/runtime_config.py +26 -0
- solace_agent_mesh-0.1.1.dist-info/METADATA +179 -0
- solace_agent_mesh-0.1.1.dist-info/RECORD +174 -0
- solace_agent_mesh-0.1.1.dist-info/entry_points.txt +3 -0
- solace_agent_mesh-0.0.1.dist-info/licenses/LICENSE.txt → solace_agent_mesh-0.1.1.dist-info/licenses/LICENSE +1 -2
- solace_agent_mesh-0.0.1.dist-info/METADATA +0 -51
- solace_agent_mesh-0.0.1.dist-info/RECORD +0 -5
- {solace_agent_mesh-0.0.1.dist-info → solace_agent_mesh-0.1.1.dist-info}/WHEEL +0 -0
solace_agent_mesh/__init__.py
CHANGED
|
File without changes
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""This is the base class for all custom agent components"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import traceback
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from abc import ABC
|
|
8
|
+
from solace_ai_connector.common.log import log
|
|
9
|
+
from solace_ai_connector.common.message import Message
|
|
10
|
+
from solace_ai_connector.common.utils import ensure_slash_on_end
|
|
11
|
+
|
|
12
|
+
from ..services.llm_service.components.llm_service_component_base import LLMServiceComponentBase
|
|
13
|
+
from ..common.action_list import ActionList
|
|
14
|
+
from ..common.action_response import ActionResponse, ErrorInfo
|
|
15
|
+
from ..services.file_service import FileService
|
|
16
|
+
from ..services.file_service.file_utils import recursive_file_resolver
|
|
17
|
+
from ..services.middleware_service.middleware_service import MiddlewareService
|
|
18
|
+
|
|
19
|
+
agent_info = {
|
|
20
|
+
"class_name": "BaseAgentComponent",
|
|
21
|
+
"description": "This component handles action requests",
|
|
22
|
+
"config_parameters": [
|
|
23
|
+
{
|
|
24
|
+
"name": "llm_service_topic",
|
|
25
|
+
"required": False,
|
|
26
|
+
"description": "The topic to use for the LLM service",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "embedding_service_topic",
|
|
30
|
+
"required": False,
|
|
31
|
+
"description": "The topic to use for the Embedding service",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "registration_interval",
|
|
35
|
+
"required": False,
|
|
36
|
+
"description": "The interval in seconds for agent registration",
|
|
37
|
+
"default": 30,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
"input_schema": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"agent_name": {"type": "string"},
|
|
44
|
+
"action_name": {"type": "string"},
|
|
45
|
+
"params": {"type": "object", "additionalProperties": True},
|
|
46
|
+
},
|
|
47
|
+
"required": ["agent_name", "action_name", "params"],
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class BaseAgentComponent(LLMServiceComponentBase, ABC):
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def get_actions_list(cls, **kwargs):
|
|
56
|
+
return ActionList(cls.actions, **kwargs)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def __init__(self, module_info={}, **kwargs):
|
|
60
|
+
super().__init__(module_info, **kwargs)
|
|
61
|
+
self.kwargs = kwargs
|
|
62
|
+
self.action_config = kwargs.get("action_config", {})
|
|
63
|
+
self.registration_interval = int(self.get_config("registration_interval", 30))
|
|
64
|
+
|
|
65
|
+
self.llm_service_topic = self.get_config("llm_service_topic")
|
|
66
|
+
if self.llm_service_topic:
|
|
67
|
+
self.llm_service_topic = ensure_slash_on_end(self.llm_service_topic)
|
|
68
|
+
# Check that the component's broker request/response is enabled
|
|
69
|
+
if not self.is_broker_request_response_enabled():
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"LLM service topic is set, but the component does not "
|
|
72
|
+
f"have its broker request/response enabled, {self.__class__.__name__}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
self.embedding_service_topic = self.get_config("embedding_service_topic")
|
|
76
|
+
if self.embedding_service_topic:
|
|
77
|
+
self.embedding_service_topic = ensure_slash_on_end(
|
|
78
|
+
self.embedding_service_topic
|
|
79
|
+
)
|
|
80
|
+
# Check that the component's broker request/response is enabled
|
|
81
|
+
if not self.is_broker_request_response_enabled():
|
|
82
|
+
raise ValueError(
|
|
83
|
+
"Embedding service topic is set, but the component does not "
|
|
84
|
+
f"have its broker request/response enabled, {self.__class__.__name__}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
self.action_list = self.get_actions_list(agent=self, config_fn=self.get_config)
|
|
88
|
+
|
|
89
|
+
def run(self):
|
|
90
|
+
# This is called when the component is started - we will use this to send the first registration message
|
|
91
|
+
# Only do this for the first of the agent components
|
|
92
|
+
if self.component_index == 0:
|
|
93
|
+
# Send the registration message immediately - this will also schedule the timer
|
|
94
|
+
self.handle_timer_event(None)
|
|
95
|
+
|
|
96
|
+
# Call the base class run method
|
|
97
|
+
super().run()
|
|
98
|
+
|
|
99
|
+
def get_actions_summary(self):
|
|
100
|
+
action_list = self.action_list
|
|
101
|
+
return action_list.get_prompt_summary(prefix=self.info.get("agent_name"))
|
|
102
|
+
|
|
103
|
+
def get_agent_summary(self):
|
|
104
|
+
return {
|
|
105
|
+
"agent_name": self.info["agent_name"],
|
|
106
|
+
"description": self.info["description"],
|
|
107
|
+
"always_open": self.info.get("always_open", False),
|
|
108
|
+
"actions": self.get_actions_summary(),
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def invoke(self, message, data):
|
|
112
|
+
"""Invoke the component"""
|
|
113
|
+
action_name = data.get("action_name")
|
|
114
|
+
action_response = None
|
|
115
|
+
file_service = FileService()
|
|
116
|
+
if not action_name:
|
|
117
|
+
log.error("Action name not provided. Data: %s", json.dumps(data))
|
|
118
|
+
action_response = ActionResponse(
|
|
119
|
+
message="Internal error: Action name not provided. Please try again",
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
action = self.action_list.get_action(action_name)
|
|
123
|
+
if not action:
|
|
124
|
+
log.error(
|
|
125
|
+
"Action not found: %s. Data: %s", action_name, json.dumps(data)
|
|
126
|
+
)
|
|
127
|
+
action_response = ActionResponse(
|
|
128
|
+
message="Internal error: Action not found. Please try again",
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
resolved_params = data.get("action_params", {}).copy()
|
|
132
|
+
try:
|
|
133
|
+
session_id = (message.get_user_properties() or {}).get("session_id")
|
|
134
|
+
resolved_params = recursive_file_resolver(
|
|
135
|
+
resolved_params,
|
|
136
|
+
resolver=file_service.resolve_all_resolvable_urls,
|
|
137
|
+
session_id=session_id,
|
|
138
|
+
)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
log.error(
|
|
141
|
+
"Error resolving file service URLs: %s. Data: %s",
|
|
142
|
+
str(e),
|
|
143
|
+
json.dumps(data),
|
|
144
|
+
exc_info=True,
|
|
145
|
+
)
|
|
146
|
+
action_response = ActionResponse(
|
|
147
|
+
message=f"Error resolving file URLs. Details: {str(e)}",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
middleware_service = MiddlewareService()
|
|
151
|
+
if middleware_service.get("base_agent_filter")(message.user_properties or {}, action):
|
|
152
|
+
try:
|
|
153
|
+
meta = {
|
|
154
|
+
"session_id": session_id,
|
|
155
|
+
}
|
|
156
|
+
action_response = action.invoke(resolved_params, meta)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
|
|
159
|
+
error_message = (
|
|
160
|
+
f"Error invoking action {action_name} "
|
|
161
|
+
f"in agent {self.info.get('agent_name', 'Unknown')}: \n\n"
|
|
162
|
+
f"Exception name: {type(e).__name__}\n"
|
|
163
|
+
f"Exception info: {str(e)}\n"
|
|
164
|
+
f"Stack trace: {traceback.format_exc()}\n\n"
|
|
165
|
+
f"Data: {json.dumps(data)}"
|
|
166
|
+
)
|
|
167
|
+
log.error(error_message)
|
|
168
|
+
action_response = ActionResponse(
|
|
169
|
+
message=f"Internal error: {type(e).__name__} - Error invoking action. Details: {str(e)}",
|
|
170
|
+
error_info=ErrorInfo(
|
|
171
|
+
error_message=error_message,
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
log.warning(
|
|
176
|
+
"Unauthorized access attempt for action %s. Data: %s",
|
|
177
|
+
action_name,
|
|
178
|
+
json.dumps(data),
|
|
179
|
+
)
|
|
180
|
+
action_response = ActionResponse(
|
|
181
|
+
message="Unauthorized: You don't have permission to perform this action.",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
action_response.action_list_id = data.get("action_list_id")
|
|
185
|
+
action_response.action_idx = data.get("action_idx")
|
|
186
|
+
action_response.action_name = action_name
|
|
187
|
+
action_response.action_params = data.get("action_params", {})
|
|
188
|
+
try:
|
|
189
|
+
action_response_dict = action_response.to_dict()
|
|
190
|
+
except Exception as e:
|
|
191
|
+
log.error(
|
|
192
|
+
"Error after action %s in converting action response to dict: %s. Data: %s",
|
|
193
|
+
action_name,
|
|
194
|
+
str(e),
|
|
195
|
+
json.dumps(data),
|
|
196
|
+
exc_info=True,
|
|
197
|
+
)
|
|
198
|
+
action_response_dict = {
|
|
199
|
+
"message": "Internal error: Error converting action response to dict",
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# Construct the response topic
|
|
203
|
+
response_topic = f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/actionResponse/agent/{self.info['agent_name']}/{action_name}"
|
|
204
|
+
|
|
205
|
+
return {"payload": action_response_dict, "topic": response_topic}
|
|
206
|
+
|
|
207
|
+
def handle_timer_event(self, timer_data):
|
|
208
|
+
"""Handle the timer event for agent registration."""
|
|
209
|
+
registration_message = self.get_agent_summary()
|
|
210
|
+
registration_topic = f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/register/agent/{self.info['agent_name']}"
|
|
211
|
+
|
|
212
|
+
message = Message(
|
|
213
|
+
topic=registration_topic,
|
|
214
|
+
payload=registration_message,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
message.set_previous(
|
|
218
|
+
{"topic": registration_topic, "payload": registration_message}
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
self.send_message(message)
|
|
222
|
+
|
|
223
|
+
# Re-schedule the timer
|
|
224
|
+
self.add_timer(self.registration_interval * 1000, "agent_registration")
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Agent state change action"""
|
|
2
|
+
|
|
3
|
+
from solace_ai_connector.common.log import log
|
|
4
|
+
from ....common.action import Action
|
|
5
|
+
from ....common.action_response import (
|
|
6
|
+
ActionResponse,
|
|
7
|
+
AgentStateChange,
|
|
8
|
+
ErrorInfo,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentStateChangeAction(Action):
|
|
13
|
+
|
|
14
|
+
def __init__(self, **kwargs):
|
|
15
|
+
super().__init__(
|
|
16
|
+
{
|
|
17
|
+
"name": "change_agent_status",
|
|
18
|
+
"prompt_directive": "Handle agent state change",
|
|
19
|
+
"params": [
|
|
20
|
+
{
|
|
21
|
+
"name": "agent_name",
|
|
22
|
+
"desc": "Name of the agent",
|
|
23
|
+
"type": "string",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "new_state",
|
|
27
|
+
"desc": "New state of the agent: open or closed",
|
|
28
|
+
"type": "string",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
"required_scopes": ["*:*:*"],
|
|
32
|
+
},
|
|
33
|
+
**kwargs,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def invoke(self, params, meta={}) -> ActionResponse:
|
|
37
|
+
agent_name = params["agent_name"]
|
|
38
|
+
new_state = params["new_state"]
|
|
39
|
+
log.debug("Handling agent state change: %s -> %s", agent_name, new_state)
|
|
40
|
+
|
|
41
|
+
if new_state == "open":
|
|
42
|
+
return ActionResponse(
|
|
43
|
+
message=f"_Opening agent {agent_name}..._",
|
|
44
|
+
agent_state_change=AgentStateChange(agent_name, "open"),
|
|
45
|
+
)
|
|
46
|
+
elif new_state == "closed":
|
|
47
|
+
return ActionResponse(
|
|
48
|
+
agent_state_change=AgentStateChange(agent_name, "closed"),
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
log.error("Invalid agent state: %s", new_state)
|
|
52
|
+
return ActionResponse(
|
|
53
|
+
error_info=ErrorInfo(error_message=f"Invalid agent state for {agent_name}: {new_state}"),
|
|
54
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Clear history action"""
|
|
2
|
+
|
|
3
|
+
from solace_ai_connector.common.log import log
|
|
4
|
+
from ....common.action import Action
|
|
5
|
+
from ....common.action_response import ActionResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ClearHistory(Action):
|
|
9
|
+
def __init__(self, **kwargs):
|
|
10
|
+
super().__init__(
|
|
11
|
+
{
|
|
12
|
+
"name": "clear_history",
|
|
13
|
+
"prompt_directive": "Clear the chat history",
|
|
14
|
+
"params": [
|
|
15
|
+
{
|
|
16
|
+
"name": "depth_to_keep",
|
|
17
|
+
"desc": "The number of messages to keep in the chat history. If 0, clear all history. ",
|
|
18
|
+
"type": "integer",
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"required_scopes": ["*:*:*"],
|
|
22
|
+
},
|
|
23
|
+
**kwargs,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def invoke(self, params, meta={}) -> ActionResponse:
|
|
27
|
+
log.debug("Clearing chat history")
|
|
28
|
+
depth_to_keep = params.get("depth_to_keep", 0)
|
|
29
|
+
return ActionResponse(
|
|
30
|
+
clear_history=True,
|
|
31
|
+
history_depth_to_keep=depth_to_keep,
|
|
32
|
+
)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Action description"""
|
|
2
|
+
|
|
3
|
+
from solace_ai_connector.common.log import log
|
|
4
|
+
from markitdown import MarkItDown
|
|
5
|
+
from markitdown import UnsupportedFormatException
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import mimetypes
|
|
9
|
+
import tempfile
|
|
10
|
+
from ....common.action import Action
|
|
11
|
+
from ....common.action_response import ActionResponse
|
|
12
|
+
from ....services.file_service import FS_PROTOCOL, FileService
|
|
13
|
+
|
|
14
|
+
STRIP_LENGTH = len(FS_PROTOCOL) + len("://") + 36 + 1 # 36 is uuid, 1 is underscore
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConvertFileToMarkdown(Action):
|
|
18
|
+
|
|
19
|
+
def __init__(self, **kwargs):
|
|
20
|
+
super().__init__(
|
|
21
|
+
{
|
|
22
|
+
"name": "convert_file_to_markdown",
|
|
23
|
+
"prompt_directive": (
|
|
24
|
+
"Convert various file formats to Markdown using the MarkItDown utility. "
|
|
25
|
+
"Supported file types include PDF, Word, Excel, HTML, CSV and PPTX "
|
|
26
|
+
"and ZIP files. Files are provided in "
|
|
27
|
+
+ f"{FS_PROTOCOL}://"
|
|
28
|
+
+ " format. "
|
|
29
|
+
"JSON and XML files are not supported. "
|
|
30
|
+
"This action will return Markdown files."
|
|
31
|
+
),
|
|
32
|
+
"params": [
|
|
33
|
+
{
|
|
34
|
+
"name": "files",
|
|
35
|
+
"desc": (
|
|
36
|
+
f"List of {FS_PROTOCOL}:// URLs to the files to convert to Markdown. "
|
|
37
|
+
f"For example: ['{FS_PROTOCOL}://file1.pdf', '{FS_PROTOCOL}://file2.docx']"
|
|
38
|
+
),
|
|
39
|
+
"type": "array",
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"required_scopes": ["global:markitdown:execute"],
|
|
43
|
+
},
|
|
44
|
+
**kwargs,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def invoke(self, params, meta={}) -> ActionResponse:
|
|
48
|
+
files = params.get("files", [])
|
|
49
|
+
session_id = meta.get("session_id")
|
|
50
|
+
log.debug("Doing markitdown convert action: %s", params["files"])
|
|
51
|
+
|
|
52
|
+
if isinstance(files, str):
|
|
53
|
+
try:
|
|
54
|
+
files = json.loads(files)
|
|
55
|
+
except json.JSONDecodeError:
|
|
56
|
+
log.error("Failed to decode files string as JSON")
|
|
57
|
+
return ActionResponse(message="Error: File URL list is not valid.")
|
|
58
|
+
|
|
59
|
+
return self.do_action(files, session_id)
|
|
60
|
+
|
|
61
|
+
def extract_file_format(self, file_path: str) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Extracts the file format from a file path or guesses it using mimetypes if not present.
|
|
64
|
+
"""
|
|
65
|
+
file_extension = os.path.splitext(file_path)[1]
|
|
66
|
+
if file_extension:
|
|
67
|
+
return file_extension.lower()
|
|
68
|
+
|
|
69
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
70
|
+
if mime_type:
|
|
71
|
+
guessed_extension = mimetypes.guess_extension(mime_type)
|
|
72
|
+
if guessed_extension:
|
|
73
|
+
return guessed_extension.lower()
|
|
74
|
+
|
|
75
|
+
raise ValueError(f"Unable to determine file format for path: {file_path}")
|
|
76
|
+
|
|
77
|
+
def do_action(self, files, session_id) -> ActionResponse:
|
|
78
|
+
markitdown_client = MarkItDown()
|
|
79
|
+
file_service = FileService()
|
|
80
|
+
|
|
81
|
+
response_messages = []
|
|
82
|
+
uploaded_files = []
|
|
83
|
+
|
|
84
|
+
# Iterate over each file URL
|
|
85
|
+
for file_url in files:
|
|
86
|
+
try:
|
|
87
|
+
file_buffer = file_service.download_to_buffer(file_url, session_id)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
log.error(
|
|
90
|
+
"Failed to download file from the url %s: %s", file_url, str(e)
|
|
91
|
+
)
|
|
92
|
+
response_messages.append(
|
|
93
|
+
f"Failed to download file from the url {file_url}"
|
|
94
|
+
)
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# Find the file extension
|
|
98
|
+
try:
|
|
99
|
+
file_extension = self.extract_file_format(file_url)
|
|
100
|
+
except ValueError:
|
|
101
|
+
response_messages.append(
|
|
102
|
+
f"Could not determine file format for: {file_url}"
|
|
103
|
+
)
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
# strip first {STRIP LENGTH} characters of the url to get the original name
|
|
107
|
+
original_name = file_url[STRIP_LENGTH:]
|
|
108
|
+
|
|
109
|
+
with tempfile.NamedTemporaryFile(
|
|
110
|
+
delete=True, suffix=file_extension
|
|
111
|
+
) as temporary_file:
|
|
112
|
+
try:
|
|
113
|
+
# Write to temp file
|
|
114
|
+
temporary_file.write(file_buffer)
|
|
115
|
+
temporary_file.flush()
|
|
116
|
+
|
|
117
|
+
# Convert the file to Markdown
|
|
118
|
+
try:
|
|
119
|
+
conversion_result = markitdown_client.convert(
|
|
120
|
+
temporary_file.name
|
|
121
|
+
)
|
|
122
|
+
# Empty content
|
|
123
|
+
if not conversion_result.text_content:
|
|
124
|
+
log.error("Error converting file %s: Conversion resulted in empty content", file_url)
|
|
125
|
+
response_messages.append(f"Could not convert file: {file_url} - conversion resulted in empty content")
|
|
126
|
+
continue
|
|
127
|
+
except UnsupportedFormatException as e:
|
|
128
|
+
log.error("Error converting file %s: %s", file_url, str(e))
|
|
129
|
+
response_messages.append(
|
|
130
|
+
f"Could not convert file: {file_url} as this format is not supported: {str(e)}"
|
|
131
|
+
)
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
# Use the original file name + "_converted" affix + .md extension
|
|
135
|
+
converted_filename = f"{original_name}_converted.md"
|
|
136
|
+
|
|
137
|
+
# Upload the converted content
|
|
138
|
+
file_obj = file_service.upload_from_buffer(
|
|
139
|
+
conversion_result.text_content.encode("utf-8"),
|
|
140
|
+
converted_filename,
|
|
141
|
+
session_id,
|
|
142
|
+
data_source="Global Agent - MarkItDown Action",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
filename = file_obj["name"]
|
|
146
|
+
uploaded_files.append(file_obj)
|
|
147
|
+
response_messages.append(f"File converted successfully: {filename}")
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
log.error(
|
|
151
|
+
"Failed to upload file %s: %s", converted_filename, str(e)
|
|
152
|
+
)
|
|
153
|
+
response_messages.append(
|
|
154
|
+
f"Failed to upload file {converted_filename}"
|
|
155
|
+
)
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
return ActionResponse(
|
|
159
|
+
message="\n".join(response_messages), files=uploaded_files
|
|
160
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Create a new file in file service"""
|
|
2
|
+
|
|
3
|
+
from solace_ai_connector.common.log import log
|
|
4
|
+
from ....common.action import Action
|
|
5
|
+
from ....common.action_response import ActionResponse
|
|
6
|
+
from ....services.file_service import FS_PROTOCOL, FileService
|
|
7
|
+
from ....services.file_service.file_utils import starts_with_fs_url
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CreateFile(Action):
|
|
11
|
+
"""Action to create a new file in file service"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, **kwargs):
|
|
14
|
+
|
|
15
|
+
super().__init__(
|
|
16
|
+
{
|
|
17
|
+
"name": "create_file",
|
|
18
|
+
"prompt_directive": f"Creates a new persisted file and returns the file block with {FS_PROTOCOL} URL",
|
|
19
|
+
"params": [
|
|
20
|
+
{
|
|
21
|
+
"name": "name",
|
|
22
|
+
"desc": "File name (required) - must have an extension. Eg: file.csv",
|
|
23
|
+
"type": "string",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "content",
|
|
27
|
+
"desc": "Content of the file (required)",
|
|
28
|
+
"type": "string",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
"required_scopes": ["global:create_file:write"],
|
|
32
|
+
},
|
|
33
|
+
**kwargs,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def invoke(self, params, meta={}) -> ActionResponse:
|
|
37
|
+
name: str = params.get("name")
|
|
38
|
+
content: str = params.get("content")
|
|
39
|
+
session_id: str = meta.get("session_id")
|
|
40
|
+
|
|
41
|
+
if not name or not content:
|
|
42
|
+
return ActionResponse(message="Error: name and content are required.")
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# Upload the file
|
|
46
|
+
file_service = FileService()
|
|
47
|
+
|
|
48
|
+
updated_content = content
|
|
49
|
+
if starts_with_fs_url(updated_content):
|
|
50
|
+
updated_content = file_service.resolve_url(updated_content, session_id)
|
|
51
|
+
else:
|
|
52
|
+
updated_content = file_service.resolve_all_resolvable_urls(updated_content, session_id)
|
|
53
|
+
|
|
54
|
+
if isinstance(updated_content, str):
|
|
55
|
+
updated_content = updated_content.encode("utf-8")
|
|
56
|
+
|
|
57
|
+
file_meta = file_service.upload_from_buffer(
|
|
58
|
+
updated_content,
|
|
59
|
+
name,
|
|
60
|
+
session_id,
|
|
61
|
+
data_source="Global Agent - Create File Action",
|
|
62
|
+
)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
log.error("Failed to upload file %s: %s", name, str(e))
|
|
65
|
+
return ActionResponse(message=f"Failed to upload the file: {name}")
|
|
66
|
+
|
|
67
|
+
file_block = file_service.get_file_block_by_metadata(file_meta)
|
|
68
|
+
return ActionResponse(
|
|
69
|
+
message=f"Following file has been created:\n\n{file_block}",
|
|
70
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Action to handle errors from the system"""
|
|
2
|
+
|
|
3
|
+
from solace_ai_connector.common.log import log
|
|
4
|
+
|
|
5
|
+
from ....common.action import Action
|
|
6
|
+
from ....common.action_response import (
|
|
7
|
+
ActionResponse,
|
|
8
|
+
ErrorInfo,
|
|
9
|
+
WithContextQuery,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ErrorAction(Action):
|
|
14
|
+
def __init__(self, **kwargs):
|
|
15
|
+
super().__init__(
|
|
16
|
+
{
|
|
17
|
+
"name": "error_action",
|
|
18
|
+
"prompt_directive": "Raise an error message",
|
|
19
|
+
"params": [
|
|
20
|
+
{
|
|
21
|
+
"name": "error_message",
|
|
22
|
+
"desc": "The error to send to the user",
|
|
23
|
+
"type": "string",
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"required_scopes": ["*:*:*"],
|
|
27
|
+
},
|
|
28
|
+
**kwargs,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def invoke(self, params, meta={}) -> ActionResponse:
|
|
32
|
+
error_message = params.get("error_message")
|
|
33
|
+
if error_message:
|
|
34
|
+
log.error("Raising error: %s", error_message)
|
|
35
|
+
# If the error_message contains "Failed to parse response",
|
|
36
|
+
# then return a response requesting a redo from the LLM
|
|
37
|
+
if "Failed to parse response" in error_message:
|
|
38
|
+
return ActionResponse(
|
|
39
|
+
context_query=WithContextQuery(
|
|
40
|
+
context_type="statement",
|
|
41
|
+
context="Your response was not correctly formated and parsing failed. Please try again but don't comment on the parsing problem in your response.",
|
|
42
|
+
query="",
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
return ActionResponse(error_info=ErrorInfo(error_message=error_message))
|