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.

Files changed (176) hide show
  1. solace_agent_mesh/__init__.py +0 -3
  2. solace_agent_mesh/agents/__init__.py +0 -0
  3. solace_agent_mesh/agents/base_agent_component.py +224 -0
  4. solace_agent_mesh/agents/global/__init__.py +0 -0
  5. solace_agent_mesh/agents/global/actions/__init__.py +0 -0
  6. solace_agent_mesh/agents/global/actions/agent_state_change.py +54 -0
  7. solace_agent_mesh/agents/global/actions/clear_history.py +32 -0
  8. solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +160 -0
  9. solace_agent_mesh/agents/global/actions/create_file.py +70 -0
  10. solace_agent_mesh/agents/global/actions/error_action.py +45 -0
  11. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +93 -0
  12. solace_agent_mesh/agents/global/actions/plotly_graph.py +117 -0
  13. solace_agent_mesh/agents/global/actions/retrieve_file.py +51 -0
  14. solace_agent_mesh/agents/global/global_agent_component.py +38 -0
  15. solace_agent_mesh/agents/image_processing/__init__.py +0 -0
  16. solace_agent_mesh/agents/image_processing/actions/__init__.py +0 -0
  17. solace_agent_mesh/agents/image_processing/actions/create_image.py +75 -0
  18. solace_agent_mesh/agents/image_processing/actions/describe_image.py +115 -0
  19. solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +23 -0
  20. solace_agent_mesh/agents/slack/__init__.py +1 -0
  21. solace_agent_mesh/agents/slack/actions/__init__.py +1 -0
  22. solace_agent_mesh/agents/slack/actions/post_message.py +177 -0
  23. solace_agent_mesh/agents/slack/slack_agent_component.py +59 -0
  24. solace_agent_mesh/agents/web_request/__init__.py +0 -0
  25. solace_agent_mesh/agents/web_request/actions/__init__.py +0 -0
  26. solace_agent_mesh/agents/web_request/actions/do_image_search.py +84 -0
  27. solace_agent_mesh/agents/web_request/actions/do_news_search.py +47 -0
  28. solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +34 -0
  29. solace_agent_mesh/agents/web_request/actions/do_web_request.py +134 -0
  30. solace_agent_mesh/agents/web_request/actions/download_file.py +69 -0
  31. solace_agent_mesh/agents/web_request/web_request_agent_component.py +33 -0
  32. solace_agent_mesh/assets/web-visualizer/assets/index-C5awueeJ.js +109 -0
  33. solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +1 -0
  34. solace_agent_mesh/assets/web-visualizer/index.html +14 -0
  35. solace_agent_mesh/assets/web-visualizer/vite.svg +1 -0
  36. solace_agent_mesh/cli/__init__.py +1 -0
  37. solace_agent_mesh/cli/commands/__init__.py +0 -0
  38. solace_agent_mesh/cli/commands/add/__init__.py +3 -0
  39. solace_agent_mesh/cli/commands/add/add.py +88 -0
  40. solace_agent_mesh/cli/commands/add/agent.py +110 -0
  41. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +90 -0
  42. solace_agent_mesh/cli/commands/add/gateway.py +221 -0
  43. solace_agent_mesh/cli/commands/build.py +631 -0
  44. solace_agent_mesh/cli/commands/chat/__init__.py +3 -0
  45. solace_agent_mesh/cli/commands/chat/chat.py +361 -0
  46. solace_agent_mesh/cli/commands/config.py +29 -0
  47. solace_agent_mesh/cli/commands/init/__init__.py +3 -0
  48. solace_agent_mesh/cli/commands/init/ai_provider_step.py +76 -0
  49. solace_agent_mesh/cli/commands/init/broker_step.py +102 -0
  50. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +88 -0
  51. solace_agent_mesh/cli/commands/init/check_if_already_done.py +13 -0
  52. solace_agent_mesh/cli/commands/init/create_config_file_step.py +52 -0
  53. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +96 -0
  54. solace_agent_mesh/cli/commands/init/file_service_step.py +73 -0
  55. solace_agent_mesh/cli/commands/init/init.py +114 -0
  56. solace_agent_mesh/cli/commands/init/project_structure_step.py +45 -0
  57. solace_agent_mesh/cli/commands/init/rest_api_step.py +50 -0
  58. solace_agent_mesh/cli/commands/init/web_ui_step.py +40 -0
  59. solace_agent_mesh/cli/commands/plugin/__init__.py +3 -0
  60. solace_agent_mesh/cli/commands/plugin/add.py +98 -0
  61. solace_agent_mesh/cli/commands/plugin/build.py +217 -0
  62. solace_agent_mesh/cli/commands/plugin/create.py +117 -0
  63. solace_agent_mesh/cli/commands/plugin/plugin.py +109 -0
  64. solace_agent_mesh/cli/commands/plugin/remove.py +71 -0
  65. solace_agent_mesh/cli/commands/run.py +68 -0
  66. solace_agent_mesh/cli/commands/visualizer.py +138 -0
  67. solace_agent_mesh/cli/config.py +81 -0
  68. solace_agent_mesh/cli/main.py +306 -0
  69. solace_agent_mesh/cli/utils.py +246 -0
  70. solace_agent_mesh/common/__init__.py +0 -0
  71. solace_agent_mesh/common/action.py +91 -0
  72. solace_agent_mesh/common/action_list.py +37 -0
  73. solace_agent_mesh/common/action_response.py +327 -0
  74. solace_agent_mesh/common/constants.py +3 -0
  75. solace_agent_mesh/common/mysql_database.py +40 -0
  76. solace_agent_mesh/common/postgres_database.py +79 -0
  77. solace_agent_mesh/common/prompt_templates.py +30 -0
  78. solace_agent_mesh/common/prompt_templates_unused_delete.py +161 -0
  79. solace_agent_mesh/common/stimulus_utils.py +152 -0
  80. solace_agent_mesh/common/time.py +24 -0
  81. solace_agent_mesh/common/utils.py +638 -0
  82. solace_agent_mesh/configs/agent_global.yaml +74 -0
  83. solace_agent_mesh/configs/agent_image_processing.yaml +82 -0
  84. solace_agent_mesh/configs/agent_slack.yaml +64 -0
  85. solace_agent_mesh/configs/agent_web_request.yaml +75 -0
  86. solace_agent_mesh/configs/conversation_to_file.yaml +56 -0
  87. solace_agent_mesh/configs/error_catcher.yaml +56 -0
  88. solace_agent_mesh/configs/monitor.yaml +0 -0
  89. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +106 -0
  90. solace_agent_mesh/configs/monitor_user_feedback.yaml +58 -0
  91. solace_agent_mesh/configs/orchestrator.yaml +241 -0
  92. solace_agent_mesh/configs/service_embedding.yaml +81 -0
  93. solace_agent_mesh/configs/service_llm.yaml +265 -0
  94. solace_agent_mesh/configs/visualize_websocket.yaml +55 -0
  95. solace_agent_mesh/gateway/__init__.py +0 -0
  96. solace_agent_mesh/gateway/components/__init__.py +0 -0
  97. solace_agent_mesh/gateway/components/gateway_base.py +41 -0
  98. solace_agent_mesh/gateway/components/gateway_input.py +265 -0
  99. solace_agent_mesh/gateway/components/gateway_output.py +289 -0
  100. solace_agent_mesh/gateway/identity/bamboohr_identity.py +18 -0
  101. solace_agent_mesh/gateway/identity/identity_base.py +10 -0
  102. solace_agent_mesh/gateway/identity/identity_provider.py +60 -0
  103. solace_agent_mesh/gateway/identity/no_identity.py +9 -0
  104. solace_agent_mesh/gateway/identity/passthru_identity.py +9 -0
  105. solace_agent_mesh/monitors/base_monitor_component.py +26 -0
  106. solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +75 -0
  107. solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +560 -0
  108. solace_agent_mesh/orchestrator/__init__.py +0 -0
  109. solace_agent_mesh/orchestrator/action_manager.py +225 -0
  110. solace_agent_mesh/orchestrator/components/__init__.py +0 -0
  111. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +54 -0
  112. solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +179 -0
  113. solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +107 -0
  114. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +477 -0
  115. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +246 -0
  116. solace_agent_mesh/orchestrator/orchestrator_main.py +166 -0
  117. solace_agent_mesh/orchestrator/orchestrator_prompt.py +410 -0
  118. solace_agent_mesh/services/__init__.py +0 -0
  119. solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +56 -0
  120. solace_agent_mesh/services/bamboo_hr_service/__init__.py +3 -0
  121. solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +182 -0
  122. solace_agent_mesh/services/common/__init__.py +4 -0
  123. solace_agent_mesh/services/common/auto_expiry.py +45 -0
  124. solace_agent_mesh/services/common/singleton.py +18 -0
  125. solace_agent_mesh/services/file_service/__init__.py +14 -0
  126. solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
  127. solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +149 -0
  128. solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +162 -0
  129. solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +64 -0
  130. solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +106 -0
  131. solace_agent_mesh/services/file_service/file_service.py +432 -0
  132. solace_agent_mesh/services/file_service/file_service_constants.py +54 -0
  133. solace_agent_mesh/services/file_service/file_transformations.py +131 -0
  134. solace_agent_mesh/services/file_service/file_utils.py +322 -0
  135. solace_agent_mesh/services/file_service/transformers/__init__.py +5 -0
  136. solace_agent_mesh/services/history_service/__init__.py +3 -0
  137. solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
  138. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +78 -0
  139. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +167 -0
  140. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +163 -0
  141. solace_agent_mesh/services/history_service/history_service.py +139 -0
  142. solace_agent_mesh/services/llm_service/components/llm_request_component.py +293 -0
  143. solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +152 -0
  144. solace_agent_mesh/services/middleware_service/__init__.py +0 -0
  145. solace_agent_mesh/services/middleware_service/middleware_service.py +20 -0
  146. solace_agent_mesh/templates/action.py +38 -0
  147. solace_agent_mesh/templates/agent.py +29 -0
  148. solace_agent_mesh/templates/agent.yaml +70 -0
  149. solace_agent_mesh/templates/gateway-config-template.yaml +6 -0
  150. solace_agent_mesh/templates/gateway-default-config.yaml +28 -0
  151. solace_agent_mesh/templates/gateway-flows.yaml +81 -0
  152. solace_agent_mesh/templates/gateway-header.yaml +16 -0
  153. solace_agent_mesh/templates/gateway_base.py +15 -0
  154. solace_agent_mesh/templates/gateway_input.py +98 -0
  155. solace_agent_mesh/templates/gateway_output.py +71 -0
  156. solace_agent_mesh/templates/plugin-pyproject.toml +30 -0
  157. solace_agent_mesh/templates/rest-api-default-config.yaml +24 -0
  158. solace_agent_mesh/templates/rest-api-flows.yaml +80 -0
  159. solace_agent_mesh/templates/slack-default-config.yaml +9 -0
  160. solace_agent_mesh/templates/slack-flows.yaml +90 -0
  161. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +77 -0
  162. solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +8 -0
  163. solace_agent_mesh/templates/web-default-config.yaml +5 -0
  164. solace_agent_mesh/templates/web-flows.yaml +86 -0
  165. solace_agent_mesh/tools/__init__.py +0 -0
  166. solace_agent_mesh/tools/components/__init__.py +0 -0
  167. solace_agent_mesh/tools/components/conversation_formatter.py +111 -0
  168. solace_agent_mesh/tools/components/file_resolver_component.py +58 -0
  169. solace_agent_mesh/tools/config/runtime_config.py +26 -0
  170. solace_agent_mesh-0.1.1.dist-info/METADATA +179 -0
  171. solace_agent_mesh-0.1.1.dist-info/RECORD +174 -0
  172. solace_agent_mesh-0.1.1.dist-info/entry_points.txt +3 -0
  173. solace_agent_mesh-0.0.1.dist-info/licenses/LICENSE.txt → solace_agent_mesh-0.1.1.dist-info/licenses/LICENSE +1 -2
  174. solace_agent_mesh-0.0.1.dist-info/METADATA +0 -51
  175. solace_agent_mesh-0.0.1.dist-info/RECORD +0 -5
  176. {solace_agent_mesh-0.0.1.dist-info → solace_agent_mesh-0.1.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,477 @@
1
+ """This is the component that handles request from users and forms the appropriate prompt for the LLM, makes the call, and parses the output and creates the appropriate ActionRequests"""
2
+
3
+ import datetime
4
+ import random
5
+ import json
6
+ import copy
7
+ import uuid
8
+ import os
9
+ from dateutil.tz import tzlocal
10
+ from time import time
11
+ from typing import Dict, Any
12
+ import yaml
13
+
14
+ from solace_ai_connector.common.log import log
15
+ from solace_ai_connector.common.message import Message
16
+
17
+ from ...services.llm_service.components.llm_request_component import LLMRequestComponent, info as base_info
18
+ from ...services.middleware_service.middleware_service import MiddlewareService
19
+ from ...services.file_service import FileService
20
+ from ...services.history_service import HistoryService
21
+ from ..orchestrator_main import (
22
+ OrchestratorState,
23
+ ORCHESTRATOR_HISTORY_IDENTIFIER,
24
+ ORCHESTRATOR_HISTORY_CONFIG,
25
+ )
26
+ from ..orchestrator_prompt import (
27
+ SystemPrompt,
28
+ UserStimulusPrompt,
29
+ ActionResponsePrompt,
30
+ )
31
+ from ...common.utils import files_to_block_text, parse_orchestrator_response
32
+ from ..action_manager import ActionManager
33
+
34
+
35
+ info = base_info.copy()
36
+ info["class_name"] = "OrchestratorStimulusProcessorComponent"
37
+ info["description"] = (
38
+ "This component is the main orchestrator of the system that "
39
+ "handles request from users and forms the appropriate prompt "
40
+ "for the LLM, makes the call, and parses the output and creates "
41
+ "the appropriate ActionRequests"
42
+ )
43
+ info["input_schema"] = {
44
+ "type": "object",
45
+ "properties": {
46
+ "event": {
47
+ "type": "object",
48
+ "properties": {
49
+ "text": {
50
+ "type": "string",
51
+ },
52
+ "files": {
53
+ "type": "array",
54
+ "items": {
55
+ "type": "object",
56
+ "properties": {
57
+ "name": {
58
+ "type": "string",
59
+ },
60
+ "data": {
61
+ "type": "string",
62
+ },
63
+ "mime_type": {
64
+ "type": "string",
65
+ },
66
+ "url": {
67
+ "type": "string",
68
+ },
69
+ "file_size": {
70
+ "type": "number",
71
+ },
72
+ },
73
+ },
74
+ },
75
+ "identity": {
76
+ "type": "string",
77
+ },
78
+ },
79
+ },
80
+ },
81
+ "required": ["event"],
82
+ }
83
+
84
+ info["output_schema"] = {
85
+ "type": "array",
86
+ "items": {
87
+ "type": "object",
88
+ "properties": {
89
+ "topic": {"type": "string"},
90
+ "payload": {"type": "object"},
91
+ },
92
+ "required": ["topic", "payload"],
93
+ },
94
+ }
95
+
96
+
97
+ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
98
+ """This is the component that handles request fromn users and forms the appropriate prompt for the LLM, makes the call, and parses the output and creates the appropriate ActionRequests"""
99
+
100
+ def __init__(self, **kwargs):
101
+ super().__init__(info, **kwargs)
102
+ with self.get_lock("orchestrator_state"):
103
+ self.orchestrator_state = self.kv_store_get("orchestrator_state")
104
+ if not self.orchestrator_state:
105
+ self.orchestrator_state = OrchestratorState()
106
+ self.kv_store_set("orchestrator_state", self.orchestrator_state)
107
+
108
+ self.history = HistoryService(
109
+ ORCHESTRATOR_HISTORY_CONFIG, identifier=ORCHESTRATOR_HISTORY_IDENTIFIER
110
+ )
111
+ self.action_manager = ActionManager(self.flow_kv_store, self.flow_lock_manager)
112
+ self.stream_to_flow = self.get_config("stream_to_flow")
113
+
114
+
115
+ def invoke(self, message: Message, data: Dict[str, Any]) -> Dict[str, Any]:
116
+ user_properties = message.get_user_properties()
117
+ user_properties['timestamp_start'] = time()
118
+ message.set_user_properties(user_properties)
119
+
120
+ results = self.pre_llm(message, data)
121
+ message.set_payload(results)
122
+
123
+ results = self.llm_call(message, results)
124
+ message.set_payload(results)
125
+
126
+ results = self.post_llm(message, results)
127
+
128
+ user_properties = message.get_user_properties()
129
+ user_properties['timestamp_end'] = time()
130
+ message.set_user_properties(user_properties)
131
+
132
+ return results
133
+
134
+ def pre_llm(self, message: Message, data) -> Message:
135
+ """Handle incoming stimuli"""
136
+
137
+ payload = data
138
+
139
+ chat_text = payload.get("text")
140
+ files = payload.get("files") or []
141
+ errors = payload.get("errors") or []
142
+ action_response_reinvoke = payload.get("action_response_reinvoke")
143
+
144
+ # Expand the files into the chat_text
145
+ file_text_blocks = files_to_block_text(files)
146
+ if file_text_blocks:
147
+ chat_text += file_text_blocks
148
+
149
+ available_files = []
150
+ try:
151
+ user_properties = message.get_user_properties() or {}
152
+ available_files_user_prop = json.loads(
153
+ user_properties.get("available_files", "[]")
154
+ )
155
+ # If we have some files, also add them
156
+ if files:
157
+ for file in files:
158
+ if file.get("url"):
159
+ available_files_user_prop.append(
160
+ {
161
+ "url": file.get("url"),
162
+ "name": file.get("name"),
163
+ "mime_type": file.get("mime_type"),
164
+ }
165
+ )
166
+ # Save the available files in the user properties
167
+ user_properties["available_files"] = json.dumps(
168
+ available_files_user_prop
169
+ )
170
+ if available_files_user_prop:
171
+ available_files = [
172
+ FileService.get_file_block_by_metadata(file)
173
+ for file in available_files_user_prop
174
+ ]
175
+ except Exception as e:
176
+ log.error("Failed to get available files: %s", str(e))
177
+
178
+ user_properties = message.get_user_properties()
179
+ stimulus_uuid = user_properties.get("stimulus_uuid")
180
+
181
+ if not stimulus_uuid:
182
+ stimulus_uuid = str(uuid.uuid4())
183
+ user_properties["stimulus_uuid"] = stimulus_uuid
184
+ message.set_user_properties(user_properties)
185
+
186
+ input_data = self.get_user_input(chat_text)
187
+ user_info = payload.get("user_info", {"email": "unknown"})
188
+
189
+ agent_state_yaml, examples = self.get_agents_yaml(user_properties)
190
+ full_input = {
191
+ "input_yaml": yaml.dump(input_data),
192
+ "input": input_data,
193
+ "originator_info_yaml": yaml.dump(user_info),
194
+ "originator_persona_prompt": user_properties.get(
195
+ "originator_persona_prompt"
196
+ ),
197
+ "system_purpose": user_properties.get("system_purpose"),
198
+ "response_format_prompt": user_properties.get("response_format_prompt"),
199
+ "originator_info": user_info, # Do we need this?
200
+ "agent_state_yaml": agent_state_yaml,
201
+ "tag_prefix": "t"
202
+ + str(random.randint(100, 999))
203
+ + "_", # Prefix with 't' as XML tags cannot start with a number
204
+ "available_files": available_files,
205
+ }
206
+
207
+ # Get the prompts
208
+ gateway_history = self.get_gateway_history(data)
209
+ system_prompt = SystemPrompt(full_input, examples)
210
+ if action_response_reinvoke:
211
+ user_prompt = ActionResponsePrompt(
212
+ {"input": chat_text, "tag_prefix": full_input["tag_prefix"]}
213
+ )
214
+ else:
215
+ has_files = len(files) > 0 or len(available_files) > 0
216
+ user_prompt = UserStimulusPrompt(
217
+ full_input, gateway_history, errors, has_files
218
+ )
219
+
220
+ # Store the user prompt in the history
221
+ self.history.store_history(stimulus_uuid, "user", user_prompt)
222
+
223
+ # Get the all the messages
224
+ orchestrator_history = self.history.get_history(stimulus_uuid)
225
+
226
+ result = {
227
+ "messages": [
228
+ {"role": "system", "content": system_prompt},
229
+ *orchestrator_history,
230
+ ],
231
+ }
232
+
233
+ return result
234
+
235
+ def post_llm(self, message: Message, data) -> Message:
236
+ """Handle LLM responses"""
237
+ content = data.get("content", "")
238
+ response_obj = parse_orchestrator_response(content, last_chunk=True)
239
+ user_properties = message.get_user_properties()
240
+ session_id = user_properties.get("session_id")
241
+
242
+ # Check if there was a parsing error
243
+ if (
244
+ not response_obj
245
+ or "actions" not in response_obj
246
+ or "content" not in response_obj
247
+ or not isinstance(response_obj["actions"], list)
248
+ or not isinstance(response_obj["content"], list)
249
+ or (len(response_obj["actions"]) == 0 and len(response_obj["content"]) == 0)
250
+ ):
251
+ # We need to re-invoke the LLM to let it know that the
252
+ # response was not correctly formatted
253
+ log.error("Failed to parse response - reinvoking LLM: %s", data)
254
+ msg = "Failed to parse your response. Please try again and make sure your response conforms to the expected format."
255
+ if response_obj:
256
+ content = response_obj.get("content", [])
257
+ actions = response_obj.get("actions", [])
258
+ if len(content) == 0 and len(actions) == 0:
259
+ msg = "There were no actions and no text or files in the response. Please try again and ensure your formatting is correct."
260
+ elif len(actions):
261
+ # Loop through the actions and make sure they all have an action and agent
262
+ for action in actions:
263
+ if not action.get("action") or not action.get("agent"):
264
+ msg = "There were actions in the response that were missing an action or agent. Please try again and ensure your formatting is correct."
265
+ break
266
+ # Store the current_subject_starting_id in the session state so that we can
267
+ # use it later to trim history
268
+ self.orchestrator_state.set_current_subject_starting_id(
269
+ session_id,
270
+ response_obj.get("current_subject_starting_id"),
271
+ )
272
+ return [
273
+ {
274
+ "payload": {
275
+ "text": msg,
276
+ "identity": user_properties.get("identity"),
277
+ "channel": user_properties.get("channel"),
278
+ "thread_ts": user_properties.get("thread_ts"),
279
+ "action_response_reinvoke": True,
280
+ },
281
+ "topic": f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/stimulus/orchestrator/reinvokeModel",
282
+ }
283
+ ]
284
+
285
+ try:
286
+ # If there are errors, we need to send it to the orchestrator
287
+ if "errors" in response_obj and len(response_obj["errors"]) > 0:
288
+ log.error("Errors in response: %s", response_obj["errors"])
289
+ return [
290
+ {
291
+ "payload": {
292
+ "text": "The following errors were encountered while processing the stimulus: "
293
+ + ", ".join(response_obj["errors"]),
294
+ "identity": user_properties.get("identity"),
295
+ "channel": user_properties.get("channel"),
296
+ "thread_ts": user_properties.get("thread_ts"),
297
+ "action_response_reinvoke": True,
298
+ },
299
+ "topic": f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/stimulus/orchestrator/reinvokeModel",
300
+ }
301
+ ]
302
+
303
+ action_requests = self.create_action_requests(response_obj, user_properties)
304
+ except ValueError as e:
305
+ return [
306
+ {
307
+ "payload": {
308
+ "text": f"Check your agents and actions something was wrong: {str(e)}",
309
+ "identity": user_properties.get("identity"),
310
+ "action_response_reinvoke": True,
311
+ },
312
+ "topic": f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/stimulus/orchestrator/reinvokeModel",
313
+ }
314
+ ]
315
+
316
+ if not action_requests or len(action_requests) == 0:
317
+ # There are no actions to perform, so we must send the
318
+ # responseComplete message - note that because it
319
+ # is very likely that there is a streamed response in flight
320
+ # we need to send the complete event on the same path to ensure
321
+ # that the ordering is preserved
322
+ user_properties = message.get_user_properties()
323
+ gateway_id = user_properties.get("gateway_id")
324
+ if self.stream_to_flow:
325
+ message = Message(
326
+ payload={
327
+ "response_complete": True,
328
+ "streaming": True,
329
+ },
330
+ user_properties=user_properties,
331
+ topic=f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/responseComplete/orchestrator/{gateway_id}",
332
+ )
333
+ self.send_to_flow(self.stream_to_flow, message)
334
+ self.discard_current_message()
335
+ return None
336
+ else:
337
+ return [
338
+ {
339
+ "payload": {},
340
+ "topic": f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/responseComplete/orchestrator/{gateway_id}",
341
+ }
342
+ ]
343
+
344
+ # Pull out the action requests from the payload and add them to the action manager
345
+ ars = [item["payload"] for item in action_requests]
346
+
347
+ self.action_manager.add_action_request(ars, message.get_user_properties())
348
+
349
+ return action_requests
350
+
351
+ def llm_call(self, message: Message, data) -> Message:
352
+ """
353
+ Invoke the LLM service request.
354
+
355
+ Args:
356
+ message (Message): The input message.
357
+ data (Dict[str, Any]): The input data containing the messages.
358
+
359
+ Returns:
360
+ Dict[str, Any]: The response from the LLM service.
361
+ """
362
+ messages = data.get("messages", [])
363
+ llm_message = self._create_llm_message(message, messages, {"type": "orchestrator"})
364
+ response_uuid = str(uuid.uuid4())
365
+
366
+ try:
367
+ if self.llm_mode == "stream":
368
+ return self._handle_streaming(message, llm_message, response_uuid)
369
+ else:
370
+ return self._handle_sync(llm_message)
371
+ except Exception as e:
372
+ log.error("Error invoking LLM service: %s", e, exc_info=True)
373
+ raise
374
+
375
+ def get_gateway_history(self, data):
376
+ gateway_history = data.get("history", [])
377
+ # Returning the history from the last user message
378
+ first_user_idx = None
379
+ last_user_idx = None
380
+ for idx, item in enumerate(gateway_history):
381
+ if item["role"] == "user":
382
+ if first_user_idx is None:
383
+ first_user_idx = idx
384
+ last_user_idx = idx
385
+
386
+ if first_user_idx is None:
387
+ return [] # No user query found
388
+
389
+ if not last_user_idx > first_user_idx:
390
+ # Latest user message is already handled by orchestator history
391
+ return []
392
+
393
+ return gateway_history[first_user_idx:last_user_idx]
394
+
395
+ def get_user_input(self, chat_text):
396
+
397
+ now = datetime.datetime.now(tzlocal())
398
+ datetime_string = now.strftime("%Y-%m-%d %H:%M:%S %Z%z")
399
+ # Add the current day of the week
400
+ day_of_week = now.strftime("%A")
401
+ datetime_string += f" ({day_of_week})"
402
+
403
+ input_data = {
404
+ "originator_input": chat_text,
405
+ "current_time_iso": datetime_string,
406
+ }
407
+
408
+ return input_data
409
+
410
+ def extract_examples_from_actions(self, agents):
411
+ examples = []
412
+ for agent in agents.values():
413
+ if not agent.get("actions"):
414
+ continue
415
+ for action in agent["actions"]:
416
+ if action:
417
+ action_data = action[next(iter(action))]
418
+ popped_examples = action_data.pop("examples", [])
419
+ if agent.get("state") == "open":
420
+ examples.extend(popped_examples)
421
+ return examples
422
+
423
+ def get_agents_yaml(self, user_properties: dict):
424
+ agents = copy.deepcopy(
425
+ self.orchestrator_state.get_agents_and_actions(user_properties)
426
+ )
427
+ examples = self.extract_examples_from_actions(agents)
428
+ agents_yaml = yaml.dump(agents)
429
+ return agents_yaml, examples
430
+
431
+ def create_action_requests(self, response_obj: dict, user_properties: dict) -> list:
432
+ """Create ActionRequests from the response object"""
433
+
434
+ action_requests = []
435
+
436
+ if "actions" not in response_obj:
437
+ return action_requests
438
+
439
+ action_idx = 0
440
+ for action in response_obj["actions"]:
441
+ action_name = action.get("action")
442
+ agent_name = action.get("agent")
443
+ action_details = self.orchestrator_state.get_agent_action(
444
+ agent_name, action_name
445
+ )
446
+ if not action_details:
447
+ raise ValueError(
448
+ f"Action not found in agent: {agent_name}, {action_name}"
449
+ )
450
+ middleware_service = MiddlewareService()
451
+ if middleware_service.get("validate_action_request")(user_properties, action_details):
452
+ action_params = action.get("parameters", {})
453
+
454
+ action_requests.append(
455
+ {
456
+ "payload": {
457
+ "agent_name": agent_name,
458
+ "action_name": action_name,
459
+ "action_params": action_params,
460
+ "action_idx": action_idx,
461
+ },
462
+ "topic": f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/actionRequest/orchestrator/agent/{agent_name}/{action_name}",
463
+ }
464
+ )
465
+ action_idx += 1
466
+
467
+ else:
468
+ log.error(
469
+ "Unauthorized to perform action: %s, %s",
470
+ agent_name,
471
+ action_name,
472
+ )
473
+ raise ValueError(
474
+ f"Unauthorized to perform action: {agent_name}, {action_name}"
475
+ )
476
+
477
+ return action_requests