lionagi 0.7.0__py3-none-any.whl → 0.7.2__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.
Files changed (43) hide show
  1. lionagi/operations/ReAct/ReAct.py +2 -2
  2. lionagi/operations/_act/act.py +10 -3
  3. lionagi/operations/communicate/communicate.py +0 -59
  4. lionagi/operations/interpret/interpret.py +1 -2
  5. lionagi/operations/operate/operate.py +10 -5
  6. lionagi/operations/parse/parse.py +0 -36
  7. lionagi/operations/plan/plan.py +3 -3
  8. lionagi/operatives/action/manager.py +105 -82
  9. lionagi/operatives/action/request_response_model.py +31 -0
  10. lionagi/operatives/action/tool.py +50 -20
  11. lionagi/protocols/_concepts.py +1 -1
  12. lionagi/protocols/adapters/adapter.py +25 -0
  13. lionagi/protocols/adapters/json_adapter.py +107 -27
  14. lionagi/protocols/adapters/pandas_/csv_adapter.py +55 -11
  15. lionagi/protocols/adapters/pandas_/excel_adapter.py +52 -10
  16. lionagi/protocols/adapters/pandas_/pd_dataframe_adapter.py +54 -4
  17. lionagi/protocols/adapters/pandas_/pd_series_adapter.py +40 -0
  18. lionagi/protocols/generic/element.py +1 -1
  19. lionagi/protocols/generic/pile.py +5 -8
  20. lionagi/protocols/graph/edge.py +1 -1
  21. lionagi/protocols/graph/graph.py +16 -8
  22. lionagi/protocols/graph/node.py +1 -1
  23. lionagi/protocols/mail/exchange.py +126 -15
  24. lionagi/protocols/mail/mail.py +33 -0
  25. lionagi/protocols/mail/mailbox.py +62 -0
  26. lionagi/protocols/mail/manager.py +97 -41
  27. lionagi/protocols/mail/package.py +57 -3
  28. lionagi/protocols/messages/action_request.py +77 -26
  29. lionagi/protocols/messages/action_response.py +55 -26
  30. lionagi/protocols/messages/assistant_response.py +50 -15
  31. lionagi/protocols/messages/base.py +36 -0
  32. lionagi/protocols/messages/instruction.py +175 -145
  33. lionagi/protocols/messages/manager.py +152 -56
  34. lionagi/protocols/messages/message.py +61 -25
  35. lionagi/protocols/messages/system.py +54 -19
  36. lionagi/service/imodel.py +24 -0
  37. lionagi/session/branch.py +40 -32
  38. lionagi/utils.py +1 -0
  39. lionagi/version.py +1 -1
  40. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/METADATA +1 -1
  41. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/RECORD +43 -43
  42. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/WHEEL +0 -0
  43. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/licenses/LICENSE +0 -0
@@ -994,8 +994,8 @@ class Pile(Element, Collective[E], Generic[E]):
994
994
  path_or_buf,
995
995
  *,
996
996
  use_pd: bool = False,
997
+ many: bool = False,
997
998
  mode="w",
998
- verbose=False,
999
999
  **kwargs,
1000
1000
  ):
1001
1001
  """Export collection to JSON file.
@@ -1007,15 +1007,9 @@ class Pile(Element, Collective[E], Generic[E]):
1007
1007
  verbose: Print confirmation message.
1008
1008
  **kwargs: Additional arguments for json.dump() or DataFrame.to_json().
1009
1009
  """
1010
-
1011
1010
  if use_pd:
1012
1011
  return self.to_df().to_json(mode=mode, **kwargs)
1013
- dict_ = self.to_dict()
1014
- with open(path_or_buf, mode) as f:
1015
- json.dump(dict_, f, **kwargs)
1016
-
1017
- if verbose:
1018
- print(f"Saved Pile to {path_or_buf}")
1012
+ return self.adapt_to(".json", fp=path_or_buf, mode=mode, many=many)
1019
1013
 
1020
1014
 
1021
1015
  def pile(
@@ -1076,3 +1070,6 @@ def to_list_type(value: Any, /) -> list[Any]:
1076
1070
  if isinstance(value, list | tuple | set | deque | Generator):
1077
1071
  return list(value)
1078
1072
  return [value]
1073
+
1074
+
1075
+ # File: lionagi/protocols/generic/pile.py
@@ -163,4 +163,4 @@ class Edge(Element):
163
163
  cond.source = source
164
164
 
165
165
 
166
- # File: protocols/graph/edge.py
166
+ # File: lionagi/protocols/graph/edge.py
@@ -204,13 +204,14 @@ class Graph(Element, Relational):
204
204
  def to_networkx(self, **kwargs) -> Any:
205
205
  """Convert the graph to a NetworkX graph object."""
206
206
  try:
207
- import networkx as nx # type: ignore
207
+ from networkx import DiGraph # type: ignore
208
+
208
209
  except ImportError:
209
- raise ImportError(
210
- "NetworkX is not installed. Please install it with `pip install networkx`."
211
- )
210
+ from lionagi.libs.package.imports import check_import
212
211
 
213
- g = nx.DiGraph(**kwargs)
212
+ DiGraph = check_import("networkx", import_name="DiGraph")
213
+
214
+ g = DiGraph(**kwargs)
214
215
  for node in self.internal_nodes:
215
216
  node_info = node.to_dict()
216
217
  node_info.pop("id")
@@ -238,9 +239,13 @@ class Graph(Element, Relational):
238
239
  import matplotlib.pyplot as plt # type: ignore
239
240
  import networkx as nx # type: ignore
240
241
  except ImportError:
241
- raise ImportError(
242
- "Failed to import Matplotlib. Please install the package with `pip install matplotlib`"
243
- )
242
+ from lionagi.libs.package.imports import check_import
243
+
244
+ check_import("matplotlib")
245
+ check_import("networkx")
246
+
247
+ import matplotlib.pyplot as plt # type: ignore
248
+ import networkx as nx # type: ignore
244
249
 
245
250
  g = self.to_networkx(**kwargs)
246
251
  pos = nx.spring_layout(g)
@@ -288,3 +293,6 @@ class Graph(Element, Relational):
288
293
 
289
294
  def __contains__(self, item: object) -> bool:
290
295
  return item in self.internal_nodes or item in self.internal_edges
296
+
297
+
298
+ # File: lionagi/protocols/graph/graph.py
@@ -122,4 +122,4 @@ class Node(Element, Relational):
122
122
  return cls._get_adapter_registry().list_adapters()
123
123
 
124
124
 
125
- # File: protocols/graph/node.py
125
+ # File: lionagi/protocols/graph/node.py
@@ -2,6 +2,12 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ """
6
+ Provides the `Exchange` class, which orchestrates mail flows among
7
+ sources that implement `Communicatable`. It collects pending outgoing
8
+ mail from each source and delivers them to the appropriate recipients.
9
+ """
10
+
5
11
  import asyncio
6
12
  from typing import Any
7
13
 
@@ -15,8 +21,34 @@ from .mailbox import Mailbox
15
21
 
16
22
 
17
23
  class Exchange:
24
+ """
25
+ Manages mail exchange operations among a set of sources that are
26
+ `Communicatable`. Each source has an associated `Mailbox` to store
27
+ inbound and outbound mail.
28
+
29
+ Attributes
30
+ ----------
31
+ sources : Pile[Communicatable]
32
+ The communicatable sources participating in the exchange.
33
+ buffer : dict[IDType, list[Mail]]
34
+ A temporary holding area for mail messages before they reach
35
+ their recipient's mailbox.
36
+ mailboxes : dict[IDType, Mailbox]
37
+ Maps each source's ID to its Mailbox.
38
+ _execute_stop : bool
39
+ A flag indicating whether to stop the asynchronous execution loop.
40
+ """
18
41
 
19
42
  def __init__(self, sources: ID[Communicatable].ItemSeq = None):
43
+ """
44
+ Initialize an `Exchange` instance.
45
+
46
+ Parameters
47
+ ----------
48
+ sources : ID[Communicatable].ItemSeq, optional
49
+ One or more communicatable sources to manage. If provided,
50
+ they are immediately added.
51
+ """
20
52
  self.sources: Pile[Communicatable] = Pile(
21
53
  item_type={Communicatable}, strict_type=False
22
54
  )
@@ -26,7 +58,20 @@ class Exchange:
26
58
  self.add_source(sources)
27
59
  self._execute_stop: bool = False
28
60
 
29
- def add_source(self, sources: ID[Communicatable].ItemSeq):
61
+ def add_source(self, sources: ID[Communicatable].ItemSeq) -> None:
62
+ """
63
+ Register new communicatable sources for mail exchange.
64
+
65
+ Parameters
66
+ ----------
67
+ sources : ID[Communicatable].ItemSeq
68
+ The source(s) to be added.
69
+
70
+ Raises
71
+ ------
72
+ ValueError
73
+ If the given sources already exist in this exchange.
74
+ """
30
75
  if sources in self.sources:
31
76
  raise ValueError(
32
77
  f"Source {sources} already exists in the mail manager."
@@ -37,8 +82,21 @@ class Exchange:
37
82
  self.mailboxes[source.id] = source.mailbox
38
83
  self.buffer.update({source.id: [] for source in sources})
39
84
 
40
- def delete_source(self, sources: ID[Communicatable].ItemSeq):
41
- """this will remove the source from the mail manager and all assosiated pending mails"""
85
+ def delete_source(self, sources: ID[Communicatable].ItemSeq) -> None:
86
+ """
87
+ Remove specified sources from the exchange, clearing any pending
88
+ mail associated with them.
89
+
90
+ Parameters
91
+ ----------
92
+ sources : ID[Communicatable].ItemSeq
93
+ The source(s) to remove.
94
+
95
+ Raises
96
+ ------
97
+ ValueError
98
+ If the given sources do not exist in this exchange.
99
+ """
42
100
  if not sources in self.sources:
43
101
  raise ValueError(
44
102
  f"Source {sources} does not exist in the mail manager."
@@ -57,13 +115,47 @@ class Exchange:
57
115
  item: Any,
58
116
  request_source: Any = None,
59
117
  ) -> Mail:
118
+ """
119
+ Helper method to create a new Mail instance.
120
+
121
+ Parameters
122
+ ----------
123
+ sender : ID[Communicatable]
124
+ The ID (or Communicatable) identifying the mail sender.
125
+ recipient : ID[Communicatable]
126
+ The ID (or Communicatable) identifying the mail recipient.
127
+ category : PackageCategory | str
128
+ A classification for the package contents.
129
+ item : Any
130
+ The actual item/data to be sent.
131
+ request_source : Any, optional
132
+ Additional context about the request origin, if any.
133
+
134
+ Returns
135
+ -------
136
+ Mail
137
+ A newly created Mail object ready for sending.
138
+ """
60
139
  package = Package(
61
140
  category=category, item=item, request_source=request_source
62
141
  )
63
142
  return Mail(sender=sender, recipient=recipient, package=package)
64
143
 
65
- def collect(self, sender: ID[Communicatable]):
66
- """collect all mails from a particular sender"""
144
+ def collect(self, sender: ID[Communicatable]) -> None:
145
+ """
146
+ Collect all outbound mail from a specific sender, moving it
147
+ to the exchange buffer.
148
+
149
+ Parameters
150
+ ----------
151
+ sender : ID[Communicatable]
152
+ The ID of the source from which mail is collected.
153
+
154
+ Raises
155
+ ------
156
+ ValueError
157
+ If the sender is not part of this exchange.
158
+ """
67
159
  if sender not in self.sources:
68
160
  raise ValueError(f"Sender source {sender} does not exist.")
69
161
 
@@ -73,7 +165,21 @@ class Exchange:
73
165
  mail: Mail = sender_mailbox.pile_.popleft()
74
166
  self.buffer[mail.recipient].append(mail)
75
167
 
76
- def deliver(self, recipient: ID[Communicatable]):
168
+ def deliver(self, recipient: ID[Communicatable]) -> None:
169
+ """
170
+ Deliver all mail in the buffer addressed to a specific recipient.
171
+
172
+ Parameters
173
+ ----------
174
+ recipient : ID[Communicatable]
175
+ The ID of the source to receive mail.
176
+
177
+ Raises
178
+ ------
179
+ ValueError
180
+ If the recipient is not part of this exchange or if mail
181
+ references an unknown sender.
182
+ """
77
183
  if recipient not in self.sources:
78
184
  raise ValueError(f"Recipient source {recipient} does not exist.")
79
185
 
@@ -90,27 +196,32 @@ class Exchange:
90
196
  recipient_mailbox.append_in(mail)
91
197
 
92
198
  def collect_all(self) -> None:
93
- """Collect mail from all sources."""
199
+ """
200
+ Collect mail from every source in this exchange.
201
+ """
94
202
  for source in self.sources:
95
203
  self.collect(sender=source.id)
96
204
 
97
205
  def deliver_all(self) -> None:
98
- """Send mail to all recipients."""
206
+ """
207
+ Deliver mail to every source in this exchange.
208
+ """
99
209
  for source in self.sources:
100
210
  self.deliver(recipient=source.id)
101
211
 
102
212
  async def execute(self, refresh_time: int = 1) -> None:
103
213
  """
104
- Execute mail collection and sending process asynchronously.
105
-
106
- This method runs in a loop, collecting and sending mail at
107
- regular intervals.
214
+ Continuously collect and deliver mail in an asynchronous loop.
108
215
 
109
- Args:
110
- refresh_time (int, optional): The time to wait between
111
- each cycle in seconds. Defaults to 1.
216
+ Parameters
217
+ ----------
218
+ refresh_time : int, optional
219
+ Number of seconds to wait between each cycle. Defaults to 1.
112
220
  """
113
221
  while not self._execute_stop:
114
222
  self.collect_all()
115
223
  self.deliver_all()
116
224
  await asyncio.sleep(refresh_time)
225
+
226
+
227
+ # File: lionagi/protocols/mail/exchange.py
@@ -2,6 +2,12 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ """
6
+ Defines the `Mail` class, which is a `Sendable` element representing
7
+ a single piece of mail, carrying a `Package` between a sender
8
+ and recipient.
9
+ """
10
+
5
11
  from pydantic import field_validator
6
12
 
7
13
  from .._concepts import Sendable
@@ -11,6 +17,20 @@ from .package import Package, PackageCategory
11
17
 
12
18
 
13
19
  class Mail(Element, Sendable):
20
+ """
21
+ A single mail message that can be sent between communicatable entities.
22
+ It includes a sender, recipient, and a package that describes the
23
+ mail's content.
24
+
25
+ Attributes
26
+ ----------
27
+ sender : IDType
28
+ The ID representing the mail sender.
29
+ recipient : IDType
30
+ The ID representing the mail recipient.
31
+ package : Package
32
+ The package (category + payload) contained in this mail.
33
+ """
14
34
 
15
35
  sender: IDType
16
36
  recipient: IDType
@@ -18,8 +38,21 @@ class Mail(Element, Sendable):
18
38
 
19
39
  @field_validator("sender", "recipient")
20
40
  def _validate_sender_recipient(cls, value):
41
+ """
42
+ Validate that the sender and recipient fields are correct IDTypes.
43
+ """
21
44
  return IDType.validate(value)
22
45
 
23
46
  @property
24
47
  def category(self) -> PackageCategory:
48
+ """
49
+ Shortcut for retrieving the category from the underlying package.
50
+
51
+ Returns
52
+ -------
53
+ PackageCategory
54
+ """
25
55
  return self.package.category
56
+
57
+
58
+ # File: lionagi/protocols/mail/mail.py
@@ -2,6 +2,11 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ """
6
+ Implements a simple mailbox system for each Communicatable entity.
7
+ Holds inbound and outbound mail, stored internally in a `Pile`.
8
+ """
9
+
5
10
  from lionagi.protocols.generic.element import IDType
6
11
 
7
12
  from ..generic.pile import Pile, Progression
@@ -11,37 +16,94 @@ __all__ = ("Mailbox",)
11
16
 
12
17
 
13
18
  class Mailbox:
19
+ """
20
+ A mailbox that accumulates inbound and outbound mail for a single
21
+ communicatable source.
22
+
23
+ Attributes
24
+ ----------
25
+ pile_ : Pile[Mail]
26
+ A concurrency-safe collection storing all mail items.
27
+ pending_ins : dict[IDType, Progression]
28
+ Maps each sender's ID to a progression of inbound mail.
29
+ pending_outs : Progression
30
+ A progression of mail items waiting to be sent (outbound).
31
+ """
14
32
 
15
33
  def __init__(self):
34
+ """
35
+ Initialize an empty Mailbox with separate tracks for inbound
36
+ and outbound mail.
37
+ """
16
38
  self.pile_ = Pile(item_type=Mail, strict_type=True)
17
39
  self.pending_ins: dict[IDType, Progression] = {}
18
40
  self.pending_outs = Progression()
19
41
 
20
42
  def __contains__(self, item: Mail) -> bool:
43
+ """
44
+ Check if a mail item is currently in this mailbox.
45
+ """
21
46
  return item in self.pile_
22
47
 
23
48
  @property
24
49
  def senders(self) -> list[str]:
50
+ """
51
+ List of sender IDs that have inbound mail in this mailbox.
52
+
53
+ Returns
54
+ -------
55
+ list[str]
56
+ """
25
57
  return list(self.pending_ins.keys())
26
58
 
27
59
  def append_in(self, item: Mail, /):
60
+ """
61
+ Add a mail item to the inbound queue for the item's sender.
62
+
63
+ Parameters
64
+ ----------
65
+ item : Mail
66
+ """
28
67
  if item.sender not in self.pending_ins:
29
68
  self.pending_ins[item.sender] = Progression()
30
69
  self.pending_ins[item.sender].include(item)
31
70
  self.pile_.include(item)
32
71
 
33
72
  def append_out(self, item: Mail, /):
73
+ """
74
+ Add a mail item to the outbound (pending_outs) queue.
75
+
76
+ Parameters
77
+ ----------
78
+ item : Mail
79
+ """
34
80
  self.pending_outs.include(item)
35
81
  self.pile_.include(item)
36
82
 
37
83
  def exclude(self, item: Mail, /):
84
+ """
85
+ Remove a mail item from all internal references (inbound, outbound, and pile).
86
+
87
+ Parameters
88
+ ----------
89
+ item : Mail
90
+ """
38
91
  self.pile_.exclude(item)
39
92
  self.pending_outs.exclude(item)
40
93
  for v in self.pending_ins.values():
41
94
  v.exclude(item)
42
95
 
43
96
  def __bool__(self) -> bool:
97
+ """
98
+ Indicates if the mailbox contains any mail.
99
+ """
44
100
  return bool(self.pile_)
45
101
 
46
102
  def __len__(self) -> int:
103
+ """
104
+ Number of mail items in this mailbox.
105
+ """
47
106
  return len(self.pile_)
107
+
108
+
109
+ # File: lionagi/protocols/mail/mailbox.py
@@ -2,6 +2,11 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ """
6
+ Defines the `MailManager` class, which coordinates mail operations
7
+ across multiple sources in a more abstract or high-level manner.
8
+ """
9
+
5
10
  import asyncio
6
11
  from collections import deque
7
12
  from typing import Any
@@ -17,19 +22,29 @@ from .mail import Mail, Package, PackageCategory
17
22
 
18
23
  class MailManager(Manager):
19
24
  """
20
- Manages mail operations for multiple sources in the Lion framework.
21
-
22
- This class handles the collection, distribution, and management of mail
23
- between different sources within the system.
25
+ A manager for mail operations across various observable sources
26
+ within LionAGI. Unlike `Exchange`, this class can manage the state
27
+ of multiple sources in a more general or higher-level context,
28
+ storing mail queues in a dictionary rather than individual buffers.
29
+
30
+ Attributes
31
+ ----------
32
+ sources : Pile[Observable]
33
+ A concurrency-safe collection of known sources.
34
+ mails : dict[str, dict[str, deque]]
35
+ A nested mapping of recipient -> sender -> queue of mail.
36
+ execute_stop : bool
37
+ Controls the asynchronous execution loop; set to True to exit.
24
38
  """
25
39
 
26
40
  def __init__(self, sources: ID.Item | ID.ItemSeq = None) -> None:
27
41
  """
28
42
  Initialize a MailManager instance.
29
43
 
30
- Args:
31
- sources (List[Any], optional): Initial list of mail sources to
32
- manage.
44
+ Parameters
45
+ ----------
46
+ sources : ID.Item | ID.ItemSeq, optional
47
+ Initial source(s) to manage. Each source must be an Observable.
33
48
  """
34
49
  self.sources: Pile[Observable] = Pile()
35
50
  self.mails: dict[str, dict[str, deque]] = {}
@@ -40,14 +55,17 @@ class MailManager(Manager):
40
55
 
41
56
  def add_sources(self, sources: ID.Item | ID.ItemSeq, /) -> None:
42
57
  """
43
- Add new sources to the MailManager.
58
+ Register new sources in the MailManager.
44
59
 
45
- Args:
46
- sources (Any): The sources to add. Can be a single source or
47
- a list of sources.
60
+ Parameters
61
+ ----------
62
+ sources : ID.Item | ID.ItemSeq
63
+ A single source or multiple sources to be added.
48
64
 
49
- Raises:
50
- LionValueError: If adding the source(s) fails.
65
+ Raises
66
+ ------
67
+ ValueError
68
+ If adding the sources fails for any reason.
51
69
  """
52
70
  try:
53
71
  sources = to_list_type(sources)
@@ -66,17 +84,25 @@ class MailManager(Manager):
66
84
  request_source: Any = None,
67
85
  ) -> Mail:
68
86
  """
69
- Create a new Mail object.
70
-
71
- Args:
72
- sender (str): The ID of the sender.
73
- recipient (str): The ID of the recipient.
74
- category (str): The category of the mail.
75
- package (Any): The content of the package.
76
- request_source (Any, optional): The source of the request.
77
-
78
- Returns:
79
- Mail: A new Mail object.
87
+ Factory method to generate a Mail object.
88
+
89
+ Parameters
90
+ ----------
91
+ sender : ID.Ref
92
+ Reference (ID or object) for the sender.
93
+ recipient : ID.Ref
94
+ Reference (ID or object) for the recipient.
95
+ category : PackageCategory | str
96
+ The category of this package.
97
+ package : Any
98
+ The payload or content in the mail.
99
+ request_source : Any, optional
100
+ Additional context about the request source.
101
+
102
+ Returns
103
+ -------
104
+ Mail
105
+ A new mail object with specified sender, recipient, and package.
80
106
  """
81
107
  pack = Package(
82
108
  category=category, package=package, request_source=request_source
@@ -85,13 +111,17 @@ class MailManager(Manager):
85
111
 
86
112
  def delete_source(self, source_id: IDType) -> None:
87
113
  """
88
- Delete a source from the MailManager.
114
+ Remove a source from the manager, discarding any associated mail.
89
115
 
90
- Args:
91
- source_id (str): The ID of the source to delete.
116
+ Parameters
117
+ ----------
118
+ source_id : IDType
119
+ The ID of the source to be removed.
92
120
 
93
- Raises:
94
- LionValueError: If the source does not exist.
121
+ Raises
122
+ ------
123
+ ItemNotFoundError
124
+ If the given source ID is not present.
95
125
  """
96
126
  if source_id not in self.sources:
97
127
  raise ItemNotFoundError(f"Source {source_id} does not exist.")
@@ -99,7 +129,19 @@ class MailManager(Manager):
99
129
  self.mails.pop(source_id)
100
130
 
101
131
  def collect(self, sender: IDType) -> None:
102
- """Collect mail from a specific sender."""
132
+ """
133
+ Collect outbound mail from a single source.
134
+
135
+ Parameters
136
+ ----------
137
+ sender : IDType
138
+ The ID of the sender whose outbound mail is retrieved.
139
+
140
+ Raises
141
+ ------
142
+ ItemNotFoundError
143
+ If the sender is not recognized.
144
+ """
103
145
  if sender not in self.sources:
104
146
  raise ItemNotFoundError(f"Sender source {sender} does not exist.")
105
147
  mailbox: Exchange = (
@@ -120,7 +162,19 @@ class MailManager(Manager):
120
162
  self.mails[mail.recipient][mail.sender].append(mail)
121
163
 
122
164
  def send(self, recipient: IDType) -> None:
123
- """Send mail to a specific recipient."""
165
+ """
166
+ Send any pending mail to a specified recipient.
167
+
168
+ Parameters
169
+ ----------
170
+ recipient : IDType
171
+ The ID of the recipient to which mail should be delivered.
172
+
173
+ Raises
174
+ ------
175
+ ItemNotFoundError
176
+ If the recipient ID is not recognized.
177
+ """
124
178
  if recipient not in self.sources:
125
179
  raise ItemNotFoundError(
126
180
  f"Recipient source {recipient} does not exist."
@@ -139,25 +193,27 @@ class MailManager(Manager):
139
193
  mailbox.include(mail, direction="in")
140
194
 
141
195
  def collect_all(self) -> None:
142
- """Collect mail from all sources."""
196
+ """
197
+ Collect outbound mail from all known sources.
198
+ """
143
199
  for source in self.sources:
144
200
  self.collect(sender=source.id)
145
201
 
146
202
  def send_all(self) -> None:
147
- """Send mail to all recipients."""
203
+ """
204
+ Send mail to all known recipients who have pending items.
205
+ """
148
206
  for source in self.sources:
149
207
  self.send(recipient=source.id)
150
208
 
151
209
  async def execute(self, refresh_time: int = 1) -> None:
152
210
  """
153
- Execute mail collection and sending process asynchronously.
154
-
155
- This method runs in a loop, collecting and sending mail at
156
- regular intervals.
211
+ Continuously collect and send mail in an asynchronous loop.
157
212
 
158
- Args:
159
- refresh_time (int, optional): The time to wait between
160
- each cycle in seconds. Defaults to 1.
213
+ Parameters
214
+ ----------
215
+ refresh_time : int, optional
216
+ Delay (in seconds) between each collect/send cycle.
161
217
  """
162
218
  while not self.execute_stop:
163
219
  self.collect_all()
@@ -165,4 +221,4 @@ class MailManager(Manager):
165
221
  await asyncio.sleep(refresh_time)
166
222
 
167
223
 
168
- # File: lion_core/communication/mail_manager.py
224
+ # File: lion_core/communication/manager.py