lionagi 0.0.312__py3-none-any.whl → 0.2.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,910 +0,0 @@
1
- """
2
- This module contains classes for representing and manipulating graph structures.
3
-
4
- The module includes the following classes:
5
- - Relationship: Represents a relationship between two nodes in a graph.
6
- - Graph: Represents a graph structure, consisting of nodes and their relationships.
7
- - Structure: Represents a structure that extends the Graph class with additional functionality.
8
-
9
- The Structure class adds methods for managing and executing a graph structure, including
10
- adding nodes and relationships, checking conditions, processing incoming and outgoing mails,
11
- and executing the structure.
12
- """
13
-
14
- import time
15
- from typing import List, Any, Dict, Callable
16
- from collections import deque
17
- from pydantic import Field
18
-
19
- from lionagi.libs import SysUtil, func_call, AsyncUtil
20
-
21
- from .base_node import BaseRelatableNode, BaseNode, Tool
22
- from lionagi.core.mail.schema import BaseMail
23
-
24
- from lionagi.core.schema.condition import Condition
25
-
26
- from lionagi.core.schema.action_node import ActionNode, ActionSelection
27
- from lionagi.core.schema.base_node import Tool
28
-
29
-
30
- class Relationship(BaseRelatableNode):
31
- """
32
- Represents a relationship between two nodes in a graph.
33
-
34
- Attributes:
35
- source_node_id (str): The identifier of the source node.
36
- target_node_id (str): The identifier of the target node.
37
- condition (Dict[str, Any]): A dictionary representing conditions for the relationship.
38
-
39
- Examples:
40
- >>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
41
- >>> relationship.add_condition({"key": "value"})
42
- >>> condition_value = relationship.get_condition("key")
43
- >>> relationship.remove_condition("key")
44
- """
45
-
46
- source_node_id: str
47
- target_node_id: str
48
- bundle: bool = False
49
- condition: Callable = None
50
-
51
- def add_condition(self, condition: Condition):
52
- """
53
- Adds a condition to the relationship.
54
-
55
- Args:
56
- condition (Condition): The condition to add.
57
-
58
- Raises:
59
- ValueError: If the condition is not an instance of the Condition class.
60
- """
61
- if not isinstance(condition, Condition):
62
- raise ValueError(
63
- "Invalid condition type, please use Condition class to build a valid condition"
64
- )
65
- self.condition = condition
66
-
67
- def check_condition(self, source_obj):
68
- """
69
- Checks the condition of the relationship.
70
-
71
- Args:
72
- source_obj: The source object to evaluate the condition against.
73
-
74
- Returns:
75
- The result of evaluating the condition.
76
-
77
- Raises:
78
- ValueError: If the relationship condition function is invalid.
79
- """
80
- try:
81
- return bool(self.condition(source_obj))
82
- except:
83
- raise ValueError("Invalid relationship condition function")
84
-
85
- def _source_existed(self, obj: Dict[str, Any]) -> bool:
86
- """
87
- Checks if the source node exists in a given object.
88
-
89
- Args:
90
- obj (Dict[str, Any]): The object to check.
91
-
92
- Returns:
93
- bool: True if the source node exists, False otherwise.
94
- """
95
- return self.source_node_id in obj.keys()
96
-
97
- def _target_existed(self, obj: Dict[str, Any]) -> bool:
98
- """
99
- Checks if the target node exists in a given object.
100
-
101
- Args:
102
- obj (Dict[str, Any]): The object to check.
103
-
104
- Returns:
105
- bool: True if the target node exists, False otherwise.
106
- """
107
- return self.target_node_id in obj.keys()
108
-
109
- def _is_in(self, obj: Dict[str, Any]) -> bool:
110
- """
111
- Validates the existence of both source and target nodes in a given object.
112
-
113
- Args:
114
- obj (Dict[str, Any]): The object to check.
115
-
116
- Returns:
117
- bool: True if both nodes exist.
118
-
119
- Raises:
120
- ValueError: If either the source or target node does not exist.
121
- """
122
- if self._source_existed(obj) and self._target_existed(obj):
123
- return True
124
-
125
- elif self._source_existed(obj):
126
- raise ValueError(f"Target node {self.source_node_id} does not exist")
127
- else:
128
- raise ValueError(f"Source node {self.target_node_id} does not exist")
129
-
130
- def __str__(self) -> str:
131
- """
132
- Returns a simple string representation of the Relationship.
133
-
134
- Examples:
135
- >>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
136
- >>> str(relationship)
137
- 'Relationship (id_=None, from=node1, to=node2, label=None)'
138
- """
139
- return (
140
- f"Relationship (id_={self.id_}, from={self.source_node_id}, to={self.target_node_id}, "
141
- f"label={self.label})"
142
- )
143
-
144
- def __repr__(self) -> str:
145
- """
146
- Returns a detailed string representation of the Relationship.
147
-
148
- Examples:
149
- >>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
150
- >>> repr(relationship)
151
- 'Relationship(id_=None, from=node1, to=node2, content=None, metadata=None, label=None)'
152
- """
153
- return (
154
- f"Relationship(id_={self.id_}, from={self.source_node_id}, to={self.target_node_id}, "
155
- f"content={self.content}, metadata={self.metadata}, label={self.label})"
156
- )
157
-
158
-
159
- class Graph(BaseRelatableNode):
160
- """
161
- Represents a graph structure, consisting of nodes and their relationships.
162
-
163
- Attributes:
164
- nodes (Dict[str, BaseNode]): A dictionary of nodes in the graph.
165
- relationships (Dict[str, Relationship]): A dictionary of relationships between nodes in the graph.
166
- node_relationships (Dict[str, Dict[str, Dict[str, str]]]): A dictionary tracking the relationships of each node.
167
-
168
- Examples:
169
- >>> graph = Graph()
170
- >>> node = BaseNode(id_='node1')
171
- >>> graph.add_node(node)
172
- >>> graph.node_exists(node)
173
- True
174
- >>> relationship = Relationship(id_='rel1', source_node_id='node1', target_node_id='node2')
175
- >>> graph.add_relationship(relationship)
176
- >>> graph.relationship_exists(relationship)
177
- True
178
- """
179
-
180
- nodes: dict = Field(default={})
181
- relationships: dict = Field(default={})
182
- node_relationships: dict = Field(default={})
183
-
184
- def add_node(self, node: BaseNode) -> None:
185
- """
186
- Adds a node to the graph.
187
-
188
- Args:
189
- node (BaseNode): The node to add to the graph.
190
- """
191
-
192
- self.nodes[node.id_] = node
193
- self.node_relationships[node.id_] = {"in": {}, "out": {}}
194
-
195
- def add_relationship(self, relationship: Relationship) -> None:
196
- """
197
- Adds a relationship between nodes in the graph.
198
-
199
- Args:
200
- relationship (Relationship): The relationship to add.
201
-
202
- Raises:
203
- KeyError: If either the source or target node of the relationship is not found in the graph.
204
- """
205
- if relationship.source_node_id not in self.node_relationships.keys():
206
- raise KeyError(f"node {relationship.source_node_id} is not found.")
207
- if relationship.target_node_id not in self.node_relationships.keys():
208
- raise KeyError(f"node {relationship.target_node_id} is not found.")
209
-
210
- self.relationships[relationship.id_] = relationship
211
- self.node_relationships[relationship.source_node_id]["out"][
212
- relationship.id_
213
- ] = relationship.target_node_id
214
- self.node_relationships[relationship.target_node_id]["in"][
215
- relationship.id_
216
- ] = relationship.source_node_id
217
-
218
- def get_node_relationships(
219
- self, node: BaseNode = None, out_edge=True
220
- ) -> List[Relationship]:
221
- """
222
- Retrieves relationships of a specific node or all relationships in the graph.
223
-
224
- Args:
225
- node (Optional[BaseNode]): The node whose relationships to retrieve. If None, retrieves all relationships.
226
- out_edge (bool): Whether to retrieve outgoing relationships. If False, retrieves incoming relationships.
227
-
228
- Returns:
229
- List[Relationship]: A list of relationships.
230
-
231
- Raises:
232
- KeyError: If the specified node is not found in the graph.
233
- """
234
- if node is None:
235
- return list(self.relationships.values())
236
-
237
- if node.id_ not in self.nodes.keys():
238
- raise KeyError(f"node {node.id_} is not found")
239
-
240
- if out_edge:
241
- relationship_ids = list(self.node_relationships[node.id_]["out"].keys())
242
- relationships = func_call.lcall(
243
- relationship_ids, lambda i: self.relationships[i]
244
- )
245
- return relationships
246
- else:
247
- relationship_ids = list(self.node_relationships[node.id_]["in"].keys())
248
- relationships = func_call.lcall(
249
- relationship_ids, lambda i: self.relationships[i]
250
- )
251
- return relationships
252
-
253
- def get_predecessors(self, node: BaseNode):
254
- """
255
- Retrieves the predecessor nodes of a given node.
256
-
257
- Args:
258
- node (BaseNode): The node whose predecessors to retrieve.
259
-
260
- Returns:
261
- list: A list of predecessor nodes.
262
- """
263
- node_ids = list(self.node_relationships[node.id_]["in"].values())
264
- nodes = func_call.lcall(node_ids, lambda i: self.nodes[i])
265
- return nodes
266
-
267
- def get_successors(self, node: BaseNode):
268
- """
269
- Retrieves the successor nodes of a given node.
270
-
271
- Args:
272
- node (BaseNode): The node whose successors to retrieve.
273
-
274
- Returns:
275
- list: A list of successor nodes.
276
- """
277
- node_ids = list(self.node_relationships[node.id_]["out"].values())
278
- nodes = func_call.lcall(node_ids, lambda i: self.nodes[i])
279
- return nodes
280
-
281
- def remove_node(self, node: BaseNode) -> BaseNode:
282
- """
283
- Removes a node and its associated relationship from the graph.
284
-
285
- Args:
286
- node (BaseNode): The node to remove.
287
-
288
- Returns:
289
- BaseNode: The removed node.
290
-
291
- Raises:
292
- KeyError: If the node is not found in the graph.
293
- """
294
- if node.id_ not in self.nodes.keys():
295
- raise KeyError(f"node {node.id_} is not found")
296
-
297
- out_relationship = self.node_relationships[node.id_]["out"]
298
- for relationship_id, node_id in out_relationship.items():
299
- self.node_relationships[node_id]["in"].pop(relationship_id)
300
- self.relationships.pop(relationship_id)
301
-
302
- in_relationship = self.node_relationships[node.id_]["in"]
303
- for relationship_id, node_id in in_relationship.items():
304
- self.node_relationships[node_id]["out"].pop(relationship_id)
305
- self.relationships.pop(relationship_id)
306
-
307
- self.node_relationships.pop(node.id_)
308
- return self.nodes.pop(node.id_)
309
-
310
- def remove_relationship(self, relationship: Relationship) -> Relationship:
311
- """
312
- Removes a relationship from the graph.
313
-
314
- Args:
315
- relationship (Relationship): The relationship to remove.
316
-
317
- Returns:
318
- Relationship: The removed relationship.
319
-
320
- Raises:
321
- KeyError: If the relationship is not found in the graph.
322
- """
323
- if relationship.id_ not in self.relationships.keys():
324
- raise KeyError(f"relationship {relationship.id_} is not found")
325
-
326
- self.node_relationships[relationship.source_node_id]["out"].pop(
327
- relationship.id_
328
- )
329
- self.node_relationships[relationship.target_node_id]["in"].pop(relationship.id_)
330
-
331
- return self.relationships.pop(relationship.id_)
332
-
333
- def node_exist(self, node: BaseNode) -> bool:
334
- """
335
- Checks if a node exists in the graph.
336
-
337
- Args:
338
- node (BaseNode): The node to check.
339
-
340
- Returns:
341
- bool: True if the node exists, False otherwise.
342
- """
343
- if node.id_ in self.nodes.keys():
344
- return True
345
- else:
346
- return False
347
-
348
- def relationship_exist(self, relationship: Relationship) -> bool:
349
- """
350
- Checks if a relationship exists in the graph.
351
-
352
- Args:
353
- relationship (Relationship): The relationship to check.
354
-
355
- Returns:
356
- bool: True if the relationship exists, False otherwise.
357
- """
358
- if relationship.id_ in self.relationships.keys():
359
- return True
360
- else:
361
- return False
362
-
363
- def is_empty(self) -> bool:
364
- """
365
- Determines if the graph is empty.
366
-
367
- Returns:
368
- bool: True if the graph has no nodes, False otherwise.
369
- """
370
- if self.nodes:
371
- return False
372
- else:
373
- return True
374
-
375
- def clear(self) -> None:
376
- """Clears the graph of all nodes and relationship."""
377
- self.nodes.clear()
378
- self.relationships.clear()
379
- self.node_relationships.clear()
380
-
381
- def to_networkx(self, **kwargs) -> Any:
382
- """
383
- Converts the graph to a NetworkX graph object.
384
-
385
- Args:
386
- **kwargs: Additional keyword arguments to pass to the NetworkX DiGraph constructor.
387
-
388
- Returns:
389
- Any: A NetworkX directed graph representing the graph.
390
-
391
- Examples:
392
- >>> graph = Graph()
393
- >>> nx_graph = graph.to_networkx()
394
- """
395
-
396
- SysUtil.check_import("networkx")
397
-
398
- from networkx import DiGraph
399
-
400
- g = DiGraph(**kwargs)
401
- for node_id, node in self.nodes.items():
402
- node_info = node.to_dict()
403
- node_info.pop("node_id")
404
- node_info.update({"class_name": node.__class__.__name__})
405
- g.add_node(node_id, **node_info)
406
-
407
- for _, relationship in self.relationships.items():
408
- relationship_info = relationship.to_dict()
409
- relationship_info.pop("node_id")
410
- relationship_info.update({"class_name": relationship.__class__.__name__})
411
- source_node_id = relationship_info.pop("source_node_id")
412
- target_node_id = relationship_info.pop("target_node_id")
413
- g.add_edge(source_node_id, target_node_id, **relationship_info)
414
-
415
- return g
416
-
417
-
418
- class Structure(BaseRelatableNode):
419
- """
420
- Represents a structure that extends the Graph class with additional functionality.
421
-
422
- Attributes:
423
- graph (Graph): The underlying graph structure.
424
- pending_ins (dict): A dictionary of pending incoming mails.
425
- pending_outs (deque): A deque of pending outgoing mails.
426
- execute_stop (bool): A flag indicating whether the execution should stop.
427
- condition_check_result (bool | None): The result of the last condition check.
428
-
429
- Methods:
430
- add_node(node: BaseNode) -> None:
431
- Adds a node to the structure's graph.
432
-
433
- add_relationship(from_node: BaseNode, to_node: BaseNode, bundle=False, condition=None, **kwargs) -> None:
434
- Adds a relationship between two nodes in the structure's graph.
435
-
436
- get_relationships() -> list[Relationship]:
437
- Retrieves all relationships in the structure's graph.
438
-
439
- get_node_relationships(node: BaseNode, out_edge=True, labels=None) -> list[Relationship]:
440
- Retrieves relationships of a specific node in the structure's graph.
441
-
442
- get_predecessors(node: BaseNode) -> list[BaseNode]:
443
- Retrieves the predecessor nodes of a given node in the structure's graph.
444
-
445
- get_successors(node: BaseNode) -> list[BaseNode]:
446
- Retrieves the successor nodes of a given node in the structure's graph.
447
-
448
- node_exist(node: BaseNode) -> bool:
449
- Checks if a node exists in the structure's graph.
450
-
451
- relationship_exist(relationship: Relationship) -> bool:
452
- Checks if a relationship exists in the structure's graph.
453
-
454
- remove_node(node: BaseNode) -> BaseNode:
455
- Removes a node from the structure's graph.
456
-
457
- remove_relationship(relationship: Relationship) -> Relationship:
458
- Removes a relationship from the structure's graph.
459
-
460
- is_empty() -> bool:
461
- Determines if the structure's graph is empty.
462
-
463
- get_heads() -> list[BaseNode]:
464
- Retrieves the head nodes of the structure's graph.
465
-
466
- parse_to_action(instruction: BaseNode, bundled_nodes: deque) -> ActionNode:
467
- Parses an instruction and bundled nodes into an ActionNode.
468
-
469
- async check_condition(relationship: Relationship, executable_id: str) -> bool:
470
- Checks the condition of a relationship.
471
-
472
- check_condition_structure(relationship: Relationship) -> bool:
473
- Checks the condition of a relationship within the structure.
474
-
475
- async get_next_step(current_node: BaseNode, executable_id: str) -> list[BaseNode]:
476
- Retrieves the next step nodes based on the current node and executable ID.
477
-
478
- acyclic() -> bool:
479
- Checks if the structure's graph is acyclic.
480
-
481
- send(recipient_id: str, category: str, package: Any) -> None:
482
- Sends a mail to a recipient.
483
-
484
- process_relationship_condition(relationship_id: str) -> None:
485
- Processes the condition of a relationship.
486
-
487
- async process() -> None:
488
- Processes the pending incoming mails and performs the corresponding actions.
489
-
490
- async execute(refresh_time=1) -> None:
491
- Executes the structure by processing incoming mails and updating the execution state.
492
- """
493
-
494
- graph: Graph = Graph()
495
- pending_ins: dict = {}
496
- pending_outs: deque = deque()
497
- execute_stop: bool = False
498
- condition_check_result: bool | None = None
499
-
500
- def add_node(self, node: BaseNode | list[BaseNode]):
501
- """
502
- Adds a node to the structure's graph.
503
-
504
- Args:
505
- node (BaseNode): The node to add.
506
- """
507
- func_call.lcall(node, self.graph.add_node)
508
-
509
- def add_relationship(
510
- self,
511
- from_node: BaseNode,
512
- to_node: BaseNode,
513
- bundle=False,
514
- condition=None,
515
- **kwargs,
516
- ):
517
- """
518
- Adds a relationship between two nodes in the structure's graph.
519
-
520
- Args:
521
- from_node (BaseNode): The source node of the relationship.
522
- to_node (BaseNode): The target node of the relationship.
523
- bundle (bool): Whether the relationship is bundled (default: False).
524
- condition (Optional[Condition]): The condition for the relationship (default: None).
525
- **kwargs: Additional keyword arguments for the relationship.
526
-
527
- Raises:
528
- ValueError: If the source node is a Tool or ActionSelection.
529
- """
530
- if isinstance(from_node, Tool) or isinstance(from_node, ActionSelection):
531
- raise ValueError(
532
- f"type {type(from_node)} should not be the head of the relationship, "
533
- f"please switch position and attach it to the tail of the relationship"
534
- )
535
- if isinstance(to_node, Tool) or isinstance(to_node, ActionSelection):
536
- bundle = True
537
- relationship = Relationship(
538
- source_node_id=from_node.id_,
539
- target_node_id=to_node.id_,
540
- bundle=bundle,
541
- **kwargs,
542
- )
543
- if condition:
544
- relationship.add_condition(condition)
545
- self.graph.add_relationship(relationship)
546
-
547
- def get_relationships(self) -> list[Relationship]:
548
- """
549
- Retrieves all relationships in the structure's graph.
550
-
551
- Returns:
552
- list[Relationship]: A list of all relationships.
553
- """
554
- return self.graph.get_node_relationships()
555
-
556
- def get_node_relationships(self, node: BaseNode, out_edge=True, labels=None):
557
- """
558
- Retrieves relationships of a specific node in the structure's graph.
559
-
560
- Args:
561
- node (BaseNode): The node whose relationships to retrieve.
562
- out_edge (bool): Whether to retrieve outgoing relationships (default: True).
563
- labels (Optional[list]): The labels of the relationships to retrieve (default: None).
564
-
565
- Returns:
566
- list[Relationship]: A list of relationships for the specified node.
567
- """
568
- relationships = self.graph.get_node_relationships(node, out_edge)
569
- if labels:
570
- if not isinstance(labels, list):
571
- labels = [labels]
572
- result = []
573
- for r in relationships:
574
- if r.label in labels:
575
- result.append(r)
576
- relationships = result
577
- return relationships
578
-
579
- def get_predecessors(self, node: BaseNode):
580
- """
581
- Retrieves the predecessor nodes of a given node in the structure's graph.
582
-
583
- Args:
584
- node (BaseNode): The node whose predecessors to retrieve.
585
-
586
- Returns:
587
- list[BaseNode]: A list of predecessor nodes.
588
- """
589
- return self.graph.get_predecessors(node)
590
-
591
- def get_successors(self, node: BaseNode):
592
- """
593
- Retrieves the successor nodes of a given node in the structure's graph.
594
-
595
- Args:
596
- node (BaseNode): The node whose successors to retrieve.
597
-
598
- Returns:
599
- list[BaseNode]: A list of successor nodes.
600
- """
601
- return self.graph.get_successors(node)
602
-
603
- def node_exist(self, node: BaseNode) -> bool:
604
- """
605
- Checks if a node exists in the structure's graph.
606
-
607
- Args:
608
- node (BaseNode): The node to check.
609
-
610
- Returns:
611
- bool: True if the node exists, False otherwise.
612
- """
613
- return self.graph.node_exist(node)
614
-
615
- def relationship_exist(self, relationship: Relationship) -> bool:
616
- """
617
- Checks if a relationship exists in the structure's graph.
618
-
619
- Args:
620
- relationship (Relationship): The relationship to check.
621
-
622
- Returns:
623
- bool: True if the relationship exists, False otherwise.
624
- """
625
- return self.graph.relationship_exist(relationship)
626
-
627
- def remove_node(self, node: BaseNode) -> BaseNode:
628
- """
629
- Removes a node from the structure's graph.
630
-
631
- Args:
632
- node (BaseNode): The node to remove.
633
-
634
- Returns:
635
- BaseNode: The removed node.
636
- """
637
- return self.graph.remove_node(node)
638
-
639
- def remove_relationship(self, relationship: Relationship) -> Relationship:
640
- """
641
- Removes a relationship from the structure's graph.
642
-
643
- Args:
644
- relationship (Relationship): The relationship to remove.
645
-
646
- Returns:
647
- Relationship: The removed relationship.
648
- """
649
- return self.graph.remove_relationship(relationship)
650
-
651
- def is_empty(self) -> bool:
652
- """
653
- Determines if the structure's graph is empty.
654
-
655
- Returns:
656
- bool: True if the graph has no nodes, False otherwise.
657
- """
658
- return self.graph.is_empty()
659
-
660
- def get_heads(self):
661
- """
662
- Retrieves the head nodes of the structure's graph.
663
-
664
- Returns:
665
- list[BaseNode]: A list of head nodes.
666
- """
667
- heads = []
668
- for key in self.graph.node_relationships:
669
- if not self.graph.node_relationships[key]["in"]:
670
- heads.append(self.graph.nodes[key])
671
- return heads
672
-
673
- @staticmethod
674
- def parse_to_action(instruction: BaseNode, bundled_nodes: deque):
675
- """
676
- Parses an instruction and bundled nodes into an ActionNode.
677
-
678
- Args:
679
- instruction (BaseNode): The instruction node.
680
- bundled_nodes (deque): A deque of bundled nodes.
681
-
682
- Returns:
683
- ActionNode: The parsed ActionNode.
684
-
685
- Raises:
686
- ValueError: If an invalid bundled node is encountered.
687
- """
688
- action_node = ActionNode(instruction)
689
- while bundled_nodes:
690
- node = bundled_nodes.popleft()
691
- if isinstance(node, ActionSelection):
692
- action_node.action = node.action
693
- action_node.action_kwargs = node.action_kwargs
694
- elif isinstance(node, Tool):
695
- action_node.tools.append(node)
696
- else:
697
- raise ValueError("Invalid bundles nodes")
698
- return action_node
699
-
700
- async def check_condition(self, relationship, executable_id):
701
- """
702
- Checks the condition of a relationship.
703
-
704
- Args:
705
- relationship (Relationship): The relationship to check the condition for.
706
- executable_id (str): The ID of the executable.
707
-
708
- Returns:
709
- bool: True if the condition is met, False otherwise.
710
-
711
- Raises:
712
- ValueError: If the source type of the condition is invalid.
713
- """
714
- if relationship.condition.source_type == "structure":
715
- return self.check_condition_structure(relationship)
716
- elif relationship.condition.source_type == "executable":
717
- self.send(
718
- recipient_id=executable_id, category="condition", package=relationship
719
- )
720
- while self.condition_check_result is None:
721
- await AsyncUtil.sleep(0.1)
722
- self.process_relationship_condition(relationship.id_)
723
- continue
724
- check_result = self.condition_check_result
725
- self.condition_check_result = None
726
- return check_result
727
- else:
728
- raise ValueError("Invalid source_type.")
729
-
730
- def check_condition_structure(self, relationship):
731
- """
732
- Checks the condition of a relationship within the structure.
733
-
734
- Args:
735
- relationship (Relationship): The relationship to check the condition for.
736
-
737
- Returns:
738
- bool: The result of the condition check.
739
- """
740
- return relationship.condition(self)
741
-
742
- async def get_next_step(self, current_node: BaseNode, executable_id):
743
- """
744
- Retrieves the next step nodes based on the current node and executable ID.
745
-
746
- Args:
747
- current_node (BaseNode): The current node.
748
- executable_id (str): The ID of the executable.
749
-
750
- Returns:
751
- list[BaseNode]: A list of next step nodes.
752
- """
753
- next_nodes = []
754
- next_relationships = self.get_node_relationships(current_node)
755
- for relationship in next_relationships:
756
- if relationship.bundle:
757
- continue
758
- if relationship.condition:
759
- check = await self.check_condition(relationship, executable_id)
760
- if not check:
761
- continue
762
- node = self.graph.nodes[relationship.target_node_id]
763
- further_relationships = self.get_node_relationships(node)
764
- bundled_nodes = deque()
765
- for f_relationship in further_relationships:
766
- if f_relationship.bundle:
767
- bundled_nodes.append(
768
- self.graph.nodes[f_relationship.target_node_id]
769
- )
770
- if bundled_nodes:
771
- node = self.parse_to_action(node, bundled_nodes)
772
- next_nodes.append(node)
773
- return next_nodes
774
-
775
- def acyclic(self):
776
- """
777
- Checks if the structure's graph is acyclic.
778
-
779
- Returns:
780
- bool: True if the graph is acyclic, False otherwise.
781
- """
782
- check_deque = deque(self.graph.nodes.keys())
783
- check_dict = {
784
- key: 0 for key in self.graph.nodes.keys()
785
- } # 0: not visited, 1: temp, 2: perm
786
-
787
- def visit(key):
788
- if check_dict[key] == 2:
789
- return True
790
- elif check_dict[key] == 1:
791
- return False
792
-
793
- check_dict[key] = 1
794
-
795
- out_relationships = self.graph.get_node_relationships(self.graph.nodes[key])
796
- for node in out_relationships:
797
- check = visit(node.target_node_id)
798
- if not check:
799
- return False
800
-
801
- check_dict[key] = 2
802
- return True
803
-
804
- while check_deque:
805
- key = check_deque.pop()
806
- check = visit(key)
807
- if not check:
808
- return False
809
- return True
810
-
811
- def send(self, recipient_id: str, category: str, package: Any) -> None:
812
- """
813
- Sends a mail to a recipient.
814
-
815
- Args:
816
- recipient_id (str): The ID of the recipient.
817
- category (str): The category of the mail.
818
- package (Any): The package to send.
819
- """
820
- mail = BaseMail(
821
- sender_id=self.id_,
822
- recipient_id=recipient_id,
823
- category=category,
824
- package=package,
825
- )
826
- self.pending_outs.append(mail)
827
-
828
- def process_relationship_condition(self, relationship_id):
829
- """
830
- Processes the condition of a relationship.
831
-
832
- Args:
833
- relationship_id (str): The ID of the relationship to process the condition for.
834
- """
835
- for key in list(self.pending_ins.keys()):
836
- skipped_requests = deque()
837
- while self.pending_ins[key]:
838
- mail = self.pending_ins[key].popleft()
839
- if (
840
- mail.category == "condition"
841
- and mail.package["relationship_id"] == relationship_id
842
- ):
843
- self.condition_check_result = mail.package["check_result"]
844
- else:
845
- skipped_requests.append(mail)
846
- self.pending_ins[key] = skipped_requests
847
-
848
- async def process(self) -> None:
849
- """
850
- Processes the pending incoming mails and performs the corresponding actions.
851
- """
852
- for key in list(self.pending_ins.keys()):
853
- while self.pending_ins[key]:
854
- mail = self.pending_ins[key].popleft()
855
- if mail.category == "start":
856
- next_nodes = self.get_heads()
857
- elif mail.category == "end":
858
- self.execute_stop = True
859
- return
860
- elif mail.category == "node_id":
861
- if mail.package not in self.graph.nodes:
862
- raise ValueError(
863
- f"Node {mail.package} does not exist in the structure {self.id_}"
864
- )
865
- next_nodes = await self.get_next_step(
866
- self.graph.nodes[mail.package], mail.sender_id
867
- )
868
- elif mail.category == "node" and isinstance(mail.package, BaseNode):
869
- if not self.node_exist(mail.package):
870
- raise ValueError(
871
- f"Node {mail.package} does not exist in the structure {self.id_}"
872
- )
873
- next_nodes = await self.get_next_step(mail.package, mail.sender_id)
874
- else:
875
- raise ValueError(f"Invalid mail type for structure")
876
-
877
- if not next_nodes: # tail
878
- self.send(
879
- recipient_id=mail.sender_id, category="end", package="end"
880
- )
881
- else:
882
- if len(next_nodes) == 1:
883
- self.send(
884
- recipient_id=mail.sender_id,
885
- category="node",
886
- package=next_nodes[0],
887
- )
888
- else:
889
- self.send(
890
- recipient_id=mail.sender_id,
891
- category="node_list",
892
- package=next_nodes,
893
- )
894
-
895
- async def execute(self, refresh_time=1):
896
- """
897
- Executes the structure by processing incoming mails and updating the execution state.
898
-
899
- Args:
900
- refresh_time (int): The refresh time for execution (default: 1).
901
-
902
- Raises:
903
- ValueError: If the structure's graph is not acyclic.
904
- """
905
- if not self.acyclic():
906
- raise ValueError("Structure is not acyclic")
907
-
908
- while not self.execute_stop:
909
- await self.process()
910
- await AsyncUtil.sleep(refresh_time)