lionagi 0.0.306__py3-none-any.whl → 0.0.307__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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"]