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.
- 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/utils.py +4 -1
- lionagi/version.py +1 -1
- {lionagi-0.7.7.dist-info → lionagi-0.8.0.dist-info}/METADATA +1 -1
- {lionagi-0.7.7.dist-info → lionagi-0.8.0.dist-info}/RECORD +12 -12
- lionagi/operatives/forms/utils.py +0 -26
- {lionagi-0.7.7.dist-info → lionagi-0.8.0.dist-info}/WHEEL +0 -0
- {lionagi-0.7.7.dist-info → lionagi-0.8.0.dist-info}/licenses/LICENSE +0 -0
lionagi/operatives/forms/form.py
CHANGED
@@ -1,786 +1,82 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
1
|
+
# forms/form.py
|
4
2
|
|
5
|
-
import
|
6
|
-
from typing import Any, Literal, TypeVar
|
3
|
+
from typing import Any, Optional
|
7
4
|
|
8
|
-
from pydantic import Field, model_validator
|
9
|
-
from
|
10
|
-
from pydantic_core import PydanticUndefined
|
11
|
-
from typing_extensions import override
|
5
|
+
from pydantic import ConfigDict, Field, model_validator
|
6
|
+
from typing_extensions import Self
|
12
7
|
|
13
|
-
from lionagi.utils import UNDEFINED, copy
|
14
|
-
|
15
|
-
from ..models.note import Note
|
16
8
|
from .base import BaseForm
|
17
|
-
from .
|
18
|
-
|
19
|
-
T = TypeVar("T", bound=BaseForm)
|
9
|
+
from .flow import FlowDefinition
|
20
10
|
|
21
11
|
|
22
12
|
class Form(BaseForm):
|
23
13
|
"""
|
24
|
-
|
25
|
-
|
26
|
-
This class builds on BaseForm to introduce concepts specific to task-based
|
27
|
-
forms, including input fields, request fields, and task-related attributes
|
28
|
-
It is designed for tasks that require filling in fields through an
|
29
|
-
intelligent process, providing the necessary framework for managing and
|
30
|
-
validating these fields.
|
31
|
-
|
32
|
-
Key Concepts:
|
33
|
-
- input_fields: Fields required to obtain the request fields.
|
34
|
-
- request_fields: Fields that need to be filled by an intelligent process.
|
35
|
-
- output_fields: Fields for presentation, which may include all, some, or
|
36
|
-
none of the request fields. These can be conditionally modified if the
|
37
|
-
form is not strict_form.
|
38
|
-
|
39
|
-
The Form class provides methods for field management, validation, and
|
40
|
-
task instruction generation. It supports both strict_form and flexible
|
41
|
-
modes of operation, allowing for immutable fields and assignments when
|
42
|
-
needed.
|
43
|
-
|
44
|
-
Attributes:
|
45
|
-
strict_form (bool): If True, form fields and assignment are immutable.
|
46
|
-
guidance (str | dict[str, Any] | None): High-level task guidance that
|
47
|
-
can be optimized by AI or provided as instructions.
|
48
|
-
input_fields (list[str]): Fields needed to obtain the requested fields.
|
49
|
-
request_fields (list[str]): Fields to be filled by an intelligent
|
50
|
-
process.
|
51
|
-
task (Any): The work to be done, including custom instructions.
|
52
|
-
task_description (str | None): A detailed description of the task.
|
53
|
-
init_input_kwargs (dict[str, Any]): Initial keyword arguments for
|
54
|
-
input fields.
|
55
|
-
|
56
|
-
Example:
|
57
|
-
>>> form = Form(
|
58
|
-
... assignment="input1, input2 -> output",
|
59
|
-
... strict_form=True,
|
60
|
-
... guidance="Complete the task with the given inputs."
|
61
|
-
... )
|
62
|
-
>>> form.fill_input_fields(input1="value1", input2="value2")
|
63
|
-
>>> print(form.get_results())
|
14
|
+
A domain form that can handle either a simple 'a,b->c' assignment
|
15
|
+
or a multi-step flow if the assignment string has semicolons, etc.
|
64
16
|
"""
|
65
17
|
|
66
|
-
|
67
|
-
default=False,
|
68
|
-
description="If True, form fields and assignment are immutable.",
|
69
|
-
frozen=True,
|
70
|
-
)
|
71
|
-
guidance: str | dict[str, Any] | None = Field(
|
72
|
-
default=None,
|
73
|
-
description="High-level task guidance, optimizable by AI.",
|
74
|
-
)
|
75
|
-
input_fields: list[str] = Field(
|
76
|
-
default_factory=list,
|
77
|
-
description="Fields required to obtain the requested fields.",
|
78
|
-
)
|
79
|
-
request_fields: list[str] = Field(
|
80
|
-
default_factory=list,
|
81
|
-
description="Fields to be filled by an intelligent process.",
|
82
|
-
)
|
83
|
-
task: Any = Field(
|
84
|
-
default_factory=str,
|
85
|
-
description="Work to be done, including custom instructions.",
|
86
|
-
)
|
87
|
-
task_description: str | None = Field(
|
88
|
-
default_factory=str,
|
89
|
-
description="Detailed description of the task",
|
90
|
-
)
|
91
|
-
init_input_kwargs: dict[str, Any] = Field(
|
92
|
-
default_factory=dict,
|
93
|
-
exclude=True,
|
94
|
-
)
|
95
|
-
|
96
|
-
def check_is_completed(
|
97
|
-
self,
|
98
|
-
handle_how: Literal["return_missing", "raise"] = "raise",
|
99
|
-
) -> list[str] | None:
|
100
|
-
"""Check if all required fields are completed.
|
18
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
101
19
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
List of incomplete fields if handle_how is "return_missing",
|
107
|
-
None otherwise.
|
108
|
-
|
109
|
-
Raises:
|
110
|
-
ValueError: If required fields are incomplete and handle_how
|
111
|
-
is "raise".
|
112
|
-
"""
|
113
|
-
if self.strict_form and self.has_processed:
|
114
|
-
return
|
115
|
-
return super().check_is_completed(handle_how)
|
20
|
+
flow_definition: FlowDefinition | None = None
|
21
|
+
# Possibly some extra fields, e.g. "guidance" or "task"
|
22
|
+
guidance: str | None = Field(default=None)
|
23
|
+
task: str | None = Field(default=None)
|
116
24
|
|
117
25
|
@model_validator(mode="before")
|
118
|
-
|
119
|
-
def check_input_output_list_omitted(cls, data: Any) -> dict[str, Any]:
|
120
|
-
"""Validate and process the input data before model creation.
|
121
|
-
|
122
|
-
Args:
|
123
|
-
data: The input data for model creation.
|
124
|
-
|
125
|
-
Returns:
|
126
|
-
The validated and processed input data.
|
127
|
-
|
128
|
-
Raises:
|
129
|
-
ValueError: If the input data is invalid or missing required fields
|
26
|
+
def parse_assignment_into_flow(cls, values):
|
130
27
|
"""
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
else:
|
141
|
-
data["assignment"] = cls.model_fields[
|
142
|
-
"assignment"
|
143
|
-
].get_default()
|
144
|
-
|
145
|
-
if "input_fields" in data:
|
146
|
-
raise ValueError("Explicit input fields are not allowed.")
|
147
|
-
|
148
|
-
if "request_fields" in data:
|
149
|
-
raise ValueError("Explicit request fields are not allowed.")
|
150
|
-
|
151
|
-
if "task" in data:
|
152
|
-
raise ValueError("Explicit task fields are not allowed.")
|
153
|
-
|
154
|
-
fields = get_input_output_fields(data.get("assignment"))
|
155
|
-
input_fields, request_fields = fields
|
156
|
-
|
157
|
-
if not input_fields or input_fields == [""]:
|
158
|
-
raise ValueError("Missing input fields.")
|
159
|
-
|
160
|
-
elif not request_fields or request_fields == [""]:
|
161
|
-
raise ValueError("Missing request fields.")
|
162
|
-
|
163
|
-
data["input_fields"] = input_fields
|
164
|
-
data["request_fields"] = request_fields
|
165
|
-
data["output_fields"] = data.get("output_fields", request_fields)
|
166
|
-
data["init_input_kwargs"] = {}
|
167
|
-
data["strict_form"] = data.get("strict_form", False)
|
168
|
-
|
169
|
-
for in_ in data["input_fields"]:
|
170
|
-
data["init_input_kwargs"][in_] = (
|
171
|
-
data.pop(in_, UNDEFINED)
|
172
|
-
if in_ not in cls.model_fields
|
173
|
-
else data.get(in_, UNDEFINED)
|
174
|
-
)
|
175
|
-
|
176
|
-
return data
|
28
|
+
If the 'assignment' has semicolons, assume multiple steps, parse into FlowDefinition.
|
29
|
+
If it's a single step or no semicolons, we remain in 'simple' mode.
|
30
|
+
"""
|
31
|
+
assignment_str = values.get("assignment")
|
32
|
+
if assignment_str and ";" in assignment_str:
|
33
|
+
flow = FlowDefinition()
|
34
|
+
flow.parse_flow_string(assignment_str)
|
35
|
+
values["flow_definition"] = flow
|
36
|
+
return values
|
177
37
|
|
178
38
|
@model_validator(mode="after")
|
179
|
-
def
|
39
|
+
def compute_output_fields(self) -> Self:
|
180
40
|
"""
|
181
|
-
|
182
|
-
|
183
|
-
Returns:
|
184
|
-
The validated Form instance.
|
41
|
+
If in simple mode, we parse something like 'a,b->c' and set output_fields=[c].
|
42
|
+
If in multi-step mode, we set output_fields to the final produced fields of the flow.
|
185
43
|
"""
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
self.
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
44
|
+
if self.flow_definition:
|
45
|
+
# multi-step
|
46
|
+
produced = self.flow_definition.get_produced_fields()
|
47
|
+
if not self.output_fields:
|
48
|
+
self.output_fields = list(produced)
|
49
|
+
else:
|
50
|
+
# single-step
|
51
|
+
if self.assignment and "->" in self.assignment:
|
52
|
+
# parse the single arrow
|
53
|
+
ins_outs = self.assignment.split("->", 1)
|
54
|
+
outs_str = ins_outs[1]
|
55
|
+
outs = [x.strip() for x in outs_str.split(",") if x.strip()]
|
56
|
+
if not self.output_fields:
|
57
|
+
self.output_fields = outs
|
199
58
|
return self
|
200
59
|
|
201
|
-
|
202
|
-
@property
|
203
|
-
def work_fields(self) -> list[str]:
|
204
|
-
"""Return a list of all fields involved in the task."""
|
205
|
-
return self.input_fields + self.request_fields
|
206
|
-
|
207
|
-
@override
|
208
|
-
@property
|
209
|
-
def required_fields(self) -> list[str]:
|
210
|
-
"""Return a list of all unique required fields."""
|
211
|
-
return list(
|
212
|
-
set(self.input_fields + self.request_fields + self.output_fields),
|
213
|
-
)
|
214
|
-
|
215
|
-
@property
|
216
|
-
def validation_kwargs(self):
|
217
|
-
"""Get validation keyword arguments for each work field."""
|
218
|
-
dict_ = {}
|
219
|
-
for i in self.work_fields:
|
220
|
-
dict_[i] = self.field_getattr(i, "validation_kwargs", {})
|
221
|
-
return dict_
|
222
|
-
|
223
|
-
@property
|
224
|
-
def instruction_dict(self) -> dict[str, Any]:
|
225
|
-
"""Return a dictionary with task instruction information."""
|
226
|
-
return {
|
227
|
-
"context": self.instruction_context,
|
228
|
-
"instruction": self.instruction_prompt,
|
229
|
-
"request_fields": self.instruction_request_fields,
|
230
|
-
}
|
231
|
-
|
232
|
-
@property
|
233
|
-
def instruction_context(self) -> str:
|
234
|
-
"""Generate a description of the form's input fields."""
|
235
|
-
a = self.all_fields
|
236
|
-
context = "### Input Fields:\n"
|
237
|
-
for idx, i in enumerate(self.input_fields):
|
238
|
-
context += f"Input No.{idx+1}: {i}\n"
|
239
|
-
if getattr(a[i], "description", None):
|
240
|
-
context += f" - description: {a[i].description}.\n"
|
241
|
-
context += f" - value: {getattr(self, i)}.\n"
|
242
|
-
return context
|
243
|
-
|
244
|
-
@property
|
245
|
-
def instruction_prompt(self) -> str:
|
246
|
-
"""Generate a task instruction prompt for the form."""
|
247
|
-
a = self.all_fields
|
248
|
-
prompt = ""
|
249
|
-
if "guidance" in a:
|
250
|
-
prompt += f"### Overall Guidance:\n{getattr(self, 'guidance')}.\n"
|
251
|
-
|
252
|
-
in_fields = ", ".join(self.input_fields)
|
253
|
-
out_fields = ", ".join(self.output_fields)
|
254
|
-
prompt += "### Task Instructions:\n"
|
255
|
-
prompt += f"1. Provided Input Fields: {in_fields}.\n"
|
256
|
-
prompt += f"2. Requested Output Fields: {out_fields}.\n"
|
257
|
-
prompt += f"3. Your task:\n{self.task}.\n"
|
258
|
-
|
259
|
-
return prompt
|
260
|
-
|
261
|
-
@property
|
262
|
-
def instruction_request_fields(self) -> dict[str, str]:
|
263
|
-
"""Get descriptions of the form's requested fields."""
|
264
|
-
a = self.all_fields
|
265
|
-
|
266
|
-
context = "### Output Fields:\n"
|
267
|
-
for idx, i in enumerate(self.request_fields):
|
268
|
-
context += f"Input No.{idx+1}: {i}\n"
|
269
|
-
if getattr(a[i], "description", None):
|
270
|
-
context += f" - description: {a[i].description}.\n"
|
271
|
-
if getattr(a[i], "examples", None):
|
272
|
-
context += f" - examples: {a[i].examples}.\n"
|
273
|
-
|
274
|
-
return context
|
275
|
-
|
276
|
-
@override
|
277
|
-
def update_field(
|
278
|
-
self,
|
279
|
-
field_name: str,
|
280
|
-
value: Any = UNDEFINED,
|
281
|
-
annotation: Any = UNDEFINED,
|
282
|
-
field_obj: FieldInfo | Any = UNDEFINED,
|
283
|
-
**kwargs: Any,
|
284
|
-
) -> None:
|
285
|
-
"""Update a field in the form.
|
286
|
-
|
287
|
-
Extends the base update_field method to also update the
|
288
|
-
init_input_kwargs dictionary.
|
289
|
-
|
290
|
-
Args:
|
291
|
-
field_name: The name of the field to update.
|
292
|
-
value: The value to assign to the field.
|
293
|
-
annotation: The type annotation for the field.
|
294
|
-
field_obj: The field object containing metadata.
|
295
|
-
**kwargs: Additional keyword arguments for field configuration.
|
60
|
+
def fill_fields(self, **kwargs) -> None:
|
296
61
|
"""
|
297
|
-
|
298
|
-
|
299
|
-
value=value,
|
300
|
-
annotation=annotation,
|
301
|
-
field_obj=field_obj,
|
302
|
-
**kwargs,
|
303
|
-
)
|
304
|
-
self._fill_init_input_kwargs(field_name)
|
305
|
-
|
306
|
-
@override
|
307
|
-
def __setattr__(self, field_name: str, value: Any) -> None:
|
308
|
-
"""Set an attribute of the form.
|
309
|
-
|
310
|
-
This method enforces the strict_form attribute, preventing
|
311
|
-
modifications to certain fields when strict_form mode is enabled.
|
312
|
-
|
313
|
-
Args:
|
314
|
-
field_name: The name of the attribute to set.
|
315
|
-
value: The value to assign to the attribute.
|
316
|
-
|
317
|
-
Raises:
|
318
|
-
ValueError: If attempting to modify a restrict_formed field
|
319
|
-
in strict_form mode.
|
62
|
+
A small helper: fill fields in this form by direct assignment.
|
63
|
+
Usually you'd do 'myform(field=val, field2=val2)', but sometimes you want partial updates.
|
320
64
|
"""
|
321
|
-
|
322
|
-
|
323
|
-
"input_fields",
|
324
|
-
"request_fields",
|
325
|
-
}:
|
326
|
-
raise ValueError(
|
327
|
-
f"Cannot modify {field_name} in strict form mode."
|
328
|
-
)
|
329
|
-
|
330
|
-
if field_name in {"input_fields", "request_fields"}:
|
331
|
-
raise ValueError("Cannot modify input or request fields.")
|
332
|
-
|
333
|
-
if field_name in {"init_input_kwargs"}:
|
334
|
-
raise ValueError(f"Cannot modify restricted field {field_name}.")
|
335
|
-
|
336
|
-
super().__setattr__(field_name, value)
|
337
|
-
self._fill_init_input_kwargs(field_name)
|
338
|
-
|
339
|
-
def _fill_init_input_kwargs(self, field_name: str) -> None:
|
340
|
-
"""Update init_input_kwargs if the field is an input field."""
|
341
|
-
if field_name in self.input_fields:
|
342
|
-
self.init_input_kwargs[field_name] = getattr(self, field_name)
|
343
|
-
|
344
|
-
def check_is_workable(
|
345
|
-
self,
|
346
|
-
handle_how: Literal["raise", "return_missing"] = "raise",
|
347
|
-
) -> list[str] | None:
|
348
|
-
"""Check if all input fields are filled and the form is workable.
|
349
|
-
|
350
|
-
Args:
|
351
|
-
handle_how: How to handle missing inputs.
|
65
|
+
for k, v in kwargs.items():
|
66
|
+
setattr(self, k, v)
|
352
67
|
|
353
|
-
|
354
|
-
List of missing inputs if handle_how is "return_missing",
|
355
|
-
None otherwise.
|
356
|
-
|
357
|
-
Raises:
|
358
|
-
ValueError: If input fields are missing and handle_how is
|
359
|
-
"raise".
|
68
|
+
def to_instructions(self) -> dict[str, Any]:
|
360
69
|
"""
|
361
|
-
|
362
|
-
raise ValueError("Cannot modify processed form in strict mode.")
|
363
|
-
|
364
|
-
missing_inputs = []
|
365
|
-
invalid_values = [UNDEFINED, PydanticUndefined]
|
366
|
-
if not self.none_as_valid_value:
|
367
|
-
invalid_values.append(None)
|
368
|
-
|
369
|
-
for i in self.input_fields:
|
370
|
-
if getattr(self, i) in invalid_values:
|
371
|
-
missing_inputs.append(i)
|
372
|
-
|
373
|
-
if missing_inputs:
|
374
|
-
if handle_how == "raise":
|
375
|
-
raise ValueError(f"Incomplete input fields: {missing_inputs}")
|
376
|
-
elif handle_how == "return_missing":
|
377
|
-
return missing_inputs
|
378
|
-
|
379
|
-
def is_completed(self) -> bool:
|
380
|
-
"""Check if the form has been completed."""
|
381
|
-
try:
|
382
|
-
self.check_is_completed(handle_how="raise")
|
383
|
-
return True
|
384
|
-
except Exception:
|
385
|
-
return False
|
386
|
-
|
387
|
-
def is_workable(self) -> bool:
|
388
|
-
"""Check if the form is workable."""
|
389
|
-
try:
|
390
|
-
self.check_is_workable(handle_how="raise")
|
391
|
-
return True
|
392
|
-
except Exception:
|
393
|
-
return False
|
394
|
-
|
395
|
-
def to_dict(self, *, valid_only: bool = False) -> dict[str, Any]:
|
396
|
-
"""Convert the form to a dictionary.
|
397
|
-
|
398
|
-
Args:
|
399
|
-
valid_only: Whether to include only valid fields in the output.
|
400
|
-
|
401
|
-
Returns:
|
402
|
-
A dictionary representation of the form.
|
403
|
-
"""
|
404
|
-
_dict = super().to_dict()
|
405
|
-
if not valid_only:
|
406
|
-
return _dict
|
407
|
-
|
408
|
-
disallow_values = [UNDEFINED, PydanticUndefined]
|
409
|
-
if not self.none_as_valid_value:
|
410
|
-
disallow_values.append(None)
|
411
|
-
return {k: v for k, v in _dict.items() if v not in disallow_values}
|
412
|
-
|
413
|
-
@override
|
414
|
-
@classmethod
|
415
|
-
def from_dict(cls, data: dict, **kwargs) -> T:
|
416
|
-
"""Create a Form instance from a dictionary.
|
417
|
-
|
418
|
-
Args:
|
419
|
-
data: The input data for creating the form.
|
420
|
-
**kwargs: Additional keyword arguments.
|
421
|
-
|
422
|
-
Returns:
|
423
|
-
The created Form instance.
|
424
|
-
"""
|
425
|
-
input_data = copy(data)
|
426
|
-
|
427
|
-
input_data.pop("lion_class", None)
|
428
|
-
input_data.pop("input_fields", None)
|
429
|
-
input_data.pop("request_fields", None)
|
430
|
-
task = input_data.pop("task", "")
|
431
|
-
|
432
|
-
extra_fields = {}
|
433
|
-
for k, v in list(input_data.items()):
|
434
|
-
if k not in cls.model_fields:
|
435
|
-
extra_fields[k] = input_data.pop(k)
|
436
|
-
obj = cls.model_validate(input_data, **kwargs)
|
437
|
-
obj.task = task
|
438
|
-
for k, v in extra_fields.items():
|
439
|
-
obj.update_field(field_name=k, value=v)
|
440
|
-
|
441
|
-
metadata = copy(data.get("metadata", {}))
|
442
|
-
last_updated = metadata.get("last_updated", None)
|
443
|
-
if last_updated is not None:
|
444
|
-
obj.metadata.set(["last_updated"], last_updated)
|
445
|
-
else:
|
446
|
-
obj.metadata.pop(["last_updated"], None)
|
447
|
-
return obj
|
448
|
-
|
449
|
-
def fill_input_fields(
|
450
|
-
self,
|
451
|
-
form: BaseForm | Any = None,
|
452
|
-
**value_kwargs,
|
453
|
-
) -> None:
|
454
|
-
"""Fill the form's input fields with values.
|
455
|
-
|
456
|
-
Args:
|
457
|
-
form: A form to copy values from.
|
458
|
-
**value_kwargs: Values to use for filling the input fields.
|
459
|
-
|
460
|
-
Raises:
|
461
|
-
TypeError: If the provided form is not a BaseForm instance.
|
70
|
+
Return a small dictionary that an LLM can read as an 'instruction context'.
|
462
71
|
"""
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
continue
|
470
|
-
value = value_kwargs.get(i, UNDEFINED)
|
471
|
-
if value is UNDEFINED:
|
472
|
-
value = copy(getattr(form, i, UNDEFINED))
|
473
|
-
if value is not UNDEFINED:
|
474
|
-
setattr(self, i, value)
|
475
|
-
else:
|
476
|
-
if getattr(self, i) in [UNDEFINED, None]:
|
477
|
-
value = value_kwargs.get(i)
|
478
|
-
if value in [UNDEFINED, None]:
|
479
|
-
value = copy(getattr(form, i, UNDEFINED))
|
480
|
-
if value not in [UNDEFINED, None]:
|
481
|
-
setattr(self, i, value)
|
482
|
-
|
483
|
-
def fill_request_fields(
|
484
|
-
self,
|
485
|
-
form: BaseForm = None,
|
486
|
-
**value_kwargs,
|
487
|
-
) -> None:
|
488
|
-
"""Fill the form's request fields with values.
|
489
|
-
|
490
|
-
Args:
|
491
|
-
form: A form to copy values from.
|
492
|
-
**value_kwargs: Values to use for filling the request fields.
|
493
|
-
|
494
|
-
Raises:
|
495
|
-
TypeError: If the provided form is not a BaseForm instance.
|
496
|
-
"""
|
497
|
-
if form is not None and not isinstance(form, BaseForm):
|
498
|
-
raise TypeError("Provided form is not a BaseForm instance.")
|
499
|
-
|
500
|
-
for i in self.request_fields:
|
501
|
-
if self.none_as_valid_value:
|
502
|
-
if getattr(self, i) is not UNDEFINED:
|
503
|
-
continue
|
504
|
-
value = value_kwargs.get(i, UNDEFINED)
|
505
|
-
if value is UNDEFINED:
|
506
|
-
value = copy(getattr(form, i, UNDEFINED))
|
507
|
-
if value is not UNDEFINED:
|
508
|
-
setattr(self, i, value)
|
509
|
-
else:
|
510
|
-
if getattr(self, i) in [UNDEFINED, None]:
|
511
|
-
value = value_kwargs.get(i)
|
512
|
-
if value in [UNDEFINED, None]:
|
513
|
-
value = copy(getattr(form, i, UNDEFINED))
|
514
|
-
if value not in [UNDEFINED, None]:
|
515
|
-
setattr(self, i, value)
|
516
|
-
|
517
|
-
@classmethod
|
518
|
-
def from_form(
|
519
|
-
cls,
|
520
|
-
form: BaseForm | type[BaseForm],
|
521
|
-
guidance: str | dict[str, Any] | None = None,
|
522
|
-
assignment: str | None = None,
|
523
|
-
strict_form: bool = False,
|
524
|
-
task_description: str | None = None,
|
525
|
-
fill_inputs: bool = True,
|
526
|
-
none_as_valid_value: bool = False,
|
527
|
-
output_fields: list[str] | None = None,
|
528
|
-
same_form_output_fields: bool = False,
|
529
|
-
**input_value_kwargs,
|
530
|
-
) -> "Form":
|
531
|
-
"""Create a Form instance from another form.
|
532
|
-
|
533
|
-
Args:
|
534
|
-
form: The form to copy from.
|
535
|
-
guidance: Guidance for the new form.
|
536
|
-
assignment: The assignment for the new form.
|
537
|
-
strict_form: Whether the new form should be strict_form.
|
538
|
-
task_description: A description of the task.
|
539
|
-
fill_inputs: Whether to fill input fields.
|
540
|
-
none_as_valid_value: Whether to treat None as a valid value.
|
541
|
-
output_fields: Output fields for the new form.
|
542
|
-
same_form_output_fields: Whether to copy output fields from the
|
543
|
-
original form.
|
544
|
-
**input_value_kwargs: Values for filling the new form's input
|
545
|
-
fields.
|
546
|
-
|
547
|
-
Returns:
|
548
|
-
The created Form instance.
|
549
|
-
|
550
|
-
Raises:
|
551
|
-
TypeError: If the provided form is not a BaseForm or its subclass.
|
552
|
-
ValueError: If both output_fields and same_form_output_fields are
|
553
|
-
provided.
|
554
|
-
"""
|
555
|
-
if inspect.isclass(form):
|
556
|
-
if not issubclass(form, BaseForm):
|
557
|
-
raise TypeError("Provided form is not a BaseForm class.")
|
558
|
-
form_fields = form.model_fields
|
559
|
-
else:
|
560
|
-
if not isinstance(form, BaseForm):
|
561
|
-
raise TypeError("Provided form is not a BaseForm instance.")
|
562
|
-
form_fields = form.all_fields
|
563
|
-
|
564
|
-
if same_form_output_fields:
|
565
|
-
if output_fields:
|
566
|
-
raise ValueError(
|
567
|
-
"Cannot provide output_fields and "
|
568
|
-
"same_form_output_fields at the same time."
|
569
|
-
)
|
570
|
-
output_fields = copy(form.output_fields)
|
571
|
-
|
572
|
-
if not assignment:
|
573
|
-
if not getattr(form, "assignment", None):
|
574
|
-
raise ValueError("Assignment is missing.")
|
575
|
-
assignment = form.assignment
|
576
|
-
|
577
|
-
obj = cls(
|
578
|
-
guidance=guidance or getattr(form, "guidance", None),
|
579
|
-
assignment=assignment,
|
580
|
-
task_description=task_description
|
581
|
-
or getattr(
|
582
|
-
form,
|
583
|
-
"task_description",
|
584
|
-
"",
|
72
|
+
return {
|
73
|
+
"assignment": self.assignment,
|
74
|
+
"flow": (
|
75
|
+
self.flow_definition.model_dump()
|
76
|
+
if self.flow_definition
|
77
|
+
else None
|
585
78
|
),
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
output_fields=output_fields,
|
590
|
-
)
|
591
|
-
|
592
|
-
for i in obj.work_fields:
|
593
|
-
if i not in form_fields:
|
594
|
-
raise ValueError(f"Invalid assignment field: {i}")
|
595
|
-
obj.update_field(i, field_obj=form_fields[i])
|
596
|
-
|
597
|
-
if fill_inputs:
|
598
|
-
if inspect.isclass(form):
|
599
|
-
obj.fill_input_fields(**input_value_kwargs)
|
600
|
-
else:
|
601
|
-
obj.fill_input_fields(form=form, **input_value_kwargs)
|
602
|
-
|
603
|
-
return obj
|
604
|
-
|
605
|
-
def remove_request_from_output(self) -> None:
|
606
|
-
"""Remove the request fields from the output fields."""
|
607
|
-
for i in self.request_fields:
|
608
|
-
if i in self.output_fields:
|
609
|
-
self.output_fields.remove(i)
|
610
|
-
|
611
|
-
def _append_to_one(
|
612
|
-
self,
|
613
|
-
field_name: str,
|
614
|
-
field_type: Literal["input", "output", "request"],
|
615
|
-
value: Any = UNDEFINED,
|
616
|
-
annotation: Any = UNDEFINED,
|
617
|
-
field_obj: FieldInfo | Any = UNDEFINED,
|
618
|
-
**kwargs,
|
619
|
-
) -> None:
|
620
|
-
"""Append a field to one of the field lists.
|
621
|
-
|
622
|
-
Args:
|
623
|
-
field_name: The name of the field to append.
|
624
|
-
field_type: The type of field to append.
|
625
|
-
value: The value of the field.
|
626
|
-
annotation: The type annotation for the field.
|
627
|
-
field_obj: The field object containing metadata.
|
628
|
-
**kwargs: Additional keyword arguments for field configuration.
|
629
|
-
|
630
|
-
Raises:
|
631
|
-
ValueError: If the field type is invalid or if appending to
|
632
|
-
input or request fields in strict_form mode.
|
633
|
-
"""
|
634
|
-
|
635
|
-
if self.strict_form and field_type in {"input", "request"}:
|
636
|
-
raise ValueError(
|
637
|
-
f"Cannot modify {field_type} fields in strict form mode."
|
638
|
-
)
|
639
|
-
|
640
|
-
config = {
|
641
|
-
"field_name": field_name,
|
642
|
-
"value": value,
|
643
|
-
"annotation": annotation,
|
644
|
-
"field_obj": field_obj,
|
645
|
-
**kwargs,
|
79
|
+
"guidance": self.guidance,
|
80
|
+
"task": self.task,
|
81
|
+
"required_outputs": self.output_fields,
|
646
82
|
}
|
647
|
-
|
648
|
-
match field_type:
|
649
|
-
case "input":
|
650
|
-
if field_name not in self.input_fields:
|
651
|
-
self.input_fields.append(field_name)
|
652
|
-
if field_name not in self.assignment:
|
653
|
-
self.assignment = f"{field_name}, " + self.assignment
|
654
|
-
|
655
|
-
case "request":
|
656
|
-
if field_name not in self.request_fields:
|
657
|
-
self.request_fields.append(field_name)
|
658
|
-
config.pop("value")
|
659
|
-
if field_name not in self.assignment:
|
660
|
-
self.assignment += f", {field_name}"
|
661
|
-
|
662
|
-
case "output":
|
663
|
-
if field_name not in self.output_fields:
|
664
|
-
self.output_fields.append(field_name)
|
665
|
-
|
666
|
-
case _:
|
667
|
-
raise ValueError(f"Invalid field type {field_type}")
|
668
|
-
|
669
|
-
if (
|
670
|
-
any(
|
671
|
-
[
|
672
|
-
value is not UNDEFINED,
|
673
|
-
annotation is not UNDEFINED,
|
674
|
-
field_obj is not UNDEFINED,
|
675
|
-
bool(kwargs),
|
676
|
-
]
|
677
|
-
)
|
678
|
-
or field_name not in self.all_fields
|
679
|
-
):
|
680
|
-
self.update_field(**config)
|
681
|
-
|
682
|
-
def append_to_input(
|
683
|
-
self,
|
684
|
-
field_name: str,
|
685
|
-
value: Any = UNDEFINED,
|
686
|
-
annotation: Any = UNDEFINED,
|
687
|
-
field_obj: FieldInfo | Any = UNDEFINED,
|
688
|
-
**kwargs,
|
689
|
-
) -> None:
|
690
|
-
"""Append a field to the input fields.
|
691
|
-
|
692
|
-
Args:
|
693
|
-
field_name: The name of the field to append.
|
694
|
-
value: The value of the field.
|
695
|
-
annotation: The type annotation for the field.
|
696
|
-
field_obj: The field object containing metadata.
|
697
|
-
**kwargs: Additional keyword arguments for field configuration.
|
698
|
-
|
699
|
-
Raises:
|
700
|
-
ValueError: If appending the field fails.
|
701
|
-
"""
|
702
|
-
try:
|
703
|
-
self._append_to_one(
|
704
|
-
field_name=field_name,
|
705
|
-
field_type="input",
|
706
|
-
value=value,
|
707
|
-
annotation=annotation,
|
708
|
-
field_obj=field_obj,
|
709
|
-
**kwargs,
|
710
|
-
)
|
711
|
-
except Exception as e:
|
712
|
-
raise ValueError(
|
713
|
-
f"Failed to append {field_name} to input fields."
|
714
|
-
) from e
|
715
|
-
|
716
|
-
def append_to_output(
|
717
|
-
self,
|
718
|
-
field_name: str,
|
719
|
-
value: Any = UNDEFINED,
|
720
|
-
annotation: Any = UNDEFINED,
|
721
|
-
field_obj: FieldInfo | Any = UNDEFINED,
|
722
|
-
**kwargs,
|
723
|
-
) -> None:
|
724
|
-
"""Append a field to the output fields.
|
725
|
-
|
726
|
-
Args:
|
727
|
-
field_name: The name of the field to append.
|
728
|
-
value: The value of the field.
|
729
|
-
annotation: The type annotation for the field.
|
730
|
-
field_obj: The field object containing metadata.
|
731
|
-
**kwargs: Additional keyword arguments for field configuration.
|
732
|
-
|
733
|
-
Raises:
|
734
|
-
ValueError: If appending the field fails.
|
735
|
-
"""
|
736
|
-
|
737
|
-
try:
|
738
|
-
self._append_to_one(
|
739
|
-
field_name=field_name,
|
740
|
-
field_type="output",
|
741
|
-
value=value,
|
742
|
-
annotation=annotation,
|
743
|
-
field_obj=field_obj,
|
744
|
-
**kwargs,
|
745
|
-
)
|
746
|
-
except Exception as e:
|
747
|
-
raise ValueError(
|
748
|
-
f"Failed to append {field_name} to output fields."
|
749
|
-
) from e
|
750
|
-
|
751
|
-
def append_to_request(
|
752
|
-
self,
|
753
|
-
field_name: str,
|
754
|
-
annotation: Any = UNDEFINED,
|
755
|
-
field_obj: FieldInfo | Any = UNDEFINED,
|
756
|
-
**kwargs,
|
757
|
-
) -> None:
|
758
|
-
"""Append a field to the request fields.
|
759
|
-
|
760
|
-
Args:
|
761
|
-
field_name: The name of the field to append.
|
762
|
-
value: The value of the field.
|
763
|
-
annotation: The type annotation for the field.
|
764
|
-
field_obj: The field object containing metadata.
|
765
|
-
**kwargs: Additional keyword arguments for field configuration.
|
766
|
-
|
767
|
-
Raises:
|
768
|
-
ValueError: If appending the field fails.
|
769
|
-
"""
|
770
|
-
if "value" in kwargs:
|
771
|
-
raise ValueError("Cannot provide value to request fields.")
|
772
|
-
try:
|
773
|
-
self._append_to_one(
|
774
|
-
field_name=field_name,
|
775
|
-
field_type="request",
|
776
|
-
annotation=annotation,
|
777
|
-
field_obj=field_obj,
|
778
|
-
**kwargs,
|
779
|
-
)
|
780
|
-
except Exception as e:
|
781
|
-
raise ValueError(
|
782
|
-
f"Failed to append {field_name} to request fields."
|
783
|
-
) from e
|
784
|
-
|
785
|
-
|
786
|
-
__all__ = ["Form"]
|