lionagi 0.1.0__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 (103) hide show
  1. lionagi/core/agent/base_agent.py +2 -3
  2. lionagi/core/branch/base.py +1 -1
  3. lionagi/core/branch/branch.py +2 -1
  4. lionagi/core/branch/flow_mixin.py +1 -1
  5. lionagi/core/branch/util.py +1 -1
  6. lionagi/core/execute/base_executor.py +1 -4
  7. lionagi/core/execute/branch_executor.py +66 -3
  8. lionagi/core/execute/instruction_map_executor.py +48 -0
  9. lionagi/core/execute/neo4j_executor.py +381 -0
  10. lionagi/core/execute/structure_executor.py +120 -4
  11. lionagi/core/flow/monoflow/ReAct.py +21 -19
  12. lionagi/core/flow/monoflow/chat_mixin.py +1 -1
  13. lionagi/core/flow/monoflow/followup.py +14 -13
  14. lionagi/core/flow/polyflow/__init__.py +1 -1
  15. lionagi/core/generic/component.py +197 -122
  16. lionagi/core/generic/condition.py +3 -1
  17. lionagi/core/generic/edge.py +77 -25
  18. lionagi/core/graph/graph.py +1 -1
  19. lionagi/core/mail/mail_manager.py +3 -2
  20. lionagi/core/session/session.py +1 -1
  21. lionagi/core/tool/tool_manager.py +10 -9
  22. lionagi/experimental/__init__.py +0 -0
  23. lionagi/experimental/directive/__init__.py +0 -0
  24. lionagi/experimental/directive/evaluator/__init__.py +0 -0
  25. lionagi/experimental/directive/evaluator/ast_evaluator.py +115 -0
  26. lionagi/experimental/directive/evaluator/base_evaluator.py +202 -0
  27. lionagi/experimental/directive/evaluator/sandbox_.py +14 -0
  28. lionagi/experimental/directive/evaluator/script_engine.py +83 -0
  29. lionagi/experimental/directive/parser/__init__.py +0 -0
  30. lionagi/experimental/directive/parser/base_parser.py +215 -0
  31. lionagi/experimental/directive/schema.py +36 -0
  32. lionagi/experimental/directive/template_/__init__.py +0 -0
  33. lionagi/experimental/directive/template_/base_template.py +63 -0
  34. lionagi/experimental/report/__init__.py +0 -0
  35. lionagi/experimental/report/form.py +64 -0
  36. lionagi/experimental/report/report.py +138 -0
  37. lionagi/experimental/report/util.py +47 -0
  38. lionagi/experimental/tool/__init__.py +0 -0
  39. lionagi/experimental/tool/function_calling.py +43 -0
  40. lionagi/experimental/tool/manual.py +66 -0
  41. lionagi/experimental/tool/schema.py +59 -0
  42. lionagi/experimental/tool/tool_manager.py +138 -0
  43. lionagi/experimental/tool/util.py +16 -0
  44. lionagi/experimental/validator/__init__.py +0 -0
  45. lionagi/experimental/validator/rule.py +139 -0
  46. lionagi/experimental/validator/validator.py +56 -0
  47. lionagi/experimental/work/__init__.py +10 -0
  48. lionagi/experimental/work/async_queue.py +54 -0
  49. lionagi/experimental/work/schema.py +73 -0
  50. lionagi/experimental/work/work_function.py +67 -0
  51. lionagi/experimental/work/worker.py +56 -0
  52. lionagi/experimental/work2/__init__.py +0 -0
  53. lionagi/experimental/work2/form.py +371 -0
  54. lionagi/experimental/work2/report.py +289 -0
  55. lionagi/experimental/work2/schema.py +30 -0
  56. lionagi/experimental/work2/tests.py +72 -0
  57. lionagi/experimental/work2/util.py +0 -0
  58. lionagi/experimental/work2/work.py +0 -0
  59. lionagi/experimental/work2/work_function.py +89 -0
  60. lionagi/experimental/work2/worker.py +12 -0
  61. lionagi/integrations/bridge/autogen_/__init__.py +0 -0
  62. lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
  63. lionagi/integrations/bridge/llamaindex_/get_index.py +294 -0
  64. lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
  65. lionagi/integrations/bridge/transformers_/__init__.py +0 -0
  66. lionagi/integrations/bridge/transformers_/install_.py +36 -0
  67. lionagi/integrations/config/oai_configs.py +1 -1
  68. lionagi/integrations/config/ollama_configs.py +1 -1
  69. lionagi/integrations/config/openrouter_configs.py +1 -1
  70. lionagi/integrations/storage/__init__.py +3 -0
  71. lionagi/integrations/storage/neo4j.py +673 -0
  72. lionagi/integrations/storage/storage_util.py +289 -0
  73. lionagi/integrations/storage/structure_excel.py +268 -0
  74. lionagi/integrations/storage/to_csv.py +63 -0
  75. lionagi/integrations/storage/to_excel.py +76 -0
  76. lionagi/libs/__init__.py +4 -0
  77. lionagi/libs/ln_knowledge_graph.py +405 -0
  78. lionagi/libs/ln_queue.py +101 -0
  79. lionagi/libs/ln_tokenizer.py +57 -0
  80. lionagi/libs/sys_util.py +1 -1
  81. lionagi/lions/__init__.py +0 -0
  82. lionagi/lions/coder/__init__.py +0 -0
  83. lionagi/lions/coder/add_feature.py +20 -0
  84. lionagi/lions/coder/base_prompts.py +22 -0
  85. lionagi/lions/coder/coder.py +121 -0
  86. lionagi/lions/coder/util.py +91 -0
  87. lionagi/lions/researcher/__init__.py +0 -0
  88. lionagi/lions/researcher/data_source/__init__.py +0 -0
  89. lionagi/lions/researcher/data_source/finhub_.py +191 -0
  90. lionagi/lions/researcher/data_source/google_.py +199 -0
  91. lionagi/lions/researcher/data_source/wiki_.py +96 -0
  92. lionagi/lions/researcher/data_source/yfinance_.py +21 -0
  93. lionagi/tests/libs/test_queue.py +67 -0
  94. lionagi/tests/test_core/generic/__init__.py +0 -0
  95. lionagi/tests/test_core/generic/test_component.py +89 -0
  96. lionagi/tests/test_core/test_branch.py +0 -1
  97. lionagi/version.py +1 -1
  98. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/METADATA +1 -1
  99. lionagi-0.1.2.dist-info/RECORD +206 -0
  100. lionagi-0.1.0.dist-info/RECORD +0 -136
  101. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/LICENSE +0 -0
  102. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/WHEEL +0 -0
  103. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,12 @@
1
- """This module provides base components used in lionagi"""
1
+ """base components in lionagi"""
2
2
 
3
3
  from abc import ABC
4
4
  from functools import singledispatchmethod
5
- from typing import Any, TypeVar
6
-
5
+ from typing import Any, TypeVar, Type
7
6
  from pydantic import AliasChoices, BaseModel, Field, ValidationError
8
7
  from pandas import DataFrame, Series
9
8
 
10
- from lionagi.libs import SysUtil, func_call, convert, ParseUtil, nested
9
+ from lionagi.libs import SysUtil, convert, ParseUtil, nested, func_call
11
10
 
12
11
 
13
12
  T = TypeVar("T")
@@ -28,24 +27,38 @@ class BaseComponent(BaseModel, ABC):
28
27
  validation_alias=AliasChoices("node_id", "ID", "id"),
29
28
  description="A 32-char unique hash identifier for the node.",
30
29
  )
31
-
32
30
  timestamp: str = Field(
33
31
  default_factory=lambda: SysUtil.get_timestamp(sep=None),
34
32
  description="The timestamp of when the node was created.",
35
33
  )
36
34
 
35
+ extra_fields: dict[str, Any] = Field(
36
+ default_factory=dict,
37
+ validation_alias=AliasChoices(
38
+ "extra", "additional_fields", "schema_extra", "extra_schema"
39
+ ),
40
+ description="Additional fields for the component.",
41
+ )
42
+
37
43
  class Config:
38
44
  """Model configuration settings."""
39
45
 
40
46
  extra = "allow"
41
47
  arbitrary_types_allowed = True
42
48
  populate_by_name = True
43
- validate_assignment = True
44
- validate_return = True
45
- str_strip_whitespace = True
49
+
50
+ @property
51
+ def class_name(self) -> str:
52
+ """
53
+ Retrieve the name of the class.
54
+
55
+ Returns:
56
+ str: The name of the class.
57
+ """
58
+ return self._class_name()
46
59
 
47
60
  @classmethod
48
- def class_name(cls) -> str:
61
+ def _class_name(cls) -> str:
49
62
  """
50
63
  Retrieve the name of the class.
51
64
 
@@ -54,6 +67,101 @@ class BaseComponent(BaseModel, ABC):
54
67
  """
55
68
  return cls.__name__
56
69
 
70
+ def to_json_str(self, *args, **kwargs) -> str:
71
+ """
72
+ Convert the component to a JSON string.
73
+
74
+ Returns:
75
+ str: The JSON string representation of the component.
76
+ """
77
+ dict_ = self.to_dict(*args, **kwargs)
78
+ return convert.to_str(dict_)
79
+
80
+ def to_dict(self, *args, **kwargs) -> dict[str, Any]:
81
+ """
82
+ Convert the component to a dictionary.
83
+
84
+ Returns:
85
+ dict[str, Any]: The dictionary representation of the component.
86
+ """
87
+ dict_ = self.model_dump(*args, by_alias=True, **kwargs)
88
+ for field_name in list(self.extra_fields.keys()):
89
+ if field_name not in dict_:
90
+ dict_[field_name] = getattr(self, field_name, None)
91
+ dict_.pop("extra_fields", None)
92
+ return dict_
93
+
94
+ def to_xml(self, *args, **kwargs) -> str:
95
+ """
96
+ Convert the component to an XML string.
97
+
98
+ Returns:
99
+ str: The XML string representation of the component.
100
+ """
101
+ import xml.etree.ElementTree as ET
102
+
103
+ root = ET.Element(self.__class__.__name__)
104
+
105
+ def convert(dict_obj: dict, parent: ET.Element) -> None:
106
+ for key, val in dict_obj.items():
107
+ if isinstance(val, dict):
108
+ element = ET.SubElement(parent, key)
109
+ convert(val, element)
110
+ else:
111
+ element = ET.SubElement(parent, key)
112
+ element.text = str(val)
113
+
114
+ convert(self.to_dict(*args, **kwargs), root)
115
+ return ET.tostring(root, encoding="unicode")
116
+
117
+ def to_pd_series(self, *args, pd_kwargs: dict | None = None, **kwargs) -> Series:
118
+ """
119
+ Convert the node to a Pandas Series.
120
+
121
+ Args:
122
+ pd_kwargs (dict | None): Additional keyword arguments for Pandas Series.
123
+
124
+ Returns:
125
+ Series: The Pandas Series representation of the node.
126
+ """
127
+ pd_kwargs = {} if pd_kwargs is None else pd_kwargs
128
+ dict_ = self.to_dict(*args, **kwargs)
129
+ return Series(dict_, **pd_kwargs)
130
+
131
+ def _add_field(
132
+ self,
133
+ field_name: str,
134
+ annotation: Any | Type | None = Any,
135
+ default: Any | None = None,
136
+ value: Any | None = None,
137
+ field: Any = None,
138
+ **kwargs,
139
+ ) -> None:
140
+ """
141
+ Add a field to the model after initialization.
142
+
143
+ Args:
144
+ field_name (str): The name of the field.
145
+ annotation (Any | Type | None): The type annotation for the field.
146
+ default (Any | None): The default value for the field.
147
+ value (Any | None): The initial value for the field.
148
+ field (Any): The Field object for the field.
149
+ **kwargs: Additional keyword arguments for the Field object.
150
+ """
151
+ field = field or Field(default=default, **kwargs)
152
+ self.extra_fields[field_name] = field
153
+ if annotation:
154
+ self.extra_fields[field_name].annotation = annotation
155
+
156
+ if not value and (a := self._get_field_attr(field_name, "default", None)):
157
+ value = a
158
+
159
+ self.__setattr__(field_name, value)
160
+
161
+ @property
162
+ def _all_fields(self):
163
+ return {**self.model_fields, **self.extra_fields}
164
+
57
165
  @property
58
166
  def _field_annotations(self) -> dict:
59
167
  """
@@ -62,21 +170,20 @@ class BaseComponent(BaseModel, ABC):
62
170
  Returns:
63
171
  dict: A dictionary mapping field names to their annotations.
64
172
  """
65
- return self._get_field_annotation(list(self.model_fields.keys()))
66
173
 
67
- def _get_field_attr(self, k: str, attr: str, default=False) -> Any:
174
+ return self._get_field_annotation(list(self._all_fields.keys()))
175
+
176
+ def _get_field_attr(self, k: str, attr: str, default: Any = False) -> Any:
68
177
  """
69
178
  Get the value of a field attribute.
70
179
 
71
180
  Args:
72
181
  k (str): The field name.
73
182
  attr (str): The attribute name.
74
- default (Any): Default value to return if the attribute is not
75
- found.
183
+ default (Any): Default value to return if the attribute is not found.
76
184
 
77
185
  Returns:
78
- Any: The value of the field attribute, or the default value if not
79
- found.
186
+ Any: The value of the field attribute, or the default value if not found.
80
187
 
81
188
  Raises:
82
189
  ValueError: If the field does not have the specified attribute.
@@ -84,8 +191,9 @@ class BaseComponent(BaseModel, ABC):
84
191
  try:
85
192
  if not self._field_has_attr(k, attr):
86
193
  raise ValueError(f"field {k} has no attribute {attr}")
87
- field = self.model_fields[k]
88
- a = getattr(field, attr)
194
+
195
+ field = self._all_fields[k]
196
+ a = getattr(field, attr, None)
89
197
  if not a:
90
198
  try:
91
199
  a = field.json_schema_extra[attr]
@@ -99,7 +207,7 @@ class BaseComponent(BaseModel, ABC):
99
207
  raise e
100
208
 
101
209
  @singledispatchmethod
102
- def _get_field_annotation(self, field_name: str) -> Any:
210
+ def _get_field_annotation(self, field_name: Any) -> Any:
103
211
  """
104
212
  Get the annotation for a field.
105
213
 
@@ -112,7 +220,7 @@ class BaseComponent(BaseModel, ABC):
112
220
  raise TypeError(f"Unsupported type {type(field_name)}")
113
221
 
114
222
  @_get_field_annotation.register(str)
115
- def _(self, field_name) -> dict[str, Any]:
223
+ def _(self, field_name: str) -> dict[str, Any]:
116
224
  """
117
225
  Get the annotation for a field as a dictionary.
118
226
 
@@ -120,10 +228,9 @@ class BaseComponent(BaseModel, ABC):
120
228
  field_name (str): The name of the field.
121
229
 
122
230
  Returns:
123
- dict[str, Any]: A dictionary mapping the field name to its
124
- annotation.
231
+ dict[str, Any]: A dictionary mapping the field name to its annotation.
125
232
  """
126
- dict_ = {field_name: self.model_fields[field_name].annotation}
233
+ dict_ = {field_name: self._all_fields[field_name].annotation}
127
234
  for k, v in dict_.items():
128
235
  if "|" in str(v):
129
236
  v = str(v)
@@ -134,41 +241,22 @@ class BaseComponent(BaseModel, ABC):
134
241
  return dict_
135
242
 
136
243
  @_get_field_annotation.register(list)
137
- def _(self, field_name) -> dict[str, Any]:
138
- """
139
- Get the annotations for multiple fields as a dictionary.
140
-
141
- Args:
142
- field_name (list): A list or tuple of field names.
143
-
144
- Returns:
145
- dict[str, Any]: A dictionary mapping field names to their
146
- annotations.
147
- """
148
- dict_ = {}
149
- for i in field_name:
150
- dict_.update(self._get_field_annotation(i))
151
- return dict_
152
-
153
-
154
244
  @_get_field_annotation.register(tuple)
155
- def _(self, field_name) -> dict[str, Any]:
245
+ def _(self, field_names: list | tuple) -> dict[str, Any]:
156
246
  """
157
247
  Get the annotations for multiple fields as a dictionary.
158
248
 
159
249
  Args:
160
- field_name (tuple): A list or tuple of field names.
250
+ field_names (list | tuple): A list or tuple of field names.
161
251
 
162
252
  Returns:
163
- dict[str, Any]: A dictionary mapping field names to their
164
- annotations.
253
+ dict[str, Any]: A dictionary mapping field names to their annotations.
165
254
  """
166
255
  dict_ = {}
167
- for i in field_name:
168
- dict_.update(self._get_field_annotation(i))
256
+ for field_name in field_names:
257
+ dict_.update(self._get_field_annotation(field_name))
169
258
  return dict_
170
259
 
171
-
172
260
  def _field_has_attr(self, k: str, attr: str) -> bool:
173
261
  """
174
262
  Check if a field has a specific attribute.
@@ -180,7 +268,7 @@ class BaseComponent(BaseModel, ABC):
180
268
  Returns:
181
269
  bool: True if the field has the attribute, False otherwise.
182
270
  """
183
- field = self.model_fields.get(k, None)
271
+ field = self._all_fields.get(k, None)
184
272
  if field is None:
185
273
  raise ValueError(f"Field {k} not found in model fields.")
186
274
 
@@ -188,65 +276,16 @@ class BaseComponent(BaseModel, ABC):
188
276
  if not a:
189
277
  try:
190
278
  a = (
191
- self.model_fields[k].json_schema_extra[attr] is not None
192
- and attr in self.model_fields[k].json_schema_extra
279
+ self._all_fields[k].json_schema_extra[attr] is not None
280
+ and attr in self._all_fields[k].json_schema_extra
193
281
  )
194
282
  return a if isinstance(a, bool) else False
195
283
  except Exception:
196
284
  return False
197
285
  return a
198
286
 
199
- def to_json_str(self, *args, **kwargs) -> str:
200
- """
201
- Convert the component to a JSON string.
202
-
203
- Returns:
204
- str: The JSON string representation of the component.
205
- """
206
- return self.model_dump_json(*args, by_alias=True, **kwargs)
207
-
208
- def to_dict(self, *args, **kwargs) -> dict[str, Any]:
209
- """
210
- Convert the component to a dictionary.
211
-
212
- Returns:
213
- dict[str, Any]: The dictionary representation of the component.
214
- """
215
- return self.model_dump(*args, by_alias=True, **kwargs)
216
-
217
- def to_xml(self) -> str:
218
- """
219
- Convert the component to an XML string.
220
-
221
- Returns:
222
- str: The XML string representation of the component.
223
- """
224
- import xml.etree.ElementTree as ET
225
-
226
- root = ET.Element(self.__class__.__name__)
227
-
228
- def convert(dict_obj, parent):
229
- for key, val in dict_obj.items():
230
- if isinstance(val, dict):
231
- element = ET.SubElement(parent, key)
232
- convert(val, element)
233
- else:
234
- element = ET.SubElement(parent, key)
235
- element.text = str(val)
236
-
237
- convert(self.to_dict(), root)
238
- return ET.tostring(root, encoding="unicode")
239
-
240
- def to_pd_series(self, *args, pd_kwargs: dict | None = None, **kwargs) -> Series:
241
- """
242
- Convert the node to a Pandas Series.
243
-
244
- Returns:
245
- Series: The Pandas Series representation of the node.
246
- """
247
- pd_kwargs = {} if pd_kwargs is None else pd_kwargs
248
- dict_ = self.to_dict(*args, **kwargs)
249
- return Series(dict_, **pd_kwargs)
287
+ def __str__(self):
288
+ return f"{self.__class__.__name__}({self.to_json_str()})"
250
289
 
251
290
 
252
291
  class BaseNode(BaseComponent):
@@ -266,7 +305,7 @@ class BaseNode(BaseComponent):
266
305
 
267
306
  metadata: dict[str, Any] = Field(
268
307
  default_factory=dict,
269
- alias="meta",
308
+ validation_alias="meta",
270
309
  description="Additional metadata for the node.",
271
310
  )
272
311
 
@@ -278,20 +317,36 @@ class BaseNode(BaseComponent):
278
317
 
279
318
  Args:
280
319
  obj (Any): The object to create the node from.
320
+ *args: Additional positional arguments.
321
+ **kwargs: Additional keyword arguments.
281
322
 
282
323
  Raises:
283
324
  NotImplementedError: If the object type is not supported.
284
325
  """
326
+ if not isinstance(obj, (dict, str, list, Series, DataFrame, BaseModel)):
327
+ type_ = str(type(obj))
328
+ if "llama_index" in type_:
329
+ return cls.from_obj(obj.to_dict())
330
+ elif "langchain" in type_:
331
+ langchain_json = obj.to_json()
332
+ langchain_dict = {
333
+ "lc_id": langchain_json["id"],
334
+ **langchain_json["kwargs"],
335
+ }
336
+ return cls.from_obj(langchain_dict)
337
+
285
338
  raise NotImplementedError(f"Unsupported type: {type(obj)}")
286
339
 
287
340
  @from_obj.register(dict)
288
341
  @classmethod
289
- def _from_dict(cls, obj, *args, **kwargs) -> T:
342
+ def _from_dict(cls, obj: dict, *args, **kwargs) -> T:
290
343
  """
291
344
  Create a node instance from a dictionary.
292
345
 
293
346
  Args:
294
347
  obj (dict): The dictionary to create the node from.
348
+ *args: Additional positional arguments.
349
+ **kwargs: Additional keyword arguments.
295
350
 
296
351
  Returns:
297
352
  T: The created node instance.
@@ -300,13 +355,15 @@ class BaseNode(BaseComponent):
300
355
 
301
356
  @from_obj.register(str)
302
357
  @classmethod
303
- def _from_str(cls, obj, *args, fuzzy_parse=False, **kwargs) -> T:
358
+ def _from_str(cls, obj: str, *args, fuzzy_parse: bool = False, **kwargs) -> T:
304
359
  """
305
360
  Create a node instance from a JSON string.
306
361
 
307
362
  Args:
308
363
  obj (str): The JSON string to create the node from.
364
+ *args: Additional positional arguments.
309
365
  fuzzy_parse (bool): Whether to perform fuzzy parsing.
366
+ **kwargs: Additional keyword arguments.
310
367
 
311
368
  Returns:
312
369
  T: The created node instance.
@@ -319,12 +376,14 @@ class BaseNode(BaseComponent):
319
376
 
320
377
  @from_obj.register(list)
321
378
  @classmethod
322
- def _from_list(cls, obj, *args, **kwargs) -> list[T]:
379
+ def _from_list(cls, obj: list, *args, **kwargs) -> list[T]:
323
380
  """
324
381
  Create a list of node instances from a list of objects.
325
382
 
326
383
  Args:
327
384
  obj (list): The list of objects to create nodes from.
385
+ *args: Additional positional arguments.
386
+ **kwargs: Additional keyword arguments.
328
387
 
329
388
  Returns:
330
389
  list[T]: The list of created node instances.
@@ -333,37 +392,51 @@ class BaseNode(BaseComponent):
333
392
 
334
393
  @from_obj.register(Series)
335
394
  @classmethod
336
- def _from_pd_series(cls, obj, *args, pd_kwargs=None, **kwargs) -> T:
395
+ def _from_pd_series(
396
+ cls, obj: Series, *args, pd_kwargs: dict | None = None, **kwargs
397
+ ) -> T:
337
398
  """
338
399
  Create a node instance from a Pandas Series.
339
400
 
340
401
  Args:
341
402
  obj (Series): The Pandas Series to create the node from.
403
+ *args: Additional positional arguments.
404
+ pd_kwargs (dict | None): Additional keyword arguments for Pandas Series.
405
+ **kwargs: Additional keyword arguments.
342
406
 
343
407
  Returns:
344
408
  T: The created node instance.
345
409
  """
346
- if pd_kwargs is None:
347
- pd_kwargs = {}
410
+ pd_kwargs = pd_kwargs or {}
348
411
  return cls.from_obj(obj.to_dict(**pd_kwargs), *args, **kwargs)
349
412
 
350
413
  @from_obj.register(DataFrame)
351
414
  @classmethod
352
- def _from_pd_dataframe(cls, obj, *args, pd_kwargs=None, **kwargs) -> list[T]:
415
+ def _from_pd_dataframe(
416
+ cls, obj: DataFrame, *args, pd_kwargs: dict | None = None, **kwargs
417
+ ) -> list[T]:
353
418
  """
354
419
  Create a list of node instances from a Pandas DataFrame.
355
420
 
356
421
  Args:
357
422
  obj (DataFrame): The Pandas DataFrame to create nodes from.
423
+ *args: Additional positional arguments.
424
+ pd_kwargs (dict | None): Additional keyword arguments for Pandas DataFrame.
425
+ **kwargs: Additional keyword arguments.
358
426
 
359
427
  Returns:
360
428
  list[T]: The list of created node instances.
361
429
  """
362
430
  if pd_kwargs is None:
363
431
  pd_kwargs = {}
364
- return [
365
- cls.from_obj(row, *args, **pd_kwargs, **kwargs) for _, row in obj.iterrows()
366
- ]
432
+
433
+ _objs = []
434
+ for index, row in obj.iterrows():
435
+ _obj = cls.from_obj(row, *args, **pd_kwargs, **kwargs)
436
+ _obj.metadata["df_index"] = index
437
+ _objs.append(_obj)
438
+
439
+ return _objs
367
440
 
368
441
  @from_obj.register(BaseModel)
369
442
  @classmethod
@@ -377,27 +450,29 @@ class BaseNode(BaseComponent):
377
450
  Returns:
378
451
  T: The created node instance.
379
452
  """
380
- if pydantic_kwargs is None:
381
- pydantic_kwargs = {"by_alias": True}
382
-
383
- config_ = {}
453
+ pydantic_kwargs = pydantic_kwargs or {"by_alias": True}
384
454
  try:
385
- config_ = obj.model_dump(**pydantic_kwargs)
386
- except:
387
- config_ = obj.dict(**pydantic_kwargs)
455
+ config_ = {}
456
+ try:
457
+ config_ = obj.model_dump(**pydantic_kwargs)
458
+ except:
459
+ config_ = obj.to_dict(**pydantic_kwargs)
460
+ else:
461
+ config_ = obj.dict(**pydantic_kwargs)
462
+ except Exception as e:
463
+ raise ValueError(f"Invalid Pydantic model for deserialization: {e}") from e
388
464
 
389
465
  return cls.from_obj(config_ | kwargs)
390
466
 
391
467
  def meta_get(
392
- self, key: str, indices: list[str | int] = None, default: Any = None
468
+ self, key: str, indices: list[str | int] | None = None, default: Any = None
393
469
  ) -> Any:
394
470
  """
395
471
  Get a value from the metadata dictionary.
396
472
 
397
473
  Args:
398
474
  key (str): The key to retrieve the value for.
399
- indices (list[str | int]): Optional list of indices for nested
400
- retrieval.
475
+ indices (list[str | int] | None): Optional list of indices for nested retrieval.
401
476
  default (Any): The default value to return if the key is not found.
402
477
 
403
478
  Returns:
@@ -454,4 +529,4 @@ class BaseNode(BaseComponent):
454
529
  """
455
530
  self.metadata = nested.nmerge(
456
531
  [self.metadata, additional_metadata], overwrite=overwrite, **kwargs
457
- )
532
+ )
@@ -1,5 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from enum import Enum
3
+ from typing import Callable
3
4
  from pydantic import BaseModel, Field
4
5
 
5
6
 
@@ -14,6 +15,7 @@ class ConditionSource(str, Enum):
14
15
 
15
16
  STRUCTURE = "structure"
16
17
  EXECUTABLE = "executable"
18
+ RULE = "rule"
17
19
 
18
20
 
19
21
  class Condition(BaseModel, ABC):
@@ -35,7 +37,7 @@ class Condition(BaseModel, ABC):
35
37
  extra = "allow"
36
38
 
37
39
  @abstractmethod
38
- def __call__(self, *args, **kwargs) -> bool:
40
+ def __call__(self, executable) -> bool:
39
41
  """Evaluates the condition based on implemented logic.
40
42
 
41
43
  Returns: