lionagi 0.0.206__py3-none-any.whl → 0.0.208__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.
- lionagi/_services/ollama.py +2 -2
- lionagi/core/branch/branch.py +517 -265
- lionagi/core/branch/branch_manager.py +0 -1
- lionagi/core/branch/conversation.py +640 -337
- lionagi/core/core_util.py +0 -59
- lionagi/core/sessions/session.py +137 -64
- lionagi/tools/tool_manager.py +39 -62
- lionagi/utils/__init__.py +3 -2
- lionagi/utils/call_util.py +9 -7
- lionagi/utils/sys_util.py +287 -255
- lionagi/version.py +1 -1
- {lionagi-0.0.206.dist-info → lionagi-0.0.208.dist-info}/METADATA +1 -1
- {lionagi-0.0.206.dist-info → lionagi-0.0.208.dist-info}/RECORD +16 -17
- lionagi/utils/pd_util.py +0 -57
- {lionagi-0.0.206.dist-info → lionagi-0.0.208.dist-info}/LICENSE +0 -0
- {lionagi-0.0.206.dist-info → lionagi-0.0.208.dist-info}/WHEEL +0 -0
- {lionagi-0.0.206.dist-info → lionagi-0.0.208.dist-info}/top_level.txt +0 -0
@@ -2,483 +2,786 @@ import json
|
|
2
2
|
import pandas as pd
|
3
3
|
from datetime import datetime
|
4
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
5
|
|
6
|
+
from lionagi.utils.sys_util import as_dict, create_copy, strip_lower, to_df
|
7
|
+
from lionagi.utils.call_util import lcall
|
8
|
+
from ..messages.messages import Message, System, Instruction, Response
|
10
9
|
|
11
10
|
class Conversation:
|
12
11
|
"""
|
13
|
-
|
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
|
-
|
12
|
+
A class to represent a conversation, encapsulating messages within a pandas DataFrame.
|
13
|
+
|
18
14
|
Attributes:
|
19
|
-
messages (pd.DataFrame): A DataFrame
|
20
|
-
_logger (DataLogger): An instance of DataLogger for logging.
|
15
|
+
messages (pd.DataFrame): A DataFrame holding conversation messages with columns specified in _cols.
|
21
16
|
"""
|
17
|
+
|
18
|
+
_cols = ["node_id", "role", "sender", "timestamp", "content"]
|
22
19
|
|
23
|
-
def __init__(self
|
20
|
+
def __init__(self) -> None:
|
24
21
|
"""
|
25
|
-
Initializes a Conversation
|
26
|
-
|
27
|
-
Args:
|
28
|
-
dir (Optional[str]): The directory path for storing logs. Defaults to None.
|
29
|
-
|
30
|
-
Examples:
|
31
|
-
>>> conversation = Conversation(dir='logs/')
|
22
|
+
Initializes a Conversation instance with an empty DataFrame structured to hold messages.
|
32
23
|
"""
|
33
|
-
self.messages = pd.DataFrame(columns=
|
34
|
-
self.logger = DataLogger(dir=dir)
|
24
|
+
self.messages = pd.DataFrame(columns=Conversation._cols)
|
35
25
|
|
36
|
-
|
37
|
-
|
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:
|
26
|
+
@classmethod
|
27
|
+
def from_csv(cls, filepath: str, **kwargs) -> 'Conversation':
|
44
28
|
"""
|
45
|
-
|
46
|
-
|
47
|
-
Only one of `system`, `instruction`, or `response` can be provided to create a message.
|
29
|
+
Create a Conversation instance from a CSV file containing messages.
|
48
30
|
|
49
31
|
Args:
|
50
|
-
|
51
|
-
|
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.
|
32
|
+
filepath (str): The path to the CSV file to be loaded.
|
33
|
+
**kwargs: Additional keyword arguments passed to pandas.read_csv function.
|
55
34
|
|
56
35
|
Returns:
|
57
|
-
|
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")
|
36
|
+
Conversation: An instance of Conversation with messages loaded from the specified CSV file.
|
65
37
|
"""
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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:
|
38
|
+
messages = pd.read_csv(filepath)
|
39
|
+
messages = to_df(messages)
|
40
|
+
self = cls(messages=messages, **kwargs)
|
41
|
+
return self
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def from_json(cls, filepath: str, **kwargs) -> 'Conversation':
|
96
45
|
"""
|
97
|
-
|
46
|
+
Create a Conversation instance from a JSON file containing messages.
|
47
|
+
|
98
48
|
Args:
|
99
|
-
|
100
|
-
|
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.
|
49
|
+
filepath (str): The path to the JSON file to be loaded.
|
50
|
+
**kwargs: Additional keyword arguments passed to pandas.read_json function.
|
104
51
|
|
105
|
-
|
106
|
-
|
107
|
-
>>> conversation.add_message(instruction="What's the weather?", sender="user")
|
52
|
+
Returns:
|
53
|
+
Conversation: An instance of Conversation with messages loaded from the specified JSON file.
|
108
54
|
"""
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
55
|
+
messages = pd.read_json(filepath, orient="records", lines=True)
|
56
|
+
messages = to_df(messages)
|
57
|
+
self = cls(messages=messages, **kwargs)
|
58
|
+
return self
|
59
|
+
|
119
60
|
@property
|
120
61
|
def last_row(self) -> pd.Series:
|
121
62
|
"""
|
122
|
-
|
63
|
+
Retrieve the last row from the conversation messages as a pandas Series.
|
123
64
|
|
124
65
|
Returns:
|
125
|
-
pd.Series:
|
66
|
+
pd.Series: The last message in the conversation.
|
126
67
|
"""
|
127
|
-
return self.messages
|
68
|
+
return get_rows(self.messages, n=1, from_='last')
|
128
69
|
|
129
70
|
@property
|
130
71
|
def first_system(self) -> pd.Series:
|
131
72
|
"""
|
132
|
-
|
73
|
+
Retrieve the first system message from the conversation.
|
133
74
|
|
134
75
|
Returns:
|
135
|
-
pd.Series:
|
76
|
+
pd.Series: The first message in the conversation where the role is 'system'.
|
136
77
|
"""
|
137
|
-
return self.messages
|
78
|
+
return get_rows(self.messages, role='system', n=1, from_='front')
|
138
79
|
|
139
80
|
@property
|
140
81
|
def last_response(self) -> pd.Series:
|
141
82
|
"""
|
142
|
-
|
83
|
+
Retrieve the last response message from the conversation.
|
143
84
|
|
144
85
|
Returns:
|
145
|
-
pd.Series:
|
86
|
+
pd.Series: The last message in the conversation where the role is 'assistant'.
|
146
87
|
"""
|
147
|
-
return self.
|
148
|
-
|
88
|
+
return get_rows(self.messages, role='assistant', n=1, from_='last')
|
89
|
+
|
149
90
|
@property
|
150
|
-
def
|
91
|
+
def last_response_content(self) -> Dict:
|
151
92
|
"""
|
152
|
-
|
93
|
+
Retrieve the last response message content from the conversation.
|
153
94
|
|
154
95
|
Returns:
|
155
|
-
pd.Series:
|
96
|
+
pd.Series: The last message in the conversation where the role is 'assistant'.
|
156
97
|
"""
|
157
|
-
return self.
|
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.
|
98
|
+
return as_dict(self.last_response.content.iloc[-1])
|
168
99
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
sign_: If sign messages with a sender identifier.
|
100
|
+
@property
|
101
|
+
def last_instruction(self) -> pd.Series:
|
102
|
+
"""
|
103
|
+
Retrieve the last instruction message from the conversation.
|
174
104
|
|
175
105
|
Returns:
|
176
|
-
|
177
|
-
|
178
|
-
Raises:
|
179
|
-
ValueError: If both sender and role are provided or if none is provided.
|
106
|
+
pd.Series: The last message in the conversation where the role is 'user'.
|
180
107
|
"""
|
108
|
+
return get_rows(self.messages, role='user', n=1, from_='last')
|
181
109
|
|
182
|
-
|
183
|
-
|
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:
|
110
|
+
@property
|
111
|
+
def last_action_request(self):
|
202
112
|
"""
|
203
|
-
|
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.
|
113
|
+
Retrieve the last action request message from the conversation.
|
212
114
|
|
213
115
|
Returns:
|
214
|
-
pd.
|
215
|
-
|
216
|
-
Raises:
|
217
|
-
ValueError: If more than one or none of the filtering criteria are provided.
|
116
|
+
pd.Series: The last message in the conversation with sender 'action_request'.
|
218
117
|
"""
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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:
|
118
|
+
return get_rows(self.messages, sender='action_request', n=1, from_='last')
|
119
|
+
|
120
|
+
@property
|
121
|
+
def last_action_response(self):
|
236
122
|
"""
|
237
|
-
|
123
|
+
Retrieve the last action response message from the conversation.
|
238
124
|
|
239
|
-
|
240
|
-
|
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.
|
125
|
+
Returns:
|
126
|
+
pd.Series: The last message in the conversation with sender 'action_response'.
|
243
127
|
"""
|
244
|
-
|
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
|
-
)
|
128
|
+
return get_rows(self.messages, sender='action_response', n=1, from_='last')
|
252
129
|
|
253
|
-
|
254
|
-
|
255
|
-
keywords: Union[str, list],
|
256
|
-
case_sensitive: bool = False
|
257
|
-
) -> pd.DataFrame:
|
130
|
+
@property
|
131
|
+
def len_messages(self):
|
258
132
|
"""
|
259
|
-
|
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.
|
133
|
+
Get the total number of messages in the conversation.
|
264
134
|
|
265
135
|
Returns:
|
266
|
-
|
136
|
+
int: The total number of messages.
|
267
137
|
"""
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
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:
|
138
|
+
return len(self.messages)
|
139
|
+
|
140
|
+
@property
|
141
|
+
def len_instructions(self):
|
277
142
|
"""
|
278
|
-
|
279
|
-
|
280
|
-
Args:
|
281
|
-
message_id (str): The ID of the message to be removed.
|
143
|
+
Get the total number of instruction messages (messages with role 'user') in the conversation.
|
282
144
|
|
283
145
|
Returns:
|
284
|
-
|
146
|
+
int: The total number of instruction messages.
|
285
147
|
"""
|
286
|
-
|
287
|
-
|
288
|
-
|
148
|
+
return len(self.messages[self.messages.role == 'user'])
|
149
|
+
|
150
|
+
@property
|
151
|
+
def len_responses(self):
|
152
|
+
"""
|
153
|
+
Get the total number of response messages (messages with role 'assistant') in the conversation.
|
289
154
|
|
290
|
-
|
291
|
-
|
292
|
-
message_id: str,
|
293
|
-
col: str,
|
294
|
-
value: Any
|
295
|
-
) -> bool:
|
155
|
+
Returns:
|
156
|
+
int: The total number of response messages.
|
296
157
|
"""
|
297
|
-
Updates the content of a specific message in the conversation.
|
298
158
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
159
|
+
return len(self.messages[self.messages.role == 'assistant'])
|
160
|
+
|
161
|
+
@property
|
162
|
+
def len_systems(self):
|
163
|
+
"""
|
164
|
+
Get the total number of system messages (messages with role 'system') in the conversation.
|
303
165
|
|
304
166
|
Returns:
|
305
|
-
|
167
|
+
int: The total number of system messages.
|
168
|
+
"""
|
169
|
+
return len(self.messages[self.messages.role == 'system'])
|
306
170
|
|
307
|
-
|
308
|
-
|
309
|
-
>>> conversation.add_message(system="Initial message", sender="system")
|
310
|
-
>>> success = conversation.update_messages_content(
|
311
|
-
... message_id="1", col="content", value="Updated message")
|
171
|
+
@property
|
172
|
+
def info(self):
|
312
173
|
"""
|
313
|
-
|
314
|
-
if index:
|
315
|
-
self.messages.at[index[0], col] = value
|
316
|
-
return True
|
317
|
-
return False
|
174
|
+
Get a summary of the conversation messages categorized by role.
|
318
175
|
|
319
|
-
|
176
|
+
Returns:
|
177
|
+
Dict[str, int]: A dictionary with keys as message roles and values as counts.
|
320
178
|
"""
|
321
|
-
Provides a summary of the conversation messages.
|
322
179
|
|
323
|
-
|
324
|
-
|
180
|
+
return self._info()
|
181
|
+
|
182
|
+
@property
|
183
|
+
def sender_info(self):
|
184
|
+
"""
|
185
|
+
Provides a descriptive summary of the conversation, including the total number of messages,
|
186
|
+
a summary by role, and the first five messages.
|
325
187
|
|
326
188
|
Returns:
|
327
|
-
Dict[str,
|
189
|
+
Dict[str, Any]: A dictionary containing the total number of messages, summary by role,
|
190
|
+
and a list of the first five message dictionaries.
|
328
191
|
"""
|
329
|
-
|
330
|
-
|
331
|
-
result['total'] = len(self.messages)
|
332
|
-
return result
|
333
|
-
|
192
|
+
return self._info(use_sender=True)
|
193
|
+
|
334
194
|
@property
|
335
195
|
def describe(self) -> Dict[str, Any]:
|
336
196
|
"""
|
337
|
-
|
197
|
+
Provides a descriptive summary of the conversation, including the total number of messages,
|
198
|
+
a summary by role, and the first five messages.
|
338
199
|
|
339
200
|
Returns:
|
340
|
-
Dict[str, Any]: A dictionary containing
|
341
|
-
and individual messages.
|
201
|
+
Dict[str, Any]: A dictionary containing the total number of messages, summary by role, and a list of the first maximum five message dictionaries.
|
342
202
|
"""
|
343
203
|
return {
|
344
204
|
"total_messages": len(self.messages),
|
345
|
-
"summary_by_role": self.
|
205
|
+
"summary_by_role": self._info(),
|
346
206
|
"messages": [
|
347
207
|
msg.to_dict() for _, msg in self.messages.iterrows()
|
348
|
-
],
|
208
|
+
][: self.len_messages -1 if self.len_messages < 5 else 5],
|
349
209
|
}
|
350
210
|
|
351
|
-
def
|
352
|
-
self, begin_: Optional[datetime] = None, end_: Optional[datetime] = None
|
353
|
-
) -> pd.DataFrame:
|
211
|
+
def clone(self, num: Optional[int] = None) -> 'Conversation':
|
354
212
|
"""
|
355
|
-
|
213
|
+
Creates a copy or multiple copies of the current Conversation instance.
|
356
214
|
|
357
215
|
Args:
|
358
|
-
|
359
|
-
|
216
|
+
num (Optional[int], optional): The number of copies to create. If None, a single copy is created.
|
217
|
+
Defaults to None.
|
360
218
|
|
361
219
|
Returns:
|
362
|
-
|
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.
|
220
|
+
Conversation: A new Conversation instance or a list of Conversation instances if num is specified.
|
386
221
|
"""
|
387
222
|
cloned = Conversation()
|
388
223
|
cloned.logger.set_dir(self.logger.dir)
|
389
224
|
cloned.messages = self.messages.copy()
|
225
|
+
if num:
|
226
|
+
return create_copy(cloned, num=num)
|
390
227
|
return cloned
|
391
228
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
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:
|
229
|
+
def add_message(
|
230
|
+
self,
|
231
|
+
system: Optional[Union[dict, list, System]] = None,
|
232
|
+
instruction: Optional[Union[dict, list, Instruction]] = None,
|
233
|
+
context: Optional[Union[str, Dict[str, Any]]] = None,
|
234
|
+
response: Optional[Union[dict, list, Response]] = None,
|
235
|
+
sender: Optional[str] = None
|
236
|
+
) -> None:
|
406
237
|
"""
|
407
|
-
|
238
|
+
Adds a message to the conversation.
|
408
239
|
|
409
240
|
Args:
|
410
|
-
|
241
|
+
system (Optional[Union[dict, list, System]], optional): System message content or object.
|
242
|
+
instruction (Optional[Union[dict, list, Instruction]], optional): Instruction message content or object.
|
243
|
+
context (Optional[Union[str, Dict[str, Any]]], optional): Context for the message.
|
244
|
+
response (Optional[Union[dict, list, Response]], optional): Response message content or object.
|
245
|
+
sender (Optional[str], optional): The sender of the message.
|
411
246
|
|
412
247
|
Raises:
|
413
|
-
ValueError: If
|
248
|
+
ValueError: If the content cannot be converted to a JSON string.
|
414
249
|
"""
|
415
|
-
|
416
|
-
|
417
|
-
|
250
|
+
msg = self._create_message(
|
251
|
+
system=system, instruction=instruction,
|
252
|
+
context=context, response=response, sender=sender
|
253
|
+
)
|
254
|
+
message_dict = msg.to_dict()
|
255
|
+
if isinstance(as_dict(message_dict['content']), dict):
|
256
|
+
message_dict['content'] = json.dumps(message_dict['content'])
|
257
|
+
message_dict['timestamp'] = datetime.now().isoformat()
|
258
|
+
self.messages.loc[len(self.messages)] = message_dict
|
259
|
+
|
260
|
+
def remove_message(self, node_id: str) -> None:
|
261
|
+
"""
|
262
|
+
Removes a message from the conversation based on its node_id.
|
418
263
|
|
419
|
-
|
264
|
+
Args:
|
265
|
+
node_id (str): The node_id of the message to be removed.
|
420
266
|
"""
|
421
|
-
|
267
|
+
_remove_message(self.messages, node_id)
|
268
|
+
|
269
|
+
def update_message(
|
270
|
+
self, value: Any, node_id: Optional[str] = None, col: str = 'node_id'
|
271
|
+
) -> None:
|
422
272
|
"""
|
423
|
-
|
273
|
+
Updates a message in the conversation based on its node_id.
|
424
274
|
|
425
|
-
|
275
|
+
Args:
|
276
|
+
value (Any): The new value to update the message with.
|
277
|
+
node_id (Optional[str], optional): The node_id of the message to be updated. Defaults to None.
|
278
|
+
col (str, optional): The column to be updated. Defaults to 'node_id'.
|
279
|
+
|
280
|
+
Returns:
|
281
|
+
bool: True if the update was successful, False otherwise.
|
426
282
|
"""
|
427
|
-
|
283
|
+
return _update_row(self.messages, node_id=node_id, col=col, value=value)
|
284
|
+
|
285
|
+
def change_first_system_message(
|
286
|
+
self, system: Union[str, Dict[str, Any], System], sender: Optional[str] = None
|
287
|
+
):
|
288
|
+
"""
|
289
|
+
Updates the first system message in the conversation.
|
428
290
|
|
429
291
|
Args:
|
430
|
-
|
431
|
-
|
292
|
+
system (Union[str, Dict[str, Any], System]): The new system message content, which can be a string,
|
293
|
+
a dictionary of message content, or a System object.
|
294
|
+
sender (Optional[str], optional): The sender of the system message. Defaults to None.
|
295
|
+
|
296
|
+
Raises:
|
297
|
+
ValueError: If there are no system messages in the conversation or if the input cannot be
|
298
|
+
converted into a system message.
|
432
299
|
"""
|
433
|
-
self.
|
300
|
+
if self.len_systems == 0:
|
301
|
+
raise ValueError("There is no system message in the messages.")
|
302
|
+
|
303
|
+
if not isinstance(system, (str, Dict, System)):
|
304
|
+
raise ValueError("Input cannot be converted into a system message.")
|
305
|
+
|
306
|
+
elif isinstance(system, (str, Dict)):
|
307
|
+
system = System(system, sender=sender)
|
308
|
+
|
309
|
+
elif isinstance(system, System):
|
310
|
+
message_dict = system.to_dict()
|
311
|
+
if sender:
|
312
|
+
message_dict['sender'] = sender
|
313
|
+
message_dict['timestamp'] = datetime.now().isoformat()
|
314
|
+
sys_index = self.messages[self.messages.role == 'system'].index
|
315
|
+
self.messages.loc[sys_index[0]] = message_dict
|
434
316
|
|
435
|
-
def
|
317
|
+
def rollback(self, steps: int) -> None:
|
318
|
+
"""
|
319
|
+
Removes the last 'n' messages from the conversation.
|
320
|
+
|
321
|
+
Args:
|
322
|
+
steps (int): The number of messages to remove from the end of the conversation.
|
323
|
+
|
324
|
+
Raises:
|
325
|
+
ValueError: If 'steps' is not a positive integer or exceeds the number of messages.
|
326
|
+
"""
|
327
|
+
return _remove_last_n_rows(self.messages, steps)
|
328
|
+
|
329
|
+
def clear_messages(self) -> None:
|
330
|
+
"""
|
331
|
+
Clears all messages from the conversation, resetting it to an empty state.
|
332
|
+
"""
|
333
|
+
self.messages = pd.DataFrame(columns=Conversation._cols)
|
334
|
+
|
335
|
+
def to_csv(self, filepath: str, **kwargs) -> None:
|
436
336
|
"""
|
437
|
-
|
337
|
+
Exports the conversation messages to a CSV file.
|
438
338
|
|
439
339
|
Args:
|
440
|
-
filepath (str): The
|
441
|
-
**kwargs: Additional keyword arguments
|
340
|
+
filepath (str): The path to the file where the CSV will be saved.
|
341
|
+
**kwargs: Additional keyword arguments passed to pandas.DataFrame.to_csv() method.
|
442
342
|
"""
|
443
|
-
self.messages
|
343
|
+
self.messages.to_csv(filepath, **kwargs)
|
444
344
|
|
445
345
|
def to_json(self, filepath: str) -> None:
|
446
346
|
"""
|
447
347
|
Exports the conversation messages to a JSON file.
|
448
348
|
|
449
349
|
Args:
|
450
|
-
filepath (str): The file
|
350
|
+
filepath (str): The path to the file where the JSON will be saved.
|
351
|
+
**kwargs: Additional keyword arguments passed to pandas.DataFrame.to_json() method, such as
|
352
|
+
'orient', 'lines', and 'date_format'.
|
353
|
+
|
354
|
+
Note:
|
355
|
+
The recommended kwargs for compatibility with the from_json class method are
|
356
|
+
orient='records', lines=True, and date_format='iso'.
|
451
357
|
"""
|
452
358
|
self.messages.to_json(
|
453
359
|
filepath, orient="records", lines=True, date_format="iso")
|
454
360
|
|
455
|
-
def
|
361
|
+
def replace_keyword(
|
362
|
+
self,
|
363
|
+
keyword: str,
|
364
|
+
replacement: str,
|
365
|
+
col: str = 'content',
|
366
|
+
case_sensitive: bool = False
|
367
|
+
) -> None:
|
456
368
|
"""
|
457
|
-
|
369
|
+
Replaces all occurrences of a keyword in a specified column of the conversation's messages with a given replacement.
|
458
370
|
|
459
371
|
Args:
|
460
|
-
|
372
|
+
keyword (str): The keyword to be replaced.
|
373
|
+
replacement (str): The string to replace the keyword with.
|
374
|
+
col (str, optional): The column where the replacement should occur. Defaults to 'content'.
|
375
|
+
case_sensitive (bool, optional): If True, the replacement is case sensitive. Defaults to False.
|
461
376
|
"""
|
462
|
-
|
463
|
-
|
377
|
+
_replace_keyword(
|
378
|
+
self.messages, keyword, replacement, col=col,
|
379
|
+
case_sensitive=case_sensitive
|
380
|
+
)
|
381
|
+
|
382
|
+
def search_keywords(
|
383
|
+
self,
|
384
|
+
keywords: Union[str, list],
|
385
|
+
case_sensitive: bool = False, reset_index: bool = False, dropna: bool = False
|
386
|
+
) -> pd.DataFrame:
|
387
|
+
"""
|
388
|
+
Searches for messages containing specified keywords within the conversation.
|
464
389
|
|
390
|
+
Args:
|
391
|
+
keywords (Union[str, list]): The keyword(s) to search for within the conversation's messages.
|
392
|
+
case_sensitive (bool, optional): If True, the search is case sensitive. Defaults to False.
|
393
|
+
reset_index (bool, optional): If True, resets the index of the resulting DataFrame. Defaults to False.
|
394
|
+
dropna (bool, optional): If True, drops messages with NA values before searching. Defaults to False.
|
395
|
+
|
396
|
+
Returns:
|
397
|
+
pd.DataFrame: A DataFrame containing messages that match the search criteria.
|
398
|
+
"""
|
399
|
+
return _search_keywords(
|
400
|
+
self.messages, keywords, case_sensitive, reset_index, dropna
|
401
|
+
)
|
402
|
+
|
465
403
|
def extend(self, messages: pd.DataFrame, **kwargs) -> None:
|
466
404
|
"""
|
467
|
-
Extends the
|
405
|
+
Extends the conversation by appending new messages, optionally avoiding duplicates based on specified criteria.
|
468
406
|
|
469
407
|
Args:
|
470
|
-
messages (pd.DataFrame):
|
471
|
-
kwargs: for
|
408
|
+
messages (pd.DataFrame): A DataFrame containing new messages to append to the conversation.
|
409
|
+
**kwargs: Additional keyword arguments for handling duplicates (passed to pandas' drop_duplicates method).
|
472
410
|
"""
|
411
|
+
self.messages = _extend(self.messages, messages, **kwargs)
|
473
412
|
|
474
|
-
|
413
|
+
def filter_by(
|
414
|
+
self,
|
415
|
+
role: Optional[str] = None,
|
416
|
+
sender: Optional[str] = None,
|
417
|
+
start_time: Optional[datetime] = None,
|
418
|
+
end_time: Optional[datetime] = None,
|
419
|
+
content_keywords: Optional[Union[str, list]] = None,
|
420
|
+
case_sensitive: bool = False
|
421
|
+
) -> pd.DataFrame:
|
422
|
+
"""
|
423
|
+
Filters the conversation's messages based on specified criteria such as role, sender, time range, and keywords.
|
424
|
+
|
425
|
+
Args:
|
426
|
+
role (Optional[str]): Filter messages by role (e.g., 'user', 'assistant', 'system').
|
427
|
+
sender (Optional[str]): Filter messages by sender.
|
428
|
+
start_time (Optional[datetime]): Filter messages sent after this time.
|
429
|
+
end_time (Optional[datetime]): Filter messages sent before this time.
|
430
|
+
content_keywords (Optional[Union[str, list]]): Filter messages containing these keywords.
|
431
|
+
case_sensitive (bool, optional): If True, keyword search is case sensitive. Defaults to False.
|
432
|
+
|
433
|
+
Returns:
|
434
|
+
pd.DataFrame: A DataFrame containing messages that match the filter criteria.
|
435
|
+
"""
|
436
|
+
return _filter_messages_by(
|
437
|
+
self.messages, role=role, sender=sender,
|
438
|
+
start_time=start_time, end_time=end_time,
|
439
|
+
content_keywords=content_keywords, case_sensitive=case_sensitive
|
440
|
+
)
|
441
|
+
|
442
|
+
def _create_message(
|
443
|
+
self,
|
444
|
+
system: Optional[Union[dict, list, System]] = None,
|
445
|
+
instruction: Optional[Union[dict, list, Instruction]] = None,
|
446
|
+
context: Optional[Union[str, Dict[str, Any]]] = None,
|
447
|
+
response: Optional[Union[dict, list, Response]] = None,
|
448
|
+
sender: Optional[str] = None
|
449
|
+
) -> Message:
|
450
|
+
"""
|
451
|
+
Creates a message object based on the given parameters, ensuring only one message type is specified.
|
452
|
+
|
453
|
+
Args:
|
454
|
+
system (Optional[Union[dict, list, System]]): System message to be added.
|
455
|
+
instruction (Optional[Union[dict, list, Instruction]]): Instruction message to be added.
|
456
|
+
context (Optional[Union[str, Dict[str, Any]]]): Context for the instruction message.
|
457
|
+
response (Optional[Union[dict, list, Response]]): Response message to be added.
|
458
|
+
sender (Optional[str]): The sender of the message.
|
459
|
+
|
460
|
+
Returns:
|
461
|
+
Message: A Message object created from the provided parameters.
|
462
|
+
|
463
|
+
Raises:
|
464
|
+
ValueError: If more than one message type is specified or if the parameters do not form a valid message.
|
465
|
+
"""
|
466
|
+
if sum(lcall([system, instruction, response], bool)) != 1:
|
467
|
+
raise ValueError("Error: Message must have one and only one role.")
|
468
|
+
|
469
|
+
else:
|
470
|
+
if isinstance(any([system, instruction, response]), Message):
|
471
|
+
if system:
|
472
|
+
return system
|
473
|
+
elif instruction:
|
474
|
+
return instruction
|
475
|
+
elif response:
|
476
|
+
return response
|
477
|
+
|
478
|
+
msg = 0
|
479
|
+
if response:
|
480
|
+
msg = Response(response=response, sender=sender)
|
481
|
+
elif instruction:
|
482
|
+
msg = Instruction(instruction=instruction,
|
483
|
+
context=context, sender=sender)
|
484
|
+
elif system:
|
485
|
+
msg = System(system=system, sender=sender)
|
486
|
+
return msg
|
487
|
+
|
488
|
+
def _info(self, use_sender: bool = False) -> Dict[str, int]:
|
489
|
+
"""
|
490
|
+
Generates a summary of the conversation's messages, either by role or sender.
|
491
|
+
|
492
|
+
Args:
|
493
|
+
use_sender (bool, optional): If True, generates the summary based on sender. If False, uses role. Defaults to False.
|
494
|
+
|
495
|
+
Returns:
|
496
|
+
Dict[str, int]: A dictionary with counts of messages, categorized either by role or sender.
|
497
|
+
"""
|
498
|
+
messages = self.messages['sender'] if use_sender else self.messages['role']
|
499
|
+
result = messages.value_counts().to_dict()
|
500
|
+
result['total'] = len(self.len_messages)
|
501
|
+
return result
|
502
|
+
|
503
|
+
def validate_messages(messages):
|
504
|
+
"""
|
505
|
+
Validates the structure and content of a DataFrame containing conversation messages.
|
506
|
+
|
507
|
+
Args:
|
508
|
+
messages (pd.DataFrame): The DataFrame containing conversation messages to validate.
|
509
|
+
|
510
|
+
Returns:
|
511
|
+
bool: True if the DataFrame is valid, raises a ValueError otherwise.
|
512
|
+
|
513
|
+
Raises:
|
514
|
+
ValueError: If the DataFrame has unmatched columns, contains null values, has an unsupported role, or
|
515
|
+
if the content cannot be parsed as a JSON string.
|
516
|
+
"""
|
517
|
+
if list(messages.columns) != ['node_id', 'role', 'sender', 'timestamp', 'content']:
|
518
|
+
raise ValueError('Invalid messages dataframe. Unmatched columns.')
|
519
|
+
if messages.isnull().values.any():
|
520
|
+
raise ValueError('Invalid messages dataframe. Cannot have null.')
|
521
|
+
if not all(role in ['system', 'user', 'assistant'] for role in messages['role'].unique()):
|
522
|
+
raise ValueError('Invalid messages dataframe. Cannot have role other than ["system", "user", "assistant"].')
|
523
|
+
for cont in messages['content']:
|
524
|
+
if cont.startswith('Sender'):
|
525
|
+
cont = cont.split(':', 1)[1]
|
475
526
|
try:
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
527
|
+
json.loads(cont)
|
528
|
+
except:
|
529
|
+
raise ValueError('Invalid messages dataframe. Content expect json string.')
|
530
|
+
return True
|
531
|
+
|
532
|
+
def _sign_message(messages, sender: str):
|
533
|
+
"""
|
534
|
+
Prefixes each message in the DataFrame with 'Sender <sender>:' to indicate the message's origin.
|
535
|
+
|
536
|
+
Args:
|
537
|
+
messages (pd.DataFrame): The DataFrame containing conversation messages to sign.
|
538
|
+
sender (str): The name or identifier of the sender to prefix the messages with.
|
539
|
+
|
540
|
+
Returns:
|
541
|
+
pd.DataFrame: The DataFrame with updated messages signed by the specified sender.
|
542
|
+
|
543
|
+
Raises:
|
544
|
+
ValueError: If the sender is None or equivalent to the string 'none'.
|
545
|
+
"""
|
546
|
+
if sender is None or strip_lower(sender) == 'none':
|
547
|
+
raise ValueError("sender cannot be None")
|
548
|
+
df = messages.copy()
|
549
|
+
|
550
|
+
for i in df.index:
|
551
|
+
if not df.loc[i, 'content'].startswith('Sender'):
|
552
|
+
df.loc[i, 'content'] = f"Sender {sender}: {df.loc[i, 'content']}"
|
553
|
+
else:
|
554
|
+
content = df.loc[i, 'content'].split(':', 1)[1]
|
555
|
+
df.loc[i, 'content'] = f"Sender {sender}: {content}"
|
556
|
+
|
557
|
+
return to_df(df)
|
558
|
+
|
559
|
+
def _search_keywords(
|
560
|
+
messages,
|
561
|
+
keywords: Union[str, list],
|
562
|
+
case_sensitive: bool = False, reset_index=False, dropna=False
|
563
|
+
):
|
564
|
+
"""
|
565
|
+
Searches for keywords in the 'content' column of a DataFrame and returns matching rows.
|
566
|
+
|
567
|
+
Args:
|
568
|
+
messages (pd.DataFrame): The DataFrame to search within.
|
569
|
+
keywords (Union[str, List[str]]): Keyword(s) to search for. If a list, combines keywords with an OR condition.
|
570
|
+
case_sensitive (bool, optional): Whether the search should be case-sensitive. Defaults to False.
|
571
|
+
reset_index (bool, optional): Whether to reset the index of the resulting DataFrame. Defaults to False.
|
572
|
+
dropna (bool, optional): Whether to drop rows with NA values in the 'content' column. Defaults to False.
|
573
|
+
|
574
|
+
Returns:
|
575
|
+
pd.DataFrame: A DataFrame containing rows where the 'content' column matches the search criteria.
|
576
|
+
"""
|
577
|
+
out = ''
|
578
|
+
if isinstance(keywords, list):
|
579
|
+
keywords = '|'.join(keywords)
|
580
|
+
if not case_sensitive:
|
581
|
+
out = messages[
|
582
|
+
messages["content"].str.contains(keywords, case=False)
|
583
|
+
]
|
584
|
+
out = messages[messages["content"].str.contains(keywords)]
|
585
|
+
if reset_index or dropna:
|
586
|
+
out = to_df(out, reset_index=reset_index)
|
587
|
+
return out
|
588
|
+
|
589
|
+
def _filter_messages_by(
|
590
|
+
messages,
|
591
|
+
role: Optional[str] = None,
|
592
|
+
sender: Optional[str] = None,
|
593
|
+
start_time: Optional[datetime] = None,
|
594
|
+
end_time: Optional[datetime] = None,
|
595
|
+
content_keywords: Optional[Union[str, list]] = None,
|
596
|
+
case_sensitive: bool = False
|
597
|
+
) -> pd.DataFrame:
|
598
|
+
"""
|
599
|
+
Filters messages in a DataFrame based on specified criteria such as role, sender, time range, and keywords.
|
600
|
+
|
601
|
+
Args:
|
602
|
+
messages (pd.DataFrame): The DataFrame of messages to filter.
|
603
|
+
role (Optional[str]): The role to filter messages by (e.g., 'user', 'assistant').
|
604
|
+
sender (Optional[str]): The sender to filter messages by.
|
605
|
+
start_time (Optional[datetime]): The start time for filtering messages.
|
606
|
+
end_time (Optional[datetime]): The end time for filtering messages.
|
607
|
+
content_keywords (Optional[Union[str, list]]): Keywords to filter messages by content.
|
608
|
+
case_sensitive (bool): Determines if the keyword search should be case-sensitive.
|
609
|
+
|
610
|
+
Returns:
|
611
|
+
pd.DataFrame: A DataFrame containing messages that match the filter criteria.
|
612
|
+
|
613
|
+
Raises:
|
614
|
+
ValueError: If an error occurs during the filtering process.
|
615
|
+
"""
|
616
|
+
|
617
|
+
try:
|
618
|
+
outs = messages.copy()
|
619
|
+
|
620
|
+
if content_keywords:
|
621
|
+
outs = _search_keywords(content_keywords, case_sensitive)
|
622
|
+
|
623
|
+
outs = outs[outs['role'] == role] if role else outs
|
624
|
+
outs = outs[outs['sender'] == sender] if sender else outs
|
625
|
+
outs = outs[outs['timestamp'] > start_time] if start_time else outs
|
626
|
+
outs = outs[outs['timestamp'] < end_time] if end_time else outs
|
627
|
+
|
628
|
+
return to_df(outs)
|
629
|
+
|
630
|
+
except Exception as e:
|
631
|
+
raise ValueError(f"Error in filtering messages: {e}")
|
632
|
+
|
633
|
+
def _replace_keyword(
|
634
|
+
df,
|
635
|
+
keyword: str,
|
636
|
+
replacement: str,
|
637
|
+
col='content',
|
638
|
+
case_sensitive: bool = False
|
639
|
+
) -> None:
|
640
|
+
"""
|
641
|
+
Replaces occurrences of a keyword within a specified column of a DataFrame with a given replacement.
|
642
|
+
|
643
|
+
Args:
|
644
|
+
df (pd.DataFrame): The DataFrame to operate on.
|
645
|
+
keyword (str): The keyword to search for and replace.
|
646
|
+
replacement (str): The string to replace the keyword with.
|
647
|
+
col (str): The column to search for the keyword in.
|
648
|
+
case_sensitive (bool): If True, the search and replacement are case-sensitive.
|
649
|
+
|
650
|
+
Returns:
|
651
|
+
None: This function modifies the DataFrame in place.
|
652
|
+
"""
|
653
|
+
if not case_sensitive:
|
654
|
+
df[col] = df[col].str.replace(
|
655
|
+
keyword, replacement, case=False
|
656
|
+
)
|
657
|
+
else:
|
658
|
+
df[col] = df[col].str.replace(
|
659
|
+
keyword, replacement
|
660
|
+
)
|
661
|
+
|
662
|
+
def _remove_message(df, node_id: str) -> bool:
|
663
|
+
"""
|
664
|
+
Removes a message from the DataFrame based on its node_id.
|
665
|
+
|
666
|
+
Args:
|
667
|
+
df (pd.DataFrame): The DataFrame from which the message should be removed.
|
668
|
+
node_id (str): The node_id of the message to be removed.
|
669
|
+
|
670
|
+
Returns:
|
671
|
+
bool: True if the message was successfully removed, False otherwise.
|
672
|
+
"""
|
673
|
+
initial_length = len(df)
|
674
|
+
df = df[df["node_id"] != node_id]
|
675
|
+
|
676
|
+
return len(df) < initial_length
|
677
|
+
|
678
|
+
def _update_row(
|
679
|
+
df, node_id = None, col = "node_id", value = None
|
680
|
+
) -> bool:
|
681
|
+
"""
|
682
|
+
Updates the value of a specified column for a row identified by node_id in a DataFrame.
|
683
|
+
|
684
|
+
Args:
|
685
|
+
df (pd.DataFrame): The DataFrame to update.
|
686
|
+
node_id (Optional[str]): The node_id of the row to be updated.
|
687
|
+
col (str): The column to update.
|
688
|
+
value (Any): The new value to be assigned to the column.
|
689
|
+
|
690
|
+
Returns:
|
691
|
+
bool: True if the update was successful, False otherwise.
|
692
|
+
"""
|
693
|
+
index = df.index[df[col] == node_id].tolist()
|
694
|
+
if index:
|
695
|
+
df.at[index[0], col] = value
|
696
|
+
return True
|
697
|
+
return False
|
698
|
+
|
699
|
+
def _remove_last_n_rows(df, steps: int) -> None:
|
700
|
+
"""
|
701
|
+
Removes the last 'n' rows from a DataFrame.
|
702
|
+
|
703
|
+
Args:
|
704
|
+
df (pd.DataFrame): The DataFrame from which rows will be removed.
|
705
|
+
steps (int): The number of rows to remove.
|
706
|
+
|
707
|
+
Returns:
|
708
|
+
pd.DataFrame: The DataFrame after the last 'n' rows have been removed.
|
709
|
+
|
710
|
+
Raises:
|
711
|
+
ValueError: If 'steps' is less than 0 or greater than the number of rows in the DataFrame.
|
712
|
+
"""
|
713
|
+
if steps < 0 or steps > len(df):
|
714
|
+
raise ValueError("Steps must be a non-negative integer less than or equal to the number of messages.")
|
715
|
+
df = to_df(df[:-steps])
|
716
|
+
|
717
|
+
def get_rows(
|
718
|
+
df,
|
719
|
+
sender: Optional[str] = None,
|
720
|
+
role: Optional[str] = None,
|
721
|
+
n: int = 1,
|
722
|
+
sign_ = False,
|
723
|
+
from_="front",
|
724
|
+
) -> pd.DataFrame:
|
725
|
+
"""
|
726
|
+
Retrieves rows from a DataFrame based on specified sender, role, and quantity, optionally signing them.
|
727
|
+
|
728
|
+
Args:
|
729
|
+
df (pd.DataFrame): The DataFrame to retrieve rows from.
|
730
|
+
sender (Optional[str]): The sender based on which to filter rows.
|
731
|
+
role (Optional[str]): The role based on which to filter rows.
|
732
|
+
n (int): The number of rows to retrieve.
|
733
|
+
sign_ (bool): Whether to sign the retrieved rows.
|
734
|
+
from_ (str): Direction to retrieve rows ('front' for the first rows, 'last' for the last rows).
|
735
|
+
|
736
|
+
Returns:
|
737
|
+
pd.DataFrame: A DataFrame containing the retrieved rows.
|
738
|
+
"""
|
739
|
+
|
740
|
+
if from_ == "last":
|
741
|
+
if sender is None and role is None:
|
742
|
+
outs = df.iloc[-n:]
|
743
|
+
elif sender and role:
|
744
|
+
outs = df[(df['sender'] == sender) & (df['role'] == role)].iloc[-n:]
|
745
|
+
elif sender:
|
746
|
+
outs = df[df['sender'] == sender].iloc[-n:]
|
747
|
+
else:
|
748
|
+
outs = df[df['role'] == role].iloc[-n:]
|
749
|
+
|
750
|
+
elif from_ == "front":
|
751
|
+
if sender is None and role is None:
|
752
|
+
outs = df.iloc[:n]
|
753
|
+
elif sender and role:
|
754
|
+
outs = df[(df['sender'] == sender) & (df['role'] == role)].iloc[:n]
|
755
|
+
elif sender:
|
756
|
+
outs = df[df['sender'] == sender].iloc[:n]
|
757
|
+
else:
|
758
|
+
outs = df[df['role'] == role].iloc[:n]
|
759
|
+
|
760
|
+
return _sign_message(outs, sender) if sign_ else outs
|
761
|
+
|
762
|
+
def _extend(df1: pd.DataFrame, df2: pd.DataFrame, **kwargs) -> pd.DataFrame:
|
763
|
+
"""
|
764
|
+
Extends a DataFrame with another DataFrame, optionally removing duplicates based on specified criteria.
|
765
|
+
|
766
|
+
Args:
|
767
|
+
df1 (pd.DataFrame): The original DataFrame to be extended.
|
768
|
+
df2 (pd.DataFrame): The DataFrame containing new rows to add to df1.
|
769
|
+
**kwargs: Additional keyword arguments for pandas.DataFrame.drop_duplicates().
|
770
|
+
|
771
|
+
Returns:
|
772
|
+
pd.DataFrame: The extended DataFrame after adding rows from df2 and removing duplicates.
|
773
|
+
|
774
|
+
Raises:
|
775
|
+
ValueError: If an error occurs during the extension process.
|
776
|
+
"""
|
777
|
+
validate_messages(df2)
|
778
|
+
try:
|
779
|
+
if len(df2.dropna(how='all')) > 0 and len(df1.dropna(how='all')) > 0:
|
780
|
+
df = to_df([df1, df2])
|
781
|
+
df.drop_duplicates(
|
782
|
+
inplace=True, subset=['node_id'], keep='first', **kwargs
|
783
|
+
)
|
784
|
+
return to_df(df)
|
785
|
+
except Exception as e:
|
786
|
+
raise ValueError(f"Error in extending messages: {e}")
|
787
|
+
|