lionagi 0.15.8__py3-none-any.whl → 0.15.11__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 +4 -6
- lionagi/adapters/async_postgres_adapter.py +55 -319
- lionagi/libs/file/_utils.py +10 -0
- lionagi/libs/file/process.py +16 -13
- lionagi/libs/file/save.py +3 -2
- lionagi/libs/schema/load_pydantic_model_from_schema.py +2 -1
- lionagi/libs/unstructured/pdf_to_image.py +2 -2
- lionagi/libs/validate/string_similarity.py +4 -4
- lionagi/ln/__init__.py +38 -0
- lionagi/ln/_extract_json.py +60 -0
- lionagi/ln/_fuzzy_json.py +116 -0
- lionagi/ln/_json_dump.py +75 -0
- lionagi/ln/_models.py +0 -1
- lionagi/models/field_model.py +8 -6
- lionagi/operations/__init__.py +3 -0
- lionagi/operations/builder.py +10 -0
- lionagi/protocols/generic/element.py +56 -53
- lionagi/protocols/generic/event.py +46 -67
- lionagi/protocols/generic/pile.py +56 -1
- lionagi/protocols/generic/progression.py +11 -11
- lionagi/protocols/graph/_utils.py +22 -0
- lionagi/protocols/graph/graph.py +17 -21
- lionagi/protocols/graph/node.py +23 -5
- lionagi/protocols/messages/manager.py +41 -45
- lionagi/protocols/messages/message.py +3 -1
- lionagi/protocols/operatives/step.py +2 -19
- lionagi/protocols/types.py +1 -2
- lionagi/service/connections/providers/claude_code_.py +9 -7
- lionagi/service/third_party/claude_code.py +3 -2
- lionagi/session/session.py +14 -2
- lionagi/tools/file/reader.py +5 -6
- lionagi/utils.py +8 -385
- lionagi/version.py +1 -1
- {lionagi-0.15.8.dist-info → lionagi-0.15.11.dist-info}/METADATA +2 -2
- {lionagi-0.15.8.dist-info → lionagi-0.15.11.dist-info}/RECORD +37 -37
- lionagi/libs/package/__init__.py +0 -3
- lionagi/libs/package/imports.py +0 -21
- lionagi/libs/package/management.py +0 -62
- lionagi/libs/package/params.py +0 -30
- lionagi/libs/package/system.py +0 -22
- {lionagi-0.15.8.dist-info → lionagi-0.15.11.dist-info}/WHEEL +0 -0
- {lionagi-0.15.8.dist-info → lionagi-0.15.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
import orjson
|
5
|
+
|
6
|
+
from ._fuzzy_json import fuzzy_json
|
7
|
+
|
8
|
+
# Precompile the regex for extracting JSON code blocks
|
9
|
+
_JSON_BLOCK_PATTERN = re.compile(r"```json\s*(.*?)\s*```", re.DOTALL)
|
10
|
+
|
11
|
+
|
12
|
+
def extract_json(
|
13
|
+
input_data: str | list[str],
|
14
|
+
/,
|
15
|
+
*,
|
16
|
+
fuzzy_parse: bool = False,
|
17
|
+
return_one_if_single: bool = True,
|
18
|
+
) -> dict[str, Any] | list[dict[str, Any]]:
|
19
|
+
"""Extract and parse JSON content from a string or markdown code blocks.
|
20
|
+
Attempts direct JSON parsing first. If that fails, looks for JSON content
|
21
|
+
within markdown code blocks denoted by ```json.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
input_data (str | list[str]): The input string or list of strings to parse.
|
25
|
+
fuzzy_parse (bool): If True, attempts fuzzy JSON parsing on failed attempts.
|
26
|
+
return_one_if_single (bool): If True and only one JSON object is found,
|
27
|
+
returns a dict instead of a list with one dict.
|
28
|
+
"""
|
29
|
+
|
30
|
+
# If input_data is a list, join into a single string
|
31
|
+
if isinstance(input_data, list):
|
32
|
+
input_str = "\n".join(input_data)
|
33
|
+
else:
|
34
|
+
input_str = input_data
|
35
|
+
|
36
|
+
# 1. Try direct parsing
|
37
|
+
try:
|
38
|
+
if fuzzy_parse:
|
39
|
+
return fuzzy_json(input_str)
|
40
|
+
return orjson.loads(input_str)
|
41
|
+
except Exception:
|
42
|
+
pass
|
43
|
+
|
44
|
+
# 2. Attempt extracting JSON blocks from markdown
|
45
|
+
matches = _JSON_BLOCK_PATTERN.findall(input_str)
|
46
|
+
if not matches:
|
47
|
+
return []
|
48
|
+
|
49
|
+
# If only one match, return single dict; if multiple, return list of dicts
|
50
|
+
if return_one_if_single and len(matches) == 1:
|
51
|
+
data_str = matches[0]
|
52
|
+
if fuzzy_parse:
|
53
|
+
return fuzzy_json(data_str)
|
54
|
+
return orjson.loads(data_str)
|
55
|
+
|
56
|
+
# Multiple matches
|
57
|
+
if fuzzy_parse:
|
58
|
+
return [fuzzy_json(m) for m in matches]
|
59
|
+
else:
|
60
|
+
return [orjson.loads(m) for m in matches]
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import contextlib
|
2
|
+
import re
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
import orjson
|
6
|
+
|
7
|
+
|
8
|
+
def fuzzy_json(str_to_parse: str, /) -> dict[str, Any] | list[dict[str, Any]]:
|
9
|
+
"""
|
10
|
+
Attempt to parse a JSON string, trying a few minimal "fuzzy" fixes if needed.
|
11
|
+
|
12
|
+
Steps:
|
13
|
+
1. Parse directly with json.loads.
|
14
|
+
2. Replace single quotes with double quotes, normalize spacing, and try again.
|
15
|
+
3. Attempt to fix unmatched brackets using fix_json_string.
|
16
|
+
4. If all fail, raise ValueError.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
str_to_parse: The JSON string to parse
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
Parsed JSON (dict or list of dicts)
|
23
|
+
|
24
|
+
Raises:
|
25
|
+
ValueError: If the string cannot be parsed as valid JSON
|
26
|
+
TypeError: If the input is not a string
|
27
|
+
"""
|
28
|
+
_check_valid_str(str_to_parse)
|
29
|
+
|
30
|
+
# 1. Direct attempt
|
31
|
+
with contextlib.suppress(Exception):
|
32
|
+
return orjson.loads(str_to_parse)
|
33
|
+
|
34
|
+
# 2. Try cleaning: replace single quotes with double and normalize
|
35
|
+
cleaned = _clean_json_string(str_to_parse.replace("'", '"'))
|
36
|
+
with contextlib.suppress(Exception):
|
37
|
+
return orjson.loads(cleaned)
|
38
|
+
|
39
|
+
# 3. Try fixing brackets
|
40
|
+
fixed = fix_json_string(cleaned)
|
41
|
+
with contextlib.suppress(Exception):
|
42
|
+
return orjson.loads(fixed)
|
43
|
+
|
44
|
+
# If all attempts fail
|
45
|
+
raise ValueError("Invalid JSON string")
|
46
|
+
|
47
|
+
|
48
|
+
def _check_valid_str(str_to_parse: str, /):
|
49
|
+
if not isinstance(str_to_parse, str):
|
50
|
+
raise TypeError("Input must be a string")
|
51
|
+
if not str_to_parse.strip():
|
52
|
+
raise ValueError("Input string is empty")
|
53
|
+
|
54
|
+
|
55
|
+
def _clean_json_string(s: str) -> str:
|
56
|
+
"""Basic normalization: replace unescaped single quotes, trim spaces, ensure keys are quoted."""
|
57
|
+
# Replace unescaped single quotes with double quotes
|
58
|
+
# '(?<!\\)'" means a single quote not preceded by a backslash
|
59
|
+
s = re.sub(r"(?<!\\)'", '"', s)
|
60
|
+
# Collapse multiple whitespaces
|
61
|
+
s = re.sub(r"\s+", " ", s)
|
62
|
+
# Ensure keys are quoted
|
63
|
+
# This attempts to find patterns like { key: value } and turn them into {"key": value}
|
64
|
+
s = re.sub(r'([{,])\s*([^"\s]+)\s*:', r'\1"\2":', s)
|
65
|
+
return s.strip()
|
66
|
+
|
67
|
+
|
68
|
+
def fix_json_string(str_to_parse: str, /) -> str:
|
69
|
+
"""Try to fix JSON string by ensuring brackets are matched properly."""
|
70
|
+
if not str_to_parse:
|
71
|
+
raise ValueError("Input string is empty")
|
72
|
+
|
73
|
+
brackets = {"{": "}", "[": "]"}
|
74
|
+
open_brackets = []
|
75
|
+
pos = 0
|
76
|
+
length = len(str_to_parse)
|
77
|
+
|
78
|
+
while pos < length:
|
79
|
+
char = str_to_parse[pos]
|
80
|
+
|
81
|
+
if char == "\\":
|
82
|
+
pos += 2 # Skip escaped chars
|
83
|
+
continue
|
84
|
+
|
85
|
+
if char == '"':
|
86
|
+
pos += 1
|
87
|
+
# skip string content
|
88
|
+
while pos < length:
|
89
|
+
if str_to_parse[pos] == "\\":
|
90
|
+
pos += 2
|
91
|
+
continue
|
92
|
+
if str_to_parse[pos] == '"':
|
93
|
+
pos += 1
|
94
|
+
break
|
95
|
+
pos += 1
|
96
|
+
continue
|
97
|
+
|
98
|
+
if char in brackets:
|
99
|
+
open_brackets.append(brackets[char])
|
100
|
+
elif char in brackets.values():
|
101
|
+
if not open_brackets:
|
102
|
+
# Extra closing bracket
|
103
|
+
# Better to raise error than guess
|
104
|
+
raise ValueError("Extra closing bracket found.")
|
105
|
+
if open_brackets[-1] != char:
|
106
|
+
# Mismatched bracket
|
107
|
+
raise ValueError("Mismatched brackets.")
|
108
|
+
open_brackets.pop()
|
109
|
+
|
110
|
+
pos += 1
|
111
|
+
|
112
|
+
# Add missing closing brackets if any
|
113
|
+
if open_brackets:
|
114
|
+
str_to_parse += "".join(reversed(open_brackets))
|
115
|
+
|
116
|
+
return str_to_parse
|
lionagi/ln/_json_dump.py
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
import datetime as dt
|
2
|
+
import decimal
|
3
|
+
from collections.abc import Callable
|
4
|
+
from pathlib import Path
|
5
|
+
from uuid import UUID
|
6
|
+
|
7
|
+
import orjson
|
8
|
+
|
9
|
+
|
10
|
+
def _get_default_serializers():
|
11
|
+
return {
|
12
|
+
dt.datetime: lambda o: o.isoformat(),
|
13
|
+
Path: lambda o: str(o),
|
14
|
+
UUID: lambda o: str(o),
|
15
|
+
decimal.Decimal: lambda o: str(o),
|
16
|
+
set: lambda o: list(o),
|
17
|
+
frozenset: lambda o: list(o),
|
18
|
+
}
|
19
|
+
|
20
|
+
|
21
|
+
def _get_default_serializer_order():
|
22
|
+
return [dt.datetime, Path, UUID, decimal.Decimal, set, frozenset]
|
23
|
+
|
24
|
+
|
25
|
+
def get_orjson_default(
|
26
|
+
order: list[type] = None,
|
27
|
+
additional: dict[type, Callable] = None,
|
28
|
+
extend_default: bool = True,
|
29
|
+
) -> Callable:
|
30
|
+
"""get the default function for orjson.dumps
|
31
|
+
Args:
|
32
|
+
order: order of types to check. Defaults to None.
|
33
|
+
additional: additional serializers
|
34
|
+
extend_default: when order is provided, whether to extend the default order or replace it.
|
35
|
+
"""
|
36
|
+
dict_ = _get_default_serializers()
|
37
|
+
dict_.update(additional or {})
|
38
|
+
order_ = _get_default_serializer_order()
|
39
|
+
|
40
|
+
if order:
|
41
|
+
if len(additional or {}) > 0 and extend_default:
|
42
|
+
order_.extend([k for k in order if k not in order_])
|
43
|
+
else:
|
44
|
+
order_ = list(order)
|
45
|
+
else:
|
46
|
+
if len(additional or {}) > 0:
|
47
|
+
order_.extend([k for k in additional.keys() if k not in order_])
|
48
|
+
|
49
|
+
def default(obj):
|
50
|
+
for t in order_:
|
51
|
+
if isinstance(obj, t) and t in dict_:
|
52
|
+
return dict_[t](obj)
|
53
|
+
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
|
54
|
+
|
55
|
+
return default
|
56
|
+
|
57
|
+
|
58
|
+
DEFAULT_SERIALIZER = get_orjson_default()
|
59
|
+
DEFAULT_SERIALIZER_OPTION = (
|
60
|
+
orjson.OPT_INDENT_2
|
61
|
+
| orjson.OPT_SORT_KEYS
|
62
|
+
| orjson.OPT_APPEND_NEWLINE
|
63
|
+
| orjson.OPT_SERIALIZE_DATACLASS
|
64
|
+
)
|
65
|
+
|
66
|
+
|
67
|
+
def json_dumps(d_, decode=True, /) -> str:
|
68
|
+
by_ = orjson.dumps(
|
69
|
+
d_,
|
70
|
+
default=DEFAULT_SERIALIZER,
|
71
|
+
option=DEFAULT_SERIALIZER_OPTION,
|
72
|
+
)
|
73
|
+
if decode:
|
74
|
+
return by_.decode("utf-8")
|
75
|
+
return by_
|
lionagi/ln/_models.py
CHANGED
lionagi/models/field_model.py
CHANGED
@@ -16,7 +16,6 @@ from typing import Annotated, Any
|
|
16
16
|
from typing_extensions import Self, override
|
17
17
|
|
18
18
|
from .._errors import ValidationError
|
19
|
-
from ..utils import UNDEFINED
|
20
19
|
|
21
20
|
# Cache of valid Pydantic Field parameters
|
22
21
|
_PYDANTIC_FIELD_PARAMS: set[str] | None = None
|
@@ -660,13 +659,16 @@ def to_dict(self) -> dict[str, Any]:
|
|
660
659
|
|
661
660
|
# Convert metadata to dictionary
|
662
661
|
for meta in self.metadata:
|
663
|
-
if meta.key not in (
|
662
|
+
if meta.key not in (
|
663
|
+
"nullable",
|
664
|
+
"listable",
|
665
|
+
"validator",
|
666
|
+
"name",
|
667
|
+
"validator_kwargs",
|
668
|
+
"annotation",
|
669
|
+
):
|
664
670
|
result[meta.key] = meta.value
|
665
671
|
|
666
|
-
# Add annotation if available
|
667
|
-
if hasattr(self, "annotation"):
|
668
|
-
result["annotation"] = self.base_type
|
669
|
-
|
670
672
|
return result
|
671
673
|
|
672
674
|
|
lionagi/operations/__init__.py
CHANGED
@@ -8,6 +8,8 @@ from .flow import flow
|
|
8
8
|
from .node import BranchOperations, Operation
|
9
9
|
from .plan.plan import PlanOperation, plan
|
10
10
|
|
11
|
+
Builder = OperationGraphBuilder
|
12
|
+
|
11
13
|
__all__ = (
|
12
14
|
"ExpansionStrategy",
|
13
15
|
"OperationGraphBuilder",
|
@@ -19,4 +21,5 @@ __all__ = (
|
|
19
21
|
"PlanOperation",
|
20
22
|
"brainstorm",
|
21
23
|
"BrainstormOperation",
|
24
|
+
"Builder",
|
22
25
|
)
|
lionagi/operations/builder.py
CHANGED
@@ -453,6 +453,16 @@ def visualize_graph(
|
|
453
453
|
figsize=(14, 10),
|
454
454
|
):
|
455
455
|
"""Visualization with improved layout for complex graphs."""
|
456
|
+
from lionagi.protocols.graph.graph import (
|
457
|
+
_MATPLIB_AVAILABLE,
|
458
|
+
_NETWORKX_AVAILABLE,
|
459
|
+
)
|
460
|
+
|
461
|
+
if _MATPLIB_AVAILABLE is not True:
|
462
|
+
raise _MATPLIB_AVAILABLE
|
463
|
+
if _NETWORKX_AVAILABLE is not True:
|
464
|
+
raise _NETWORKX_AVAILABLE
|
465
|
+
|
456
466
|
import matplotlib.pyplot as plt
|
457
467
|
import networkx as nx
|
458
468
|
import numpy as np
|
@@ -4,11 +4,12 @@
|
|
4
4
|
|
5
5
|
from __future__ import annotations
|
6
6
|
|
7
|
+
import datetime as dt
|
7
8
|
from collections.abc import Mapping, Sequence
|
8
|
-
from
|
9
|
-
from typing import Any, Generic, TypeAlias, TypeVar
|
9
|
+
from typing import Any, Generic, Literal, TypeAlias, TypeVar
|
10
10
|
from uuid import UUID, uuid4
|
11
11
|
|
12
|
+
import orjson
|
12
13
|
from pydantic import (
|
13
14
|
BaseModel,
|
14
15
|
ConfigDict,
|
@@ -17,10 +18,11 @@ from pydantic import (
|
|
17
18
|
field_validator,
|
18
19
|
)
|
19
20
|
|
21
|
+
from lionagi import ln
|
20
22
|
from lionagi._class_registry import get_class
|
21
23
|
from lionagi._errors import IDError
|
22
24
|
from lionagi.settings import Settings
|
23
|
-
from lionagi.utils import
|
25
|
+
from lionagi.utils import import_module, time, to_dict
|
24
26
|
|
25
27
|
from .._concepts import Collective, Observable, Ordering
|
26
28
|
|
@@ -29,6 +31,7 @@ __all__ = (
|
|
29
31
|
"Element",
|
30
32
|
"ID",
|
31
33
|
"validate_order",
|
34
|
+
"DEFAULT_ELEMENT_SERIALIZER",
|
32
35
|
)
|
33
36
|
|
34
37
|
|
@@ -173,15 +176,6 @@ class Element(BaseModel, Observable):
|
|
173
176
|
If a `lion_class` field is present in `metadata`, it must match the
|
174
177
|
fully qualified name of this class. Converts `metadata` to a dict
|
175
178
|
if needed.
|
176
|
-
|
177
|
-
Args:
|
178
|
-
val (dict): The initial metadata value.
|
179
|
-
|
180
|
-
Returns:
|
181
|
-
dict: A valid dictionary of metadata.
|
182
|
-
|
183
|
-
Raises:
|
184
|
-
ValueError: If the metadata is invalid or has a class mismatch.
|
185
179
|
"""
|
186
180
|
if not val:
|
187
181
|
return {}
|
@@ -196,7 +190,7 @@ class Element(BaseModel, Observable):
|
|
196
190
|
return val
|
197
191
|
|
198
192
|
@field_validator("created_at", mode="before")
|
199
|
-
def _coerce_created_at(cls, val: float | datetime | None) -> float:
|
193
|
+
def _coerce_created_at(cls, val: float | dt.datetime | None) -> float:
|
200
194
|
"""Coerces `created_at` to a float-based timestamp.
|
201
195
|
|
202
196
|
Args:
|
@@ -212,7 +206,7 @@ class Element(BaseModel, Observable):
|
|
212
206
|
return time(tz=Settings.Config.TIMEZONE, type_="timestamp")
|
213
207
|
if isinstance(val, float):
|
214
208
|
return val
|
215
|
-
if isinstance(val, datetime):
|
209
|
+
if isinstance(val, dt.datetime):
|
216
210
|
return val.timestamp()
|
217
211
|
try:
|
218
212
|
return float(val) # type: ignore
|
@@ -245,33 +239,24 @@ class Element(BaseModel, Observable):
|
|
245
239
|
return str(val)
|
246
240
|
|
247
241
|
@property
|
248
|
-
def created_datetime(self) -> datetime:
|
242
|
+
def created_datetime(self) -> dt.datetime:
|
249
243
|
"""Returns the creation time as a datetime object.
|
250
244
|
|
251
245
|
Returns:
|
252
246
|
datetime: The creation time in UTC.
|
253
247
|
"""
|
254
|
-
return datetime.fromtimestamp(self.created_at)
|
248
|
+
return dt.datetime.fromtimestamp(self.created_at)
|
255
249
|
|
256
250
|
def __eq__(self, other: Any) -> bool:
|
257
|
-
"""Compares two Element instances by their ID.
|
258
|
-
|
259
|
-
Args:
|
260
|
-
other (Any): Another object for comparison.
|
261
|
-
|
262
|
-
Returns:
|
263
|
-
bool: True if both share the same ID, False otherwise.
|
264
|
-
"""
|
251
|
+
"""Compares two Element instances by their ID."""
|
265
252
|
if not isinstance(other, Element):
|
266
|
-
|
253
|
+
raise NotImplementedError(
|
254
|
+
f"Cannot compare Element with {type(other)}"
|
255
|
+
)
|
267
256
|
return self.id == other.id
|
268
257
|
|
269
258
|
def __hash__(self) -> int:
|
270
|
-
"""Returns a hash of this element's ID.
|
271
|
-
|
272
|
-
Returns:
|
273
|
-
int: The hash of the ID, making elements usable as dictionary keys.
|
274
|
-
"""
|
259
|
+
"""Returns a hash of this element's ID."""
|
275
260
|
return hash(self.id)
|
276
261
|
|
277
262
|
def __bool__(self) -> bool:
|
@@ -282,42 +267,37 @@ class Element(BaseModel, Observable):
|
|
282
267
|
def class_name(cls, full: bool = False) -> str:
|
283
268
|
"""Returns this class's name.
|
284
269
|
|
285
|
-
|
286
|
-
|
287
|
-
If True, returns the fully qualified class name; otherwise,
|
288
|
-
returns only the class name.
|
289
|
-
|
290
|
-
Returns:
|
291
|
-
str: The class name or fully qualified name.
|
270
|
+
full (bool): If True, returns the fully qualified class name; otherwise,
|
271
|
+
returns only the class name.
|
292
272
|
"""
|
293
273
|
if full:
|
294
274
|
return str(cls).split("'")[1]
|
295
275
|
return cls.__name__
|
296
276
|
|
297
|
-
def
|
298
|
-
"""Converts this Element to a dictionary.
|
299
|
-
|
300
|
-
All fields are included except those set to `UNDEFINED`.
|
301
|
-
|
302
|
-
Returns:
|
303
|
-
dict: The dictionary representation of this Element.
|
304
|
-
"""
|
277
|
+
def _to_dict(self) -> dict:
|
305
278
|
dict_ = self.model_dump()
|
306
279
|
dict_["metadata"].update({"lion_class": self.class_name(full=True)})
|
307
|
-
return {k: v for k, v in dict_.items() if v
|
280
|
+
return {k: v for k, v in dict_.items() if ln.not_sentinel(v)}
|
281
|
+
|
282
|
+
def to_dict(self, mode: Literal["python", "json"] = "python") -> dict:
|
283
|
+
"""Converts this Element to a dictionary."""
|
284
|
+
if mode == "python":
|
285
|
+
return self._to_dict()
|
286
|
+
return orjson.loads(self.to_json(decode=False))
|
287
|
+
|
288
|
+
def as_jsonable(self) -> dict:
|
289
|
+
"""Converts this Element to a JSON-serializable dictionary."""
|
290
|
+
return self.to_dict(mode="json")
|
308
291
|
|
309
292
|
@classmethod
|
310
293
|
def from_dict(cls, data: dict, /) -> Element:
|
311
294
|
"""Deserializes a dictionary into an Element or subclass of Element.
|
312
295
|
|
313
296
|
If `lion_class` in `metadata` refers to a subclass, this method
|
314
|
-
|
297
|
+
is polymorphic, it will attempt to create an instance of that subclass.
|
315
298
|
|
316
299
|
Args:
|
317
300
|
data (dict): A dictionary of field data.
|
318
|
-
|
319
|
-
Returns:
|
320
|
-
Element: An Element or a subclass instance loaded from `data`.
|
321
301
|
"""
|
322
302
|
metadata = data.pop("metadata", {})
|
323
303
|
if "lion_class" in metadata:
|
@@ -337,9 +317,6 @@ class Element(BaseModel, Observable):
|
|
337
317
|
return subcls_type.from_dict(data)
|
338
318
|
|
339
319
|
except Exception:
|
340
|
-
# Fallback attempt: direct import if not in registry
|
341
|
-
from lionagi.libs.package.imports import import_module
|
342
|
-
|
343
320
|
mod, imp = subcls.rsplit(".", 1)
|
344
321
|
subcls_type = import_module(mod, import_name=imp)
|
345
322
|
data["metadata"] = metadata
|
@@ -350,6 +327,32 @@ class Element(BaseModel, Observable):
|
|
350
327
|
data["metadata"] = metadata
|
351
328
|
return cls.model_validate(data)
|
352
329
|
|
330
|
+
def to_json(self, decode: bool = True) -> str:
|
331
|
+
"""Converts this Element to a JSON string."""
|
332
|
+
dict_ = self._to_dict()
|
333
|
+
if decode:
|
334
|
+
return orjson.dumps(
|
335
|
+
dict_,
|
336
|
+
default=DEFAULT_ELEMENT_SERIALIZER,
|
337
|
+
option=ln.DEFAULT_SERIALIZER_OPTION,
|
338
|
+
).decode()
|
339
|
+
return orjson.dumps(dict_, default=DEFAULT_ELEMENT_SERIALIZER)
|
340
|
+
|
341
|
+
def from_json(cls, json_str: str) -> Element:
|
342
|
+
"""Deserializes a JSON string into an Element or subclass of Element."""
|
343
|
+
data = orjson.loads(json_str)
|
344
|
+
return cls.from_dict(data)
|
345
|
+
|
346
|
+
|
347
|
+
DEFAULT_ELEMENT_SERIALIZER = ln.get_orjson_default(
|
348
|
+
order=[IDType, Element, BaseModel],
|
349
|
+
additional={
|
350
|
+
IDType: lambda o: str(o),
|
351
|
+
Element: lambda o: o.to_dict(),
|
352
|
+
BaseModel: lambda o: o.model_dump(mode="json"),
|
353
|
+
},
|
354
|
+
)
|
355
|
+
|
353
356
|
|
354
357
|
def validate_order(order: Any) -> list[IDType]:
|
355
358
|
"""Validates and flattens an ordering into a list of IDType objects.
|