solace-agent-mesh 0.0.1__py3-none-any.whl → 0.1.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/__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/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 +23 -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.0.dist-info/METADATA +179 -0
- solace_agent_mesh-0.1.0.dist-info/RECORD +170 -0
- solace_agent_mesh-0.1.0.dist-info/entry_points.txt +3 -0
- solace_agent_mesh-0.0.1.dist-info/licenses/LICENSE.txt → solace_agent_mesh-0.1.0.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.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""The Action Manager tracks all pending actions and manages their execution. It
|
|
2
|
+
does the following things:
|
|
3
|
+
|
|
4
|
+
1. For each LLM response that has any actions, it create a unique ActionRequestList object.
|
|
5
|
+
2. The ActionRequestList object holds the list of actions to be executed.
|
|
6
|
+
3. As each action response is received, the Action Manager updates the ActionRequestList object.
|
|
7
|
+
4. Once all actions are received, the Action Manager sends the full response to the Orchestrator.
|
|
8
|
+
5. A periodic timer checks to see if any actions should be timed out. The timer is externally
|
|
9
|
+
managed - this class just has a method to call to check for timeouts.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from uuid import uuid4
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
from solace_ai_connector.common.log import log
|
|
17
|
+
from ..common.utils import format_agent_response
|
|
18
|
+
|
|
19
|
+
ACTION_REQUEST_TIMEOUT = 180
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ActionManager:
|
|
23
|
+
"""This class manages all the ActionRequests that are pending"""
|
|
24
|
+
|
|
25
|
+
def __new__(cls, kv_store, lock_manager):
|
|
26
|
+
lock = lock_manager.get_lock("action_manager")
|
|
27
|
+
with lock:
|
|
28
|
+
instance = kv_store.get("action_manager_instance")
|
|
29
|
+
if instance is None:
|
|
30
|
+
instance = super(ActionManager, cls).__new__(cls)
|
|
31
|
+
kv_store.set("action_manager_instance", instance)
|
|
32
|
+
return instance
|
|
33
|
+
|
|
34
|
+
def __init__(self, kv_store, lock_manager):
|
|
35
|
+
self.action_requests = {}
|
|
36
|
+
self.lock = lock_manager.get_lock("action_manager")
|
|
37
|
+
|
|
38
|
+
with self.lock:
|
|
39
|
+
action_requests = kv_store.get("action_requests")
|
|
40
|
+
if not action_requests:
|
|
41
|
+
action_requests = {}
|
|
42
|
+
kv_store.set("action_requests", action_requests)
|
|
43
|
+
self.action_requests = action_requests
|
|
44
|
+
|
|
45
|
+
def add_action_request(self, action_requestlist, user_properties):
|
|
46
|
+
"""Add an action request to the list"""
|
|
47
|
+
uuid = str(uuid4())
|
|
48
|
+
|
|
49
|
+
# Add the uuid to each action
|
|
50
|
+
for action in action_requestlist:
|
|
51
|
+
action["action_list_id"] = uuid
|
|
52
|
+
with self.lock:
|
|
53
|
+
if uuid in self.action_requests:
|
|
54
|
+
log.error("Action request with UUID %s already exists", uuid)
|
|
55
|
+
|
|
56
|
+
arl = ActionRequestList(uuid, action_requestlist, user_properties)
|
|
57
|
+
self.action_requests[uuid] = arl
|
|
58
|
+
|
|
59
|
+
def delete_action_request(self, action_list_id):
|
|
60
|
+
"""Delete an action request from the list"""
|
|
61
|
+
with self.lock:
|
|
62
|
+
if action_list_id in self.action_requests:
|
|
63
|
+
del self.action_requests[action_list_id]
|
|
64
|
+
|
|
65
|
+
def get_action_info(self, action_list_id, action_name, action_idx):
|
|
66
|
+
"""Get an action request"""
|
|
67
|
+
with self.lock:
|
|
68
|
+
action_list = self.action_requests.get(action_list_id)
|
|
69
|
+
if action_list is None:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
action = action_list.get_action(action_name, action_idx)
|
|
73
|
+
if action is None:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
return action
|
|
77
|
+
|
|
78
|
+
def add_action_response(self, action_response_obj, response_text_and_files):
|
|
79
|
+
"""Add an action response to the list"""
|
|
80
|
+
action_list_id = action_response_obj.get("action_list_id")
|
|
81
|
+
with self.lock:
|
|
82
|
+
action_list = self.action_requests.get(action_list_id)
|
|
83
|
+
if action_list is None:
|
|
84
|
+
log.error(
|
|
85
|
+
"Action request %s not found. Maybe it had already timed out",
|
|
86
|
+
action_list_id,
|
|
87
|
+
)
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
action_list.add_response(action_response_obj, response_text_and_files)
|
|
91
|
+
|
|
92
|
+
return action_list
|
|
93
|
+
|
|
94
|
+
def do_timeout_check(self):
|
|
95
|
+
"""Check for any actions that have timed out"""
|
|
96
|
+
events = []
|
|
97
|
+
with self.lock:
|
|
98
|
+
action_list_ids = list(self.action_requests.keys())
|
|
99
|
+
for action_list_id in action_list_ids:
|
|
100
|
+
action_requestlist = self.action_requests.get(action_list_id)
|
|
101
|
+
if action_requestlist.has_timed_out():
|
|
102
|
+
log.info("Action request %s has timed out", action_list_id)
|
|
103
|
+
events.extend(self.do_timeout(action_requestlist, action_list_id))
|
|
104
|
+
return events
|
|
105
|
+
|
|
106
|
+
def do_timeout(self, action_requestlist, action_list_id):
|
|
107
|
+
"""Handle a timeout for a specific action request"""
|
|
108
|
+
events = action_requestlist.get_action_response_timeout_events()
|
|
109
|
+
if len(events) == 0:
|
|
110
|
+
timeout_count = action_requestlist.get_timeout_count()
|
|
111
|
+
if timeout_count > 10:
|
|
112
|
+
log.error(
|
|
113
|
+
"Action request %s has timed out too many times", action_list_id
|
|
114
|
+
)
|
|
115
|
+
del self.action_requests[action_list_id]
|
|
116
|
+
else:
|
|
117
|
+
action_requestlist.increment_timeout_count()
|
|
118
|
+
return events
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ActionRequestList:
|
|
122
|
+
"""This class holds the list of actions to be executed for a single LLM response"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, action_list_id, actions, user_properties):
|
|
125
|
+
self.action_list_id = action_list_id
|
|
126
|
+
self.actions = actions
|
|
127
|
+
self.user_properties = user_properties
|
|
128
|
+
self.num_pending_actions = len(actions)
|
|
129
|
+
self.create_time = datetime.now()
|
|
130
|
+
self.timeout_count = 0
|
|
131
|
+
self.responses = {}
|
|
132
|
+
|
|
133
|
+
def has_timed_out(self):
|
|
134
|
+
"""Check if the action request has timed out"""
|
|
135
|
+
return (
|
|
136
|
+
datetime.now() - self.create_time
|
|
137
|
+
).total_seconds() > ACTION_REQUEST_TIMEOUT
|
|
138
|
+
|
|
139
|
+
def get_timeout_count(self):
|
|
140
|
+
"""Get the number of times this action request has timed out"""
|
|
141
|
+
return self.timeout_count
|
|
142
|
+
|
|
143
|
+
def increment_timeout_count(self):
|
|
144
|
+
"""Increment the number of times this action request has timed out"""
|
|
145
|
+
self.timeout_count += 1
|
|
146
|
+
|
|
147
|
+
def get_user_properties(self):
|
|
148
|
+
"""Get the user properties"""
|
|
149
|
+
return self.user_properties
|
|
150
|
+
|
|
151
|
+
def get_action_response_timeout_events(self):
|
|
152
|
+
"""Go through all the action requests and return a timeout event
|
|
153
|
+
for each one that does not yet have a response"""
|
|
154
|
+
timeout_events = []
|
|
155
|
+
for action in self.actions:
|
|
156
|
+
if "response" not in action:
|
|
157
|
+
timeout_events.append(
|
|
158
|
+
{
|
|
159
|
+
"message": "Action response timed out",
|
|
160
|
+
"action_list_id": self.action_list_id,
|
|
161
|
+
"action_idx": action.get("action_idx"),
|
|
162
|
+
"action_name": action.get("action_name"),
|
|
163
|
+
"user_properties": self.user_properties,
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
action["response"] = {"text": "Action response timed out"}
|
|
167
|
+
action["timed_out"] = True
|
|
168
|
+
|
|
169
|
+
return timeout_events
|
|
170
|
+
|
|
171
|
+
def get_action(self, action_name, action_idx):
|
|
172
|
+
"""Get the action with the specified name and index"""
|
|
173
|
+
if action_idx is None:
|
|
174
|
+
log.error("Action Response does not have an action_idx")
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
if action_idx >= len(self.actions):
|
|
178
|
+
log.error(
|
|
179
|
+
"Action Response has an invalid action_idx. Max is %s, index is %s",
|
|
180
|
+
len(self.actions),
|
|
181
|
+
action_idx,
|
|
182
|
+
)
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
action = self.actions[action_idx]
|
|
186
|
+
if action.get("action_name") != action_name:
|
|
187
|
+
log.error(
|
|
188
|
+
"Action Response has an invalid action name. Expected %s but got %s",
|
|
189
|
+
action.get("action_name"),
|
|
190
|
+
action_name,
|
|
191
|
+
)
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
return action
|
|
195
|
+
|
|
196
|
+
def add_response(self, action_response_obj, response_text_and_files):
|
|
197
|
+
"""Add a response to the list - returns True if all actions are complete"""
|
|
198
|
+
action_idx = action_response_obj.get("action_idx")
|
|
199
|
+
action_name = action_response_obj.get("action_name")
|
|
200
|
+
|
|
201
|
+
action = self.get_action(action_name, action_idx)
|
|
202
|
+
|
|
203
|
+
if "response" in action and not action.get("timed_out"):
|
|
204
|
+
log.error("Action Response already has a response")
|
|
205
|
+
else:
|
|
206
|
+
action["response"] = response_text_and_files
|
|
207
|
+
self.num_pending_actions -= 1
|
|
208
|
+
|
|
209
|
+
if self.is_complete():
|
|
210
|
+
log.info("Action request %s is complete", self.action_list_id)
|
|
211
|
+
return True
|
|
212
|
+
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
def is_complete(self):
|
|
216
|
+
"""Check if all actions have been completed"""
|
|
217
|
+
return self.num_pending_actions == 0
|
|
218
|
+
|
|
219
|
+
def get_responses(self):
|
|
220
|
+
"""Get all the responses"""
|
|
221
|
+
return self.actions
|
|
222
|
+
|
|
223
|
+
def format_ai_response(self):
|
|
224
|
+
"""Format the action response for the AI"""
|
|
225
|
+
return format_agent_response(self.actions)
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""This is a custom component that handles the action_manager timer going off"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from solace_ai_connector.components.component_base import ComponentBase
|
|
6
|
+
|
|
7
|
+
from solace_ai_connector.common.log import log
|
|
8
|
+
from solace_ai_connector.common.message import Message
|
|
9
|
+
from ..action_manager import ActionManager
|
|
10
|
+
|
|
11
|
+
info = {
|
|
12
|
+
"class_name": "OrchestratorActionManagerTimeoutComponent",
|
|
13
|
+
"description": ("This component handles the action_manager timer going off"),
|
|
14
|
+
"config_parameters": [],
|
|
15
|
+
"input_schema": {
|
|
16
|
+
"type": "none",
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OrchestratorActionManagerTimeoutComponent(ComponentBase):
|
|
22
|
+
|
|
23
|
+
def __init__(self, **kwargs):
|
|
24
|
+
super().__init__(info, **kwargs)
|
|
25
|
+
self.action_manager = ActionManager(self.flow_kv_store, self.flow_lock_manager)
|
|
26
|
+
|
|
27
|
+
def invoke(self, message: Message, data):
|
|
28
|
+
"""Called when the timer goes off"""
|
|
29
|
+
|
|
30
|
+
# Need to go through all the active action_requests and check if any of them have timed out
|
|
31
|
+
timeout_events = self.action_manager.do_timeout_check()
|
|
32
|
+
|
|
33
|
+
# Now turn these into messages
|
|
34
|
+
messages = []
|
|
35
|
+
for event in timeout_events:
|
|
36
|
+
action_name = event.get("action_name")
|
|
37
|
+
if action_name is None:
|
|
38
|
+
log.error("Action name not found in event")
|
|
39
|
+
continue
|
|
40
|
+
action_parts = action_name.split(".")
|
|
41
|
+
if len(action_parts) == 1:
|
|
42
|
+
action_parts.append(action_parts[0])
|
|
43
|
+
action_parts[0] = "global"
|
|
44
|
+
payload = event
|
|
45
|
+
user_properties = event.get("user_properties")
|
|
46
|
+
del payload["user_properties"]
|
|
47
|
+
new_message = {
|
|
48
|
+
"topic": f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/actionResponse/agent/{action_parts[0]}/{action_parts[1]}/timeout",
|
|
49
|
+
"payload": payload,
|
|
50
|
+
"user_properties": user_properties,
|
|
51
|
+
}
|
|
52
|
+
messages.append(new_message)
|
|
53
|
+
|
|
54
|
+
return messages
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""This is the component that handles processing all action responses from agents"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from time import time
|
|
5
|
+
|
|
6
|
+
from solace_ai_connector.components.component_base import ComponentBase
|
|
7
|
+
from solace_ai_connector.common.log import log
|
|
8
|
+
from solace_ai_connector.common.message import Message
|
|
9
|
+
from solace_ai_connector.common.event import Event, EventType
|
|
10
|
+
from ...orchestrator.orchestrator_main import OrchestratorState
|
|
11
|
+
from ...orchestrator.orchestrator_prompt import BasicRagPrompt, ContextQueryPrompt
|
|
12
|
+
from ..action_manager import ActionManager
|
|
13
|
+
|
|
14
|
+
info = {
|
|
15
|
+
"class_name": "OrchestratorActionResponseComponent",
|
|
16
|
+
"description": ("This component handles all action responses from agents"),
|
|
17
|
+
"config_parameters": [],
|
|
18
|
+
"input_schema": {
|
|
19
|
+
# A action response object - it doesn't have a fixed schema
|
|
20
|
+
"type": "object",
|
|
21
|
+
"additionalProperties": True,
|
|
22
|
+
},
|
|
23
|
+
# We will output a list of topics and messages to send to either the
|
|
24
|
+
# orchestrator stimulus or to the originator via slack
|
|
25
|
+
"output_schema": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"items": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"properties": {
|
|
30
|
+
"topic": {"type": "string"},
|
|
31
|
+
"payload": {"type": "object"},
|
|
32
|
+
},
|
|
33
|
+
"required": ["topic", "payload"],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class OrchestratorActionResponseComponent(ComponentBase):
|
|
40
|
+
"""This is the component that handles processing all action responses from agents"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, **kwargs):
|
|
43
|
+
super().__init__(info, **kwargs)
|
|
44
|
+
with self.get_lock("orchestrator_state"):
|
|
45
|
+
self.orchestrator_state = self.kv_store_get("orchestrator_state")
|
|
46
|
+
if not self.orchestrator_state:
|
|
47
|
+
self.orchestrator_state = OrchestratorState()
|
|
48
|
+
self.kv_store_set("orchestrator_state", self.orchestrator_state)
|
|
49
|
+
|
|
50
|
+
self.action_manager = ActionManager(self.flow_kv_store, self.flow_lock_manager)
|
|
51
|
+
|
|
52
|
+
def invoke(self, message: Message, data):
|
|
53
|
+
"""Handle action responses from agents"""
|
|
54
|
+
|
|
55
|
+
user_properties = message.get_user_properties()
|
|
56
|
+
user_properties['timestamp_start'] = time()
|
|
57
|
+
message.set_user_properties(user_properties)
|
|
58
|
+
|
|
59
|
+
if not data:
|
|
60
|
+
log.error("No data received from agent")
|
|
61
|
+
self.discard_current_message()
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
if not isinstance(data, dict):
|
|
65
|
+
log.error("Data received from agent is not a dictionary")
|
|
66
|
+
self.discard_current_message()
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
session_id = data.get("session_id")
|
|
70
|
+
user_response = {}
|
|
71
|
+
|
|
72
|
+
if data.get("message"):
|
|
73
|
+
user_response["text"] = data.get("message")
|
|
74
|
+
|
|
75
|
+
user_response["files"] = []
|
|
76
|
+
if data.get("files"):
|
|
77
|
+
user_response["files"] = data.get("files")
|
|
78
|
+
|
|
79
|
+
if data.get("clear_history"):
|
|
80
|
+
user_properties = message.get_user_properties()
|
|
81
|
+
keep_depth = data.get("history_depth_to_keep")
|
|
82
|
+
if type(keep_depth) != int:
|
|
83
|
+
if keep_depth.isdigit():
|
|
84
|
+
keep_depth = int(keep_depth)
|
|
85
|
+
else:
|
|
86
|
+
keep_depth = 0
|
|
87
|
+
user_properties["clear_gateway_history"] = [True, keep_depth]
|
|
88
|
+
message.set_user_properties(user_properties)
|
|
89
|
+
|
|
90
|
+
if data.get("error_info"):
|
|
91
|
+
if self.error_queue is not None:
|
|
92
|
+
error_info = data.get("error_info", {})
|
|
93
|
+
action_name = data.get("action_name")
|
|
94
|
+
action_list_id = data.get("action_list_id")
|
|
95
|
+
action_idx = data.get("action_idx")
|
|
96
|
+
action = (
|
|
97
|
+
self.action_manager.get_action_info(
|
|
98
|
+
action_list_id, action_name, action_idx
|
|
99
|
+
)
|
|
100
|
+
or {}
|
|
101
|
+
)
|
|
102
|
+
agent_name = action.get("agent_name", "Unknown")
|
|
103
|
+
|
|
104
|
+
source = ""
|
|
105
|
+
if agent_name == "global" and action_name == "error_action":
|
|
106
|
+
source = "Error raised by the Orchestrator"
|
|
107
|
+
else:
|
|
108
|
+
source = f"Agent: {agent_name}, Action: {action_name}, Params: {action.get('action_params', {})}"
|
|
109
|
+
|
|
110
|
+
self.error_queue.put(
|
|
111
|
+
Event(
|
|
112
|
+
EventType.MESSAGE,
|
|
113
|
+
Message(
|
|
114
|
+
payload={
|
|
115
|
+
"error_message": error_info.get("error_message"),
|
|
116
|
+
"source": source,
|
|
117
|
+
},
|
|
118
|
+
user_properties=message.get_user_properties()
|
|
119
|
+
if message
|
|
120
|
+
else {},
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if data.get("agent_state_change"):
|
|
126
|
+
agent_state_change = data.get("agent_state_change")
|
|
127
|
+
agent_name = agent_state_change.get("agent_name")
|
|
128
|
+
state = agent_state_change.get("new_state")
|
|
129
|
+
self.orchestrator_state.update_agent_state(agent_name, state, session_id)
|
|
130
|
+
if state == "open":
|
|
131
|
+
user_response["text"] = (
|
|
132
|
+
f"Opened {agent_name}. Now you can interact with it."
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if data.get("context_query"):
|
|
136
|
+
cq = data.get("context_query")
|
|
137
|
+
query = cq.get("query")
|
|
138
|
+
context = cq.get("context")
|
|
139
|
+
context_type = cq.get("context_type")
|
|
140
|
+
if context_type == "raw":
|
|
141
|
+
user_response["text"] = query
|
|
142
|
+
else:
|
|
143
|
+
user_response["text"] = ContextQueryPrompt(query=query, context=context)
|
|
144
|
+
|
|
145
|
+
events = []
|
|
146
|
+
|
|
147
|
+
# Tell the ActionManager about the result - if it is complete, then
|
|
148
|
+
# it will send the result back to the model
|
|
149
|
+
action_list = self.action_manager.add_action_response(data, user_response)
|
|
150
|
+
if action_list and action_list.is_complete():
|
|
151
|
+
response_text, files = action_list.format_ai_response()
|
|
152
|
+
|
|
153
|
+
if response_text:
|
|
154
|
+
# Send the result back to the model
|
|
155
|
+
user_properties = message.get_user_properties()
|
|
156
|
+
events.append(
|
|
157
|
+
{
|
|
158
|
+
"topic": f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/stimulus/orchestrator/reinvokeModel",
|
|
159
|
+
"payload": {
|
|
160
|
+
"text": response_text,
|
|
161
|
+
"files": files,
|
|
162
|
+
"identity": user_properties.get("identity"),
|
|
163
|
+
"channel": user_properties.get("channel"),
|
|
164
|
+
"thread_ts": user_properties.get("thread_ts"),
|
|
165
|
+
"action_response_reinvoke": True,
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
self.action_manager.delete_action_request(action_list.action_list_id)
|
|
170
|
+
|
|
171
|
+
if len(events) == 0:
|
|
172
|
+
self.discard_current_message()
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
user_properties = message.get_user_properties()
|
|
176
|
+
user_properties['timestamp_end'] = time()
|
|
177
|
+
message.set_user_properties(user_properties)
|
|
178
|
+
|
|
179
|
+
return events
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""This is a custom component that handles registrations from the distributed agents"""
|
|
2
|
+
|
|
3
|
+
# from solace_ai_connector.common.log import log
|
|
4
|
+
from solace_ai_connector.components.component_base import ComponentBase
|
|
5
|
+
from solace_ai_connector.common.message import Message
|
|
6
|
+
from ...orchestrator.orchestrator_main import OrchestratorState
|
|
7
|
+
|
|
8
|
+
info = {
|
|
9
|
+
"class_name": "OrchestratorRegister",
|
|
10
|
+
"description": ("This component handles registrations from the distributed agents"),
|
|
11
|
+
"config_parameters": [
|
|
12
|
+
{
|
|
13
|
+
"name": "agent_ttl_ms",
|
|
14
|
+
"required": False,
|
|
15
|
+
"description": "The time-to-live for agent registrations in milliseconds. There must be a registration from an agent within this time period, otherwise the agent will be considered offline.",
|
|
16
|
+
"default": 60000,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
"input_schema": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {
|
|
22
|
+
"agent_name": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "The name of the agent.",
|
|
25
|
+
},
|
|
26
|
+
"description": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "A description of the application.",
|
|
29
|
+
},
|
|
30
|
+
"actions": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"description": "A list of actions the application can perform.",
|
|
33
|
+
"items": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"name": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "The name of the action.",
|
|
39
|
+
},
|
|
40
|
+
"description": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "A description of what the action does.",
|
|
43
|
+
},
|
|
44
|
+
"params": {
|
|
45
|
+
"type": "array",
|
|
46
|
+
"description": "A list of parameters for the action.",
|
|
47
|
+
"items": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": {
|
|
50
|
+
"name": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "The name of the parameter.",
|
|
53
|
+
},
|
|
54
|
+
"description": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "A description of the parameter.",
|
|
57
|
+
},
|
|
58
|
+
"type": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "The type of the parameter.",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
"required": ["name", "description", "type"],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
"examples": {
|
|
67
|
+
"type": "array",
|
|
68
|
+
"description": "A list of examples for the action.",
|
|
69
|
+
"items": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"description": "An example for the action.",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
"required_scopes": {
|
|
75
|
+
"type": "array",
|
|
76
|
+
"description": "A list of required scopes for the action.",
|
|
77
|
+
"items": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "A required scope for the action.",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
"required": ["name", "description", "params"],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
"required": ["agent_name", "description", "actions"],
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class OrchestratorRegister(ComponentBase):
|
|
93
|
+
def __init__(self, **kwargs):
|
|
94
|
+
super().__init__(info, **kwargs)
|
|
95
|
+
self.agent_ttl_ms = self.get_config("agent_ttl_ms")
|
|
96
|
+
OrchestratorState.set_config({"agent_ttl_ms": self.agent_ttl_ms})
|
|
97
|
+
with self.get_lock("orchestrator_state"):
|
|
98
|
+
self.orchestrator_state = self.kv_store_get("orchestrator_state")
|
|
99
|
+
if not self.orchestrator_state:
|
|
100
|
+
self.orchestrator_state = OrchestratorState()
|
|
101
|
+
self.kv_store_set("orchestrator_state", self.orchestrator_state)
|
|
102
|
+
|
|
103
|
+
def invoke(self, message: Message, data):
|
|
104
|
+
"""Receive a registration from an agent and store it in the component's state."""
|
|
105
|
+
# log.info("Received registration from agent")
|
|
106
|
+
self.orchestrator_state.register_agent(data)
|
|
107
|
+
return data
|