lionagi 0.0.315__py3-none-any.whl → 0.1.0__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/__init__.py +19 -8
  2. lionagi/core/agent/__init__.py +0 -3
  3. lionagi/core/agent/base_agent.py +26 -30
  4. lionagi/core/branch/__init__.py +0 -4
  5. lionagi/core/branch/{base_branch.py → base.py} +13 -14
  6. lionagi/core/branch/branch.py +22 -20
  7. lionagi/core/branch/executable_branch.py +0 -347
  8. lionagi/core/branch/{branch_flow_mixin.py → flow_mixin.py} +6 -6
  9. lionagi/core/branch/util.py +1 -1
  10. lionagi/core/direct/__init__.py +13 -1
  11. lionagi/core/direct/cot.py +123 -1
  12. lionagi/core/direct/plan.py +164 -0
  13. lionagi/core/direct/predict.py +13 -9
  14. lionagi/core/direct/react.py +12 -8
  15. lionagi/core/direct/score.py +4 -4
  16. lionagi/core/direct/select.py +4 -4
  17. lionagi/core/direct/utils.py +23 -0
  18. lionagi/core/direct/vote.py +2 -2
  19. lionagi/core/execute/base_executor.py +50 -0
  20. lionagi/core/execute/branch_executor.py +233 -0
  21. lionagi/core/execute/instruction_map_executor.py +131 -0
  22. lionagi/core/execute/structure_executor.py +218 -0
  23. lionagi/core/flow/monoflow/ReAct.py +4 -4
  24. lionagi/core/flow/monoflow/chat.py +6 -6
  25. lionagi/core/flow/monoflow/chat_mixin.py +24 -34
  26. lionagi/core/flow/monoflow/followup.py +4 -4
  27. lionagi/core/flow/polyflow/__init__.py +1 -1
  28. lionagi/core/flow/polyflow/chat.py +15 -12
  29. lionagi/core/{prompt/action_template.py → form/action_form.py} +2 -2
  30. lionagi/core/{prompt → form}/field_validator.py +40 -31
  31. lionagi/core/form/form.py +302 -0
  32. lionagi/core/form/mixin.py +214 -0
  33. lionagi/core/{prompt/scored_template.py → form/scored_form.py} +2 -2
  34. lionagi/core/generic/__init__.py +37 -0
  35. lionagi/core/generic/action.py +26 -0
  36. lionagi/core/generic/component.py +457 -0
  37. lionagi/core/generic/condition.py +44 -0
  38. lionagi/core/generic/data_logger.py +305 -0
  39. lionagi/core/generic/edge.py +110 -0
  40. lionagi/core/generic/mail.py +90 -0
  41. lionagi/core/generic/mailbox.py +36 -0
  42. lionagi/core/generic/node.py +285 -0
  43. lionagi/core/generic/relation.py +70 -0
  44. lionagi/core/generic/signal.py +22 -0
  45. lionagi/core/generic/structure.py +362 -0
  46. lionagi/core/generic/transfer.py +20 -0
  47. lionagi/core/generic/work.py +40 -0
  48. lionagi/core/graph/graph.py +126 -0
  49. lionagi/core/graph/tree.py +190 -0
  50. lionagi/core/mail/__init__.py +0 -8
  51. lionagi/core/mail/mail_manager.py +12 -10
  52. lionagi/core/mail/schema.py +9 -2
  53. lionagi/core/messages/__init__.py +0 -3
  54. lionagi/core/messages/schema.py +17 -225
  55. lionagi/core/session/__init__.py +0 -3
  56. lionagi/core/session/session.py +25 -23
  57. lionagi/core/tool/__init__.py +3 -1
  58. lionagi/core/tool/tool.py +28 -0
  59. lionagi/core/tool/tool_manager.py +75 -75
  60. lionagi/integrations/chunker/chunk.py +7 -7
  61. lionagi/integrations/config/oai_configs.py +4 -4
  62. lionagi/integrations/loader/load.py +6 -6
  63. lionagi/integrations/loader/load_util.py +8 -8
  64. lionagi/libs/ln_api.py +3 -3
  65. lionagi/libs/ln_parse.py +43 -6
  66. lionagi/libs/ln_validate.py +288 -0
  67. lionagi/libs/sys_util.py +28 -6
  68. lionagi/tests/libs/test_async.py +0 -0
  69. lionagi/tests/libs/test_field_validators.py +353 -0
  70. lionagi/tests/test_core/test_base_branch.py +0 -1
  71. lionagi/tests/test_core/test_branch.py +3 -0
  72. lionagi/tests/test_core/test_session_base_util.py +1 -0
  73. lionagi/version.py +1 -1
  74. {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/METADATA +1 -1
  75. lionagi-0.1.0.dist-info/RECORD +136 -0
  76. lionagi/core/prompt/prompt_template.py +0 -312
  77. lionagi/core/schema/__init__.py +0 -22
  78. lionagi/core/schema/action_node.py +0 -29
  79. lionagi/core/schema/base_mixin.py +0 -296
  80. lionagi/core/schema/base_node.py +0 -199
  81. lionagi/core/schema/condition.py +0 -24
  82. lionagi/core/schema/data_logger.py +0 -354
  83. lionagi/core/schema/data_node.py +0 -93
  84. lionagi/core/schema/prompt_template.py +0 -67
  85. lionagi/core/schema/structure.py +0 -912
  86. lionagi/core/tool/manual.py +0 -1
  87. lionagi-0.0.315.dist-info/RECORD +0 -121
  88. /lionagi/core/{branch/base → execute}/__init__.py +0 -0
  89. /lionagi/core/flow/{base/baseflow.py → baseflow.py} +0 -0
  90. /lionagi/core/flow/{base/__init__.py → mono_chat_mixin.py} +0 -0
  91. /lionagi/core/{prompt → form}/__init__.py +0 -0
  92. /lionagi/{tests/test_integrations → core/graph}/__init__.py +0 -0
  93. /lionagi/tests/{test_libs → integrations}/__init__.py +0 -0
  94. /lionagi/tests/{test_libs/test_async.py → libs/__init__.py} +0 -0
  95. /lionagi/tests/{test_libs → libs}/test_api.py +0 -0
  96. /lionagi/tests/{test_libs → libs}/test_convert.py +0 -0
  97. /lionagi/tests/{test_libs → libs}/test_func_call.py +0 -0
  98. /lionagi/tests/{test_libs → libs}/test_nested.py +0 -0
  99. /lionagi/tests/{test_libs → libs}/test_parse.py +0 -0
  100. /lionagi/tests/{test_libs → libs}/test_sys_util.py +0 -0
  101. {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/LICENSE +0 -0
  102. {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/WHEEL +0 -0
  103. {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,457 @@
1
+ """This module provides base components used in lionagi"""
2
+
3
+ from abc import ABC
4
+ from functools import singledispatchmethod
5
+ from typing import Any, TypeVar
6
+
7
+ from pydantic import AliasChoices, BaseModel, Field, ValidationError
8
+ from pandas import DataFrame, Series
9
+
10
+ from lionagi.libs import SysUtil, func_call, convert, ParseUtil, nested
11
+
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ class BaseComponent(BaseModel, ABC):
17
+ """
18
+ Base class for creating component models.
19
+
20
+ Attributes:
21
+ id_ (str): A 32-char unique hash identifier for the node.
22
+ timestamp (str): The timestamp of when the node was created.
23
+ """
24
+
25
+ id_: str = Field(
26
+ title="ID",
27
+ default_factory=SysUtil.create_id,
28
+ validation_alias=AliasChoices("node_id", "ID", "id"),
29
+ description="A 32-char unique hash identifier for the node.",
30
+ )
31
+
32
+ timestamp: str = Field(
33
+ default_factory=lambda: SysUtil.get_timestamp(sep=None),
34
+ description="The timestamp of when the node was created.",
35
+ )
36
+
37
+ class Config:
38
+ """Model configuration settings."""
39
+
40
+ extra = "allow"
41
+ arbitrary_types_allowed = True
42
+ populate_by_name = True
43
+ validate_assignment = True
44
+ validate_return = True
45
+ str_strip_whitespace = True
46
+
47
+ @classmethod
48
+ def class_name(cls) -> str:
49
+ """
50
+ Retrieve the name of the class.
51
+
52
+ Returns:
53
+ str: The name of the class.
54
+ """
55
+ return cls.__name__
56
+
57
+ @property
58
+ def _field_annotations(self) -> dict:
59
+ """
60
+ Return the annotations for each field in the model.
61
+
62
+ Returns:
63
+ dict: A dictionary mapping field names to their annotations.
64
+ """
65
+ return self._get_field_annotation(list(self.model_fields.keys()))
66
+
67
+ def _get_field_attr(self, k: str, attr: str, default=False) -> Any:
68
+ """
69
+ Get the value of a field attribute.
70
+
71
+ Args:
72
+ k (str): The field name.
73
+ attr (str): The attribute name.
74
+ default (Any): Default value to return if the attribute is not
75
+ found.
76
+
77
+ Returns:
78
+ Any: The value of the field attribute, or the default value if not
79
+ found.
80
+
81
+ Raises:
82
+ ValueError: If the field does not have the specified attribute.
83
+ """
84
+ try:
85
+ if not self._field_has_attr(k, attr):
86
+ raise ValueError(f"field {k} has no attribute {attr}")
87
+ field = self.model_fields[k]
88
+ a = getattr(field, attr)
89
+ if not a:
90
+ try:
91
+ a = field.json_schema_extra[attr]
92
+ return a
93
+ except Exception:
94
+ return None
95
+ return a
96
+ except Exception as e:
97
+ if default is not False:
98
+ return default
99
+ raise e
100
+
101
+ @singledispatchmethod
102
+ def _get_field_annotation(self, field_name: str) -> Any:
103
+ """
104
+ Get the annotation for a field.
105
+
106
+ Args:
107
+ field_name (str): The name of the field.
108
+
109
+ Raises:
110
+ TypeError: If the field_name is of an unsupported type.
111
+ """
112
+ raise TypeError(f"Unsupported type {type(field_name)}")
113
+
114
+ @_get_field_annotation.register(str)
115
+ def _(self, field_name) -> dict[str, Any]:
116
+ """
117
+ Get the annotation for a field as a dictionary.
118
+
119
+ Args:
120
+ field_name (str): The name of the field.
121
+
122
+ Returns:
123
+ dict[str, Any]: A dictionary mapping the field name to its
124
+ annotation.
125
+ """
126
+ dict_ = {field_name: self.model_fields[field_name].annotation}
127
+ for k, v in dict_.items():
128
+ if "|" in str(v):
129
+ v = str(v)
130
+ v = v.split("|")
131
+ dict_[k] = func_call.lcall(v, convert.strip_lower)
132
+ else:
133
+ dict_[k] = [v.__name__]
134
+ return dict_
135
+
136
+ @_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
+ @_get_field_annotation.register(tuple)
155
+ def _(self, field_name) -> dict[str, Any]:
156
+ """
157
+ Get the annotations for multiple fields as a dictionary.
158
+
159
+ Args:
160
+ field_name (tuple): A list or tuple of field names.
161
+
162
+ Returns:
163
+ dict[str, Any]: A dictionary mapping field names to their
164
+ annotations.
165
+ """
166
+ dict_ = {}
167
+ for i in field_name:
168
+ dict_.update(self._get_field_annotation(i))
169
+ return dict_
170
+
171
+
172
+ def _field_has_attr(self, k: str, attr: str) -> bool:
173
+ """
174
+ Check if a field has a specific attribute.
175
+
176
+ Args:
177
+ k (str): The field name.
178
+ attr (str): The attribute name.
179
+
180
+ Returns:
181
+ bool: True if the field has the attribute, False otherwise.
182
+ """
183
+ field = self.model_fields.get(k, None)
184
+ if field is None:
185
+ raise ValueError(f"Field {k} not found in model fields.")
186
+
187
+ a = attr in str(field)
188
+ if not a:
189
+ try:
190
+ a = (
191
+ self.model_fields[k].json_schema_extra[attr] is not None
192
+ and attr in self.model_fields[k].json_schema_extra
193
+ )
194
+ return a if isinstance(a, bool) else False
195
+ except Exception:
196
+ return False
197
+ return a
198
+
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)
250
+
251
+
252
+ class BaseNode(BaseComponent):
253
+ """
254
+ Base class for creating node models.
255
+
256
+ Attributes:
257
+ content (Any | None): The optional content of the node.
258
+ metadata (dict[str, Any]): Additional metadata for the node.
259
+ """
260
+
261
+ content: Any | None = Field(
262
+ default=None,
263
+ validation_alias=AliasChoices("text", "page_content", "chunk_content", "data"),
264
+ description="The optional content of the node.",
265
+ )
266
+
267
+ metadata: dict[str, Any] = Field(
268
+ default_factory=dict,
269
+ alias="meta",
270
+ description="Additional metadata for the node.",
271
+ )
272
+
273
+ @singledispatchmethod
274
+ @classmethod
275
+ def from_obj(cls, obj: Any, *args, **kwargs) -> T:
276
+ """
277
+ Create a node instance from an object.
278
+
279
+ Args:
280
+ obj (Any): The object to create the node from.
281
+
282
+ Raises:
283
+ NotImplementedError: If the object type is not supported.
284
+ """
285
+ raise NotImplementedError(f"Unsupported type: {type(obj)}")
286
+
287
+ @from_obj.register(dict)
288
+ @classmethod
289
+ def _from_dict(cls, obj, *args, **kwargs) -> T:
290
+ """
291
+ Create a node instance from a dictionary.
292
+
293
+ Args:
294
+ obj (dict): The dictionary to create the node from.
295
+
296
+ Returns:
297
+ T: The created node instance.
298
+ """
299
+ return cls.model_validate(obj, *args, **kwargs)
300
+
301
+ @from_obj.register(str)
302
+ @classmethod
303
+ def _from_str(cls, obj, *args, fuzzy_parse=False, **kwargs) -> T:
304
+ """
305
+ Create a node instance from a JSON string.
306
+
307
+ Args:
308
+ obj (str): The JSON string to create the node from.
309
+ fuzzy_parse (bool): Whether to perform fuzzy parsing.
310
+
311
+ Returns:
312
+ T: The created node instance.
313
+ """
314
+ obj = ParseUtil.fuzzy_parse_json(obj) if fuzzy_parse else convert.to_dict(obj)
315
+ try:
316
+ return cls.from_obj(obj, *args, **kwargs)
317
+ except ValidationError as e:
318
+ raise ValueError(f"Invalid JSON for deserialization: {e}") from e
319
+
320
+ @from_obj.register(list)
321
+ @classmethod
322
+ def _from_list(cls, obj, *args, **kwargs) -> list[T]:
323
+ """
324
+ Create a list of node instances from a list of objects.
325
+
326
+ Args:
327
+ obj (list): The list of objects to create nodes from.
328
+
329
+ Returns:
330
+ list[T]: The list of created node instances.
331
+ """
332
+ return [cls.from_obj(item, *args, **kwargs) for item in obj]
333
+
334
+ @from_obj.register(Series)
335
+ @classmethod
336
+ def _from_pd_series(cls, obj, *args, pd_kwargs=None, **kwargs) -> T:
337
+ """
338
+ Create a node instance from a Pandas Series.
339
+
340
+ Args:
341
+ obj (Series): The Pandas Series to create the node from.
342
+
343
+ Returns:
344
+ T: The created node instance.
345
+ """
346
+ if pd_kwargs is None:
347
+ pd_kwargs = {}
348
+ return cls.from_obj(obj.to_dict(**pd_kwargs), *args, **kwargs)
349
+
350
+ @from_obj.register(DataFrame)
351
+ @classmethod
352
+ def _from_pd_dataframe(cls, obj, *args, pd_kwargs=None, **kwargs) -> list[T]:
353
+ """
354
+ Create a list of node instances from a Pandas DataFrame.
355
+
356
+ Args:
357
+ obj (DataFrame): The Pandas DataFrame to create nodes from.
358
+
359
+ Returns:
360
+ list[T]: The list of created node instances.
361
+ """
362
+ if pd_kwargs is None:
363
+ pd_kwargs = {}
364
+ return [
365
+ cls.from_obj(row, *args, **pd_kwargs, **kwargs) for _, row in obj.iterrows()
366
+ ]
367
+
368
+ @from_obj.register(BaseModel)
369
+ @classmethod
370
+ def _from_base_model(cls, obj, pydantic_kwargs=None, **kwargs) -> T:
371
+ """
372
+ Create a node instance from a Pydantic BaseModel.
373
+
374
+ Args:
375
+ obj (BaseModel): The Pydantic BaseModel to create the node from.
376
+
377
+ Returns:
378
+ T: The created node instance.
379
+ """
380
+ if pydantic_kwargs is None:
381
+ pydantic_kwargs = {"by_alias": True}
382
+
383
+ config_ = {}
384
+ try:
385
+ config_ = obj.model_dump(**pydantic_kwargs)
386
+ except:
387
+ config_ = obj.dict(**pydantic_kwargs)
388
+
389
+ return cls.from_obj(config_ | kwargs)
390
+
391
+ def meta_get(
392
+ self, key: str, indices: list[str | int] = None, default: Any = None
393
+ ) -> Any:
394
+ """
395
+ Get a value from the metadata dictionary.
396
+
397
+ Args:
398
+ key (str): The key to retrieve the value for.
399
+ indices (list[str | int]): Optional list of indices for nested
400
+ retrieval.
401
+ default (Any): The default value to return if the key is not found.
402
+
403
+ Returns:
404
+ Any: The retrieved value or the default value if not found.
405
+ """
406
+ if indices:
407
+ return nested.nget(self.metadata, indices, default)
408
+ return self.metadata.get(key, default)
409
+
410
+ def meta_change_key(self, old_key: str, new_key: str) -> bool:
411
+ """
412
+ Change a key in the metadata dictionary.
413
+
414
+ Args:
415
+ old_key (str): The old key to be changed.
416
+ new_key (str): The new key to replace the old key.
417
+
418
+ Returns:
419
+ bool: True if the key was changed successfully, False otherwise.
420
+ """
421
+ if old_key in self.metadata:
422
+ SysUtil.change_dict_key(self.metadata, old_key, new_key)
423
+ return True
424
+ return False
425
+
426
+ def meta_insert(self, indices: str | list, value: Any, **kwargs) -> bool:
427
+ """
428
+ Insert a value into the metadata dictionary at the specified indices.
429
+
430
+ Args:
431
+ indices (str | list): The indices to insert the value at.
432
+ value (Any): The value to be inserted.
433
+ **kwargs: Additional keyword arguments for the `nested.ninsert`
434
+ function.
435
+
436
+ Returns:
437
+ bool: True if the value was inserted successfully, False otherwise.
438
+ """
439
+ return nested.ninsert(self.metadata, indices, value, **kwargs)
440
+
441
+ def meta_merge(
442
+ self, additional_metadata: dict[str, Any], overwrite: bool = False, **kwargs
443
+ ) -> None:
444
+ """
445
+ Merge additional metadata into the existing metadata dictionary.
446
+
447
+ Args:
448
+ additional_metadata (dict[str, Any]): The additional metadata to be
449
+ merged.
450
+ overwrite (bool): Whether to overwrite existing keys with the new
451
+ values.
452
+ **kwargs: Additional keyword arguments for the `nested.nmerge`
453
+ function.
454
+ """
455
+ self.metadata = nested.nmerge(
456
+ [self.metadata, additional_metadata], overwrite=overwrite, **kwargs
457
+ )
@@ -0,0 +1,44 @@
1
+ from abc import ABC, abstractmethod
2
+ from enum import Enum
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class ConditionSource(str, Enum):
7
+ """
8
+ Enumeration for specifying the source type of a condition.
9
+
10
+ Attributes:
11
+ STRUCTURE: Represents a condition based on the structure.
12
+ EXECUTABLE: Represents a condition that can be executed or evaluated.
13
+ """
14
+
15
+ STRUCTURE = "structure"
16
+ EXECUTABLE = "executable"
17
+
18
+
19
+ class Condition(BaseModel, ABC):
20
+ """
21
+ Abstract base class for defining conditions associated with edges.
22
+
23
+ Attributes:
24
+ source_type (ConditionSource): Specifies the type of source for the condition.
25
+
26
+ Methods:
27
+ check: Abstract method that should be implemented to evaluate the condition.
28
+ """
29
+
30
+ source_type: str = Field(..., description="The type of source for the condition.")
31
+
32
+ class Config:
33
+ """Model configuration settings."""
34
+
35
+ extra = "allow"
36
+
37
+ @abstractmethod
38
+ def __call__(self, *args, **kwargs) -> bool:
39
+ """Evaluates the condition based on implemented logic.
40
+
41
+ Returns:
42
+ bool: The boolean result of the condition evaluation.
43
+ """
44
+ pass