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.
Files changed (109) hide show
  1. lionagi/_errors.py +0 -5
  2. lionagi/fields.py +83 -0
  3. lionagi/libs/schema/minimal_yaml.py +98 -0
  4. lionagi/ln/__init__.py +3 -1
  5. lionagi/ln/concurrency/primitives.py +4 -4
  6. lionagi/ln/concurrency/task.py +1 -0
  7. lionagi/ln/types.py +32 -5
  8. lionagi/models/field_model.py +21 -4
  9. lionagi/models/hashable_model.py +2 -3
  10. lionagi/operations/ReAct/ReAct.py +475 -238
  11. lionagi/operations/ReAct/utils.py +3 -0
  12. lionagi/operations/act/act.py +206 -0
  13. lionagi/operations/builder.py +5 -7
  14. lionagi/operations/chat/chat.py +130 -114
  15. lionagi/operations/communicate/communicate.py +101 -42
  16. lionagi/operations/fields.py +380 -0
  17. lionagi/operations/flow.py +8 -10
  18. lionagi/operations/interpret/interpret.py +65 -20
  19. lionagi/operations/node.py +4 -4
  20. lionagi/operations/operate/operate.py +216 -108
  21. lionagi/{protocols/operatives → operations/operate}/operative.py +4 -5
  22. lionagi/{protocols/operatives → operations/operate}/step.py +34 -39
  23. lionagi/operations/parse/parse.py +170 -142
  24. lionagi/operations/select/select.py +79 -18
  25. lionagi/operations/select/utils.py +8 -2
  26. lionagi/operations/types.py +119 -23
  27. lionagi/protocols/action/manager.py +5 -6
  28. lionagi/protocols/contracts.py +2 -2
  29. lionagi/protocols/generic/__init__.py +22 -0
  30. lionagi/protocols/generic/element.py +36 -127
  31. lionagi/protocols/generic/log.py +3 -2
  32. lionagi/protocols/generic/pile.py +9 -10
  33. lionagi/protocols/generic/progression.py +23 -22
  34. lionagi/protocols/graph/edge.py +6 -5
  35. lionagi/protocols/ids.py +6 -49
  36. lionagi/protocols/messages/__init__.py +29 -0
  37. lionagi/protocols/messages/action_request.py +86 -184
  38. lionagi/protocols/messages/action_response.py +73 -131
  39. lionagi/protocols/messages/assistant_response.py +130 -159
  40. lionagi/protocols/messages/base.py +31 -22
  41. lionagi/protocols/messages/instruction.py +280 -625
  42. lionagi/protocols/messages/manager.py +112 -62
  43. lionagi/protocols/messages/message.py +87 -197
  44. lionagi/protocols/messages/system.py +52 -123
  45. lionagi/protocols/types.py +1 -13
  46. lionagi/service/connections/__init__.py +3 -0
  47. lionagi/service/connections/endpoint.py +0 -8
  48. lionagi/service/connections/providers/claude_code_cli.py +3 -2
  49. lionagi/service/connections/providers/oai_.py +29 -94
  50. lionagi/service/connections/providers/ollama_.py +3 -2
  51. lionagi/service/hooks/_types.py +1 -1
  52. lionagi/service/hooks/_utils.py +1 -1
  53. lionagi/service/hooks/hook_event.py +3 -8
  54. lionagi/service/hooks/hook_registry.py +5 -5
  55. lionagi/service/hooks/hooked_event.py +63 -3
  56. lionagi/service/imodel.py +24 -20
  57. lionagi/service/third_party/claude_code.py +3 -3
  58. lionagi/service/third_party/openai_models.py +435 -0
  59. lionagi/service/token_calculator.py +1 -94
  60. lionagi/session/branch.py +190 -400
  61. lionagi/session/session.py +8 -99
  62. lionagi/tools/file/reader.py +2 -2
  63. lionagi/version.py +1 -1
  64. {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/METADATA +6 -6
  65. lionagi-0.18.1.dist-info/RECORD +164 -0
  66. lionagi/fields/__init__.py +0 -47
  67. lionagi/fields/action.py +0 -188
  68. lionagi/fields/base.py +0 -153
  69. lionagi/fields/code.py +0 -239
  70. lionagi/fields/file.py +0 -234
  71. lionagi/fields/instruct.py +0 -135
  72. lionagi/fields/reason.py +0 -55
  73. lionagi/fields/research.py +0 -52
  74. lionagi/operations/_act/act.py +0 -86
  75. lionagi/operations/brainstorm/__init__.py +0 -2
  76. lionagi/operations/brainstorm/brainstorm.py +0 -498
  77. lionagi/operations/brainstorm/prompt.py +0 -11
  78. lionagi/operations/instruct/__init__.py +0 -2
  79. lionagi/operations/instruct/instruct.py +0 -28
  80. lionagi/operations/plan/__init__.py +0 -6
  81. lionagi/operations/plan/plan.py +0 -386
  82. lionagi/operations/plan/prompt.py +0 -25
  83. lionagi/operations/utils.py +0 -45
  84. lionagi/protocols/forms/__init__.py +0 -2
  85. lionagi/protocols/forms/base.py +0 -85
  86. lionagi/protocols/forms/flow.py +0 -79
  87. lionagi/protocols/forms/form.py +0 -86
  88. lionagi/protocols/forms/report.py +0 -48
  89. lionagi/protocols/mail/__init__.py +0 -2
  90. lionagi/protocols/mail/exchange.py +0 -220
  91. lionagi/protocols/mail/mail.py +0 -51
  92. lionagi/protocols/mail/mailbox.py +0 -103
  93. lionagi/protocols/mail/manager.py +0 -218
  94. lionagi/protocols/mail/package.py +0 -101
  95. lionagi/protocols/messages/templates/README.md +0 -28
  96. lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
  97. lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
  98. lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
  99. lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
  100. lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
  101. lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
  102. lionagi/protocols/operatives/__init__.py +0 -2
  103. lionagi/service/connections/providers/types.py +0 -28
  104. lionagi/service/third_party/openai_model_names.py +0 -198
  105. lionagi/service/types.py +0 -58
  106. lionagi-0.17.11.dist-info/RECORD +0 -199
  107. /lionagi/operations/{_act → act}/__init__.py +0 -0
  108. {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/WHEEL +0 -0
  109. {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/licenses/LICENSE +0 -0
@@ -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,2 +0,0 @@
1
- # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
- # SPDX-License-Identifier: Apache-2.0
@@ -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
@@ -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