lionagi 0.14.11__py3-none-any.whl → 0.15.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 (34) hide show
  1. lionagi/libs/concurrency.py +1 -0
  2. lionagi/libs/token_transform/perplexity.py +2 -1
  3. lionagi/libs/token_transform/symbolic_compress_context.py +8 -7
  4. lionagi/ln/__init__.py +49 -0
  5. lionagi/ln/_async_call.py +293 -0
  6. lionagi/ln/_list_call.py +129 -0
  7. lionagi/ln/_models.py +126 -0
  8. lionagi/ln/_to_list.py +175 -0
  9. lionagi/ln/_types.py +146 -0
  10. lionagi/{libs → ln}/concurrency/__init__.py +4 -2
  11. lionagi/ln/concurrency/utils.py +14 -0
  12. lionagi/models/hashable_model.py +1 -2
  13. lionagi/operations/brainstorm/brainstorm.py +2 -1
  14. lionagi/operations/flow.py +3 -3
  15. lionagi/operations/plan/plan.py +3 -3
  16. lionagi/protocols/generic/pile.py +1 -1
  17. lionagi/service/hooks/_types.py +2 -2
  18. lionagi/session/branch.py +4 -2
  19. lionagi/utils.py +90 -510
  20. lionagi/version.py +1 -1
  21. {lionagi-0.14.11.dist-info → lionagi-0.15.0.dist-info}/METADATA +4 -4
  22. {lionagi-0.14.11.dist-info → lionagi-0.15.0.dist-info}/RECORD +32 -26
  23. lionagi/libs/hash/__init__.py +0 -3
  24. lionagi/libs/hash/manager.py +0 -26
  25. /lionagi/{libs/hash/hash_dict.py → ln/_hash.py} +0 -0
  26. /lionagi/{libs → ln}/concurrency/cancel.py +0 -0
  27. /lionagi/{libs → ln}/concurrency/errors.py +0 -0
  28. /lionagi/{libs → ln}/concurrency/patterns.py +0 -0
  29. /lionagi/{libs → ln}/concurrency/primitives.py +0 -0
  30. /lionagi/{libs → ln}/concurrency/resource_tracker.py +0 -0
  31. /lionagi/{libs → ln}/concurrency/task.py +0 -0
  32. /lionagi/{libs → ln}/concurrency/throttle.py +0 -0
  33. {lionagi-0.14.11.dist-info → lionagi-0.15.0.dist-info}/WHEEL +0 -0
  34. {lionagi-0.14.11.dist-info → lionagi-0.15.0.dist-info}/licenses/LICENSE +0 -0
lionagi/ln/_to_list.py ADDED
@@ -0,0 +1,175 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum as _Enum
3
+ from typing import Any, ClassVar, Iterable, Mapping
4
+
5
+ from pydantic import BaseModel
6
+ from pydantic_core import PydanticUndefinedType
7
+
8
+ from ._hash import hash_dict
9
+ from ._models import Params
10
+ from ._types import UndefinedType, UnsetType
11
+
12
+ __all__ = ("to_list", "ToListParams")
13
+
14
+
15
+ _SKIP_TYPE = (str, bytes, bytearray, Mapping, BaseModel, _Enum)
16
+ _TUPLE_SET_TYPES = (tuple, set, frozenset)
17
+ _SKIP_TUPLE_SET = (*_SKIP_TYPE, *_TUPLE_SET_TYPES)
18
+ _SINGLETONE_TYPES = (UndefinedType, UnsetType, PydanticUndefinedType)
19
+ _BYTE_LIKE_TYPES = (str, bytes, bytearray)
20
+
21
+
22
+ def to_list(
23
+ input_: Any,
24
+ /,
25
+ *,
26
+ flatten: bool = False,
27
+ dropna: bool = False,
28
+ unique: bool = False,
29
+ use_values: bool = False,
30
+ flatten_tuple_set: bool = False,
31
+ ) -> list:
32
+ """Convert input to a list with optional transformations.
33
+
34
+ Transforms various input types into a list with configurable processing
35
+ options for flattening, filtering, and value extraction.
36
+
37
+ Args:
38
+ input_: Value to convert to list.
39
+ flatten: If True, recursively flatten nested iterables.
40
+ dropna: If True, remove None and undefined values.
41
+ unique: If True, remove duplicates (requires flatten=True).
42
+ use_values: If True, extract values from enums/mappings.
43
+ flatten_tuple_items: If True, include tuples in flattening.
44
+ flatten_set_items: If True, include sets in flattening.
45
+
46
+ Raises:
47
+ ValueError: If unique=True is used without flatten=True.
48
+ """
49
+
50
+ def _process_list(
51
+ lst: list[Any],
52
+ flatten: bool,
53
+ dropna: bool,
54
+ ) -> list[Any]:
55
+ """Process list according to flatten and dropna options.
56
+
57
+ Args:
58
+ lst: Input list to process.
59
+ flatten: Whether to flatten nested iterables.
60
+ dropna: Whether to remove None/undefined values.
61
+ """
62
+ result = []
63
+ skip_types = _SKIP_TYPE if flatten_tuple_set else _SKIP_TUPLE_SET
64
+
65
+ for item in lst:
66
+ if dropna and (
67
+ item is None or isinstance(item, _SINGLETONE_TYPES)
68
+ ):
69
+ continue
70
+
71
+ is_iterable = isinstance(item, Iterable)
72
+ should_skip = isinstance(item, skip_types)
73
+
74
+ if is_iterable and not should_skip:
75
+ item_list = list(item)
76
+ if flatten:
77
+ result.extend(_process_list(item_list, flatten, dropna))
78
+ else:
79
+ result.append(_process_list(item_list, flatten, dropna))
80
+ else:
81
+ result.append(item)
82
+
83
+ return result
84
+
85
+ def _to_list_type(input_: Any, use_values: bool) -> list[Any]:
86
+ """Convert input to initial list based on type.
87
+
88
+ Args:
89
+ input_: Value to convert to list.
90
+ use_values: Whether to extract values from containers.
91
+ """
92
+ if input_ is None or isinstance(input_, _SINGLETONE_TYPES):
93
+ return []
94
+
95
+ if isinstance(input_, list):
96
+ return input_
97
+
98
+ if isinstance(input_, type) and issubclass(input_, _Enum):
99
+ members = input_.__members__.values()
100
+ return (
101
+ [member.value for member in members]
102
+ if use_values
103
+ else list(members)
104
+ )
105
+
106
+ if isinstance(input_, _BYTE_LIKE_TYPES):
107
+ return list(input_) if use_values else [input_]
108
+
109
+ if isinstance(input_, Mapping):
110
+ return (
111
+ list(input_.values())
112
+ if use_values and hasattr(input_, "values")
113
+ else [input_]
114
+ )
115
+
116
+ if isinstance(input_, BaseModel):
117
+ return [input_]
118
+
119
+ if isinstance(input_, Iterable) and not isinstance(
120
+ input_, _BYTE_LIKE_TYPES
121
+ ):
122
+ return list(input_)
123
+
124
+ return [input_]
125
+
126
+ if unique and not flatten:
127
+ raise ValueError("unique=True requires flatten=True")
128
+
129
+ initial_list = _to_list_type(input_, use_values=use_values)
130
+ processed = _process_list(initial_list, flatten=flatten, dropna=dropna)
131
+
132
+ if unique:
133
+ seen = set()
134
+ out = []
135
+ try:
136
+ return [x for x in processed if not (x in seen or seen.add(x))]
137
+ except TypeError:
138
+ for i in processed:
139
+ hash_value = None
140
+ try:
141
+ hash_value = hash(i)
142
+ except TypeError:
143
+ if isinstance(i, (BaseModel, Mapping)):
144
+ hash_value = hash_dict(i)
145
+ else:
146
+ raise ValueError(
147
+ "Unhashable type encountered in list unique value processing."
148
+ )
149
+ if hash_value not in seen:
150
+ seen.add(hash_value)
151
+ out.append(i)
152
+ return out
153
+
154
+ return processed
155
+
156
+
157
+ @dataclass(slots=True, frozen=True, init=False)
158
+ class ToListParams(Params):
159
+ _func: ClassVar[Any] = to_list
160
+
161
+ flatten: bool
162
+ """If True, recursively flatten nested iterables."""
163
+ dropna: bool
164
+ """If True, remove None and undefined values."""
165
+ unique: bool
166
+ """If True, remove duplicates (requires flatten=True)."""
167
+ use_values: bool
168
+ """If True, extract values from enums/mappings."""
169
+ flatten_tuple_set: bool
170
+ """If True, include tuples and sets in flattening."""
171
+
172
+ def __call__(self, input_: Any, **kw) -> list:
173
+ """Convert parameters to a list."""
174
+ partial = self.as_partial()
175
+ return partial(input_, **kw)
lionagi/ln/_types.py ADDED
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum as _Enum
4
+ from typing import Any, Final, Literal, TypeVar, Union
5
+
6
+ from typing_extensions import TypedDict
7
+
8
+ __all__ = (
9
+ "Undefined",
10
+ "Unset",
11
+ "MaybeUndefined",
12
+ "MaybeUnset",
13
+ "MaybeSentinel",
14
+ "SingletonType",
15
+ "UndefinedType",
16
+ "UnsetType",
17
+ "KeysDict",
18
+ "T",
19
+ "Enum",
20
+ "is_sentinel",
21
+ "not_sentinel",
22
+ )
23
+
24
+ T = TypeVar("T")
25
+
26
+
27
+ class _SingletonMeta(type):
28
+ """Metaclass that guarantees exactly one instance per subclass.
29
+
30
+ This ensures that sentinel values maintain identity across the entire application,
31
+ allowing safe identity checks with 'is' operator.
32
+ """
33
+
34
+ _cache: dict[type, "SingletonType"] = {}
35
+
36
+ def __call__(cls, *a, **kw):
37
+ if cls not in cls._cache:
38
+ cls._cache[cls] = super().__call__(*a, **kw)
39
+ return cls._cache[cls]
40
+
41
+
42
+ class SingletonType(metaclass=_SingletonMeta):
43
+ """Base class for singleton sentinel types.
44
+
45
+ Provides consistent interface for sentinel values with:
46
+ - Identity preservation across deepcopy
47
+ - Falsy boolean evaluation
48
+ - Clear string representation
49
+ """
50
+
51
+ __slots__: tuple[str, ...] = ()
52
+
53
+ def __deepcopy__(self, memo): # copy & deepcopy both noop
54
+ return self
55
+
56
+ def __copy__(self):
57
+ return self
58
+
59
+ # concrete classes *must* override the two methods below
60
+ def __bool__(self) -> bool: ...
61
+ def __repr__(self) -> str: ...
62
+
63
+
64
+ class UndefinedType(SingletonType):
65
+ """Sentinel for a key or field entirely missing from a namespace.
66
+
67
+ Use this when:
68
+ - A field has never been set
69
+ - A key doesn't exist in a mapping
70
+ - A value is conceptually undefined (not just unset)
71
+
72
+ Example:
73
+ >>> d = {"a": 1}
74
+ >>> d.get("b", Undefined) is Undefined
75
+ True
76
+ """
77
+
78
+ __slots__ = ()
79
+
80
+ def __bool__(self) -> Literal[False]:
81
+ return False
82
+
83
+ def __repr__(self) -> Literal["Undefined"]:
84
+ return "Undefined"
85
+
86
+ def __str__(self) -> Literal["Undefined"]:
87
+ return "Undefined"
88
+
89
+
90
+ class UnsetType(SingletonType):
91
+ """Sentinel for a key present but value not yet provided.
92
+
93
+ Use this when:
94
+ - A parameter exists but hasn't been given a value
95
+ - Distinguishing between None and "not provided"
96
+ - API parameters that are optional but need explicit handling
97
+
98
+ Example:
99
+ >>> def func(param=Unset):
100
+ ... if param is not Unset:
101
+ ... # param was explicitly provided
102
+ ... process(param)
103
+ """
104
+
105
+ __slots__ = ()
106
+
107
+ def __bool__(self) -> Literal[False]:
108
+ return False
109
+
110
+ def __repr__(self) -> Literal["Unset"]:
111
+ return "Unset"
112
+
113
+ def __str__(self) -> Literal["Unset"]:
114
+ return "Unset"
115
+
116
+
117
+ Undefined: Final = UndefinedType()
118
+ """A key or field entirely missing from a namespace"""
119
+ Unset: Final = UnsetType()
120
+ """A key present but value not yet provided."""
121
+
122
+ MaybeUndefined = Union[T, UndefinedType]
123
+ MaybeUnset = Union[T, UnsetType]
124
+ MaybeSentinel = Union[T, UndefinedType, UnsetType]
125
+
126
+
127
+ def is_sentinel(value: Any) -> bool:
128
+ """Check if a value is any sentinel (Undefined or Unset)."""
129
+ return value is Undefined or value is Unset
130
+
131
+
132
+ def not_sentinel(value: Any) -> bool:
133
+ """Check if a value is NOT a sentinel. Useful for filtering operations."""
134
+ return value is not Undefined and value is not Unset
135
+
136
+
137
+ class Enum(_Enum):
138
+ @classmethod
139
+ def allowed(cls) -> tuple[str, ...]:
140
+ return tuple(e.value for e in cls)
141
+
142
+
143
+ class KeysDict(TypedDict, total=False):
144
+ """TypedDict for keys dictionary."""
145
+
146
+ key: Any # Represents any key-type pair
@@ -22,8 +22,9 @@ from .resource_tracker import (
22
22
  untrack_resource,
23
23
  )
24
24
  from .task import TaskGroup, create_task_group
25
+ from .utils import is_coro_func
25
26
 
26
- __all__ = [
27
+ __all__ = (
27
28
  "TaskGroup",
28
29
  "create_task_group",
29
30
  "CancelScope",
@@ -46,4 +47,5 @@ __all__ = [
46
47
  "untrack_resource",
47
48
  "cleanup_check",
48
49
  "get_global_tracker",
49
- ]
50
+ "is_coro_func",
51
+ )
@@ -0,0 +1,14 @@
1
+ import asyncio
2
+ from functools import lru_cache
3
+ from typing import Any, Callable
4
+
5
+ __all__ = ("is_coro_func",)
6
+
7
+
8
+ @lru_cache(maxsize=None)
9
+ def _is_coro_func(func: Callable[..., Any]) -> bool:
10
+ return asyncio.iscoroutinefunction(func)
11
+
12
+
13
+ def is_coro_func(func: Callable[..., Any]) -> bool:
14
+ return _is_coro_func(func)
@@ -1,8 +1,7 @@
1
1
  from pydantic import BaseModel
2
2
  from typing_extensions import Self
3
3
 
4
- from lionagi.libs.hash.hash_dict import hash_dict
5
- from lionagi.utils import UNDEFINED
4
+ from lionagi.utils import UNDEFINED, hash_dict
6
5
 
7
6
 
8
7
  class HashableModel(BaseModel):
@@ -12,10 +12,11 @@ from lionagi.fields.instruct import (
12
12
  Instruct,
13
13
  InstructResponse,
14
14
  )
15
+ from lionagi.ln import alcall
15
16
  from lionagi.protocols.generic.element import ID
16
17
  from lionagi.session.branch import Branch
17
18
  from lionagi.session.session import Session
18
- from lionagi.utils import alcall, to_list
19
+ from lionagi.utils import to_list
19
20
 
20
21
  from ..utils import prepare_instruct, prepare_session
21
22
  from .prompt import PROMPT
@@ -12,9 +12,9 @@ using Events for synchronization and CapacityLimiter for concurrency control.
12
12
  import os
13
13
  from typing import Any
14
14
 
15
- from lionagi.libs.concurrency.primitives import CapacityLimiter
16
- from lionagi.libs.concurrency.primitives import Event as ConcurrencyEvent
17
- from lionagi.libs.concurrency.task import create_task_group
15
+ from lionagi.ln.concurrency.primitives import CapacityLimiter
16
+ from lionagi.ln.concurrency.primitives import Event as ConcurrencyEvent
17
+ from lionagi.ln.concurrency.task import create_task_group
18
18
  from lionagi.operations.node import Operation
19
19
  from lionagi.protocols.types import EventStatus, Graph
20
20
  from lionagi.session.branch import Branch
@@ -11,10 +11,10 @@ from lionagi.fields.instruct import (
11
11
  Instruct,
12
12
  InstructResponse,
13
13
  )
14
+ from lionagi.ln import alcall
14
15
  from lionagi.protocols.types import ID
15
16
  from lionagi.session.branch import Branch
16
17
  from lionagi.session.session import Session
17
- from lionagi.utils import alcall
18
18
 
19
19
  from ..utils import prepare_instruct, prepare_session
20
20
  from .prompt import EXPANSION_PROMPT, PLAN_PROMPT
@@ -366,8 +366,8 @@ async def plan(
366
366
  parallel_chunk_results = await alcall(
367
367
  all_chunks,
368
368
  execute_chunk_sequentially,
369
- flatten=True,
370
- dropna=True,
369
+ output_flatten=True,
370
+ output_dropna=True,
371
371
  )
372
372
 
373
373
  out.execute = parallel_chunk_results
@@ -54,7 +54,7 @@ def async_synchronized(func: Callable):
54
54
  return wrapper
55
55
 
56
56
 
57
- class Pile(Element, Collective[E], Generic[E], Adaptable, AsyncAdaptable):
57
+ class Pile(Element, Collective[T], Generic[T], Adaptable, AsyncAdaptable):
58
58
  """Thread-safe async-compatible, ordered collection of elements.
59
59
 
60
60
  The Pile class provides a thread-safe, async-compatible collection with:
@@ -8,7 +8,7 @@ from typing import TypeVar
8
8
 
9
9
  from typing_extensions import TypedDict
10
10
 
11
- from lionagi.utils import StringEnum
11
+ from lionagi.utils import Enum
12
12
 
13
13
  SC = TypeVar("SC") # streaming chunk type
14
14
 
@@ -21,7 +21,7 @@ __all__ = (
21
21
  )
22
22
 
23
23
 
24
- class HookEventTypes(StringEnum):
24
+ class HookEventTypes(str, Enum):
25
25
  PreEventCreate = "pre_event_create"
26
26
  PreInvokation = "pre_invokation"
27
27
  PostInvokation = "post_invokation"
lionagi/session/branch.py CHANGED
@@ -47,7 +47,9 @@ from lionagi.service.connections.endpoint import Endpoint
47
47
  from lionagi.service.types import iModel, iModelManager
48
48
  from lionagi.settings import Settings
49
49
  from lionagi.tools.base import LionTool
50
- from lionagi.utils import UNDEFINED, alcall, bcall, copy
50
+ from lionagi.utils import UNDEFINED
51
+ from lionagi.utils import alcall as alcall_legacy
52
+ from lionagi.utils import copy
51
53
 
52
54
  from .prompts import LION_SYSTEM_MESSAGE
53
55
 
@@ -1268,7 +1270,7 @@ class Branch(Element, Communicatable, Relational):
1268
1270
  action_request: ActionRequest | BaseModel | dict,
1269
1271
  **kwargs,
1270
1272
  ) -> list:
1271
- return await alcall(action_request, self._act, **kwargs)
1273
+ return await alcall_legacy(action_request, self._act, **kwargs)
1272
1274
 
1273
1275
  async def _sequential_act(
1274
1276
  self,