lionagi 0.0.306__py3-none-any.whl → 0.0.307__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 (78) hide show
  1. lionagi/__init__.py +2 -5
  2. lionagi/core/__init__.py +7 -5
  3. lionagi/core/agent/__init__.py +3 -0
  4. lionagi/core/agent/base_agent.py +10 -12
  5. lionagi/core/branch/__init__.py +4 -0
  6. lionagi/core/branch/base_branch.py +81 -81
  7. lionagi/core/branch/branch.py +16 -28
  8. lionagi/core/branch/branch_flow_mixin.py +3 -7
  9. lionagi/core/branch/executable_branch.py +86 -56
  10. lionagi/core/branch/util.py +77 -162
  11. lionagi/core/{flow/direct → direct}/__init__.py +1 -1
  12. lionagi/core/{flow/direct/predict.py → direct/parallel_predict.py} +39 -17
  13. lionagi/core/direct/parallel_react.py +0 -0
  14. lionagi/core/direct/parallel_score.py +0 -0
  15. lionagi/core/direct/parallel_select.py +0 -0
  16. lionagi/core/direct/parallel_sentiment.py +0 -0
  17. lionagi/core/direct/predict.py +174 -0
  18. lionagi/core/{flow/direct → direct}/react.py +2 -2
  19. lionagi/core/{flow/direct → direct}/score.py +28 -23
  20. lionagi/core/{flow/direct → direct}/select.py +48 -45
  21. lionagi/core/direct/utils.py +83 -0
  22. lionagi/core/flow/monoflow/ReAct.py +6 -5
  23. lionagi/core/flow/monoflow/__init__.py +9 -0
  24. lionagi/core/flow/monoflow/chat.py +10 -10
  25. lionagi/core/flow/monoflow/chat_mixin.py +11 -10
  26. lionagi/core/flow/monoflow/followup.py +6 -5
  27. lionagi/core/flow/polyflow/__init__.py +1 -0
  28. lionagi/core/flow/polyflow/chat.py +15 -3
  29. lionagi/core/mail/mail_manager.py +18 -19
  30. lionagi/core/mail/schema.py +5 -4
  31. lionagi/core/messages/schema.py +18 -20
  32. lionagi/core/prompt/__init__.py +0 -0
  33. lionagi/core/prompt/prompt_template.py +0 -0
  34. lionagi/core/schema/__init__.py +2 -2
  35. lionagi/core/schema/action_node.py +11 -3
  36. lionagi/core/schema/base_mixin.py +56 -59
  37. lionagi/core/schema/base_node.py +35 -38
  38. lionagi/core/schema/condition.py +24 -0
  39. lionagi/core/schema/data_logger.py +96 -99
  40. lionagi/core/schema/data_node.py +19 -19
  41. lionagi/core/schema/prompt_template.py +0 -0
  42. lionagi/core/schema/structure.py +171 -169
  43. lionagi/core/session/__init__.py +1 -3
  44. lionagi/core/session/session.py +196 -214
  45. lionagi/core/tool/tool_manager.py +95 -103
  46. lionagi/integrations/__init__.py +1 -3
  47. lionagi/integrations/bridge/langchain_/documents.py +17 -18
  48. lionagi/integrations/bridge/langchain_/langchain_bridge.py +14 -14
  49. lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +22 -22
  50. lionagi/integrations/bridge/llamaindex_/node_parser.py +12 -12
  51. lionagi/integrations/bridge/llamaindex_/reader.py +11 -11
  52. lionagi/integrations/bridge/llamaindex_/textnode.py +7 -7
  53. lionagi/integrations/config/openrouter_configs.py +0 -1
  54. lionagi/integrations/provider/oai.py +26 -26
  55. lionagi/integrations/provider/services.py +38 -38
  56. lionagi/libs/__init__.py +34 -1
  57. lionagi/libs/ln_api.py +211 -221
  58. lionagi/libs/ln_async.py +53 -60
  59. lionagi/libs/ln_convert.py +118 -120
  60. lionagi/libs/ln_dataframe.py +32 -33
  61. lionagi/libs/ln_func_call.py +334 -342
  62. lionagi/libs/ln_nested.py +99 -107
  63. lionagi/libs/ln_parse.py +161 -165
  64. lionagi/libs/sys_util.py +52 -52
  65. lionagi/tests/test_core/test_session.py +254 -266
  66. lionagi/tests/test_core/test_session_base_util.py +299 -300
  67. lionagi/tests/test_core/test_tool_manager.py +70 -74
  68. lionagi/tests/test_libs/test_nested.py +2 -7
  69. lionagi/tests/test_libs/test_parse.py +2 -2
  70. lionagi/version.py +1 -1
  71. {lionagi-0.0.306.dist-info → lionagi-0.0.307.dist-info}/METADATA +4 -2
  72. lionagi-0.0.307.dist-info/RECORD +115 -0
  73. lionagi/core/flow/direct/utils.py +0 -43
  74. lionagi-0.0.306.dist-info/RECORD +0 -106
  75. /lionagi/core/{flow/direct → direct}/sentiment.py +0 -0
  76. {lionagi-0.0.306.dist-info → lionagi-0.0.307.dist-info}/LICENSE +0 -0
  77. {lionagi-0.0.306.dist-info → lionagi-0.0.307.dist-info}/WHEEL +0 -0
  78. {lionagi-0.0.306.dist-info → lionagi-0.0.307.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,14 @@
1
- from typing import List, Any, Dict
1
+ import time
2
+ from typing import List, Any, Dict, Callable
2
3
  from collections import deque
3
4
  from pydantic import Field
4
5
 
5
- from lionagi.libs.sys_util import SysUtil
6
- from lionagi.libs import ln_func_call as func_call
7
- from lionagi.libs.ln_async import AsyncUtil
6
+ from lionagi.libs import SysUtil, func_call, AsyncUtil
8
7
 
9
- from lionagi.core.schema.base_node import BaseRelatableNode, BaseNode
8
+ from .base_node import BaseRelatableNode, BaseNode, Tool
10
9
  from lionagi.core.mail.schema import BaseMail
11
10
 
11
+ from lionagi.core.schema.condition import Condition
12
12
 
13
13
  from lionagi.core.schema.action_node import ActionNode, ActionSelection
14
14
  from lionagi.core.schema.base_node import Tool
@@ -19,113 +19,44 @@ class Relationship(BaseRelatableNode):
19
19
  Represents a relationship between two nodes in a graph.
20
20
 
21
21
  Attributes:
22
- source_node_id (str): The identifier of the source node.
23
- target_node_id (str): The identifier of the target node.
24
- condition (Dict[str, Any]): A dictionary representing conditions for the relationship.
22
+ source_node_id (str): The identifier of the source node.
23
+ target_node_id (str): The identifier of the target node.
24
+ condition (Dict[str, Any]): A dictionary representing conditions for the relationship.
25
25
 
26
26
  Examples:
27
- >>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
28
- >>> relationship.add_condition({"key": "value"})
29
- >>> condition_value = relationship.get_condition("key")
30
- >>> relationship.remove_condition("key")
27
+ >>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
28
+ >>> relationship.add_condition({"key": "value"})
29
+ >>> condition_value = relationship.get_condition("key")
30
+ >>> relationship.remove_condition("key")
31
31
  """
32
32
 
33
33
  source_node_id: str
34
34
  target_node_id: str
35
35
  bundle: bool = False
36
- condition: dict = Field(default={})
37
-
38
- def add_condition(self, condition: Dict[str, Any]) -> None:
39
- """
40
- Adds a condition to the relationship.
41
-
42
- Args:
43
- condition: The condition to be added.
44
-
45
- Examples:
46
- >>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
47
- >>> relationship.add_condition({"key": "value"})
48
- """
49
- self.condition.update(condition)
50
-
51
- def remove_condition(self, condition_key: str) -> Any:
52
- """
53
- Removes a condition from the relationship.
54
-
55
- Args:
56
- condition_key: The key of the condition to be removed.
57
-
58
- Returns:
59
- The value of the removed condition.
60
-
61
- Raises:
62
- KeyError: If the condition key is not found.
63
-
64
- Examples:
65
- >>> relationship = Relationship(source_node_id="node1", target_node_id="node2", condition={"key": "value"})
66
- >>> relationship.remove_condition("key")
67
- 'value'
68
- """
69
- if condition_key not in self.condition.keys():
70
- raise KeyError(f"condition {condition_key} is not found")
71
- return self.condition.pop(condition_key)
72
-
73
- def condition_exists(self, condition_key: str) -> bool:
74
- """
75
- Checks if a condition exists in the relationship.
76
-
77
- Args:
78
- condition_key: The key of the condition to check.
79
-
80
- Returns:
81
- True if the condition exists, False otherwise.
82
-
83
- Examples:
84
- >>> relationship = Relationship(source_node_id="node1", target_node_id="node2", condition={"key": "value"})
85
- >>> relationship.condition_exists("key")
86
- True
87
- """
88
- if condition_key in self.condition.keys():
89
- return True
90
- else:
91
- return False
92
-
93
- def get_condition(self, condition_key: str | None = None) -> Any:
94
- """
95
- Retrieves a specific condition or all conditions of the relationship.
36
+ condition: Callable = None
96
37
 
97
- Args:
98
- condition_key: The key of the specific condition. If None, all conditions are returned.
99
-
100
- Returns:
101
- The requested condition or all conditions if no key is provided.
102
-
103
- Raises:
104
- ValueError: If the specified condition key does not exist.
38
+ def add_condition(self, condition: Condition):
39
+ if not isinstance(condition, Condition):
40
+ raise ValueError(
41
+ "Invalid condition type, please use Condition class to build a valid condition"
42
+ )
43
+ self.condition = condition
105
44
 
106
- Examples:
107
- >>> relationship = Relationship(source_node_id="node1", target_node_id="node2", condition={"key": "value"})
108
- >>> relationship.get_condition("key")
109
- 'value'
110
- >>> relationship.get_condition()
111
- {'key': 'value'}
112
- """
113
- if condition_key is None:
114
- return self.condition
115
- if self.condition_exists(condition_key=condition_key):
116
- return self.condition[condition_key]
117
- else:
118
- raise ValueError(f"Condition {condition_key} does not exist")
45
+ def check_condition(self, source_obj):
46
+ try:
47
+ return bool(self.condition(source_obj))
48
+ except:
49
+ raise ValueError("Invalid relationship condition function")
119
50
 
120
51
  def _source_existed(self, obj: Dict[str, Any]) -> bool:
121
52
  """
122
53
  Checks if the source node exists in a given object.
123
54
 
124
55
  Args:
125
- obj (Dict[str, Any]): The object to check.
56
+ obj (Dict[str, Any]): The object to check.
126
57
 
127
58
  Returns:
128
- bool: True if the source node exists, False otherwise.
59
+ bool: True if the source node exists, False otherwise.
129
60
  """
130
61
  return self.source_node_id in obj.keys()
131
62
 
@@ -134,10 +65,10 @@ class Relationship(BaseRelatableNode):
134
65
  Checks if the target node exists in a given object.
135
66
 
136
67
  Args:
137
- obj (Dict[str, Any]): The object to check.
68
+ obj (Dict[str, Any]): The object to check.
138
69
 
139
70
  Returns:
140
- bool: True if the target node exists, False otherwise.
71
+ bool: True if the target node exists, False otherwise.
141
72
  """
142
73
  return self.target_node_id in obj.keys()
143
74
 
@@ -146,13 +77,13 @@ class Relationship(BaseRelatableNode):
146
77
  Validates the existence of both source and target nodes in a given object.
147
78
 
148
79
  Args:
149
- obj (Dict[str, Any]): The object to check.
80
+ obj (Dict[str, Any]): The object to check.
150
81
 
151
82
  Returns:
152
- bool: True if both nodes exist.
83
+ bool: True if both nodes exist.
153
84
 
154
85
  Raises:
155
- ValueError: If either the source or target node does not exist.
86
+ ValueError: If either the source or target node does not exist.
156
87
  """
157
88
  if self._source_existed(obj) and self._target_existed(obj):
158
89
  return True
@@ -167,9 +98,9 @@ class Relationship(BaseRelatableNode):
167
98
  Returns a simple string representation of the Relationship.
168
99
 
169
100
  Examples:
170
- >>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
171
- >>> str(relationship)
172
- 'Relationship (id_=None, from=node1, to=node2, label=None)'
101
+ >>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
102
+ >>> str(relationship)
103
+ 'Relationship (id_=None, from=node1, to=node2, label=None)'
173
104
  """
174
105
  return (
175
106
  f"Relationship (id_={self.id_}, from={self.source_node_id}, to={self.target_node_id}, "
@@ -181,9 +112,9 @@ class Relationship(BaseRelatableNode):
181
112
  Returns a detailed string representation of the Relationship.
182
113
 
183
114
  Examples:
184
- >>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
185
- >>> repr(relationship)
186
- 'Relationship(id_=None, from=node1, to=node2, content=None, metadata=None, label=None)'
115
+ >>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
116
+ >>> repr(relationship)
117
+ 'Relationship(id_=None, from=node1, to=node2, content=None, metadata=None, label=None)'
187
118
  """
188
119
  return (
189
120
  f"Relationship(id_={self.id_}, from={self.source_node_id}, to={self.target_node_id}, "
@@ -196,20 +127,20 @@ class Graph(BaseRelatableNode):
196
127
  Represents a graph structure, consisting of nodes and their relationship.
197
128
 
198
129
  Attributes:
199
- nodes (Dict[str, BaseNode]): A dictionary of nodes in the graph.
200
- relationships (Dict[str, Relationship]): A dictionary of relationship between nodes in the graph.
201
- node_relationships (Dict[str, Dict[str, Dict[str, str]]]): A dictionary tracking the relationship of each node.
130
+ nodes (Dict[str, BaseNode]): A dictionary of nodes in the graph.
131
+ relationships (Dict[str, Relationship]): A dictionary of relationship between nodes in the graph.
132
+ node_relationships (Dict[str, Dict[str, Dict[str, str]]]): A dictionary tracking the relationship of each node.
202
133
 
203
134
  Examples:
204
- >>> graph = Graph()
205
- >>> node = BaseNode(id_='node1')
206
- >>> graph.add_node(node)
207
- >>> graph.node_exists(node)
208
- True
209
- >>> relationship = Relationship(id_='rel1', source_node_id='node1', target_node_id='node2')
210
- >>> graph.add_relationship(relationship)
211
- >>> graph.relationship_exists(relationship)
212
- True
135
+ >>> graph = Graph()
136
+ >>> node = BaseNode(id_='node1')
137
+ >>> graph.add_node(node)
138
+ >>> graph.node_exists(node)
139
+ True
140
+ >>> relationship = Relationship(id_='rel1', source_node_id='node1', target_node_id='node2')
141
+ >>> graph.add_relationship(relationship)
142
+ >>> graph.relationship_exists(relationship)
143
+ True
213
144
  """
214
145
 
215
146
  nodes: dict = Field(default={})
@@ -221,7 +152,7 @@ class Graph(BaseRelatableNode):
221
152
  Adds a node to the graph.
222
153
 
223
154
  Args:
224
- node (BaseNode): The node to add to the graph.
155
+ node (BaseNode): The node to add to the graph.
225
156
  """
226
157
 
227
158
  self.nodes[node.id_] = node
@@ -232,10 +163,10 @@ class Graph(BaseRelatableNode):
232
163
  Adds a relationship between nodes in the graph.
233
164
 
234
165
  Args:
235
- relationship (Relationship): The relationship to add.
166
+ relationship (Relationship): The relationship to add.
236
167
 
237
168
  Raises:
238
- KeyError: If either the source or target node of the relationship is not found in the graph.
169
+ KeyError: If either the source or target node of the relationship is not found in the graph.
239
170
  """
240
171
  if relationship.source_node_id not in self.node_relationships.keys():
241
172
  raise KeyError(f"node {relationship.source_node_id} is not found.")
@@ -257,14 +188,14 @@ class Graph(BaseRelatableNode):
257
188
  Retrieves relationship of a specific node or all relationship in the graph.
258
189
 
259
190
  Args:
260
- node (Optional[BaseNode]): The node whose relationship to retrieve. If None, retrieves all relationship.
261
- out_edge (bool): Whether to retrieve outgoing relationship. If False, retrieves incoming relationship.
191
+ node (Optional[BaseNode]): The node whose relationship to retrieve. If None, retrieves all relationship.
192
+ out_edge (bool): Whether to retrieve outgoing relationship. If False, retrieves incoming relationship.
262
193
 
263
194
  Returns:
264
- List[Relationship]: A list of relationship.
195
+ List[Relationship]: A list of relationship.
265
196
 
266
197
  Raises:
267
- KeyError: If the specified node is not found in the graph.
198
+ KeyError: If the specified node is not found in the graph.
268
199
  """
269
200
  if node is None:
270
201
  return list(self.relationships.values())
@@ -285,18 +216,28 @@ class Graph(BaseRelatableNode):
285
216
  )
286
217
  return relationships
287
218
 
219
+ def get_predecessors(self, node: BaseNode):
220
+ node_ids = list(self.node_relationships[node.id_]["in"].values())
221
+ nodes = func_call.lcall(node_ids, lambda i: self.nodes[i])
222
+ return nodes
223
+
224
+ def get_successors(self, node: BaseNode):
225
+ node_ids = list(self.node_relationships[node.id_]["out"].values())
226
+ nodes = func_call.lcall(node_ids, lambda i: self.nodes[i])
227
+ return nodes
228
+
288
229
  def remove_node(self, node: BaseNode) -> BaseNode:
289
230
  """
290
231
  Removes a node and its associated relationship from the graph.
291
232
 
292
233
  Args:
293
- node (BaseNode): The node to remove.
234
+ node (BaseNode): The node to remove.
294
235
 
295
236
  Returns:
296
- BaseNode: The removed node.
237
+ BaseNode: The removed node.
297
238
 
298
239
  Raises:
299
- KeyError: If the node is not found in the graph.
240
+ KeyError: If the node is not found in the graph.
300
241
  """
301
242
  if node.id_ not in self.nodes.keys():
302
243
  raise KeyError(f"node {node.id_} is not found")
@@ -319,13 +260,13 @@ class Graph(BaseRelatableNode):
319
260
  Removes a relationship from the graph.
320
261
 
321
262
  Args:
322
- relationship (Relationship): The relationship to remove.
263
+ relationship (Relationship): The relationship to remove.
323
264
 
324
265
  Returns:
325
- Relationship: The removed relationship.
266
+ Relationship: The removed relationship.
326
267
 
327
268
  Raises:
328
- KeyError: If the relationship is not found in the graph.
269
+ KeyError: If the relationship is not found in the graph.
329
270
  """
330
271
  if relationship.id_ not in self.relationships.keys():
331
272
  raise KeyError(f"relationship {relationship.id_} is not found")
@@ -342,10 +283,10 @@ class Graph(BaseRelatableNode):
342
283
  Checks if a node exists in the graph.
343
284
 
344
285
  Args:
345
- node (BaseNode): The node to check.
286
+ node (BaseNode): The node to check.
346
287
 
347
288
  Returns:
348
- bool: True if the node exists, False otherwise.
289
+ bool: True if the node exists, False otherwise.
349
290
  """
350
291
  if node.id_ in self.nodes.keys():
351
292
  return True
@@ -357,10 +298,10 @@ class Graph(BaseRelatableNode):
357
298
  Checks if a relationship exists in the graph.
358
299
 
359
300
  Args:
360
- relationship (Relationship): The relationship to check.
301
+ relationship (Relationship): The relationship to check.
361
302
 
362
303
  Returns:
363
- bool: True if the relationship exists, False otherwise.
304
+ bool: True if the relationship exists, False otherwise.
364
305
  """
365
306
  if relationship.id_ in self.relationships.keys():
366
307
  return True
@@ -372,7 +313,7 @@ class Graph(BaseRelatableNode):
372
313
  Determines if the graph is empty.
373
314
 
374
315
  Returns:
375
- bool: True if the graph has no nodes, False otherwise.
316
+ bool: True if the graph has no nodes, False otherwise.
376
317
  """
377
318
  if self.nodes:
378
319
  return False
@@ -390,14 +331,14 @@ class Graph(BaseRelatableNode):
390
331
  Converts the graph to a NetworkX graph object.
391
332
 
392
333
  Args:
393
- **kwargs: Additional keyword arguments to pass to the NetworkX DiGraph constructor.
334
+ **kwargs: Additional keyword arguments to pass to the NetworkX DiGraph constructor.
394
335
 
395
336
  Returns:
396
- Any: A NetworkX directed graph representing the graph.
337
+ Any: A NetworkX directed graph representing the graph.
397
338
 
398
339
  Examples:
399
- >>> graph = Graph()
400
- >>> nx_graph = graph.to_networkx()
340
+ >>> graph = Graph()
341
+ >>> nx_graph = graph.to_networkx()
401
342
  """
402
343
 
403
344
  SysUtil.check_import("networkx")
@@ -408,11 +349,13 @@ class Graph(BaseRelatableNode):
408
349
  for node_id, node in self.nodes.items():
409
350
  node_info = node.to_dict()
410
351
  node_info.pop("node_id")
352
+ node_info.update({"class_name": node.__class__.__name__})
411
353
  g.add_node(node_id, **node_info)
412
354
 
413
355
  for _, relationship in self.relationships.items():
414
356
  relationship_info = relationship.to_dict()
415
357
  relationship_info.pop("node_id")
358
+ relationship_info.update({"class_name": relationship.__class__.__name__})
416
359
  source_node_id = relationship_info.pop("source_node_id")
417
360
  target_node_id = relationship_info.pop("target_node_id")
418
361
  g.add_edge(source_node_id, target_node_id, **relationship_info)
@@ -422,20 +365,37 @@ class Graph(BaseRelatableNode):
422
365
 
423
366
  class Structure(BaseRelatableNode):
424
367
  graph: Graph = Graph()
425
- processing_mails: deque = deque()
426
368
  pending_ins: dict = {}
427
369
  pending_outs: deque = deque()
428
370
  execute_stop: bool = False
371
+ condition_check_result: bool | None = None
429
372
 
430
373
  def add_node(self, node: BaseNode):
431
374
  self.graph.add_node(node)
432
375
 
433
- # def add_relationship(self, relationship: Relationship):
434
- # self.graph.add_relationship(relationship)
435
- def add_relationship(self, from_node: BaseNode, to_node: BaseNode, **kwargs):
376
+ def add_relationship(
377
+ self,
378
+ from_node: BaseNode,
379
+ to_node: BaseNode,
380
+ bundle=False,
381
+ condition=None,
382
+ **kwargs,
383
+ ):
384
+ if isinstance(from_node, Tool) or isinstance(from_node, ActionSelection):
385
+ raise ValueError(
386
+ f"type {type(from_node)} should not be the head of the relationship, "
387
+ f"please switch position and attach it to the tail of the relationship"
388
+ )
389
+ if isinstance(to_node, Tool) or isinstance(to_node, ActionSelection):
390
+ bundle = True
436
391
  relationship = Relationship(
437
- source_node_id=from_node.id_, target_node_id=to_node.id_, **kwargs
392
+ source_node_id=from_node.id_,
393
+ target_node_id=to_node.id_,
394
+ bundle=bundle,
395
+ **kwargs,
438
396
  )
397
+ if condition:
398
+ relationship.add_condition(condition)
439
399
  self.graph.add_relationship(relationship)
440
400
 
441
401
  def get_relationships(self) -> list[Relationship]:
@@ -453,6 +413,12 @@ class Structure(BaseRelatableNode):
453
413
  relationships = result
454
414
  return relationships
455
415
 
416
+ def get_predecessors(self, node: BaseNode):
417
+ return self.graph.get_predecessors(node)
418
+
419
+ def get_successors(self, node: BaseNode):
420
+ return self.graph.get_successors(node)
421
+
456
422
  def node_exist(self, node: BaseNode) -> bool:
457
423
  return self.graph.node_exist(node)
458
424
 
@@ -469,24 +435,12 @@ class Structure(BaseRelatableNode):
469
435
  return self.graph.is_empty()
470
436
 
471
437
  def get_heads(self):
472
- heads = deque()
438
+ heads = []
473
439
  for key in self.graph.node_relationships:
474
440
  if not self.graph.node_relationships[key]["in"]:
475
441
  heads.append(self.graph.nodes[key])
476
442
  return heads
477
443
 
478
- # def get_next_step(self, current_node: BaseNode):
479
- # next_nodes = deque()
480
- # next_relationships = self.get_node_relationships(current_node)
481
- # for relationship in next_relationships:
482
- # node = self.graph.nodes[relationship.target_node_id]
483
- # next_nodes.append(node)
484
- # further_relationships = self.get_node_relationships(node)
485
- # for f_relationship in further_relationships:
486
- # if f_relationship.bundle:
487
- # next_nodes.append(self.graph.nodes[f_relationship.target_node_id])
488
- # return next_nodes
489
-
490
444
  @staticmethod
491
445
  def parse_to_action(instruction: BaseNode, bundled_nodes: deque):
492
446
  action_node = ActionNode(instruction)
@@ -501,12 +455,36 @@ class Structure(BaseRelatableNode):
501
455
  raise ValueError("Invalid bundles nodes")
502
456
  return action_node
503
457
 
504
- def get_next_step(self, current_node: BaseNode):
505
- next_nodes = deque()
458
+ async def check_condition(self, relationship, executable_id):
459
+ if relationship.condition.source_type == "structure":
460
+ return self.check_condition_structure(relationship)
461
+ elif relationship.condition.source_type == "executable":
462
+ self.send(
463
+ recipient_id=executable_id, category="condition", package=relationship
464
+ )
465
+ while self.condition_check_result is None:
466
+ await AsyncUtil.sleep(0.1)
467
+ self.process_relationship_condition(relationship.id_)
468
+ continue
469
+ check_result = self.condition_check_result
470
+ self.condition_check_result = None
471
+ return check_result
472
+ else:
473
+ raise ValueError(f"Invalid source_type.")
474
+
475
+ def check_condition_structure(self, relationship):
476
+ return relationship.condition(self)
477
+
478
+ async def get_next_step(self, current_node: BaseNode, executable_id):
479
+ next_nodes = []
506
480
  next_relationships = self.get_node_relationships(current_node)
507
481
  for relationship in next_relationships:
508
482
  if relationship.bundle:
509
483
  continue
484
+ if relationship.condition:
485
+ check = await self.check_condition(relationship, executable_id)
486
+ if not check:
487
+ continue
510
488
  node = self.graph.nodes[relationship.target_node_id]
511
489
  further_relationships = self.get_node_relationships(node)
512
490
  bundled_nodes = deque()
@@ -559,7 +537,21 @@ class Structure(BaseRelatableNode):
559
537
  )
560
538
  self.pending_outs.append(mail)
561
539
 
562
- def process(self) -> None:
540
+ def process_relationship_condition(self, relationship_id):
541
+ for key in list(self.pending_ins.keys()):
542
+ skipped_requests = deque()
543
+ while self.pending_ins[key]:
544
+ mail = self.pending_ins[key].popleft()
545
+ if (
546
+ mail.category == "condition"
547
+ and mail.package["relationship_id"] == relationship_id
548
+ ):
549
+ self.condition_check_result = mail.package["check_result"]
550
+ else:
551
+ skipped_requests.append(mail)
552
+ self.pending_ins[key] = skipped_requests
553
+
554
+ async def process(self) -> None:
563
555
  for key in list(self.pending_ins.keys()):
564
556
  while self.pending_ins[key]:
565
557
  mail = self.pending_ins[key].popleft()
@@ -573,13 +565,15 @@ class Structure(BaseRelatableNode):
573
565
  raise ValueError(
574
566
  f"Node {mail.package} does not exist in the structure {self.id_}"
575
567
  )
576
- next_nodes = self.get_next_step(self.graph.nodes[mail.package])
568
+ next_nodes = await self.get_next_step(
569
+ self.graph.nodes[mail.package], mail.sender_id
570
+ )
577
571
  elif mail.category == "node" and isinstance(mail.package, BaseNode):
578
572
  if not self.node_exist(mail.package):
579
573
  raise ValueError(
580
574
  f"Node {mail.package} does not exist in the structure {self.id_}"
581
575
  )
582
- next_nodes = self.get_next_step(mail.package)
576
+ next_nodes = await self.get_next_step(mail.package, mail.sender_id)
583
577
  else:
584
578
  raise ValueError(f"Invalid mail type for structure")
585
579
 
@@ -587,16 +581,24 @@ class Structure(BaseRelatableNode):
587
581
  self.send(
588
582
  recipient_id=mail.sender_id, category="end", package="end"
589
583
  )
590
- while next_nodes:
591
- package = next_nodes.popleft()
592
- self.send(
593
- recipient_id=mail.sender_id, category="node", package=package
594
- )
584
+ else:
585
+ if len(next_nodes) == 1:
586
+ self.send(
587
+ recipient_id=mail.sender_id,
588
+ category="node",
589
+ package=next_nodes[0],
590
+ )
591
+ else:
592
+ self.send(
593
+ recipient_id=mail.sender_id,
594
+ category="node_list",
595
+ package=next_nodes,
596
+ )
595
597
 
596
598
  async def execute(self, refresh_time=1):
597
599
  if not self.acyclic():
598
600
  raise ValueError("Structure is not acyclic")
599
601
 
600
602
  while not self.execute_stop:
601
- self.process()
603
+ await self.process()
602
604
  await AsyncUtil.sleep(refresh_time)
@@ -1,5 +1,3 @@
1
1
  from .session import Session
2
- from lionagi.core.branch.branch import Branch
3
2
 
4
-
5
- __all__ = ["Session", "Branch"]
3
+ __all__ = ["Session"]