lionagi 0.17.11__py3-none-any.whl → 0.18.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lionagi/_errors.py +0 -5
- lionagi/fields.py +83 -0
- lionagi/libs/schema/minimal_yaml.py +98 -0
- lionagi/ln/__init__.py +3 -1
- lionagi/ln/concurrency/primitives.py +4 -4
- lionagi/ln/concurrency/task.py +1 -0
- lionagi/ln/types.py +32 -5
- lionagi/models/field_model.py +21 -4
- lionagi/models/hashable_model.py +2 -3
- lionagi/operations/ReAct/ReAct.py +475 -238
- lionagi/operations/ReAct/utils.py +3 -0
- lionagi/operations/act/act.py +206 -0
- lionagi/operations/builder.py +5 -7
- lionagi/operations/chat/chat.py +130 -114
- lionagi/operations/communicate/communicate.py +101 -42
- lionagi/operations/fields.py +380 -0
- lionagi/operations/flow.py +8 -10
- lionagi/operations/interpret/interpret.py +65 -20
- lionagi/operations/node.py +4 -4
- lionagi/operations/operate/operate.py +216 -108
- lionagi/{protocols/operatives → operations/operate}/operative.py +4 -5
- lionagi/{protocols/operatives → operations/operate}/step.py +34 -39
- lionagi/operations/parse/parse.py +170 -142
- lionagi/operations/select/select.py +79 -18
- lionagi/operations/select/utils.py +8 -2
- lionagi/operations/types.py +119 -23
- lionagi/protocols/action/manager.py +5 -6
- lionagi/protocols/contracts.py +2 -2
- lionagi/protocols/generic/__init__.py +22 -0
- lionagi/protocols/generic/element.py +36 -127
- lionagi/protocols/generic/log.py +3 -2
- lionagi/protocols/generic/pile.py +9 -10
- lionagi/protocols/generic/progression.py +23 -22
- lionagi/protocols/graph/edge.py +6 -5
- lionagi/protocols/ids.py +6 -49
- lionagi/protocols/messages/__init__.py +29 -0
- lionagi/protocols/messages/action_request.py +86 -184
- lionagi/protocols/messages/action_response.py +73 -131
- lionagi/protocols/messages/assistant_response.py +130 -159
- lionagi/protocols/messages/base.py +31 -22
- lionagi/protocols/messages/instruction.py +280 -625
- lionagi/protocols/messages/manager.py +112 -62
- lionagi/protocols/messages/message.py +87 -197
- lionagi/protocols/messages/system.py +52 -123
- lionagi/protocols/types.py +1 -13
- lionagi/service/connections/__init__.py +3 -0
- lionagi/service/connections/endpoint.py +0 -8
- lionagi/service/connections/providers/claude_code_cli.py +3 -2
- lionagi/service/connections/providers/oai_.py +29 -94
- lionagi/service/connections/providers/ollama_.py +3 -2
- lionagi/service/hooks/_types.py +1 -1
- lionagi/service/hooks/_utils.py +1 -1
- lionagi/service/hooks/hook_event.py +3 -8
- lionagi/service/hooks/hook_registry.py +5 -5
- lionagi/service/hooks/hooked_event.py +63 -3
- lionagi/service/imodel.py +24 -20
- lionagi/service/third_party/claude_code.py +3 -3
- lionagi/service/third_party/openai_models.py +435 -0
- lionagi/service/token_calculator.py +1 -94
- lionagi/session/branch.py +190 -400
- lionagi/session/session.py +8 -99
- lionagi/tools/file/reader.py +2 -2
- lionagi/version.py +1 -1
- {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/METADATA +6 -6
- lionagi-0.18.1.dist-info/RECORD +164 -0
- lionagi/fields/__init__.py +0 -47
- lionagi/fields/action.py +0 -188
- lionagi/fields/base.py +0 -153
- lionagi/fields/code.py +0 -239
- lionagi/fields/file.py +0 -234
- lionagi/fields/instruct.py +0 -135
- lionagi/fields/reason.py +0 -55
- lionagi/fields/research.py +0 -52
- lionagi/operations/_act/act.py +0 -86
- lionagi/operations/brainstorm/__init__.py +0 -2
- lionagi/operations/brainstorm/brainstorm.py +0 -498
- lionagi/operations/brainstorm/prompt.py +0 -11
- lionagi/operations/instruct/__init__.py +0 -2
- lionagi/operations/instruct/instruct.py +0 -28
- lionagi/operations/plan/__init__.py +0 -6
- lionagi/operations/plan/plan.py +0 -386
- lionagi/operations/plan/prompt.py +0 -25
- lionagi/operations/utils.py +0 -45
- lionagi/protocols/forms/__init__.py +0 -2
- lionagi/protocols/forms/base.py +0 -85
- lionagi/protocols/forms/flow.py +0 -79
- lionagi/protocols/forms/form.py +0 -86
- lionagi/protocols/forms/report.py +0 -48
- lionagi/protocols/mail/__init__.py +0 -2
- lionagi/protocols/mail/exchange.py +0 -220
- lionagi/protocols/mail/mail.py +0 -51
- lionagi/protocols/mail/mailbox.py +0 -103
- lionagi/protocols/mail/manager.py +0 -218
- lionagi/protocols/mail/package.py +0 -101
- lionagi/protocols/messages/templates/README.md +0 -28
- lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
- lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
- lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
- lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
- lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
- lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
- lionagi/protocols/operatives/__init__.py +0 -2
- lionagi/service/connections/providers/types.py +0 -28
- lionagi/service/third_party/openai_model_names.py +0 -198
- lionagi/service/types.py +0 -58
- lionagi-0.17.11.dist-info/RECORD +0 -199
- /lionagi/operations/{_act → act}/__init__.py +0 -0
- {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/WHEEL +0 -0
- {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/licenses/LICENSE +0 -0
lionagi/protocols/forms/form.py
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
from pydantic import ConfigDict, Field, model_validator
|
|
7
|
-
from typing_extensions import Self
|
|
8
|
-
|
|
9
|
-
from .base import BaseForm
|
|
10
|
-
from .flow import FlowDefinition
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Form(BaseForm):
|
|
14
|
-
"""
|
|
15
|
-
A domain form that can handle either a simple 'a,b->c' assignment
|
|
16
|
-
or a multi-step flow if the assignment string has semicolons, etc.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
20
|
-
|
|
21
|
-
flow_definition: FlowDefinition | None = None
|
|
22
|
-
# Possibly some extra fields, e.g. "guidance" or "task"
|
|
23
|
-
guidance: str | None = Field(default=None)
|
|
24
|
-
task: str | None = Field(default=None)
|
|
25
|
-
|
|
26
|
-
@model_validator(mode="before")
|
|
27
|
-
def parse_assignment_into_flow(cls, values):
|
|
28
|
-
"""
|
|
29
|
-
If the 'assignment' has semicolons, assume multiple steps, parse into FlowDefinition.
|
|
30
|
-
If it's a single step or no semicolons, we remain in 'simple' mode.
|
|
31
|
-
"""
|
|
32
|
-
assignment_str = values.get("assignment")
|
|
33
|
-
if assignment_str and ";" in assignment_str:
|
|
34
|
-
flow = FlowDefinition()
|
|
35
|
-
flow.parse_flow_string(assignment_str)
|
|
36
|
-
values["flow_definition"] = flow
|
|
37
|
-
return values
|
|
38
|
-
|
|
39
|
-
@model_validator(mode="after")
|
|
40
|
-
def compute_output_fields(self) -> Self:
|
|
41
|
-
"""
|
|
42
|
-
If in simple mode, we parse something like 'a,b->c' and set output_fields=[c].
|
|
43
|
-
If in multi-step mode, we set output_fields to the final produced fields of the flow.
|
|
44
|
-
"""
|
|
45
|
-
if self.flow_definition:
|
|
46
|
-
# multi-step
|
|
47
|
-
produced = self.flow_definition.get_produced_fields()
|
|
48
|
-
if not self.output_fields:
|
|
49
|
-
self.output_fields = list(produced)
|
|
50
|
-
else:
|
|
51
|
-
# single-step
|
|
52
|
-
if self.assignment and "->" in self.assignment:
|
|
53
|
-
# parse the single arrow
|
|
54
|
-
ins_outs = self.assignment.split("->", 1)
|
|
55
|
-
outs_str = ins_outs[1]
|
|
56
|
-
outs = [x.strip() for x in outs_str.split(",") if x.strip()]
|
|
57
|
-
if not self.output_fields:
|
|
58
|
-
self.output_fields = outs
|
|
59
|
-
return self
|
|
60
|
-
|
|
61
|
-
def fill_fields(self, **kwargs) -> None:
|
|
62
|
-
"""
|
|
63
|
-
A small helper: fill fields in this form by direct assignment.
|
|
64
|
-
Usually you'd do 'myform(field=val, field2=val2)', but sometimes you want partial updates.
|
|
65
|
-
"""
|
|
66
|
-
for k, v in kwargs.items():
|
|
67
|
-
setattr(self, k, v)
|
|
68
|
-
|
|
69
|
-
def to_instructions(self) -> dict[str, Any]:
|
|
70
|
-
"""
|
|
71
|
-
Return a small dictionary that an LLM can read as an 'instruction context'.
|
|
72
|
-
"""
|
|
73
|
-
return {
|
|
74
|
-
"assignment": self.assignment,
|
|
75
|
-
"flow": (
|
|
76
|
-
self.flow_definition.model_dump()
|
|
77
|
-
if self.flow_definition
|
|
78
|
-
else None
|
|
79
|
-
),
|
|
80
|
-
"guidance": self.guidance,
|
|
81
|
-
"task": self.task,
|
|
82
|
-
"required_outputs": self.output_fields,
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# File: lionagi/protocols/forms/form.py
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
from pydantic import Field
|
|
5
|
-
|
|
6
|
-
from ..generic.pile import Pile
|
|
7
|
-
from .base import BaseForm
|
|
8
|
-
from .form import Form
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Report(BaseForm):
|
|
12
|
-
"""
|
|
13
|
-
A minimal class that collects multiple completed forms as "sub-tasks."
|
|
14
|
-
If you have a single FlowDefinition that describes the entire multi-step pipeline,
|
|
15
|
-
you can track each step as a separate form in here.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
default_form_cls: type[Form] = Form
|
|
19
|
-
completed_forms: Pile[Form] = Field(
|
|
20
|
-
default_factory=lambda: Pile(item_type={Form}),
|
|
21
|
-
description="A list of forms that have been completed for this report.",
|
|
22
|
-
)
|
|
23
|
-
form_assignments: dict[str, str] = Field(
|
|
24
|
-
default_factory=dict,
|
|
25
|
-
description="Mapping from form ID -> assignment string",
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
def add_completed_form(
|
|
29
|
-
self, form: Form, update_report_fields: bool = False
|
|
30
|
-
):
|
|
31
|
-
"""
|
|
32
|
-
Add a completed form. Optionally update the report’s fields from the form's output.
|
|
33
|
-
"""
|
|
34
|
-
missing = form.check_completeness()
|
|
35
|
-
if missing:
|
|
36
|
-
raise ValueError(
|
|
37
|
-
f"Form {form.id} is incomplete: missing {missing}."
|
|
38
|
-
)
|
|
39
|
-
self.completed_forms.append(form)
|
|
40
|
-
self.form_assignments[form.id] = form.assignment or ""
|
|
41
|
-
# optionally update the report’s own fields
|
|
42
|
-
if update_report_fields:
|
|
43
|
-
for f_ in form.output_fields:
|
|
44
|
-
val = getattr(form, f_, None)
|
|
45
|
-
setattr(self, f_, val)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# File: lionagi/protocols/forms/report.py
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import asyncio
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from lionagi.protocols.generic.element import IDType
|
|
8
|
-
|
|
9
|
-
from .._concepts import Communicatable
|
|
10
|
-
from ..generic.element import ID
|
|
11
|
-
from ..generic.pile import Pile
|
|
12
|
-
from .mail import Mail, Package, PackageCategory
|
|
13
|
-
from .mailbox import Mailbox
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Exchange:
|
|
17
|
-
"""
|
|
18
|
-
Manages mail exchange operations among a set of sources that are
|
|
19
|
-
`Communicatable`. Each source has an associated `Mailbox` to store
|
|
20
|
-
inbound and outbound mail.
|
|
21
|
-
|
|
22
|
-
Attributes
|
|
23
|
-
----------
|
|
24
|
-
sources : Pile[Communicatable]
|
|
25
|
-
The communicatable sources participating in the exchange.
|
|
26
|
-
buffer : dict[IDType, list[Mail]]
|
|
27
|
-
A temporary holding area for mail messages before they reach
|
|
28
|
-
their recipient's mailbox.
|
|
29
|
-
mailboxes : dict[IDType, Mailbox]
|
|
30
|
-
Maps each source's ID to its Mailbox.
|
|
31
|
-
_execute_stop : bool
|
|
32
|
-
A flag indicating whether to stop the asynchronous execution loop.
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
def __init__(self, sources: ID[Communicatable].ItemSeq = None):
|
|
36
|
-
"""
|
|
37
|
-
Initialize an `Exchange` instance.
|
|
38
|
-
|
|
39
|
-
Parameters
|
|
40
|
-
----------
|
|
41
|
-
sources : ID[Communicatable].ItemSeq, optional
|
|
42
|
-
One or more communicatable sources to manage. If provided,
|
|
43
|
-
they are immediately added.
|
|
44
|
-
"""
|
|
45
|
-
self.sources: Pile[Communicatable] = Pile(
|
|
46
|
-
item_type={Communicatable}, strict_type=False
|
|
47
|
-
)
|
|
48
|
-
self.buffer: dict[IDType, list[Mail]] = {}
|
|
49
|
-
self.mailboxes: dict[IDType, Mailbox] = {}
|
|
50
|
-
if sources:
|
|
51
|
-
self.add_source(sources)
|
|
52
|
-
self._execute_stop: bool = False
|
|
53
|
-
|
|
54
|
-
def add_source(self, sources: ID[Communicatable].ItemSeq) -> None:
|
|
55
|
-
"""
|
|
56
|
-
Register new communicatable sources for mail exchange.
|
|
57
|
-
|
|
58
|
-
Parameters
|
|
59
|
-
----------
|
|
60
|
-
sources : ID[Communicatable].ItemSeq
|
|
61
|
-
The source(s) to be added.
|
|
62
|
-
|
|
63
|
-
Raises
|
|
64
|
-
------
|
|
65
|
-
ValueError
|
|
66
|
-
If the given sources already exist in this exchange.
|
|
67
|
-
"""
|
|
68
|
-
if sources in self.sources:
|
|
69
|
-
raise ValueError(
|
|
70
|
-
f"Source {sources} already exists in the mail manager."
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
self.sources.include(sources)
|
|
74
|
-
for source in sources:
|
|
75
|
-
self.mailboxes[source.id] = source.mailbox
|
|
76
|
-
self.buffer.update({source.id: [] for source in sources})
|
|
77
|
-
|
|
78
|
-
def delete_source(self, sources: ID[Communicatable].ItemSeq) -> None:
|
|
79
|
-
"""
|
|
80
|
-
Remove specified sources from the exchange, clearing any pending
|
|
81
|
-
mail associated with them.
|
|
82
|
-
|
|
83
|
-
Parameters
|
|
84
|
-
----------
|
|
85
|
-
sources : ID[Communicatable].ItemSeq
|
|
86
|
-
The source(s) to remove.
|
|
87
|
-
|
|
88
|
-
Raises
|
|
89
|
-
------
|
|
90
|
-
ValueError
|
|
91
|
-
If the given sources do not exist in this exchange.
|
|
92
|
-
"""
|
|
93
|
-
if not sources in self.sources:
|
|
94
|
-
raise ValueError(
|
|
95
|
-
f"Source {sources} does not exist in the mail manager."
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
self.sources.exclude(sources)
|
|
99
|
-
for source in sources:
|
|
100
|
-
self.buffer.pop(source.id)
|
|
101
|
-
self.mailboxes.pop(source.id)
|
|
102
|
-
|
|
103
|
-
@staticmethod
|
|
104
|
-
def create_mail(
|
|
105
|
-
sender: ID[Communicatable],
|
|
106
|
-
recipient: ID[Communicatable],
|
|
107
|
-
category: PackageCategory | str,
|
|
108
|
-
item: Any,
|
|
109
|
-
request_source: Any = None,
|
|
110
|
-
) -> Mail:
|
|
111
|
-
"""
|
|
112
|
-
Helper method to create a new Mail instance.
|
|
113
|
-
|
|
114
|
-
Parameters
|
|
115
|
-
----------
|
|
116
|
-
sender : ID[Communicatable]
|
|
117
|
-
The ID (or Communicatable) identifying the mail sender.
|
|
118
|
-
recipient : ID[Communicatable]
|
|
119
|
-
The ID (or Communicatable) identifying the mail recipient.
|
|
120
|
-
category : PackageCategory | str
|
|
121
|
-
A classification for the package contents.
|
|
122
|
-
item : Any
|
|
123
|
-
The actual item/data to be sent.
|
|
124
|
-
request_source : Any, optional
|
|
125
|
-
Additional context about the request origin, if any.
|
|
126
|
-
|
|
127
|
-
Returns
|
|
128
|
-
-------
|
|
129
|
-
Mail
|
|
130
|
-
A newly created Mail object ready for sending.
|
|
131
|
-
"""
|
|
132
|
-
package = Package(
|
|
133
|
-
category=category, item=item, request_source=request_source
|
|
134
|
-
)
|
|
135
|
-
return Mail(sender=sender, recipient=recipient, package=package)
|
|
136
|
-
|
|
137
|
-
def collect(self, sender: ID[Communicatable]) -> None:
|
|
138
|
-
"""
|
|
139
|
-
Collect all outbound mail from a specific sender, moving it
|
|
140
|
-
to the exchange buffer.
|
|
141
|
-
|
|
142
|
-
Parameters
|
|
143
|
-
----------
|
|
144
|
-
sender : ID[Communicatable]
|
|
145
|
-
The ID of the source from which mail is collected.
|
|
146
|
-
|
|
147
|
-
Raises
|
|
148
|
-
------
|
|
149
|
-
ValueError
|
|
150
|
-
If the sender is not part of this exchange.
|
|
151
|
-
"""
|
|
152
|
-
if sender not in self.sources:
|
|
153
|
-
raise ValueError(f"Sender source {sender} does not exist.")
|
|
154
|
-
|
|
155
|
-
sender_mailbox: Mailbox = self.sources[sender].mailbox
|
|
156
|
-
|
|
157
|
-
while sender_mailbox.pending_outs:
|
|
158
|
-
mail: Mail = sender_mailbox.pile_.popleft()
|
|
159
|
-
self.buffer[mail.recipient].append(mail)
|
|
160
|
-
|
|
161
|
-
def deliver(self, recipient: ID[Communicatable]) -> None:
|
|
162
|
-
"""
|
|
163
|
-
Deliver all mail in the buffer addressed to a specific recipient.
|
|
164
|
-
|
|
165
|
-
Parameters
|
|
166
|
-
----------
|
|
167
|
-
recipient : ID[Communicatable]
|
|
168
|
-
The ID of the source to receive mail.
|
|
169
|
-
|
|
170
|
-
Raises
|
|
171
|
-
------
|
|
172
|
-
ValueError
|
|
173
|
-
If the recipient is not part of this exchange or if mail
|
|
174
|
-
references an unknown sender.
|
|
175
|
-
"""
|
|
176
|
-
if recipient not in self.sources:
|
|
177
|
-
raise ValueError(f"Recipient source {recipient} does not exist.")
|
|
178
|
-
|
|
179
|
-
recipient_mailbox: Mailbox = self.sources[recipient].mailbox
|
|
180
|
-
|
|
181
|
-
while self.buffer[recipient]:
|
|
182
|
-
mail = self.buffer[recipient].pop(0)
|
|
183
|
-
if mail.recipient != recipient:
|
|
184
|
-
raise ValueError(
|
|
185
|
-
f"Mail recipient {mail.recipient} does not match recipient {recipient}"
|
|
186
|
-
)
|
|
187
|
-
if mail.sender not in self.sources:
|
|
188
|
-
raise ValueError(f"Mail sender {mail.sender} does not exist.")
|
|
189
|
-
recipient_mailbox.append_in(mail)
|
|
190
|
-
|
|
191
|
-
def collect_all(self) -> None:
|
|
192
|
-
"""
|
|
193
|
-
Collect mail from every source in this exchange.
|
|
194
|
-
"""
|
|
195
|
-
for source in self.sources:
|
|
196
|
-
self.collect(sender=source.id)
|
|
197
|
-
|
|
198
|
-
def deliver_all(self) -> None:
|
|
199
|
-
"""
|
|
200
|
-
Deliver mail to every source in this exchange.
|
|
201
|
-
"""
|
|
202
|
-
for source in self.sources:
|
|
203
|
-
self.deliver(recipient=source.id)
|
|
204
|
-
|
|
205
|
-
async def execute(self, refresh_time: int = 1) -> None:
|
|
206
|
-
"""
|
|
207
|
-
Continuously collect and deliver mail in an asynchronous loop.
|
|
208
|
-
|
|
209
|
-
Parameters
|
|
210
|
-
----------
|
|
211
|
-
refresh_time : int, optional
|
|
212
|
-
Number of seconds to wait between each cycle. Defaults to 1.
|
|
213
|
-
"""
|
|
214
|
-
while not self._execute_stop:
|
|
215
|
-
self.collect_all()
|
|
216
|
-
self.deliver_all()
|
|
217
|
-
await asyncio.sleep(refresh_time)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
# File: lionagi/protocols/mail/exchange.py
|
lionagi/protocols/mail/mail.py
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
from pydantic import field_validator
|
|
5
|
-
|
|
6
|
-
from .._concepts import Sendable
|
|
7
|
-
from ..generic.element import Element
|
|
8
|
-
from ..messages.base import IDType
|
|
9
|
-
from .package import Package, PackageCategory
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Mail(Element, Sendable):
|
|
13
|
-
"""
|
|
14
|
-
A single mail message that can be sent between communicatable entities.
|
|
15
|
-
It includes a sender, recipient, and a package that describes the
|
|
16
|
-
mail's content.
|
|
17
|
-
|
|
18
|
-
Attributes
|
|
19
|
-
----------
|
|
20
|
-
sender : IDType
|
|
21
|
-
The ID representing the mail sender.
|
|
22
|
-
recipient : IDType
|
|
23
|
-
The ID representing the mail recipient.
|
|
24
|
-
package : Package
|
|
25
|
-
The package (category + payload) contained in this mail.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
sender: IDType
|
|
29
|
-
recipient: IDType
|
|
30
|
-
package: Package
|
|
31
|
-
|
|
32
|
-
@field_validator("sender", "recipient")
|
|
33
|
-
def _validate_sender_recipient(cls, value):
|
|
34
|
-
"""
|
|
35
|
-
Validate that the sender and recipient fields are correct IDTypes.
|
|
36
|
-
"""
|
|
37
|
-
return IDType.validate(value)
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def category(self) -> PackageCategory:
|
|
41
|
-
"""
|
|
42
|
-
Shortcut for retrieving the category from the underlying package.
|
|
43
|
-
|
|
44
|
-
Returns
|
|
45
|
-
-------
|
|
46
|
-
PackageCategory
|
|
47
|
-
"""
|
|
48
|
-
return self.package.category
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# File: lionagi/protocols/mail/mail.py
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
from lionagi.protocols.generic.element import IDType
|
|
5
|
-
|
|
6
|
-
from ..generic.pile import Pile, Progression
|
|
7
|
-
from .mail import Mail
|
|
8
|
-
|
|
9
|
-
__all__ = ("Mailbox",)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Mailbox:
|
|
13
|
-
"""
|
|
14
|
-
A mailbox that accumulates inbound and outbound mail for a single
|
|
15
|
-
communicatable source.
|
|
16
|
-
|
|
17
|
-
Attributes
|
|
18
|
-
----------
|
|
19
|
-
pile_ : Pile[Mail]
|
|
20
|
-
A concurrency-safe collection storing all mail items.
|
|
21
|
-
pending_ins : dict[IDType, Progression]
|
|
22
|
-
Maps each sender's ID to a progression of inbound mail.
|
|
23
|
-
pending_outs : Progression
|
|
24
|
-
A progression of mail items waiting to be sent (outbound).
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
def __init__(self):
|
|
28
|
-
"""
|
|
29
|
-
Initialize an empty Mailbox with separate tracks for inbound
|
|
30
|
-
and outbound mail.
|
|
31
|
-
"""
|
|
32
|
-
self.pile_ = Pile(item_type=Mail, strict_type=True)
|
|
33
|
-
self.pending_ins: dict[IDType, Progression] = {}
|
|
34
|
-
self.pending_outs = Progression()
|
|
35
|
-
|
|
36
|
-
def __contains__(self, item: Mail) -> bool:
|
|
37
|
-
"""
|
|
38
|
-
Check if a mail item is currently in this mailbox.
|
|
39
|
-
"""
|
|
40
|
-
return item in self.pile_
|
|
41
|
-
|
|
42
|
-
@property
|
|
43
|
-
def senders(self) -> list[str]:
|
|
44
|
-
"""
|
|
45
|
-
List of sender IDs that have inbound mail in this mailbox.
|
|
46
|
-
|
|
47
|
-
Returns
|
|
48
|
-
-------
|
|
49
|
-
list[str]
|
|
50
|
-
"""
|
|
51
|
-
return list(self.pending_ins.keys())
|
|
52
|
-
|
|
53
|
-
def append_in(self, item: Mail, /):
|
|
54
|
-
"""
|
|
55
|
-
Add a mail item to the inbound queue for the item's sender.
|
|
56
|
-
|
|
57
|
-
Parameters
|
|
58
|
-
----------
|
|
59
|
-
item : Mail
|
|
60
|
-
"""
|
|
61
|
-
if item.sender not in self.pending_ins:
|
|
62
|
-
self.pending_ins[item.sender] = Progression()
|
|
63
|
-
self.pending_ins[item.sender].include(item)
|
|
64
|
-
self.pile_.include(item)
|
|
65
|
-
|
|
66
|
-
def append_out(self, item: Mail, /):
|
|
67
|
-
"""
|
|
68
|
-
Add a mail item to the outbound (pending_outs) queue.
|
|
69
|
-
|
|
70
|
-
Parameters
|
|
71
|
-
----------
|
|
72
|
-
item : Mail
|
|
73
|
-
"""
|
|
74
|
-
self.pending_outs.include(item)
|
|
75
|
-
self.pile_.include(item)
|
|
76
|
-
|
|
77
|
-
def exclude(self, item: Mail, /):
|
|
78
|
-
"""
|
|
79
|
-
Remove a mail item from all internal references (inbound, outbound, and pile).
|
|
80
|
-
|
|
81
|
-
Parameters
|
|
82
|
-
----------
|
|
83
|
-
item : Mail
|
|
84
|
-
"""
|
|
85
|
-
self.pile_.exclude(item)
|
|
86
|
-
self.pending_outs.exclude(item)
|
|
87
|
-
for v in self.pending_ins.values():
|
|
88
|
-
v.exclude(item)
|
|
89
|
-
|
|
90
|
-
def __bool__(self) -> bool:
|
|
91
|
-
"""
|
|
92
|
-
Indicates if the mailbox contains any mail.
|
|
93
|
-
"""
|
|
94
|
-
return bool(self.pile_)
|
|
95
|
-
|
|
96
|
-
def __len__(self) -> int:
|
|
97
|
-
"""
|
|
98
|
-
Number of mail items in this mailbox.
|
|
99
|
-
"""
|
|
100
|
-
return len(self.pile_)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
# File: lionagi/protocols/mail/mailbox.py
|