lionagi 0.7.7__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.
@@ -1,4 +1,4 @@
1
- from typing import Callable
1
+ from collections.abc import Callable
2
2
 
3
3
  from lionagi.protocols._concepts import Manager
4
4
 
@@ -1,237 +1,80 @@
1
- # Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
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 Field, field_validator
5
+ from pydantic import ConfigDict, Field
8
6
  from pydantic_core import PydanticUndefined
9
7
 
10
- from lionagi.protocols.types import Node
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
- class BaseForm(Node, OperableModel):
17
- """Base form class providing core functionality for form handling.
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="The objective of the task.",
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="Fields that are outputted and presented by the form.",
28
+ description="Which fields are considered mandatory outputs.",
58
29
  )
59
- none_as_valid_value: bool = Field(
30
+ # Whether None counts as valid or incomplete
31
+ none_as_valid: bool = Field(
60
32
  default=False,
61
- description="Indicate whether to treat None as a valid value.",
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="Indicates if the task has been processed.",
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 the form is completed.
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
- Returns:
109
- True if the form is completed, False otherwise.
45
+ def check_completeness(
46
+ self, how: Literal["raise", "return_missing"] = "return_missing"
47
+ ) -> list[str]:
110
48
  """
111
- try:
112
- self.check_is_completed(handle_how="raise")
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
- if isinstance(value, str):
132
- return [value]
133
- if isinstance(value, list) and all(
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
- @property
142
- def work_fields(self) -> list[str]:
143
- """Get the list of fields that are outputted by the form.
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
- Returns:
146
- A list of field names.
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
- @property
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
- return {i: getattr(self, i, UNDEFINED) for i in self.work_fields}
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
- dict_ = {}
176
- for i in self.required_fields:
177
- dict_[i] = getattr(self, i, UNDEFINED)
178
-
179
- return dict_
180
-
181
- def get_results(
182
- self,
183
- suppress: bool = False,
184
- valid_only: bool = False,
185
- ) -> dict[str, Any]:
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