lionagi 0.0.201__py3-none-any.whl → 0.0.204__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. lionagi/_services/anthropic.py +79 -1
  2. lionagi/_services/base_service.py +1 -1
  3. lionagi/_services/services.py +61 -25
  4. lionagi/_services/transformers.py +46 -0
  5. lionagi/agents/__init__.py +0 -0
  6. lionagi/configs/oai_configs.py +1 -1
  7. lionagi/configs/openrouter_configs.py +1 -1
  8. lionagi/core/__init__.py +3 -7
  9. lionagi/core/branch/__init__.py +0 -0
  10. lionagi/core/branch/branch.py +589 -0
  11. lionagi/core/branch/branch_manager.py +139 -0
  12. lionagi/core/branch/cluster.py +1 -0
  13. lionagi/core/branch/conversation.py +484 -0
  14. lionagi/core/core_util.py +59 -0
  15. lionagi/core/flow/__init__.py +0 -0
  16. lionagi/core/flow/flow.py +19 -0
  17. lionagi/core/instruction_set/__init__.py +0 -0
  18. lionagi/core/instruction_set/instruction_set.py +343 -0
  19. lionagi/core/messages/__init__.py +0 -0
  20. lionagi/core/messages/messages.py +176 -0
  21. lionagi/core/sessions/__init__.py +0 -0
  22. lionagi/core/sessions/session.py +428 -0
  23. lionagi/models/__init__.py +0 -0
  24. lionagi/models/base_model.py +0 -0
  25. lionagi/models/imodel.py +53 -0
  26. lionagi/schema/data_logger.py +75 -155
  27. lionagi/tests/test_utils/test_call_util.py +658 -657
  28. lionagi/tools/tool_manager.py +121 -188
  29. lionagi/utils/__init__.py +5 -10
  30. lionagi/utils/call_util.py +667 -585
  31. lionagi/utils/io_util.py +3 -0
  32. lionagi/utils/nested_util.py +17 -211
  33. lionagi/utils/pd_util.py +57 -0
  34. lionagi/utils/sys_util.py +220 -184
  35. lionagi/utils/url_util.py +55 -0
  36. lionagi/version.py +1 -1
  37. {lionagi-0.0.201.dist-info → lionagi-0.0.204.dist-info}/METADATA +12 -8
  38. {lionagi-0.0.201.dist-info → lionagi-0.0.204.dist-info}/RECORD +47 -32
  39. lionagi/core/branch.py +0 -193
  40. lionagi/core/conversation.py +0 -341
  41. lionagi/core/flow.py +0 -8
  42. lionagi/core/instruction_set.py +0 -150
  43. lionagi/core/messages.py +0 -243
  44. lionagi/core/sessions.py +0 -474
  45. /lionagi/{tools → agents}/planner.py +0 -0
  46. /lionagi/{tools → agents}/prompter.py +0 -0
  47. /lionagi/{tools → agents}/scorer.py +0 -0
  48. /lionagi/{tools → agents}/summarizer.py +0 -0
  49. /lionagi/{tools → agents}/validator.py +0 -0
  50. /lionagi/core/{flow_util.py → flow/flow_util.py} +0 -0
  51. {lionagi-0.0.201.dist-info → lionagi-0.0.204.dist-info}/LICENSE +0 -0
  52. {lionagi-0.0.201.dist-info → lionagi-0.0.204.dist-info}/WHEEL +0 -0
  53. {lionagi-0.0.201.dist-info → lionagi-0.0.204.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,139 @@
1
+ from typing import Dict, Any
2
+ from collections import deque
3
+ from enum import Enum
4
+
5
+
6
+ class RequestTitle(str, Enum):
7
+ """
8
+ An enumeration of valid request titles for communication requests.
9
+
10
+ Attributes:
11
+ MESSAGES (str): Represents a request carrying messages.
12
+ TOOL (str): Represents a request for tool information or actions.
13
+ SERVICE (str): Represents a request related to services.
14
+ LLMCONFIG (str): Represents a request to configure or modify LLM settings.
15
+ """
16
+ MESSAGES = 'messages'
17
+ TOOL = 'tool'
18
+ SERVICE = 'service'
19
+ LLMCONFIG = 'llmconfig'
20
+
21
+
22
+ class Request:
23
+ """
24
+ Represents a request for communication between components in the system.
25
+
26
+ Args:
27
+ from_name (str): The name of the sender.
28
+ to_name (str): The name of the recipient.
29
+ title (Union[str, RequestTitle]): The title of the request, indicating its type or category.
30
+ request (Any): The actual content or data of the request.
31
+
32
+ Raises:
33
+ ValueError: If the request title is invalid or not recognized.
34
+ """
35
+
36
+ def __init__(self, from_name, to_name, title, request):
37
+ self.from_ = from_name
38
+ self.to_ = to_name
39
+ try:
40
+ if isinstance(title, str):
41
+ title = RequestTitle(title)
42
+ if isinstance(title, RequestTitle):
43
+ self.title = title
44
+ else:
45
+ raise ValueError(f'Invalid request title. Valid titles are {list(RequestTitle)}')
46
+ except:
47
+ raise ValueError(f'Invalid request title. Valid titles are {list(RequestTitle)}')
48
+ self.request = request
49
+
50
+
51
+ class BranchManager:
52
+ """
53
+ Manages branches and their communication requests within a system.
54
+
55
+ Args:
56
+ sources (Dict[str, Any]): Initial mapping of source names to their respective details.
57
+
58
+ Methods:
59
+ add_source: Adds a new source to the manager.
60
+ delete_source: Removes a source from the manager.
61
+ collect: Collects outgoing requests from a specified source and queues them for the recipient.
62
+ send: Sends the queued requests to their respective recipients in the system.
63
+ """
64
+
65
+ def __init__(self, sources: Dict[str, Any]):
66
+ self.sources = sources
67
+ self.requests = {}
68
+ for key in self.sources.keys():
69
+ self.requests[key] = {}
70
+
71
+ def add_source(self, sources: Dict[str, Any]):
72
+ """
73
+ Adds a new source or multiple sources to the manager.
74
+
75
+ Args:
76
+ sources (Dict[str, Any]): A dictionary mapping new source names to their details.
77
+
78
+ Raises:
79
+ ValueError: If any of the provided source names already exist.
80
+ """
81
+ for key in sources.keys():
82
+ if key in self.sources:
83
+ raise ValueError(f'{key} exists, please input a different name.')
84
+ self.sources[key] = {}
85
+
86
+ def delete_source(self, source_name):
87
+ """
88
+ Deletes a source from the manager by name.
89
+
90
+ Args:
91
+ source_name (str): The name of the source to delete.
92
+
93
+ Raises:
94
+ ValueError: If the specified source name does not exist.
95
+ """
96
+ if source_name not in self.sources:
97
+ raise ValueError(f'{source_name} does not exist.')
98
+ self.sources.pop(source_name)
99
+
100
+ def collect(self, from_name):
101
+ """
102
+ Collects all outgoing requests from a specified source.
103
+
104
+ Args:
105
+ from_name (str): The name of the source from which to collect outgoing requests.
106
+
107
+ Raises:
108
+ ValueError: If the specified source name does not exist.
109
+ """
110
+ if from_name not in self.sources:
111
+ raise ValueError(f'{from_name} does not exist.')
112
+ while self.sources[from_name].pending_outs:
113
+ request = self.sources[from_name].pending_outs.popleft()
114
+ if request.from_ not in self.requests[request.to_]:
115
+ self.requests[request.to_] = {request.from_: deque()}
116
+ self.requests[request.to_][request.from_].append(request)
117
+
118
+ def send(self, to_name):
119
+ """
120
+ Sends all queued requests to a specified recipient.
121
+
122
+ Args:
123
+ to_name (str): The name of the recipient to whom the requests should be sent.
124
+
125
+ Raises:
126
+ ValueError: If the specified recipient name does not exist or there are no requests to send.
127
+ """
128
+ if to_name not in self.sources:
129
+ raise ValueError(f'{to_name} does not exist.')
130
+ if not self.requests[to_name]:
131
+ return
132
+ else:
133
+ for key in list(self.requests[to_name].keys()):
134
+ request = self.requests[to_name].pop(key)
135
+ if key not in self.sources[to_name].pending_ins:
136
+ self.sources[to_name].pending_ins[key] = request
137
+ else:
138
+ self.sources[to_name].pending_ins[key].append(request)
139
+
@@ -0,0 +1 @@
1
+ # TODO
@@ -0,0 +1,484 @@
1
+ import json
2
+ import pandas as pd
3
+ from datetime import datetime
4
+ from typing import Any, Optional, Dict, Union
5
+ from lionagi.schema import DataLogger
6
+ from lionagi.utils import lcall, as_dict
7
+ from ..messages.messages import Message, System, Instruction, Response
8
+ from ..core_util import sign_message, validate_messages
9
+
10
+
11
+ class Conversation:
12
+ """
13
+ Represents a conversation with messages, tools, and instructions.
14
+
15
+ A `Conversation` is a container for messages exchanged in a conversation, as well as tools and instructions
16
+ for interacting with external services or tools.
17
+
18
+ Attributes:
19
+ messages (pd.DataFrame): A DataFrame containing conversation messages.
20
+ _logger (DataLogger): An instance of DataLogger for logging.
21
+ """
22
+
23
+ def __init__(self, dir: Optional[str] = None) -> None:
24
+ """
25
+ Initializes a Conversation object with an empty DataFrame for messages and a DataLogger.
26
+
27
+ Args:
28
+ dir (Optional[str]): The directory path for storing logs. Defaults to None.
29
+
30
+ Examples:
31
+ >>> conversation = Conversation(dir='logs/')
32
+ """
33
+ self.messages = pd.DataFrame(columns=["node_id", "role", "sender", "timestamp", "content"])
34
+ self.logger = DataLogger(dir=dir)
35
+
36
+ def _create_message(
37
+ self,
38
+ system: Optional[Union[dict, list, System]] = None,
39
+ instruction: Optional[Union[dict, list, Instruction]] = None,
40
+ context: Optional[Union[str, Dict[str, Any]]] = None,
41
+ response: Optional[Union[dict, list, Response]] = None,
42
+ sender: Optional[str] = None
43
+ ) -> Message:
44
+ """
45
+ Creates a Message object based on the given parameters.
46
+
47
+ Only one of `system`, `instruction`, or `response` can be provided to create a message.
48
+
49
+ Args:
50
+ system (Optional[Union[dict, list, System]]): The system message content.
51
+ instruction (Optional[Union[dict, list, Instruction]]): The instruction content.
52
+ context (Optional[Union[str, Dict[str, Any]]]): The context associated with the instruction.
53
+ response (Optional[Union[dict, list, Response]]): The response content.
54
+ sender (Optional[str]): The sender of the message.
55
+
56
+ Returns:
57
+ Message: A message object created from the provided parameters.
58
+
59
+ Raises:
60
+ ValueError: If more than one or none of the parameters (system, instruction, response) are provided.
61
+
62
+ Examples:
63
+ >>> conversation = Conversation()
64
+ >>> msg = conversation._create_message(system="System message", sender="system")
65
+ """
66
+
67
+ if sum(lcall([system, instruction, response], bool)) != 1:
68
+ raise ValueError("Error: Message must have one and only one role.")
69
+ else:
70
+ if isinstance(any([system, instruction, response]), Message):
71
+ if system:
72
+ return system
73
+ elif instruction:
74
+ return instruction
75
+ elif response:
76
+ return response
77
+
78
+ msg = 0
79
+ if response:
80
+ msg = Response(response=response, sender=sender)
81
+ elif instruction:
82
+ msg = Instruction(instruction=instruction,
83
+ context=context, sender=sender)
84
+ elif system:
85
+ msg = System(system=system, sender=sender)
86
+ return msg
87
+
88
+ def add_message(
89
+ self,
90
+ system: Optional[Union[dict, list, System]] = None,
91
+ instruction: Optional[Union[dict, list, Instruction]] = None,
92
+ context: Optional[Union[str, Dict[str, Any]]] = None,
93
+ response: Optional[Union[dict, list, Response]] = None,
94
+ sender: Optional[str] = None
95
+ ) -> None:
96
+ """
97
+ Creates and adds a new message to the conversation's messages DataFrame.
98
+ Args:
99
+ system (Optional[System]): Content for a system message.
100
+ instruction (Optional[Instruction]): Content for an instruction message.
101
+ context (Optional[Union[str, Dict[str, Any]]]): Context for the instruction message.
102
+ response (Optional[Response]): Content for a response message.
103
+ sender (Optional[str]): The sender of the message.
104
+
105
+ Examples:
106
+ >>> conversation = Conversation()
107
+ >>> conversation.add_message(instruction="What's the weather?", sender="user")
108
+ """
109
+ msg = self._create_message(
110
+ system=system, instruction=instruction,
111
+ context=context, response=response, sender=sender
112
+ )
113
+ message_dict = msg.to_dict()
114
+ if isinstance(as_dict(message_dict['content']), dict):
115
+ message_dict['content'] = json.dumps(message_dict['content'])
116
+ message_dict['timestamp'] = datetime.now()
117
+ self.messages.loc[len(self.messages)] = message_dict
118
+
119
+ @property
120
+ def last_row(self) -> pd.Series:
121
+ """
122
+ Retrieves the last row from the messages DataFrame.
123
+
124
+ Returns:
125
+ pd.Series: A Series object representing the last message.
126
+ """
127
+ return self.messages.iloc[-1]
128
+
129
+ @property
130
+ def first_system(self) -> pd.Series:
131
+ """
132
+ Retrieves the first system message from the messages DataFrame.
133
+
134
+ Returns:
135
+ pd.Series: A Series object representing the first system message.
136
+ """
137
+ return self.messages[self.messages.role == 'system'].iloc[0]
138
+
139
+ @property
140
+ def last_response(self) -> pd.Series:
141
+ """
142
+ Retrieves the last response message from the messages DataFrame.
143
+
144
+ Returns:
145
+ pd.Series: A Series object representing the last response message.
146
+ """
147
+ return self.get_last_rows(role='assistant')
148
+
149
+ @property
150
+ def last_instruction(self) -> pd.Series:
151
+ """
152
+ Retrieves the last instruction message from the messages DataFrame.
153
+
154
+ Returns:
155
+ pd.Series: A Series object representing the last instruction message.
156
+ """
157
+ return self.get_last_rows(role='user')
158
+
159
+ def get_last_rows(
160
+ self,
161
+ sender: Optional[str] = None,
162
+ role: Optional[str] = None,
163
+ n: int = 1,
164
+ sign_ = False
165
+ ) -> Union[pd.DataFrame, pd.Series]:
166
+ """
167
+ Retrieves the last n rows from the messages DataFrame filtered by sender or role.
168
+
169
+ Args:
170
+ sender (Optional[str]): The sender filter for the messages.
171
+ role (Optional[str]): The role filter for the messages.
172
+ n (int): The number of rows to retrieve.
173
+ sign_: If sign messages with a sender identifier.
174
+
175
+ Returns:
176
+ Union[pd.DataFrame, pd.Series]: The last n messages as a DataFrame or a single message as a Series.
177
+
178
+ Raises:
179
+ ValueError: If both sender and role are provided or if none is provided.
180
+ """
181
+
182
+ if sender is None and role is None:
183
+ outs = self.messages.iloc[-n:]
184
+ elif sender and role:
185
+ outs = self.messages[(self.messages['sender'] == sender) & (self.messages['role'] == role)].iloc[-n:]
186
+ elif sender:
187
+ outs = self.messages[self.messages['sender'] == sender].iloc[-n:]
188
+ else:
189
+ outs = self.messages[self.messages['role'] == role].iloc[-n:]
190
+
191
+ return sign_message(outs, sender) if sign_ else outs
192
+
193
+ def filter_messages_by(
194
+ self,
195
+ role: Optional[str] = None,
196
+ sender: Optional[str] = None,
197
+ start_time: Optional[datetime] = None,
198
+ end_time: Optional[datetime] = None,
199
+ content_keywords: Optional[Union[str, list]] = None,
200
+ case_sensitive: bool = False
201
+ ) -> pd.DataFrame:
202
+ """
203
+ Retrieves messages filtered by a specific criterion.
204
+
205
+ Args:
206
+ role (Optional[str]): The role to filter the messages.
207
+ sender (Optional[str]): The sender to filter the messages.
208
+ start_time (Optional[datetime]): The start time to filter the messages.
209
+ end_time (Optional[datetime]): The end time to filter the messages.
210
+ content_keywords (Optional[Union[str, list]]): The content to filter the messages.
211
+ case_sensitive (bool): Flag to indicate if the search should be case sensitive. Defaults to False.
212
+
213
+ Returns:
214
+ pd.DataFrame: A DataFrame containing filtered messages.
215
+
216
+ Raises:
217
+ ValueError: If more than one or none of the filtering criteria are provided.
218
+ """
219
+ outs = self.messages.copy()
220
+
221
+ if content_keywords:
222
+ outs = self.search_keywords(content_keywords, case_sensitive)
223
+
224
+ outs = outs[outs['role'] == role] if role else outs
225
+ outs = outs[outs['sender'] == sender] if sender else outs
226
+ outs = outs[outs['timestamp'] > start_time] if start_time else outs
227
+ outs = outs[outs['timestamp'] < end_time] if end_time else outs
228
+ return outs
229
+
230
+ def replace_keyword(
231
+ self,
232
+ keyword: str,
233
+ replacement: str,
234
+ case_sensitive: bool = False
235
+ ) -> None:
236
+ """
237
+ Replaces a keyword in the content of all messages with a replacement string.
238
+
239
+ Args:
240
+ keyword (str): The keyword to replace.
241
+ replacement (str): The string to replace the keyword with.
242
+ case_sensitive (bool, optional): Flag to indicate if the replacement should be case sensitive. Defaults to False.
243
+ """
244
+ if not case_sensitive:
245
+ self.messages["content"] = self.messages["content"].str.replace(
246
+ keyword, replacement, case=False
247
+ )
248
+ else:
249
+ self.messages["content"] = self.messages["content"].str.replace(
250
+ keyword, replacement
251
+ )
252
+
253
+ def search_keywords(
254
+ self,
255
+ keywords: Union[str, list],
256
+ case_sensitive: bool = False
257
+ ) -> pd.DataFrame:
258
+ """
259
+ Searches for a keyword in the content of all messages and returns the messages containing it.
260
+
261
+ Args:
262
+ keywords (str): The keywords to search for.
263
+ case_sensitive (bool, optional): Flag to indicate if the search should be case sensitive. Defaults to False.
264
+
265
+ Returns:
266
+ pd.DataFrame: A DataFrame containing messages with the specified keyword.
267
+ """
268
+ if isinstance(keywords, list):
269
+ keywords = '|'.join(keywords)
270
+ if not case_sensitive:
271
+ return self.messages[
272
+ self.messages["content"].str.contains(keywords, case=False)
273
+ ]
274
+ return self.messages[self.messages["content"].str.contains(keywords)]
275
+
276
+ def remove_from_messages(self, message_id: str) -> bool:
277
+ """
278
+ Removes a message from the conversation based on its message ID.
279
+
280
+ Args:
281
+ message_id (str): The ID of the message to be removed.
282
+
283
+ Returns:
284
+ bool: True if the message was successfully removed, False otherwise.
285
+ """
286
+ initial_length = len(self.messages)
287
+ self.messages = self.messages[self.messages["node_id"] != message_id]
288
+ return len(self.messages) < initial_length
289
+
290
+ def update_messages_content(
291
+ self,
292
+ message_id: str,
293
+ col: str,
294
+ value: Any
295
+ ) -> bool:
296
+ """
297
+ Updates the content of a specific message in the conversation.
298
+
299
+ Args:
300
+ message_id (str): The ID of the message to be updated.
301
+ col (str): The column of the message that needs to be updated.
302
+ value (Any): The new value to be set for the specified column.
303
+
304
+ Returns:
305
+ bool: True if the update was successful, False otherwise.
306
+
307
+ Examples:
308
+ >>> conversation = Conversation()
309
+ >>> conversation.add_message(system="Initial message", sender="system")
310
+ >>> success = conversation.update_messages_content(
311
+ ... message_id="1", col="content", value="Updated message")
312
+ """
313
+ index = self.messages.index[self.messages["id_"] == message_id].tolist()
314
+ if index:
315
+ self.messages.at[index[0], col] = value
316
+ return True
317
+ return False
318
+
319
+ def info(self, use_sender: bool = False) -> Dict[str, int]:
320
+ """
321
+ Provides a summary of the conversation messages.
322
+
323
+ Args:
324
+ use_sender (bool, optional): Determines whether to summarize by sender or by role. Defaults to False.
325
+
326
+ Returns:
327
+ Dict[str, int]: A dictionary containing counts of messages either by role or sender.
328
+ """
329
+ messages = self.messages['sender'] if use_sender else self.messages['role']
330
+ result = messages.value_counts().to_dict()
331
+ result['total'] = len(self.messages)
332
+ return result
333
+
334
+ @property
335
+ def describe(self) -> Dict[str, Any]:
336
+ """
337
+ Describes the conversation with various statistics and information.
338
+
339
+ Returns:
340
+ Dict[str, Any]: A dictionary containing information such as total number of messages, summary by role,
341
+ and individual messages.
342
+ """
343
+ return {
344
+ "total_messages": len(self.messages),
345
+ "summary_by_role": self.info(),
346
+ "messages": [
347
+ msg.to_dict() for _, msg in self.messages.iterrows()
348
+ ],
349
+ }
350
+
351
+ def history(
352
+ self, begin_: Optional[datetime] = None, end_: Optional[datetime] = None
353
+ ) -> pd.DataFrame:
354
+ """
355
+ Retrieves a history of messages within a specified date range.
356
+
357
+ Args:
358
+ begin_ (Optional[datetime], optional): The start date of the message history. Defaults to None.
359
+ end_ (Optional[datetime], optional): The end date of the message history. Defaults to None.
360
+
361
+ Returns:
362
+ pd.DataFrame: A DataFrame containing messages within the specified date range.
363
+ """
364
+
365
+ if isinstance(begin_, str):
366
+ begin_ = datetime.strptime(begin_, '%Y-%m-%d')
367
+ if isinstance(end_, str):
368
+ end_ = datetime.strptime(end_, '%Y-%m-%d')
369
+ if begin_ and end_:
370
+ return self.messages[
371
+ (self.messages["timestamp"].dt.date >= begin_.date())
372
+ & (self.messages["timestamp"].dt.date <= end_.date())
373
+ ]
374
+ elif begin_:
375
+ return self.messages[(self.messages["timestamp"].dt.date >= begin_.date())]
376
+ elif end_:
377
+ return self.messages[(self.messages["timestamp"].dt.date <= end_.date())]
378
+ return self.messages
379
+
380
+ def clone(self) -> 'Conversation':
381
+ """
382
+ Creates a clone of the current conversation.
383
+
384
+ Returns:
385
+ Conversation: A new Conversation object that is a clone of the current conversation.
386
+ """
387
+ cloned = Conversation()
388
+ cloned.logger.set_dir(self.logger.dir)
389
+ cloned.messages = self.messages.copy()
390
+ return cloned
391
+
392
+ # def merge_conversation(self, other: 'Conversation', update: bool = False,) -> None:
393
+ # """
394
+ # Merges another conversation into the current one.
395
+ #
396
+ # Args:
397
+ # other (Conversation): The other conversation to merge with the current one.
398
+ # update (bool, optional): If True, updates the first system message before merging. Defaults to False.
399
+ # """
400
+ # if update:
401
+ # self.first_system = other.first_system.copy()
402
+ # df = pd.concat([self.messages.copy(), other.messages.copy()], ignore_index=True)
403
+ # self.messages = df.drop_duplicates().reset_index(drop=True, inplace=True)
404
+
405
+ def rollback(self, steps: int) -> None:
406
+ """
407
+ Rollbacks the conversation by a specified number of steps (messages).
408
+
409
+ Args:
410
+ steps (int): The number of steps to rollback.
411
+
412
+ Raises:
413
+ ValueError: If steps are not a non-negative integer or greater than the number of messages.
414
+ """
415
+ if steps < 0 or steps > len(self.messages):
416
+ raise ValueError("Steps must be a non-negative integer less than or equal to the number of messages.")
417
+ self.messages = self.messages[:-steps].reset_index(drop=True)
418
+
419
+ def reset(self) -> None:
420
+ """
421
+ Resets the conversation, clearing all messages.
422
+ """
423
+ self.messages = pd.DataFrame(columns=self.messages.columns)
424
+
425
+ def to_csv(self, filepath: str, **kwargs) -> None:
426
+ """
427
+ Exports the conversation messages to a CSV file.
428
+
429
+ Args:
430
+ filepath (str): The file path where the CSV will be saved.
431
+ **kwargs: Additional keyword arguments for `pandas.DataFrame.to_csv` method.
432
+ """
433
+ self.messages.to_csv(filepath, **kwargs)
434
+
435
+ def from_csv(self, filepath: str, **kwargs) -> None:
436
+ """
437
+ Imports conversation messages from a CSV file.
438
+
439
+ Args:
440
+ filepath (str): The file path of the CSV to be read.
441
+ **kwargs: Additional keyword arguments for `pandas.read_csv` method.
442
+ """
443
+ self.messages = pd.read_csv(filepath, **kwargs)
444
+
445
+ def to_json(self, filepath: str) -> None:
446
+ """
447
+ Exports the conversation messages to a JSON file.
448
+
449
+ Args:
450
+ filepath (str): The file path where the JSON will be saved.
451
+ """
452
+ self.messages.to_json(
453
+ filepath, orient="records", lines=True, date_format="iso")
454
+
455
+ def from_json(self, filepath: str) -> None:
456
+ """
457
+ Imports conversation messages from a JSON file.
458
+
459
+ Args:
460
+ filepath (str): The file path of the JSON to be read.
461
+ """
462
+ self.reset()
463
+ self.messages = pd.read_json(filepath, orient="records", lines=True)
464
+
465
+ def extend(self, messages: pd.DataFrame, **kwargs) -> None:
466
+ """
467
+ Extends the current conversation messages with additional messages from a DataFrame.
468
+
469
+ Args:
470
+ messages (pd.DataFrame): The DataFrame containing messages to be added to the conversation.
471
+ kwargs: for pd.df.drop_duplicates
472
+ """
473
+
474
+ validate_messages(messages)
475
+ try:
476
+ if len(messages.dropna(how='all')) > 0 and len(self.messages.dropna(how='all')) > 0:
477
+ self.messages = pd.concat([self.messages, messages], ignore_index=True)
478
+ self.messages.drop_duplicates(
479
+ inplace=True, subset=['node_id'], keep='first', **kwargs
480
+ )
481
+ self.messages.reset_index(drop=True, inplace=True)
482
+ return
483
+ except Exception as e:
484
+ raise ValueError(f"Error in extending messages: {e}")
@@ -0,0 +1,59 @@
1
+ import json
2
+ from ..utils.sys_util import strip_lower
3
+
4
+
5
+ def sign_message(messages, sender: str):
6
+ """
7
+ Sign messages with a sender identifier.
8
+
9
+ Args:
10
+ messages (pd.DataFrame): A DataFrame containing messages with columns 'node_id', 'role', 'sender', 'timestamp', and 'content'.
11
+ sender (str): The sender identifier to be added to the messages.
12
+
13
+ Returns:
14
+ pd.DataFrame: A new DataFrame with the sender identifier added to each message.
15
+
16
+ Raises:
17
+ ValueError: If the 'sender' is None or 'None'.
18
+ """
19
+ if sender is None or strip_lower(sender) == 'none':
20
+ raise ValueError("sender cannot be None")
21
+ df = messages.copy()
22
+
23
+ for i in df.index:
24
+ if not df.loc[i, 'content'].startswith('Sender'):
25
+ df.loc[i, 'content'] = f"Sender {sender}: {df.loc[i, 'content']}"
26
+ else:
27
+ content = df.loc[i, 'content'].split(':', 1)[1]
28
+ df.loc[i, 'content'] = f"Sender {sender}: {content}"
29
+ return df
30
+
31
+
32
+ def validate_messages(messages):
33
+ """
34
+ Validate the structure and content of a messages DataFrame.
35
+
36
+ Args:
37
+ messages (pd.DataFrame): A DataFrame containing messages with columns 'node_id', 'role', 'sender', 'timestamp', and 'content'.
38
+
39
+ Returns:
40
+ bool: True if the messages DataFrame is valid; otherwise, raises a ValueError.
41
+
42
+ Raises:
43
+ ValueError: If the DataFrame structure is invalid or if it contains null values, roles other than ["system", "user", "assistant"],
44
+ or content that cannot be parsed as JSON strings.
45
+ """
46
+ if list(messages.columns) != ['node_id', 'role', 'sender', 'timestamp', 'content']:
47
+ raise ValueError('Invalid messages dataframe. Unmatched columns.')
48
+ if messages.isnull().values.any():
49
+ raise ValueError('Invalid messages dataframe. Cannot have null.')
50
+ if not all(role in ['system', 'user', 'assistant'] for role in messages['role'].unique()):
51
+ raise ValueError('Invalid messages dataframe. Cannot have role other than ["system", "user", "assistant"].')
52
+ for cont in messages['content']:
53
+ if cont.startswith('Sender'):
54
+ cont = cont.split(':', 1)[1]
55
+ try:
56
+ json.loads(cont)
57
+ except:
58
+ raise ValueError('Invalid messages dataframe. Content expect json string.')
59
+ return True
File without changes