solace-agent-mesh 0.1.2__py3-none-any.whl → 0.2.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.

Files changed (59) hide show
  1. solace_agent_mesh/agents/base_agent_component.py +2 -0
  2. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +14 -2
  3. solace_agent_mesh/agents/global/actions/plotly_graph.py +49 -40
  4. solace_agent_mesh/agents/web_request/actions/do_web_request.py +34 -33
  5. solace_agent_mesh/cli/__init__.py +1 -1
  6. solace_agent_mesh/cli/commands/add/gateway.py +162 -9
  7. solace_agent_mesh/cli/commands/build.py +0 -1
  8. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +1 -6
  9. solace_agent_mesh/cli/commands/init/create_config_file_step.py +5 -0
  10. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +52 -1
  11. solace_agent_mesh/cli/commands/init/init.py +1 -5
  12. solace_agent_mesh/cli/commands/init/project_structure_step.py +0 -29
  13. solace_agent_mesh/cli/commands/plugin/add.py +3 -1
  14. solace_agent_mesh/cli/commands/plugin/build.py +11 -2
  15. solace_agent_mesh/cli/commands/plugin/plugin.py +20 -5
  16. solace_agent_mesh/cli/commands/plugin/remove.py +3 -1
  17. solace_agent_mesh/cli/config.py +4 -0
  18. solace_agent_mesh/cli/utils.py +7 -2
  19. solace_agent_mesh/common/action_response.py +13 -0
  20. solace_agent_mesh/common/constants.py +12 -0
  21. solace_agent_mesh/common/postgres_database.py +11 -5
  22. solace_agent_mesh/common/utils.py +16 -11
  23. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +3 -0
  24. solace_agent_mesh/configs/service_embedding.yaml +1 -1
  25. solace_agent_mesh/configs/service_llm.yaml +1 -1
  26. solace_agent_mesh/gateway/components/gateway_base.py +7 -1
  27. solace_agent_mesh/gateway/components/gateway_input.py +8 -5
  28. solace_agent_mesh/gateway/components/gateway_output.py +12 -3
  29. solace_agent_mesh/orchestrator/action_manager.py +13 -1
  30. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +25 -5
  31. solace_agent_mesh/orchestrator/orchestrator_prompt.py +155 -35
  32. solace_agent_mesh/services/file_service/file_service.py +5 -0
  33. solace_agent_mesh/services/file_service/file_service_constants.py +1 -1
  34. solace_agent_mesh/services/file_service/file_transformations.py +11 -1
  35. solace_agent_mesh/services/file_service/file_utils.py +2 -0
  36. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +21 -45
  37. solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +74 -0
  38. solace_agent_mesh/services/history_service/history_providers/index.py +40 -0
  39. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +19 -153
  40. solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +66 -0
  41. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +40 -137
  42. solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +93 -0
  43. solace_agent_mesh/services/history_service/history_service.py +315 -41
  44. solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
  45. solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +399 -0
  46. solace_agent_mesh/services/llm_service/components/llm_request_component.py +24 -0
  47. solace_agent_mesh/templates/gateway-config-template.yaml +2 -1
  48. solace_agent_mesh/templates/gateway-default-config.yaml +3 -3
  49. solace_agent_mesh/templates/plugin-gateway-default-config.yaml +29 -0
  50. solace_agent_mesh/templates/rest-api-default-config.yaml +2 -1
  51. solace_agent_mesh/templates/slack-default-config.yaml +1 -1
  52. solace_agent_mesh/templates/web-default-config.yaml +2 -1
  53. {solace_agent_mesh-0.1.2.dist-info → solace_agent_mesh-0.2.0.dist-info}/METADATA +38 -8
  54. {solace_agent_mesh-0.1.2.dist-info → solace_agent_mesh-0.2.0.dist-info}/RECORD +57 -52
  55. solace_agent_mesh/cli/commands/init/rest_api_step.py +0 -50
  56. solace_agent_mesh/cli/commands/init/web_ui_step.py +0 -40
  57. {solace_agent_mesh-0.1.2.dist-info → solace_agent_mesh-0.2.0.dist-info}/WHEEL +0 -0
  58. {solace_agent_mesh-0.1.2.dist-info → solace_agent_mesh-0.2.0.dist-info}/entry_points.txt +0 -0
  59. {solace_agent_mesh-0.1.2.dist-info → solace_agent_mesh-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -37,7 +37,9 @@ def add_command(name: str, installer: str = None, from_url=None, add_all=False):
37
37
  f"Module '{name}' not found. Attempting to install '{install_name}' using {installer}..."
38
38
  )
39
39
  if installer == "pip":
40
- subprocess.check_call(["pip", "install", install_name])
40
+ subprocess.check_call(["pip3", "install", install_name])
41
+ elif installer == "uv":
42
+ subprocess.check_call(["uv", "pip", "install", install_name])
41
43
  elif installer == "poetry":
42
44
  subprocess.check_call(["poetry", "add", install_name])
43
45
  elif installer == "conda":
@@ -11,7 +11,7 @@ def build_command():
11
11
  subprocess.check_call(["python", "-m", "build"])
12
12
 
13
13
 
14
- def get_all_plugin_gateway_interfaces(config, abort):
14
+ def get_all_plugin_gateway_interfaces(config, abort, return_plugin_config=False):
15
15
  plugins = config.get("plugins", [])
16
16
  gateway_interfaces = {}
17
17
 
@@ -27,6 +27,8 @@ def get_all_plugin_gateway_interfaces(config, abort):
27
27
  if not os.path.exists(interface_path):
28
28
  continue
29
29
 
30
+ interface_gateway_configs = (Config.load_config(os.path.join(plugin_path, Config.user_plugin_config_file)) or {}).get("solace_agent_mesh_plugin", {}).get("interface_gateway_configs", {})
31
+ interface_gateway_config = None
30
32
  # Ensuring flow and default pair exist
31
33
  interface_pairs = {}
32
34
  for file in os.listdir(interface_path):
@@ -44,7 +46,14 @@ def get_all_plugin_gateway_interfaces(config, abort):
44
46
 
45
47
  for name, files in interface_pairs.items():
46
48
  if len(files) == 2:
47
- gateway_interfaces[name] = interface_path
49
+ if return_plugin_config:
50
+ for interface_name, interface_config in interface_gateway_configs.items():
51
+ if interface_name == name.replace("-", "_"):
52
+ interface_gateway_config = interface_config
53
+ break
54
+ gateway_interfaces[name] = (interface_path, interface_gateway_config)
55
+ else:
56
+ gateway_interfaces[name] = interface_path
48
57
 
49
58
  return gateway_interfaces
50
59
 
@@ -44,6 +44,7 @@ def plugin_command(plugin):
44
44
  @click.argument("name")
45
45
  @click.option("--add-all", is_flag=True, help="Added the plugin with default of loading all exported files from the plugin")
46
46
  @click.option("--pip", is_flag=True, help="Install with pip.")
47
+ @click.option("--uv", is_flag=True, help="Install with uv pip.")
47
48
  @click.option("--poetry", is_flag=True, help="Install with poetry.")
48
49
  @click.option("--conda", is_flag=True, help="Install with conda.")
49
50
  @click.option(
@@ -51,7 +52,7 @@ def plugin_command(plugin):
51
52
  "--from-url",
52
53
  help="Install the plugin from a the given URL instead of the given name. (URL can be a file path or a git URL)",
53
54
  )
54
- def add(name, add_all, pip, poetry, conda, from_url):
55
+ def add(name, add_all, uv, pip, poetry, conda, from_url):
55
56
  """
56
57
  Add a new plugin to solace-agent-mesh config yaml.
57
58
  Optional install the module if not found.
@@ -59,10 +60,16 @@ def plugin_command(plugin):
59
60
  Only one installation method can be selected at a time.
60
61
  """
61
62
  # Only one option can be true at a time
62
- if sum([pip, poetry, conda]) > 1:
63
+ if sum([uv, pip, poetry, conda]) > 1:
63
64
  log_error("Only one installation method can be selected.")
64
65
  return 1
65
- installer = "pip" if pip else "poetry" if poetry else "conda" if conda else None
66
+ installer = (
67
+ "uv" if uv
68
+ else "pip" if pip
69
+ else "poetry" if poetry
70
+ else "conda" if conda
71
+ else None
72
+ )
66
73
  return add_command(name, installer, from_url, add_all)
67
74
 
68
75
  @plugin.command()
@@ -73,6 +80,12 @@ def plugin_command(plugin):
73
80
  is_flag=True,
74
81
  help="Removes the plugin module using pip",
75
82
  )
83
+ @click.option(
84
+ "--uv-uninstall",
85
+ default=False,
86
+ is_flag=True,
87
+ help="Removes the plugin module using uv.",
88
+ )
76
89
  @click.option(
77
90
  "--poetry-uninstall",
78
91
  default=False,
@@ -85,7 +98,7 @@ def plugin_command(plugin):
85
98
  is_flag=True,
86
99
  help="Removes the plugin module using conda",
87
100
  )
88
- def remove(name, pip_uninstall, poetry_uninstall, conda_uninstall):
101
+ def remove(name, pip_uninstall, uv_uninstall, poetry_uninstall, conda_uninstall):
89
102
  """
90
103
  Remove a plugin by removing it from solace-agent-mesh config yaml
91
104
  Optionally uninstall the module.
@@ -93,13 +106,15 @@ def plugin_command(plugin):
93
106
  Only one uninstallation method can be selected at a time.
94
107
  """
95
108
  # Only one option can be true at a time
96
- if sum([pip_uninstall, poetry_uninstall, conda_uninstall]) > 1:
109
+ if sum([pip_uninstall, uv_uninstall, poetry_uninstall, conda_uninstall]) > 1:
97
110
  log_error("Only one uninstallation method can be selected.")
98
111
  return 1
99
112
 
100
113
  installer = (
101
114
  "pip"
102
115
  if pip_uninstall
116
+ else "uv"
117
+ if uv_uninstall
103
118
  else "poetry"
104
119
  if poetry_uninstall
105
120
  else "conda"
@@ -48,7 +48,9 @@ def remove_command(name: str, installer: str = None):
48
48
  click.echo(f"Attempting to uninstall module '{name}' using {installer}...")
49
49
  try:
50
50
  if installer == "pip":
51
- subprocess.check_call(["pip", "uninstall", "-y", name])
51
+ subprocess.check_call(["pip3", "uninstall", "-y", name])
52
+ elif installer == "uv":
53
+ subprocess.check_call(["uv", "pip", "uninstall", "-y", name])
52
54
  elif installer == "poetry":
53
55
  subprocess.check_call(["poetry", "remove", name])
54
56
  elif installer == "conda":
@@ -79,3 +79,7 @@ class Config:
79
79
  if os.path.exists(path):
80
80
  with open(path, "r", encoding="utf-8") as f:
81
81
  return yaml.load(f)
82
+
83
+ @staticmethod
84
+ def get_yaml_parser():
85
+ return yaml.load
@@ -42,7 +42,7 @@ def literal_format_template(template, literals):
42
42
  return template
43
43
 
44
44
 
45
- def load_template(name, format_pair={}):
45
+ def load_template(name, format_pair={}, parser=None):
46
46
  """Load a template file and format it with the given key-value pairs."""
47
47
  # Construct the path to the template file using a relative path
48
48
  template_file = os.path.normpath(
@@ -53,7 +53,10 @@ def load_template(name, format_pair={}):
53
53
  return None
54
54
 
55
55
  with open(template_file, "r", encoding="utf-8") as f:
56
- file = f.read()
56
+ if parser:
57
+ file = parser(f)
58
+ else:
59
+ file = f.read()
57
60
 
58
61
  file = literal_format_template(file, format_pair)
59
62
 
@@ -75,6 +78,8 @@ def get_display_path(path):
75
78
  def log_error(message):
76
79
  click.echo(click.style(message, fg="red"), err=True)
77
80
 
81
+ def log_warning(message):
82
+ click.echo(click.style(message, fg="yellow"), err=False)
78
83
 
79
84
  def log_link(message):
80
85
  click.echo(click.style(message, fg="blue"), err=False)
@@ -1,5 +1,7 @@
1
1
  """This is the definition of responses for the actions of the system."""
2
2
 
3
+ from typing import Optional
4
+
3
5
 
4
6
  class RagMatch:
5
7
 
@@ -212,6 +214,8 @@ class ActionResponse:
212
214
  self._is_async: bool = is_async
213
215
  # async_response_id - unique identifier for correlating async responses
214
216
  self._async_response_id: str = async_response_id
217
+ # originator - the component that originated the action request
218
+ self._originator: Optional[str] = None
215
219
 
216
220
  @property
217
221
  def message(self) -> any:
@@ -269,6 +273,10 @@ class ActionResponse:
269
273
  def action_params(self) -> dict:
270
274
  return self._action_params
271
275
 
276
+ @property
277
+ def originator(self) -> dict:
278
+ return self._originator
279
+
272
280
  @action_list_id.setter
273
281
  def action_list_id(self, action_list_id: str):
274
282
  self._action_list_id = action_list_id
@@ -285,6 +293,10 @@ class ActionResponse:
285
293
  def action_params(self, action_params: dict):
286
294
  self._action_params = action_params
287
295
 
296
+ @originator.setter
297
+ def originator(self, originator: str):
298
+ self._originator = originator
299
+
288
300
  @property
289
301
  def is_async(self) -> bool:
290
302
  return self._is_async
@@ -324,4 +336,5 @@ class ActionResponse:
324
336
  response["action_idx"] = self._action_idx
325
337
  response["action_name"] = self._action_name
326
338
  response["action_params"] = self._action_params
339
+ response["originator"] = self._originator
327
340
  return response
@@ -1,3 +1,15 @@
1
1
  SOLACE_AGENT_MESH_SYSTEM_SESSION_ID = "solace_agent_mesh_system_session_id"
2
2
 
3
3
  DEFAULT_IDENTITY_KEY_FIELD = "identity"
4
+
5
+ ORCHESTRATOR_COMPONENT_NAME = "orchestrator"
6
+
7
+ HISTORY_MEMORY_ROLE = "history"
8
+
9
+ HISTORY_ACTION_ROLE = "tool_call"
10
+
11
+ HISTORY_USER_ROLE = "user"
12
+
13
+ HISTORY_SYSTEM_ROLE = "system"
14
+
15
+ HISTORY_ASSISTANT_ROLE = "assistant"
@@ -56,11 +56,17 @@ class PostgreSQLDatabase:
56
56
 
57
57
  return cursor
58
58
 
59
- def get_db_for_action(action_obj):
60
- sql_host = action_obj.get_config("sql_host")
61
- sql_user = action_obj.get_config("sql_user")
62
- sql_password = action_obj.get_config("sql_password")
63
- sql_database = action_obj.get_config("sql_database")
59
+ def get_db_for_action(action_obj, sql_params=None):
60
+ if sql_params:
61
+ sql_host = sql_params.get("sql_host")
62
+ sql_user = sql_params.get("sql_user")
63
+ sql_password = sql_params.get("sql_password")
64
+ sql_database = sql_params.get("sql_database")
65
+ else:
66
+ sql_host = action_obj.get_config("sql_host")
67
+ sql_user = action_obj.get_config("sql_user")
68
+ sql_password = action_obj.get_config("sql_password")
69
+ sql_database = action_obj.get_config("sql_database")
64
70
  sql_db = None
65
71
 
66
72
  if sql_host and sql_user and sql_password and sql_database:
@@ -139,17 +139,22 @@ def parse_file_content(file_xml: str) -> dict:
139
139
  """
140
140
  Parse the xml tags in the content and return a dictionary of the content.
141
141
  """
142
- ignore_content_tags = ["data"]
143
- file_dict = xml_to_dict(file_xml, ignore_content_tags)
144
- dict_keys = list(file_dict.keys())
145
- top_key = [key for key in dict_keys if key not in ignore_content_tags][0]
146
-
147
- return {
148
- "data": file_dict.get("data", {}).get("data", ""),
149
- "url": file_dict.get(top_key, {}).get("url", {}).get("url", ""),
150
- "mime_type": file_dict.get(top_key, {}).get("mime_type", ""),
151
- "name": file_dict.get(top_key, {}).get("name", ""),
152
- }
142
+ try:
143
+ ignore_content_tags = ["data"]
144
+ file_dict = xml_to_dict(file_xml, ignore_content_tags)
145
+ dict_keys = list(file_dict.keys())
146
+ top_key = [key for key in dict_keys if key not in ignore_content_tags][0]
147
+
148
+ return {
149
+ "data": file_dict.get("data", {}).get("data", ""),
150
+ "url": file_dict.get(top_key, {}).get("url", {}).get("url", ""),
151
+ "mime_type": file_dict.get(top_key, {}).get("mime_type", ""),
152
+ "name": file_dict.get(top_key, {}).get("name", ""),
153
+ }
154
+ except Exception as e:
155
+ result = {"data": "", "url": "", "mime_type": "", "name": ""}
156
+ log.error("Error parsing file content: %s", e)
157
+ return result
153
158
 
154
159
 
155
160
  def parse_llm_output(llm_output: str) -> dict:
@@ -99,6 +99,9 @@ flows:
99
99
  - type: copy
100
100
  source_value: '0'
101
101
  dest_expression: user_data.output:payload.action_idx
102
+ - type: copy
103
+ source_value: 'stim_and_error_monitor'
104
+ dest_expression: user_data.output:payload.originator
102
105
  - type: copy
103
106
  source_expression: template:${SOLACE_AGENT_MESH_NAMESPACE}solace-agent-mesh/v1/actionRequest/monitor/x/slack/post_message/{{text://input.payload:correlation_id}}
104
107
  dest_expression: user_data.output:topic
@@ -53,7 +53,7 @@ flows:
53
53
  source_expression: input.payload
54
54
 
55
55
  - component_name: embedding_service_model
56
- num_instances: 1
56
+ num_instances: ${EMBEDDING_SERVICE_MODEL_NUM_INSTANCES, 1}
57
57
  component_module: litellm_embeddings
58
58
  component_config:
59
59
  load_balancer:
@@ -81,7 +81,7 @@ flows:
81
81
  source_expression: input.payload
82
82
 
83
83
  - component_name: llm_service_planning
84
- num_instances: 1
84
+ num_instances: ${LLM_SERVICE_PLANNING_MODEL_NUM_INSTANCES, 1}
85
85
  component_module: litellm_chat_model
86
86
  component_config:
87
87
  <<: *llm_config
@@ -3,15 +3,17 @@ from solace_ai_connector.common.log import log
3
3
  from ...services.history_service import HistoryService
4
4
  from ..identity.identity_provider import IdentityProvider
5
5
  from ...common.constants import DEFAULT_IDENTITY_KEY_FIELD
6
+ from ...orchestrator.orchestrator_prompt import LONG_TERM_MEMORY_PROMPT
6
7
 
7
8
 
8
9
  class GatewayBase(ComponentBase):
9
10
  def __init__(self, info, **kwargs):
10
11
  super().__init__(info, **kwargs)
11
12
  self.gateway_id = self.get_config("gateway_id", "default-change-me")
13
+ self.system_purpose_prompt_suffix = ""
12
14
  self.history_instance = self._initialize_history()
13
15
 
14
- def _initialize_history(self):
16
+ def _initialize_history(self) -> HistoryService:
15
17
  self.use_history = self.get_config("retain_history", True)
16
18
 
17
19
  if not self.use_history:
@@ -19,6 +21,9 @@ class GatewayBase(ComponentBase):
19
21
 
20
22
  history_config = self.get_config("history_config", {})
21
23
 
24
+ if history_config.get("enable_long_term_memory", False):
25
+ self.system_purpose_prompt_suffix = LONG_TERM_MEMORY_PROMPT
26
+
22
27
  try:
23
28
  return HistoryService(
24
29
  history_config, identifier=self.gateway_id + "_history"
@@ -27,6 +32,7 @@ class GatewayBase(ComponentBase):
27
32
  log.error("Failed to load history class: %s", e)
28
33
  raise
29
34
 
35
+
30
36
  def _initialize_identity_component(self):
31
37
  identity_config = self.get_config("identity", {})
32
38
  identity_key_field = self.get_config("identity_key_field", DEFAULT_IDENTITY_KEY_FIELD)
@@ -6,7 +6,7 @@ from uuid import uuid4
6
6
  from solace_ai_connector.common.message import Message
7
7
  from solace_ai_connector.common.log import log
8
8
  from ...services.file_service import FileService
9
- from ...common.constants import DEFAULT_IDENTITY_KEY_FIELD
9
+ from ...common.constants import DEFAULT_IDENTITY_KEY_FIELD, HISTORY_USER_ROLE
10
10
  from .gateway_base import GatewayBase
11
11
 
12
12
  info = {
@@ -130,7 +130,7 @@ class GatewayInput(GatewayBase):
130
130
 
131
131
  def __init__(self, **kwargs):
132
132
  super().__init__(info, **kwargs)
133
- self.system_purpose = self.get_config("system_purpose", DEFAULT_SYSTEM_PURPOSE)
133
+ self.system_purpose = self.get_config("system_purpose", DEFAULT_SYSTEM_PURPOSE) + self.system_purpose_prompt_suffix
134
134
  self.interaction_type = self.get_config(
135
135
  "interaction_type", DEFAULT_INTERACTION_TYPE
136
136
  )
@@ -209,17 +209,20 @@ class GatewayInput(GatewayBase):
209
209
 
210
210
  copied_data["history"] = []
211
211
  if self.use_history:
212
+ other_history_props = {
213
+ "identity": identity_value,
214
+ }
212
215
  prompt = data.get("text", "")
213
- self.history_instance.store_history(session_id, "user", prompt)
216
+ self.history_instance.store_history(session_id, HISTORY_USER_ROLE, prompt, other_history_props)
214
217
 
215
218
  for file in attached_files:
216
- self.history_instance.store_file(session_id, file)
219
+ self.history_instance.store_file(session_id, file )
217
220
 
218
221
  # retrieve all files for the session
219
222
  available_files = self.history_instance.get_files(session_id)
220
223
 
221
224
  # Add history to the data
222
- copied_data["history"] = self.history_instance.get_history(session_id)
225
+ copied_data["history"] = self.history_instance.get_history(session_id, other_history_props)
223
226
 
224
227
  available_files = json.dumps(available_files)
225
228
  except Exception as e:
@@ -5,7 +5,7 @@ from solace_ai_connector.common.log import log
5
5
  from .gateway_base import GatewayBase
6
6
  from ...services.file_service import FileService
7
7
  from ...common.utils import files_to_block_text
8
-
8
+ from ...common.constants import HISTORY_ASSISTANT_ROLE
9
9
 
10
10
  info = {
11
11
  "class_name": "GatewayOutput",
@@ -170,6 +170,7 @@ class GatewayOutput(GatewayBase):
170
170
  file_service = FileService()
171
171
  user_properties = message.get_user_properties()
172
172
  session_id = user_properties.get("session_id")
173
+ identity_value = user_properties.get("identity")
173
174
  files = data.get("files", [])
174
175
 
175
176
  # Extract the interface queue ID
@@ -181,6 +182,10 @@ class GatewayOutput(GatewayBase):
181
182
  )
182
183
 
183
184
  if self.use_history and session_id:
185
+ other_history_props = {
186
+ "identity": identity_value,
187
+ }
188
+
184
189
  topic = message.get_topic()
185
190
  content = data.get("text") or ""
186
191
 
@@ -193,14 +198,18 @@ class GatewayOutput(GatewayBase):
193
198
  and data.get("last_chunk")
194
199
  and "text" in data
195
200
  ):
201
+ actions_called = user_properties.get("actions_called", [])
202
+ if actions_called:
203
+ self.history_instance.store_actions(session_id, actions_called)
204
+
196
205
  if content:
197
206
  self.history_instance.store_history(
198
- session_id, "assistant", content
207
+ session_id, HISTORY_ASSISTANT_ROLE, content, other_history_props
199
208
  )
200
209
 
201
210
  for file in files:
202
211
  self.history_instance.store_history(
203
- session_id, "assistant", f'\n[Returned file: {{name: {file.get("name")}, url: {file.get("url")}}}]\n'
212
+ session_id, HISTORY_ASSISTANT_ROLE, f'\n[Returned file: {{name: {file.get("name")}, url: {file.get("url")}}}]\n', other_history_props
204
213
  )
205
214
  self.history_instance.store_file(session_id, file)
206
215
 
@@ -15,6 +15,7 @@ from datetime import datetime
15
15
 
16
16
  from solace_ai_connector.common.log import log
17
17
  from ..common.utils import format_agent_response
18
+ from ..common.constants import ORCHESTRATOR_COMPONENT_NAME
18
19
 
19
20
  ACTION_REQUEST_TIMEOUT = 180
20
21
 
@@ -78,6 +79,17 @@ class ActionManager:
78
79
  def add_action_response(self, action_response_obj, response_text_and_files):
79
80
  """Add an action response to the list"""
80
81
  action_list_id = action_response_obj.get("action_list_id")
82
+
83
+ originator = action_response_obj.get("originator", "unknown")
84
+ # Ignore responses for actions that are not originated by the orchestrator
85
+ if originator != ORCHESTRATOR_COMPONENT_NAME:
86
+ log.debug(
87
+ "Ignoring response for action not originated by the orchestrator. "
88
+ "originator: %s action_list_id: %s",
89
+ originator, action_list_id
90
+ )
91
+ return None
92
+
81
93
  with self.lock:
82
94
  action_list = self.action_requests.get(action_list_id)
83
95
  if action_list is None:
@@ -222,4 +234,4 @@ class ActionRequestList:
222
234
 
223
235
  def format_ai_response(self):
224
236
  """Format the action response for the AI"""
225
- return format_agent_response(self.actions)
237
+ return format_agent_response(self.actions)
@@ -14,6 +14,7 @@ import yaml
14
14
  from solace_ai_connector.common.log import log
15
15
  from solace_ai_connector.common.message import Message
16
16
 
17
+ from ...common.constants import ORCHESTRATOR_COMPONENT_NAME, HISTORY_MEMORY_ROLE
17
18
  from ...services.llm_service.components.llm_request_component import LLMRequestComponent, info as base_info
18
19
  from ...services.middleware_service.middleware_service import MiddlewareService
19
20
  from ...services.file_service import FileService
@@ -127,6 +128,18 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
127
128
 
128
129
  user_properties = message.get_user_properties()
129
130
  user_properties['timestamp_end'] = time()
131
+
132
+ actions_called = []
133
+ if results:
134
+ for result in results:
135
+ if result.get("payload", {}).get("action_name"):
136
+ actions_called.append({
137
+ "agent_name": result.get("payload", {}).get("agent_name"),
138
+ "action_name": result.get("payload", {}).get("action_name"),
139
+ "action_params": result.get("payload", {}).get("action_params"),
140
+ })
141
+ user_properties['actions_called'] = actions_called
142
+
130
143
  message.set_user_properties(user_properties)
131
144
 
132
145
  return results
@@ -184,7 +197,7 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
184
197
  message.set_user_properties(user_properties)
185
198
 
186
199
  input_data = self.get_user_input(chat_text)
187
- user_info = payload.get("user_info", {"email": "unknown"})
200
+ user_info = user_properties.get("user_info", {"email": "unknown"})
188
201
 
189
202
  agent_state_yaml, examples = self.get_agents_yaml(user_properties)
190
203
  full_input = {
@@ -205,7 +218,7 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
205
218
  }
206
219
 
207
220
  # Get the prompts
208
- gateway_history = self.get_gateway_history(data)
221
+ gateway_history, memory_history = self.get_gateway_history(data)
209
222
  system_prompt = SystemPrompt(full_input, examples)
210
223
  if action_response_reinvoke:
211
224
  user_prompt = ActionResponsePrompt(
@@ -216,6 +229,9 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
216
229
  user_prompt = UserStimulusPrompt(
217
230
  full_input, gateway_history, errors, has_files
218
231
  )
232
+ if memory_history:
233
+ self.history.store_history(stimulus_uuid, "system", memory_history)
234
+
219
235
 
220
236
  # Store the user prompt in the history
221
237
  self.history.store_history(stimulus_uuid, "user", user_prompt)
@@ -374,6 +390,9 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
374
390
 
375
391
  def get_gateway_history(self, data):
376
392
  gateway_history = data.get("history", [])
393
+ memory_history = None
394
+ if gateway_history and gateway_history[0].get("role") == HISTORY_MEMORY_ROLE:
395
+ memory_history =gateway_history[0].get("content")
377
396
  # Returning the history from the last user message
378
397
  first_user_idx = None
379
398
  last_user_idx = None
@@ -384,13 +403,13 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
384
403
  last_user_idx = idx
385
404
 
386
405
  if first_user_idx is None:
387
- return [] # No user query found
406
+ return [], memory_history # No user query found
388
407
 
389
408
  if not last_user_idx > first_user_idx:
390
409
  # Latest user message is already handled by orchestator history
391
- return []
410
+ return [], memory_history
392
411
 
393
- return gateway_history[first_user_idx:last_user_idx]
412
+ return gateway_history[first_user_idx:last_user_idx], memory_history
394
413
 
395
414
  def get_user_input(self, chat_text):
396
415
 
@@ -458,6 +477,7 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
458
477
  "action_name": action_name,
459
478
  "action_params": action_params,
460
479
  "action_idx": action_idx,
480
+ "originator": ORCHESTRATOR_COMPONENT_NAME,
461
481
  },
462
482
  "topic": f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/actionRequest/orchestrator/agent/{agent_name}/{action_name}",
463
483
  }