lionagi 0.10.7__py3-none-any.whl → 0.12.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.
Files changed (39) hide show
  1. lionagi/adapters/__init__.py +1 -0
  2. lionagi/fields/file.py +1 -1
  3. lionagi/fields/reason.py +1 -1
  4. lionagi/libs/file/concat.py +6 -1
  5. lionagi/libs/file/concat_files.py +5 -1
  6. lionagi/libs/file/create_path.py +80 -0
  7. lionagi/libs/file/file_util.py +358 -0
  8. lionagi/libs/file/save.py +1 -1
  9. lionagi/libs/package/imports.py +177 -8
  10. lionagi/libs/parse/fuzzy_parse_json.py +117 -0
  11. lionagi/libs/parse/to_dict.py +336 -0
  12. lionagi/libs/parse/to_json.py +61 -0
  13. lionagi/libs/parse/to_num.py +378 -0
  14. lionagi/libs/parse/to_xml.py +57 -0
  15. lionagi/libs/parse/xml_parser.py +148 -0
  16. lionagi/libs/schema/breakdown_pydantic_annotation.py +48 -0
  17. lionagi/protocols/generic/log.py +2 -1
  18. lionagi/utils.py +123 -921
  19. lionagi/version.py +1 -1
  20. {lionagi-0.10.7.dist-info → lionagi-0.12.0.dist-info}/METADATA +8 -11
  21. {lionagi-0.10.7.dist-info → lionagi-0.12.0.dist-info}/RECORD +24 -30
  22. lionagi/libs/parse.py +0 -30
  23. lionagi/tools/browser/__init__.py +0 -0
  24. lionagi/tools/browser/providers/browser_use_.py +0 -3
  25. lionagi/tools/code/__init__.py +0 -3
  26. lionagi/tools/code/coder.py +0 -3
  27. lionagi/tools/code/manager.py +0 -3
  28. lionagi/tools/code/providers/__init__.py +0 -3
  29. lionagi/tools/code/providers/aider_.py +0 -3
  30. lionagi/tools/code/providers/e2b_.py +0 -3
  31. lionagi/tools/code/sandbox.py +0 -3
  32. lionagi/tools/file/manager.py +0 -3
  33. lionagi/tools/file/providers/__init__.py +0 -3
  34. lionagi/tools/file/providers/docling_.py +0 -3
  35. lionagi/tools/file/writer.py +0 -3
  36. lionagi/tools/query/__init__.py +0 -3
  37. /lionagi/{tools/browser/providers → libs/parse}/__init__.py +0 -0
  38. {lionagi-0.10.7.dist-info → lionagi-0.12.0.dist-info}/WHEEL +0 -0
  39. {lionagi-0.10.7.dist-info → lionagi-0.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,378 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from decimal import Decimal
5
+ from typing import Any, Literal, TypeVar
6
+
7
+ # Type definitions
8
+ NUM_TYPE_LITERAL = Literal["int", "float", "complex"]
9
+ NUM_TYPES = type[int] | type[float] | type[complex] | NUM_TYPE_LITERAL
10
+ NumericType = TypeVar("NumericType", int, float, complex)
11
+
12
+ # Type mapping
13
+ TYPE_MAP = {"int": int, "float": float, "complex": complex}
14
+
15
+ # Regex patterns for different numeric formats
16
+ PATTERNS = {
17
+ "scientific": r"[-+]?(?:\d*\.)?\d+[eE][-+]?\d+",
18
+ "complex_sci": r"[-+]?(?:\d*\.)?\d+(?:[eE][-+]?\d+)?[-+](?:\d*\.)?\d+(?:[eE][-+]?\d+)?[jJ]",
19
+ "complex": r"[-+]?(?:\d*\.)?\d+[-+](?:\d*\.)?\d+[jJ]",
20
+ "pure_imaginary": r"[-+]?(?:\d*\.)?\d*[jJ]",
21
+ "percentage": r"[-+]?(?:\d*\.)?\d+%",
22
+ "fraction": r"[-+]?\d+/\d+",
23
+ "decimal": r"[-+]?(?:\d*\.)?\d+",
24
+ "special": r"[-+]?(?:inf|infinity|nan)",
25
+ }
26
+
27
+
28
+ def to_num(
29
+ input_: Any,
30
+ /,
31
+ *,
32
+ upper_bound: int | float | None = None,
33
+ lower_bound: int | float | None = None,
34
+ num_type: NUM_TYPES = float,
35
+ precision: int | None = None,
36
+ num_count: int = 1,
37
+ ) -> int | float | complex | list[int | float | complex]:
38
+ """Convert input to numeric type(s) with validation and bounds checking.
39
+
40
+ Args:
41
+ input_value: The input to convert to number(s).
42
+ upper_bound: Maximum allowed value (inclusive).
43
+ lower_bound: Minimum allowed value (inclusive).
44
+ num_type: Target numeric type ('int', 'float', 'complex' or type objects).
45
+ precision: Number of decimal places for rounding (float only).
46
+ num_count: Number of numeric values to extract.
47
+
48
+ Returns:
49
+ Converted number(s). Single value if num_count=1, else list.
50
+
51
+ Raises:
52
+ ValueError: For invalid input or out of bounds values.
53
+ TypeError: For invalid input types or invalid type conversions.
54
+ """
55
+ # Validate input
56
+ if isinstance(input_, (list, tuple)):
57
+ raise TypeError("Input cannot be a sequence")
58
+
59
+ # Handle boolean input
60
+ if isinstance(input_, bool):
61
+ return validate_num_type(num_type)(input_)
62
+
63
+ # Handle direct numeric input
64
+ if isinstance(input_, (int, float, complex, Decimal)):
65
+ inferred_type = type(input_)
66
+ if isinstance(input_, Decimal):
67
+ inferred_type = float
68
+ value = float(input_) if not isinstance(input_, complex) else input_
69
+ value = apply_bounds(value, upper_bound, lower_bound)
70
+ value = apply_precision(value, precision)
71
+ return convert_type(value, validate_num_type(num_type), inferred_type)
72
+
73
+ # Convert input to string and extract numbers
74
+ input_str = str(input_)
75
+ number_matches = extract_numbers(input_str)
76
+
77
+ if not number_matches:
78
+ raise ValueError(f"No valid numbers found in: {input_str}")
79
+
80
+ # Process numbers
81
+ results = []
82
+ target_type = validate_num_type(num_type)
83
+
84
+ number_matches = (
85
+ number_matches[:num_count]
86
+ if num_count < len(number_matches)
87
+ else number_matches
88
+ )
89
+
90
+ for type_and_value in number_matches:
91
+ try:
92
+ # Infer appropriate type
93
+ inferred_type = infer_type(type_and_value)
94
+
95
+ # Parse to numeric value
96
+ value = parse_number(type_and_value)
97
+
98
+ # Apply bounds if not complex
99
+ value = apply_bounds(value, upper_bound, lower_bound)
100
+
101
+ # Apply precision
102
+ value = apply_precision(value, precision)
103
+
104
+ # Convert to target type if different from inferred
105
+ value = convert_type(value, target_type, inferred_type)
106
+
107
+ results.append(value)
108
+
109
+ except Exception as e:
110
+ if len(type_and_value) == 2:
111
+ raise type(e)(
112
+ f"Error processing {type_and_value[1]}: {str(e)}"
113
+ )
114
+ raise type(e)(f"Error processing {type_and_value}: {str(e)}")
115
+
116
+ if results and num_count == 1:
117
+ return results[0]
118
+ return results
119
+
120
+
121
+ def extract_numbers(text: str) -> list[tuple[str, str]]:
122
+ """Extract numeric values from text using ordered regex patterns.
123
+
124
+ Args:
125
+ text: The text to extract numbers from.
126
+
127
+ Returns:
128
+ List of tuples containing (pattern_type, matched_value).
129
+ """
130
+ combined_pattern = "|".join(PATTERNS.values())
131
+ matches = re.finditer(combined_pattern, text, re.IGNORECASE)
132
+ numbers = []
133
+
134
+ for match in matches:
135
+ value = match.group()
136
+ # Check which pattern matched
137
+ for pattern_name, pattern in PATTERNS.items():
138
+ if re.fullmatch(pattern, value, re.IGNORECASE):
139
+ numbers.append((pattern_name, value))
140
+ break
141
+
142
+ return numbers
143
+
144
+
145
+ def validate_num_type(num_type: NUM_TYPES) -> type:
146
+ """Validate and normalize numeric type specification.
147
+
148
+ Args:
149
+ num_type: The numeric type to validate.
150
+
151
+ Returns:
152
+ The normalized Python type object.
153
+
154
+ Raises:
155
+ ValueError: If the type specification is invalid.
156
+ """
157
+ if isinstance(num_type, str):
158
+ if num_type not in TYPE_MAP:
159
+ raise ValueError(f"Invalid number type: {num_type}")
160
+ return TYPE_MAP[num_type]
161
+
162
+ if num_type not in (int, float, complex):
163
+ raise ValueError(f"Invalid number type: {num_type}")
164
+ return num_type
165
+
166
+
167
+ def infer_type(value: tuple[str, str]) -> type:
168
+ """Infer appropriate numeric type from value.
169
+
170
+ Args:
171
+ value: Tuple of (pattern_type, matched_value).
172
+
173
+ Returns:
174
+ The inferred Python type.
175
+ """
176
+ pattern_type, _ = value
177
+ if pattern_type in ("complex", "complex_sci", "pure_imaginary"):
178
+ return complex
179
+ return float
180
+
181
+
182
+ def convert_special(value: str) -> float:
183
+ """Convert special float values (inf, -inf, nan).
184
+
185
+ Args:
186
+ value: The string value to convert.
187
+
188
+ Returns:
189
+ The converted float value.
190
+ """
191
+ value = value.lower()
192
+ if "infinity" in value or "inf" in value:
193
+ return float("-inf") if value.startswith("-") else float("inf")
194
+ return float("nan")
195
+
196
+
197
+ def convert_percentage(value: str) -> float:
198
+ """Convert percentage string to float.
199
+
200
+ Args:
201
+ value: The percentage string to convert.
202
+
203
+ Returns:
204
+ The converted float value.
205
+
206
+ Raises:
207
+ ValueError: If the percentage value is invalid.
208
+ """
209
+ try:
210
+ return float(value.rstrip("%")) / 100
211
+ except ValueError as e:
212
+ raise ValueError(f"Invalid percentage value: {value}") from e
213
+
214
+
215
+ def convert_complex(value: str) -> complex:
216
+ """Convert complex number string to complex.
217
+
218
+ Args:
219
+ value: The complex number string to convert.
220
+
221
+ Returns:
222
+ The converted complex value.
223
+
224
+ Raises:
225
+ ValueError: If the complex number is invalid.
226
+ """
227
+ try:
228
+ # Handle pure imaginary numbers
229
+ if value.endswith("j") or value.endswith("J"):
230
+ if value in ("j", "J"):
231
+ return complex(0, 1)
232
+ if value in ("+j", "+J"):
233
+ return complex(0, 1)
234
+ if value in ("-j", "-J"):
235
+ return complex(0, -1)
236
+ if "+" not in value and "-" not in value[1:]:
237
+ # Pure imaginary number
238
+ imag = float(value[:-1] or "1")
239
+ return complex(0, imag)
240
+
241
+ return complex(value.replace(" ", ""))
242
+ except ValueError as e:
243
+ raise ValueError(f"Invalid complex number: {value}") from e
244
+
245
+
246
+ def convert_type(
247
+ value: float | complex,
248
+ target_type: type,
249
+ inferred_type: type,
250
+ ) -> int | float | complex:
251
+ """Convert value to target type if specified, otherwise use inferred type.
252
+
253
+ Args:
254
+ value: The value to convert.
255
+ target_type: The requested target type.
256
+ inferred_type: The inferred type from the value.
257
+
258
+ Returns:
259
+ The converted value.
260
+
261
+ Raises:
262
+ TypeError: If the conversion is not possible.
263
+ """
264
+ try:
265
+ # If no specific type requested, use inferred type
266
+ if target_type is float and inferred_type is complex:
267
+ return value
268
+
269
+ # Handle explicit type conversions
270
+ if target_type is int and isinstance(value, complex):
271
+ raise TypeError("Cannot convert complex number to int")
272
+ return target_type(value)
273
+ except (ValueError, TypeError) as e:
274
+ raise TypeError(
275
+ f"Cannot convert {value} to {target_type.__name__}"
276
+ ) from e
277
+
278
+
279
+ def apply_bounds(
280
+ value: float | complex,
281
+ upper_bound: float | None = None,
282
+ lower_bound: float | None = None,
283
+ ) -> float | complex:
284
+ """Apply bounds checking to numeric value.
285
+
286
+ Args:
287
+ value: The value to check.
288
+ upper_bound: Maximum allowed value (inclusive).
289
+ lower_bound: Minimum allowed value (inclusive).
290
+
291
+ Returns:
292
+ The validated value.
293
+
294
+ Raises:
295
+ ValueError: If the value is outside bounds.
296
+ """
297
+ if isinstance(value, complex):
298
+ return value
299
+
300
+ if upper_bound is not None and value > upper_bound:
301
+ raise ValueError(f"Value {value} exceeds upper bound {upper_bound}")
302
+ if lower_bound is not None and value < lower_bound:
303
+ raise ValueError(f"Value {value} below lower bound {lower_bound}")
304
+ return value
305
+
306
+
307
+ def apply_precision(
308
+ value: float | complex,
309
+ precision: int | None,
310
+ ) -> float | complex:
311
+ """Apply precision rounding to numeric value.
312
+
313
+ Args:
314
+ value: The value to round.
315
+ precision: Number of decimal places.
316
+
317
+ Returns:
318
+ The rounded value.
319
+ """
320
+ if precision is None or isinstance(value, complex):
321
+ return value
322
+ if isinstance(value, float):
323
+ return round(value, precision)
324
+ return value
325
+
326
+
327
+ def parse_number(type_and_value: tuple[str, str]) -> float | complex:
328
+ """Parse string to numeric value based on pattern type.
329
+
330
+ Args:
331
+ type_and_value: Tuple of (pattern_type, matched_value).
332
+
333
+ Returns:
334
+ The parsed numeric value.
335
+
336
+ Raises:
337
+ ValueError: If parsing fails.
338
+ """
339
+ num_type, value = type_and_value
340
+ value = value.strip()
341
+
342
+ try:
343
+ if num_type == "special":
344
+ return convert_special(value)
345
+
346
+ if num_type == "percentage":
347
+ return convert_percentage(value)
348
+
349
+ if num_type == "fraction":
350
+ if "/" not in value:
351
+ raise ValueError(f"Invalid fraction: {value}")
352
+ if value.count("/") > 1:
353
+ raise ValueError(f"Invalid fraction: {value}")
354
+ num, denom = value.split("/")
355
+ if not (num.strip("-").isdigit() and denom.isdigit()):
356
+ raise ValueError(f"Invalid fraction: {value}")
357
+ denom_val = float(denom)
358
+ if denom_val == 0:
359
+ raise ValueError("Division by zero")
360
+ return float(num) / denom_val
361
+ if num_type in ("complex", "complex_sci", "pure_imaginary"):
362
+ return convert_complex(value)
363
+ if num_type == "scientific":
364
+ if "e" not in value.lower():
365
+ raise ValueError(f"Invalid scientific notation: {value}")
366
+ parts = value.lower().split("e")
367
+ if len(parts) != 2:
368
+ raise ValueError(f"Invalid scientific notation: {value}")
369
+ if not (parts[1].lstrip("+-").isdigit()):
370
+ raise ValueError(f"Invalid scientific notation: {value}")
371
+ return float(value)
372
+ if num_type == "decimal":
373
+ return float(value)
374
+
375
+ raise ValueError(f"Unknown number type: {num_type}")
376
+ except Exception as e:
377
+ # Preserve the specific error type but wrap with more context
378
+ raise type(e)(f"Failed to parse {value} as {num_type}: {str(e)}")
@@ -0,0 +1,57 @@
1
+ from typing import Any
2
+
3
+
4
+ def to_xml(
5
+ obj: dict | list | str | int | float | bool | None,
6
+ root_name: str = "root",
7
+ ) -> str:
8
+ """
9
+ Convert a dictionary into an XML formatted string.
10
+
11
+ Rules:
12
+ - A dictionary key becomes an XML tag.
13
+ - If the dictionary value is:
14
+ - A primitive type (str, int, float, bool, None): it becomes the text content of the tag.
15
+ - A list: each element of the list will repeat the same tag.
16
+ - Another dictionary: it is recursively converted to nested XML.
17
+ - root_name sets the top-level XML element name.
18
+
19
+ Args:
20
+ obj: The Python object to convert (typically a dictionary).
21
+ root_name: The name of the root XML element.
22
+
23
+ Returns:
24
+ A string representing the XML.
25
+
26
+ Examples:
27
+ >>> to_xml({"a": 1, "b": {"c": "hello", "d": [10, 20]}}, root_name="data")
28
+ '<data><a>1</a><b><c>hello</c><d>10</d><d>20</d></b></data>'
29
+ """
30
+
31
+ def _convert(value: Any, tag_name: str) -> str:
32
+ # If value is a dict, recursively convert its keys
33
+ if isinstance(value, dict):
34
+ inner = "".join(_convert(v, k) for k, v in value.items())
35
+ return f"<{tag_name}>{inner}</{tag_name}>"
36
+ # If value is a list, repeat the same tag for each element
37
+ elif isinstance(value, list):
38
+ return "".join(_convert(item, tag_name) for item in value)
39
+ # If value is a primitive, convert to string and place inside tag
40
+ else:
41
+ text = "" if value is None else str(value)
42
+ # Escape special XML characters if needed (minimal)
43
+ text = (
44
+ text.replace("&", "&amp;")
45
+ .replace("<", "&lt;")
46
+ .replace(">", "&gt;")
47
+ .replace('"', "&quot;")
48
+ .replace("'", "&apos;")
49
+ )
50
+ return f"<{tag_name}>{text}</{tag_name}>"
51
+
52
+ # If top-level obj is not a dict, wrap it in one
53
+ if not isinstance(obj, dict):
54
+ obj = {root_name: obj}
55
+
56
+ inner_xml = "".join(_convert(v, k) for k, v in obj.items())
57
+ return f"<{root_name}>{inner_xml}</{root_name}>"
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Any
5
+
6
+
7
+ class XMLParser:
8
+ def __init__(self, xml_string: str):
9
+ self.xml_string = xml_string.strip()
10
+ self.index = 0
11
+
12
+ def parse(self) -> dict[str, Any]:
13
+ """Parse the XML string and return the root element as a dictionary."""
14
+ return self._parse_element()
15
+
16
+ def _parse_element(self) -> dict[str, Any]:
17
+ """Parse a single XML element and its children."""
18
+ self._skip_whitespace()
19
+ if self.xml_string[self.index] != "<":
20
+ raise ValueError(
21
+ f"Expected '<', found '{self.xml_string[self.index]}'"
22
+ )
23
+
24
+ tag, attributes = self._parse_opening_tag()
25
+ children: dict[str, str | list | dict] = {}
26
+ text = ""
27
+
28
+ while self.index < len(self.xml_string):
29
+ self._skip_whitespace()
30
+ if self.xml_string.startswith("</", self.index):
31
+ closing_tag = self._parse_closing_tag()
32
+ if closing_tag != tag:
33
+ raise ValueError(
34
+ f"Mismatched tags: '{tag}' and '{closing_tag}'"
35
+ )
36
+ break
37
+ elif self.xml_string.startswith("<", self.index):
38
+ child = self._parse_element()
39
+ child_tag, child_data = next(iter(child.items()))
40
+ if child_tag in children:
41
+ if not isinstance(children[child_tag], list):
42
+ children[child_tag] = [children[child_tag]]
43
+ children[child_tag].append(child_data)
44
+ else:
45
+ children[child_tag] = child_data
46
+ else:
47
+ text += self._parse_text()
48
+
49
+ result: dict[str, Any] = {}
50
+ if attributes:
51
+ result["@attributes"] = attributes
52
+ if children:
53
+ result.update(children)
54
+ elif text.strip():
55
+ result = text.strip()
56
+
57
+ return {tag: result}
58
+
59
+ def _parse_opening_tag(self) -> tuple[str, dict[str, str]]:
60
+ """Parse an opening XML tag and its attributes."""
61
+ match = re.match(
62
+ r'<(\w+)((?:\s+\w+="[^"]*")*)\s*/?>',
63
+ self.xml_string[self.index :], # noqa
64
+ )
65
+ if not match:
66
+ raise ValueError("Invalid opening tag")
67
+ self.index += match.end()
68
+ tag = match.group(1)
69
+ attributes = dict(re.findall(r'(\w+)="([^"]*)"', match.group(2)))
70
+ return tag, attributes
71
+
72
+ def _parse_closing_tag(self) -> str:
73
+ """Parse a closing XML tag."""
74
+ match = re.match(r"</(\w+)>", self.xml_string[self.index :]) # noqa
75
+ if not match:
76
+ raise ValueError("Invalid closing tag")
77
+ self.index += match.end()
78
+ return match.group(1)
79
+
80
+ def _parse_text(self) -> str:
81
+ """Parse text content between XML tags."""
82
+ start = self.index
83
+ while (
84
+ self.index < len(self.xml_string)
85
+ and self.xml_string[self.index] != "<"
86
+ ):
87
+ self.index += 1
88
+ return self.xml_string[start : self.index] # noqa
89
+
90
+ def _skip_whitespace(self) -> None:
91
+ """Skip any whitespace characters at the current parsing position."""
92
+ p_ = len(self.xml_string[self.index :]) # noqa
93
+ m_ = len(self.xml_string[self.index :].lstrip()) # noqa
94
+
95
+ self.index += p_ - m_
96
+
97
+
98
+ def xml_to_dict(
99
+ xml_string: str,
100
+ /,
101
+ suppress=False,
102
+ remove_root: bool = True,
103
+ root_tag: str = None,
104
+ ) -> dict[str, Any]:
105
+ """
106
+ Parse an XML string into a nested dictionary structure.
107
+
108
+ This function converts an XML string into a dictionary where:
109
+ - Element tags become dictionary keys
110
+ - Text content is assigned directly to the tag key if there are no children
111
+ - Attributes are stored in a '@attributes' key
112
+ - Multiple child elements with the same tag are stored as lists
113
+
114
+ Args:
115
+ xml_string: The XML string to parse.
116
+
117
+ Returns:
118
+ A dictionary representation of the XML structure.
119
+
120
+ Raises:
121
+ ValueError: If the XML is malformed or parsing fails.
122
+ """
123
+ try:
124
+ a = XMLParser(xml_string).parse()
125
+ if remove_root and (root_tag or "root") in a:
126
+ a = a[root_tag or "root"]
127
+ return a
128
+ except ValueError as e:
129
+ if not suppress:
130
+ raise e
131
+
132
+
133
+ def dict_to_xml(data: dict, /, root_tag: str = "root") -> str:
134
+ import xml.etree.ElementTree as ET
135
+
136
+ root = ET.Element(root_tag)
137
+
138
+ def convert(dict_obj: dict, parent: Any) -> None:
139
+ for key, val in dict_obj.items():
140
+ if isinstance(val, dict):
141
+ element = ET.SubElement(parent, key)
142
+ convert(dict_obj=val, parent=element)
143
+ else:
144
+ element = ET.SubElement(parent, key)
145
+ element.text = str(object=val)
146
+
147
+ convert(dict_obj=data, parent=root)
148
+ return ET.tostring(root, encoding="unicode")
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from inspect import isclass
4
+ from typing import Any, get_args, get_origin
5
+
6
+ from pydantic import BaseModel
7
+
8
+
9
+ def breakdown_pydantic_annotation(
10
+ model: type[BaseModel],
11
+ max_depth: int | None = None,
12
+ current_depth: int = 0,
13
+ ) -> dict[str, Any]:
14
+
15
+ if not _is_pydantic_model(model):
16
+ raise TypeError("Input must be a Pydantic model")
17
+
18
+ if max_depth is not None and current_depth >= max_depth:
19
+ raise RecursionError("Maximum recursion depth reached")
20
+
21
+ out: dict[str, Any] = {}
22
+ for k, v in model.__annotations__.items():
23
+ origin = get_origin(v)
24
+ if _is_pydantic_model(v):
25
+ out[k] = breakdown_pydantic_annotation(
26
+ v, max_depth, current_depth + 1
27
+ )
28
+ elif origin is list:
29
+ args = get_args(v)
30
+ if args and _is_pydantic_model(args[0]):
31
+ out[k] = [
32
+ breakdown_pydantic_annotation(
33
+ args[0], max_depth, current_depth + 1
34
+ )
35
+ ]
36
+ else:
37
+ out[k] = [args[0] if args else Any]
38
+ else:
39
+ out[k] = v
40
+
41
+ return out
42
+
43
+
44
+ def _is_pydantic_model(x: Any) -> bool:
45
+ try:
46
+ return isclass(x) and issubclass(x, BaseModel)
47
+ except TypeError:
48
+ return False
@@ -11,7 +11,8 @@ from typing import Any
11
11
 
12
12
  from pydantic import BaseModel, Field, PrivateAttr, field_validator
13
13
 
14
- from lionagi.utils import create_path, to_dict
14
+ from lionagi.libs.file.create_path import create_path
15
+ from lionagi.utils import to_dict
15
16
 
16
17
  from .._concepts import Manager
17
18
  from .element import Element