lionagi 0.18.0__py3-none-any.whl → 0.18.2__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 (93) hide show
  1. lionagi/__init__.py +102 -59
  2. lionagi/_errors.py +0 -5
  3. lionagi/adapters/spec_adapters/__init__.py +9 -0
  4. lionagi/adapters/spec_adapters/_protocol.py +236 -0
  5. lionagi/adapters/spec_adapters/pydantic_field.py +158 -0
  6. lionagi/fields.py +83 -0
  7. lionagi/ln/__init__.py +3 -1
  8. lionagi/ln/_async_call.py +2 -2
  9. lionagi/ln/concurrency/primitives.py +4 -4
  10. lionagi/ln/concurrency/task.py +1 -0
  11. lionagi/ln/fuzzy/_fuzzy_match.py +2 -2
  12. lionagi/ln/types/__init__.py +51 -0
  13. lionagi/ln/types/_sentinel.py +154 -0
  14. lionagi/ln/{types.py → types/base.py} +108 -168
  15. lionagi/ln/types/operable.py +221 -0
  16. lionagi/ln/types/spec.py +441 -0
  17. lionagi/models/field_model.py +69 -7
  18. lionagi/models/hashable_model.py +2 -3
  19. lionagi/models/model_params.py +4 -3
  20. lionagi/operations/ReAct/ReAct.py +1 -1
  21. lionagi/operations/act/act.py +3 -3
  22. lionagi/operations/builder.py +5 -7
  23. lionagi/operations/fields.py +380 -0
  24. lionagi/operations/flow.py +4 -6
  25. lionagi/operations/node.py +4 -4
  26. lionagi/operations/operate/operate.py +123 -89
  27. lionagi/operations/operate/operative.py +198 -0
  28. lionagi/operations/operate/step.py +203 -0
  29. lionagi/operations/select/select.py +1 -1
  30. lionagi/operations/select/utils.py +7 -1
  31. lionagi/operations/types.py +7 -7
  32. lionagi/protocols/action/manager.py +5 -6
  33. lionagi/protocols/contracts.py +2 -2
  34. lionagi/protocols/generic/__init__.py +22 -0
  35. lionagi/protocols/generic/element.py +36 -127
  36. lionagi/protocols/generic/pile.py +9 -10
  37. lionagi/protocols/generic/progression.py +23 -22
  38. lionagi/protocols/graph/edge.py +6 -5
  39. lionagi/protocols/ids.py +6 -49
  40. lionagi/protocols/messages/__init__.py +3 -1
  41. lionagi/protocols/messages/base.py +7 -6
  42. lionagi/protocols/messages/instruction.py +0 -1
  43. lionagi/protocols/messages/message.py +2 -2
  44. lionagi/protocols/types.py +1 -11
  45. lionagi/service/connections/__init__.py +3 -0
  46. lionagi/service/connections/providers/claude_code_cli.py +3 -2
  47. lionagi/service/hooks/_types.py +1 -1
  48. lionagi/service/hooks/_utils.py +1 -1
  49. lionagi/service/hooks/hook_event.py +3 -8
  50. lionagi/service/hooks/hook_registry.py +5 -5
  51. lionagi/service/hooks/hooked_event.py +61 -1
  52. lionagi/service/imodel.py +24 -20
  53. lionagi/service/third_party/claude_code.py +1 -2
  54. lionagi/service/third_party/openai_models.py +24 -22
  55. lionagi/service/token_calculator.py +1 -94
  56. lionagi/session/branch.py +26 -228
  57. lionagi/session/session.py +5 -90
  58. lionagi/version.py +1 -1
  59. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/METADATA +6 -5
  60. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/RECORD +62 -82
  61. lionagi/fields/__init__.py +0 -47
  62. lionagi/fields/action.py +0 -188
  63. lionagi/fields/base.py +0 -153
  64. lionagi/fields/code.py +0 -239
  65. lionagi/fields/file.py +0 -234
  66. lionagi/fields/instruct.py +0 -135
  67. lionagi/fields/reason.py +0 -55
  68. lionagi/fields/research.py +0 -52
  69. lionagi/operations/brainstorm/__init__.py +0 -2
  70. lionagi/operations/brainstorm/brainstorm.py +0 -498
  71. lionagi/operations/brainstorm/prompt.py +0 -11
  72. lionagi/operations/instruct/__init__.py +0 -2
  73. lionagi/operations/instruct/instruct.py +0 -28
  74. lionagi/operations/plan/__init__.py +0 -6
  75. lionagi/operations/plan/plan.py +0 -386
  76. lionagi/operations/plan/prompt.py +0 -25
  77. lionagi/operations/utils.py +0 -45
  78. lionagi/protocols/forms/__init__.py +0 -2
  79. lionagi/protocols/forms/base.py +0 -85
  80. lionagi/protocols/forms/flow.py +0 -79
  81. lionagi/protocols/forms/form.py +0 -86
  82. lionagi/protocols/forms/report.py +0 -48
  83. lionagi/protocols/mail/__init__.py +0 -2
  84. lionagi/protocols/mail/exchange.py +0 -220
  85. lionagi/protocols/mail/mail.py +0 -51
  86. lionagi/protocols/mail/mailbox.py +0 -103
  87. lionagi/protocols/mail/manager.py +0 -218
  88. lionagi/protocols/mail/package.py +0 -101
  89. lionagi/protocols/operatives/__init__.py +0 -2
  90. lionagi/protocols/operatives/operative.py +0 -362
  91. lionagi/protocols/operatives/step.py +0 -227
  92. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/WHEEL +0 -0
  93. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/licenses/LICENSE +0 -0
lionagi/fields.py ADDED
@@ -0,0 +1,83 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from .operations.fields import (
5
+ ActionRequestModel,
6
+ ActionResponseModel,
7
+ Instruct,
8
+ Reason,
9
+ get_default_field,
10
+ )
11
+
12
+ _lazy_imports = {}
13
+
14
+
15
+ def __getattr__(name: str):
16
+ if name in _lazy_imports:
17
+ return _lazy_imports[name]
18
+
19
+ if name == "ActionRequestModel":
20
+ from .operations.fields import ActionRequestModel
21
+
22
+ _lazy_imports[name] = ActionRequestModel
23
+ return ActionRequestModel
24
+
25
+ if name == "ActionResponseModel":
26
+ from .operations.fields import ActionResponseModel
27
+
28
+ _lazy_imports[name] = ActionResponseModel
29
+ return ActionResponseModel
30
+
31
+ if name == "Instruct":
32
+ from .operations.fields import Instruct
33
+
34
+ _lazy_imports[name] = Instruct
35
+ return Instruct
36
+
37
+ if name == "Reason":
38
+ from .operations.fields import Reason
39
+
40
+ _lazy_imports[name] = Reason
41
+ return Reason
42
+
43
+ from .operations.fields import get_default_field
44
+
45
+ if name == "get_default_field":
46
+
47
+ _lazy_imports[name] = get_default_field
48
+ return get_default_field
49
+
50
+ if name == "ACTION_REQUESTS_FIELD":
51
+ return get_default_field("action_requests")
52
+
53
+ if name == "ACTION_RESPONSES_FIELD":
54
+ return get_default_field("action_responses")
55
+
56
+ if name == "ACTION_REQUIRED_FIELD":
57
+ return get_default_field("action_required")
58
+
59
+ if name == "INSTRUCT_FIELD":
60
+ return get_default_field("instruct")
61
+
62
+ if name == "LIST_INSTRUCT_FIELD_MODEL":
63
+ return get_default_field("instruct", listable=True)
64
+
65
+ if name == "REASON_FIELD":
66
+ return get_default_field("reason")
67
+
68
+ raise AttributeError(f"module {__name__} has no attribute {name}")
69
+
70
+
71
+ __all__ = (
72
+ "ACTION_REQUESTS_FIELD",
73
+ "ACTION_RESPONSES_FIELD",
74
+ "ACTION_REQUIRED_FIELD",
75
+ "INSTRUCT_FIELD",
76
+ "LIST_INSTRUCT_FIELD_MODEL",
77
+ "REASON_FIELD",
78
+ "ActionRequestModel",
79
+ "ActionResponseModel",
80
+ "Instruct",
81
+ "Reason",
82
+ "get_default_field",
83
+ )
lionagi/ln/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- from ._async_call import alcall, bcall
1
+ from ._async_call import AlcallParams, BcallParams, alcall, bcall
2
2
  from ._hash import hash_dict
3
3
  from ._json_dump import (
4
4
  get_orjson_default,
@@ -80,4 +80,6 @@ __all__ = (
80
80
  "not_sentinel",
81
81
  "to_dict",
82
82
  "fuzzy_validate_mapping",
83
+ "AlcallParams",
84
+ "BcallParams",
83
85
  )
lionagi/ln/_async_call.py CHANGED
@@ -15,7 +15,7 @@ from .concurrency import (
15
15
  is_coro_func,
16
16
  move_on_after,
17
17
  )
18
- from .types import Params, T, Unset, not_sentinel
18
+ from .types import ModelConfig, Params, T, Unset, not_sentinel
19
19
 
20
20
  _INITIALIZED = False
21
21
  _MODEL_LIKE = None
@@ -262,7 +262,7 @@ async def bcall(
262
262
  @dataclass(slots=True, init=False, frozen=True)
263
263
  class AlcallParams(Params):
264
264
  # ClassVar attributes
265
- _none_as_sentinel: ClassVar[bool] = True
265
+ _config: ClassVar[ModelConfig] = ModelConfig(none_as_sentinel=True)
266
266
  _func: ClassVar[Any] = alcall
267
267
 
268
268
  # input processing
@@ -142,15 +142,15 @@ class CapacityLimiter:
142
142
  """Get the number of currently available tokens."""
143
143
  return self._lim.available_tokens
144
144
 
145
- def acquire_on_behalf_of(self, borrower: object) -> None:
146
- """Synchronously acquire capacity on behalf of another object.
145
+ async def acquire_on_behalf_of(self, borrower: object) -> None:
146
+ """Asynchronously acquire capacity on behalf of another object.
147
147
 
148
148
  For resource pooling where the acquirer differs from the releaser.
149
149
 
150
150
  Args:
151
151
  borrower: Object that will be responsible for releasing.
152
152
  """
153
- self._lim.acquire_on_behalf_of(borrower)
153
+ await self._lim.acquire_on_behalf_of(borrower)
154
154
 
155
155
  def release_on_behalf_of(self, borrower: object) -> None:
156
156
  """Release capacity that was acquired on behalf of an object.
@@ -326,6 +326,6 @@ class Condition:
326
326
  """Wake up all tasks waiting on this condition."""
327
327
  self._condition.notify_all()
328
328
 
329
- def statistics(self) -> anyio.abc.ConditionStatistics:
329
+ def statistics(self) -> anyio.ConditionStatistics:
330
330
  """Return statistics about waiting tasks."""
331
331
  return self._condition.statistics()
@@ -7,6 +7,7 @@ from contextlib import asynccontextmanager
7
7
  from typing import Any, TypeVar
8
8
 
9
9
  import anyio
10
+ import anyio.abc
10
11
 
11
12
  T = TypeVar("T")
12
13
  R = TypeVar("R")
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Any, ClassVar, Literal
3
3
 
4
- from ..types import KeysLike, Params, Unset
4
+ from ..types import KeysLike, ModelConfig, Params, Unset
5
5
  from ._string_similarity import (
6
6
  SIMILARITY_ALGO_MAP,
7
7
  SIMILARITY_TYPE,
@@ -152,7 +152,7 @@ def fuzzy_match_keys(
152
152
 
153
153
  @dataclass(slots=True, init=False, frozen=True)
154
154
  class FuzzyMatchKeysParams(Params):
155
- _none_as_sentinel: ClassVar[bool] = False
155
+ _config: ClassVar[ModelConfig] = ModelConfig(none_as_sentinel=False)
156
156
  _func: ClassVar[Any] = fuzzy_match_keys
157
157
 
158
158
  similarity_algo: SIMILARITY_TYPE | SimilarityFunc = "jaro_winkler"
@@ -0,0 +1,51 @@
1
+ from ._sentinel import (
2
+ MaybeSentinel,
3
+ MaybeUndefined,
4
+ MaybeUnset,
5
+ SingletonType,
6
+ T,
7
+ Undefined,
8
+ UndefinedType,
9
+ Unset,
10
+ UnsetType,
11
+ is_sentinel,
12
+ not_sentinel,
13
+ )
14
+ from .base import (
15
+ DataClass,
16
+ Enum,
17
+ KeysDict,
18
+ KeysLike,
19
+ Meta,
20
+ ModelConfig,
21
+ Params,
22
+ )
23
+ from .operable import Operable
24
+ from .spec import CommonMeta, Spec
25
+
26
+ __all__ = (
27
+ # Sentinel types
28
+ "Undefined",
29
+ "Unset",
30
+ "MaybeUndefined",
31
+ "MaybeUnset",
32
+ "MaybeSentinel",
33
+ "SingletonType",
34
+ "UndefinedType",
35
+ "UnsetType",
36
+ "is_sentinel",
37
+ "not_sentinel",
38
+ # Base classes
39
+ "ModelConfig",
40
+ "Enum",
41
+ "Params",
42
+ "DataClass",
43
+ "Meta",
44
+ "KeysDict",
45
+ "KeysLike",
46
+ "T",
47
+ # Spec system
48
+ "Spec",
49
+ "CommonMeta",
50
+ "Operable",
51
+ )
@@ -0,0 +1,154 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Final, Literal, TypeVar, Union
4
+
5
+ __all__ = (
6
+ "Undefined",
7
+ "Unset",
8
+ "MaybeUndefined",
9
+ "MaybeUnset",
10
+ "MaybeSentinel",
11
+ "SingletonType",
12
+ "UndefinedType",
13
+ "UnsetType",
14
+ "is_sentinel",
15
+ "not_sentinel",
16
+ "T",
17
+ )
18
+
19
+ T = TypeVar("T")
20
+
21
+
22
+ class _SingletonMeta(type):
23
+ """Metaclass that guarantees exactly one instance per subclass.
24
+
25
+ This ensures that sentinel values maintain identity across the entire application,
26
+ allowing safe identity checks with 'is' operator.
27
+ """
28
+
29
+ _cache: dict[type, SingletonType] = {}
30
+
31
+ def __call__(cls, *a, **kw):
32
+ if cls not in cls._cache:
33
+ cls._cache[cls] = super().__call__(*a, **kw)
34
+ return cls._cache[cls]
35
+
36
+
37
+ class SingletonType(metaclass=_SingletonMeta):
38
+ """Base class for singleton sentinel types.
39
+
40
+ Provides consistent interface for sentinel values with:
41
+ - Identity preservation across deepcopy
42
+ - Falsy boolean evaluation
43
+ - Clear string representation
44
+ """
45
+
46
+ __slots__: tuple[str, ...] = ()
47
+
48
+ def __deepcopy__(self, memo): # copy & deepcopy both noop
49
+ return self
50
+
51
+ def __copy__(self):
52
+ return self
53
+
54
+ # concrete classes *must* override the two methods below
55
+ def __bool__(self) -> bool: ...
56
+ def __repr__(self) -> str: ...
57
+
58
+
59
+ class UndefinedType(SingletonType):
60
+ """Sentinel for a key or field entirely missing from a namespace.
61
+
62
+ Use this when:
63
+ - A field has never been set
64
+ - A key doesn't exist in a mapping
65
+ - A value is conceptually undefined (not just unset)
66
+
67
+ Example:
68
+ >>> d = {"a": 1}
69
+ >>> d.get("b", Undefined) is Undefined
70
+ True
71
+ """
72
+
73
+ __slots__ = ()
74
+
75
+ def __bool__(self) -> Literal[False]:
76
+ return False
77
+
78
+ def __repr__(self) -> Literal["Undefined"]:
79
+ return "Undefined"
80
+
81
+ def __str__(self) -> Literal["Undefined"]:
82
+ return "Undefined"
83
+
84
+ def __reduce__(self):
85
+ """Ensure pickle preservation of singleton identity."""
86
+ return "Undefined"
87
+
88
+
89
+ class UnsetType(SingletonType):
90
+ """Sentinel for a key present but value not yet provided.
91
+
92
+ Use this when:
93
+ - A parameter exists but hasn't been given a value
94
+ - Distinguishing between None and "not provided"
95
+ - API parameters that are optional but need explicit handling
96
+
97
+ Example:
98
+ >>> def func(param=Unset):
99
+ ... if param is not Unset:
100
+ ... # param was explicitly provided
101
+ ... process(param)
102
+ """
103
+
104
+ __slots__ = ()
105
+
106
+ def __bool__(self) -> Literal[False]:
107
+ return False
108
+
109
+ def __repr__(self) -> Literal["Unset"]:
110
+ return "Unset"
111
+
112
+ def __str__(self) -> Literal["Unset"]:
113
+ return "Unset"
114
+
115
+ def __reduce__(self):
116
+ """Ensure pickle preservation of singleton identity."""
117
+ return "Unset"
118
+
119
+
120
+ Undefined: Final = UndefinedType()
121
+ """A key or field entirely missing from a namespace"""
122
+ Unset: Final = UnsetType()
123
+ """A key present but value not yet provided."""
124
+
125
+ MaybeUndefined = Union[T, UndefinedType]
126
+ MaybeUnset = Union[T, UnsetType]
127
+ MaybeSentinel = Union[T, UndefinedType, UnsetType]
128
+
129
+ _EMPTY_TUPLE = (tuple(), set(), frozenset(), dict(), list(), "")
130
+
131
+
132
+ def is_sentinel(
133
+ value: Any,
134
+ *,
135
+ none_as_sentinel: bool = False,
136
+ empty_as_sentinel: bool = False,
137
+ ) -> bool:
138
+ """Check if a value is any sentinel (Undefined or Unset)."""
139
+ if none_as_sentinel and value is None:
140
+ return True
141
+ if empty_as_sentinel and value in _EMPTY_TUPLE:
142
+ return True
143
+ return value is Undefined or value is Unset
144
+
145
+
146
+ def not_sentinel(
147
+ value: Any, none_as_sentinel: bool = False, empty_as_sentinel: bool = False
148
+ ) -> bool:
149
+ """Check if a value is NOT a sentinel. Useful for filtering operations."""
150
+ return not is_sentinel(
151
+ value,
152
+ none_as_sentinel=none_as_sentinel,
153
+ empty_as_sentinel=empty_as_sentinel,
154
+ )