lionagi 0.7.8__py3-none-any.whl → 0.8.0__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/manager.py +1 -1
- lionagi/operatives/forms/base.py +49 -206
- lionagi/operatives/forms/flow.py +74 -0
- lionagi/operatives/forms/form.py +55 -759
- lionagi/operatives/forms/report.py +23 -301
- lionagi/operatives/types.py +3 -0
- lionagi/version.py +1 -1
- {lionagi-0.7.8.dist-info → lionagi-0.8.0.dist-info}/METADATA +1 -1
- {lionagi-0.7.8.dist-info → lionagi-0.8.0.dist-info}/RECORD +11 -11
- lionagi/operatives/forms/utils.py +0 -26
- {lionagi-0.7.8.dist-info → lionagi-0.8.0.dist-info}/WHEEL +0 -0
- {lionagi-0.7.8.dist-info → lionagi-0.8.0.dist-info}/licenses/LICENSE +0 -0
lionagi/operations/manager.py
CHANGED
lionagi/operatives/forms/base.py
CHANGED
@@ -1,237 +1,80 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
1
|
+
# forms/base_form.py
|
4
2
|
|
5
3
|
from typing import Any, Literal
|
6
4
|
|
7
|
-
from pydantic import
|
5
|
+
from pydantic import ConfigDict, Field
|
8
6
|
from pydantic_core import PydanticUndefined
|
9
7
|
|
10
|
-
from lionagi.protocols.
|
8
|
+
from lionagi.protocols.generic.element import Element
|
11
9
|
from lionagi.utils import UNDEFINED
|
12
10
|
|
13
|
-
from ..models.operable_model import OperableModel
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
This class serves as a foundation for creating custom forms within the
|
20
|
-
lion-core library. It includes methods for managing output fields,
|
21
|
-
handling results, and applying field annotations.
|
22
|
-
|
23
|
-
The BaseForm class focuses on output fields, which are fields that are
|
24
|
-
presented as the result of form processing. Output fields can include all,
|
25
|
-
part, or none of the request fields and can be conditionally modified by
|
26
|
-
the process if the form is not set to be strict.
|
27
|
-
|
28
|
-
Attributes:
|
29
|
-
assignment: The objective of the task, which may define how input
|
30
|
-
fields are processed into output fields.
|
31
|
-
template_name: The name of the form template.
|
32
|
-
output_fields: A list of field names that are outputted and presented
|
33
|
-
by the form.
|
34
|
-
none_as_valid_value: Indicates whether to treat None as a valid value
|
35
|
-
when processing output fields.
|
36
|
-
has_processed: Indicates if the task has been processed.
|
37
|
-
|
38
|
-
Example:
|
39
|
-
>>> form = BaseForm(
|
40
|
-
... assignment="input1, input2 -> output",
|
41
|
-
... output_fields=["output1", "output2"],
|
42
|
-
... )
|
43
|
-
>>> result = form.get_results()
|
44
|
-
>>> print(result)
|
12
|
+
class BaseForm(Element):
|
13
|
+
"""
|
14
|
+
A minimal base form class to store fields and define output logic.
|
15
|
+
Typically, you'll inherit from this for domain-specific forms.
|
45
16
|
"""
|
46
17
|
|
18
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
19
|
+
|
20
|
+
# A short "assignment" describing input->output
|
47
21
|
assignment: str | None = Field(
|
48
22
|
default=None,
|
49
|
-
description="
|
50
|
-
examples=["input1, input2 -> output"],
|
51
|
-
)
|
52
|
-
template_name: str = Field(
|
53
|
-
default="default_form", description="Name of the form template"
|
23
|
+
description="A small DSL describing transformation, e.g. 'a,b -> c'.",
|
54
24
|
)
|
25
|
+
# Which fields are produced as 'final' or 'required' outputs.
|
55
26
|
output_fields: list[str] = Field(
|
56
27
|
default_factory=list,
|
57
|
-
description="
|
28
|
+
description="Which fields are considered mandatory outputs.",
|
58
29
|
)
|
59
|
-
|
30
|
+
# Whether None counts as valid or incomplete
|
31
|
+
none_as_valid: bool = Field(
|
60
32
|
default=False,
|
61
|
-
description="
|
33
|
+
description="If True, None is accepted as a valid value for completion checks.",
|
62
34
|
)
|
63
35
|
has_processed: bool = Field(
|
64
36
|
default=False,
|
65
|
-
description="
|
66
|
-
exclude=True,
|
37
|
+
description="Marks if the form is considered completed or 'processed'.",
|
67
38
|
)
|
68
39
|
|
69
|
-
def check_is_completed(
|
70
|
-
self,
|
71
|
-
handle_how: Literal["raise", "return_missing"] = "raise",
|
72
|
-
) -> list[str] | None:
|
73
|
-
"""Check if all required fields are completed.
|
74
|
-
|
75
|
-
Args:
|
76
|
-
handle_how: How to handle incomplete fields.
|
77
|
-
|
78
|
-
Returns:
|
79
|
-
List of incomplete fields if handle_how is "return_missing",
|
80
|
-
None otherwise.
|
81
|
-
|
82
|
-
Raises:
|
83
|
-
ValueError: If required fields are incomplete and handle_how
|
84
|
-
is "raise".
|
85
|
-
"""
|
86
|
-
non_complete_request = []
|
87
|
-
invalid_values = [UNDEFINED, PydanticUndefined]
|
88
|
-
if not self.none_as_valid_value:
|
89
|
-
invalid_values.append(None)
|
90
|
-
|
91
|
-
for i in self.required_fields:
|
92
|
-
if getattr(self, i) in invalid_values:
|
93
|
-
non_complete_request.append(i)
|
94
|
-
|
95
|
-
if non_complete_request:
|
96
|
-
if handle_how == "raise":
|
97
|
-
raise ValueError(
|
98
|
-
f"Incomplete request fields: {non_complete_request}"
|
99
|
-
)
|
100
|
-
elif handle_how == "return_missing":
|
101
|
-
return non_complete_request
|
102
|
-
else:
|
103
|
-
self.has_processed = True
|
104
|
-
|
105
40
|
def is_completed(self) -> bool:
|
106
|
-
"""Check if
|
41
|
+
"""Check if all required output fields are set (and not UNDEFINED/None if not allowed)."""
|
42
|
+
missing = self.check_completeness()
|
43
|
+
return not missing
|
107
44
|
|
108
|
-
|
109
|
-
|
45
|
+
def check_completeness(
|
46
|
+
self, how: Literal["raise", "return_missing"] = "return_missing"
|
47
|
+
) -> list[str]:
|
110
48
|
"""
|
111
|
-
|
112
|
-
|
113
|
-
return True
|
114
|
-
except Exception:
|
115
|
-
return False
|
116
|
-
|
117
|
-
@field_validator("output_fields", mode="before")
|
118
|
-
@classmethod
|
119
|
-
def _validate_output(cls, value):
|
120
|
-
"""Validate the output_fields attribute.
|
121
|
-
|
122
|
-
Args:
|
123
|
-
value: The value to validate.
|
124
|
-
|
125
|
-
Returns:
|
126
|
-
The validated value.
|
127
|
-
|
128
|
-
Raises:
|
129
|
-
LionValueError: If the value is invalid.
|
49
|
+
Return a list of any 'required' output fields that are missing or invalid.
|
50
|
+
If how='raise', raise an exception if missing any.
|
130
51
|
"""
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
[i for i in value if isinstance(i, str)],
|
135
|
-
):
|
136
|
-
return value
|
137
|
-
if not value:
|
138
|
-
return []
|
139
|
-
raise ValueError("Invalid output fields.")
|
52
|
+
invalid_vals = [UNDEFINED, PydanticUndefined]
|
53
|
+
if not self.none_as_valid:
|
54
|
+
invalid_vals.append(None)
|
140
55
|
|
141
|
-
|
142
|
-
|
143
|
-
|
56
|
+
missing = []
|
57
|
+
for f in self.output_fields:
|
58
|
+
val = getattr(self, f, UNDEFINED)
|
59
|
+
if val in invalid_vals:
|
60
|
+
missing.append(f)
|
144
61
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
return self.output_fields
|
62
|
+
if missing and how == "raise":
|
63
|
+
raise ValueError(f"Form missing required fields: {missing}")
|
64
|
+
return missing
|
149
65
|
|
150
|
-
|
151
|
-
def work_dict(self) -> dict[str, Any]:
|
152
|
-
"""Get a dictionary of all work fields and their values.
|
153
|
-
|
154
|
-
Returns:
|
155
|
-
A dictionary of field names and their values.
|
66
|
+
def get_results(self, valid_only: bool = False) -> dict[str, Any]:
|
156
67
|
"""
|
157
|
-
|
158
|
-
|
159
|
-
@property
|
160
|
-
def required_fields(self) -> list[str]:
|
161
|
-
"""Get the list of required fields for the form.
|
162
|
-
|
163
|
-
Returns:
|
164
|
-
A list of required field names.
|
165
|
-
"""
|
166
|
-
return self.output_fields
|
167
|
-
|
168
|
-
@property
|
169
|
-
def required_dict(self) -> dict[str, Any]:
|
170
|
-
"""Get a dictionary of all required fields and their values.
|
171
|
-
|
172
|
-
Returns:
|
173
|
-
A dictionary of required field names and their values.
|
68
|
+
Return a dict of all `output_fields`, optionally skipping invalid/None if `valid_only`.
|
174
69
|
"""
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
"""Retrieve the results of the form.
|
187
|
-
|
188
|
-
Args:
|
189
|
-
suppress: If True, suppress errors for missing fields.
|
190
|
-
valid_only: If True, return only valid (non-empty) results.
|
191
|
-
|
192
|
-
Returns:
|
193
|
-
A dictionary of field names and their values.
|
194
|
-
|
195
|
-
Raises:
|
196
|
-
ValueError: If a required field is missing and suppress is False.
|
197
|
-
"""
|
198
|
-
result = {}
|
199
|
-
out_fields = self.output_fields or getattr(self, "request_fields", [])
|
200
|
-
|
201
|
-
for i in out_fields:
|
202
|
-
if i not in self.all_fields:
|
203
|
-
if not suppress:
|
204
|
-
raise ValueError(f"Missing field: {i}")
|
205
|
-
else:
|
206
|
-
result[i] = getattr(self, i, UNDEFINED)
|
207
|
-
|
208
|
-
# add a validator for output fields
|
209
|
-
|
210
|
-
if valid_only:
|
211
|
-
invalid_values = [
|
212
|
-
UNDEFINED,
|
213
|
-
PydanticUndefined,
|
214
|
-
]
|
215
|
-
if not self.none_as_valid_value:
|
216
|
-
invalid_values.append(None)
|
217
|
-
|
218
|
-
res_ = {}
|
219
|
-
for k, v in result.items():
|
220
|
-
if v not in invalid_values:
|
221
|
-
res_[k] = v
|
222
|
-
result = res_
|
223
|
-
|
224
|
-
return result
|
225
|
-
|
226
|
-
@property
|
227
|
-
def display_dict(self) -> dict[str, Any]:
|
228
|
-
"""
|
229
|
-
Get a dictionary of the required fields and their values for display.
|
230
|
-
|
231
|
-
Returns:
|
232
|
-
A dictionary of required field names and their values.
|
233
|
-
"""
|
234
|
-
return self.required_dict
|
235
|
-
|
236
|
-
|
237
|
-
__all__ = ["BaseForm"]
|
70
|
+
results = {}
|
71
|
+
invalid_vals = [UNDEFINED, PydanticUndefined]
|
72
|
+
if not self.none_as_valid:
|
73
|
+
invalid_vals.append(None)
|
74
|
+
|
75
|
+
for f in self.output_fields:
|
76
|
+
val = getattr(self, f, UNDEFINED)
|
77
|
+
if valid_only and val in invalid_vals:
|
78
|
+
continue
|
79
|
+
results[f] = val
|
80
|
+
return results
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# forms/flow.py
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field
|
5
|
+
|
6
|
+
|
7
|
+
class FlowStep(BaseModel):
|
8
|
+
"""
|
9
|
+
A minimal 'step' describing one transformation from some input fields to some output fields.
|
10
|
+
"""
|
11
|
+
|
12
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
13
|
+
|
14
|
+
name: str = Field(..., description="Identifier for the step.")
|
15
|
+
inputs: list[str] = Field(
|
16
|
+
..., description="Which fields are needed for this step."
|
17
|
+
)
|
18
|
+
outputs: list[str] = Field(
|
19
|
+
..., description="Which fields are produced by this step."
|
20
|
+
)
|
21
|
+
description: str | None = None # optional text doc
|
22
|
+
|
23
|
+
|
24
|
+
class FlowDefinition(BaseModel):
|
25
|
+
"""
|
26
|
+
A minimal DSL-based multi-step flow, e.g. 'a,b->c; c->d' to yield two steps.
|
27
|
+
"""
|
28
|
+
|
29
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
30
|
+
|
31
|
+
steps: list[FlowStep] = Field(default_factory=list)
|
32
|
+
|
33
|
+
def parse_flow_string(self, flow_str: str):
|
34
|
+
"""
|
35
|
+
Parse a string like 'a,b->c; c->d' into multiple FlowSteps.
|
36
|
+
We'll store them in self.steps in order.
|
37
|
+
"""
|
38
|
+
if not flow_str:
|
39
|
+
return
|
40
|
+
segments = [seg.strip() for seg in flow_str.split(";") if seg.strip()]
|
41
|
+
for i, seg in enumerate(segments):
|
42
|
+
# seg might be like 'a,b->c' or 'a->b, c' etc
|
43
|
+
if "->" not in seg:
|
44
|
+
raise ValueError(f"Invalid DSL segment (no '->'): '{seg}'")
|
45
|
+
ins_str, outs_str = seg.split("->", 1)
|
46
|
+
inputs = [x.strip() for x in ins_str.split(",") if x.strip()]
|
47
|
+
outputs = [y.strip() for y in outs_str.split(",") if y.strip()]
|
48
|
+
step = FlowStep(name=f"step_{i+1}", inputs=inputs, outputs=outputs)
|
49
|
+
self.steps.append(step)
|
50
|
+
|
51
|
+
def get_required_fields(self) -> set[str]:
|
52
|
+
"""
|
53
|
+
Return all fields that are used as inputs in the earliest steps but not produced by prior steps.
|
54
|
+
This is a minimal approach; or we can do more advanced logic if needed.
|
55
|
+
"""
|
56
|
+
produced = set()
|
57
|
+
required = set()
|
58
|
+
for step in self.steps:
|
59
|
+
# anything not yet produced is needed
|
60
|
+
for i in step.inputs:
|
61
|
+
if i not in produced:
|
62
|
+
required.add(i)
|
63
|
+
for o in step.outputs:
|
64
|
+
produced.add(o)
|
65
|
+
return required
|
66
|
+
|
67
|
+
def get_produced_fields(self) -> set[str]:
|
68
|
+
"""
|
69
|
+
Return all fields that eventually get produced by any step.
|
70
|
+
"""
|
71
|
+
result = set()
|
72
|
+
for st in self.steps:
|
73
|
+
result.update(st.outputs)
|
74
|
+
return result
|