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,384 @@
1
+ from collections import deque
2
+ import json
3
+ from typing import Callable
4
+
5
+ from lionagi.core.executor.base_executor import BaseExecutor
6
+ from lionagi.integrations.storage.neo4j import Neo4j
7
+ from lionagi.integrations.storage.storage_util import ParseNode
8
+ from lionagi.core.agent.base_agent import BaseAgent
9
+ from lionagi.core.engine.instruction_map_engine import InstructionMapEngine
10
+
11
+ from lionagi.core.mail import Mail
12
+ from lionagi.core.action import Tool, DirectiveSelection, ActionNode
13
+ from lionagi.core.generic.edge import Edge
14
+ from lionagi.core.collections.progression import progression
15
+
16
+ from lionagi.libs import AsyncUtil
17
+
18
+
19
+ class Neo4jExecutor(BaseExecutor):
20
+ """
21
+ Executes tasks within a Neo4j graph database, handling dynamic instruction flows and conditional logic across various nodes and agents.
22
+
23
+ Attributes:
24
+ driver (Neo4j | None): Connection driver to the Neo4j database.
25
+ structure_id (str | None): Identifier for the structure being executed within the graph.
26
+ structure_name (str | None): Name of the structure being executed.
27
+ middle_agents (list | None): List of agents operating within the structure.
28
+ default_agent_executable (BaseExecutor): Default executor for running tasks not handled by specific agents.
29
+ condition_check_result (bool | None): Result of the last condition check performed during execution.
30
+ """
31
+
32
+ driver: Neo4j | None
33
+ structure_id: str = None
34
+ structure_name: str = None
35
+ middle_agents: list | None = None
36
+ default_agent_executable: BaseExecutor = InstructionMapEngine()
37
+ condition_check_result: bool | None = None
38
+
39
+ class Config:
40
+ arbitrary_types_allowed = True
41
+
42
+ async def check_edge_condition(
43
+ self, condition, executable_id, request_source, head, tail
44
+ ):
45
+ """
46
+ Evaluates the condition associated with an edge in the graph, determining if execution should proceed along that edge.
47
+
48
+ Args:
49
+ condition: The condition object or logic to be evaluated.
50
+ executable_id (str): ID of the executor responsible for this condition check.
51
+ request_source (str): Origin of the request prompting this check.
52
+ head (str): ID of the head node in the edge.
53
+ tail (str): ID of the tail node in the edge.
54
+
55
+ Returns:
56
+ bool: Result of the condition check.
57
+ """
58
+ if condition.source == "structure":
59
+ return condition.applies(self)
60
+ elif condition.source == "executable":
61
+ return await self._check_executable_condition(
62
+ condition, executable_id, head, tail, request_source
63
+ )
64
+
65
+ def _process_edge_condition(self, edge_id):
66
+ """
67
+ Process the condition of a edge.
68
+
69
+ Args:
70
+ edge_id (str): The ID of the edge.
71
+ """
72
+ for key in list(self.mailbox.pending_ins.keys()):
73
+ skipped_requests = progression()
74
+ while self.mailbox.pending_ins[key].size() > 0:
75
+ mail_id = self.mailbox.pending_ins[key].popleft()
76
+ mail = self.mailbox.pile[mail_id]
77
+ if (
78
+ mail.category == "condition"
79
+ and mail.package.package["edge_id"] == edge_id
80
+ ):
81
+ self.mailbox.pile.pop(mail_id)
82
+ self.condition_check_result = mail.package.package["check_result"]
83
+ else:
84
+ skipped_requests.append(mail)
85
+ self.mailbox.pending_ins[key] = skipped_requests
86
+
87
+ async def _check_executable_condition(
88
+ self, condition, executable_id, head, tail, request_source
89
+ ):
90
+ """
91
+ Sends a condition to be checked by an external executable and awaits the result.
92
+
93
+ Args:
94
+ condition: The condition object to be evaluated.
95
+ executable_id (str): ID of the executable that will evaluate the condition.
96
+ head (str): Starting node of the edge.
97
+ tail (str): Ending node of the edge.
98
+ request_source (str): Source of the request for condition evaluation.
99
+
100
+ Returns:
101
+ bool: The result of the condition check.
102
+ """
103
+ edge = Edge(head=head, tail=tail, condition=condition)
104
+ self.send(
105
+ recipient=executable_id,
106
+ category="condition",
107
+ package=edge,
108
+ request_source=request_source,
109
+ )
110
+ while self.condition_check_result is None:
111
+ await AsyncUtil.sleep(0.1)
112
+ self._process_edge_condition(edge.ln_id)
113
+ continue
114
+ check_result = self.condition_check_result
115
+ self.condition_check_result = None
116
+ return check_result
117
+
118
+ @staticmethod
119
+ def parse_bundled_to_action(instruction, bundle_list):
120
+ """
121
+ Parses bundled actions and tools from a list of nodes, creating a composite action node from them.
122
+
123
+ Args:
124
+ instruction: The initial instruction leading to this bundle.
125
+ bundle_list (list): List of nodes bundled together.
126
+
127
+ Returns:
128
+ ActionNode: A node representing a composite action constructed from the bundled nodes.
129
+ """
130
+ bundled_nodes = deque()
131
+ for node_labels, node_properties in bundle_list:
132
+ try:
133
+ if "DirectiveSelection" in node_labels:
134
+ node = ParseNode.parse_directiveSelection(node_properties)
135
+ bundled_nodes.append(node)
136
+ elif "Tool" in node_labels:
137
+ node = ParseNode.parse_tool(node_properties)
138
+ bundled_nodes.append(node)
139
+ else:
140
+ raise ValueError(
141
+ f"Invalid bundle node {node_properties.ln_id}. Valid nodes are ActionSelection or Tool"
142
+ )
143
+ except Exception as e:
144
+ raise ValueError(
145
+ f"Failed to parse ActionSelection or Tool node {node_properties.ln_id}. Error: {e}"
146
+ )
147
+
148
+ action_node = ActionNode(instruction=instruction)
149
+ while bundled_nodes:
150
+ node = bundled_nodes.popleft()
151
+ if isinstance(node, DirectiveSelection):
152
+ action_node.directive = node.directive
153
+ action_node.directive_kwargs = node.directive_kwargs
154
+ elif isinstance(node, Tool):
155
+ action_node.tools.append(node)
156
+ return action_node
157
+
158
+ def parse_agent(self, node_properties):
159
+ """
160
+ Parses agent properties and creates an agent executor.
161
+
162
+ Args:
163
+ node_properties (dict): Properties defining the agent.
164
+
165
+ Returns:
166
+ BaseAgent: An agent executor configured with the given properties.
167
+ """
168
+ output_parser = (
169
+ ParseNode.convert_to_def(node_properties["outputParser"])
170
+ if "outputParser" in node_properties
171
+ else None
172
+ )
173
+
174
+ structure = Neo4jExecutor(
175
+ driver=self.driver, structure_id=node_properties["structureId"]
176
+ )
177
+ agent = BaseAgent(
178
+ structure=structure,
179
+ executable=self.default_agent_executable,
180
+ output_parser=output_parser,
181
+ ln_id=node_properties["ln_id"],
182
+ timestamp=node_properties["timestamp"],
183
+ )
184
+ return agent
185
+
186
+ async def _next_node(
187
+ self, query_list, node_id=None, executable_id=None, request_source=None
188
+ ):
189
+ """
190
+ Processes the next set of nodes based on the results of a query list, applying conditions and preparing nodes
191
+ for further execution.
192
+
193
+ Args:
194
+ query_list (list): List of nodes and their properties.
195
+ node_id (str | None): Current node ID, if applicable.
196
+ executable_id (str | None): ID of the executor handling these nodes.
197
+ request_source (str | None): Source of the node processing request.
198
+
199
+ Returns:
200
+ list: Next nodes ready for processing.
201
+ """
202
+ next_nodes = []
203
+ for edge_properties, node_labels, node_properties in query_list:
204
+ if "condition" in edge_properties.keys():
205
+ try:
206
+ condition = json.loads(edge_properties["condition"])
207
+ condition_cls = await self.driver.get_condition_cls_code(
208
+ condition["class"]
209
+ )
210
+ condition_obj = ParseNode.parse_condition(condition, condition_cls)
211
+
212
+ head = node_id
213
+ tail = node_properties["ln_id"]
214
+ check = await self.check_edge_condition(
215
+ condition_obj, executable_id, request_source, head, tail
216
+ )
217
+ if not check:
218
+ continue
219
+ except Exception as e:
220
+ raise ValueError(
221
+ f"Failed to use condition {edge_properties['condition']} from {node_id} to {node_properties['ln_id']}, Error: {e}"
222
+ )
223
+
224
+ try:
225
+ if "System" in node_labels:
226
+ node = ParseNode.parse_system(node_properties)
227
+ elif "Instruction" in node_labels:
228
+ node = ParseNode.parse_instruction(node_properties)
229
+ elif "Agent" in node_labels:
230
+ node = self.parse_agent(node_properties)
231
+
232
+ else:
233
+ raise ValueError(
234
+ f"Invalid start node {node_properties.ln_id}. Valid nodes are System or Instruction"
235
+ )
236
+ except Exception as e:
237
+ raise ValueError(
238
+ f"Failed to parse System or Instruction node {node_properties.ln_id}. Error: {e}"
239
+ )
240
+
241
+ bundle_list = await self.driver.get_bundle(node.ln_id)
242
+
243
+ if bundle_list and "System" in node_labels:
244
+ raise ValueError("System node does not support bundle edge")
245
+ if bundle_list:
246
+ node = self.parse_bundled_to_action(node, bundle_list)
247
+ next_nodes.append(node)
248
+ return next_nodes
249
+
250
+ async def _handle_start(self):
251
+ """
252
+ Handles the start of execution, fetching and processing head nodes from the structure.
253
+
254
+ Raises:
255
+ ValueError: If there is an issue with finding or starting the structure.
256
+ """
257
+ try:
258
+ id_, head_list = await self.driver.get_heads(
259
+ self.structure_name, self.structure_id
260
+ )
261
+ self.structure_id = id_
262
+ return await self._next_node(head_list)
263
+ except Exception as e:
264
+ raise ValueError(f"Error in searching for structure in Neo4j. Error: {e}")
265
+
266
+ async def _handle_node_id(self, node_id, executable_id, request_source):
267
+ """
268
+ Handles the processing of a specific node ID, fetching its forward connections and conditions.
269
+
270
+ Args:
271
+ node_id (str): The node ID to process.
272
+ executable_id (str): ID of the executor handling this node.
273
+ request_source (str): Source of the node processing request.
274
+
275
+ Returns:
276
+ list: Next nodes derived from the given node ID.
277
+ """
278
+ check = await self.driver.node_exist(node_id)
279
+ if not check:
280
+ raise ValueError(f"Node {node_id} if not found in the database")
281
+ node_list = await self.driver.get_forwards(node_id)
282
+ return await self._next_node(node_list, node_id, executable_id, request_source)
283
+
284
+ async def _handle_mail(self, mail: Mail):
285
+ """
286
+ Processes incoming mail, determining the next action based on the mail's category and content.
287
+
288
+ Args:
289
+ mail (Mail): The incoming mail to be processed.
290
+
291
+ Raises:
292
+ ValueError: If there is an error processing the mail.
293
+ """
294
+ if mail.category == "start":
295
+ try:
296
+ return await self._handle_start()
297
+ except Exception as e:
298
+ raise ValueError(f"Error in start. Error: {e}")
299
+
300
+ elif mail.category == "end":
301
+ self.execute_stop = True
302
+ return None
303
+
304
+ elif mail.category == "node_id":
305
+ try:
306
+ node_id = mail.package.package
307
+ executable_id = mail.sender
308
+ request_source = mail.package.request_source
309
+ return await self._handle_node_id(
310
+ node_id, executable_id, request_source
311
+ )
312
+ except Exception as e:
313
+ raise ValueError(f"Error in handling node_id: {e}")
314
+ elif mail.category == "node":
315
+ try:
316
+ node_id = mail.package.package.ln_id
317
+ executable_id = mail.sender
318
+ request_source = mail.package.request_source
319
+ return await self._handle_node_id(
320
+ node_id, executable_id, request_source
321
+ )
322
+ except Exception as e:
323
+ raise ValueError(f"Error in handling node: {e}")
324
+ else:
325
+ raise ValueError(f"Invalid mail type for structure")
326
+
327
+ def _send_mail(self, next_nodes: list | None, mail: Mail):
328
+ """
329
+ Sends out mail to the next nodes or marks the execution as ended if there are no next nodes.
330
+
331
+ Args:
332
+ next_nodes (list | None): List of next nodes to which mail should be sent.
333
+ mail (Mail): The current mail being processed.
334
+ """
335
+ if not next_nodes: # tail
336
+ self.send(
337
+ recipient=mail.sender,
338
+ category="end",
339
+ package="end",
340
+ request_source=mail.package.request_source,
341
+ )
342
+ else:
343
+ if len(next_nodes) == 1:
344
+ self.send(
345
+ recipient=mail.sender,
346
+ category="node",
347
+ package=next_nodes[0],
348
+ request_source=mail.package.request_source,
349
+ )
350
+ else:
351
+ self.send(
352
+ recipient=mail.sender,
353
+ category="node_list",
354
+ package=next_nodes,
355
+ request_source=mail.package.request_source,
356
+ )
357
+
358
+ async def forward(self) -> None:
359
+ """
360
+ Forwards execution by processing all pending mails and advancing to next nodes or actions.
361
+ """
362
+ for key in list(self.mailbox.pending_ins.keys()):
363
+ while self.mailbox.pending_ins[key].size() > 0:
364
+ mail_id = self.mailbox.pending_ins[key].popleft()
365
+ mail = self.mailbox.pile.pop(mail_id)
366
+ try:
367
+ if mail == "end":
368
+ self.execute_stop = True
369
+ return
370
+ next_nodes = await self._handle_mail(mail)
371
+ self._send_mail(next_nodes, mail)
372
+ except Exception as e:
373
+ raise ValueError(f"Error handling mail: {e}") from e
374
+
375
+ async def execute(self, refresh_time=1):
376
+ """
377
+ Continuously executes the forward process at specified intervals until instructed to stop.
378
+
379
+ Args:
380
+ refresh_time (int): The time in seconds between execution cycles.
381
+ """
382
+ while not self.execute_stop:
383
+ await self.forward()
384
+ await AsyncUtil.sleep(refresh_time)
@@ -0,0 +1,7 @@
1
+ from .edge import Edge
2
+ from .node import Node
3
+ from .graph import Graph
4
+ from .tree import Tree
5
+
6
+
7
+ __all__ = ["Edge", "Node", "Graph", "Tree"]
@@ -0,0 +1,112 @@
1
+ from pydantic import Field, field_validator
2
+ from typing import Any
3
+ from lionagi.core.collections.abc import Component, get_lion_id, LionIDable, Condition
4
+ from lionagi.core.generic.edge_condition import EdgeCondition
5
+
6
+
7
+ class Edge(Component):
8
+ """Represents a directed edge between two nodes in a graph."""
9
+
10
+ head: str = Field(
11
+ ...,
12
+ title="Head",
13
+ description="The identifier of the head node of the edge.",
14
+ )
15
+
16
+ tail: str = Field(
17
+ ...,
18
+ title="Out",
19
+ description="The identifier of the tail node of the edge.",
20
+ )
21
+
22
+ condition: Condition | EdgeCondition | None = Field(
23
+ default=None,
24
+ description="Optional condition that must be met for the edge "
25
+ "to be considered active.",
26
+ )
27
+
28
+ label: str | None = Field(
29
+ default=None,
30
+ description="An optional label for the edge.",
31
+ )
32
+
33
+ bundle: bool = Field(
34
+ default=False,
35
+ description="A flag indicating if the edge is bundled.",
36
+ )
37
+
38
+ async def check_condition(self, obj: Any) -> bool:
39
+ """Check if the edge condition is met for the given object."""
40
+ if not self.condition:
41
+ raise ValueError("The condition for the edge is not set.")
42
+ check = await self.condition.applies(obj)
43
+ return check
44
+
45
+ @field_validator("head", "tail", mode="before")
46
+ def _validate_head_tail(cls, value):
47
+ """Validate the head and tail fields."""
48
+ return get_lion_id(value)
49
+
50
+ def string_condition(self):
51
+ """
52
+ Retrieves the condition class source code as a string.
53
+
54
+ This method is useful for serialization and debugging, allowing
55
+ the condition logic to be inspected or stored in a human-readable
56
+ format. It employs advanced introspection techniques to locate and
57
+ extract the exact class definition, handling edge cases like
58
+ dynamically defined classes or classes defined interactively.
59
+
60
+ Returns:
61
+ str | None: The condition class source code if available.
62
+ If the condition is None or the source code cannot be
63
+ located, this method returns None.
64
+
65
+ Raises:
66
+ TypeError: If the condition class source code cannot be found
67
+ due to the class being defined in a non-standard manner or
68
+ in the interactive interpreter (__main__ context).
69
+ """
70
+ if self.condition is None:
71
+ return
72
+
73
+ import inspect, sys
74
+
75
+ def new_getfile(object, _old_getfile=inspect.getfile):
76
+ if not inspect.isclass(object):
77
+ return _old_getfile(object)
78
+
79
+ # Lookup by parent module (as in current inspect)
80
+ if hasattr(object, "__module__"):
81
+ object_ = sys.modules.get(object.__module__)
82
+ if hasattr(object_, "__file__"):
83
+ return object_.__file__
84
+
85
+ # If parent module is __main__, lookup by methods (NEW)
86
+ for name, member in inspect.getmembers(object):
87
+ if (
88
+ inspect.isfunction(member)
89
+ and object.__qualname__ + "." + member.__name__
90
+ == member.__qualname__
91
+ ):
92
+ return inspect.getfile(member)
93
+ else:
94
+ raise TypeError("Source for {!r} not found".format(object))
95
+
96
+ inspect.getfile = new_getfile
97
+
98
+ import inspect
99
+ from IPython.core.magics.code import extract_symbols
100
+
101
+ obj = self.condition.__class__
102
+ cell_code = "".join(inspect.linecache.getlines(new_getfile(obj)))
103
+ class_code = extract_symbols(cell_code, obj.__name__)[0][0]
104
+ return class_code
105
+
106
+ def __len__(self):
107
+ """Return the length of the edge (always 1)."""
108
+ return 1
109
+
110
+ def __contains__(self, item: LionIDable) -> bool:
111
+ """Check if the given item is the head or tail of the edge."""
112
+ return get_lion_id(item) in (self.head, self.tail)
@@ -0,0 +1,16 @@
1
+ from typing import Any
2
+ from pydantic import Field
3
+ from lionagi.core.collections.abc import Condition
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class EdgeCondition(Condition, BaseModel):
8
+ source: Any = Field(
9
+ title="Source",
10
+ description="The source for condition check",
11
+ )
12
+
13
+ class Config:
14
+ """Model configuration settings."""
15
+
16
+ extra = "allow"