solace-agent-mesh 0.1.3__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 (45) hide show
  1. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +9 -2
  2. solace_agent_mesh/agents/global/actions/plotly_graph.py +38 -40
  3. solace_agent_mesh/agents/web_request/actions/do_web_request.py +34 -33
  4. solace_agent_mesh/cli/__init__.py +1 -1
  5. solace_agent_mesh/cli/commands/add/gateway.py +162 -9
  6. solace_agent_mesh/cli/commands/build.py +0 -1
  7. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +52 -1
  8. solace_agent_mesh/cli/commands/plugin/build.py +11 -2
  9. solace_agent_mesh/cli/config.py +4 -0
  10. solace_agent_mesh/cli/utils.py +7 -2
  11. solace_agent_mesh/common/constants.py +10 -0
  12. solace_agent_mesh/common/utils.py +16 -11
  13. solace_agent_mesh/configs/service_embedding.yaml +1 -1
  14. solace_agent_mesh/configs/service_llm.yaml +1 -1
  15. solace_agent_mesh/gateway/components/gateway_base.py +7 -1
  16. solace_agent_mesh/gateway/components/gateway_input.py +8 -5
  17. solace_agent_mesh/gateway/components/gateway_output.py +12 -3
  18. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +23 -5
  19. solace_agent_mesh/orchestrator/orchestrator_prompt.py +155 -35
  20. solace_agent_mesh/services/file_service/file_service.py +5 -0
  21. solace_agent_mesh/services/file_service/file_service_constants.py +1 -1
  22. solace_agent_mesh/services/file_service/file_transformations.py +11 -1
  23. solace_agent_mesh/services/file_service/file_utils.py +2 -0
  24. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +21 -46
  25. solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +74 -0
  26. solace_agent_mesh/services/history_service/history_providers/index.py +40 -0
  27. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +19 -156
  28. solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +66 -0
  29. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +40 -140
  30. solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +93 -0
  31. solace_agent_mesh/services/history_service/history_service.py +315 -41
  32. solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
  33. solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +399 -0
  34. solace_agent_mesh/services/llm_service/components/llm_request_component.py +19 -0
  35. solace_agent_mesh/templates/gateway-config-template.yaml +2 -1
  36. solace_agent_mesh/templates/gateway-default-config.yaml +3 -3
  37. solace_agent_mesh/templates/plugin-gateway-default-config.yaml +29 -0
  38. solace_agent_mesh/templates/rest-api-default-config.yaml +2 -1
  39. solace_agent_mesh/templates/slack-default-config.yaml +1 -1
  40. solace_agent_mesh/templates/web-default-config.yaml +2 -1
  41. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.0.dist-info}/METADATA +4 -3
  42. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.0.dist-info}/RECORD +45 -38
  43. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.0.dist-info}/WHEEL +0 -0
  44. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.0.dist-info}/entry_points.txt +0 -0
  45. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
 
@@ -14,7 +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
17
+ from ...common.constants import ORCHESTRATOR_COMPONENT_NAME, HISTORY_MEMORY_ROLE
18
18
  from ...services.llm_service.components.llm_request_component import LLMRequestComponent, info as base_info
19
19
  from ...services.middleware_service.middleware_service import MiddlewareService
20
20
  from ...services.file_service import FileService
@@ -128,6 +128,18 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
128
128
 
129
129
  user_properties = message.get_user_properties()
130
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
+
131
143
  message.set_user_properties(user_properties)
132
144
 
133
145
  return results
@@ -206,7 +218,7 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
206
218
  }
207
219
 
208
220
  # Get the prompts
209
- gateway_history = self.get_gateway_history(data)
221
+ gateway_history, memory_history = self.get_gateway_history(data)
210
222
  system_prompt = SystemPrompt(full_input, examples)
211
223
  if action_response_reinvoke:
212
224
  user_prompt = ActionResponsePrompt(
@@ -217,6 +229,9 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
217
229
  user_prompt = UserStimulusPrompt(
218
230
  full_input, gateway_history, errors, has_files
219
231
  )
232
+ if memory_history:
233
+ self.history.store_history(stimulus_uuid, "system", memory_history)
234
+
220
235
 
221
236
  # Store the user prompt in the history
222
237
  self.history.store_history(stimulus_uuid, "user", user_prompt)
@@ -375,6 +390,9 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
375
390
 
376
391
  def get_gateway_history(self, data):
377
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")
378
396
  # Returning the history from the last user message
379
397
  first_user_idx = None
380
398
  last_user_idx = None
@@ -385,13 +403,13 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
385
403
  last_user_idx = idx
386
404
 
387
405
  if first_user_idx is None:
388
- return [] # No user query found
406
+ return [], memory_history # No user query found
389
407
 
390
408
  if not last_user_idx > first_user_idx:
391
409
  # Latest user message is already handled by orchestator history
392
- return []
410
+ return [], memory_history
393
411
 
394
- return gateway_history[first_user_idx:last_user_idx]
412
+ return gateway_history[first_user_idx:last_user_idx], memory_history
395
413
 
396
414
  def get_user_input(self, chat_text):
397
415
 
@@ -2,42 +2,39 @@ from typing import Dict, Any, List
2
2
  import yaml
3
3
  from langchain_core.messages import HumanMessage
4
4
  from ..services.file_service import FS_PROTOCOL, Types, LLM_QUERY_OPTIONS, TRANSFORMERS
5
+ from solace_ai_connector.common.log import log
5
6
 
6
7
  # Cap the number of examples so we don't overwhelm the LLM
7
8
  MAX_SYSTEM_PROMPT_EXAMPLES = 6
8
9
 
9
10
  # Examples that should always be included in the prompt
10
11
  fixed_examples = [
11
- """ <example>
12
- <example_docstring>
13
- This example shows a stimulus from a chatbot gateway in which a user is asking about the top stories on the website hacker news. The web_request is not yet open, so the change_agent_status action is invoked to open the web_request agent.
14
- </example_docstring>
15
- <example_stimulus>
16
- <{tp}stimulus starting_id="1"/>
17
- What is the top story on hacker news?
18
- </{tp}stimulus>
19
- <{tp}stimulus_metadata>
20
- local_time: 2024-11-06 15:58:04 EST-0500 (Wednesday)
21
- </{tp}stimulus_metadata>
22
- </example_stimulus>
23
- <example_response>
24
- <{tp}reasoning>
25
- - User is asking for the top story on Hacker News
26
- - We need to use the web_request agent to fetch the latest information
27
- - The web_request agent is currently closed, so we need to open it first
28
- - After opening the agent, we\'ll need to make a web request to Hacker News
29
- </{tp}reasoning>
30
-
31
- <{tp}status_update>To get the latest top story from Hacker News, I\'ll need to access the web. I\'m preparing to do that now.</{tp}status_update>
32
-
33
- <{tp}invoke_action agent="global" action="change_agent_status">
34
- <{tp}parameter name="agent_name">web_request</{tp}parameter>
35
- <{tp}parameter name="new_state">open</{tp}parameter>
36
- </{tp}invoke_action>
37
- </example_response>
38
- </example>
39
- """
40
- ]
12
+ {
13
+ "docstring": "This example shows a stimulus from a chatbot gateway in which a user is asking about the top stories on the website hacker news. The web_request is not yet open, so the change_agent_status action is invoked to open the web_request agent.",
14
+ "tag_prefix_placeholder": "{tp}",
15
+ "starting_id": "1",
16
+ "user_input": "What is the top story on hacker news?",
17
+ "metadata": [
18
+ "local_time: 2024-11-06 15:58:04 EST-0500 (Wednesday)"
19
+ ],
20
+ "reasoning": [
21
+ "- User is asking for the top story on Hacker News",
22
+ "- We need to use the web_request agent to fetch the latest information",
23
+ "- The web_request agent is currently closed, so we need to open it first",
24
+ "- After opening the agent, we'll need to make a web request to Hacker News"
25
+ ],
26
+ "response_text": "",
27
+ "status_update": "To get the latest top story from Hacker News, I'll need to access the web. I'm preparing to do that now.",
28
+ "action": {
29
+ "agent": "global",
30
+ "name": "change_agent_status",
31
+ "parameters": {
32
+ "agent_name": "web_request",
33
+ "new_state": "open"
34
+ }
35
+ }
36
+ }
37
+ ]
41
38
 
42
39
 
43
40
  def get_file_handling_prompt(tp: str) -> str:
@@ -102,6 +99,7 @@ def get_file_handling_prompt(tp: str) -> str:
102
99
  For all files, there's `encoding` parameter, this is used to encode the file format. The supported values are `datauri`, `base64`, `zip`, and `gzip`. Use this to convert a file to ZIP or datauri for HTML encoding.
103
100
  For example (return a file as zip): `{FS_PROTOCOL}://c27e6908-55d5-4ce0-bc93-a8e28f84be12_annual_report.csv?encoding=zip&resolve=true`
104
101
  Example 2 (HTML tag must be datauri encoded): `<img src="{FS_PROTOCOL}://c9183b0f-fd11-48b4-ad8f-df221bff3da9_generated_image.png?encoding=datauri&resolve=true" alt="image">`
102
+ Example 3 (return a regular image directly): `amfs://a1b2c3d4-5e6f-7g8h-9i0j-1k2l3m4n5o6p_photo.png?resolve=true` - When returning images directly in messaging platforms like Slack, don't use any encoding parameter
105
103
 
106
104
  For all files, there's `resolve=true` parameter, this is used to resolve the url to the actual data before processing.
107
105
  For example: `{FS_PROTOCOL}://577c928c-c126-42d8-8a48-020b93096110_names.csv?resolve=true`
@@ -156,11 +154,15 @@ def create_examples(
156
154
  String containing all examples with replaced placeholders
157
155
  """
158
156
  examples = (fixed_examples + agent_examples)[:MAX_SYSTEM_PROMPT_EXAMPLES]
159
- return "\n".join([example.replace("{tp}", tp) for example in examples])
157
+ formatted_examples = format_examples_by_llm_type(examples)
158
+
159
+ return "\n".join([example.replace("{tp}", tp) for example in formatted_examples])
160
160
 
161
161
 
162
162
  def SystemPrompt(info: Dict[str, Any], action_examples: List[str]) -> str:
163
- response_format_prompt = info.get("response_format_prompt", "")
163
+ tp = info["tag_prefix"]
164
+ response_format_prompt = info.get("response_format_prompt", "") or ""
165
+ response_format_prompt = response_format_prompt.replace("{{tag_prefix}}", tp)
164
166
  response_guidelines_prompt = (
165
167
  f"<response_guidelines>\nConsider the following when generating a response to the originator:\n"
166
168
  f"{response_format_prompt}</response_guidelines>"
@@ -183,7 +185,6 @@ def SystemPrompt(info: Dict[str, Any], action_examples: List[str]) -> str:
183
185
  )
184
186
 
185
187
  # Merged
186
- tp = info["tag_prefix"]
187
188
  examples = create_examples(fixed_examples, action_examples, tp)
188
189
 
189
190
  handling_files = get_file_handling_prompt(tp)
@@ -216,7 +217,21 @@ The assistant's behavior aligns with the system purpose specified below:
216
217
  5. Do not perform any other actions besides opening the required agents in this step.
217
218
  - Report generation:
218
219
  1. If a report is requested and no format is specified, create the report in an HTML file.
219
- 2. Generate each section of the report independently and store it in the file service with create_file action. When finishing the report, combine the sections using amfs urls with the resolve=true query parameter to insert the sections into the main document. When generating HTML, create the header first with all the necessary CSS and JS links so that it is clear what css the rest of the document will use.
220
+ 2. Generate each section of the report independently and store it in the file service with create_file action. When finishing the report, combine the sections using amfs urls with the resolve=true query parameter to insert the sections into the main document. When inserting amfs HTML URLs into the HTML document, place them directly in the document without any surrounding tags or brackets. Here is an example of the body section of an HTML report combining multiple sections:
221
+ <body>
222
+ <!-- Title -->
223
+ <h1>Report Title</h1>
224
+
225
+ <!-- Section 1 -->
226
+ amfs://xxxxxx.html?resolve=true
227
+
228
+ <!-- Section 2 -->
229
+ amfs://yyyyyy.html?resolve=true
230
+
231
+ <!-- Section 3 -->
232
+ amfs://zzzzzz.html?resolve=true
233
+ </body>
234
+ When generating HTML, create the header first with all the necessary CSS and JS links so that it is clear what css the rest of the document will use.
220
235
  3. Images are always very useful in reports, so the assistant will add them when appropriate. If images are embedded in html, they must be resolved and converted to datauri format or they won't render in the final document. This can be done by using the encoding=datauri&resolve=true in the amfs link. For example, <img src="amfs://xxxxxx.png?encoding=datauri&resolve=true". The assistant will take care of the rest. Images can be created in parallel
221
236
  4. During report generation in interactive sessions, the assistant will send lots of status messages to indicate what is happening.
222
237
  - Handling stimuli with open agents:
@@ -330,7 +345,7 @@ def UserStimulusPrompt(
330
345
  f"\tExample of returning a file as zip: <{info['tag_prefix']}file><url>{FS_PROTOCOL}://519321d8-3506-4f8d-9377-e5d6ce74d917_filename.csv?encoding=zip</url></{info['tag_prefix']}file>.\n"
331
346
  f"You can also optionally return a non-persistent temporary file using the format <{info['tag_prefix']}file name=\"filename.csv\" mime_type=\"text/csv\">\n<data> data </data>\n</{info['tag_prefix']}file>.\n"
332
347
  f" can't nest `<{info['tag_prefix']}file>` tags inside the `<data>` tag. If you need to address another file within the data, use the URL with the `resolve=true` query parameter.\n"
333
- "When using a {FS_PROTOCOL} URL outside of a file block, always include the 'resolve=true' query parameter to ensure the URL is resolved to the actual data.\n"
348
+ f"When using a {FS_PROTOCOL} URL outside of a file block, always include the 'resolve=true' query parameter to ensure the URL is resolved to the actual data.\n"
334
349
  f"</{info['tag_prefix']}file_instructions>"
335
350
  )
336
351
 
@@ -413,3 +428,108 @@ you should ask the originator for more information. Include links to the source
413
428
  {context}
414
429
  </context>
415
430
  """
431
+
432
+
433
+ def format_examples_by_llm_type(examples: list, llm_type: str = "anthropic") -> list:
434
+ """
435
+ Render examples based on llm type
436
+
437
+ Args:
438
+ llm_type (str): The type of LLM to render examples for (default: "anthropic")
439
+ examples (list): List of examples in model-agnostic format
440
+
441
+ Returns:
442
+ list: List of examples formatted for the specified LLM
443
+ """
444
+ formatted_examples = []
445
+
446
+ if llm_type == "anthropic":
447
+ for example in examples:
448
+ formatted_example = format_example_for_anthropic(example)
449
+ formatted_examples.append(formatted_example)
450
+ else:
451
+ log.error(f"Unsupported LLM type: {llm_type}")
452
+
453
+ return formatted_examples
454
+
455
+ def format_example_for_anthropic(example: dict) -> str:
456
+ """
457
+ Format an example for the Anthropic's LLMs
458
+ """
459
+
460
+ tag_prefix = example.get("tag_prefix_placeholder", "t123")
461
+ starting_id = example.get("starting_id", "1")
462
+ docstring = example.get("docstring", "")
463
+ user_input = example.get("user_input", "")
464
+ metadata_lines = example.get("metadata", [])
465
+ reasoning_lines = example.get("reasoning", [])
466
+ response_text = example.get("response_text", "")
467
+
468
+ # Start building the XML structure, add the description and user input
469
+ xml_content = f"""<example>
470
+ <example_docstring>
471
+ {docstring}
472
+ </example_docstring>
473
+ <example_stimulus>
474
+ <{tag_prefix}stimulus starting_id="{starting_id}">
475
+ {user_input}
476
+ </{tag_prefix}stimulus>
477
+ <{tag_prefix}stimulus_metadata>
478
+ """
479
+
480
+ # Add metadata lines
481
+ for metadata_line in metadata_lines:
482
+ xml_content += f"{metadata_line}\n"
483
+
484
+ xml_content += f"""</{tag_prefix}stimulus_metadata>
485
+ </example_stimulus>
486
+ <example_response>
487
+ <{tag_prefix}reasoning>
488
+ """
489
+
490
+ # Add reasoning lines
491
+ for reasoning_line in reasoning_lines:
492
+ xml_content += f"{reasoning_line}\n"
493
+
494
+ xml_content += f"""</{tag_prefix}reasoning>
495
+ {response_text}"""
496
+
497
+ # Add action invocation section
498
+ if "action" in example:
499
+ action_data = example.get("action", {})
500
+ status_update = example.get("status_update", "")
501
+ agent_name = action_data.get("agent", "")
502
+ action_name = action_data.get("name", "")
503
+
504
+ xml_content += f"""
505
+ <{tag_prefix}status_update>{status_update}</{tag_prefix}status_update>
506
+ <{tag_prefix}invoke_action agent="{agent_name}" action="{action_name}">"""
507
+
508
+ # Handle parameters as dictionary
509
+ parameter_dict = action_data.get("parameters", {})
510
+ for param_name, param_value in parameter_dict.items():
511
+ xml_content += f"""
512
+ <{tag_prefix}parameter name="{param_name}">"""
513
+
514
+ # Handle parameter names and values (as lists)
515
+ if isinstance(param_value, list):
516
+ for line in param_value:
517
+ xml_content += f"\n{line}"
518
+ xml_content += "\n"
519
+ else:
520
+ # For simple string values
521
+ xml_content += f"{param_value}"
522
+
523
+ xml_content += f"</{tag_prefix}parameter>\n"
524
+
525
+ xml_content += f"</{tag_prefix}invoke_action>"
526
+
527
+ # Close the XML structure
528
+ xml_content += """
529
+ </example_response>
530
+ </example>
531
+ """
532
+
533
+ return xml_content
534
+
535
+ LONG_TERM_MEMORY_PROMPT = " - You are capable of remembering things and have long-term memory, this happens automatically."
@@ -392,6 +392,11 @@ class FileService(AutoExpiry, metaclass=AutoExpirySingletonMeta):
392
392
  url = url[5:-6].strip()
393
393
  if url.endswith('"') or url.endswith("'") or url.endswith(","):
394
394
  url = url[:-1]
395
+ if url.endswith(")"):
396
+ open_parenthesis_count = url.count("(")
397
+ close_parenthesis_count = url.count(")")
398
+ if close_parenthesis_count > open_parenthesis_count:
399
+ url = url[:-1]
395
400
  return url
396
401
 
397
402
  @staticmethod
@@ -40,7 +40,7 @@ BLOCK_TAG_KEYS = [
40
40
  Keys to be treated as tags in the file block, and not file attributes.
41
41
  """
42
42
 
43
- FS_URL_REGEX = r"""<url>\s*({protocol}:\/\/.+?)\s*<\/url>|{protocol}:\/\/[^\s\?]+(\s|$)|{protocol}:\/\/[^\n\?]+\?.*?(?=\n|$)|\"({protocol}:\/\/[^\"]+?)(\"|\n)|'({protocol}:\/\/[^']+?)('|\n)""".format(
43
+ FS_URL_REGEX = r"""<url>\s*({protocol}:\/\/.+?)\s*<\/url>|{protocol}:\/\/[^\s\?]+(\s|$)|{protocol}:\/\/[^\n\?]+\?.*?(?=\n|$|>)|\"({protocol}:\/\/[^\"]+?)(\"|\n)|'({protocol}:\/\/[^']+?)('|\n)""".format(
44
44
  protocol=FS_PROTOCOL
45
45
  )
46
46
  """
@@ -82,6 +82,8 @@ def apply_file_transformations(
82
82
  return file
83
83
  text_mime_type_regex = r"text/.*|.*csv|.*json|.*xml|.*yaml|.*x-yaml|.*txt"
84
84
  mime_type = metadata.get("mime_type", "")
85
+ if mime_type is None:
86
+ mime_type = ""
85
87
  name = metadata.get("name", "unknown")
86
88
  other = {
87
89
  "mime_type": mime_type,
@@ -126,6 +128,14 @@ def apply_file_transformations(
126
128
  return file
127
129
 
128
130
  if not isinstance(data, str):
129
- data = json.dumps(data)
131
+ # Convert bytes to string
132
+ if isinstance(data, bytes):
133
+ try:
134
+ data = data.decode("utf-8")
135
+ except UnicodeDecodeError:
136
+ data = base64.b64encode(data).decode("utf-8")
137
+ data = f"data:{mime_type};base64,{data}"
138
+ else:
139
+ data = json.dumps(data)
130
140
 
131
141
  return data
@@ -34,6 +34,8 @@ def get_str_type(value: str) -> str:
34
34
  Returns:
35
35
  - str: The type of the value.
36
36
  """
37
+ if not value:
38
+ return Types.NULL
37
39
  if value.isdigit():
38
40
  return Types.INT
39
41
  elif value.replace(".", "", 1).isdigit():
@@ -1,79 +1,54 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Union
3
2
 
4
3
  class BaseHistoryProvider(ABC):
5
4
 
6
5
  def __init__(self, config=None):
7
- self.config = config
8
- self.max_turns = self.config.get("max_turns")
9
- self.max_characters = self.config.get("max_characters")
10
- self.enforce_alternate_message_roles = self.config.get("enforce_alternate_message_roles")
11
-
12
- @abstractmethod
13
- def store_history(self, session_id: str, role: str, content: Union[str, dict]):
14
- """
15
- Store a new entry in the history.
16
-
17
- :param session_id: The session identifier.
18
- :param role: The role of the entry to be stored in the history.
19
- :param content: The content of the entry to be stored in the history.
20
- """
21
- raise NotImplementedError("Method not implemented")
22
-
6
+ self.config = config or {}
7
+
23
8
  @abstractmethod
24
- def get_history(self, session_id: str):
9
+ def get_all_sessions(self) -> list[str]:
25
10
  """
26
- Retrieve the entire history.
27
-
28
- :param session_id: The session identifier.
29
- :return: The complete history.
11
+ Retrieve all session identifiers.
30
12
  """
31
13
  raise NotImplementedError("Method not implemented")
32
14
 
33
15
  @abstractmethod
34
- def store_file(self, session_id: str, file: dict):
16
+ def get_session(self, session_id: str)->dict:
35
17
  """
36
- Store a file in the history.
18
+ Retrieve the session .
37
19
 
38
20
  :param session_id: The session identifier.
39
- :param file: The file metadata to be stored in the history.
21
+ :return: The session metadata.
40
22
  """
41
23
  raise NotImplementedError("Method not implemented")
42
-
24
+
43
25
  @abstractmethod
44
- def get_files(self, session_id: str):
26
+ def delete_session(self, session_id: str):
45
27
  """
46
- Retrieve the files for a session.
28
+ Delete the session.
47
29
 
48
30
  :param session_id: The session identifier.
49
- :return: The files for the session.
50
31
  """
51
32
  raise NotImplementedError("Method not implemented")
52
33
 
53
34
  @abstractmethod
54
- def get_session_meta(self, session_id: str):
35
+ def store_session(self, session_id: str, data: dict):
55
36
  """
56
- Retrieve the session metadata.
37
+ Store the session metadata.
57
38
 
58
39
  :param session_id: The session identifier.
59
- :return: The session metadata.
40
+ :param data: The session data to be stored.
60
41
  """
61
42
  raise NotImplementedError("Method not implemented")
43
+
62
44
 
63
- @abstractmethod
64
- def clear_history(self, session_id: str, keep_levels=0, clear_files=True):
65
- """
66
- Clear the history and files, optionally keeping a specified number of recent entries.
67
-
68
- :param session_id: The session identifier.
69
- :param keep_levels: Number of most recent history entries to keep. Default is 0 (clear all).
70
- :param clear_files: Whether to clear associated files. Default is True.
45
+ def update_session(self, session_id: str, data: dict):
71
46
  """
72
- raise NotImplementedError("Method not implemented")
47
+ Update data in the store using the partial data provided.
73
48
 
74
- @abstractmethod
75
- def get_all_sessions(self) -> list[str]:
49
+ :param key: The key to update the data under.
50
+ :param data: The data to update.
76
51
  """
77
- Retrieve all session identifiers.
78
- """
79
- raise NotImplementedError("Method not implemented")
52
+ history = self.get_session(session_id).copy()
53
+ history.update(data)
54
+ self.store_session(session_id, history)