solace-agent-mesh 0.1.3__py3-none-any.whl → 0.2.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 (73) hide show
  1. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +9 -2
  2. solace_agent_mesh/agents/global/actions/plotly_graph.py +70 -46
  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/copy_from_plugin.py +8 -6
  6. solace_agent_mesh/cli/commands/add/gateway.py +162 -9
  7. solace_agent_mesh/cli/commands/build.py +15 -1
  8. solace_agent_mesh/cli/commands/init/ai_provider_step.py +45 -28
  9. solace_agent_mesh/cli/commands/init/broker_step.py +1 -4
  10. solace_agent_mesh/cli/commands/init/create_config_file_step.py +8 -0
  11. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +52 -1
  12. solace_agent_mesh/cli/commands/init/init.py +50 -37
  13. solace_agent_mesh/cli/commands/plugin/build.py +60 -9
  14. solace_agent_mesh/cli/commands/run.py +2 -2
  15. solace_agent_mesh/cli/config.py +4 -0
  16. solace_agent_mesh/cli/main.py +14 -8
  17. solace_agent_mesh/cli/utils.py +7 -2
  18. solace_agent_mesh/common/constants.py +10 -0
  19. solace_agent_mesh/common/prompt_templates.py +1 -3
  20. solace_agent_mesh/common/utils.py +104 -30
  21. solace_agent_mesh/config_portal/__init__.py +0 -0
  22. solace_agent_mesh/config_portal/backend/__init__.py +0 -0
  23. solace_agent_mesh/config_portal/backend/common.py +35 -0
  24. solace_agent_mesh/config_portal/backend/server.py +233 -0
  25. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DRPGOzHj.js +42 -0
  26. solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +191 -0
  27. solace_agent_mesh/config_portal/frontend/static/client/assets/entry.client-DX1misIU.js +19 -0
  28. solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +17 -0
  29. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-8147e469.js +1 -0
  30. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DgMDqKDc.js +10 -0
  31. solace_agent_mesh/config_portal/frontend/static/client/assets/root-hhS5izs8.css +1 -0
  32. solace_agent_mesh/config_portal/frontend/static/client/favicon.ico +0 -0
  33. solace_agent_mesh/config_portal/frontend/static/client/index.html +7 -0
  34. solace_agent_mesh/configs/orchestrator.yaml +1 -1
  35. solace_agent_mesh/configs/service_embedding.yaml +1 -1
  36. solace_agent_mesh/configs/service_llm.yaml +1 -1
  37. solace_agent_mesh/gateway/components/gateway_base.py +7 -1
  38. solace_agent_mesh/gateway/components/gateway_input.py +8 -5
  39. solace_agent_mesh/gateway/components/gateway_output.py +12 -3
  40. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +4 -0
  41. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +43 -12
  42. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +19 -5
  43. solace_agent_mesh/orchestrator/orchestrator_main.py +11 -5
  44. solace_agent_mesh/orchestrator/orchestrator_prompt.py +184 -60
  45. solace_agent_mesh/services/file_service/file_service.py +5 -0
  46. solace_agent_mesh/services/file_service/file_service_constants.py +1 -1
  47. solace_agent_mesh/services/file_service/file_transformations.py +11 -1
  48. solace_agent_mesh/services/file_service/file_utils.py +2 -0
  49. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +21 -46
  50. solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +74 -0
  51. solace_agent_mesh/services/history_service/history_providers/index.py +40 -0
  52. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +19 -156
  53. solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +66 -0
  54. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +40 -140
  55. solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +93 -0
  56. solace_agent_mesh/services/history_service/history_service.py +315 -41
  57. solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
  58. solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +399 -0
  59. solace_agent_mesh/services/llm_service/components/llm_request_component.py +19 -0
  60. solace_agent_mesh/templates/gateway-config-template.yaml +2 -1
  61. solace_agent_mesh/templates/gateway-default-config.yaml +3 -3
  62. solace_agent_mesh/templates/plugin-gateway-default-config.yaml +29 -0
  63. solace_agent_mesh/templates/rest-api-default-config.yaml +2 -1
  64. solace_agent_mesh/templates/slack-default-config.yaml +1 -1
  65. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +9 -0
  66. solace_agent_mesh/templates/web-default-config.yaml +2 -1
  67. solace_agent_mesh-0.2.1.dist-info/METADATA +172 -0
  68. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/RECORD +71 -52
  69. solace_agent_mesh/common/prompt_templates_unused_delete.py +0 -161
  70. solace_agent_mesh-0.1.3.dist-info/METADATA +0 -208
  71. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/WHEEL +0 -0
  72. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/entry_points.txt +0 -0
  73. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,69 +1,60 @@
1
1
  from typing import Dict, Any, List
2
2
  import yaml
3
- from langchain_core.messages import HumanMessage
4
3
  from ..services.file_service import FS_PROTOCOL, Types, LLM_QUERY_OPTIONS, TRANSFORMERS
4
+ from solace_ai_connector.common.log import log
5
5
 
6
6
  # Cap the number of examples so we don't overwhelm the LLM
7
7
  MAX_SYSTEM_PROMPT_EXAMPLES = 6
8
8
 
9
9
  # Examples that should always be included in the prompt
10
10
  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
- """
11
+ {
12
+ "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.",
13
+ "tag_prefix_placeholder": "{tp}",
14
+ "starting_id": "1",
15
+ "user_input": "What is the top story on hacker news?",
16
+ "metadata": ["local_time: 2024-11-06 15:58:04 EST-0500 (Wednesday)"],
17
+ "reasoning": [
18
+ "- User is asking for the top story on Hacker News",
19
+ "- We need to use the web_request agent to fetch the latest information",
20
+ "- The web_request agent is currently closed, so we need to open it first",
21
+ "- After opening the agent, we'll need to make a web request to Hacker News",
22
+ ],
23
+ "response_text": "",
24
+ "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.",
25
+ "action": {
26
+ "agent": "global",
27
+ "name": "change_agent_status",
28
+ "parameters": {"agent_name": "web_request", "new_state": "open"},
29
+ },
30
+ }
40
31
  ]
41
32
 
42
33
 
43
34
  def get_file_handling_prompt(tp: str) -> str:
44
- parameters_desc = ""
45
- parameter_examples = ""
35
+ parameters_desc = ""
36
+ parameter_examples = ""
46
37
 
47
- for transformer in TRANSFORMERS:
48
- if transformer.description:
49
- parameters_desc += "\n" + transformer.description.strip() + "\n"
38
+ for transformer in TRANSFORMERS:
39
+ if transformer.description:
40
+ parameters_desc += "\n" + transformer.description.strip() + "\n"
50
41
 
51
- if transformer.examples:
52
- for example in transformer.examples:
53
- parameter_examples += "\n" + example.strip() + "\n"
42
+ if transformer.examples:
43
+ for example in transformer.examples:
44
+ parameter_examples += "\n" + example.strip() + "\n"
54
45
 
55
- parameters_desc = "\n ".join(parameters_desc.split("\n"))
56
- parameter_examples = "\n ".join(parameter_examples.split("\n"))
46
+ parameters_desc = "\n ".join(parameters_desc.split("\n"))
47
+ parameter_examples = "\n ".join(parameter_examples.split("\n"))
57
48
 
58
- parameters_desc = parameters_desc.replace("{tp}", tp)
59
- parameter_examples = parameter_examples.replace("{tp}", tp)
49
+ parameters_desc = parameters_desc.replace("{tp}", tp)
50
+ parameter_examples = parameter_examples.replace("{tp}", tp)
60
51
 
61
- if parameter_examples:
62
- parameter_examples = f"""
52
+ if parameter_examples:
53
+ parameter_examples = f"""
63
54
  Here are some examples of how to use the query parameters:
64
55
  {parameter_examples}"""
65
56
 
66
- prompt = f"""
57
+ prompt = f"""
67
58
  XML tags are used to represent files. The assistant will use the <{tp}file> tag to represent a file. The file tag has the following format:
68
59
  <{tp}file name="filename" mime_type="mimetype" size="size in bytes">
69
60
  <schema-yaml>...JSON schema, yaml format...</schema-yaml> (optional)
@@ -102,6 +93,7 @@ def get_file_handling_prompt(tp: str) -> str:
102
93
  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
94
  For example (return a file as zip): `{FS_PROTOCOL}://c27e6908-55d5-4ce0-bc93-a8e28f84be12_annual_report.csv?encoding=zip&resolve=true`
104
95
  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">`
96
+ Example 3 (return a regular image directly): `{FS_PROTOCOL}://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
97
 
106
98
  For all files, there's `resolve=true` parameter, this is used to resolve the url to the actual data before processing.
107
99
  For example: `{FS_PROTOCOL}://577c928c-c126-42d8-8a48-020b93096110_names.csv?resolve=true`
@@ -140,7 +132,7 @@ def get_file_handling_prompt(tp: str) -> str:
140
132
 
141
133
  {parameter_examples}
142
134
  """
143
- return prompt
135
+ return prompt
144
136
 
145
137
 
146
138
  def create_examples(
@@ -156,11 +148,15 @@ def create_examples(
156
148
  String containing all examples with replaced placeholders
157
149
  """
158
150
  examples = (fixed_examples + agent_examples)[:MAX_SYSTEM_PROMPT_EXAMPLES]
159
- return "\n".join([example.replace("{tp}", tp) for example in examples])
151
+ formatted_examples = format_examples_by_llm_type(examples)
152
+
153
+ return "\n".join([example.replace("{tp}", tp) for example in formatted_examples])
160
154
 
161
155
 
162
156
  def SystemPrompt(info: Dict[str, Any], action_examples: List[str]) -> str:
163
- response_format_prompt = info.get("response_format_prompt", "")
157
+ tp = info["tag_prefix"]
158
+ response_format_prompt = info.get("response_format_prompt", "") or ""
159
+ response_format_prompt = response_format_prompt.replace("{{tag_prefix}}", tp)
164
160
  response_guidelines_prompt = (
165
161
  f"<response_guidelines>\nConsider the following when generating a response to the originator:\n"
166
162
  f"{response_format_prompt}</response_guidelines>"
@@ -183,24 +179,23 @@ def SystemPrompt(info: Dict[str, Any], action_examples: List[str]) -> str:
183
179
  )
184
180
 
185
181
  # Merged
186
- tp = info["tag_prefix"]
187
182
  examples = create_examples(fixed_examples, action_examples, tp)
188
183
 
189
184
  handling_files = get_file_handling_prompt(tp)
190
185
 
191
186
  return f"""
192
- Note to avoid unintended collisions, all tag names in the assistant response will start with the value {tp}
187
+ Note to avoid unintended collisions, all tag names in the assistant response will start with the value `{tp}`
193
188
  <orchestrator_info>
194
189
  You are an assistant serving as the orchestrator in an AI agentic system. Your primary functions are to:
195
190
  1. Receive stimuli from external sources via the system Gateway
196
191
  2. Invoke actions within system agents to address these stimuli
197
192
  3. Formulate responses based on agent actions
198
193
 
199
- This process is iterative, with the assistant being reinvoked at each step.
194
+ This process is iterative, where the assistant is reinvoked at each step.
200
195
 
201
196
  The Stimulus represents user or application requests.
202
197
 
203
- The assistant receives a history of all gateway-orchestrator exchanges, excluding its own action invocations and reasoning.
198
+ The assistant receives a history of all gateway-orchestrator exchanges, excluding responses from agents' action invocations and reasoning. Don't use this as a guide for the responses.
204
199
 
205
200
  The assistant's behavior aligns with the system purpose specified below:
206
201
  <system_purpose>
@@ -214,19 +209,39 @@ The assistant's behavior aligns with the system purpose specified below:
214
209
  3. After opening agents, the assistant will be reinvoked with an updated list of open agents and their actions.
215
210
  4. When opening an agent, provide only a brief status update without detailed explanations.
216
211
  5. Do not perform any other actions besides opening the required agents in this step.
212
+ 6. All `{tp}` XML tags must be generated as raw text directly within the response stream, seamlessly integrated with any natural language text, as shown in the positive examples.
213
+ 7. Crucially, `{tp}` directive tags must *never* be wrapped in markdown code fences (``` ```) of any kind, including ```xml.
217
214
  - Report generation:
218
215
  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
- 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
216
+ 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 {FS_PROTOCOL} urls with the resolve=true query parameter to insert the sections into the main document. When inserting {FS_PROTOCOL} 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:
217
+ <body>
218
+ <!-- Title -->
219
+ <h1>Report Title</h1>
220
+
221
+ <!-- Section 1 -->
222
+ {FS_PROTOCOL}://xxxxxx.html?resolve=true
223
+
224
+ <!-- Section 2 -->
225
+ {FS_PROTOCOL}://yyyyyy.html?resolve=true
226
+
227
+ <!-- Section 3 -->
228
+ {FS_PROTOCOL}://zzzzzz.html?resolve=true
229
+ </body>
230
+ 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.
231
+ 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 {FS_PROTOCOL} link. For example, <img src="{FS_PROTOCOL}://xxxxxx.png?encoding=datauri&resolve=true">. The assistant will take care of the rest. Images can be created in parallel
221
232
  4. During report generation in interactive sessions, the assistant will send lots of status messages to indicate what is happening.
222
233
  - Handling stimuli with open agents:
223
234
  1. Use agents' actions to break down the stimulus into smaller, manageable tasks.
224
- 2. Prioritize using available actions to fulfill the stimulus whenever possible.
225
- 3. If no suitable agents or actions are available, the assistant will:
235
+ 2. Invoke agents' actions to perform these tasks
236
+ 3. After invoking an action with the invoke action directive, finish the response and wait for the action to complete.
237
+ 4. The action will be run and the results will then be returned on a later step. NEVER guess or fill in the response without waiting for the action's response.
238
+ 5. Prioritize using available actions to fulfill the stimulus whenever possible.
239
+ 6. If no suitable agents or actions are available, the assistant will:
226
240
  a) Use its own knowledge to respond, or
227
241
  b) Ask the user for additional information, or
228
242
  c) Inform the user that it cannot fulfill the request.
229
- - The first user message contains the history of all exchanges between the gateway and the orchestrator before now. Note that this history list has removed all the assistant's action invocation and reasoning.
243
+ - The first user message contains the history of all exchanges between the gateway and the orchestrator before now. Note that this history list has removed all the agent's action invocation outputs and reasoning.
244
+ - Do not use the history as a guide for how to respond. That history is only present to provide some context to the coversation. The format and data have been modified and does not show all the assistant's directives.
230
245
  - The assistant will not guess at an answer. No answer is better than a wrong answer.
231
246
  - The assistant will invoke the actions and specify the parameters for each action, following the rules of the action. If there is not sufficient context to fill in an action parameter, the assistant will ask the user for more information.
232
247
  - After invoking the actions, the assistant will end the response and wait for the action responses. It will not guess at the answers.
@@ -238,6 +253,7 @@ The assistant's behavior aligns with the system purpose specified below:
238
253
  2. Within this tag, include:
239
254
  a) A brief list of points describing the plan and thoughts.
240
255
  b) A list of potential actions needed to fulfill the stimulus.
256
+ c) Always include a statement that the assistant will not follow invoke actions with any other output.
241
257
  3. Ensure all content is contained within the <{tp}reasoning> tag.
242
258
  4. Keep each point concise and focused.
243
259
  - For large grouped output, such as a list of items or a big code block (> 10 lines), the assistant will create a file by surrounding the output with the tags <{tp}file name="filename" mime_type="mimetype"><data> the content </data></{tp}file>. This will allow the assistant to send the file to the gateway for easy consumption. This works well for a csv file, a code file or just a big text file.
@@ -247,6 +263,7 @@ The assistant's behavior aligns with the system purpose specified below:
247
263
  - When the stimulus asks what the system can do, the assistant will open all the agents to see their details before creating a nicely formatted list describing the actions available and indicating that it can do normal chatbot things as well. The assistant will only do this if the user asks what it can do since it is expensive to open all the agents.
248
264
  - The assistant is concise and professional in its responses. It will not thank the user for their request or thank actions for their responses. It will not provide any unnecessary information in its responses.
249
265
  - The assistant will not follow invoke_actions with further comments or explanations
266
+ - After outputing the invoke action tags, the assistant will not add any additional text. It will just end the response always.
250
267
  - The assistant will distinguish between normal text and status updates. All status updates will be enclosed in <{tp}status_update/> tags.
251
268
  - Responses that are just letting the originator know that progress is being made or what the next step is should be status updates. They should be brief and to the point.
252
269
  <action_rules>
@@ -330,12 +347,12 @@ def UserStimulusPrompt(
330
347
  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
348
  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
349
  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"
350
+ 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
351
  f"</{info['tag_prefix']}file_instructions>"
335
352
  )
336
353
 
337
354
  prompt = (
338
- "NOTE - this history represents the conversation as seen by the user on the other side of the gateway. It does not include the assistant's invoke actions or reasoning. All of that has been removed, so don't use this history as an example for how the assistant should behave\n"
355
+ "NOTE - this history represents the conversation as seen by the user on the other side of the gateway. It does not include the responses from invoked actions or reasoning. All of that has been removed, so don't use this history as an example for how the assistant should behave\n"
339
356
  f"{gateway_history_str}\n"
340
357
  f"<{info['tag_prefix']}stimulus>\n"
341
358
  f"{stimulus}\n"
@@ -393,7 +410,7 @@ reputation is on the line.
393
410
  """
394
411
 
395
412
 
396
- def ContextQueryPrompt(query: str, context: str) -> HumanMessage:
413
+ def ContextQueryPrompt(query: str, context: str) -> str:
397
414
  return f"""
398
415
  You (orchestrator) are being asked to query, comment on or edit the following text following the originator's request.
399
416
  Do your best to give a complete and accurate answer using only the context given below. Ensure that you
@@ -413,3 +430,110 @@ you should ask the originator for more information. Include links to the source
413
430
  {context}
414
431
  </context>
415
432
  """
433
+
434
+
435
+ def format_examples_by_llm_type(examples: list, llm_type: str = "anthropic") -> list:
436
+ """
437
+ Render examples based on llm type
438
+
439
+ Args:
440
+ llm_type (str): The type of LLM to render examples for (default: "anthropic")
441
+ examples (list): List of examples in model-agnostic format
442
+
443
+ Returns:
444
+ list: List of examples formatted for the specified LLM
445
+ """
446
+ formatted_examples = []
447
+
448
+ if llm_type == "anthropic":
449
+ for example in examples:
450
+ formatted_example = format_example_for_anthropic(example)
451
+ formatted_examples.append(formatted_example)
452
+ else:
453
+ log.error(f"Unsupported LLM type: {llm_type}")
454
+
455
+ return formatted_examples
456
+
457
+
458
+ def format_example_for_anthropic(example: dict) -> str:
459
+ """
460
+ Format an example for the Anthropic's LLMs
461
+ """
462
+
463
+ tag_prefix = example.get("tag_prefix_placeholder", "t123")
464
+ starting_id = example.get("starting_id", "1")
465
+ docstring = example.get("docstring", "")
466
+ user_input = example.get("user_input", "")
467
+ metadata_lines = example.get("metadata", [])
468
+ reasoning_lines = example.get("reasoning", [])
469
+ response_text = example.get("response_text", "")
470
+
471
+ # Start building the XML structure, add the description and user input
472
+ xml_content = f"""<example>
473
+ <example_docstring>
474
+ {docstring}
475
+ </example_docstring>
476
+ <example_stimulus>
477
+ <{tag_prefix}stimulus starting_id="{starting_id}">
478
+ {user_input}
479
+ </{tag_prefix}stimulus>
480
+ <{tag_prefix}stimulus_metadata>
481
+ """
482
+
483
+ # Add metadata lines
484
+ for metadata_line in metadata_lines:
485
+ xml_content += f"{metadata_line}\n"
486
+
487
+ xml_content += f"""</{tag_prefix}stimulus_metadata>
488
+ </example_stimulus>
489
+ <example_response>
490
+ <{tag_prefix}reasoning>
491
+ """
492
+
493
+ # Add reasoning lines
494
+ for reasoning_line in reasoning_lines:
495
+ xml_content += f"{reasoning_line}\n"
496
+
497
+ xml_content += f"""</{tag_prefix}reasoning>
498
+ {response_text}"""
499
+
500
+ # Add action invocation section
501
+ if "action" in example:
502
+ action_data = example.get("action", {})
503
+ status_update = example.get("status_update", "")
504
+ agent_name = action_data.get("agent", "")
505
+ action_name = action_data.get("name", "")
506
+
507
+ xml_content += f"""
508
+ <{tag_prefix}status_update>{status_update}</{tag_prefix}status_update>
509
+ <{tag_prefix}invoke_action agent="{agent_name}" action="{action_name}">"""
510
+
511
+ # Handle parameters as dictionary
512
+ parameter_dict = action_data.get("parameters", {})
513
+ for param_name, param_value in parameter_dict.items():
514
+ xml_content += f"""
515
+ <{tag_prefix}parameter name="{param_name}">"""
516
+
517
+ # Handle parameter names and values (as lists)
518
+ if isinstance(param_value, list):
519
+ for line in param_value:
520
+ xml_content += f"\n{line}"
521
+ xml_content += "\n"
522
+ else:
523
+ # For simple string values
524
+ xml_content += f"{param_value}"
525
+
526
+ xml_content += f"</{tag_prefix}parameter>\n"
527
+
528
+ xml_content += f"</{tag_prefix}invoke_action>"
529
+
530
+ # Close the XML structure
531
+ xml_content += """
532
+ </example_response>
533
+ </example>
534
+ """
535
+
536
+ return xml_content
537
+
538
+
539
+ 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)
@@ -0,0 +1,74 @@
1
+ import json
2
+ import os
3
+ from .base_history_provider import BaseHistoryProvider
4
+
5
+ class FileHistoryProvider(BaseHistoryProvider):
6
+ """
7
+ A simple file-based history provider for storing session data.
8
+ """
9
+ def __init__(self, config=None):
10
+ super().__init__(config)
11
+
12
+ if not self.config.get("path"):
13
+ raise ValueError("Missing required configuration for FileHistoryProvider, Missing 'path' in configs.")
14
+
15
+ self.path = self.config.get("path")
16
+
17
+ if not self._exists(self.path):
18
+ os.makedirs(self.path, exist_ok=True)
19
+
20
+ def _get_key(self, session_id):
21
+ """
22
+ Generate a file path for a session.
23
+
24
+ :param session_id: The session identifier.
25
+ :return: A formatted file path.
26
+ """
27
+ return os.path.join(self.path, f"sessions_{session_id}_history.json")
28
+
29
+ def store_session(self, session_id: str, data: dict):
30
+ """
31
+ Store the session metadata.
32
+
33
+ :param session_id: The session identifier.
34
+ :param data: The session data to be stored.
35
+ """
36
+ file_path = self._get_key(session_id)
37
+ with open(file_path, "w", encoding="utf-8") as f:
38
+ f.write(json.dumps(data))
39
+
40
+ def get_session(self, session_id: str)->dict:
41
+ """
42
+ Retrieve the session.
43
+
44
+ :param session_id: The session identifier.
45
+ :return: The session metadata as a dictionary.
46
+ """
47
+ file_path = self._get_key(session_id)
48
+ if not self._exists(file_path):
49
+ return {}
50
+
51
+ try:
52
+ with open(file_path, "r", encoding="utf-8") as f:
53
+ return json.load(f)
54
+ except json.JSONDecodeError:
55
+ return {}
56
+
57
+ def get_all_sessions(self) -> list[str]:
58
+ """
59
+ Retrieve all session identifiers.
60
+ """
61
+ return [f[9:-13] for f in os.listdir(self.path) if f.startswith("sessions_") and f.endswith("_history.json")]
62
+
63
+ def delete_session(self, session_id: str):
64
+ """
65
+ Delete the session.
66
+
67
+ :param session_id: The session identifier.
68
+ """
69
+ file_path = self._get_key(session_id)
70
+ if self._exists(file_path):
71
+ os.remove(file_path)
72
+
73
+ def _exists(self, path: str):
74
+ return os.path.exists(path)
@@ -0,0 +1,40 @@
1
+
2
+
3
+ class HistoryProviderFactory:
4
+ """
5
+ Factory class for creating history provider instances.
6
+ """
7
+ HISTORY_PROVIDERS = ["redis", "memory", "file", "mongodb", "sql"]
8
+
9
+ @staticmethod
10
+ def has_provider(class_name):
11
+ """
12
+ Check if the history provider is supported.
13
+ """
14
+ return class_name in HistoryProviderFactory.HISTORY_PROVIDERS
15
+
16
+ @staticmethod
17
+ def get_provider_class(class_name):
18
+ """
19
+ Get the history provider class based on the class name.
20
+ """
21
+ if class_name not in HistoryProviderFactory.HISTORY_PROVIDERS:
22
+ raise ValueError(f"Unsupported history provider: {class_name}")
23
+ if class_name == "redis":
24
+ from .redis_history_provider import RedisHistoryProvider
25
+ return RedisHistoryProvider
26
+ elif class_name == "memory":
27
+ from .memory_history_provider import MemoryHistoryProvider
28
+ return MemoryHistoryProvider
29
+ elif class_name == "file":
30
+ from .file_history_provider import FileHistoryProvider
31
+ return FileHistoryProvider
32
+ elif class_name == "mongodb":
33
+ from .mongodb_history_provider import MongoDBHistoryProvider
34
+ return MongoDBHistoryProvider
35
+ elif class_name == "sql":
36
+ from .sql_history_provider import SQLHistoryProvider
37
+ return SQLHistoryProvider
38
+ else:
39
+ raise ValueError(f"Unsupported history provider: {class_name}")
40
+