lionagi 0.17.11__py3-none-any.whl → 0.18.1__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 (109) hide show
  1. lionagi/_errors.py +0 -5
  2. lionagi/fields.py +83 -0
  3. lionagi/libs/schema/minimal_yaml.py +98 -0
  4. lionagi/ln/__init__.py +3 -1
  5. lionagi/ln/concurrency/primitives.py +4 -4
  6. lionagi/ln/concurrency/task.py +1 -0
  7. lionagi/ln/types.py +32 -5
  8. lionagi/models/field_model.py +21 -4
  9. lionagi/models/hashable_model.py +2 -3
  10. lionagi/operations/ReAct/ReAct.py +475 -238
  11. lionagi/operations/ReAct/utils.py +3 -0
  12. lionagi/operations/act/act.py +206 -0
  13. lionagi/operations/builder.py +5 -7
  14. lionagi/operations/chat/chat.py +130 -114
  15. lionagi/operations/communicate/communicate.py +101 -42
  16. lionagi/operations/fields.py +380 -0
  17. lionagi/operations/flow.py +8 -10
  18. lionagi/operations/interpret/interpret.py +65 -20
  19. lionagi/operations/node.py +4 -4
  20. lionagi/operations/operate/operate.py +216 -108
  21. lionagi/{protocols/operatives → operations/operate}/operative.py +4 -5
  22. lionagi/{protocols/operatives → operations/operate}/step.py +34 -39
  23. lionagi/operations/parse/parse.py +170 -142
  24. lionagi/operations/select/select.py +79 -18
  25. lionagi/operations/select/utils.py +8 -2
  26. lionagi/operations/types.py +119 -23
  27. lionagi/protocols/action/manager.py +5 -6
  28. lionagi/protocols/contracts.py +2 -2
  29. lionagi/protocols/generic/__init__.py +22 -0
  30. lionagi/protocols/generic/element.py +36 -127
  31. lionagi/protocols/generic/log.py +3 -2
  32. lionagi/protocols/generic/pile.py +9 -10
  33. lionagi/protocols/generic/progression.py +23 -22
  34. lionagi/protocols/graph/edge.py +6 -5
  35. lionagi/protocols/ids.py +6 -49
  36. lionagi/protocols/messages/__init__.py +29 -0
  37. lionagi/protocols/messages/action_request.py +86 -184
  38. lionagi/protocols/messages/action_response.py +73 -131
  39. lionagi/protocols/messages/assistant_response.py +130 -159
  40. lionagi/protocols/messages/base.py +31 -22
  41. lionagi/protocols/messages/instruction.py +280 -625
  42. lionagi/protocols/messages/manager.py +112 -62
  43. lionagi/protocols/messages/message.py +87 -197
  44. lionagi/protocols/messages/system.py +52 -123
  45. lionagi/protocols/types.py +1 -13
  46. lionagi/service/connections/__init__.py +3 -0
  47. lionagi/service/connections/endpoint.py +0 -8
  48. lionagi/service/connections/providers/claude_code_cli.py +3 -2
  49. lionagi/service/connections/providers/oai_.py +29 -94
  50. lionagi/service/connections/providers/ollama_.py +3 -2
  51. lionagi/service/hooks/_types.py +1 -1
  52. lionagi/service/hooks/_utils.py +1 -1
  53. lionagi/service/hooks/hook_event.py +3 -8
  54. lionagi/service/hooks/hook_registry.py +5 -5
  55. lionagi/service/hooks/hooked_event.py +63 -3
  56. lionagi/service/imodel.py +24 -20
  57. lionagi/service/third_party/claude_code.py +3 -3
  58. lionagi/service/third_party/openai_models.py +435 -0
  59. lionagi/service/token_calculator.py +1 -94
  60. lionagi/session/branch.py +190 -400
  61. lionagi/session/session.py +8 -99
  62. lionagi/tools/file/reader.py +2 -2
  63. lionagi/version.py +1 -1
  64. {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/METADATA +6 -6
  65. lionagi-0.18.1.dist-info/RECORD +164 -0
  66. lionagi/fields/__init__.py +0 -47
  67. lionagi/fields/action.py +0 -188
  68. lionagi/fields/base.py +0 -153
  69. lionagi/fields/code.py +0 -239
  70. lionagi/fields/file.py +0 -234
  71. lionagi/fields/instruct.py +0 -135
  72. lionagi/fields/reason.py +0 -55
  73. lionagi/fields/research.py +0 -52
  74. lionagi/operations/_act/act.py +0 -86
  75. lionagi/operations/brainstorm/__init__.py +0 -2
  76. lionagi/operations/brainstorm/brainstorm.py +0 -498
  77. lionagi/operations/brainstorm/prompt.py +0 -11
  78. lionagi/operations/instruct/__init__.py +0 -2
  79. lionagi/operations/instruct/instruct.py +0 -28
  80. lionagi/operations/plan/__init__.py +0 -6
  81. lionagi/operations/plan/plan.py +0 -386
  82. lionagi/operations/plan/prompt.py +0 -25
  83. lionagi/operations/utils.py +0 -45
  84. lionagi/protocols/forms/__init__.py +0 -2
  85. lionagi/protocols/forms/base.py +0 -85
  86. lionagi/protocols/forms/flow.py +0 -79
  87. lionagi/protocols/forms/form.py +0 -86
  88. lionagi/protocols/forms/report.py +0 -48
  89. lionagi/protocols/mail/__init__.py +0 -2
  90. lionagi/protocols/mail/exchange.py +0 -220
  91. lionagi/protocols/mail/mail.py +0 -51
  92. lionagi/protocols/mail/mailbox.py +0 -103
  93. lionagi/protocols/mail/manager.py +0 -218
  94. lionagi/protocols/mail/package.py +0 -101
  95. lionagi/protocols/messages/templates/README.md +0 -28
  96. lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
  97. lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
  98. lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
  99. lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
  100. lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
  101. lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
  102. lionagi/protocols/operatives/__init__.py +0 -2
  103. lionagi/service/connections/providers/types.py +0 -28
  104. lionagi/service/third_party/openai_model_names.py +0 -198
  105. lionagi/service/types.py +0 -58
  106. lionagi-0.17.11.dist-info/RECORD +0 -199
  107. /lionagi/operations/{_act → act}/__init__.py +0 -0
  108. {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/WHEEL +0 -0
  109. {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/licenses/LICENSE +0 -0
lionagi/_errors.py CHANGED
@@ -13,7 +13,6 @@ __all__ = (
13
13
  "ObservationError",
14
14
  "ResourceError",
15
15
  "RateLimitError",
16
- "IDError",
17
16
  "RelationError",
18
17
  "OperationError",
19
18
  "ExecutionError",
@@ -127,10 +126,6 @@ class RateLimitError(LionError):
127
126
  object.__setattr__(self, "retry_after", retry_after)
128
127
 
129
128
 
130
- class IDError(LionError):
131
- pass
132
-
133
-
134
129
  class RelationError(LionError):
135
130
  pass
136
131
 
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
+ )
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import orjson
6
+ import yaml
7
+
8
+ # --- YAML Dumper with minimal, readable settings --------------------------------
9
+
10
+
11
+ class MinimalDumper(yaml.SafeDumper):
12
+ # Disable anchors/aliases (&id001, *id001) for repeated objects.
13
+ def ignore_aliases(self, data: Any) -> bool: # type: ignore[override]
14
+ return True
15
+
16
+
17
+ def _represent_str(dumper: yaml.SafeDumper, data: str):
18
+ # Use block scalars for multiline text; plain style otherwise.
19
+ if "\n" in data:
20
+ return dumper.represent_scalar(
21
+ "tag:yaml.org,2002:str", data, style="|"
22
+ )
23
+ return dumper.represent_scalar("tag:yaml.org,2002:str", data)
24
+
25
+
26
+ MinimalDumper.add_representer(str, _represent_str)
27
+
28
+ # --- Optional pruning of empty values -------------------------------------------
29
+
30
+
31
+ def _is_empty(x: Any) -> bool:
32
+ """
33
+ Define 'empty' for pruning. Keeps 0 and False.
34
+ - None or '' (after strip) are empty
35
+ - Empty containers are empty
36
+ """
37
+ if x is None:
38
+ return True
39
+ if isinstance(x, str):
40
+ return x.strip() == ""
41
+ if isinstance(x, dict):
42
+ return len(x) == 0
43
+ if isinstance(x, (list, tuple, set)):
44
+ return len(x) == 0
45
+ # Keep numbers (including 0) and booleans (including False)
46
+ return False
47
+
48
+
49
+ def _prune(x: Any) -> Any:
50
+ """Recursively remove empty leaves and empty containers produced thereby."""
51
+ if isinstance(x, dict):
52
+ pruned = {k: _prune(v) for k, v in x.items() if not _is_empty(v)}
53
+ # Remove keys that became empty after recursion
54
+ return {k: v for k, v in pruned.items() if not _is_empty(v)}
55
+ if isinstance(x, list):
56
+ pruned_list = [_prune(v) for v in x if not _is_empty(v)]
57
+ return [v for v in pruned_list if not _is_empty(v)]
58
+ if isinstance(x, tuple):
59
+ pruned_list = [_prune(v) for v in x if not _is_empty(v)]
60
+ return tuple(v for v in pruned_list if not _is_empty(v))
61
+ if isinstance(x, set):
62
+ pruned_set = {_prune(v) for v in x if not _is_empty(v)}
63
+ return {v for v in pruned_set if not _is_empty(v)}
64
+ return x
65
+
66
+
67
+ # --- Public API ------------------------------------------------------------------
68
+
69
+
70
+ def minimal_yaml(
71
+ value: Any,
72
+ *,
73
+ drop_empties: bool = True,
74
+ indent: int = 2,
75
+ line_width: int = 2**31 - 1, # avoid PyYAML inserting line-wraps
76
+ sort_keys: bool = False,
77
+ ) -> str:
78
+ """
79
+ Convert any Python value (dict/list/scalars) to a minimal, readable YAML string.
80
+ - Lists -> YAML sequences with '- '
81
+ - Dicts -> 'key: value' mappings
82
+ - Multiline strings -> block scalars (|)
83
+ - Optional pruning of empty values (keeps 0 and False)
84
+ - No aliases/anchors
85
+ """
86
+ if isinstance(value, str):
87
+ value = orjson.loads(value)
88
+
89
+ data = _prune(value) if drop_empties else value
90
+ return yaml.dump(
91
+ data,
92
+ Dumper=MinimalDumper,
93
+ default_flow_style=False, # block style
94
+ sort_keys=sort_keys, # preserve insertion order
95
+ allow_unicode=True,
96
+ indent=indent,
97
+ width=line_width,
98
+ )
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
  )
@@ -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")
lionagi/ln/types.py CHANGED
@@ -232,10 +232,13 @@ class Params:
232
232
  dict_.update(kw_)
233
233
  return dict_
234
234
 
235
- def to_dict(self) -> dict[str, str]:
235
+ def to_dict(self, exclude: set[str] = None) -> dict[str, str]:
236
236
  data = {}
237
+ exclude = exclude or set()
237
238
  for k in self.allowed():
238
- if not self._is_sentinel(v := getattr(self, k, Undefined)):
239
+ if k not in exclude and not self._is_sentinel(
240
+ v := getattr(self, k, Undefined)
241
+ ):
239
242
  data[k] = v
240
243
  return data
241
244
 
@@ -249,6 +252,12 @@ class Params:
249
252
  return False
250
253
  return hash(self) == hash(other)
251
254
 
255
+ def with_updates(self, **kwargs: Any) -> DataClass:
256
+ """Return a new instance with updated fields."""
257
+ dict_ = self.to_dict()
258
+ dict_.update(kwargs)
259
+ return type(self)(**dict_)
260
+
252
261
 
253
262
  @dataclass(slots=True)
254
263
  class DataClass:
@@ -296,11 +305,13 @@ class DataClass:
296
305
  for k in self.allowed():
297
306
  _validate_strict(k)
298
307
 
299
- def to_dict(self) -> dict[str, str]:
308
+ def to_dict(self, exclude: set[str] = None) -> dict[str, str]:
300
309
  data = {}
301
- print(self.allowed())
310
+ exclude = exclude or set()
302
311
  for k in type(self).allowed():
303
- if not self._is_sentinel(v := getattr(self, k)):
312
+ if k not in exclude and not self._is_sentinel(
313
+ v := getattr(self, k)
314
+ ):
304
315
  data[k] = v
305
316
  return data
306
317
 
@@ -311,6 +322,22 @@ class DataClass:
311
322
  return True
312
323
  return is_sentinel(value)
313
324
 
325
+ def with_updates(self, **kwargs: Any) -> DataClass:
326
+ """Return a new instance with updated fields."""
327
+ dict_ = self.to_dict()
328
+ dict_.update(kwargs)
329
+ return type(self)(**dict_)
330
+
331
+ def __hash__(self) -> int:
332
+ from ._hash import hash_dict
333
+
334
+ return hash_dict(self.to_dict())
335
+
336
+ def __eq__(self, other: Any) -> bool:
337
+ if not isinstance(other, DataClass):
338
+ return False
339
+ return hash(self) == hash(other)
340
+
314
341
 
315
342
  KeysLike = Sequence[str] | KeysDict
316
343
 
@@ -84,7 +84,7 @@ class FieldModel(Params):
84
84
  base_type: type[Any]
85
85
  metadata: tuple[Meta, ...]
86
86
 
87
- def __init__(self, **kwargs: Any) -> None:
87
+ def __init__(self, base_type: type[Any] = None, **kwargs: Any) -> None:
88
88
  """Initialize FieldModel with legacy compatibility.
89
89
 
90
90
  Handles backward compatibility by converting old-style kwargs to the new
@@ -94,6 +94,8 @@ class FieldModel(Params):
94
94
  **kwargs: Arbitrary keyword arguments, including legacy ones
95
95
  """
96
96
  # Convert legacy kwargs to proper format
97
+ if base_type is not None:
98
+ kwargs["base_type"] = base_type
97
99
  converted = self._convert_kwargs_to_params(**kwargs)
98
100
 
99
101
  # Set fields directly and validate
@@ -513,6 +515,15 @@ class FieldModel(Params):
513
515
  # These are FieldTemplate markers, don't pass to FieldInfo
514
516
  pass
515
517
  else:
518
+ # Filter out unserializable objects from json_schema_extra
519
+ # to avoid Pydantic serialization errors when generating JSON schema
520
+ from pydantic import BaseModel
521
+
522
+ # Skip model classes and other unserializable types
523
+ if isinstance(meta.value, type):
524
+ # Skip type objects (including model classes) - they can't be serialized
525
+ continue
526
+
516
527
  # Any other metadata goes in json_schema_extra
517
528
  if "json_schema_extra" not in field_kwargs:
518
529
  field_kwargs["json_schema_extra"] = {}
@@ -529,7 +540,7 @@ class FieldModel(Params):
529
540
  field_info = PydanticField(**field_kwargs)
530
541
 
531
542
  # Set the annotation from base_type for backward compatibility
532
- field_info.annotation = self.base_type
543
+ field_info.annotation = self.annotation
533
544
 
534
545
  return field_info
535
546
 
@@ -736,8 +747,14 @@ class FieldModel(Params):
736
747
 
737
748
  @property
738
749
  def annotation(self) -> type[Any]:
739
- """Get field annotation (base_type) for backward compatibility."""
740
- return Any if self._is_sentinel(self.base_type) else self.base_type
750
+ if self._is_sentinel(self.base_type):
751
+ return Any
752
+ t_ = self.base_type
753
+ if self.is_listable:
754
+ t_ = list[t_]
755
+ if self.is_nullable:
756
+ t_ = t_ | None
757
+ return t_
741
758
 
742
759
  def to_dict(self) -> dict[str, Any]:
743
760
  """Convert field model to dictionary for backward compatibility.
@@ -94,12 +94,11 @@ class HashableModel(BaseModel):
94
94
  def _get_default_hashable_serializer():
95
95
  global _DEFAULT_HASHABLE_SERIALIZER
96
96
  if _DEFAULT_HASHABLE_SERIALIZER is None:
97
- from lionagi.protocols.ids import Element, IDType
97
+ from lionagi.protocols.ids import Element
98
98
 
99
99
  _DEFAULT_HASHABLE_SERIALIZER = ln.get_orjson_default(
100
- order=[IDType, Element, BaseModel],
100
+ order=[Element, BaseModel],
101
101
  additional={
102
- IDType: lambda o: str(o),
103
102
  Element: lambda o: o.to_dict(),
104
103
  BaseModel: lambda o: o.model_dump(mode="json"),
105
104
  },