lionagi 0.1.2__py3-none-any.whl → 0.2.1__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.
Files changed (268) hide show
  1. lionagi/__init__.py +60 -5
  2. lionagi/core/__init__.py +0 -25
  3. lionagi/core/_setting/_setting.py +59 -0
  4. lionagi/core/action/__init__.py +14 -0
  5. lionagi/core/action/function_calling.py +136 -0
  6. lionagi/core/action/manual.py +1 -0
  7. lionagi/core/action/node.py +109 -0
  8. lionagi/core/action/tool.py +114 -0
  9. lionagi/core/action/tool_manager.py +356 -0
  10. lionagi/core/agent/base_agent.py +27 -13
  11. lionagi/core/agent/eval/evaluator.py +1 -0
  12. lionagi/core/agent/eval/vote.py +40 -0
  13. lionagi/core/agent/learn/learner.py +59 -0
  14. lionagi/core/agent/plan/unit_template.py +1 -0
  15. lionagi/core/collections/__init__.py +17 -0
  16. lionagi/core/{generic/data_logger.py → collections/_logger.py} +69 -55
  17. lionagi/core/collections/abc/__init__.py +53 -0
  18. lionagi/core/collections/abc/component.py +615 -0
  19. lionagi/core/collections/abc/concepts.py +297 -0
  20. lionagi/core/collections/abc/exceptions.py +150 -0
  21. lionagi/core/collections/abc/util.py +45 -0
  22. lionagi/core/collections/exchange.py +161 -0
  23. lionagi/core/collections/flow.py +426 -0
  24. lionagi/core/collections/model.py +419 -0
  25. lionagi/core/collections/pile.py +913 -0
  26. lionagi/core/collections/progression.py +236 -0
  27. lionagi/core/collections/util.py +64 -0
  28. lionagi/core/director/direct.py +314 -0
  29. lionagi/core/director/director.py +2 -0
  30. lionagi/core/{execute/branch_executor.py → engine/branch_engine.py} +134 -97
  31. lionagi/core/{execute/instruction_map_executor.py → engine/instruction_map_engine.py} +80 -55
  32. lionagi/{experimental/directive/evaluator → core/engine}/script_engine.py +17 -1
  33. lionagi/core/executor/base_executor.py +90 -0
  34. lionagi/core/{execute/structure_executor.py → executor/graph_executor.py} +62 -66
  35. lionagi/core/{execute → executor}/neo4j_executor.py +70 -67
  36. lionagi/core/generic/__init__.py +3 -33
  37. lionagi/core/generic/edge.py +29 -79
  38. lionagi/core/generic/edge_condition.py +16 -0
  39. lionagi/core/generic/graph.py +236 -0
  40. lionagi/core/generic/hyperedge.py +1 -0
  41. lionagi/core/generic/node.py +156 -221
  42. lionagi/core/generic/tree.py +48 -0
  43. lionagi/core/generic/tree_node.py +79 -0
  44. lionagi/core/mail/__init__.py +12 -0
  45. lionagi/core/mail/mail.py +25 -0
  46. lionagi/core/mail/mail_manager.py +139 -58
  47. lionagi/core/mail/package.py +45 -0
  48. lionagi/core/mail/start_mail.py +36 -0
  49. lionagi/core/message/__init__.py +19 -0
  50. lionagi/core/message/action_request.py +133 -0
  51. lionagi/core/message/action_response.py +135 -0
  52. lionagi/core/message/assistant_response.py +95 -0
  53. lionagi/core/message/instruction.py +234 -0
  54. lionagi/core/message/message.py +101 -0
  55. lionagi/core/message/system.py +86 -0
  56. lionagi/core/message/util.py +283 -0
  57. lionagi/core/report/__init__.py +4 -0
  58. lionagi/core/report/base.py +217 -0
  59. lionagi/core/report/form.py +231 -0
  60. lionagi/core/report/report.py +166 -0
  61. lionagi/core/report/util.py +28 -0
  62. lionagi/core/rule/_default.py +16 -0
  63. lionagi/core/rule/action.py +99 -0
  64. lionagi/core/rule/base.py +238 -0
  65. lionagi/core/rule/boolean.py +56 -0
  66. lionagi/core/rule/choice.py +47 -0
  67. lionagi/core/rule/mapping.py +96 -0
  68. lionagi/core/rule/number.py +71 -0
  69. lionagi/core/rule/rulebook.py +109 -0
  70. lionagi/core/rule/string.py +52 -0
  71. lionagi/core/rule/util.py +35 -0
  72. lionagi/core/session/branch.py +431 -0
  73. lionagi/core/session/directive_mixin.py +287 -0
  74. lionagi/core/session/session.py +229 -903
  75. lionagi/core/structure/__init__.py +1 -0
  76. lionagi/core/structure/chain.py +1 -0
  77. lionagi/core/structure/forest.py +1 -0
  78. lionagi/core/structure/graph.py +1 -0
  79. lionagi/core/structure/tree.py +1 -0
  80. lionagi/core/unit/__init__.py +5 -0
  81. lionagi/core/unit/parallel_unit.py +245 -0
  82. lionagi/core/unit/template/action.py +81 -0
  83. lionagi/core/unit/template/base.py +51 -0
  84. lionagi/core/unit/template/plan.py +84 -0
  85. lionagi/core/unit/template/predict.py +109 -0
  86. lionagi/core/unit/template/score.py +124 -0
  87. lionagi/core/unit/template/select.py +104 -0
  88. lionagi/core/unit/unit.py +362 -0
  89. lionagi/core/unit/unit_form.py +305 -0
  90. lionagi/core/unit/unit_mixin.py +1168 -0
  91. lionagi/core/unit/util.py +71 -0
  92. lionagi/core/validator/validator.py +364 -0
  93. lionagi/core/work/work.py +76 -0
  94. lionagi/core/work/work_function.py +101 -0
  95. lionagi/core/work/work_queue.py +103 -0
  96. lionagi/core/work/worker.py +258 -0
  97. lionagi/core/work/worklog.py +120 -0
  98. lionagi/experimental/compressor/base.py +46 -0
  99. lionagi/experimental/compressor/llm_compressor.py +247 -0
  100. lionagi/experimental/compressor/llm_summarizer.py +61 -0
  101. lionagi/experimental/compressor/util.py +70 -0
  102. lionagi/experimental/directive/__init__.py +19 -0
  103. lionagi/experimental/directive/parser/base_parser.py +69 -2
  104. lionagi/experimental/directive/{template_ → template}/base_template.py +17 -1
  105. lionagi/{libs/ln_tokenizer.py → experimental/directive/tokenizer.py} +16 -0
  106. lionagi/experimental/{directive/evaluator → evaluator}/ast_evaluator.py +16 -0
  107. lionagi/experimental/{directive/evaluator → evaluator}/base_evaluator.py +16 -0
  108. lionagi/experimental/knowledge/base.py +10 -0
  109. lionagi/experimental/memory/__init__.py +0 -0
  110. lionagi/experimental/strategies/__init__.py +0 -0
  111. lionagi/experimental/strategies/base.py +1 -0
  112. lionagi/integrations/bridge/langchain_/documents.py +4 -0
  113. lionagi/integrations/bridge/llamaindex_/index.py +30 -0
  114. lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
  115. lionagi/integrations/chunker/chunk.py +161 -24
  116. lionagi/integrations/config/oai_configs.py +34 -3
  117. lionagi/integrations/config/openrouter_configs.py +14 -2
  118. lionagi/integrations/loader/load.py +122 -21
  119. lionagi/integrations/loader/load_util.py +6 -77
  120. lionagi/integrations/provider/_mapping.py +46 -0
  121. lionagi/integrations/provider/litellm.py +2 -1
  122. lionagi/integrations/provider/mlx_service.py +16 -9
  123. lionagi/integrations/provider/oai.py +91 -4
  124. lionagi/integrations/provider/ollama.py +6 -5
  125. lionagi/integrations/provider/openrouter.py +115 -8
  126. lionagi/integrations/provider/services.py +2 -2
  127. lionagi/integrations/provider/transformers.py +18 -22
  128. lionagi/integrations/storage/__init__.py +3 -3
  129. lionagi/integrations/storage/neo4j.py +52 -60
  130. lionagi/integrations/storage/storage_util.py +44 -46
  131. lionagi/integrations/storage/structure_excel.py +43 -26
  132. lionagi/integrations/storage/to_excel.py +11 -4
  133. lionagi/libs/__init__.py +22 -1
  134. lionagi/libs/ln_api.py +75 -20
  135. lionagi/libs/ln_context.py +37 -0
  136. lionagi/libs/ln_convert.py +21 -9
  137. lionagi/libs/ln_func_call.py +69 -28
  138. lionagi/libs/ln_image.py +107 -0
  139. lionagi/libs/ln_nested.py +26 -11
  140. lionagi/libs/ln_parse.py +82 -23
  141. lionagi/libs/ln_queue.py +16 -0
  142. lionagi/libs/ln_tokenize.py +164 -0
  143. lionagi/libs/ln_validate.py +16 -0
  144. lionagi/libs/special_tokens.py +172 -0
  145. lionagi/libs/sys_util.py +95 -24
  146. lionagi/lions/coder/code_form.py +13 -0
  147. lionagi/lions/coder/coder.py +50 -3
  148. lionagi/lions/coder/util.py +30 -25
  149. lionagi/tests/libs/test_func_call.py +23 -21
  150. lionagi/tests/libs/test_nested.py +36 -21
  151. lionagi/tests/libs/test_parse.py +1 -1
  152. lionagi/tests/test_core/collections/__init__.py +0 -0
  153. lionagi/tests/test_core/collections/test_component.py +206 -0
  154. lionagi/tests/test_core/collections/test_exchange.py +138 -0
  155. lionagi/tests/test_core/collections/test_flow.py +145 -0
  156. lionagi/tests/test_core/collections/test_pile.py +171 -0
  157. lionagi/tests/test_core/collections/test_progression.py +129 -0
  158. lionagi/tests/test_core/generic/test_edge.py +67 -0
  159. lionagi/tests/test_core/generic/test_graph.py +96 -0
  160. lionagi/tests/test_core/generic/test_node.py +106 -0
  161. lionagi/tests/test_core/generic/test_tree_node.py +73 -0
  162. lionagi/tests/test_core/test_branch.py +115 -294
  163. lionagi/tests/test_core/test_form.py +46 -0
  164. lionagi/tests/test_core/test_report.py +105 -0
  165. lionagi/tests/test_core/test_validator.py +111 -0
  166. lionagi/version.py +1 -1
  167. lionagi-0.2.1.dist-info/LICENSE +202 -0
  168. lionagi-0.2.1.dist-info/METADATA +272 -0
  169. lionagi-0.2.1.dist-info/RECORD +240 -0
  170. lionagi/core/branch/base.py +0 -653
  171. lionagi/core/branch/branch.py +0 -474
  172. lionagi/core/branch/flow_mixin.py +0 -96
  173. lionagi/core/branch/util.py +0 -323
  174. lionagi/core/direct/__init__.py +0 -19
  175. lionagi/core/direct/cot.py +0 -123
  176. lionagi/core/direct/plan.py +0 -164
  177. lionagi/core/direct/predict.py +0 -166
  178. lionagi/core/direct/react.py +0 -171
  179. lionagi/core/direct/score.py +0 -279
  180. lionagi/core/direct/select.py +0 -170
  181. lionagi/core/direct/sentiment.py +0 -1
  182. lionagi/core/direct/utils.py +0 -110
  183. lionagi/core/direct/vote.py +0 -64
  184. lionagi/core/execute/base_executor.py +0 -47
  185. lionagi/core/flow/baseflow.py +0 -23
  186. lionagi/core/flow/monoflow/ReAct.py +0 -240
  187. lionagi/core/flow/monoflow/__init__.py +0 -9
  188. lionagi/core/flow/monoflow/chat.py +0 -95
  189. lionagi/core/flow/monoflow/chat_mixin.py +0 -253
  190. lionagi/core/flow/monoflow/followup.py +0 -215
  191. lionagi/core/flow/polyflow/__init__.py +0 -1
  192. lionagi/core/flow/polyflow/chat.py +0 -251
  193. lionagi/core/form/action_form.py +0 -26
  194. lionagi/core/form/field_validator.py +0 -287
  195. lionagi/core/form/form.py +0 -302
  196. lionagi/core/form/mixin.py +0 -214
  197. lionagi/core/form/scored_form.py +0 -13
  198. lionagi/core/generic/action.py +0 -26
  199. lionagi/core/generic/component.py +0 -532
  200. lionagi/core/generic/condition.py +0 -46
  201. lionagi/core/generic/mail.py +0 -90
  202. lionagi/core/generic/mailbox.py +0 -36
  203. lionagi/core/generic/relation.py +0 -70
  204. lionagi/core/generic/signal.py +0 -22
  205. lionagi/core/generic/structure.py +0 -362
  206. lionagi/core/generic/transfer.py +0 -20
  207. lionagi/core/generic/work.py +0 -40
  208. lionagi/core/graph/graph.py +0 -126
  209. lionagi/core/graph/tree.py +0 -190
  210. lionagi/core/mail/schema.py +0 -63
  211. lionagi/core/messages/schema.py +0 -325
  212. lionagi/core/tool/__init__.py +0 -5
  213. lionagi/core/tool/tool.py +0 -28
  214. lionagi/core/tool/tool_manager.py +0 -283
  215. lionagi/experimental/report/form.py +0 -64
  216. lionagi/experimental/report/report.py +0 -138
  217. lionagi/experimental/report/util.py +0 -47
  218. lionagi/experimental/tool/function_calling.py +0 -43
  219. lionagi/experimental/tool/manual.py +0 -66
  220. lionagi/experimental/tool/schema.py +0 -59
  221. lionagi/experimental/tool/tool_manager.py +0 -138
  222. lionagi/experimental/tool/util.py +0 -16
  223. lionagi/experimental/validator/rule.py +0 -139
  224. lionagi/experimental/validator/validator.py +0 -56
  225. lionagi/experimental/work/__init__.py +0 -10
  226. lionagi/experimental/work/async_queue.py +0 -54
  227. lionagi/experimental/work/schema.py +0 -73
  228. lionagi/experimental/work/work_function.py +0 -67
  229. lionagi/experimental/work/worker.py +0 -56
  230. lionagi/experimental/work2/form.py +0 -371
  231. lionagi/experimental/work2/report.py +0 -289
  232. lionagi/experimental/work2/schema.py +0 -30
  233. lionagi/experimental/work2/tests.py +0 -72
  234. lionagi/experimental/work2/work_function.py +0 -89
  235. lionagi/experimental/work2/worker.py +0 -12
  236. lionagi/integrations/bridge/llamaindex_/get_index.py +0 -294
  237. lionagi/tests/test_core/generic/test_component.py +0 -89
  238. lionagi/tests/test_core/test_base_branch.py +0 -426
  239. lionagi/tests/test_core/test_chat_flow.py +0 -63
  240. lionagi/tests/test_core/test_mail_manager.py +0 -75
  241. lionagi/tests/test_core/test_prompts.py +0 -51
  242. lionagi/tests/test_core/test_session.py +0 -254
  243. lionagi/tests/test_core/test_session_base_util.py +0 -313
  244. lionagi/tests/test_core/test_tool_manager.py +0 -95
  245. lionagi-0.1.2.dist-info/LICENSE +0 -9
  246. lionagi-0.1.2.dist-info/METADATA +0 -174
  247. lionagi-0.1.2.dist-info/RECORD +0 -206
  248. /lionagi/core/{branch → _setting}/__init__.py +0 -0
  249. /lionagi/core/{execute → agent/eval}/__init__.py +0 -0
  250. /lionagi/core/{flow → agent/learn}/__init__.py +0 -0
  251. /lionagi/core/{form → agent/plan}/__init__.py +0 -0
  252. /lionagi/core/{branch/executable_branch.py → agent/plan/plan.py} +0 -0
  253. /lionagi/core/{graph → director}/__init__.py +0 -0
  254. /lionagi/core/{messages → engine}/__init__.py +0 -0
  255. /lionagi/{experimental/directive/evaluator → core/engine}/sandbox_.py +0 -0
  256. /lionagi/{experimental/directive/evaluator → core/executor}/__init__.py +0 -0
  257. /lionagi/{experimental/directive/template_ → core/rule}/__init__.py +0 -0
  258. /lionagi/{experimental/report → core/unit/template}/__init__.py +0 -0
  259. /lionagi/{experimental/tool → core/validator}/__init__.py +0 -0
  260. /lionagi/{experimental/validator → core/work}/__init__.py +0 -0
  261. /lionagi/experimental/{work2 → compressor}/__init__.py +0 -0
  262. /lionagi/{core/flow/mono_chat_mixin.py → experimental/directive/template/__init__.py} +0 -0
  263. /lionagi/experimental/directive/{schema.py → template/schema.py} +0 -0
  264. /lionagi/experimental/{work2/util.py → evaluator/__init__.py} +0 -0
  265. /lionagi/experimental/{work2/work.py → knowledge/__init__.py} +0 -0
  266. /lionagi/{tests/libs/test_async.py → experimental/knowledge/graph.py} +0 -0
  267. {lionagi-0.1.2.dist-info → lionagi-0.2.1.dist-info}/WHEEL +0 -0
  268. {lionagi-0.1.2.dist-info → lionagi-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,615 @@
1
+ """
2
+ Copyright 2024 HaiyangLi
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ """Component class, base building block in LionAGI."""
18
+
19
+ from abc import ABC
20
+ import contextlib
21
+ from collections.abc import Sequence
22
+ from functools import singledispatchmethod
23
+ from typing import Any, TypeVar, Type, TypeAlias, Union
24
+
25
+ from pandas import DataFrame, Series
26
+ from pydantic import BaseModel, Field, ValidationError, AliasChoices
27
+
28
+ from lionagi.libs import ParseUtil, SysUtil
29
+ from lionagi.libs.ln_convert import strip_lower, to_dict, to_str
30
+ from lionagi.libs.ln_func_call import lcall
31
+ from lionagi.libs.ln_nested import nget, nset, ninsert, flatten, unflatten
32
+
33
+ from .exceptions import FieldError, LionTypeError, LionValueError
34
+ from .util import base_lion_fields, llama_meta_fields, lc_meta_fields
35
+
36
+ T = TypeVar("T")
37
+
38
+ _init_class = {}
39
+
40
+
41
+ class Element(BaseModel, ABC):
42
+ """Base class for elements within the LionAGI system.
43
+
44
+ Attributes:
45
+ ln_id (str): A 32-char unique hash identifier.
46
+ timestamp (str): The UTC timestamp of creation.
47
+ """
48
+
49
+ ln_id: str = Field(
50
+ default_factory=SysUtil.create_id,
51
+ title="ID",
52
+ description="A 32-char unique hash identifier.",
53
+ frozen=True,
54
+ validation_alias=AliasChoices("node_id", "ID", "id"),
55
+ )
56
+
57
+ timestamp: str = Field(
58
+ default_factory=lambda: SysUtil.get_timestamp(sep=None)[:-6],
59
+ title="Creation Timestamp",
60
+ description="The UTC timestamp of creation",
61
+ frozen=True,
62
+ alias="created",
63
+ validation_alias=AliasChoices("created_on", "creation_date"),
64
+ )
65
+
66
+ def __init_subclass__(cls, **kwargs):
67
+ super().__init_subclass__(**kwargs)
68
+ if cls.__name__ not in _init_class:
69
+ _init_class[cls.__name__] = cls
70
+
71
+ # element is always true
72
+ def __bool__(self):
73
+ return True
74
+
75
+
76
+ class Component(Element, ABC):
77
+ """
78
+ Represents a distinguishable, temporal entity in LionAGI.
79
+
80
+ Encapsulates essential attributes and behaviors needed for individual
81
+ components within the system's architecture. Each component is uniquely
82
+ identifiable, with built-in version control and metadata handling.
83
+
84
+ Attributes:
85
+ ln_id (str): A unique identifier for the component.
86
+ timestamp (str): The UTC timestamp when the component was created.
87
+ metadata (dict): Additional metadata for the component.
88
+ extra_fields (dict): Additional fields for the component.
89
+ content (Any): Optional content of the component.
90
+ """
91
+
92
+ metadata: dict[str, Any] = Field(
93
+ default_factory=dict,
94
+ validation_alias=AliasChoices("meta", "info"),
95
+ description="Additional metadata for the component.",
96
+ )
97
+
98
+ extra_fields: dict[str, Any] = Field(
99
+ default_factory=dict,
100
+ description="Additional fields for the component.",
101
+ validation_alias=AliasChoices(
102
+ "extra", "additional_fields", "schema_extra", "extra_schema"
103
+ ),
104
+ )
105
+
106
+ content: Any = Field(
107
+ default=None,
108
+ description="The optional content of the node.",
109
+ validation_alias=AliasChoices("text", "page_content", "chunk_content", "data"),
110
+ )
111
+
112
+ embedding: list[float] = Field(
113
+ default=[],
114
+ description="The optional embedding of the node.",
115
+ )
116
+
117
+ @staticmethod
118
+ def _validate_embedding(value: Any) -> list:
119
+ if not value:
120
+ return []
121
+ if isinstance(value, str):
122
+ if len(value) < 10:
123
+ return []
124
+
125
+ string_elements = value.strip("[]").split(",")
126
+ # Convert each string element to a float
127
+ with contextlib.suppress(ValueError):
128
+ return [float(element) for element in string_elements]
129
+ raise ValueError("Invalid embedding format.")
130
+
131
+ class Config:
132
+ """Model configuration settings."""
133
+
134
+ extra = "allow"
135
+ arbitrary_types_allowed = True
136
+ populate_by_name = True
137
+ use_enum_values = True
138
+
139
+ @singledispatchmethod
140
+ @classmethod
141
+ def from_obj(cls, obj: Any, /, **kwargs) -> T:
142
+ """
143
+ Create Component instance(s) from various input types.
144
+
145
+ This method dynamically handles different types of input data,
146
+ allowing the creation of Component instances from dictionaries,
147
+ strings (JSON), lists, pandas Series, pandas DataFrames, and
148
+ instances of other classes, including Pydantic models. Additionally,
149
+ it includes support for custom types such as LlamaIndex and
150
+ Langchain specific data.
151
+
152
+ The type of the input data determines how it is processed:
153
+ - `dict`: Treated as field-value pairs for the Component.
154
+ - `str`: Expected to be JSON format; parsed into a dictionary first.
155
+ - `list`: Each item is processed independently, and a list of
156
+ Components is returned.
157
+ - `pandas.Series`: Converted to a dictionary; treated as field-value
158
+ pairs.
159
+ - `pandas.DataFrame`: Each row is treated as a separate Component;
160
+ returns a list of Components.
161
+ - `Pydantic BaseModel`: Extracts data directly from the Pydantic
162
+ model.
163
+ - `LlamaIndex model`: Converts using LlamaIndex-specific logic to
164
+ extract data suitable for Component creation.
165
+ - `Langchain model`: Processes Langchain-specific structures to
166
+ produce Component data.
167
+
168
+ Args:
169
+ obj: The input object to create Component instance(s) from.
170
+ **kwargs: Additional keyword arguments to pass to the creation
171
+ method.
172
+
173
+ Returns:
174
+ T: The created Component instance(s).
175
+
176
+ Raises:
177
+ LionTypeError: If the input type is not supported.
178
+ """
179
+ if isinstance(obj, (dict, str, list, Series, DataFrame, BaseModel)):
180
+ return cls._dispatch_from_obj(obj, **kwargs)
181
+
182
+ type_ = str(type(obj))
183
+
184
+ if "llama_index" in type_:
185
+ return cls._from_llama_index(obj)
186
+ elif "langchain" in type_:
187
+ return cls._from_langchain(obj)
188
+
189
+ raise LionTypeError(f"Unsupported type: {type(obj)}")
190
+
191
+ @classmethod
192
+ def _dispatch_from_obj(cls, obj: Any, **kwargs) -> T:
193
+ """Dispatch the from_obj method based on the input type."""
194
+ if isinstance(obj, dict):
195
+ return cls._from_dict(obj, **kwargs)
196
+ elif isinstance(obj, str):
197
+ return cls._from_str(obj, **kwargs)
198
+ elif isinstance(obj, list):
199
+ return cls._from_list(obj, **kwargs)
200
+ elif isinstance(obj, Series):
201
+ return cls._from_pd_series(obj, **kwargs)
202
+ elif isinstance(obj, DataFrame):
203
+ return cls._from_pd_dataframe(obj, **kwargs)
204
+ elif isinstance(obj, BaseModel):
205
+ return cls._from_base_model(obj, **kwargs)
206
+
207
+ @classmethod
208
+ def _from_llama_index(cls, obj: Any) -> T:
209
+ """Create a Component instance from a LlamaIndex object."""
210
+ dict_ = obj.to_dict()
211
+
212
+ SysUtil.change_dict_key(dict_, "text", "content")
213
+ metadata = dict_.pop("metadata", {})
214
+
215
+ for field in llama_meta_fields:
216
+ metadata[field] = dict_.pop(field, None)
217
+
218
+ SysUtil.change_dict_key(metadata, "class_name", "llama_index_class")
219
+ SysUtil.change_dict_key(metadata, "id_", "llama_index_id")
220
+ SysUtil.change_dict_key(metadata, "relationships", "llama_index_relationships")
221
+
222
+ dict_["metadata"] = metadata
223
+ return cls.from_obj(dict_)
224
+
225
+ @classmethod
226
+ def _from_langchain(cls, obj: Any) -> T:
227
+ """Create a Component instance from a Langchain object."""
228
+ dict_ = obj.to_json()
229
+ return cls.from_obj(dict_)
230
+
231
+ @classmethod
232
+ def _from_dict(cls, obj: dict, /, *args, **kwargs) -> T:
233
+ """Create a Component instance from a dictionary."""
234
+ try:
235
+ dict_ = {**obj, **kwargs}
236
+ if "embedding" in dict_:
237
+ dict_["embedding"] = cls._validate_embedding(dict_["embedding"])
238
+
239
+ if "lion_class" in dict_:
240
+ cls = _init_class.get(dict_.pop("lion_class"), cls)
241
+
242
+ if "lc" in dict_:
243
+ dict_ = cls._process_langchain_dict(dict_)
244
+ else:
245
+ dict_ = cls._process_generic_dict(dict_)
246
+
247
+ return cls.model_validate(dict_, *args, **kwargs)
248
+
249
+ except ValidationError as e:
250
+ raise LionValueError("Invalid dictionary for deserialization.") from e
251
+
252
+ @classmethod
253
+ def _process_langchain_dict(cls, dict_: dict) -> dict:
254
+ """Process a dictionary containing Langchain-specific data."""
255
+ SysUtil.change_dict_key(dict_, "page_content", "content")
256
+
257
+ metadata = dict_.pop("metadata", {})
258
+ metadata.update(dict_.pop("kwargs", {}))
259
+
260
+ if not isinstance(metadata, dict):
261
+ metadata = {"extra_meta": metadata}
262
+
263
+ for field in base_lion_fields:
264
+ if field in metadata:
265
+ dict_[field] = metadata.pop(field)
266
+
267
+ for key in list(metadata.keys()):
268
+ if key not in lc_meta_fields:
269
+ dict_[key] = metadata.pop(key)
270
+
271
+ for field in lc_meta_fields:
272
+ if field in dict_:
273
+ metadata[field] = dict_.pop(field)
274
+
275
+ SysUtil.change_dict_key(metadata, "lc", "langchain")
276
+ SysUtil.change_dict_key(metadata, "type", "lc_type")
277
+ SysUtil.change_dict_key(metadata, "id", "lc_id")
278
+
279
+ extra_fields = {k: v for k, v in metadata.items() if k not in lc_meta_fields}
280
+ metadata = {k: v for k, v in metadata.items() if k in lc_meta_fields}
281
+ dict_["metadata"] = metadata
282
+ dict_.update(extra_fields)
283
+
284
+ return dict_
285
+
286
+ @classmethod
287
+ def _process_generic_dict(cls, dict_: dict) -> dict:
288
+ """Process a generic dictionary."""
289
+ meta_ = dict_.pop("metadata", None) or {}
290
+
291
+ if not isinstance(meta_, dict):
292
+ meta_ = {"extra_meta": meta_}
293
+
294
+ for key in list(dict_.keys()):
295
+ if key not in base_lion_fields:
296
+ meta_[key] = dict_.pop(key)
297
+
298
+ if not dict_.get("content", None):
299
+ for field in ["page_content", "text", "chunk_content", "data"]:
300
+ if field in meta_:
301
+ dict_["content"] = meta_.pop(field)
302
+ break
303
+
304
+ dict_["metadata"] = meta_
305
+
306
+ if "ln_id" not in dict_:
307
+ dict_["ln_id"] = meta_.pop("ln_id", SysUtil.create_id())
308
+ if "timestamp" not in dict_:
309
+ dict_["timestamp"] = SysUtil.get_timestamp(sep=None)[:-6]
310
+ if "metadata" not in dict_:
311
+ dict_["metadata"] = {}
312
+ if "extra_fields" not in dict_:
313
+ dict_["extra_fields"] = {}
314
+
315
+ return dict_
316
+
317
+ @classmethod
318
+ def _from_str(cls, obj: str, /, *args, fuzzy_parse: bool = False, **kwargs) -> T:
319
+ """Create a Component instance from a JSON string."""
320
+ obj = ParseUtil.fuzzy_parse_json(obj) if fuzzy_parse else to_dict(obj)
321
+ try:
322
+ return cls.from_obj(obj, *args, **kwargs)
323
+ except ValidationError as e:
324
+ raise LionValueError("Invalid JSON for deserialization: ") from e
325
+
326
+ @classmethod
327
+ def _from_list(cls, obj: list, /, *args, **kwargs) -> list[T]:
328
+ """Create a list of node instances from a list of objects."""
329
+ return [cls.from_obj(item, *args, **kwargs) for item in obj]
330
+
331
+ @classmethod
332
+ def _from_pd_series(
333
+ cls, obj: Series, /, *args, pd_kwargs: dict | None = None, **kwargs
334
+ ) -> T:
335
+ """Create a node instance from a Pandas Series."""
336
+ pd_kwargs = pd_kwargs or {}
337
+ return cls.from_obj(obj.to_dict(**pd_kwargs), *args, **kwargs)
338
+
339
+ @classmethod
340
+ def _from_pd_dataframe(
341
+ cls,
342
+ obj: DataFrame,
343
+ /,
344
+ *args,
345
+ pd_kwargs: dict | None = None,
346
+ include_index=False,
347
+ **kwargs,
348
+ ) -> list[T]:
349
+ """Create a list of node instances from a Pandas DataFrame."""
350
+ pd_kwargs = pd_kwargs or {}
351
+
352
+ _objs = []
353
+ for index, row in obj.iterrows():
354
+ _obj = cls.from_obj(row, *args, **pd_kwargs, **kwargs)
355
+ if include_index:
356
+ _obj.metadata["df_index"] = index
357
+ _objs.append(_obj)
358
+
359
+ return _objs
360
+
361
+ @classmethod
362
+ def _from_base_model(cls, obj, /, pydantic_kwargs=None, **kwargs) -> T:
363
+ """Create a node instance from a Pydantic BaseModel."""
364
+ pydantic_kwargs = pydantic_kwargs or {"by_alias": True}
365
+ try:
366
+ config_ = obj.model_dump(**pydantic_kwargs)
367
+ except:
368
+ try:
369
+ if hasattr(obj, "to_dict"):
370
+ config_ = obj.to_dict(**pydantic_kwargs)
371
+ elif hasattr(obj, "dict"):
372
+ config_ = obj.dict(**pydantic_kwargs)
373
+ else:
374
+ raise LionValueError(
375
+ "Invalid Pydantic model for deserialization: "
376
+ "missing 'to_dict'(V2) or 'dict'(V1) method."
377
+ )
378
+ except Exception as e:
379
+ raise LionValueError(
380
+ f"Invalid Pydantic model for deserialization: {e}"
381
+ ) from e
382
+ return cls.from_obj(config_ | kwargs)
383
+
384
+ @property
385
+ def class_name(self) -> str:
386
+ """Get the class name."""
387
+ return self._class_name()
388
+
389
+ @classmethod
390
+ def _class_name(cls) -> str:
391
+ """Get the class name."""
392
+ return cls.__name__
393
+
394
+ def to_json_str(self, *args, dropna=False, **kwargs) -> str:
395
+ """Convert the component to a JSON string."""
396
+ dict_ = self.to_dict(*args, dropna=dropna, **kwargs)
397
+ return to_str(dict_)
398
+
399
+ def to_dict(self, *args, dropna=False, **kwargs) -> dict[str, Any]:
400
+ """Convert the component to a dictionary."""
401
+ dict_ = self.model_dump(*args, by_alias=True, **kwargs)
402
+
403
+ for field_name in list(self.extra_fields.keys()):
404
+ if field_name not in dict_:
405
+ dict_[field_name] = getattr(self, field_name, None)
406
+
407
+ dict_.pop("extra_fields", None)
408
+ dict_["lion_class"] = self.class_name
409
+ if dropna:
410
+ dict_ = {k: v for k, v in dict_.items() if v is not None}
411
+ return dict_
412
+
413
+ def to_xml(self, *args, dropna=False, **kwargs) -> str:
414
+ """Convert the component to an XML string."""
415
+ import xml.etree.ElementTree as ET
416
+
417
+ root = ET.Element(self.__class__.__name__)
418
+
419
+ def convert(dict_obj: dict, parent: ET.Element) -> None:
420
+ for key, val in dict_obj.items():
421
+ if isinstance(val, dict):
422
+ element = ET.SubElement(parent, key)
423
+ convert(val, element)
424
+ else:
425
+ element = ET.SubElement(parent, key)
426
+ element.text = str(val)
427
+
428
+ convert(self.to_dict(*args, dropna=dropna, **kwargs), root)
429
+ return ET.tostring(root, encoding="unicode")
430
+
431
+ def to_pd_series(self, *args, pd_kwargs=None, dropna=False, **kwargs) -> Series:
432
+ """Convert the node to a Pandas Series."""
433
+ pd_kwargs = pd_kwargs or {}
434
+ dict_ = self.to_dict(*args, dropna=dropna, **kwargs)
435
+ return Series(dict_, **pd_kwargs)
436
+
437
+ def to_llama_index_node(self, node_type: Type | str | Any = None, **kwargs) -> Any:
438
+ """Serializes this node for LlamaIndex."""
439
+ from lionagi.integrations.bridge import LlamaIndexBridge
440
+
441
+ return LlamaIndexBridge.to_llama_index_node(self, node_type=node_type, **kwargs)
442
+
443
+ def to_langchain_doc(self, **kwargs) -> Any:
444
+ """Serializes this node for Langchain."""
445
+ from lionagi.integrations.bridge import LangchainBridge
446
+
447
+ return LangchainBridge.to_langchain_document(self, **kwargs)
448
+
449
+ def _add_last_update(self, name):
450
+ if (a := nget(self.metadata, ["last_updated", name], None)) is None:
451
+ ninsert(
452
+ self.metadata,
453
+ ["last_updated", name],
454
+ SysUtil.get_timestamp(sep=None)[:-6],
455
+ )
456
+ elif isinstance(a, tuple) and isinstance(a[0], int):
457
+ nset(
458
+ self.metadata,
459
+ ["last_updated", name],
460
+ SysUtil.get_timestamp(sep=None)[:-6],
461
+ )
462
+
463
+ def _meta_pop(self, indices, default=...):
464
+ indices = (
465
+ indices
466
+ if not isinstance(indices, list)
467
+ else "[^_^]".join([str(i) for i in indices])
468
+ )
469
+ dict_ = self.metadata.copy()
470
+ dict_ = flatten(dict_)
471
+
472
+ try:
473
+ out_ = dict_.pop(indices, default) if default != ... else dict_.pop(indices)
474
+ except KeyError as e:
475
+ if default == ...:
476
+ raise KeyError(f"Key {indices} not found in metadata.") from e
477
+ return default
478
+
479
+ a = unflatten(dict_)
480
+ self.metadata.clear()
481
+ self.metadata.update(a)
482
+ return out_
483
+
484
+ def _meta_insert(self, indices, value):
485
+ ninsert(self.metadata, indices, value)
486
+
487
+ def _meta_set(self, indices, value):
488
+ if not self._meta_get(indices):
489
+ self._meta_insert(indices, value)
490
+ nset(self.metadata, indices, value)
491
+
492
+ def _meta_get(self, indices, default=...):
493
+ if default != ...:
494
+ return nget(self.metadata, indices=indices, default=default)
495
+ return nget(self.metadata, indices)
496
+
497
+ def __setattr__(self, name, value):
498
+ if name == "metadata":
499
+ raise AttributeError("Cannot directly assign to metadata.")
500
+ super().__setattr__(name, value)
501
+ self._add_last_update(name)
502
+
503
+ def _add_field(
504
+ self,
505
+ field: str,
506
+ annotation: Any = None,
507
+ default: Any = None,
508
+ value: Any = None,
509
+ field_obj: Any = None,
510
+ **kwargs,
511
+ ) -> None:
512
+ """Add a field to the model after initialization."""
513
+ self.extra_fields[field] = field_obj or Field(default=default, **kwargs)
514
+ if annotation:
515
+ self.extra_fields[field].annotation = annotation
516
+
517
+ if not value and (a := self._get_field_attr(field, "default", None)):
518
+ value = a
519
+
520
+ self.__setattr__(field, value)
521
+
522
+ def add_field(self, field, value, annotation=None, **kwargs):
523
+ self._add_field(field, annotation, value=value, **kwargs)
524
+
525
+ @property
526
+ def _all_fields(self):
527
+ return {**self.model_fields, **self.extra_fields}
528
+
529
+ @property
530
+ def _field_annotations(self) -> dict:
531
+ """Return the annotations for each field in the model."""
532
+ return self._get_field_annotation(list(self._all_fields.keys()))
533
+
534
+ def _get_field_attr(self, k: str, attr: str, default: Any = False) -> Any:
535
+ """Get the value of a field attribute."""
536
+ try:
537
+ if not self._field_has_attr(k, attr):
538
+ raise FieldError(f"field {k} has no attribute {attr}")
539
+
540
+ field = self._all_fields[k]
541
+ if not (a := getattr(field, attr, None)):
542
+ try:
543
+ return field.json_schema_extra[attr]
544
+ except Exception:
545
+ return None
546
+ return a
547
+ except Exception as e:
548
+ if default is not False:
549
+ return default
550
+ raise e
551
+
552
+ @singledispatchmethod
553
+ def _get_field_annotation(self, field_name: Any) -> Any:
554
+ raise LionTypeError
555
+
556
+ @_get_field_annotation.register(str)
557
+ def _(self, field_name: str) -> dict[str, Any]:
558
+ dict_ = {field_name: self._all_fields[field_name].annotation}
559
+ for k, v in dict_.items():
560
+ if "|" in str(v):
561
+ v = str(v)
562
+ v = v.split("|")
563
+ dict_[k] = lcall(v, strip_lower)
564
+ else:
565
+ dict_[k] = [v.__name__] if v else None
566
+ return dict_
567
+
568
+ @_get_field_annotation.register(list)
569
+ @_get_field_annotation.register(tuple)
570
+ def _(self, field_names: list | tuple) -> dict[str, Any]:
571
+ dict_ = {}
572
+ for field_name in field_names:
573
+ dict_.update(self._get_field_annotation(field_name))
574
+ return dict_
575
+
576
+ def _field_has_attr(self, k: str, attr: str) -> bool:
577
+ """Check if a field has a specific attribute."""
578
+ if not (field := self._all_fields.get(k, None)):
579
+ raise KeyError(f"Field {k} not found in model fields.")
580
+
581
+ if attr not in str(field):
582
+ try:
583
+ a = (
584
+ attr in self._all_fields[k].json_schema_extra
585
+ and self._all_fields[k].json_schema_extra[attr] is not None
586
+ )
587
+ return a if isinstance(a, bool) else False
588
+ except Exception:
589
+ return False
590
+ return True
591
+
592
+ def __str__(self):
593
+ dict_ = self.to_dict()
594
+ return Series(dict_).__str__()
595
+
596
+ def __repr__(self):
597
+ dict_ = self.to_dict()
598
+ return Series(dict_).__repr__()
599
+
600
+ def __len__(self):
601
+ return 1
602
+
603
+
604
+ LionIDable: TypeAlias = Union[str, Element]
605
+
606
+
607
+ def get_lion_id(item: LionIDable) -> str:
608
+ """Get the Lion ID of an item."""
609
+ if isinstance(item, Sequence) and len(item) == 1:
610
+ item = item[0]
611
+ if isinstance(item, str) and len(item) == 32:
612
+ return item
613
+ if getattr(item, "ln_id", None) is not None:
614
+ return item.ln_id
615
+ raise LionTypeError("Item must be a single LionIDable object.")