lionagi 0.15.8__py3-none-any.whl → 0.15.11__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 (42) hide show
  1. lionagi/__init__.py +4 -6
  2. lionagi/adapters/async_postgres_adapter.py +55 -319
  3. lionagi/libs/file/_utils.py +10 -0
  4. lionagi/libs/file/process.py +16 -13
  5. lionagi/libs/file/save.py +3 -2
  6. lionagi/libs/schema/load_pydantic_model_from_schema.py +2 -1
  7. lionagi/libs/unstructured/pdf_to_image.py +2 -2
  8. lionagi/libs/validate/string_similarity.py +4 -4
  9. lionagi/ln/__init__.py +38 -0
  10. lionagi/ln/_extract_json.py +60 -0
  11. lionagi/ln/_fuzzy_json.py +116 -0
  12. lionagi/ln/_json_dump.py +75 -0
  13. lionagi/ln/_models.py +0 -1
  14. lionagi/models/field_model.py +8 -6
  15. lionagi/operations/__init__.py +3 -0
  16. lionagi/operations/builder.py +10 -0
  17. lionagi/protocols/generic/element.py +56 -53
  18. lionagi/protocols/generic/event.py +46 -67
  19. lionagi/protocols/generic/pile.py +56 -1
  20. lionagi/protocols/generic/progression.py +11 -11
  21. lionagi/protocols/graph/_utils.py +22 -0
  22. lionagi/protocols/graph/graph.py +17 -21
  23. lionagi/protocols/graph/node.py +23 -5
  24. lionagi/protocols/messages/manager.py +41 -45
  25. lionagi/protocols/messages/message.py +3 -1
  26. lionagi/protocols/operatives/step.py +2 -19
  27. lionagi/protocols/types.py +1 -2
  28. lionagi/service/connections/providers/claude_code_.py +9 -7
  29. lionagi/service/third_party/claude_code.py +3 -2
  30. lionagi/session/session.py +14 -2
  31. lionagi/tools/file/reader.py +5 -6
  32. lionagi/utils.py +8 -385
  33. lionagi/version.py +1 -1
  34. {lionagi-0.15.8.dist-info → lionagi-0.15.11.dist-info}/METADATA +2 -2
  35. {lionagi-0.15.8.dist-info → lionagi-0.15.11.dist-info}/RECORD +37 -37
  36. lionagi/libs/package/__init__.py +0 -3
  37. lionagi/libs/package/imports.py +0 -21
  38. lionagi/libs/package/management.py +0 -62
  39. lionagi/libs/package/params.py +0 -30
  40. lionagi/libs/package/system.py +0 -22
  41. {lionagi-0.15.8.dist-info → lionagi-0.15.11.dist-info}/WHEEL +0 -0
  42. {lionagi-0.15.8.dist-info → lionagi-0.15.11.dist-info}/licenses/LICENSE +0 -0
@@ -2,14 +2,15 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import contextlib
6
- import json
7
8
  from enum import Enum
8
9
  from typing import Any
9
10
 
10
11
  from pydantic import Field, field_serializer
11
12
 
12
- from lionagi.ln import Unset
13
+ from lionagi import ln
13
14
  from lionagi.utils import to_dict
14
15
 
15
16
  from .element import Element
@@ -24,7 +25,7 @@ __all__ = (
24
25
  _SIMPLE_TYPE = (str, bytes, bytearray, int, float, type(None), Enum)
25
26
 
26
27
 
27
- class EventStatus(str, Enum):
28
+ class EventStatus(str, ln.Enum):
28
29
  """Status states for tracking action execution progress.
29
30
 
30
31
  Attributes:
@@ -95,29 +96,30 @@ class Execution:
95
96
  Returns:
96
97
  dict: A dictionary representation of the execution state.
97
98
  """
98
- res_ = Unset
99
+ res_ = ln.Unset
99
100
  json_serializable = True
100
101
 
101
102
  if not isinstance(self.response, _SIMPLE_TYPE):
102
103
  json_serializable = False
103
104
  try:
104
105
  # check whether response is JSON serializable
105
- json.dumps(self.response)
106
+ ln.json_dumps(self.response)
106
107
  res_ = self.response
107
108
  json_serializable = True
108
109
  except Exception:
109
110
  with contextlib.suppress(Exception):
110
- # attempt to convert to dict
111
+ # attempt to force convert to dict
111
112
  d_ = to_dict(
112
113
  self.response,
113
114
  recursive=True,
114
115
  recursive_python_only=False,
116
+ use_enum_values=True,
115
117
  )
116
- json.dumps(d_)
118
+ ln.json_dumps(d_)
117
119
  res_ = d_
118
120
  json_serializable = True
119
121
 
120
- if res_ is Unset and not json_serializable:
122
+ if res_ is ln.Unset and not json_serializable:
121
123
  res_ = "<unserializable>"
122
124
 
123
125
  return {
@@ -140,93 +142,70 @@ class Event(Element):
140
142
 
141
143
  @field_serializer("execution")
142
144
  def _serialize_execution(self, val: Execution) -> dict:
143
- """Serializes the Execution object into a dictionary.
144
-
145
- Args:
146
- val (Execution): The Execution object to serialize.
147
-
148
- Returns:
149
- dict: The serialized data containing status, duration, response,
150
- and error fields.
151
- """
145
+ """Serializes the Execution object into a dictionary."""
152
146
  return val.to_dict()
153
147
 
154
148
  @property
155
149
  def response(self) -> Any:
156
- """Gets or sets the execution response.
157
-
158
- Returns:
159
- Any: The current response for this event.
160
- """
150
+ """Gets or sets the execution response."""
161
151
  return self.execution.response
162
152
 
163
153
  @response.setter
164
154
  def response(self, val: Any) -> None:
165
- """Sets the execution response.
166
-
167
- Args:
168
- val (Any): The new response value for this event.
169
- """
155
+ """Sets the execution response."""
170
156
  self.execution.response = val
171
157
 
172
158
  @property
173
159
  def status(self) -> EventStatus:
174
- """Gets or sets the event status.
175
-
176
- Returns:
177
- EventStatus: The current status of this event.
178
- """
160
+ """Gets or sets the event status."""
179
161
  return self.execution.status
180
162
 
181
163
  @status.setter
182
- def status(self, val: EventStatus) -> None:
183
- """Sets the event status.
184
-
185
- Args:
186
- val (EventStatus): The new status for the event.
187
- """
188
- self.execution.status = val
164
+ def status(self, val: EventStatus | str) -> None:
165
+ """Sets the event status."""
166
+ if isinstance(val, str):
167
+ if val not in EventStatus.allowed():
168
+ raise ValueError(f"Invalid status: {val}")
169
+ val = EventStatus(val)
170
+ if isinstance(val, EventStatus):
171
+ self.execution.status = val
172
+ else:
173
+ raise ValueError(
174
+ f"Invalid status type: {type(val)}. Expected EventStatus or str."
175
+ )
189
176
 
190
177
  @property
191
178
  def request(self) -> dict:
192
- """Gets the request for this event.
193
-
194
- Returns:
195
- dict: An empty dictionary by default. Override in subclasses
196
- if needed.
197
- """
179
+ """Gets the request for this event. Override in subclasses"""
198
180
  return {}
199
181
 
200
182
  async def invoke(self) -> None:
201
- """Performs the event action asynchronously.
202
-
203
- Raises:
204
- NotImplementedError: This base method must be overridden by
205
- subclasses.
206
- """
183
+ """Performs the event action asynchronously."""
207
184
  raise NotImplementedError("Override in subclass.")
208
185
 
209
186
  async def stream(self) -> None:
210
- """Performs the event action asynchronously, streaming results.
211
-
212
- Raises:
213
- NotImplementedError: This base method must be overridden by
214
- subclasses.
215
- """
187
+ """Performs the event action asynchronously, streaming results."""
216
188
  raise NotImplementedError("Override in subclass.")
217
189
 
218
190
  @classmethod
219
- def from_dict(cls, data: dict) -> "Event":
220
- """Not implemented. Events cannot be fully recreated once done.
221
-
222
- Args:
223
- data (dict): Event data (unused).
224
-
225
- Raises:
226
- NotImplementedError: Always, because recreating an event is
227
- disallowed.
228
- """
191
+ def from_dict(cls, data: dict) -> Event:
192
+ """Not implemented. Events cannot be fully recreated once done."""
229
193
  raise NotImplementedError("Cannot recreate an event once it's done.")
230
194
 
195
+ def as_fresh_event(self, copy_meta: bool = False) -> Event:
196
+ """Creates a clone of this event with the same execution state."""
197
+ d_ = self.to_dict()
198
+ for i in ["execution", "created_at", "id", "metadata"]:
199
+ d_.pop(i, None)
200
+ fresh = self.__class__(**d_)
201
+ if copy_meta:
202
+ meta = self.metadata.copy()
203
+ fresh.metadata = meta
204
+ fresh.metadata["original"] = {
205
+ "id": str(self.id),
206
+ "created_at": self.created_at,
207
+ }
208
+ return fresh
209
+
231
210
 
232
211
  # File: lionagi/protocols/generic/event.py
@@ -19,7 +19,7 @@ from pathlib import Path
19
19
  from typing import Any, ClassVar, Generic, Literal, TypeVar
20
20
 
21
21
  import pandas as pd
22
- from pydantic import Field, field_serializer, field_validator, model_validator
22
+ from pydantic import Field, field_serializer
23
23
  from pydantic.fields import FieldInfo
24
24
  from pydapter import Adaptable, AsyncAdaptable
25
25
  from typing_extensions import Self, deprecated, override
@@ -988,6 +988,12 @@ class Pile(Element, Collective[T], Generic[T], Adaptable, AsyncAdaptable):
988
988
  is_same_dtype(self.collections.values())
989
989
  )
990
990
 
991
+ @classmethod
992
+ def list_adapters(cls) -> list[str]:
993
+ syn_ = cls._adapter_registry._reg.keys()
994
+ asy_ = cls._async_registry._reg.keys()
995
+ return list(set(syn_) | set(asy_))
996
+
991
997
  def adapt_to(self, obj_key: str, many=False, **kw: Any) -> Any:
992
998
  """Adapt to another format.
993
999
 
@@ -1132,6 +1138,55 @@ class Pile(Element, Collective[T], Generic[T], Adaptable, AsyncAdaptable):
1132
1138
  ) -> None:
1133
1139
  return self.dump(fp, obj_key=obj_key, mode=mode, clear=clear, **kw)
1134
1140
 
1141
+ def filter_by_type(
1142
+ self,
1143
+ item_type: type[T] | list | set,
1144
+ strict_type: bool = False,
1145
+ as_pile: bool = False,
1146
+ reverse: bool = False,
1147
+ num_items: int | None = None,
1148
+ ) -> list[T]:
1149
+ if isinstance(item_type, type):
1150
+ if is_union_type(item_type):
1151
+ item_type = set(union_members(item_type))
1152
+ else:
1153
+ item_type = {item_type}
1154
+
1155
+ if isinstance(item_type, list | tuple):
1156
+ item_type = set(item_type)
1157
+
1158
+ if not isinstance(item_type, set):
1159
+ raise TypeError("item_type must be a type or a list/set of types")
1160
+
1161
+ meth = None
1162
+
1163
+ if strict_type:
1164
+ meth = lambda item: type(item) in item_type
1165
+ else:
1166
+ meth = (
1167
+ lambda item: any(isinstance(item, t) for t in item_type)
1168
+ is True
1169
+ )
1170
+
1171
+ out = []
1172
+ prog = (
1173
+ list(self.progression)
1174
+ if not reverse
1175
+ else reversed(list(self.progression))
1176
+ )
1177
+ for i in prog:
1178
+ item = self.collections[i]
1179
+ if meth(item):
1180
+ out.append(item)
1181
+ if num_items is not None and len(out) == num_items:
1182
+ break
1183
+
1184
+ if as_pile:
1185
+ return self.__class__(
1186
+ collections=out, item_type=item_type, strict_type=strict_type
1187
+ )
1188
+ return out
1189
+
1135
1190
 
1136
1191
  def to_list_type(value: Any, /) -> list[Any]:
1137
1192
  """Convert input to a list format"""
@@ -14,7 +14,7 @@ from lionagi._errors import ItemNotFoundError
14
14
  from .._concepts import Ordering
15
15
  from .element import ID, Element, IDError, IDType, validate_order
16
16
 
17
- E = TypeVar("E", bound=Element)
17
+ T = TypeVar("T", bound=Element)
18
18
 
19
19
 
20
20
  __all__ = (
@@ -23,7 +23,7 @@ __all__ = (
23
23
  )
24
24
 
25
25
 
26
- class Progression(Element, Ordering[E], Generic[E]):
26
+ class Progression(Element, Ordering[T], Generic[T]):
27
27
  """Tracks an ordered sequence of item IDs, with optional naming.
28
28
 
29
29
  This class extends `Element` and implements `Ordering`, providing
@@ -39,7 +39,7 @@ class Progression(Element, Ordering[E], Generic[E]):
39
39
  An optional human-readable identifier for the progression.
40
40
  """
41
41
 
42
- order: list[ID[E].ID] = Field(
42
+ order: list[ID[T].ID] = Field(
43
43
  default_factory=list,
44
44
  title="Order",
45
45
  description="A sequence of IDs representing the progression.",
@@ -358,7 +358,7 @@ class Progression(Element, Ordering[E], Generic[E]):
358
358
  raise ValueError("Can only extend with another Progression.")
359
359
  self.order.extend(other.order)
360
360
 
361
- def __add__(self, other: Any) -> Progression[E]:
361
+ def __add__(self, other: Any) -> Progression[T]:
362
362
  """Returns a new Progression with IDs from both this and `other`.
363
363
 
364
364
  Args:
@@ -371,7 +371,7 @@ class Progression(Element, Ordering[E], Generic[E]):
371
371
  new_refs = validate_order(other)
372
372
  return Progression(order=self.order + new_refs)
373
373
 
374
- def __radd__(self, other: Any) -> Progression[E]:
374
+ def __radd__(self, other: Any) -> Progression[T]:
375
375
  """Returns a new Progression with IDs from `other` + this.
376
376
 
377
377
  Args:
@@ -396,7 +396,7 @@ class Progression(Element, Ordering[E], Generic[E]):
396
396
  self.append(other)
397
397
  return self
398
398
 
399
- def __sub__(self, other: Any) -> Progression[E]:
399
+ def __sub__(self, other: Any) -> Progression[T]:
400
400
  """Returns a new Progression excluding specified IDs.
401
401
 
402
402
  Args:
@@ -435,7 +435,7 @@ class Progression(Element, Ordering[E], Generic[E]):
435
435
  for i in reversed(item_):
436
436
  self.order.insert(index, ID.get_id(i))
437
437
 
438
- def __reverse__(self) -> Progression[E]:
438
+ def __reversed__(self) -> Progression[T]:
439
439
  """Returns a new reversed Progression.
440
440
 
441
441
  Returns:
@@ -456,19 +456,19 @@ class Progression(Element, Ordering[E], Generic[E]):
456
456
  return NotImplemented
457
457
  return (self.order == other.order) and (self.name == other.name)
458
458
 
459
- def __gt__(self, other: Progression[E]) -> bool:
459
+ def __gt__(self, other: Progression[T]) -> bool:
460
460
  """Compares if this progression is "greater" by ID order."""
461
461
  return self.order > other.order
462
462
 
463
- def __lt__(self, other: Progression[E]) -> bool:
463
+ def __lt__(self, other: Progression[T]) -> bool:
464
464
  """Compares if this progression is "less" by ID order."""
465
465
  return self.order < other.order
466
466
 
467
- def __ge__(self, other: Progression[E]) -> bool:
467
+ def __ge__(self, other: Progression[T]) -> bool:
468
468
  """Compares if this progression is >= the other by ID order."""
469
469
  return self.order >= other.order
470
470
 
471
- def __le__(self, other: Progression[E]) -> bool:
471
+ def __le__(self, other: Progression[T]) -> bool:
472
472
  """Compares if this progression is <= the other by ID order."""
473
473
  return self.order <= other.order
474
474
 
@@ -0,0 +1,22 @@
1
+ def check_networkx_available():
2
+ try:
3
+ from networkx import DiGraph # noqa: F401
4
+
5
+ return True
6
+ except Exception:
7
+ return ImportError(
8
+ "The 'networkx' package is required for this feature. "
9
+ "Please install `networkx` or `'lionagi[graph]'`."
10
+ )
11
+
12
+
13
+ def check_matplotlib_available():
14
+ try:
15
+ import matplotlib.pyplot as plt
16
+
17
+ return True
18
+ except Exception:
19
+ return ImportError(
20
+ "The 'matplotlib' package is required for this feature. "
21
+ "Please install `matplotlib` or `'lionagi[graph]'`."
22
+ )
@@ -3,7 +3,7 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
  from collections import deque
6
- from typing import Any, Literal
6
+ from typing import Any, Generic, Literal, TypeVar
7
7
 
8
8
  from pydantic import Field, field_serializer, field_validator, model_validator
9
9
  from typing_extensions import Self
@@ -17,11 +17,17 @@ from ..generic.pile import Pile
17
17
  from .edge import Edge
18
18
  from .node import Node
19
19
 
20
+ T = TypeVar("T", bound=Node)
21
+
22
+ from ._utils import check_matplotlib_available, check_networkx_available
23
+
24
+ _NETWORKX_AVAILABLE = check_networkx_available()
25
+ _MATPLIB_AVAILABLE = check_matplotlib_available()
20
26
  __all__ = ("Graph",)
21
27
 
22
28
 
23
- class Graph(Element, Relational):
24
- internal_nodes: Pile[Node] = Field(
29
+ class Graph(Element, Relational, Generic[T]):
30
+ internal_nodes: Pile[T] = Field(
25
31
  default_factory=lambda: Pile(item_type={Node}, strict_type=False),
26
32
  title="Internal Nodes",
27
33
  description="A collection of nodes in the graph.",
@@ -214,13 +220,10 @@ class Graph(Element, Relational):
214
220
 
215
221
  def to_networkx(self, **kwargs) -> Any:
216
222
  """Convert the graph to a NetworkX graph object."""
217
- try:
218
- from networkx import DiGraph # type: ignore
223
+ if _NETWORKX_AVAILABLE is not True:
224
+ raise _NETWORKX_AVAILABLE
219
225
 
220
- except ImportError:
221
- from lionagi.libs.package.imports import check_import
222
-
223
- DiGraph = check_import("networkx", import_name="DiGraph")
226
+ from networkx import DiGraph # type: ignore
224
227
 
225
228
  g = DiGraph(**kwargs)
226
229
  for node in self.internal_nodes:
@@ -245,20 +248,13 @@ class Graph(Element, Relational):
245
248
  **kwargs,
246
249
  ):
247
250
  """Display the graph using NetworkX and Matplotlib."""
251
+ g = self.to_networkx(**kwargs)
252
+ if _MATPLIB_AVAILABLE is not True:
253
+ raise _MATPLIB_AVAILABLE
248
254
 
249
- try:
250
- import matplotlib.pyplot as plt # type: ignore
251
- import networkx as nx # type: ignore
252
- except ImportError:
253
- from lionagi.libs.package.imports import check_import
254
-
255
- check_import("matplotlib")
256
- check_import("networkx")
257
-
258
- import matplotlib.pyplot as plt # type: ignore
259
- import networkx as nx # type: ignore
255
+ import matplotlib.pyplot as plt # type: ignore
256
+ import networkx as nx # type: ignore
260
257
 
261
- g = self.to_networkx(**kwargs)
262
258
  pos = nx.spring_layout(g)
263
259
  nx.draw(
264
260
  g,
@@ -4,10 +4,10 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- import json
8
7
  from typing import Any
9
8
 
10
- from pydantic import field_validator
9
+ import orjson
10
+ from pydantic import BaseModel, field_serializer, field_validator
11
11
  from pydapter import Adaptable, AsyncAdaptable
12
12
 
13
13
  from lionagi._class_registry import LION_CLASS_REGISTRY
@@ -44,7 +44,7 @@ class Node(Element, Relational, AsyncAdaptable, Adaptable):
44
44
  return None
45
45
  if isinstance(value, str):
46
46
  try:
47
- loaded = json.loads(value)
47
+ loaded = orjson.loads(value)
48
48
  if not isinstance(loaded, list):
49
49
  raise ValueError
50
50
  return [float(x) for x in loaded]
@@ -62,7 +62,8 @@ class Node(Element, Relational, AsyncAdaptable, Adaptable):
62
62
  async def adapt_to_async(
63
63
  self, obj_key: str, many=False, **kwargs: Any
64
64
  ) -> Any:
65
- kwargs["adapt_meth"] = "to_dict"
65
+ kwargs["adapt_meth"] = "as_jsonable"
66
+ kwargs["mode"] = "json"
66
67
  return await super().adapt_to_async(
67
68
  obj_key=obj_key, many=many, **kwargs
68
69
  )
@@ -84,7 +85,8 @@ class Node(Element, Relational, AsyncAdaptable, Adaptable):
84
85
  """
85
86
  Convert this Node to another format using a registered adapter.
86
87
  """
87
- kwargs["adapt_meth"] = "to_dict"
88
+ kwargs["adapt_meth"] = "as_jsonable"
89
+ kwargs["mode"] = "json"
88
90
  return super().adapt_to(obj_key=obj_key, many=many, **kwargs)
89
91
 
90
92
  @classmethod
@@ -103,6 +105,22 @@ class Node(Element, Relational, AsyncAdaptable, Adaptable):
103
105
  kwargs["adapt_meth"] = "from_dict"
104
106
  return super().adapt_from(obj, obj_key=obj_key, many=many, **kwargs)
105
107
 
108
+ @field_serializer("content")
109
+ def _serialize_content(self, value: Any) -> Any:
110
+ if isinstance(value, Element):
111
+ return value.to_dict()
112
+ if isinstance(value, BaseModel):
113
+ return value.model_dump()
114
+ return value
115
+
116
+ @field_validator("content", mode="before")
117
+ def _validate_content(cls, value: Any) -> Any:
118
+ if isinstance(value, dict) and "lion_class" in value.get(
119
+ "metadata", {}
120
+ ):
121
+ return Element.from_dict(value)
122
+ return value
123
+
106
124
 
107
125
  if not _ADAPATER_REGISTERED:
108
126
  from pydapter.adapters import JsonAdapter, TomlAdapter
@@ -392,79 +392,75 @@ class MessageManager(Manager):
392
392
 
393
393
  @property
394
394
  def last_response(self) -> AssistantResponse | None:
395
- """
396
- Retrieve the most recent `AssistantResponse`.
397
- """
398
- for mid in reversed(self.messages.progression):
399
- if isinstance(self.messages[mid], AssistantResponse):
400
- return self.messages[mid]
395
+ """Retrieve the most recent `AssistantResponse`."""
396
+ res = self.messages.filter_by_type(
397
+ item_type=AssistantResponse,
398
+ strict_type=True,
399
+ as_pile=False,
400
+ reverse=True,
401
+ num_items=1,
402
+ )
403
+ if len(res) == 1:
404
+ return res[0]
401
405
  return None
402
406
 
403
407
  @property
404
408
  def last_instruction(self) -> Instruction | None:
405
- """
406
- Retrieve the most recent `Instruction`.
407
- """
408
- for mid in reversed(self.messages.progression):
409
- if isinstance(self.messages[mid], Instruction):
410
- return self.messages[mid]
409
+ """Retrieve the most recent `Instruction`."""
410
+ res = self.messages.filter_by_type(
411
+ item_type=Instruction,
412
+ strict_type=True,
413
+ as_pile=False,
414
+ reverse=True,
415
+ num_items=1,
416
+ )
417
+ if len(res) == 1:
418
+ return res[0]
411
419
  return None
412
420
 
413
421
  @property
414
422
  def assistant_responses(self) -> Pile[AssistantResponse]:
415
423
  """All `AssistantResponse` messages in the manager."""
416
- return Pile(
417
- collections=[
418
- self.messages[mid]
419
- for mid in self.messages.progression
420
- if isinstance(self.messages[mid], AssistantResponse)
421
- ]
424
+ return self.messages.filter_by_type(
425
+ item_type=AssistantResponse,
426
+ strict_type=True,
427
+ as_pile=True,
422
428
  )
423
429
 
424
430
  @property
425
431
  def actions(self) -> Pile[ActionRequest | ActionResponse]:
426
432
  """All action messages in the manager."""
427
- return Pile(
428
- collections=[
429
- self.messages[mid]
430
- for mid in self.messages.progression
431
- if isinstance(
432
- self.messages[mid], (ActionRequest, ActionResponse)
433
- )
434
- ]
433
+ return self.messages.filter_by_type(
434
+ item_type={ActionRequest, ActionResponse},
435
+ strict_type=True,
436
+ as_pile=True,
435
437
  )
436
438
 
437
439
  @property
438
440
  def action_requests(self) -> Pile[ActionRequest]:
439
441
  """All `ActionRequest` messages in the manager."""
440
- return Pile(
441
- collections=[
442
- self.messages[mid]
443
- for mid in self.messages.progression
444
- if isinstance(self.messages[mid], ActionRequest)
445
- ]
442
+ return self.messages.filter_by_type(
443
+ item_type=ActionRequest,
444
+ strict_type=True,
445
+ as_pile=True,
446
446
  )
447
447
 
448
448
  @property
449
449
  def action_responses(self) -> Pile[ActionResponse]:
450
450
  """All `ActionResponse` messages in the manager."""
451
- return Pile(
452
- collections=[
453
- self.messages[mid]
454
- for mid in self.messages.progression
455
- if isinstance(self.messages[mid], ActionResponse)
456
- ]
451
+ return self.messages.filter_by_type(
452
+ item_type=ActionResponse,
453
+ strict_type=True,
454
+ as_pile=True,
457
455
  )
458
456
 
459
457
  @property
460
458
  def instructions(self) -> Pile[Instruction]:
461
459
  """All `Instruction` messages in the manager."""
462
- return Pile(
463
- collections=[
464
- self.messages[mid]
465
- for mid in self.messages.progression
466
- if isinstance(self.messages[mid], Instruction)
467
- ]
460
+ return self.messages.filter_by_type(
461
+ item_type=Instruction,
462
+ strict_type=True,
463
+ as_pile=True,
468
464
  )
469
465
 
470
466
  def remove_last_instruction_tool_schemas(self) -> None:
@@ -481,7 +477,7 @@ class MessageManager(Manager):
481
477
  Example method to merge the content of recent ActionResponses
482
478
  into an instruction's context.
483
479
  """
484
- for i in reversed(self.messages.progression):
480
+ for i in reversed(list(self.messages.progression)):
485
481
  if isinstance(self.messages[i], ActionResponse):
486
482
  instruction.context.append(self.messages[i].content)
487
483
  else:
@@ -11,6 +11,8 @@ from typing import Any
11
11
  from jinja2 import Environment, FileSystemLoader, Template
12
12
  from pydantic import Field, PrivateAttr, field_serializer
13
13
 
14
+ from lionagi import ln
15
+
14
16
  from .._concepts import Sendable
15
17
  from ..generic.element import Element, IDType
16
18
  from ..generic.log import Log
@@ -114,7 +116,7 @@ class RoledMessage(Node, Sendable):
114
116
  if isinstance(self.template, Template):
115
117
  return self.template.render(**self.content)
116
118
  except Exception:
117
- return json.dumps(self.content)
119
+ return ln.json_dumps(self.content)
118
120
 
119
121
  @classmethod
120
122
  def create(cls, **kwargs):