solace-agent-mesh 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of solace-agent-mesh might be problematic. Click here for more details.

Files changed (176) hide show
  1. solace_agent_mesh/__init__.py +0 -3
  2. solace_agent_mesh/agents/__init__.py +0 -0
  3. solace_agent_mesh/agents/base_agent_component.py +224 -0
  4. solace_agent_mesh/agents/global/__init__.py +0 -0
  5. solace_agent_mesh/agents/global/actions/__init__.py +0 -0
  6. solace_agent_mesh/agents/global/actions/agent_state_change.py +54 -0
  7. solace_agent_mesh/agents/global/actions/clear_history.py +32 -0
  8. solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +160 -0
  9. solace_agent_mesh/agents/global/actions/create_file.py +70 -0
  10. solace_agent_mesh/agents/global/actions/error_action.py +45 -0
  11. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +93 -0
  12. solace_agent_mesh/agents/global/actions/plotly_graph.py +117 -0
  13. solace_agent_mesh/agents/global/actions/retrieve_file.py +51 -0
  14. solace_agent_mesh/agents/global/global_agent_component.py +38 -0
  15. solace_agent_mesh/agents/image_processing/__init__.py +0 -0
  16. solace_agent_mesh/agents/image_processing/actions/__init__.py +0 -0
  17. solace_agent_mesh/agents/image_processing/actions/create_image.py +75 -0
  18. solace_agent_mesh/agents/image_processing/actions/describe_image.py +115 -0
  19. solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +23 -0
  20. solace_agent_mesh/agents/slack/__init__.py +1 -0
  21. solace_agent_mesh/agents/slack/actions/__init__.py +1 -0
  22. solace_agent_mesh/agents/slack/actions/post_message.py +177 -0
  23. solace_agent_mesh/agents/slack/slack_agent_component.py +59 -0
  24. solace_agent_mesh/agents/web_request/__init__.py +0 -0
  25. solace_agent_mesh/agents/web_request/actions/__init__.py +0 -0
  26. solace_agent_mesh/agents/web_request/actions/do_image_search.py +84 -0
  27. solace_agent_mesh/agents/web_request/actions/do_news_search.py +47 -0
  28. solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +34 -0
  29. solace_agent_mesh/agents/web_request/actions/do_web_request.py +134 -0
  30. solace_agent_mesh/agents/web_request/actions/download_file.py +69 -0
  31. solace_agent_mesh/agents/web_request/web_request_agent_component.py +33 -0
  32. solace_agent_mesh/assets/web-visualizer/assets/index-C5awueeJ.js +109 -0
  33. solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +1 -0
  34. solace_agent_mesh/assets/web-visualizer/index.html +14 -0
  35. solace_agent_mesh/assets/web-visualizer/vite.svg +1 -0
  36. solace_agent_mesh/cli/__init__.py +1 -0
  37. solace_agent_mesh/cli/commands/__init__.py +0 -0
  38. solace_agent_mesh/cli/commands/add/__init__.py +3 -0
  39. solace_agent_mesh/cli/commands/add/add.py +88 -0
  40. solace_agent_mesh/cli/commands/add/agent.py +110 -0
  41. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +90 -0
  42. solace_agent_mesh/cli/commands/add/gateway.py +221 -0
  43. solace_agent_mesh/cli/commands/build.py +631 -0
  44. solace_agent_mesh/cli/commands/chat/__init__.py +3 -0
  45. solace_agent_mesh/cli/commands/chat/chat.py +361 -0
  46. solace_agent_mesh/cli/commands/config.py +29 -0
  47. solace_agent_mesh/cli/commands/init/__init__.py +3 -0
  48. solace_agent_mesh/cli/commands/init/ai_provider_step.py +76 -0
  49. solace_agent_mesh/cli/commands/init/broker_step.py +102 -0
  50. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +88 -0
  51. solace_agent_mesh/cli/commands/init/check_if_already_done.py +13 -0
  52. solace_agent_mesh/cli/commands/init/create_config_file_step.py +52 -0
  53. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +96 -0
  54. solace_agent_mesh/cli/commands/init/file_service_step.py +73 -0
  55. solace_agent_mesh/cli/commands/init/init.py +114 -0
  56. solace_agent_mesh/cli/commands/init/project_structure_step.py +45 -0
  57. solace_agent_mesh/cli/commands/init/rest_api_step.py +50 -0
  58. solace_agent_mesh/cli/commands/init/web_ui_step.py +40 -0
  59. solace_agent_mesh/cli/commands/plugin/__init__.py +3 -0
  60. solace_agent_mesh/cli/commands/plugin/add.py +98 -0
  61. solace_agent_mesh/cli/commands/plugin/build.py +217 -0
  62. solace_agent_mesh/cli/commands/plugin/create.py +117 -0
  63. solace_agent_mesh/cli/commands/plugin/plugin.py +109 -0
  64. solace_agent_mesh/cli/commands/plugin/remove.py +71 -0
  65. solace_agent_mesh/cli/commands/run.py +68 -0
  66. solace_agent_mesh/cli/commands/visualizer.py +138 -0
  67. solace_agent_mesh/cli/config.py +81 -0
  68. solace_agent_mesh/cli/main.py +306 -0
  69. solace_agent_mesh/cli/utils.py +246 -0
  70. solace_agent_mesh/common/__init__.py +0 -0
  71. solace_agent_mesh/common/action.py +91 -0
  72. solace_agent_mesh/common/action_list.py +37 -0
  73. solace_agent_mesh/common/action_response.py +327 -0
  74. solace_agent_mesh/common/constants.py +3 -0
  75. solace_agent_mesh/common/mysql_database.py +40 -0
  76. solace_agent_mesh/common/postgres_database.py +79 -0
  77. solace_agent_mesh/common/prompt_templates.py +30 -0
  78. solace_agent_mesh/common/prompt_templates_unused_delete.py +161 -0
  79. solace_agent_mesh/common/stimulus_utils.py +152 -0
  80. solace_agent_mesh/common/time.py +24 -0
  81. solace_agent_mesh/common/utils.py +638 -0
  82. solace_agent_mesh/configs/agent_global.yaml +74 -0
  83. solace_agent_mesh/configs/agent_image_processing.yaml +82 -0
  84. solace_agent_mesh/configs/agent_slack.yaml +64 -0
  85. solace_agent_mesh/configs/agent_web_request.yaml +75 -0
  86. solace_agent_mesh/configs/conversation_to_file.yaml +56 -0
  87. solace_agent_mesh/configs/error_catcher.yaml +56 -0
  88. solace_agent_mesh/configs/monitor.yaml +0 -0
  89. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +106 -0
  90. solace_agent_mesh/configs/monitor_user_feedback.yaml +58 -0
  91. solace_agent_mesh/configs/orchestrator.yaml +241 -0
  92. solace_agent_mesh/configs/service_embedding.yaml +81 -0
  93. solace_agent_mesh/configs/service_llm.yaml +265 -0
  94. solace_agent_mesh/configs/visualize_websocket.yaml +55 -0
  95. solace_agent_mesh/gateway/__init__.py +0 -0
  96. solace_agent_mesh/gateway/components/__init__.py +0 -0
  97. solace_agent_mesh/gateway/components/gateway_base.py +41 -0
  98. solace_agent_mesh/gateway/components/gateway_input.py +265 -0
  99. solace_agent_mesh/gateway/components/gateway_output.py +289 -0
  100. solace_agent_mesh/gateway/identity/bamboohr_identity.py +18 -0
  101. solace_agent_mesh/gateway/identity/identity_base.py +10 -0
  102. solace_agent_mesh/gateway/identity/identity_provider.py +60 -0
  103. solace_agent_mesh/gateway/identity/no_identity.py +9 -0
  104. solace_agent_mesh/gateway/identity/passthru_identity.py +9 -0
  105. solace_agent_mesh/monitors/base_monitor_component.py +26 -0
  106. solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +75 -0
  107. solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +560 -0
  108. solace_agent_mesh/orchestrator/__init__.py +0 -0
  109. solace_agent_mesh/orchestrator/action_manager.py +225 -0
  110. solace_agent_mesh/orchestrator/components/__init__.py +0 -0
  111. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +54 -0
  112. solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +179 -0
  113. solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +107 -0
  114. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +477 -0
  115. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +246 -0
  116. solace_agent_mesh/orchestrator/orchestrator_main.py +166 -0
  117. solace_agent_mesh/orchestrator/orchestrator_prompt.py +410 -0
  118. solace_agent_mesh/services/__init__.py +0 -0
  119. solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +56 -0
  120. solace_agent_mesh/services/bamboo_hr_service/__init__.py +3 -0
  121. solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +182 -0
  122. solace_agent_mesh/services/common/__init__.py +4 -0
  123. solace_agent_mesh/services/common/auto_expiry.py +45 -0
  124. solace_agent_mesh/services/common/singleton.py +18 -0
  125. solace_agent_mesh/services/file_service/__init__.py +14 -0
  126. solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
  127. solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +149 -0
  128. solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +162 -0
  129. solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +64 -0
  130. solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +106 -0
  131. solace_agent_mesh/services/file_service/file_service.py +432 -0
  132. solace_agent_mesh/services/file_service/file_service_constants.py +54 -0
  133. solace_agent_mesh/services/file_service/file_transformations.py +131 -0
  134. solace_agent_mesh/services/file_service/file_utils.py +322 -0
  135. solace_agent_mesh/services/file_service/transformers/__init__.py +5 -0
  136. solace_agent_mesh/services/history_service/__init__.py +3 -0
  137. solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
  138. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +78 -0
  139. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +167 -0
  140. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +163 -0
  141. solace_agent_mesh/services/history_service/history_service.py +139 -0
  142. solace_agent_mesh/services/llm_service/components/llm_request_component.py +293 -0
  143. solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +152 -0
  144. solace_agent_mesh/services/middleware_service/__init__.py +0 -0
  145. solace_agent_mesh/services/middleware_service/middleware_service.py +20 -0
  146. solace_agent_mesh/templates/action.py +38 -0
  147. solace_agent_mesh/templates/agent.py +29 -0
  148. solace_agent_mesh/templates/agent.yaml +70 -0
  149. solace_agent_mesh/templates/gateway-config-template.yaml +6 -0
  150. solace_agent_mesh/templates/gateway-default-config.yaml +28 -0
  151. solace_agent_mesh/templates/gateway-flows.yaml +81 -0
  152. solace_agent_mesh/templates/gateway-header.yaml +16 -0
  153. solace_agent_mesh/templates/gateway_base.py +15 -0
  154. solace_agent_mesh/templates/gateway_input.py +98 -0
  155. solace_agent_mesh/templates/gateway_output.py +71 -0
  156. solace_agent_mesh/templates/plugin-pyproject.toml +30 -0
  157. solace_agent_mesh/templates/rest-api-default-config.yaml +24 -0
  158. solace_agent_mesh/templates/rest-api-flows.yaml +80 -0
  159. solace_agent_mesh/templates/slack-default-config.yaml +9 -0
  160. solace_agent_mesh/templates/slack-flows.yaml +90 -0
  161. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +77 -0
  162. solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +8 -0
  163. solace_agent_mesh/templates/web-default-config.yaml +5 -0
  164. solace_agent_mesh/templates/web-flows.yaml +86 -0
  165. solace_agent_mesh/tools/__init__.py +0 -0
  166. solace_agent_mesh/tools/components/__init__.py +0 -0
  167. solace_agent_mesh/tools/components/conversation_formatter.py +111 -0
  168. solace_agent_mesh/tools/components/file_resolver_component.py +58 -0
  169. solace_agent_mesh/tools/config/runtime_config.py +26 -0
  170. solace_agent_mesh-0.1.1.dist-info/METADATA +179 -0
  171. solace_agent_mesh-0.1.1.dist-info/RECORD +174 -0
  172. solace_agent_mesh-0.1.1.dist-info/entry_points.txt +3 -0
  173. solace_agent_mesh-0.0.1.dist-info/licenses/LICENSE.txt → solace_agent_mesh-0.1.1.dist-info/licenses/LICENSE +1 -2
  174. solace_agent_mesh-0.0.1.dist-info/METADATA +0 -51
  175. solace_agent_mesh-0.0.1.dist-info/RECORD +0 -5
  176. {solace_agent_mesh-0.0.1.dist-info → solace_agent_mesh-0.1.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,322 @@
1
+ # Add utility functions for upload CSV that automatically creates the number of row and data types
2
+ import csv
3
+ from typing import Tuple, Union
4
+ import json
5
+
6
+ from .file_service_constants import FS_PROTOCOL, INDENT_SIZE
7
+
8
+
9
+ class Types:
10
+ """
11
+ Type constants definition to be used when representing the schema.
12
+ """
13
+
14
+ INT = "int"
15
+ FLOAT = "float"
16
+ BOOL = "bool"
17
+ STR = "str"
18
+ ARRAY = "array"
19
+ OBJECT = "object"
20
+ NULL = "null"
21
+
22
+ @property
23
+ def PRIMITIVES():
24
+ return [Types.INT, Types.FLOAT, Types.BOOL, Types.STR]
25
+
26
+
27
+ def get_str_type(value: str) -> str:
28
+ """
29
+ Get the type of a string value.
30
+
31
+ Parameters:
32
+ - value (str): The string value.
33
+
34
+ Returns:
35
+ - str: The type of the value.
36
+ """
37
+ if value.isdigit():
38
+ return Types.INT
39
+ elif value.replace(".", "", 1).isdigit():
40
+ return Types.FLOAT
41
+ else:
42
+ return Types.STR
43
+
44
+
45
+ def convert_dict_to_yaml(schema: dict, indent_size=INDENT_SIZE, _level=0) -> str:
46
+ """
47
+ Convert a dictionary schema to a YAML string.
48
+
49
+ Parameters:
50
+ - schema (dict): The dictionary schema.
51
+
52
+ Returns:
53
+ - str: The YAML string.
54
+ """
55
+ yaml_str = ""
56
+ indent = indent_size * _level
57
+
58
+ for key, value in schema.items():
59
+ if isinstance(value, dict):
60
+ value_str = convert_dict_to_yaml(value, indent_size, _level + 1)
61
+ yaml_str += f"{space(indent)}{key}:\n{value_str}\n"
62
+ else:
63
+ yaml_str += f"{space(indent)}{key}: {value}\n"
64
+ return yaml_str.rstrip()
65
+
66
+
67
+ def space(size=INDENT_SIZE):
68
+ """
69
+ Get a string of spaces.
70
+ """
71
+ if size == 0:
72
+ return ""
73
+ return " " * size
74
+
75
+
76
+ def extract_csv_schema_and_shape(csv_file: Union[str, bytes]) -> Tuple[dict, Tuple[int, int]]:
77
+ """
78
+ Get the schema and shape of a CSV file.
79
+
80
+ Parameters:
81
+ - csv_file (str): The CSV file content.
82
+ - header_row (str): The header row of the CSV file.
83
+
84
+ Returns:
85
+ - str: The schema of the CSV file.
86
+ - st: The shape of the CSV file.
87
+ """
88
+ if type(csv_file) == bytes:
89
+ csv_file = csv_file.decode("utf-8")
90
+
91
+ # Parse the CSV string
92
+ csv_lines = csv_file.splitlines()
93
+ csv_reader = csv.DictReader(csv_lines)
94
+ columns = csv_reader.fieldnames
95
+ listed_csv = list(csv_reader)
96
+ shape = (len(listed_csv), len(columns))
97
+ schema = {}
98
+ for col in columns:
99
+ # deriving type from the top 10 rows
100
+ col_type = None
101
+ for i, row in enumerate(listed_csv):
102
+ if i >= 10:
103
+ break
104
+ curr_type = get_str_type(row[col])
105
+ if col_type is None:
106
+ col_type = curr_type
107
+ elif col_type != curr_type:
108
+ # multiple types, set as str
109
+ col_type = Types.STR
110
+ break
111
+ schema[col] = {"type": col_type}
112
+
113
+ return schema, shape
114
+
115
+
116
+ def dict_to_schema(input_dict: dict) -> dict:
117
+ """
118
+ Convert a dictionary to a JSON schema.
119
+
120
+ Parameters:
121
+ - input_dict (dict): The input dictionary.
122
+
123
+ Returns:
124
+ - dict: The JSON schema.
125
+ """
126
+
127
+ def infer_type(value):
128
+ if isinstance(value, str):
129
+ return Types.STR
130
+ elif isinstance(value, int):
131
+ return Types.INT
132
+ elif isinstance(value, float):
133
+ return Types.FLOAT
134
+ elif isinstance(value, bool):
135
+ return Types.BOOL
136
+ elif isinstance(value, list):
137
+ if len(value) > 0:
138
+ return {"type": Types.ARRAY, "items": infer_type(value[0])}
139
+ else:
140
+ return {"type": Types.ARRAY, "items": {}}
141
+ elif isinstance(value, dict):
142
+ return {"type": Types.OBJECT, "properties": infer_properties(value)}
143
+ else:
144
+ return Types.NULL
145
+
146
+ def infer_properties(input_dict):
147
+ return {
148
+ key: (
149
+ {"type": infer_type(value)}
150
+ if isinstance(infer_type(value), str)
151
+ else infer_type(value)
152
+ )
153
+ for key, value in input_dict.items()
154
+ }
155
+
156
+ if type(input_dict) is list:
157
+ input_dict = input_dict[0]
158
+ json_schema = {
159
+ "type": Types.ARRAY,
160
+ "items": {"type": Types.OBJECT, "properties": infer_properties(input_dict)},
161
+ }
162
+ else:
163
+ json_schema = {"type": Types.OBJECT, "properties": infer_properties(input_dict)}
164
+
165
+ return json_schema
166
+
167
+
168
+ def get_dict_array_shape(
169
+ data: Union[dict, list], max_level=2, _path="", _result=None, _level=0
170
+ ):
171
+ """
172
+ Get the count of each array in a dictionary schema.
173
+ """
174
+ if _result is None:
175
+ _result = []
176
+
177
+ # If it's a list, record its length and traverse each element
178
+ if isinstance(data, list):
179
+ if _path:
180
+ _result.append([_path, len(data)])
181
+ else:
182
+ _result.append(["top level", len(data)])
183
+ if _level < max_level:
184
+ get_dict_array_shape(data[0], max_level, f"{_path}[*]", _result, _level + 1)
185
+
186
+ # If it's a dictionary, traverse each key-value pair
187
+ elif isinstance(data, dict):
188
+ for key, value in data.items():
189
+ new_path = f"{_path}.{key}" if _path else key
190
+ if _level < max_level:
191
+ get_dict_array_shape(value, max_level, new_path, _result, _level + 1)
192
+
193
+ return _result
194
+
195
+
196
+ def get_csv_schema_and_shape(schema: dict, shape: Tuple[int, int]) -> Tuple[str, str]:
197
+ """
198
+ Get the formatted schema and shape of a CSV file.
199
+
200
+ Parameters:
201
+ - schema (dict): The schema of the CSV file.
202
+ - shape (Tuple[int, int]): The shape of the CSV file.
203
+
204
+ Returns:
205
+ - str: The formatted schema of the CSV file.
206
+ - Tuple[int, int]: The shape of the CSV file.
207
+ """
208
+ shape_str = f"{shape[0]} rows x {shape[1]} columns"
209
+ schema_dict = {
210
+ "type": Types.ARRAY,
211
+ "items": {"type": Types.OBJECT, "properties": schema},
212
+ }
213
+ schema_str = convert_dict_to_yaml(schema_dict)
214
+ return schema_str, shape_str
215
+
216
+
217
+ def get_json_schema_and_shape(file: Union[bytes, str]) -> Tuple[str, str]:
218
+ try:
219
+ if type(file) == bytes:
220
+ file = file.decode("utf-8")
221
+ json_data = json.loads(file)
222
+ schema = dict_to_schema(json_data)
223
+ schema_str = convert_dict_to_yaml(schema)
224
+ shape_str = None
225
+
226
+ def get_row_text(count):
227
+ return f"{count} elements" if count > 1 else f"{count} element"
228
+
229
+ array_lengths = get_dict_array_shape(json_data)
230
+ if array_lengths:
231
+ shape_str = "\n".join(
232
+ [f"{key}: {get_row_text(val)}" for key, val in array_lengths]
233
+ )
234
+ return schema_str, shape_str
235
+ except Exception as e:
236
+ return None, None
237
+
238
+
239
+ def get_file_schema_and_shape(file: bytes, metadata: dict) -> Tuple[str, str]:
240
+ """
241
+ Get the schema and shape of a file.
242
+
243
+ Parameters:
244
+ - file (bytes): The file content as bytes.
245
+ - metadata (dict): The file metadata.
246
+
247
+ Returns:
248
+ - str: The schema of the file. None if cannot be determined.
249
+ - str: The shape of the file. None if cannot be determined.
250
+ """
251
+ if metadata.get("mime_type") == "text/csv":
252
+ schema, shape = extract_csv_schema_and_shape(file)
253
+ schema_str, shape_str = get_csv_schema_and_shape(schema, shape)
254
+ return schema_str, shape_str
255
+ elif metadata.get("mime_type") == "application/json":
256
+ schema, shape = get_json_schema_and_shape(file)
257
+ return schema, shape
258
+ else:
259
+ return None, None
260
+
261
+
262
+ def recursive_file_resolver(
263
+ obj: Union[dict, str, list],
264
+ resolver: callable,
265
+ session_id: str,
266
+ force_resolve: bool = False,
267
+ ):
268
+ """
269
+ Recursively resolve all the file URLs in the input data.
270
+
271
+ Parameters:
272
+ - obj (dict|str|list): The input data.
273
+ - resolver (callable): The resolver function.
274
+ - session_id (str): The session ID.
275
+ - force_resolve (bool): Resolve all the URLs regardless of the 'resolve' query parameter.
276
+
277
+ Returns:
278
+ - dict|str|list: The object with resolved URLs.
279
+ """
280
+ if type(obj) == str:
281
+ return resolver(obj, session_id, force_resolve)
282
+
283
+ keys = []
284
+ if type(obj) == list:
285
+ keys = range(len(obj))
286
+ elif type(obj) == dict:
287
+ keys = obj.keys()
288
+
289
+ for key in keys:
290
+ if isinstance(obj[key], dict):
291
+ obj[key] = recursive_file_resolver(
292
+ obj[key], resolver, session_id, force_resolve
293
+ )
294
+ elif isinstance(obj[key], list):
295
+ obj[key] = [
296
+ (
297
+ recursive_file_resolver(item, resolver, session_id, force_resolve)
298
+ if isinstance(item, dict)
299
+ else item
300
+ )
301
+ for item in obj[key]
302
+ ]
303
+ elif isinstance(obj[key], str):
304
+ obj[key] = resolver(obj[key], session_id, force_resolve)
305
+ return obj
306
+
307
+
308
+ def starts_with_fs_url(url: str) -> bool:
309
+ """
310
+ Check if the URL starts with the file service protocol.
311
+
312
+ Parameters:
313
+ - url (str): The URL.
314
+
315
+ Returns:
316
+ - bool: True if the URL starts with the file service protocol.
317
+ """
318
+ return (
319
+ url.startswith(f"{FS_PROTOCOL}://")
320
+ or url.startswith(f"<url>{FS_PROTOCOL}://")
321
+ or url.startswith(f"<url> {FS_PROTOCOL}://")
322
+ )
@@ -0,0 +1,5 @@
1
+ TRANSFORMERS = []
2
+
3
+ __all__ = [
4
+ "TRANSFORMERS"
5
+ ]
@@ -0,0 +1,3 @@
1
+ from .history_service import HistoryService
2
+
3
+ __all__ = ["HistoryService"]
@@ -0,0 +1,78 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Union
3
+
4
+ class BaseHistoryProvider(ABC):
5
+
6
+ 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
+
23
+ @abstractmethod
24
+ def get_history(self, session_id: str):
25
+ """
26
+ Retrieve the entire history.
27
+
28
+ :param session_id: The session identifier.
29
+ :return: The complete history.
30
+ """
31
+ raise NotImplementedError("Method not implemented")
32
+
33
+ @abstractmethod
34
+ def store_file(self, session_id: str, file: dict):
35
+ """
36
+ Store a file in the history.
37
+
38
+ :param session_id: The session identifier.
39
+ :param file: The file metadata to be stored in the history.
40
+ """
41
+ raise NotImplementedError("Method not implemented")
42
+
43
+ @abstractmethod
44
+ def get_files(self, session_id: str):
45
+ """
46
+ Retrieve the files for a session.
47
+
48
+ :param session_id: The session identifier.
49
+ :return: The files for the session.
50
+ """
51
+ raise NotImplementedError("Method not implemented")
52
+
53
+ @abstractmethod
54
+ def get_session_meta(self, session_id: str):
55
+ """
56
+ Retrieve the session metadata.
57
+
58
+ :param session_id: The session identifier.
59
+ :return: The session metadata.
60
+ """
61
+ raise NotImplementedError("Method not implemented")
62
+
63
+ @abstractmethod
64
+ def clear_history(self, session_id: str, keep_levels=0):
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
+ """
71
+ raise NotImplementedError("Method not implemented")
72
+
73
+ @abstractmethod
74
+ def get_all_sessions(self) -> list[str]:
75
+ """
76
+ Retrieve all session identifiers.
77
+ """
78
+ raise NotImplementedError("Method not implemented")
@@ -0,0 +1,167 @@
1
+ import time
2
+ from .base_history_provider import BaseHistoryProvider
3
+
4
+
5
+ class MemoryHistoryProvider(BaseHistoryProvider):
6
+ def __init__(self, config=None):
7
+ super().__init__(config)
8
+ self.history = {}
9
+
10
+ def store_history(self, session_id: str, role: str, content: str | dict):
11
+ """
12
+ Store a new entry in the history.
13
+
14
+ :param session_id: The session identifier.
15
+ :param history_entry: The entry to be stored in the history.
16
+ """
17
+
18
+ if session_id not in self.history:
19
+ self.history[session_id] = {
20
+ "history": [],
21
+ "files": [],
22
+ "last_active_time": time.time(),
23
+ "num_characters": 0,
24
+ "num_turns": 0,
25
+ }
26
+
27
+ # Check if adding another entry would exceed the max_turns
28
+ if self.history[session_id]["num_turns"] == self.max_turns:
29
+ # Remove the oldest entry
30
+ oldest_entry = self.history[session_id]["history"].pop(0)
31
+ # Subtract the length of the oldest entry from the total length
32
+ self.history[session_id]["num_characters"] -= len(
33
+ str(oldest_entry["content"])
34
+ )
35
+ self.history[session_id]["num_turns"] -= 1
36
+
37
+ if (
38
+ self.enforce_alternate_message_roles
39
+ and self.history[session_id]["num_turns"] > 0
40
+ # Check if the last entry was by the same role
41
+ and self.history[session_id]["history"]
42
+ and self.history[session_id]["history"][-1]["role"] == role
43
+ ):
44
+ # Append to last entry
45
+ self.history[session_id]["history"][-1]["content"] += content
46
+ else:
47
+ # Add the new entry
48
+ self.history[session_id]["history"].append(
49
+ {"role": role, "content": content}
50
+ )
51
+ # Update the number of turns
52
+ self.history[session_id]["num_turns"] += 1
53
+
54
+ # Update the length
55
+ self.history[session_id]["num_characters"] += len(str(content))
56
+ # Update the last active time
57
+ self.history[session_id]["last_active_time"] = time.time()
58
+
59
+ # Check if we have exceeded max_characters
60
+ if self.max_characters:
61
+ while (
62
+ self.history[session_id]["num_characters"] > self.max_characters
63
+ and self.history[session_id]["num_turns"] > 0
64
+ ):
65
+ # Remove the oldest entry
66
+ oldest_entry = self.history[session_id]["history"].pop(0)
67
+ # Subtract the length of the oldest entry from the total length
68
+ self.history[session_id]["num_characters"] -= len(
69
+ str(oldest_entry["content"])
70
+ )
71
+ self.history[session_id]["num_turns"] -= 1
72
+
73
+ def get_history(self, session_id: str):
74
+ """
75
+ Retrieve the entire history for a session.
76
+
77
+ :param session_id: The session identifier.
78
+ :return: The complete history for the session.
79
+ """
80
+ if session_id not in self.history:
81
+ return []
82
+ return self.history.get(session_id)["history"]
83
+
84
+ def store_file(self, session_id: str, file: dict):
85
+ """
86
+ Store a file in the history.
87
+
88
+ :param session_id: The session identifier.
89
+ :param file: The file metadata to be stored in the history.
90
+ """
91
+ if session_id not in self.history:
92
+ self.history[session_id] = {
93
+ "history": [],
94
+ "files": [],
95
+ "last_active_time": time.time(),
96
+ "num_characters": 0,
97
+ "num_turns": 0,
98
+ }
99
+ self.history[session_id]["last_active_time"] = time.time()
100
+
101
+ # Check duplicate
102
+ for f in self.history[session_id]["files"]:
103
+ if f.get("url") and f.get("url") == file.get("url"):
104
+ return
105
+
106
+ self.history[session_id]["files"].append(file)
107
+
108
+ def get_files(self, session_id: str):
109
+ """
110
+ Retrieve the files for a session.
111
+
112
+ :param session_id: The session identifier.
113
+ :return: The files for the session.
114
+ """
115
+ if session_id not in self.history:
116
+ return []
117
+ files = []
118
+ current_time = time.time()
119
+ all_files = self.history.get(session_id)["files"].copy()
120
+ for file in all_files:
121
+ expiration_timestamp = file.get("expiration_timestamp")
122
+ if expiration_timestamp and current_time > expiration_timestamp:
123
+ self.history[session_id]["files"].remove(file)
124
+ continue
125
+ files.append(file)
126
+ return files
127
+
128
+ def clear_history(self, session_id: str, keep_levels=0):
129
+ """
130
+ Clear the history for a session, optionally keeping a specified number of recent entries.
131
+
132
+ :param session_id: The session identifier.
133
+ :param keep_levels: Number of most recent history entries to keep. Default is 0 (clear all).
134
+ """
135
+ if session_id in self.history:
136
+ if keep_levels <= 0:
137
+ del self.history[session_id]
138
+ else:
139
+ self.history[session_id]["history"] = self.history[session_id][
140
+ "history"
141
+ ][-keep_levels:]
142
+ # Recalculate the length and num_turns
143
+ self.history[session_id]["num_characters"] = sum(
144
+ len(str(entry)) for entry in self.history[session_id]["history"]
145
+ )
146
+ self.history[session_id]["num_turns"] = len(
147
+ self.history[session_id]["history"]
148
+ )
149
+
150
+ def get_session_meta(self, session_id: str):
151
+ """
152
+ Retrieve the session metadata.
153
+
154
+ :param session_id: The session identifier.
155
+ :return: The session metadata.
156
+ """
157
+ if session_id in self.history:
158
+ session = self.history[session_id]
159
+ return {
160
+ "num_characters": session["num_characters"],
161
+ "num_turns": session["num_turns"],
162
+ "last_active_time": session["last_active_time"],
163
+ }
164
+ return None
165
+
166
+ def get_all_sessions(self) -> list[str]:
167
+ return list(self.history.keys())