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,236 @@
1
+ import contextlib
2
+ from collections import deque
3
+ from typing import Any
4
+
5
+ from lionagi.libs.ln_convert import to_list
6
+ from lionagi.core.collections.abc import (
7
+ Condition,
8
+ Actionable,
9
+ LionTypeError,
10
+ ItemNotFoundError,
11
+ LionIDable,
12
+ )
13
+ from lionagi.core.collections import pile, Pile
14
+
15
+ from lionagi.core.generic.edge import Edge
16
+ from lionagi.core.generic.node import Node
17
+
18
+
19
+ class Graph(Node):
20
+ """Represents a graph structure with nodes and edges."""
21
+
22
+ internal_nodes: Pile = pile()
23
+
24
+ @property
25
+ def internal_edges(self) -> Pile[Edge]:
26
+ """Return a pile of all edges in the graph."""
27
+ return pile(
28
+ {edge.ln_id: edge for node in self.internal_nodes for edge in node.edges},
29
+ Edge,
30
+ )
31
+
32
+ def is_empty(self) -> bool:
33
+ """Check if the graph is empty (has no nodes)."""
34
+ return self.internal_nodes.is_empty()
35
+
36
+ def clear(self):
37
+ """Clear all nodes and edges from the graph."""
38
+ self.internal_nodes.clear()
39
+
40
+ def add_edge(
41
+ self,
42
+ head: Node,
43
+ tail: Node,
44
+ condition: Condition | None = None,
45
+ bundle=False,
46
+ label=None,
47
+ **kwargs,
48
+ ):
49
+ """Add an edge between two nodes in the graph."""
50
+ if isinstance(head, Actionable):
51
+ raise LionTypeError("Actionable nodes cannot be related as head.")
52
+ if isinstance(tail, Actionable):
53
+ bundle = True
54
+
55
+ self.internal_nodes.include(head)
56
+ self.internal_nodes.include(tail)
57
+
58
+ head.relate(
59
+ tail,
60
+ direction="out",
61
+ condition=condition,
62
+ label=label,
63
+ bundle=bundle,
64
+ **kwargs,
65
+ )
66
+
67
+ def remove_edge(self, edge: Any) -> bool:
68
+ """Remove an edge from the graph."""
69
+ edge = edge if isinstance(edge, list) else [edge]
70
+ for i in edge:
71
+ if i not in self.internal_edges:
72
+ raise ItemNotFoundError(f"Edge {i} does not exist in structure.")
73
+ with contextlib.suppress(ItemNotFoundError):
74
+ self._remove_edge(i)
75
+
76
+ def add_node(self, node: Any) -> None:
77
+ """Add a node to the graph."""
78
+ self.internal_nodes.update(node)
79
+
80
+ def get_node(self, item: LionIDable, default=...):
81
+ """Get a node from the graph by its identifier."""
82
+ return self.internal_nodes.get(item, default)
83
+
84
+ def get_node_edges(
85
+ self,
86
+ node: Node | str,
87
+ direction: str = "both",
88
+ label: list | str = None,
89
+ ) -> Pile[Edge] | None:
90
+ """Get the edges of a node in the specified direction and with the given label."""
91
+ node = self.internal_nodes[node]
92
+ edges = None
93
+ match direction:
94
+ case "both":
95
+ edges = node.edges
96
+ case "head" | "predecessor" | "outgoing" | "out" | "predecessors":
97
+ edges = node.relations["out"]
98
+ case "tail" | "successor" | "incoming" | "in" | "successors":
99
+ edges = node.relations["in"]
100
+
101
+ if label:
102
+ return (
103
+ pile(
104
+ [
105
+ edge
106
+ for edge in edges
107
+ if edge.label in to_list(label, dropna=True, flatten=True)
108
+ ]
109
+ )
110
+ if edges
111
+ else None
112
+ )
113
+ return pile(edges) if edges else None
114
+
115
+ def pop_node(self, item, default=...):
116
+ """Remove and return a node from the graph by its identifier."""
117
+ return self.internal_nodes.pop(item, default)
118
+
119
+ def remove_node(self, item):
120
+ """Remove a node from the graph by its identifier."""
121
+ return self.internal_nodes.remove(item)
122
+
123
+ def _remove_edge(self, edge: Edge | str) -> bool:
124
+ """Remove a specific edge from the graph."""
125
+ if edge not in self.internal_edges:
126
+ raise ItemNotFoundError(f"Edge {edge} does not exist in structure.")
127
+
128
+ edge = self.internal_edges[edge]
129
+ head: Node = self.internal_nodes[edge.head]
130
+ tail: Node = self.internal_nodes[edge.tail]
131
+
132
+ head.unrelate(tail, edge=edge)
133
+ return True
134
+
135
+ def get_heads(self) -> Pile:
136
+ """Get all head nodes in the graph."""
137
+ return pile(
138
+ [
139
+ node
140
+ for node in self.internal_nodes
141
+ if node.relations["in"].is_empty() and not isinstance(node, Actionable)
142
+ ]
143
+ )
144
+
145
+ def is_acyclic(self) -> bool:
146
+ """Check if the graph is acyclic (contains no cycles)."""
147
+ node_ids = list(self.internal_nodes.keys())
148
+ check_deque = deque(node_ids)
149
+ check_dict = {key: 0 for key in node_ids} # 0: not visited, 1: temp, 2: perm
150
+
151
+ def visit(key):
152
+ if check_dict[key] == 2:
153
+ return True
154
+ elif check_dict[key] == 1:
155
+ return False
156
+
157
+ check_dict[key] = 1
158
+
159
+ for edge in self.internal_nodes[key].relations["out"]:
160
+ check = visit(edge.tail)
161
+ if not check:
162
+ return False
163
+
164
+ check_dict[key] = 2
165
+ return True
166
+
167
+ while check_deque:
168
+ key = check_deque.pop()
169
+ check = visit(key)
170
+ if not check:
171
+ return False
172
+ return True
173
+
174
+ def to_networkx(self, **kwargs) -> Any:
175
+ """Convert the graph to a NetworkX graph object."""
176
+ from lionagi.libs import SysUtil
177
+
178
+ SysUtil.check_import("networkx")
179
+
180
+ from networkx import DiGraph
181
+
182
+ g = DiGraph(**kwargs)
183
+ for node in self.internal_nodes:
184
+ node_info = node.to_dict()
185
+ node_info.pop("ln_id")
186
+ node_info.update({"class_name": node.class_name})
187
+ g.add_node(node.ln_id, **node_info)
188
+
189
+ for _edge in self.internal_edges:
190
+ edge_info = _edge.to_dict()
191
+ edge_info.pop("ln_id")
192
+ edge_info.update({"class_name": _edge.class_name})
193
+ source_node_id = edge_info.pop("head")
194
+ target_node_id = edge_info.pop("tail")
195
+ g.add_edge(source_node_id, target_node_id, **edge_info)
196
+
197
+ return g
198
+
199
+ def display(self, **kwargs):
200
+ """Display the graph using NetworkX and Matplotlib."""
201
+ from lionagi.libs import SysUtil
202
+
203
+ SysUtil.check_import("networkx")
204
+ SysUtil.check_import("matplotlib", "pyplot")
205
+
206
+ import networkx as nx
207
+ import matplotlib.pyplot as plt
208
+
209
+ g = self.to_networkx(**kwargs)
210
+ pos = nx.spring_layout(g)
211
+ nx.draw(
212
+ g,
213
+ pos,
214
+ edge_color="black",
215
+ width=1,
216
+ linewidths=1,
217
+ node_size=500,
218
+ node_color="orange",
219
+ alpha=0.9,
220
+ labels=nx.get_node_attributes(g, "class_name"),
221
+ )
222
+
223
+ labels = nx.get_edge_attributes(g, "label")
224
+ labels = {k: v for k, v in labels.items() if v}
225
+
226
+ if labels:
227
+ nx.draw_networkx_edge_labels(
228
+ g, pos, edge_labels=labels, font_color="purple"
229
+ )
230
+
231
+ plt.axis("off")
232
+ plt.show()
233
+
234
+ def size(self) -> int:
235
+ """Return the number of nodes in the graph."""
236
+ return len(self.internal_nodes)
@@ -0,0 +1 @@
1
+ # TODO
@@ -0,0 +1,220 @@
1
+ """
2
+ This module defines the Node class, representing a node in a graph-like
3
+ structure within LionAGI. Nodes can form relationships with other nodes
4
+ through directed edges, enabling construction and manipulation of complex
5
+ relational networks.
6
+
7
+ Includes functionality for managing relationships, such as adding,
8
+ modifying, and removing edges, and querying related nodes and connections.
9
+ """
10
+
11
+ from pydantic import Field
12
+ from pandas import Series
13
+
14
+ from lionagi.libs.ln_convert import to_list
15
+
16
+ from lionagi.core.collections.abc import (
17
+ Component,
18
+ Condition,
19
+ Relatable,
20
+ RelationError,
21
+ get_lion_id,
22
+ )
23
+ from lionagi.core.collections import pile, Pile
24
+ from lionagi.core.generic.edge import Edge
25
+
26
+
27
+ class Node(Component, Relatable):
28
+ """
29
+ Node in a graph structure, can connect to other nodes via edges.
30
+
31
+ Extends `Component` by incorporating relational capabilities, allowing
32
+ nodes to connect through 'in' and 'out' directed edges, representing
33
+ incoming and outgoing relationships.
34
+
35
+ Attributes:
36
+ relations (dict[str, Pile]): Dictionary holding 'Pile' instances
37
+ for incoming ('in') and outgoing ('out') edges.
38
+ """
39
+
40
+ relations: dict[str, Pile] = Field(
41
+ default_factory=lambda: {"in": pile(), "out": pile()},
42
+ description="The relations of the node.",
43
+ )
44
+
45
+ @property
46
+ def edges(self) -> Pile[Edge]:
47
+ """
48
+ Get unified view of all incoming and outgoing edges.
49
+
50
+ Returns:
51
+ Combined pile of all edges connected to this node.
52
+ """
53
+ return self.relations["in"] + self.relations["out"]
54
+
55
+ @property
56
+ def related_nodes(self) -> list[str]:
57
+ """
58
+ Get list of all unique node IDs directly related to this node.
59
+
60
+ Returns:
61
+ List of node IDs related to this node.
62
+ """
63
+ all_nodes = set(
64
+ to_list([[i.head, i.tail] for i in self.edges], flatten=True, dropna=True)
65
+ )
66
+ all_nodes.discard(self.ln_id)
67
+ return list(all_nodes)
68
+
69
+ @property
70
+ def node_relations(self) -> dict:
71
+ """
72
+ Get categorized view of direct relationships into groups.
73
+
74
+ Returns:
75
+ Dict with keys 'in' and 'out', each containing a mapping of
76
+ related node IDs to lists of edges representing relationships.
77
+ """
78
+ out_node_edges = {}
79
+ if not self.relations["out"].is_empty():
80
+ for edge in self.relations["out"]:
81
+ for node_id in self.related_nodes:
82
+ if edge.tail == node_id:
83
+ out_node_edges.setdefault(node_id, []).append(edge)
84
+
85
+ in_node_edges = {}
86
+ if not self.relations["in"].is_empty():
87
+ for edge in self.relations["in"]:
88
+ for node_id in self.related_nodes:
89
+ if edge.head == node_id:
90
+ in_node_edges.setdefault(node_id, []).append(edge)
91
+
92
+ return {"out": out_node_edges, "in": in_node_edges}
93
+
94
+ @property
95
+ def predecessors(self) -> list[str]:
96
+ """
97
+ Get list of IDs of nodes with direct incoming relation to this.
98
+
99
+ Returns:
100
+ List of node IDs that precede this node.
101
+ """
102
+ return [
103
+ node_id for node_id, edges in self.node_relations["in"].items() if edges
104
+ ]
105
+
106
+ @property
107
+ def successors(self) -> list[str]:
108
+ """
109
+ Get list of IDs of nodes with direct outgoing relation from this.
110
+
111
+ Returns:
112
+ List of node IDs that succeed this node.
113
+ """
114
+ return [
115
+ node_id for node_id, edges in self.node_relations["out"].items() if edges
116
+ ]
117
+
118
+ def relate(
119
+ self,
120
+ node: "Node",
121
+ direction: str = "out",
122
+ condition: Condition | None = None,
123
+ label: str | None = None,
124
+ bundle: bool = False,
125
+ ) -> None:
126
+ """
127
+ Establish directed relationship from this node to another.
128
+
129
+ Args:
130
+ node: Target node to relate to.
131
+ direction: Direction of edge ('in' or 'out'). Default 'out'.
132
+ condition: Optional condition to associate with edge.
133
+ label: Optional label for edge.
134
+ bundle: Whether to bundle edge with others. Default False.
135
+
136
+ Raises:
137
+ ValueError: If direction is neither 'in' nor 'out'.
138
+ """
139
+ if direction not in ["in", "out"]:
140
+ raise ValueError(
141
+ f"Invalid value for direction: {direction}, " "must be 'in' or 'out'"
142
+ )
143
+
144
+ edge = Edge(
145
+ head=self if direction == "out" else node,
146
+ tail=node if direction == "out" else self,
147
+ condition=condition,
148
+ bundle=bundle,
149
+ label=label,
150
+ )
151
+
152
+ self.relations[direction].include(edge)
153
+ node.relations["in" if direction == "out" else "out"].include(edge)
154
+
155
+ def remove_edge(self, node: "Node", edge: Edge | str) -> bool:
156
+ """
157
+ Remove specified edge or all edges between this and another node.
158
+
159
+ Args:
160
+ node: Other node involved in edge.
161
+ edge: Specific edge to remove or 'all' to remove all edges.
162
+
163
+ Returns:
164
+ True if edge(s) successfully removed, False otherwise.
165
+
166
+ Raises:
167
+ RelationError: If removal fails or edge does not exist.
168
+ """
169
+ edge_piles = [
170
+ self.relations["in"],
171
+ self.relations["out"],
172
+ node.relations["in"],
173
+ node.relations["out"],
174
+ ]
175
+
176
+ if not all(pile.exclude(edge) for pile in edge_piles):
177
+ raise RelationError(f"Failed to remove edge between nodes.")
178
+ return True
179
+
180
+ def unrelate(self, node: "Node", edge: Edge | str = "all") -> bool:
181
+ """
182
+ Remove all or specific relationships between this and another node.
183
+
184
+ Args:
185
+ node: Other node to unrelate from.
186
+ edge: Specific edge to remove or 'all' for all. Default 'all'.
187
+
188
+ Returns:
189
+ True if relationships successfully removed, False otherwise.
190
+
191
+ Raises:
192
+ RelationError: If operation fails to unrelate nodes.
193
+ """
194
+ if edge == "all":
195
+ edges = self.node_relations["out"].get(
196
+ node.ln_id, []
197
+ ) + self.node_relations["in"].get(node.ln_id, [])
198
+ else:
199
+ edges = [get_lion_id(edge)]
200
+
201
+ if not edges:
202
+ raise RelationError(f"Node is not related to {node.ln_id}.")
203
+
204
+ try:
205
+ for edge_id in edges:
206
+ self.remove_edge(node, edge_id)
207
+ return True
208
+ except RelationError as e:
209
+ raise e
210
+
211
+ def __str__(self):
212
+ _dict = self.to_dict()
213
+ _dict["relations"] = [
214
+ len(self.relations["in"]),
215
+ len(self.relations["out"]),
216
+ ]
217
+ return Series(_dict).__str__()
218
+
219
+ def __repr__(self):
220
+ return self.__str__()
@@ -0,0 +1,48 @@
1
+ """This module provides tree structure."""
2
+
3
+ from pydantic import Field
4
+ from lionagi.core.collections.abc import Condition
5
+ from lionagi.core.collections.util import to_list_type
6
+ from lionagi.core.generic.tree_node import TreeNode
7
+ from lionagi.core.generic.graph import Graph
8
+
9
+
10
+ class Tree(Graph):
11
+ """
12
+ Represents a tree structure, extending the graph with tree-specific functionalities.
13
+
14
+ Manages parent-child relationships within the tree.
15
+
16
+ Attributes:
17
+ root (TreeNode | None): The root node of the tree. Defaults to None.
18
+ """
19
+
20
+ root: TreeNode | None = Field(
21
+ default=None, description="The root node of the tree graph."
22
+ )
23
+
24
+ def relate_parent_child(
25
+ self,
26
+ parent: TreeNode,
27
+ children,
28
+ condition: Condition | None = None,
29
+ bundle: bool = False,
30
+ ) -> None:
31
+ """
32
+ Establishes parent-child relationships between the given parent and child node(s).
33
+
34
+ Args:
35
+ parent (TreeNode): The parent node.
36
+ children (list[TreeNode]): A list of child nodes.
37
+ condition (Condition | None): The condition associated with the relationships, if any.
38
+ bundle (bool): Indicates whether to bundle the relations into a single
39
+ transaction. Defaults to False.
40
+ """
41
+
42
+ for i in to_list_type(children):
43
+ i.relate_parent(parent, condition=condition, bundle=bundle)
44
+
45
+ if self.root is None:
46
+ self.root = parent
47
+
48
+ self.add_node([parent, *children])
@@ -0,0 +1,79 @@
1
+ from enum import Enum
2
+ from pydantic import Field
3
+ from lionagi.core.collections.abc import Condition
4
+ from lionagi.core.collections.util import to_list_type
5
+ from lionagi.core.generic.node import Node
6
+
7
+
8
+ class TreeLabel(str, Enum):
9
+ """Enumeration representing tree relationships."""
10
+
11
+ PARENT = "parent"
12
+ CHILD = "child"
13
+
14
+
15
+ class TreeNode(Node):
16
+ """Represents a node in a tree structure."""
17
+
18
+ parent: Node | None = Field(
19
+ default=None,
20
+ description="The parent node, as an instance of Node.",
21
+ )
22
+
23
+ @property
24
+ def children(self) -> list[str]:
25
+ """Return a list of child node ids."""
26
+ if not self.parent:
27
+ return list(self.related_nodes)
28
+ else:
29
+ return [node for node in self.related_nodes if node != self.parent.ln_id]
30
+
31
+ def relate_child(
32
+ self,
33
+ node: Node | list[Node],
34
+ condition: Condition | None = None,
35
+ bundle: bool = False,
36
+ ) -> None:
37
+ """Establish a parent-child relationship with the given node(s)."""
38
+ children = to_list_type(node)
39
+ for _child in children:
40
+ self.relate(
41
+ _child,
42
+ direction="out",
43
+ # label=TreeLabel.PARENT,
44
+ condition=condition,
45
+ bundle=bundle,
46
+ )
47
+ if isinstance(_child, TreeNode):
48
+ _child.parent = self
49
+
50
+ def relate_parent(
51
+ self,
52
+ node: Node,
53
+ condition: Condition | None = None,
54
+ bundle: bool = False,
55
+ ) -> None:
56
+ """Establish a parent-child relationship with the given parent node."""
57
+ if self.parent:
58
+ self.unrelate(self.parent)
59
+ self.relate(
60
+ node,
61
+ direction="in",
62
+ # label=TreeLabel.PARENT,
63
+ condition=condition,
64
+ bundle=bundle,
65
+ )
66
+ self.parent = node
67
+
68
+ def unrelate_parent(self):
69
+ """Remove the parent-child relationship with the parent node."""
70
+ self.unrelate(self.parent)
71
+ self.parent = None
72
+
73
+ def unrelate_child(self, child: Node | list[Node]):
74
+ """Remove the parent-child relationship with the given child node(s)."""
75
+ children: list[Node] = [child] if isinstance(child, Node) else child
76
+ for _child in children:
77
+ self.unrelate(_child)
78
+ if isinstance(_child, TreeNode):
79
+ _child.parent = None
@@ -1,8 +1,12 @@
1
- from .schema import BaseMail, StartMail
1
+ from .mail import Mail
2
2
  from .mail_manager import MailManager
3
+ from .package import Package
4
+ from .start_mail import StartMail
5
+
3
6
 
4
7
  __all__ = [
5
- "BaseMail",
6
- "StartMail",
8
+ "Mail",
7
9
  "MailManager",
10
+ "Package",
11
+ "StartMail",
8
12
  ]
@@ -0,0 +1,25 @@
1
+ from lionagi.core.collections.abc import Element, Field, Sendable
2
+ from .package import PackageCategory, Package
3
+
4
+
5
+ class Mail(Element, Sendable):
6
+ """Represents a mail component with sender and recipient information."""
7
+
8
+ package: Package | None = Field(
9
+ None,
10
+ title="Package",
11
+ description="The package to be delivered.",
12
+ )
13
+
14
+ @property
15
+ def category(self) -> PackageCategory:
16
+ """Return the category of the package."""
17
+ return self.package.category
18
+
19
+ def to_dict(self):
20
+ return {
21
+ "ln_id": self.ln_id,
22
+ "created": self.timestamp,
23
+ "package_category": self.package.category,
24
+ "package_id": self.package.ln_id,
25
+ }