lionagi 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. lionagi/core/execute/structure_executor.py +21 -1
  2. lionagi/core/flow/monoflow/ReAct.py +3 -1
  3. lionagi/core/flow/monoflow/followup.py +3 -1
  4. lionagi/core/generic/component.py +197 -120
  5. lionagi/core/generic/condition.py +2 -0
  6. lionagi/core/generic/edge.py +33 -33
  7. lionagi/core/graph/graph.py +1 -1
  8. lionagi/core/tool/tool_manager.py +10 -9
  9. lionagi/experimental/report/form.py +64 -0
  10. lionagi/experimental/report/report.py +138 -0
  11. lionagi/experimental/report/util.py +47 -0
  12. lionagi/experimental/tool/schema.py +3 -3
  13. lionagi/experimental/tool/tool_manager.py +1 -1
  14. lionagi/experimental/validator/rule.py +139 -0
  15. lionagi/experimental/validator/validator.py +56 -0
  16. lionagi/experimental/work/__init__.py +10 -0
  17. lionagi/experimental/work/async_queue.py +54 -0
  18. lionagi/experimental/work/schema.py +60 -17
  19. lionagi/experimental/work/work_function.py +55 -77
  20. lionagi/experimental/work/worker.py +56 -12
  21. lionagi/experimental/work2/__init__.py +0 -0
  22. lionagi/experimental/work2/form.py +371 -0
  23. lionagi/experimental/work2/report.py +289 -0
  24. lionagi/experimental/work2/schema.py +30 -0
  25. lionagi/experimental/{work → work2}/tests.py +1 -1
  26. lionagi/experimental/work2/util.py +0 -0
  27. lionagi/experimental/work2/work.py +0 -0
  28. lionagi/experimental/work2/work_function.py +89 -0
  29. lionagi/experimental/work2/worker.py +12 -0
  30. lionagi/integrations/storage/storage_util.py +4 -4
  31. lionagi/integrations/storage/structure_excel.py +268 -0
  32. lionagi/integrations/storage/to_excel.py +18 -9
  33. lionagi/libs/__init__.py +4 -0
  34. lionagi/tests/test_core/generic/__init__.py +0 -0
  35. lionagi/tests/test_core/generic/test_component.py +89 -0
  36. lionagi/version.py +1 -1
  37. {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/METADATA +1 -1
  38. {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/RECORD +43 -27
  39. lionagi/experimental/work/_logger.py +0 -25
  40. /lionagi/experimental/{work/exchange.py → report/__init__.py} +0 -0
  41. /lionagi/experimental/{work/util.py → validator/__init__.py} +0 -0
  42. {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/LICENSE +0 -0
  43. {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/WHEEL +0 -0
  44. {lionagi-0.1.1.dist-info → lionagi-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,371 @@
1
+ from typing import Any
2
+ from pydantic import Field
3
+ from lionagi.core.generic import BaseComponent
4
+ from lionagi.libs import validation_funcs, convert
5
+ from lionagi.experimental.report.util import get_input_output_fields
6
+
7
+
8
+ class Form(BaseComponent):
9
+
10
+ form_name: str = Field(
11
+ default="default_form",
12
+ )
13
+ description: Any = Field(default=None)
14
+ assignment: str = Field(..., examples=["input1, input2 -> output"])
15
+ instruction: Any = Field(
16
+ default=None,
17
+ )
18
+ input_fields: list[str] = Field(default_factory=list)
19
+ output_fields: list[str] = Field(default_factory=list)
20
+ examples: Any = Field(
21
+ default=None,
22
+ )
23
+ fix_input: bool = Field(False, description="whether to fix input")
24
+ fix_output: bool = Field(True, description="whether to fix output")
25
+ filled: bool = Field(False, description="whether the form is completed")
26
+
27
+ def __init__(self, **kwargs):
28
+ super().__init__(**kwargs)
29
+ self.input_fields, self.output_fields = get_input_output_fields(self.assignment)
30
+
31
+ for field in self.input_fields:
32
+ if not hasattr(self, field):
33
+ setattr(self, field, None) # zero initialization
34
+ self.process()
35
+
36
+ @property
37
+ def work_fields(self):
38
+ return self.input_fields + self.output_fields
39
+
40
+ @property
41
+ def is_completed(self):
42
+ return all(getattr(self, i, None) for i in self.work_fields)
43
+
44
+ def process(self, in_=True, out_=None):
45
+ if not in_ and not out_:
46
+ raise ValueError("at least one of in_ and out_ must be True")
47
+ if in_:
48
+ self._process_input()
49
+ if out_:
50
+ self._process_output(out_)
51
+
52
+ def _process_input(self):
53
+ for k in self.input_fields:
54
+ try:
55
+ valid_ = self._validate_field(
56
+ k,
57
+ getattr(self, k, None),
58
+ choices=self._get_field_attr(k, "choices", None),
59
+ keys=self._get_field_attr(k, "keys", None),
60
+ fix_=self.fix_input,
61
+ **self._get_field_attr(k, "validation_kwargs", {}),
62
+ )
63
+ if not valid_:
64
+ raise ValueError(f"failed to validate field {k}")
65
+ except Exception as e:
66
+ raise ValueError(f"failed to validate field {k}") from e
67
+
68
+ def _process_output(self, out_: dict = None):
69
+ for k, v in out_.items():
70
+ try:
71
+ valid_ = self._validate_field(
72
+ k,
73
+ v,
74
+ choices=self._get_field_attr(k, "choices", None),
75
+ keys=self._get_field_attr(k, "keys", None),
76
+ fix_=self.fix_output,
77
+ **self._get_field_attr(k, "validation_kwargs", {}),
78
+ )
79
+ if not valid_:
80
+ raise ValueError(f"failed to validate field {k}")
81
+ except Exception as e:
82
+ raise ValueError(f"failed to validate field {k}") from e
83
+
84
+ if self.is_completed:
85
+ self.filled = True
86
+
87
+ def _validate_field(self, k, v, choices=None, keys=None, fix_=False, **kwargs):
88
+ annotation = self.field_annotations[k]
89
+
90
+ if choices:
91
+ if choices and not isinstance(choices, list):
92
+ try:
93
+ choices = [i.value for i in choices]
94
+ except Exception as e:
95
+ raise ValueError(f"failed to get choices for field {k}") from e
96
+ v_ = validation_funcs["enum"](v, choices=choices, fix_=fix_, **kwargs)
97
+ if v_ not in choices:
98
+ raise ValueError(f"{v} is not in chocies {choices}")
99
+ setattr(self, k, v_)
100
+ return True
101
+
102
+ if any("actionrequest" in i for i in annotation):
103
+ self.__setattr__(k, validation_funcs["action"](v))
104
+ return True
105
+
106
+ if "bool" in annotation and "str" not in annotation:
107
+ self.__setattr__(k, validation_funcs["bool"](v, fix_=fix_, **kwargs))
108
+ return True
109
+
110
+ if (
111
+ any([i in annotation for i in ["int", "float", "number"]])
112
+ and "str" not in annotation
113
+ ):
114
+ if "float" in annotation:
115
+ kwargs["num_type"] = float
116
+ if "precision" not in kwargs:
117
+ kwargs["precision"] = 10
118
+
119
+ self.__setattr__(k, validation_funcs["number"](v, fix_=fix_, **kwargs))
120
+ return True
121
+
122
+ if "dict" in annotation:
123
+ if "str" not in annotation or keys:
124
+ v_ = validation_funcs["dict"](v, keys=keys, fix_=fix_, **kwargs)
125
+ setattr(self, k, v_)
126
+ return True
127
+
128
+ if "str" in annotation:
129
+ self.__setattr__(k, validation_funcs["str"](v, fix_=fix_, **kwargs))
130
+ return True
131
+
132
+ return False
133
+
134
+
135
+ # from enum import Enum
136
+ # from pydantic import Field
137
+
138
+ # class EXAMPLES(str, Enum):
139
+ # EXAMPLE1 = "example1"
140
+ # EXAMPLE2 = "example2"
141
+ # EXAMPLE3 = "example3"
142
+
143
+ # class Form1(Form):
144
+ # a: str | EXAMPLES = Field("example3", choices=EXAMPLES)
145
+ # b: str = "input2"
146
+ # c: str|None = Field(None, choices=["output1", "output2"])
147
+ # d: float | None = Field(None, json_schema_extra={"validation_kwargs":{"num_type": float, "precision": 2}})
148
+ # assignment: str='a, b -> c, d'
149
+ # form_name: str='custom_form'
150
+ # description: str='Test Form'
151
+
152
+ # form = Form1()
153
+ # form.process(out_ = {"c": "output1", "d": "1.1"})
154
+
155
+
156
+ """
157
+ usage pattern:
158
+
159
+ from pydantic import Field
160
+
161
+ class JokeForm(Form):
162
+
163
+ # 1. use the key kwarg to specify the key in the dict,
164
+ # which will be used validate the field value
165
+
166
+ # 2. use fix flag to indicate whether to fix the value
167
+ # if it is not valid, default to True
168
+
169
+ material: dict = Field(
170
+ ...,
171
+ description="materials to read",
172
+ keys=["title", "author", "year"], # use fix flag for dict key can be dangerous
173
+ fix=True # the validator will force the value to match
174
+ ) # the provided keys, which might pollute the data
175
+
176
+
177
+ # 3. use choices to specify the available options
178
+ topic: str = Field(
179
+ ...,
180
+ description="topic to write joke on",
181
+ choices=["animal", "food", "people"],
182
+ )
183
+
184
+ # you can also use enum or subclass of Enum to explicitly
185
+ # declare that the field requires
186
+ # the value to be one of the provided choices
187
+
188
+ from enum import Enum
189
+
190
+ class TOPIC(str, Enum):
191
+ ANIMAL = "animal"
192
+ FOOD = "food"
193
+ PEOPLE = "people"
194
+
195
+
196
+ topic: TOPIC = Field(
197
+ default = TOPIC.ANIMAL,
198
+ description="topic to write joke on",
199
+ choices=TOPIC,
200
+ fix_input=True, # fix_input and fix_output are used to indicate
201
+ ) # whether to fix invalid values, default to True
202
+
203
+ # 4. using optional fields
204
+ # you can add None in type annotation to indicate that the field is optional
205
+ # otherwise there will be a validation error if there is no default
206
+ # field value, and value is also not provided at initialization
207
+
208
+ joke: str | None = Field(
209
+ default = None,
210
+ description = "joke to write"
211
+ )
212
+
213
+ # 5. using validation_kwargs
214
+ # you can use validation_kwargs to specify the validation parameters
215
+ # there are built-in validators for numbers
216
+
217
+ rating: float | None = Field(
218
+ default=None,
219
+ description="rating, a numerical value",
220
+ validation_kwargs={
221
+ "upper_bound": 10, # this will ensure the value for this field
222
+ "lower_bound": 0, # is a number between upper_bound and lower_bound
223
+ "num_type": "float",
224
+ "precision": 2}
225
+ )
226
+
227
+
228
+ # 6. using assignment
229
+ # in a form, you do not need to specify the input output for each field,
230
+ # instead, you can use assignment to specify the input and output of the form
231
+
232
+ # an assignment is a string that describes the input and output of the form
233
+ # it is used to generate a composable set of fields specific to different context
234
+ # from the same custom Form class you specified, for example, two fields "a" and "b",
235
+ # in one form, you can specify the assignment as "a -> b", and in another form,
236
+ # you can specify the assignment as "b -> a", and the system will generate two different
237
+ # instruction for the worker to perform.
238
+
239
+ assignment: str = Field(
240
+ default=...,
241
+ examples=["input1, input2 -> output"]
242
+ )
243
+
244
+ # for example, "work -> review, rating" and "review, work -> rating" have completely different
245
+ # meanings, and the system will generate two different forms for the worker to complete
246
+ # the former means, a review task of a work performance with corresponding rating for the work
247
+ # the latter means, a rating task on the review quality for the work performance,
248
+ # such as, whether it is fair for the rating
249
+
250
+
251
+ # 7. general guidance
252
+ # in principle, a field should only be filled once, thus making form a single use object
253
+ # the form has a filled flag to indicate whether the form is completed
254
+
255
+ """
256
+
257
+ # import unittest
258
+ # from pydantic import Field
259
+
260
+
261
+ # class Form1(Form):
262
+ # input1: str = Field("input1", json_schema_extra={"choices": ["option1", "option2"]})
263
+ # input2: str = "input2"
264
+ # output1: str = "output1"
265
+
266
+
267
+ # class TestForm(unittest.TestCase):
268
+
269
+ # def test_default_initialization(self):
270
+ # class Form1(Form):
271
+ # input1: str = "input1"
272
+ # input2: str = "input2"
273
+ # output1: str = "output1"
274
+ # assignment: str = "input1, input2 -> output1"
275
+
276
+ # form = Form1()
277
+ # self.assertEqual(form.form_name, "default_form")
278
+ # self.assertIsNone(form.description)
279
+ # self.assertEqual(form.input_fields, ["input1", "input2"])
280
+ # self.assertEqual(form.output_fields, ["output1"])
281
+ # self.assertFalse(form.filled)
282
+ # self.assertFalse(form.fix_input)
283
+ # self.assertTrue(form.fix_output)
284
+
285
+ # def test_custom_initialization(self):
286
+ # class Form1(Form):
287
+ # a: str = "input1"
288
+ # b: str = "input2"
289
+ # c: str = "output1"
290
+ # assignment: str = "a, b -> c"
291
+ # form_name: str = "custom_form"
292
+ # description: str = "Test Form"
293
+
294
+ # form = Form1()
295
+ # self.assertEqual(form.form_name, "custom_form")
296
+ # self.assertEqual(form.description, "Test Form")
297
+ # self.assertEqual(form.input_fields, ["a", "b"])
298
+ # self.assertEqual(form.output_fields, ["c"])
299
+
300
+ # def test_process_inputs_outputs(self):
301
+ # class Form1(Form):
302
+ # input1: str = Field(
303
+ # "option1", json_schema_extra={"choices": ["option1", "option2"]}
304
+ # )
305
+ # input2: str = "input2"
306
+ # output1: str = "output1"
307
+ # assignment: str = "input1, input2 -> output1"
308
+
309
+ # form = Form1()
310
+ # setattr(form, "input1", "option1")
311
+ # # Test processing valid input
312
+ # form.process(in_=True) # Should process without error
313
+
314
+ # setattr(form, "input1", "option3")
315
+ # # Test processing invalid input
316
+ # with self.assertRaises(ValueError):
317
+ # form.process(in_=True) # Should raise error
318
+
319
+ # def test_check_complete(self):
320
+ # class Form1(Form):
321
+ # input1: str = "input1"
322
+ # input2: str = "input2"
323
+ # output1: str = "output1"
324
+
325
+ # form = Form1(assignment="input1, input2 -> output1")
326
+ # setattr(form, "input1", "value1")
327
+ # setattr(form, "input2", "value2")
328
+ # setattr(form, "output1", "result1")
329
+ # self.assertTrue(form.is_completed)
330
+
331
+ # def test_input_output_fields_parsing(self):
332
+ # class Form1(Form):
333
+ # x: str = "input1"
334
+ # y: str = "input2"
335
+ # z: str = "output1"
336
+
337
+ # form = Form1(assignment="x, y -> z")
338
+ # self.assertEqual(form.input_fields, ["x", "y"])
339
+ # self.assertEqual(form.output_fields, ["z"])
340
+
341
+ # def test_validation_failure(self):
342
+ # class Form1(Form):
343
+ # input1: str = Field(
344
+ # None, json_schema_extra={"choices": ["option1", "option2"]}
345
+ # )
346
+ # input2: str = "input2"
347
+ # output1: str = "output1"
348
+ # assignment: str = "input1, input2 -> output1"
349
+
350
+ # # Test handling invalid input choice
351
+ # with self.assertRaises(ValueError):
352
+ # form = Form1()
353
+
354
+ # def test_output_assignment(self):
355
+ # class Form1(Form):
356
+ # input1: str = "value1"
357
+ # output1: str = Field("output1", choices=["result1", "result2"])
358
+ # assignment: str = "input1 -> output1"
359
+
360
+ # form = Form1()
361
+ # # Test handling invalid output choice
362
+ # try:
363
+ # form.process(out_={"output1": "result3"})
364
+ # except ValueError:
365
+ # pass
366
+
367
+ # form.process(out_={"output1": "result3"}) # Should process without error
368
+
369
+
370
+ # if __name__ == "__main__":
371
+ # unittest.main()
@@ -0,0 +1,289 @@
1
+ from typing import Any
2
+ from pydantic import Field
3
+ from lionagi.experimental.report.form import Form
4
+ from lionagi.core.generic import BaseComponent
5
+ from lionagi.experimental.report.util import get_input_output_fields
6
+
7
+ """
8
+ ## Report Usage Pattern
9
+
10
+
11
+
12
+
13
+
14
+
15
+ requirements,
16
+ the assignments name should be consistent within a report,
17
+ meaning, all forms will refer to the same field value when using the same name.
18
+
19
+ the same input field can be used in multiple forms, but each unique output field
20
+ should be filled only once.
21
+ meaning, not two forms should have the any output fields in common.
22
+
23
+ a filled field cannot be None
24
+
25
+ usage pattern:
26
+
27
+ class Report1(Report):
28
+ a: Any = None
29
+ b: Any = None
30
+ c: Any = None
31
+ d: Any = None
32
+ e: Any = None
33
+ f: Any = None
34
+
35
+ assignments: list = [
36
+ "a, b -> c",
37
+ "c -> d",
38
+ "b, d -> e"
39
+ ]
40
+
41
+ this means that the report will have 3 work steps, each corresponding to an unique
42
+ form. the context for the report is the fields that cannot be output by any work
43
+ teps in the report.
44
+
45
+ the deliverable for the report is the fields that are output produced by the work
46
+ steps but not used as input for any other work steps in the same report.
47
+
48
+ under the hood:
49
+
50
+ the report first create three forms and add to its forms dictionary
51
+
52
+ form1 = Form1(assignment="a, b -> c")
53
+ form2 = Form2(assignment="c -> d")
54
+ form3 = Form3(assignment="b -> e")
55
+ form3 = Form3(assignment="d, e -> f")
56
+
57
+ The Form1, Form2, Form3 can be a single form class or multiple form classes,
58
+ as long as they have the same fields to interact
59
+
60
+ report is created by intaking different context inputs, in this case, we
61
+ need to provide a and b to the report
62
+
63
+ report let work scheduler know which form to fill next, by using the next_forms() method,
64
+ this method check if the dependencies of a form are filled, if so, all such forms are
65
+ deemed to be next to fill.
66
+
67
+ so to begin with we need the context fields, a and b, then the report checks the
68
+ next forms, notice, for (a, b -> c), we have a and b, so this form can be filled next
69
+ also for (b -> e), we have b, so this form can also be filled next
70
+
71
+ thus the report will first send forms of those two assignments to the scheduler.
72
+
73
+ as work gets done, forms get filled, the completed forms will be sent back to scheduler,
74
+ and the scheduler is in charge of filling in the fields onto the report. After each
75
+ time a report gets new fields filled, the scheduler will check the next forms to fill,
76
+ and the process continues until all forms are filled.
77
+
78
+ so as (a, b -> c) is done, we get c, thus c -> d is then the next form
79
+ and d, e -> f is the last form to fill
80
+ once all forms are filled and all fields transfered to reports, the report is completed.
81
+
82
+ """
83
+
84
+
85
+ class Report(BaseComponent):
86
+
87
+ report_name: str = Field(
88
+ default="default_report",
89
+ )
90
+ description: Any = Field(default=None)
91
+ task: Any = Field(default=None)
92
+ forms: dict[str, Form] = Field(
93
+ default_factory=dict,
94
+ description="A dictionary of forms related to the report, in {form.id_: form} format.",
95
+ )
96
+ context: dict = Field(default_factory=dict, description="context for the report")
97
+ deliverable: dict = Field(
98
+ default_factory=dict, description="deliverable for the report"
99
+ )
100
+ intermediate: dict = Field(
101
+ default_factory=dict, description="intermediate fields for the report"
102
+ )
103
+ filled: bool = Field(False, description="whether the report is completed")
104
+ assignments: list = Field([], description="assignment for the report")
105
+
106
+ def fill_report(self, form: Form | str):
107
+ form = self.forms[form] if isinstance(form, str) else form
108
+ if not form.filled or form not in self.forms.values():
109
+ raise ValueError("The form is not filled or not in the report.")
110
+
111
+ for i in form.input_fields:
112
+ if i not in self._filled_fields:
113
+ setattr(self, i, getattr(form, i))
114
+ if i not in self.deliverable:
115
+ self.intermediate[i] = getattr(form, i)
116
+
117
+ for i in form.output_fields:
118
+ setattr(self, i, getattr(form, i))
119
+ if i not in self.deliverable:
120
+ self.intermediate[i] = getattr(form, i)
121
+
122
+ @property
123
+ def work_fields(self):
124
+ """
125
+ all work fields across all forms, including intermediate output fields
126
+ """
127
+ all_fields = []
128
+ for form in self.forms.values():
129
+ all_fields.extend(form.work_fields)
130
+ return list(set(all_fields))
131
+
132
+ @property
133
+ def is_completed(self):
134
+ return all([hasattr(self, i) for i in self.work_fields])
135
+
136
+ @property
137
+ def is_workable(self):
138
+ context_fields, deliverable_fields = get_input_output_fields(self.assignment)
139
+ context_fields.extend(deliverable_fields)
140
+
141
+ # check whether all work fields are assigned in the forms for the report
142
+ if not all([i in self.work_fields for i in context_fields]):
143
+ raise ValueError(
144
+ f"Not all work fields are assigned in the forms for the report."
145
+ )
146
+
147
+ outs = []
148
+ for form in self.forms.values():
149
+ outs.extend(form.output_fields)
150
+
151
+ if len(outs) != len(set(outs)):
152
+ raise ValueError(f"Output fields should be unique across all forms.")
153
+
154
+ inputs = []
155
+ for form in self.forms.values():
156
+ inputs.extend(form.input_fields)
157
+
158
+ inputs = [i for i in inputs if i not in outs]
159
+ if not all([i in self.work_fields for i in inputs]):
160
+ raise ValueError(
161
+ f"Not all input fields are assigned in the forms for the report."
162
+ )
163
+
164
+ return True
165
+
166
+ def next_forms(self):
167
+ to_do = []
168
+ for i in self._unfilled_forms:
169
+ if all([j in self._filled_fields for j in i.input_fields]):
170
+ to_do.append(i)
171
+ return to_do[0] if len(to_do) == 1 else to_do or None
172
+
173
+ @property
174
+ def _filled_forms(self):
175
+ return [form for form in self.forms.values() if form.filled]
176
+
177
+ @property
178
+ def _unfilled_forms(self):
179
+ return [form for form in self.forms.values() if not form.filled]
180
+
181
+ @property
182
+ def _filled_fields(self):
183
+ filled_fields = []
184
+ for i in self.work_fields:
185
+ if getattr(self, i, None) is not None:
186
+ filled_fields.append(i)
187
+ return filled_fields
188
+
189
+ @property
190
+ def _unfilled_fields(self):
191
+ return [i for i in self.work_fields if i not in self._filled_fields]
192
+
193
+
194
+ # import unittest
195
+ # from enum import Enum
196
+ # from typing import Any
197
+ # from pydantic import Field
198
+ # from lionagi import logging as _logging
199
+ # from lionagi.experimental.form.form import Form
200
+ # from lionagi.core.generic import BaseComponent
201
+ # from lionagi.experimental.form.util import get_input_output_fields
202
+
203
+
204
+ # class EXAMPLES(str, Enum):
205
+ # EXAMPLE1 = "example1"
206
+ # EXAMPLE2 = "example2"
207
+ # EXAMPLE3 = "example3"
208
+
209
+
210
+ # class Form1(Form):
211
+ # a: str | EXAMPLES = Field(EXAMPLES.EXAMPLE3, choices=list(EXAMPLES))
212
+ # b: str = "input2"
213
+ # c: str | None = Field(None, choices=["output1", "output2"])
214
+ # d: float | None = Field(None)
215
+ # assignment: str = "a, b -> c, d"
216
+ # form_name: str = "custom_form"
217
+ # description: str = "Test Form"
218
+
219
+
220
+ # class TestReport(unittest.TestCase):
221
+ # def setUp(self):
222
+ # self.report = Report(assignment="a, b -> c, d")
223
+ # self.form1 = Form1(assignment="a, b -> c, d")
224
+ # self.report.forms[self.form1.id_] = self.form1
225
+
226
+ # def test_initialization(self):
227
+ # self.assertEqual(self.report.report_name, "default_report")
228
+ # self.assertIn(self.form1.id_, self.report.forms)
229
+
230
+ # def test_fill_report(self):
231
+ # self.form1.process(out_={"c": "output1", "d": 1.1})
232
+ # self.report.fill_report(self.form1)
233
+ # self.assertTrue(self.form1.filled)
234
+ # self.assertEqual(self.report.intermediate["c"], "output1")
235
+
236
+ # def test_report_completeness(self):
237
+ # self.form1.process(out_={"c": "output1", "d": 1.1})
238
+ # self.report.fill_report(self.form1)
239
+ # self.assertTrue(self.report.is_completed)
240
+
241
+ # def test_report_workability(self):
242
+ # # self.form1.process(out_={'c': 'output1', 'd': 1.1})
243
+ # # self.report.fill_report(self.form1)
244
+ # self.assertTrue(self.report.is_workable)
245
+
246
+ # def test_handling_invalid_form_id(self):
247
+ # with self.assertRaises(KeyError):
248
+ # self.report.fill_report("nonexistent_form")
249
+
250
+ # def test_next_forms_logic(self):
251
+ # next_forms = self.report.next_forms()
252
+ # self.assertEqual(next_forms, None)
253
+
254
+ # def test_work_fields_property(self):
255
+ # self.form1.process(out_={"c": "output1", "d": 1.1})
256
+ # self.report.fill_report(self.form1)
257
+ # self.assertIn("a", self.report.work_fields)
258
+ # self.assertIn("b", self.report.work_fields)
259
+ # self.assertIn("c", self.report.work_fields)
260
+ # self.assertIn("d", self.report.work_fields)
261
+
262
+ # def test_filled_forms_property(self):
263
+ # self.form1.process(out_={"c": "output1", "d": 1.1})
264
+ # self.report.fill_report(self.form1)
265
+ # self.assertIn(self.form1, self.report._filled_forms)
266
+
267
+ # def test_unfilled_forms_property(self):
268
+ # self.assertIn(self.form1, self.report._unfilled_forms)
269
+ # self.form1.process(out_={"c": "output1", "d": 1.1})
270
+ # self.report.fill_report(self.form1)
271
+ # self.assertNotIn(self.form1, self.report._unfilled_forms)
272
+
273
+ # def test_filled_fields_property(self):
274
+ # self.form1.process(out_={"c": "output1", "d": 1.1})
275
+ # self.report.fill_report(self.form1)
276
+ # self.assertIn("c", self.report._filled_fields)
277
+ # self.assertIn("d", self.report._filled_fields)
278
+
279
+ # def test_unfilled_fields_property(self):
280
+ # self.assertIn("c", self.report._unfilled_fields)
281
+ # self.assertIn("d", self.report._unfilled_fields)
282
+ # self.form1.process(out_={"c": "output1", "d": 1.1})
283
+ # self.report.fill_report(self.form1)
284
+ # self.assertNotIn("c", self.report._unfilled_fields)
285
+ # self.assertNotIn("d", self.report._unfilled_fields)
286
+
287
+
288
+ # if __name__ == "__main__":
289
+ # unittest.main()
@@ -0,0 +1,30 @@
1
+ from enum import Enum
2
+ from typing import Any, Dict, List
3
+ from pydantic import Field
4
+ from lionagi.core.generic import BaseComponent
5
+
6
+
7
+ class WorkStatus(str, Enum):
8
+ """Enum to represent different statuses of work."""
9
+
10
+ PENDING = "PENDING"
11
+ IN_PROGRESS = "IN_PROGRESS"
12
+ COMPLETED = "COMPLETED"
13
+ FAILED = "FAILED"
14
+ CANCELLED = "CANCELLED"
15
+
16
+
17
+ class Work(BaseComponent):
18
+ """Base component for handling individual units of work."""
19
+
20
+ form_id: str = Field(..., description="ID of the form for this work")
21
+ priority: int = Field(default=0, description="Priority of the work")
22
+ status: WorkStatus = Field(
23
+ default=WorkStatus.PENDING, description="Current status of the work"
24
+ )
25
+ deliverables: Dict[str, Any] | list = Field(
26
+ default={}, description="Deliverables produced by the work"
27
+ )
28
+ dependencies: List["Work"] = Field(
29
+ default_factory=list, description="List of work items this work depends on"
30
+ )
@@ -1,5 +1,5 @@
1
1
  from .schema import Work, WorkStatus
2
- from ._logger import WorkLog
2
+ from ..work.worklog import WorkLog
3
3
  from .work_function import WorkFunction
4
4
 
5
5
  import unittest
File without changes
File without changes