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.
- lionagi/libs/concurrency.py +1 -0
- lionagi/libs/token_transform/perplexity.py +2 -1
- lionagi/libs/token_transform/symbolic_compress_context.py +8 -7
- lionagi/ln/__init__.py +49 -0
- lionagi/ln/_async_call.py +293 -0
- lionagi/ln/_list_call.py +129 -0
- lionagi/ln/_models.py +126 -0
- lionagi/ln/_to_list.py +175 -0
- lionagi/ln/_types.py +146 -0
- lionagi/{libs → ln}/concurrency/__init__.py +4 -2
- lionagi/ln/concurrency/utils.py +14 -0
- lionagi/models/hashable_model.py +1 -2
- lionagi/operations/brainstorm/brainstorm.py +2 -1
- lionagi/operations/flow.py +3 -3
- lionagi/operations/plan/plan.py +3 -3
- lionagi/protocols/generic/pile.py +1 -1
- lionagi/service/hooks/_types.py +2 -2
- lionagi/session/branch.py +4 -2
- lionagi/utils.py +90 -510
- lionagi/version.py +1 -1
- {lionagi-0.14.11.dist-info → lionagi-0.15.0.dist-info}/METADATA +4 -4
- {lionagi-0.14.11.dist-info → lionagi-0.15.0.dist-info}/RECORD +32 -26
- lionagi/libs/hash/__init__.py +0 -3
- lionagi/libs/hash/manager.py +0 -26
- /lionagi/{libs/hash/hash_dict.py → ln/_hash.py} +0 -0
- /lionagi/{libs → ln}/concurrency/cancel.py +0 -0
- /lionagi/{libs → ln}/concurrency/errors.py +0 -0
- /lionagi/{libs → ln}/concurrency/patterns.py +0 -0
- /lionagi/{libs → ln}/concurrency/primitives.py +0 -0
- /lionagi/{libs → ln}/concurrency/resource_tracker.py +0 -0
- /lionagi/{libs → ln}/concurrency/task.py +0 -0
- /lionagi/{libs → ln}/concurrency/throttle.py +0 -0
- {lionagi-0.14.11.dist-info → lionagi-0.15.0.dist-info}/WHEEL +0 -0
- {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)
|
lionagi/models/hashable_model.py
CHANGED
@@ -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
|
19
|
+
from lionagi.utils import to_list
|
19
20
|
|
20
21
|
from ..utils import prepare_instruct, prepare_session
|
21
22
|
from .prompt import PROMPT
|
lionagi/operations/flow.py
CHANGED
@@ -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.
|
16
|
-
from lionagi.
|
17
|
-
from lionagi.
|
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
|
lionagi/operations/plan/plan.py
CHANGED
@@ -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
|
-
|
370
|
-
|
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[
|
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:
|
lionagi/service/hooks/_types.py
CHANGED
@@ -8,7 +8,7 @@ from typing import TypeVar
|
|
8
8
|
|
9
9
|
from typing_extensions import TypedDict
|
10
10
|
|
11
|
-
from lionagi.utils import
|
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(
|
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
|
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
|
1273
|
+
return await alcall_legacy(action_request, self._act, **kwargs)
|
1272
1274
|
|
1273
1275
|
async def _sequential_act(
|
1274
1276
|
self,
|