lionagi 0.0.312__py3-none-any.whl → 0.2.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 (268) hide show
  1. lionagi/__init__.py +61 -3
  2. lionagi/core/__init__.py +0 -14
  3. lionagi/core/_setting/_setting.py +59 -0
  4. lionagi/core/action/__init__.py +14 -0
  5. lionagi/core/action/function_calling.py +136 -0
  6. lionagi/core/action/manual.py +1 -0
  7. lionagi/core/action/node.py +109 -0
  8. lionagi/core/action/tool.py +114 -0
  9. lionagi/core/action/tool_manager.py +356 -0
  10. lionagi/core/agent/__init__.py +0 -3
  11. lionagi/core/agent/base_agent.py +45 -36
  12. lionagi/core/agent/eval/evaluator.py +1 -0
  13. lionagi/core/agent/eval/vote.py +40 -0
  14. lionagi/core/agent/learn/learner.py +59 -0
  15. lionagi/core/agent/plan/unit_template.py +1 -0
  16. lionagi/core/collections/__init__.py +17 -0
  17. lionagi/core/collections/_logger.py +319 -0
  18. lionagi/core/collections/abc/__init__.py +53 -0
  19. lionagi/core/collections/abc/component.py +615 -0
  20. lionagi/core/collections/abc/concepts.py +297 -0
  21. lionagi/core/collections/abc/exceptions.py +150 -0
  22. lionagi/core/collections/abc/util.py +45 -0
  23. lionagi/core/collections/exchange.py +161 -0
  24. lionagi/core/collections/flow.py +426 -0
  25. lionagi/core/collections/model.py +419 -0
  26. lionagi/core/collections/pile.py +913 -0
  27. lionagi/core/collections/progression.py +236 -0
  28. lionagi/core/collections/util.py +64 -0
  29. lionagi/core/director/direct.py +314 -0
  30. lionagi/core/director/director.py +2 -0
  31. lionagi/core/engine/branch_engine.py +333 -0
  32. lionagi/core/engine/instruction_map_engine.py +204 -0
  33. lionagi/core/engine/sandbox_.py +14 -0
  34. lionagi/core/engine/script_engine.py +99 -0
  35. lionagi/core/executor/base_executor.py +90 -0
  36. lionagi/core/executor/graph_executor.py +330 -0
  37. lionagi/core/executor/neo4j_executor.py +384 -0
  38. lionagi/core/generic/__init__.py +7 -0
  39. lionagi/core/generic/edge.py +112 -0
  40. lionagi/core/generic/edge_condition.py +16 -0
  41. lionagi/core/generic/graph.py +236 -0
  42. lionagi/core/generic/hyperedge.py +1 -0
  43. lionagi/core/generic/node.py +220 -0
  44. lionagi/core/generic/tree.py +48 -0
  45. lionagi/core/generic/tree_node.py +79 -0
  46. lionagi/core/mail/__init__.py +7 -3
  47. lionagi/core/mail/mail.py +25 -0
  48. lionagi/core/mail/mail_manager.py +142 -58
  49. lionagi/core/mail/package.py +45 -0
  50. lionagi/core/mail/start_mail.py +36 -0
  51. lionagi/core/message/__init__.py +19 -0
  52. lionagi/core/message/action_request.py +133 -0
  53. lionagi/core/message/action_response.py +135 -0
  54. lionagi/core/message/assistant_response.py +95 -0
  55. lionagi/core/message/instruction.py +234 -0
  56. lionagi/core/message/message.py +101 -0
  57. lionagi/core/message/system.py +86 -0
  58. lionagi/core/message/util.py +283 -0
  59. lionagi/core/report/__init__.py +4 -0
  60. lionagi/core/report/base.py +217 -0
  61. lionagi/core/report/form.py +231 -0
  62. lionagi/core/report/report.py +166 -0
  63. lionagi/core/report/util.py +28 -0
  64. lionagi/core/rule/__init__.py +0 -0
  65. lionagi/core/rule/_default.py +16 -0
  66. lionagi/core/rule/action.py +99 -0
  67. lionagi/core/rule/base.py +238 -0
  68. lionagi/core/rule/boolean.py +56 -0
  69. lionagi/core/rule/choice.py +47 -0
  70. lionagi/core/rule/mapping.py +96 -0
  71. lionagi/core/rule/number.py +71 -0
  72. lionagi/core/rule/rulebook.py +109 -0
  73. lionagi/core/rule/string.py +52 -0
  74. lionagi/core/rule/util.py +35 -0
  75. lionagi/core/session/__init__.py +0 -3
  76. lionagi/core/session/branch.py +431 -0
  77. lionagi/core/session/directive_mixin.py +287 -0
  78. lionagi/core/session/session.py +230 -902
  79. lionagi/core/structure/__init__.py +1 -0
  80. lionagi/core/structure/chain.py +1 -0
  81. lionagi/core/structure/forest.py +1 -0
  82. lionagi/core/structure/graph.py +1 -0
  83. lionagi/core/structure/tree.py +1 -0
  84. lionagi/core/unit/__init__.py +5 -0
  85. lionagi/core/unit/parallel_unit.py +245 -0
  86. lionagi/core/unit/template/__init__.py +0 -0
  87. lionagi/core/unit/template/action.py +81 -0
  88. lionagi/core/unit/template/base.py +51 -0
  89. lionagi/core/unit/template/plan.py +84 -0
  90. lionagi/core/unit/template/predict.py +109 -0
  91. lionagi/core/unit/template/score.py +124 -0
  92. lionagi/core/unit/template/select.py +104 -0
  93. lionagi/core/unit/unit.py +362 -0
  94. lionagi/core/unit/unit_form.py +305 -0
  95. lionagi/core/unit/unit_mixin.py +1168 -0
  96. lionagi/core/unit/util.py +71 -0
  97. lionagi/core/validator/__init__.py +0 -0
  98. lionagi/core/validator/validator.py +364 -0
  99. lionagi/core/work/__init__.py +0 -0
  100. lionagi/core/work/work.py +76 -0
  101. lionagi/core/work/work_function.py +101 -0
  102. lionagi/core/work/work_queue.py +103 -0
  103. lionagi/core/work/worker.py +258 -0
  104. lionagi/core/work/worklog.py +120 -0
  105. lionagi/experimental/__init__.py +0 -0
  106. lionagi/experimental/compressor/__init__.py +0 -0
  107. lionagi/experimental/compressor/base.py +46 -0
  108. lionagi/experimental/compressor/llm_compressor.py +247 -0
  109. lionagi/experimental/compressor/llm_summarizer.py +61 -0
  110. lionagi/experimental/compressor/util.py +70 -0
  111. lionagi/experimental/directive/__init__.py +19 -0
  112. lionagi/experimental/directive/parser/__init__.py +0 -0
  113. lionagi/experimental/directive/parser/base_parser.py +282 -0
  114. lionagi/experimental/directive/template/__init__.py +0 -0
  115. lionagi/experimental/directive/template/base_template.py +79 -0
  116. lionagi/experimental/directive/template/schema.py +36 -0
  117. lionagi/experimental/directive/tokenizer.py +73 -0
  118. lionagi/experimental/evaluator/__init__.py +0 -0
  119. lionagi/experimental/evaluator/ast_evaluator.py +131 -0
  120. lionagi/experimental/evaluator/base_evaluator.py +218 -0
  121. lionagi/experimental/knowledge/__init__.py +0 -0
  122. lionagi/experimental/knowledge/base.py +10 -0
  123. lionagi/experimental/knowledge/graph.py +0 -0
  124. lionagi/experimental/memory/__init__.py +0 -0
  125. lionagi/experimental/strategies/__init__.py +0 -0
  126. lionagi/experimental/strategies/base.py +1 -0
  127. lionagi/integrations/bridge/autogen_/__init__.py +0 -0
  128. lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
  129. lionagi/integrations/bridge/langchain_/documents.py +4 -0
  130. lionagi/integrations/bridge/llamaindex_/index.py +30 -0
  131. lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
  132. lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
  133. lionagi/integrations/bridge/llamaindex_/node_parser.py +6 -9
  134. lionagi/integrations/bridge/pydantic_/pydantic_bridge.py +1 -0
  135. lionagi/integrations/bridge/transformers_/__init__.py +0 -0
  136. lionagi/integrations/bridge/transformers_/install_.py +36 -0
  137. lionagi/integrations/chunker/__init__.py +0 -0
  138. lionagi/integrations/chunker/chunk.py +312 -0
  139. lionagi/integrations/config/oai_configs.py +38 -7
  140. lionagi/integrations/config/ollama_configs.py +1 -1
  141. lionagi/integrations/config/openrouter_configs.py +14 -2
  142. lionagi/integrations/loader/__init__.py +0 -0
  143. lionagi/integrations/loader/load.py +253 -0
  144. lionagi/integrations/loader/load_util.py +195 -0
  145. lionagi/integrations/provider/_mapping.py +46 -0
  146. lionagi/integrations/provider/litellm.py +2 -1
  147. lionagi/integrations/provider/mlx_service.py +16 -9
  148. lionagi/integrations/provider/oai.py +91 -4
  149. lionagi/integrations/provider/ollama.py +7 -6
  150. lionagi/integrations/provider/openrouter.py +115 -8
  151. lionagi/integrations/provider/services.py +2 -2
  152. lionagi/integrations/provider/transformers.py +18 -22
  153. lionagi/integrations/storage/__init__.py +3 -0
  154. lionagi/integrations/storage/neo4j.py +665 -0
  155. lionagi/integrations/storage/storage_util.py +287 -0
  156. lionagi/integrations/storage/structure_excel.py +285 -0
  157. lionagi/integrations/storage/to_csv.py +63 -0
  158. lionagi/integrations/storage/to_excel.py +83 -0
  159. lionagi/libs/__init__.py +26 -1
  160. lionagi/libs/ln_api.py +78 -23
  161. lionagi/libs/ln_context.py +37 -0
  162. lionagi/libs/ln_convert.py +21 -9
  163. lionagi/libs/ln_func_call.py +69 -28
  164. lionagi/libs/ln_image.py +107 -0
  165. lionagi/libs/ln_knowledge_graph.py +405 -0
  166. lionagi/libs/ln_nested.py +26 -11
  167. lionagi/libs/ln_parse.py +110 -14
  168. lionagi/libs/ln_queue.py +117 -0
  169. lionagi/libs/ln_tokenize.py +164 -0
  170. lionagi/{core/prompt/field_validator.py → libs/ln_validate.py} +79 -14
  171. lionagi/libs/special_tokens.py +172 -0
  172. lionagi/libs/sys_util.py +107 -2
  173. lionagi/lions/__init__.py +0 -0
  174. lionagi/lions/coder/__init__.py +0 -0
  175. lionagi/lions/coder/add_feature.py +20 -0
  176. lionagi/lions/coder/base_prompts.py +22 -0
  177. lionagi/lions/coder/code_form.py +13 -0
  178. lionagi/lions/coder/coder.py +168 -0
  179. lionagi/lions/coder/util.py +96 -0
  180. lionagi/lions/researcher/__init__.py +0 -0
  181. lionagi/lions/researcher/data_source/__init__.py +0 -0
  182. lionagi/lions/researcher/data_source/finhub_.py +191 -0
  183. lionagi/lions/researcher/data_source/google_.py +199 -0
  184. lionagi/lions/researcher/data_source/wiki_.py +96 -0
  185. lionagi/lions/researcher/data_source/yfinance_.py +21 -0
  186. lionagi/tests/integrations/__init__.py +0 -0
  187. lionagi/tests/libs/__init__.py +0 -0
  188. lionagi/tests/libs/test_field_validators.py +353 -0
  189. lionagi/tests/{test_libs → libs}/test_func_call.py +23 -21
  190. lionagi/tests/{test_libs → libs}/test_nested.py +36 -21
  191. lionagi/tests/{test_libs → libs}/test_parse.py +1 -1
  192. lionagi/tests/libs/test_queue.py +67 -0
  193. lionagi/tests/test_core/collections/__init__.py +0 -0
  194. lionagi/tests/test_core/collections/test_component.py +206 -0
  195. lionagi/tests/test_core/collections/test_exchange.py +138 -0
  196. lionagi/tests/test_core/collections/test_flow.py +145 -0
  197. lionagi/tests/test_core/collections/test_pile.py +171 -0
  198. lionagi/tests/test_core/collections/test_progression.py +129 -0
  199. lionagi/tests/test_core/generic/__init__.py +0 -0
  200. lionagi/tests/test_core/generic/test_edge.py +67 -0
  201. lionagi/tests/test_core/generic/test_graph.py +96 -0
  202. lionagi/tests/test_core/generic/test_node.py +106 -0
  203. lionagi/tests/test_core/generic/test_tree_node.py +73 -0
  204. lionagi/tests/test_core/test_branch.py +115 -292
  205. lionagi/tests/test_core/test_form.py +46 -0
  206. lionagi/tests/test_core/test_report.py +105 -0
  207. lionagi/tests/test_core/test_validator.py +111 -0
  208. lionagi/version.py +1 -1
  209. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/LICENSE +12 -11
  210. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/METADATA +19 -118
  211. lionagi-0.2.1.dist-info/RECORD +240 -0
  212. lionagi/core/branch/__init__.py +0 -4
  213. lionagi/core/branch/base_branch.py +0 -654
  214. lionagi/core/branch/branch.py +0 -471
  215. lionagi/core/branch/branch_flow_mixin.py +0 -96
  216. lionagi/core/branch/executable_branch.py +0 -347
  217. lionagi/core/branch/util.py +0 -323
  218. lionagi/core/direct/__init__.py +0 -6
  219. lionagi/core/direct/predict.py +0 -161
  220. lionagi/core/direct/score.py +0 -278
  221. lionagi/core/direct/select.py +0 -169
  222. lionagi/core/direct/utils.py +0 -87
  223. lionagi/core/direct/vote.py +0 -64
  224. lionagi/core/flow/base/baseflow.py +0 -23
  225. lionagi/core/flow/monoflow/ReAct.py +0 -238
  226. lionagi/core/flow/monoflow/__init__.py +0 -9
  227. lionagi/core/flow/monoflow/chat.py +0 -95
  228. lionagi/core/flow/monoflow/chat_mixin.py +0 -263
  229. lionagi/core/flow/monoflow/followup.py +0 -214
  230. lionagi/core/flow/polyflow/__init__.py +0 -1
  231. lionagi/core/flow/polyflow/chat.py +0 -248
  232. lionagi/core/mail/schema.py +0 -56
  233. lionagi/core/messages/__init__.py +0 -3
  234. lionagi/core/messages/schema.py +0 -533
  235. lionagi/core/prompt/prompt_template.py +0 -316
  236. lionagi/core/schema/__init__.py +0 -22
  237. lionagi/core/schema/action_node.py +0 -29
  238. lionagi/core/schema/base_mixin.py +0 -296
  239. lionagi/core/schema/base_node.py +0 -199
  240. lionagi/core/schema/condition.py +0 -24
  241. lionagi/core/schema/data_logger.py +0 -354
  242. lionagi/core/schema/data_node.py +0 -93
  243. lionagi/core/schema/prompt_template.py +0 -67
  244. lionagi/core/schema/structure.py +0 -910
  245. lionagi/core/tool/__init__.py +0 -3
  246. lionagi/core/tool/tool_manager.py +0 -280
  247. lionagi/integrations/bridge/pydantic_/base_model.py +0 -7
  248. lionagi/tests/test_core/test_base_branch.py +0 -427
  249. lionagi/tests/test_core/test_chat_flow.py +0 -63
  250. lionagi/tests/test_core/test_mail_manager.py +0 -75
  251. lionagi/tests/test_core/test_prompts.py +0 -51
  252. lionagi/tests/test_core/test_session.py +0 -254
  253. lionagi/tests/test_core/test_session_base_util.py +0 -312
  254. lionagi/tests/test_core/test_tool_manager.py +0 -95
  255. lionagi-0.0.312.dist-info/RECORD +0 -111
  256. /lionagi/core/{branch/base → _setting}/__init__.py +0 -0
  257. /lionagi/core/{flow → agent/eval}/__init__.py +0 -0
  258. /lionagi/core/{flow/base → agent/learn}/__init__.py +0 -0
  259. /lionagi/core/{prompt → agent/plan}/__init__.py +0 -0
  260. /lionagi/core/{tool/manual.py → agent/plan/plan.py} +0 -0
  261. /lionagi/{tests/test_integrations → core/director}/__init__.py +0 -0
  262. /lionagi/{tests/test_libs → core/engine}/__init__.py +0 -0
  263. /lionagi/{tests/test_libs/test_async.py → core/executor/__init__.py} +0 -0
  264. /lionagi/tests/{test_libs → libs}/test_api.py +0 -0
  265. /lionagi/tests/{test_libs → libs}/test_convert.py +0 -0
  266. /lionagi/tests/{test_libs → libs}/test_sys_util.py +0 -0
  267. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/WHEEL +0 -0
  268. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,90 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ from pydantic import Field
5
+
6
+ from lionagi.core.collections.abc import Element, Progressable, Executable
7
+ from lionagi.core.collections import Exchange
8
+ from lionagi.core.mail.mail import Mail, Package
9
+
10
+
11
+ class BaseExecutor(Element, Progressable, Executable, ABC):
12
+ """
13
+ BaseExecutor is an abstract base class that defines the structure for executors
14
+ handling mails, execution control, and context management within the LionAGI system.
15
+ """
16
+
17
+ mailbox: Exchange = Field(
18
+ default_factory=Exchange[Mail], description="The pending mails."
19
+ )
20
+
21
+ execute_stop: bool = Field(
22
+ False, description="A flag indicating whether to stop execution."
23
+ )
24
+
25
+ context: dict | str | list | None = Field(
26
+ None, description="The context buffer for the next instruction."
27
+ )
28
+
29
+ execution_responses: list = Field(
30
+ default_factory=list, description="The list of responses."
31
+ )
32
+
33
+ context_log: list = Field(default_factory=list, description="The context log.")
34
+
35
+ verbose: bool = Field(
36
+ True, description="A flag indicating whether to provide verbose output."
37
+ )
38
+
39
+ def send(
40
+ self, recipient: str, category: str, package: Any, request_source: str = None
41
+ ) -> None:
42
+ """
43
+ Sends a mail to a recipient.
44
+
45
+ Args:
46
+ recipient (str): The ID of the recipient.
47
+ category (str): The category of the mail.
48
+ package (Any): The package to send in the mail.
49
+ request_source (str): The source of the request.
50
+ """
51
+ pack = Package(
52
+ category=category, package=package, request_source=request_source
53
+ )
54
+ mail = Mail(
55
+ sender=self.ln_id,
56
+ recipient=recipient,
57
+ package=pack,
58
+ )
59
+ self.mailbox.include(mail, "out")
60
+
61
+ @abstractmethod
62
+ async def execute(self, *args: Any, **kwargs: Any) -> Any:
63
+ """
64
+ Execute the executor's main function.
65
+
66
+ Args:
67
+ *args (Any): Positional arguments.
68
+ **kwargs (Any): Keyword arguments.
69
+
70
+ Returns:
71
+ Any: The result of the execution.
72
+
73
+ Raises:
74
+ NotImplementedError: If the method is not implemented.
75
+ """
76
+ raise NotImplementedError("The execute method must be implemented.")
77
+
78
+ @abstractmethod
79
+ async def forward(self, *args: Any, **kwargs: Any) -> None:
80
+ """
81
+ Forward the execution flow.
82
+
83
+ Args:
84
+ *args (Any): Positional arguments.
85
+ **kwargs (Any): Keyword arguments.
86
+
87
+ Raises:
88
+ NotImplementedError: If the method is not implemented.
89
+ """
90
+ raise NotImplementedError("The forward method must be implemented.")
@@ -0,0 +1,330 @@
1
+ from collections import deque
2
+
3
+ from lionagi.libs import AsyncUtil, convert
4
+
5
+ from lionagi.core.generic.node import Node
6
+ from lionagi.core.generic.edge import Edge
7
+ from lionagi.core.executor.base_executor import BaseExecutor
8
+
9
+ from lionagi.core.action import Tool, DirectiveSelection, ActionNode
10
+
11
+ from lionagi.core.mail import Mail
12
+ from lionagi.core.generic.graph import Graph
13
+ from lionagi.core.collections.progression import progression
14
+
15
+
16
+ class GraphExecutor(BaseExecutor, Graph):
17
+ """
18
+ Executes tasks within a graph structure, handling dynamic node flows and conditional edge logic.
19
+
20
+ Attributes:
21
+ condition_check_result (bool | None): Result of the last condition check performed during execution,
22
+ used to control flow based on dynamic conditions.
23
+ """
24
+
25
+ condition_check_result: bool | None = None
26
+
27
+ async def check_edge_condition(self, edge: Edge, executable_id, request_source):
28
+ """
29
+ Evaluates the condition associated with an edge, determining if execution should proceed along that edge based
30
+ on the condition's source type.
31
+
32
+ Args:
33
+ edge (Edge): The edge whose condition needs to be checked.
34
+ executable_id (str): ID of the executor handling this edge's condition.
35
+ request_source (str): Origin of the request prompting this condition check.
36
+
37
+ Returns:
38
+ bool: Result of the condition evaluation.
39
+
40
+ Raises:
41
+ ValueError: If the source_type of the condition is invalid.
42
+ """
43
+ if edge.condition.source == "structure" or isinstance(
44
+ edge.condition.source, Node
45
+ ):
46
+ return await edge.check_condition(self)
47
+
48
+ elif edge.condition.source == "executable":
49
+ return await self._check_executable_condition(
50
+ edge, executable_id, request_source
51
+ )
52
+
53
+ else:
54
+ raise ValueError("Invalid source_type.")
55
+
56
+ def _process_edge_condition(self, edge_id):
57
+ """
58
+ Process the condition of a edge.
59
+
60
+ Args:
61
+ edge_id (str): The ID of the edge.
62
+ """
63
+ for key in list(self.mailbox.pending_ins.keys()):
64
+ skipped_requests = progression()
65
+ while self.mailbox.pending_ins[key].size() > 0:
66
+ mail_id = self.mailbox.pending_ins[key].popleft()
67
+ mail = self.mailbox.pile[mail_id]
68
+ if (
69
+ mail.category == "condition"
70
+ and mail.package.package["edge_id"] == edge_id
71
+ ):
72
+ self.mailbox.pile.pop(mail_id)
73
+ self.condition_check_result = mail.package.package["check_result"]
74
+ else:
75
+ skipped_requests.append(mail)
76
+ self.mailbox.pending_ins[key] = skipped_requests
77
+
78
+ async def _check_executable_condition(
79
+ self, edge: Edge, executable_id, request_source
80
+ ):
81
+ """
82
+ Sends the edge's condition to an external executable for evaluation and waits for the result.
83
+
84
+ Args:
85
+ edge (Edge): The edge containing the condition to be checked.
86
+ executable_id (str): ID of the executable that will evaluate the condition.
87
+ request_source (str): Source of the request for condition evaluation.
88
+
89
+ Returns:
90
+ bool: The result of the condition check.
91
+ """
92
+ self.send(
93
+ recipient=executable_id,
94
+ category="condition",
95
+ package=edge,
96
+ request_source=request_source,
97
+ )
98
+ while self.condition_check_result is None:
99
+ await AsyncUtil.sleep(0.1)
100
+ self._process_edge_condition(edge.ln_id)
101
+ continue
102
+ check_result = self.condition_check_result
103
+ self.condition_check_result = None
104
+ return check_result
105
+
106
+ async def _handle_node_id(self, mail: Mail):
107
+ """
108
+ Processes the node identified by its ID in the mail's package, ensuring it exists and retrieving the next set of
109
+ nodes based on the current node.
110
+
111
+ Args:
112
+ mail (BaseMail): The mail containing the node ID and related execution details.
113
+
114
+ Raises:
115
+ ValueError: If the node does not exist within the structure.
116
+ """
117
+ if mail.package.package not in self.internal_nodes:
118
+ raise ValueError(
119
+ f"Node {mail.package.package}: Node does not exist in the structure {self.ln_id}"
120
+ )
121
+ return await self._next_node(
122
+ self.internal_nodes[mail.package.package],
123
+ mail.sender,
124
+ mail.package.request_source,
125
+ )
126
+
127
+ async def _handle_node(self, mail: Mail):
128
+ """
129
+ Processes the node specified in the mail's package, ensuring it exists within the structure.
130
+
131
+ Args:
132
+ mail (BaseMail): The mail containing the node details to be processed.
133
+
134
+ Raises:
135
+ ValueError: If the node does not exist within the structure.
136
+ """
137
+ if not self.node_exist(mail.package.package):
138
+ raise ValueError(
139
+ f"Node {mail.package.package.ln_id}: does not exist in the structure {self.ln_id}"
140
+ )
141
+ return await self._next_node(
142
+ mail.package.package, mail.sender, mail.package.request_source
143
+ )
144
+
145
+ async def _handle_mail(self, mail: Mail):
146
+ """
147
+ Processes incoming mail based on its category, initiating node execution or structure operations accordingly.
148
+
149
+ Args:
150
+ mail (BaseMail): The mail to be processed, containing category and package information.
151
+
152
+ Raises:
153
+ ValueError: If the mail type is invalid for the current structure or an error occurs in handling the node ID.
154
+ """
155
+ if mail.category == "start":
156
+ return self.get_heads()
157
+
158
+ elif mail.category == "end":
159
+ self.execute_stop = True
160
+ return None
161
+
162
+ elif mail.category == "node_id":
163
+ try:
164
+ return await self._handle_node_id(mail)
165
+ except Exception as e:
166
+ raise ValueError(f"Error handling node_id: {e}") from e
167
+
168
+ elif mail.category == "node" and isinstance(mail.package.package, Node):
169
+ try:
170
+ return await self._handle_node(mail)
171
+ except Exception as e:
172
+ raise ValueError(f"Error handling node: {e}") from e
173
+
174
+ else:
175
+ raise ValueError(f"Invalid mail type for structure")
176
+
177
+ async def _next_node(self, current_node: Node, executable_id, request_source):
178
+ """
179
+ Get the next step nodes based on the current node.
180
+
181
+ Args:
182
+ current_node (Node): The current node.
183
+ executable_id (str): The ID of the executable.
184
+
185
+ Returns:
186
+ list[Node]: The next step nodes.
187
+ """
188
+ next_nodes = []
189
+ next_edges = self.get_node_edges(current_node, direction="out")
190
+ for edge in convert.to_list(list(next_edges.values())):
191
+ if edge.bundle:
192
+ continue
193
+ if edge.condition:
194
+ check = await self.check_edge_condition(
195
+ edge, executable_id, request_source
196
+ )
197
+ if not check:
198
+ continue
199
+ node = self.internal_nodes[edge.tail]
200
+ further_edges = self.get_node_edges(node, direction="out")
201
+ bundled_nodes = deque()
202
+ for f_edge in convert.to_list(list(further_edges.values())):
203
+ if f_edge.bundle:
204
+ bundled_nodes.append(self.internal_nodes[f_edge.tail])
205
+ if bundled_nodes:
206
+ node = self.parse_bundled_to_action(node, bundled_nodes)
207
+ next_nodes.append(node)
208
+ return next_nodes
209
+
210
+ def _send_mail(self, next_nodes: list | None, mail: Mail):
211
+ """
212
+ Sends mails to the next nodes or signals the end of execution if no next nodes exist.
213
+
214
+ Args:
215
+ next_nodes (list | None): List of next nodes to process or None if no further nodes are available.
216
+ mail (BaseMail): The base mail used for sending follow-up actions.
217
+ """
218
+ if not next_nodes: # tail
219
+ self.send(
220
+ recipient=mail.sender,
221
+ category="end",
222
+ package="end",
223
+ request_source=mail.package.request_source,
224
+ )
225
+ else:
226
+ if len(next_nodes) == 1:
227
+ self.send(
228
+ recipient=mail.sender,
229
+ category="node",
230
+ package=next_nodes[0],
231
+ request_source=mail.package.request_source,
232
+ )
233
+ else:
234
+ self.send(
235
+ recipient=mail.sender,
236
+ category="node_list",
237
+ package=next_nodes,
238
+ request_source=mail.package.request_source,
239
+ )
240
+
241
+ @staticmethod
242
+ def parse_bundled_to_action(instruction: Node, bundled_nodes: deque):
243
+ """
244
+ Constructs an action node from a bundle of nodes, combining various types of nodes like ActionSelection or Tool
245
+ into a single actionable unit.
246
+
247
+ This method takes a bundle of nodes and systematically integrates their functionalities into a single `ActionNode`.
248
+ This is crucial in scenarios where multiple actions or tools need to be executed sequentially or in a coordinated
249
+ manner as part of a larger instruction flow.
250
+
251
+ Args:
252
+ instruction (Node): The initial instruction node leading to this action.
253
+ bundled_nodes (deque): A deque containing nodes to be bundled into the action. These nodes typically represent
254
+ either actions to be taken or tools to be utilized.
255
+
256
+ Returns:
257
+ ActionNode: An `ActionNode` that encapsulates the combined functionality of the bundled nodes, ready for execution.
258
+
259
+ Raises:
260
+ ValueError: If an unrecognized node type is encountered within the bundled nodes. Only `ActionSelection` and
261
+ `Tool` nodes are valid for bundling into an `ActionNode`.
262
+ """
263
+ action_node = ActionNode(instruction=instruction)
264
+ while bundled_nodes:
265
+ node = bundled_nodes.popleft()
266
+ if isinstance(node, DirectiveSelection):
267
+ action_node.directive = node.directive
268
+ action_node.directive_kwargs = node.directive_kwargs
269
+ elif isinstance(node, Tool):
270
+ action_node.tools.append(node)
271
+ else:
272
+ raise ValueError("Invalid bundles nodes")
273
+ return action_node
274
+
275
+ async def forward(self) -> None:
276
+ """
277
+ Process the pending incoming mails and perform the corresponding actions.
278
+ """
279
+ for key in list(self.mailbox.pending_ins.keys()):
280
+ while self.mailbox.pending_ins[key].size() > 0:
281
+ mail_id = self.mailbox.pending_ins[key].popleft()
282
+ mail = self.mailbox.pile.pop(mail_id)
283
+ try:
284
+ if mail.category == "end":
285
+ self.execute_stop = True
286
+ return
287
+ next_nodes = await self._handle_mail(mail)
288
+ self._send_mail(next_nodes, mail)
289
+ except Exception as e:
290
+ raise ValueError(f"Error handling mail: {e}") from e
291
+ if self.mailbox.pending_ins[key].size() == 0:
292
+ self.mailbox.pending_ins.pop(key)
293
+
294
+ async def execute(self, refresh_time=1):
295
+ """
296
+ Executes the forward processing loop, checking conditions and processing nodes at defined intervals.
297
+
298
+ Args:
299
+ refresh_time (int): The delay between execution cycles, allowing for asynchronous operations to complete.
300
+
301
+ Raises:
302
+ ValueError: If the graph structure is found to be cyclic, which is unsupported.
303
+ """
304
+ if not self.is_acyclic():
305
+ raise ValueError("Structure is not acyclic")
306
+
307
+ while not self.execute_stop:
308
+ await self.forward()
309
+ await AsyncUtil.sleep(refresh_time)
310
+
311
+ def to_excel(self, structure_name, dir="structure_storage"):
312
+ """
313
+ Exports the current structure to an Excel file using a specified structure name and directory.
314
+
315
+ This method utilizes the `to_excel` function from the `lionagi.integrations.storage.to_excel` module,
316
+ saving the current structure instance into an Excel file format. The Excel file will contain details
317
+ about nodes, edges, and other relevant data as separate sheets within the file.
318
+
319
+ Args:
320
+ structure_name (str): The name to assign to the structure within the Excel file. This name is
321
+ used as part of the file naming convention.
322
+ dir (str, optional): The directory where the Excel file will be saved. Defaults to "structure_storage".
323
+
324
+ Raises:
325
+ Exception: Propagates any exceptions raised by the `to_excel` function, which might occur during
326
+ the file writing process or data formatting.
327
+ """
328
+ from lionagi.integrations.storage.to_excel import to_excel
329
+
330
+ to_excel(self, structure_name, dir)