lionagi 0.14.8__py3-none-any.whl → 0.14.10__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 (54) hide show
  1. lionagi/_errors.py +120 -11
  2. lionagi/_types.py +0 -6
  3. lionagi/config.py +3 -1
  4. lionagi/fields/reason.py +1 -1
  5. lionagi/libs/concurrency/throttle.py +79 -0
  6. lionagi/libs/parse.py +2 -1
  7. lionagi/libs/unstructured/__init__.py +0 -0
  8. lionagi/libs/unstructured/pdf_to_image.py +45 -0
  9. lionagi/libs/unstructured/read_image_to_base64.py +33 -0
  10. lionagi/libs/validate/to_num.py +378 -0
  11. lionagi/libs/validate/xml_parser.py +203 -0
  12. lionagi/models/operable_model.py +8 -3
  13. lionagi/operations/flow.py +0 -1
  14. lionagi/protocols/generic/event.py +2 -0
  15. lionagi/protocols/generic/log.py +26 -10
  16. lionagi/protocols/operatives/step.py +1 -1
  17. lionagi/protocols/types.py +9 -1
  18. lionagi/service/__init__.py +22 -1
  19. lionagi/service/connections/api_calling.py +57 -2
  20. lionagi/service/connections/endpoint_config.py +1 -1
  21. lionagi/service/connections/header_factory.py +4 -2
  22. lionagi/service/connections/match_endpoint.py +10 -10
  23. lionagi/service/connections/providers/anthropic_.py +5 -2
  24. lionagi/service/connections/providers/claude_code_.py +13 -17
  25. lionagi/service/connections/providers/claude_code_cli.py +51 -16
  26. lionagi/service/connections/providers/exa_.py +5 -3
  27. lionagi/service/connections/providers/oai_.py +116 -81
  28. lionagi/service/connections/providers/ollama_.py +38 -18
  29. lionagi/service/connections/providers/perplexity_.py +36 -14
  30. lionagi/service/connections/providers/types.py +30 -0
  31. lionagi/service/hooks/__init__.py +25 -0
  32. lionagi/service/hooks/_types.py +52 -0
  33. lionagi/service/hooks/_utils.py +85 -0
  34. lionagi/service/hooks/hook_event.py +67 -0
  35. lionagi/service/hooks/hook_registry.py +221 -0
  36. lionagi/service/imodel.py +120 -34
  37. lionagi/service/third_party/claude_code.py +715 -0
  38. lionagi/service/third_party/openai_model_names.py +198 -0
  39. lionagi/service/third_party/pplx_models.py +16 -8
  40. lionagi/service/types.py +21 -0
  41. lionagi/session/branch.py +1 -4
  42. lionagi/tools/base.py +1 -3
  43. lionagi/tools/file/reader.py +1 -1
  44. lionagi/tools/memory/tools.py +2 -2
  45. lionagi/utils.py +12 -775
  46. lionagi/version.py +1 -1
  47. {lionagi-0.14.8.dist-info → lionagi-0.14.10.dist-info}/METADATA +6 -2
  48. {lionagi-0.14.8.dist-info → lionagi-0.14.10.dist-info}/RECORD +50 -40
  49. lionagi/service/connections/providers/_claude_code/__init__.py +0 -3
  50. lionagi/service/connections/providers/_claude_code/models.py +0 -244
  51. lionagi/service/connections/providers/_claude_code/stream_cli.py +0 -359
  52. lionagi/service/third_party/openai_models.py +0 -18241
  53. {lionagi-0.14.8.dist-info → lionagi-0.14.10.dist-info}/WHEEL +0 -0
  54. {lionagi-0.14.8.dist-info → lionagi-0.14.10.dist-info}/licenses/LICENSE +0 -0
lionagi/_errors.py CHANGED
@@ -2,28 +2,137 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ from __future__ import annotations
6
+
7
+ from typing import Any, ClassVar
8
+
9
+ __all__ = (
10
+ "LionError",
11
+ "ValidationError",
12
+ "NotFoundError",
13
+ "ExistsError",
14
+ "ObservationError",
15
+ "ResourceError",
16
+ "RateLimitError",
17
+ "IDError",
18
+ "RelationError",
19
+ "OperationError",
20
+ "ExecutionError",
21
+ "ItemNotFoundError",
22
+ "ItemExistsError",
23
+ )
24
+
5
25
 
6
26
  class LionError(Exception):
7
- pass
27
+ default_message: ClassVar[str] = "LionAGI error"
28
+ default_status_code: ClassVar[int] = 500
29
+ __slots__ = ("message", "details", "status_code")
30
+
31
+ def __init__(
32
+ self,
33
+ message: str | None = None,
34
+ *,
35
+ details: dict[str, Any] | None = None,
36
+ status_code: int | None = None,
37
+ cause: Exception | None = None,
38
+ ):
39
+ super().__init__(message or self.default_message)
40
+ if cause:
41
+ self.__cause__ = cause # preserves traceback
42
+ self.message = message or self.default_message
43
+ self.details = details or {}
44
+ self.status_code = status_code or type(self).default_status_code
45
+
46
+ def to_dict(self, *, include_cause: bool = False) -> dict[str, Any]:
47
+ data = {
48
+ "error": self.__class__.__name__,
49
+ "message": self.message,
50
+ "status_code": self.status_code,
51
+ **({"details": self.details} if self.details else {}),
52
+ }
53
+ if include_cause and (cause := self.get_cause()):
54
+ data["cause"] = repr(cause)
55
+ return data
56
+
57
+ def get_cause(self) -> Exception | None:
58
+ """Get the cause of this error, if any."""
59
+ return self.__cause__ if hasattr(self, "__cause__") else None
60
+
61
+ @classmethod
62
+ def from_value(
63
+ cls,
64
+ value: Any,
65
+ *,
66
+ expected: str | None = None,
67
+ message: str | None = None,
68
+ cause: Exception | None = None,
69
+ **extra: Any,
70
+ ):
71
+ """Create a ValidationError from a value with optional expected type and message."""
72
+ details = {
73
+ "value": value,
74
+ "type": type(value).__name__,
75
+ **({"expected": expected} if expected else {}),
76
+ **extra,
77
+ }
78
+ return cls(message=message, details=details, cause=cause)
8
79
 
9
80
 
10
- class ItemNotFoundError(LionError):
11
- pass
81
+ class ValidationError(LionError):
82
+ """Exception raised when validation fails."""
12
83
 
84
+ default_message = "Validation failed"
85
+ default_status_code = 422
86
+ __slots__ = ()
13
87
 
14
- class ItemExistsError(LionError):
15
- pass
16
88
 
89
+ class NotFoundError(LionError):
90
+ """Exception raised when an item is not found."""
17
91
 
18
- class IDError(LionError):
19
- pass
92
+ default_message = "Item not found"
93
+ default_status_code = 404
94
+ __slots__ = ()
20
95
 
21
96
 
22
- class RelationError(LionError):
23
- pass
97
+ class ExistsError(LionError):
98
+ """Exception raised when an item already exists."""
99
+
100
+ default_message = "Item already exists"
101
+ default_status_code = 409
102
+ __slots__ = ()
103
+
104
+
105
+ class ObservationError(LionError):
106
+ """Exception raised when an observation fails."""
107
+
108
+ default_message = "Observation failed"
109
+ default_status_code = 500
110
+ __slots__ = ()
111
+
112
+
113
+ class ResourceError(LionError):
114
+ """Exception raised when resource access fails."""
115
+
116
+ default_message = "Resource error"
117
+ default_status_code = 429
118
+ __slots__ = ()
24
119
 
25
120
 
26
121
  class RateLimitError(LionError):
122
+ __slots__ = ("retry_after",) # one extra attr
123
+ default_message = "Rate limit exceeded"
124
+ default_status_code = 429
125
+
126
+ def __init__(self, retry_after: float, **kw):
127
+ super().__init__(**kw)
128
+ object.__setattr__(self, "retry_after", retry_after)
129
+
130
+
131
+ class IDError(LionError):
132
+ pass
133
+
134
+
135
+ class RelationError(LionError):
27
136
  pass
28
137
 
29
138
 
@@ -35,5 +144,5 @@ class ExecutionError(LionError):
35
144
  pass
36
145
 
37
146
 
38
- class ValidationError(LionError):
39
- pass
147
+ ItemNotFoundError = NotFoundError
148
+ ItemExistsError = ExistsError
lionagi/_types.py CHANGED
@@ -1,9 +1,3 @@
1
1
  from .fields import *
2
2
  from .models import *
3
- from .operations import (
4
- BrainstormOperation,
5
- ExpansionStrategy,
6
- Operation,
7
- PlanOperation,
8
- )
9
3
  from .protocols.types import *
lionagi/config.py CHANGED
@@ -63,12 +63,14 @@ class AppSettings(BaseSettings, frozen=True):
63
63
  GROQ_API_KEY: SecretStr | None = None
64
64
  ANTHROPIC_API_KEY: SecretStr | None = None
65
65
 
66
+ OPENAI_DEFAULT_MODEL: str = "gpt-4.1-mini"
67
+
66
68
  # defaults models
67
69
  LIONAGI_EMBEDDING_PROVIDER: str = "openai"
68
70
  LIONAGI_EMBEDDING_MODEL: str = "text-embedding-3-small"
69
71
 
70
72
  LIONAGI_CHAT_PROVIDER: str = "openai"
71
- LIONAGI_CHAT_MODEL: str = "gpt-4.1-nano"
73
+ LIONAGI_CHAT_MODEL: str = "gpt-4.1-mini"
72
74
 
73
75
  # default storage
74
76
  LIONAGI_AUTO_STORE_EVENT: bool = False
lionagi/fields/reason.py CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  from pydantic import Field, field_validator
6
6
 
7
+ from lionagi.libs.validate.to_num import to_num
7
8
  from lionagi.models import FieldModel, HashableModel
8
- from lionagi.utils import to_num
9
9
 
10
10
  __all__ = ("Reason",)
11
11
 
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import functools
5
+ from time import sleep, time
6
+ from typing import Any, Callable, TypeVar
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ __all__ = ("Throttle",)
12
+
13
+
14
+ class Throttle:
15
+ """
16
+ Provide a throttling mechanism for function calls.
17
+
18
+ When used as a decorator, it ensures that the decorated function can only
19
+ be called once per specified period. Subsequent calls within this period
20
+ are delayed to enforce this constraint.
21
+
22
+ Attributes:
23
+ period: The minimum time period (in seconds) between successive calls.
24
+ """
25
+
26
+ def __init__(self, period: float) -> None:
27
+ """
28
+ Initialize a new instance of Throttle.
29
+
30
+ Args:
31
+ period: The minimum time period (in seconds) between
32
+ successive calls.
33
+ """
34
+ self.period = period
35
+ self.last_called = 0
36
+
37
+ def __call__(self, func: Callable[..., T]) -> Callable[..., T]:
38
+ """
39
+ Decorate a synchronous function with the throttling mechanism.
40
+
41
+ Args:
42
+ func: The synchronous function to be throttled.
43
+
44
+ Returns:
45
+ The throttled synchronous function.
46
+ """
47
+
48
+ @functools.wraps(func)
49
+ def wrapper(*args, **kwargs) -> Any:
50
+ elapsed = time() - self.last_called
51
+ if elapsed < self.period:
52
+ sleep(self.period - elapsed)
53
+ self.last_called = time()
54
+ return func(*args, **kwargs)
55
+
56
+ return wrapper
57
+
58
+ def __call_async__(
59
+ self, func: Callable[..., Callable[..., T]]
60
+ ) -> Callable[..., Callable[..., T]]:
61
+ """
62
+ Decorate an asynchronous function with the throttling mechanism.
63
+
64
+ Args:
65
+ func: The asynchronous function to be throttled.
66
+
67
+ Returns:
68
+ The throttled asynchronous function.
69
+ """
70
+
71
+ @functools.wraps(func)
72
+ async def wrapper(*args, **kwargs) -> Any:
73
+ elapsed = time() - self.last_called
74
+ if elapsed < self.period:
75
+ await asyncio.sleep(self.period - elapsed)
76
+ self.last_called = time()
77
+ return await func(*args, **kwargs)
78
+
79
+ return wrapper
lionagi/libs/parse.py CHANGED
@@ -8,7 +8,8 @@ from lionagi.libs.schema.function_to_schema import function_to_schema
8
8
  from lionagi.libs.validate.fuzzy_match_keys import fuzzy_match_keys
9
9
  from lionagi.libs.validate.fuzzy_validate_mapping import fuzzy_validate_mapping
10
10
  from lionagi.libs.validate.string_similarity import string_similarity
11
- from lionagi.utils import fuzzy_parse_json, to_dict, to_json, to_num
11
+ from lionagi.libs.validate.to_num import to_num
12
+ from lionagi.utils import fuzzy_parse_json, to_dict, to_json
12
13
 
13
14
  validate_keys = fuzzy_match_keys # for backward compatibility
14
15
  validate_mapping = fuzzy_validate_mapping # for backward compatibility
File without changes
@@ -0,0 +1,45 @@
1
+ from lionagi.utils import check_import, is_import_installed
2
+
3
+ _HAS_PDF2IMAGE = is_import_installed("pdf2image")
4
+
5
+
6
+ def pdf_to_images(
7
+ pdf_path: str, output_folder: str, dpi: int = 300, fmt: str = "jpeg"
8
+ ) -> list:
9
+ """
10
+ Convert a PDF file into images, one image per page.
11
+
12
+ Args:
13
+ pdf_path (str): Path to the input PDF file.
14
+ output_folder (str): Directory to save the output images.
15
+ dpi (int): Dots per inch (resolution) for conversion (default: 300).
16
+ fmt (str): Image format (default: 'jpeg'). Use 'png' if preferred.
17
+
18
+ Returns:
19
+ list: A list of file paths for the saved images.
20
+ """
21
+ if not _HAS_PDF2IMAGE:
22
+ raise ModuleNotFoundError(
23
+ "pdf2image is not installed, please install it with `pip install lionagi[unstructured]`"
24
+ )
25
+
26
+ import os
27
+
28
+ convert_from_path = check_import(
29
+ "pdf2image", import_name="convert_from_path"
30
+ )
31
+
32
+ # Ensure the output folder exists
33
+ os.makedirs(output_folder, exist_ok=True)
34
+
35
+ # Convert PDF to a list of PIL Image objects
36
+ images = convert_from_path(pdf_path, dpi=dpi)
37
+
38
+ saved_paths = []
39
+ for i, image in enumerate(images):
40
+ # Construct the output file name
41
+ image_file = os.path.join(output_folder, f"page_{i + 1}.{fmt}")
42
+ image.save(image_file, fmt.upper())
43
+ saved_paths.append(image_file)
44
+
45
+ return saved_paths
@@ -0,0 +1,33 @@
1
+ from pathlib import Path
2
+
3
+ from lionagi.utils import is_import_installed
4
+
5
+ _HAS_OPENCV = is_import_installed("cv2")
6
+
7
+
8
+ __all__ = ("read_image_to_base64",)
9
+
10
+
11
+ def read_image_to_base64(image_path: str | Path) -> str:
12
+ if not _HAS_OPENCV:
13
+ raise ModuleNotFoundError(
14
+ "OpenCV is not installed, please install it with `pip install lionagi[unstructured]`"
15
+ )
16
+
17
+ import base64
18
+
19
+ import cv2
20
+
21
+ image_path = str(image_path)
22
+ image = cv2.imread(image_path, cv2.COLOR_BGR2RGB)
23
+
24
+ if image is None:
25
+ raise ValueError(f"Could not read image from path: {image_path}")
26
+
27
+ file_extension = "." + image_path.split(".")[-1]
28
+
29
+ success, buffer = cv2.imencode(file_extension, image)
30
+ if not success:
31
+ raise ValueError(f"Could not encode image to {file_extension} format.")
32
+ encoded_image = base64.b64encode(buffer).decode("utf-8")
33
+ return encoded_image