lionagi 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lionagi/__init__.py +60 -5
- lionagi/core/__init__.py +0 -25
- lionagi/core/_setting/_setting.py +59 -0
- lionagi/core/action/__init__.py +14 -0
- lionagi/core/action/function_calling.py +136 -0
- lionagi/core/action/manual.py +1 -0
- lionagi/core/action/node.py +109 -0
- lionagi/core/action/tool.py +114 -0
- lionagi/core/action/tool_manager.py +356 -0
- lionagi/core/agent/base_agent.py +27 -13
- lionagi/core/agent/eval/evaluator.py +1 -0
- lionagi/core/agent/eval/vote.py +40 -0
- lionagi/core/agent/learn/learner.py +59 -0
- lionagi/core/agent/plan/unit_template.py +1 -0
- lionagi/core/collections/__init__.py +17 -0
- lionagi/core/{generic/data_logger.py → collections/_logger.py} +69 -55
- lionagi/core/collections/abc/__init__.py +53 -0
- lionagi/core/collections/abc/component.py +615 -0
- lionagi/core/collections/abc/concepts.py +297 -0
- lionagi/core/collections/abc/exceptions.py +150 -0
- lionagi/core/collections/abc/util.py +45 -0
- lionagi/core/collections/exchange.py +161 -0
- lionagi/core/collections/flow.py +426 -0
- lionagi/core/collections/model.py +419 -0
- lionagi/core/collections/pile.py +913 -0
- lionagi/core/collections/progression.py +236 -0
- lionagi/core/collections/util.py +64 -0
- lionagi/core/director/direct.py +314 -0
- lionagi/core/director/director.py +2 -0
- lionagi/core/{execute/branch_executor.py → engine/branch_engine.py} +134 -97
- lionagi/core/{execute/instruction_map_executor.py → engine/instruction_map_engine.py} +80 -55
- lionagi/{experimental/directive/evaluator → core/engine}/script_engine.py +17 -1
- lionagi/core/executor/base_executor.py +90 -0
- lionagi/core/{execute/structure_executor.py → executor/graph_executor.py} +62 -66
- lionagi/core/{execute → executor}/neo4j_executor.py +70 -67
- lionagi/core/generic/__init__.py +3 -33
- lionagi/core/generic/edge.py +29 -79
- lionagi/core/generic/edge_condition.py +16 -0
- lionagi/core/generic/graph.py +236 -0
- lionagi/core/generic/hyperedge.py +1 -0
- lionagi/core/generic/node.py +156 -221
- lionagi/core/generic/tree.py +48 -0
- lionagi/core/generic/tree_node.py +79 -0
- lionagi/core/mail/__init__.py +12 -0
- lionagi/core/mail/mail.py +25 -0
- lionagi/core/mail/mail_manager.py +139 -58
- lionagi/core/mail/package.py +45 -0
- lionagi/core/mail/start_mail.py +36 -0
- lionagi/core/message/__init__.py +19 -0
- lionagi/core/message/action_request.py +133 -0
- lionagi/core/message/action_response.py +135 -0
- lionagi/core/message/assistant_response.py +95 -0
- lionagi/core/message/instruction.py +234 -0
- lionagi/core/message/message.py +101 -0
- lionagi/core/message/system.py +86 -0
- lionagi/core/message/util.py +283 -0
- lionagi/core/report/__init__.py +4 -0
- lionagi/core/report/base.py +217 -0
- lionagi/core/report/form.py +231 -0
- lionagi/core/report/report.py +166 -0
- lionagi/core/report/util.py +28 -0
- lionagi/core/rule/_default.py +16 -0
- lionagi/core/rule/action.py +99 -0
- lionagi/core/rule/base.py +238 -0
- lionagi/core/rule/boolean.py +56 -0
- lionagi/core/rule/choice.py +47 -0
- lionagi/core/rule/mapping.py +96 -0
- lionagi/core/rule/number.py +71 -0
- lionagi/core/rule/rulebook.py +109 -0
- lionagi/core/rule/string.py +52 -0
- lionagi/core/rule/util.py +35 -0
- lionagi/core/session/branch.py +431 -0
- lionagi/core/session/directive_mixin.py +287 -0
- lionagi/core/session/session.py +229 -903
- lionagi/core/structure/__init__.py +1 -0
- lionagi/core/structure/chain.py +1 -0
- lionagi/core/structure/forest.py +1 -0
- lionagi/core/structure/graph.py +1 -0
- lionagi/core/structure/tree.py +1 -0
- lionagi/core/unit/__init__.py +5 -0
- lionagi/core/unit/parallel_unit.py +245 -0
- lionagi/core/unit/template/action.py +81 -0
- lionagi/core/unit/template/base.py +51 -0
- lionagi/core/unit/template/plan.py +84 -0
- lionagi/core/unit/template/predict.py +109 -0
- lionagi/core/unit/template/score.py +124 -0
- lionagi/core/unit/template/select.py +104 -0
- lionagi/core/unit/unit.py +362 -0
- lionagi/core/unit/unit_form.py +305 -0
- lionagi/core/unit/unit_mixin.py +1168 -0
- lionagi/core/unit/util.py +71 -0
- lionagi/core/validator/validator.py +364 -0
- lionagi/core/work/work.py +74 -0
- lionagi/core/work/work_function.py +92 -0
- lionagi/core/work/work_queue.py +81 -0
- lionagi/core/work/worker.py +195 -0
- lionagi/core/work/worklog.py +124 -0
- lionagi/experimental/compressor/base.py +46 -0
- lionagi/experimental/compressor/llm_compressor.py +247 -0
- lionagi/experimental/compressor/llm_summarizer.py +61 -0
- lionagi/experimental/compressor/util.py +70 -0
- lionagi/experimental/directive/__init__.py +19 -0
- lionagi/experimental/directive/parser/base_parser.py +69 -2
- lionagi/experimental/directive/{template_ → template}/base_template.py +17 -1
- lionagi/{libs/ln_tokenizer.py → experimental/directive/tokenizer.py} +16 -0
- lionagi/experimental/{directive/evaluator → evaluator}/ast_evaluator.py +16 -0
- lionagi/experimental/{directive/evaluator → evaluator}/base_evaluator.py +16 -0
- lionagi/experimental/knowledge/base.py +10 -0
- lionagi/experimental/memory/__init__.py +0 -0
- lionagi/experimental/strategies/__init__.py +0 -0
- lionagi/experimental/strategies/base.py +1 -0
- lionagi/integrations/bridge/langchain_/documents.py +4 -0
- lionagi/integrations/bridge/llamaindex_/index.py +30 -0
- lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
- lionagi/integrations/chunker/chunk.py +161 -24
- lionagi/integrations/config/oai_configs.py +34 -3
- lionagi/integrations/config/openrouter_configs.py +14 -2
- lionagi/integrations/loader/load.py +122 -21
- lionagi/integrations/loader/load_util.py +6 -77
- lionagi/integrations/provider/_mapping.py +46 -0
- lionagi/integrations/provider/litellm.py +2 -1
- lionagi/integrations/provider/mlx_service.py +16 -9
- lionagi/integrations/provider/oai.py +91 -4
- lionagi/integrations/provider/ollama.py +6 -5
- lionagi/integrations/provider/openrouter.py +115 -8
- lionagi/integrations/provider/services.py +2 -2
- lionagi/integrations/provider/transformers.py +18 -22
- lionagi/integrations/storage/__init__.py +3 -3
- lionagi/integrations/storage/neo4j.py +52 -60
- lionagi/integrations/storage/storage_util.py +44 -46
- lionagi/integrations/storage/structure_excel.py +43 -26
- lionagi/integrations/storage/to_excel.py +11 -4
- lionagi/libs/__init__.py +22 -1
- lionagi/libs/ln_api.py +75 -20
- lionagi/libs/ln_context.py +37 -0
- lionagi/libs/ln_convert.py +21 -9
- lionagi/libs/ln_func_call.py +69 -28
- lionagi/libs/ln_image.py +107 -0
- lionagi/libs/ln_nested.py +26 -11
- lionagi/libs/ln_parse.py +82 -23
- lionagi/libs/ln_queue.py +16 -0
- lionagi/libs/ln_tokenize.py +164 -0
- lionagi/libs/ln_validate.py +16 -0
- lionagi/libs/special_tokens.py +172 -0
- lionagi/libs/sys_util.py +95 -24
- lionagi/lions/coder/code_form.py +13 -0
- lionagi/lions/coder/coder.py +50 -3
- lionagi/lions/coder/util.py +30 -25
- lionagi/tests/libs/test_func_call.py +23 -21
- lionagi/tests/libs/test_nested.py +36 -21
- lionagi/tests/libs/test_parse.py +1 -1
- lionagi/tests/test_core/collections/__init__.py +0 -0
- lionagi/tests/test_core/collections/test_component.py +206 -0
- lionagi/tests/test_core/collections/test_exchange.py +138 -0
- lionagi/tests/test_core/collections/test_flow.py +145 -0
- lionagi/tests/test_core/collections/test_pile.py +171 -0
- lionagi/tests/test_core/collections/test_progression.py +129 -0
- lionagi/tests/test_core/generic/test_edge.py +67 -0
- lionagi/tests/test_core/generic/test_graph.py +96 -0
- lionagi/tests/test_core/generic/test_node.py +106 -0
- lionagi/tests/test_core/generic/test_tree_node.py +73 -0
- lionagi/tests/test_core/test_branch.py +115 -294
- lionagi/tests/test_core/test_form.py +46 -0
- lionagi/tests/test_core/test_report.py +105 -0
- lionagi/tests/test_core/test_validator.py +111 -0
- lionagi/version.py +1 -1
- lionagi-0.2.0.dist-info/LICENSE +202 -0
- lionagi-0.2.0.dist-info/METADATA +272 -0
- lionagi-0.2.0.dist-info/RECORD +240 -0
- lionagi/core/branch/base.py +0 -653
- lionagi/core/branch/branch.py +0 -474
- lionagi/core/branch/flow_mixin.py +0 -96
- lionagi/core/branch/util.py +0 -323
- lionagi/core/direct/__init__.py +0 -19
- lionagi/core/direct/cot.py +0 -123
- lionagi/core/direct/plan.py +0 -164
- lionagi/core/direct/predict.py +0 -166
- lionagi/core/direct/react.py +0 -171
- lionagi/core/direct/score.py +0 -279
- lionagi/core/direct/select.py +0 -170
- lionagi/core/direct/sentiment.py +0 -1
- lionagi/core/direct/utils.py +0 -110
- lionagi/core/direct/vote.py +0 -64
- lionagi/core/execute/base_executor.py +0 -47
- lionagi/core/flow/baseflow.py +0 -23
- lionagi/core/flow/monoflow/ReAct.py +0 -240
- lionagi/core/flow/monoflow/__init__.py +0 -9
- lionagi/core/flow/monoflow/chat.py +0 -95
- lionagi/core/flow/monoflow/chat_mixin.py +0 -253
- lionagi/core/flow/monoflow/followup.py +0 -215
- lionagi/core/flow/polyflow/__init__.py +0 -1
- lionagi/core/flow/polyflow/chat.py +0 -251
- lionagi/core/form/action_form.py +0 -26
- lionagi/core/form/field_validator.py +0 -287
- lionagi/core/form/form.py +0 -302
- lionagi/core/form/mixin.py +0 -214
- lionagi/core/form/scored_form.py +0 -13
- lionagi/core/generic/action.py +0 -26
- lionagi/core/generic/component.py +0 -532
- lionagi/core/generic/condition.py +0 -46
- lionagi/core/generic/mail.py +0 -90
- lionagi/core/generic/mailbox.py +0 -36
- lionagi/core/generic/relation.py +0 -70
- lionagi/core/generic/signal.py +0 -22
- lionagi/core/generic/structure.py +0 -362
- lionagi/core/generic/transfer.py +0 -20
- lionagi/core/generic/work.py +0 -40
- lionagi/core/graph/graph.py +0 -126
- lionagi/core/graph/tree.py +0 -190
- lionagi/core/mail/schema.py +0 -63
- lionagi/core/messages/schema.py +0 -325
- lionagi/core/tool/__init__.py +0 -5
- lionagi/core/tool/tool.py +0 -28
- lionagi/core/tool/tool_manager.py +0 -283
- lionagi/experimental/report/form.py +0 -64
- lionagi/experimental/report/report.py +0 -138
- lionagi/experimental/report/util.py +0 -47
- lionagi/experimental/tool/function_calling.py +0 -43
- lionagi/experimental/tool/manual.py +0 -66
- lionagi/experimental/tool/schema.py +0 -59
- lionagi/experimental/tool/tool_manager.py +0 -138
- lionagi/experimental/tool/util.py +0 -16
- lionagi/experimental/validator/rule.py +0 -139
- lionagi/experimental/validator/validator.py +0 -56
- lionagi/experimental/work/__init__.py +0 -10
- lionagi/experimental/work/async_queue.py +0 -54
- lionagi/experimental/work/schema.py +0 -73
- lionagi/experimental/work/work_function.py +0 -67
- lionagi/experimental/work/worker.py +0 -56
- lionagi/experimental/work2/form.py +0 -371
- lionagi/experimental/work2/report.py +0 -289
- lionagi/experimental/work2/schema.py +0 -30
- lionagi/experimental/work2/tests.py +0 -72
- lionagi/experimental/work2/work_function.py +0 -89
- lionagi/experimental/work2/worker.py +0 -12
- lionagi/integrations/bridge/llamaindex_/get_index.py +0 -294
- lionagi/tests/test_core/generic/test_component.py +0 -89
- lionagi/tests/test_core/test_base_branch.py +0 -426
- lionagi/tests/test_core/test_chat_flow.py +0 -63
- lionagi/tests/test_core/test_mail_manager.py +0 -75
- lionagi/tests/test_core/test_prompts.py +0 -51
- lionagi/tests/test_core/test_session.py +0 -254
- lionagi/tests/test_core/test_session_base_util.py +0 -313
- lionagi/tests/test_core/test_tool_manager.py +0 -95
- lionagi-0.1.2.dist-info/LICENSE +0 -9
- lionagi-0.1.2.dist-info/METADATA +0 -174
- lionagi-0.1.2.dist-info/RECORD +0 -206
- /lionagi/core/{branch → _setting}/__init__.py +0 -0
- /lionagi/core/{execute → agent/eval}/__init__.py +0 -0
- /lionagi/core/{flow → agent/learn}/__init__.py +0 -0
- /lionagi/core/{form → agent/plan}/__init__.py +0 -0
- /lionagi/core/{branch/executable_branch.py → agent/plan/plan.py} +0 -0
- /lionagi/core/{graph → director}/__init__.py +0 -0
- /lionagi/core/{messages → engine}/__init__.py +0 -0
- /lionagi/{experimental/directive/evaluator → core/engine}/sandbox_.py +0 -0
- /lionagi/{experimental/directive/evaluator → core/executor}/__init__.py +0 -0
- /lionagi/{experimental/directive/template_ → core/rule}/__init__.py +0 -0
- /lionagi/{experimental/report → core/unit/template}/__init__.py +0 -0
- /lionagi/{experimental/tool → core/validator}/__init__.py +0 -0
- /lionagi/{experimental/validator → core/work}/__init__.py +0 -0
- /lionagi/experimental/{work2 → compressor}/__init__.py +0 -0
- /lionagi/{core/flow/mono_chat_mixin.py → experimental/directive/template/__init__.py} +0 -0
- /lionagi/experimental/directive/{schema.py → template/schema.py} +0 -0
- /lionagi/experimental/{work2/util.py → evaluator/__init__.py} +0 -0
- /lionagi/experimental/{work2/work.py → knowledge/__init__.py} +0 -0
- /lionagi/{tests/libs/test_async.py → experimental/knowledge/graph.py} +0 -0
- {lionagi-0.1.2.dist-info → lionagi-0.2.0.dist-info}/WHEEL +0 -0
- {lionagi-0.1.2.dist-info → lionagi-0.2.0.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.")
|