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.
- lionagi/operations/ReAct/ReAct.py +2 -2
- lionagi/operations/_act/act.py +10 -3
- lionagi/operations/communicate/communicate.py +0 -59
- lionagi/operations/interpret/interpret.py +1 -2
- lionagi/operations/operate/operate.py +10 -5
- lionagi/operations/parse/parse.py +0 -36
- lionagi/operations/plan/plan.py +3 -3
- lionagi/operatives/action/manager.py +105 -82
- lionagi/operatives/action/request_response_model.py +31 -0
- lionagi/operatives/action/tool.py +50 -20
- lionagi/protocols/_concepts.py +1 -1
- lionagi/protocols/adapters/adapter.py +25 -0
- lionagi/protocols/adapters/json_adapter.py +107 -27
- lionagi/protocols/adapters/pandas_/csv_adapter.py +55 -11
- lionagi/protocols/adapters/pandas_/excel_adapter.py +52 -10
- lionagi/protocols/adapters/pandas_/pd_dataframe_adapter.py +54 -4
- lionagi/protocols/adapters/pandas_/pd_series_adapter.py +40 -0
- lionagi/protocols/generic/element.py +1 -1
- lionagi/protocols/generic/pile.py +5 -8
- lionagi/protocols/graph/edge.py +1 -1
- lionagi/protocols/graph/graph.py +16 -8
- lionagi/protocols/graph/node.py +1 -1
- lionagi/protocols/mail/exchange.py +126 -15
- lionagi/protocols/mail/mail.py +33 -0
- lionagi/protocols/mail/mailbox.py +62 -0
- lionagi/protocols/mail/manager.py +97 -41
- lionagi/protocols/mail/package.py +57 -3
- lionagi/protocols/messages/action_request.py +77 -26
- lionagi/protocols/messages/action_response.py +55 -26
- lionagi/protocols/messages/assistant_response.py +50 -15
- lionagi/protocols/messages/base.py +36 -0
- lionagi/protocols/messages/instruction.py +175 -145
- lionagi/protocols/messages/manager.py +152 -56
- lionagi/protocols/messages/message.py +61 -25
- lionagi/protocols/messages/system.py +54 -19
- lionagi/service/imodel.py +24 -0
- lionagi/session/branch.py +40 -32
- lionagi/utils.py +1 -0
- lionagi/version.py +1 -1
- {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/METADATA +1 -1
- {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/RECORD +43 -43
- {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/WHEEL +0 -0
- {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 | 
            -
                     | 
| 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
         | 
    
        lionagi/protocols/graph/edge.py
    CHANGED
    
    
    
        lionagi/protocols/graph/graph.py
    CHANGED
    
    | @@ -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 | 
            -
                         | 
| 207 | 
            +
                        from networkx import DiGraph  # type: ignore
         | 
| 208 | 
            +
             | 
| 208 209 | 
             
                    except ImportError:
         | 
| 209 | 
            -
                         | 
| 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 | 
            -
             | 
| 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 | 
            -
                         | 
| 242 | 
            -
             | 
| 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
         | 
    
        lionagi/protocols/graph/node.py
    CHANGED
    
    
| @@ -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 | 
            -
                    """ | 
| 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 | 
            -
                    """ | 
| 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 | 
            -
                    """ | 
| 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 | 
            -
                    """ | 
| 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 | 
            -
                     | 
| 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 | 
            -
                     | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 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
         | 
    
        lionagi/protocols/mail/mail.py
    CHANGED
    
    | @@ -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 | 
            -
                 | 
| 21 | 
            -
             | 
| 22 | 
            -
                 | 
| 23 | 
            -
                 | 
| 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 | 
            -
                     | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 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 | 
            -
                     | 
| 58 | 
            +
                    Register new sources in the MailManager.
         | 
| 44 59 |  | 
| 45 | 
            -
                     | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 60 | 
            +
                    Parameters
         | 
| 61 | 
            +
                    ----------
         | 
| 62 | 
            +
                    sources : ID.Item | ID.ItemSeq
         | 
| 63 | 
            +
                        A single source or multiple sources to be added.
         | 
| 48 64 |  | 
| 49 | 
            -
                    Raises | 
| 50 | 
            -
             | 
| 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 | 
            -
                     | 
| 70 | 
            -
             | 
| 71 | 
            -
                     | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
                         | 
| 75 | 
            -
             | 
| 76 | 
            -
                         | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 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 | 
            -
                     | 
| 114 | 
            +
                    Remove a source from the manager, discarding any associated mail.
         | 
| 89 115 |  | 
| 90 | 
            -
                     | 
| 91 | 
            -
             | 
| 116 | 
            +
                    Parameters
         | 
| 117 | 
            +
                    ----------
         | 
| 118 | 
            +
                    source_id : IDType
         | 
| 119 | 
            +
                        The ID of the source to be removed.
         | 
| 92 120 |  | 
| 93 | 
            -
                    Raises | 
| 94 | 
            -
             | 
| 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 | 
            -
                    """ | 
| 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 | 
            -
                    """ | 
| 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 | 
            -
                    """ | 
| 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 | 
            -
                    """ | 
| 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 | 
            -
                     | 
| 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 | 
            -
                     | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 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/ | 
| 224 | 
            +
            # File: lion_core/communication/manager.py
         |