lionagi 0.1.2__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 +60 -5
  2. lionagi/core/__init__.py +0 -25
  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/base_agent.py +27 -13
  11. lionagi/core/agent/eval/evaluator.py +1 -0
  12. lionagi/core/agent/eval/vote.py +40 -0
  13. lionagi/core/agent/learn/learner.py +59 -0
  14. lionagi/core/agent/plan/unit_template.py +1 -0
  15. lionagi/core/collections/__init__.py +17 -0
  16. lionagi/core/{generic/data_logger.py → collections/_logger.py} +69 -55
  17. lionagi/core/collections/abc/__init__.py +53 -0
  18. lionagi/core/collections/abc/component.py +615 -0
  19. lionagi/core/collections/abc/concepts.py +297 -0
  20. lionagi/core/collections/abc/exceptions.py +150 -0
  21. lionagi/core/collections/abc/util.py +45 -0
  22. lionagi/core/collections/exchange.py +161 -0
  23. lionagi/core/collections/flow.py +426 -0
  24. lionagi/core/collections/model.py +419 -0
  25. lionagi/core/collections/pile.py +913 -0
  26. lionagi/core/collections/progression.py +236 -0
  27. lionagi/core/collections/util.py +64 -0
  28. lionagi/core/director/direct.py +314 -0
  29. lionagi/core/director/director.py +2 -0
  30. lionagi/core/{execute/branch_executor.py → engine/branch_engine.py} +134 -97
  31. lionagi/core/{execute/instruction_map_executor.py → engine/instruction_map_engine.py} +80 -55
  32. lionagi/{experimental/directive/evaluator → core/engine}/script_engine.py +17 -1
  33. lionagi/core/executor/base_executor.py +90 -0
  34. lionagi/core/{execute/structure_executor.py → executor/graph_executor.py} +62 -66
  35. lionagi/core/{execute → executor}/neo4j_executor.py +70 -67
  36. lionagi/core/generic/__init__.py +3 -33
  37. lionagi/core/generic/edge.py +29 -79
  38. lionagi/core/generic/edge_condition.py +16 -0
  39. lionagi/core/generic/graph.py +236 -0
  40. lionagi/core/generic/hyperedge.py +1 -0
  41. lionagi/core/generic/node.py +156 -221
  42. lionagi/core/generic/tree.py +48 -0
  43. lionagi/core/generic/tree_node.py +79 -0
  44. lionagi/core/mail/__init__.py +12 -0
  45. lionagi/core/mail/mail.py +25 -0
  46. lionagi/core/mail/mail_manager.py +139 -58
  47. lionagi/core/mail/package.py +45 -0
  48. lionagi/core/mail/start_mail.py +36 -0
  49. lionagi/core/message/__init__.py +19 -0
  50. lionagi/core/message/action_request.py +133 -0
  51. lionagi/core/message/action_response.py +135 -0
  52. lionagi/core/message/assistant_response.py +95 -0
  53. lionagi/core/message/instruction.py +234 -0
  54. lionagi/core/message/message.py +101 -0
  55. lionagi/core/message/system.py +86 -0
  56. lionagi/core/message/util.py +283 -0
  57. lionagi/core/report/__init__.py +4 -0
  58. lionagi/core/report/base.py +217 -0
  59. lionagi/core/report/form.py +231 -0
  60. lionagi/core/report/report.py +166 -0
  61. lionagi/core/report/util.py +28 -0
  62. lionagi/core/rule/_default.py +16 -0
  63. lionagi/core/rule/action.py +99 -0
  64. lionagi/core/rule/base.py +238 -0
  65. lionagi/core/rule/boolean.py +56 -0
  66. lionagi/core/rule/choice.py +47 -0
  67. lionagi/core/rule/mapping.py +96 -0
  68. lionagi/core/rule/number.py +71 -0
  69. lionagi/core/rule/rulebook.py +109 -0
  70. lionagi/core/rule/string.py +52 -0
  71. lionagi/core/rule/util.py +35 -0
  72. lionagi/core/session/branch.py +431 -0
  73. lionagi/core/session/directive_mixin.py +287 -0
  74. lionagi/core/session/session.py +229 -903
  75. lionagi/core/structure/__init__.py +1 -0
  76. lionagi/core/structure/chain.py +1 -0
  77. lionagi/core/structure/forest.py +1 -0
  78. lionagi/core/structure/graph.py +1 -0
  79. lionagi/core/structure/tree.py +1 -0
  80. lionagi/core/unit/__init__.py +5 -0
  81. lionagi/core/unit/parallel_unit.py +245 -0
  82. lionagi/core/unit/template/action.py +81 -0
  83. lionagi/core/unit/template/base.py +51 -0
  84. lionagi/core/unit/template/plan.py +84 -0
  85. lionagi/core/unit/template/predict.py +109 -0
  86. lionagi/core/unit/template/score.py +124 -0
  87. lionagi/core/unit/template/select.py +104 -0
  88. lionagi/core/unit/unit.py +362 -0
  89. lionagi/core/unit/unit_form.py +305 -0
  90. lionagi/core/unit/unit_mixin.py +1168 -0
  91. lionagi/core/unit/util.py +71 -0
  92. lionagi/core/validator/validator.py +364 -0
  93. lionagi/core/work/work.py +76 -0
  94. lionagi/core/work/work_function.py +101 -0
  95. lionagi/core/work/work_queue.py +103 -0
  96. lionagi/core/work/worker.py +258 -0
  97. lionagi/core/work/worklog.py +120 -0
  98. lionagi/experimental/compressor/base.py +46 -0
  99. lionagi/experimental/compressor/llm_compressor.py +247 -0
  100. lionagi/experimental/compressor/llm_summarizer.py +61 -0
  101. lionagi/experimental/compressor/util.py +70 -0
  102. lionagi/experimental/directive/__init__.py +19 -0
  103. lionagi/experimental/directive/parser/base_parser.py +69 -2
  104. lionagi/experimental/directive/{template_ → template}/base_template.py +17 -1
  105. lionagi/{libs/ln_tokenizer.py → experimental/directive/tokenizer.py} +16 -0
  106. lionagi/experimental/{directive/evaluator → evaluator}/ast_evaluator.py +16 -0
  107. lionagi/experimental/{directive/evaluator → evaluator}/base_evaluator.py +16 -0
  108. lionagi/experimental/knowledge/base.py +10 -0
  109. lionagi/experimental/memory/__init__.py +0 -0
  110. lionagi/experimental/strategies/__init__.py +0 -0
  111. lionagi/experimental/strategies/base.py +1 -0
  112. lionagi/integrations/bridge/langchain_/documents.py +4 -0
  113. lionagi/integrations/bridge/llamaindex_/index.py +30 -0
  114. lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
  115. lionagi/integrations/chunker/chunk.py +161 -24
  116. lionagi/integrations/config/oai_configs.py +34 -3
  117. lionagi/integrations/config/openrouter_configs.py +14 -2
  118. lionagi/integrations/loader/load.py +122 -21
  119. lionagi/integrations/loader/load_util.py +6 -77
  120. lionagi/integrations/provider/_mapping.py +46 -0
  121. lionagi/integrations/provider/litellm.py +2 -1
  122. lionagi/integrations/provider/mlx_service.py +16 -9
  123. lionagi/integrations/provider/oai.py +91 -4
  124. lionagi/integrations/provider/ollama.py +6 -5
  125. lionagi/integrations/provider/openrouter.py +115 -8
  126. lionagi/integrations/provider/services.py +2 -2
  127. lionagi/integrations/provider/transformers.py +18 -22
  128. lionagi/integrations/storage/__init__.py +3 -3
  129. lionagi/integrations/storage/neo4j.py +52 -60
  130. lionagi/integrations/storage/storage_util.py +44 -46
  131. lionagi/integrations/storage/structure_excel.py +43 -26
  132. lionagi/integrations/storage/to_excel.py +11 -4
  133. lionagi/libs/__init__.py +22 -1
  134. lionagi/libs/ln_api.py +75 -20
  135. lionagi/libs/ln_context.py +37 -0
  136. lionagi/libs/ln_convert.py +21 -9
  137. lionagi/libs/ln_func_call.py +69 -28
  138. lionagi/libs/ln_image.py +107 -0
  139. lionagi/libs/ln_nested.py +26 -11
  140. lionagi/libs/ln_parse.py +82 -23
  141. lionagi/libs/ln_queue.py +16 -0
  142. lionagi/libs/ln_tokenize.py +164 -0
  143. lionagi/libs/ln_validate.py +16 -0
  144. lionagi/libs/special_tokens.py +172 -0
  145. lionagi/libs/sys_util.py +95 -24
  146. lionagi/lions/coder/code_form.py +13 -0
  147. lionagi/lions/coder/coder.py +50 -3
  148. lionagi/lions/coder/util.py +30 -25
  149. lionagi/tests/libs/test_func_call.py +23 -21
  150. lionagi/tests/libs/test_nested.py +36 -21
  151. lionagi/tests/libs/test_parse.py +1 -1
  152. lionagi/tests/test_core/collections/__init__.py +0 -0
  153. lionagi/tests/test_core/collections/test_component.py +206 -0
  154. lionagi/tests/test_core/collections/test_exchange.py +138 -0
  155. lionagi/tests/test_core/collections/test_flow.py +145 -0
  156. lionagi/tests/test_core/collections/test_pile.py +171 -0
  157. lionagi/tests/test_core/collections/test_progression.py +129 -0
  158. lionagi/tests/test_core/generic/test_edge.py +67 -0
  159. lionagi/tests/test_core/generic/test_graph.py +96 -0
  160. lionagi/tests/test_core/generic/test_node.py +106 -0
  161. lionagi/tests/test_core/generic/test_tree_node.py +73 -0
  162. lionagi/tests/test_core/test_branch.py +115 -294
  163. lionagi/tests/test_core/test_form.py +46 -0
  164. lionagi/tests/test_core/test_report.py +105 -0
  165. lionagi/tests/test_core/test_validator.py +111 -0
  166. lionagi/version.py +1 -1
  167. lionagi-0.2.1.dist-info/LICENSE +202 -0
  168. lionagi-0.2.1.dist-info/METADATA +272 -0
  169. lionagi-0.2.1.dist-info/RECORD +240 -0
  170. lionagi/core/branch/base.py +0 -653
  171. lionagi/core/branch/branch.py +0 -474
  172. lionagi/core/branch/flow_mixin.py +0 -96
  173. lionagi/core/branch/util.py +0 -323
  174. lionagi/core/direct/__init__.py +0 -19
  175. lionagi/core/direct/cot.py +0 -123
  176. lionagi/core/direct/plan.py +0 -164
  177. lionagi/core/direct/predict.py +0 -166
  178. lionagi/core/direct/react.py +0 -171
  179. lionagi/core/direct/score.py +0 -279
  180. lionagi/core/direct/select.py +0 -170
  181. lionagi/core/direct/sentiment.py +0 -1
  182. lionagi/core/direct/utils.py +0 -110
  183. lionagi/core/direct/vote.py +0 -64
  184. lionagi/core/execute/base_executor.py +0 -47
  185. lionagi/core/flow/baseflow.py +0 -23
  186. lionagi/core/flow/monoflow/ReAct.py +0 -240
  187. lionagi/core/flow/monoflow/__init__.py +0 -9
  188. lionagi/core/flow/monoflow/chat.py +0 -95
  189. lionagi/core/flow/monoflow/chat_mixin.py +0 -253
  190. lionagi/core/flow/monoflow/followup.py +0 -215
  191. lionagi/core/flow/polyflow/__init__.py +0 -1
  192. lionagi/core/flow/polyflow/chat.py +0 -251
  193. lionagi/core/form/action_form.py +0 -26
  194. lionagi/core/form/field_validator.py +0 -287
  195. lionagi/core/form/form.py +0 -302
  196. lionagi/core/form/mixin.py +0 -214
  197. lionagi/core/form/scored_form.py +0 -13
  198. lionagi/core/generic/action.py +0 -26
  199. lionagi/core/generic/component.py +0 -532
  200. lionagi/core/generic/condition.py +0 -46
  201. lionagi/core/generic/mail.py +0 -90
  202. lionagi/core/generic/mailbox.py +0 -36
  203. lionagi/core/generic/relation.py +0 -70
  204. lionagi/core/generic/signal.py +0 -22
  205. lionagi/core/generic/structure.py +0 -362
  206. lionagi/core/generic/transfer.py +0 -20
  207. lionagi/core/generic/work.py +0 -40
  208. lionagi/core/graph/graph.py +0 -126
  209. lionagi/core/graph/tree.py +0 -190
  210. lionagi/core/mail/schema.py +0 -63
  211. lionagi/core/messages/schema.py +0 -325
  212. lionagi/core/tool/__init__.py +0 -5
  213. lionagi/core/tool/tool.py +0 -28
  214. lionagi/core/tool/tool_manager.py +0 -283
  215. lionagi/experimental/report/form.py +0 -64
  216. lionagi/experimental/report/report.py +0 -138
  217. lionagi/experimental/report/util.py +0 -47
  218. lionagi/experimental/tool/function_calling.py +0 -43
  219. lionagi/experimental/tool/manual.py +0 -66
  220. lionagi/experimental/tool/schema.py +0 -59
  221. lionagi/experimental/tool/tool_manager.py +0 -138
  222. lionagi/experimental/tool/util.py +0 -16
  223. lionagi/experimental/validator/rule.py +0 -139
  224. lionagi/experimental/validator/validator.py +0 -56
  225. lionagi/experimental/work/__init__.py +0 -10
  226. lionagi/experimental/work/async_queue.py +0 -54
  227. lionagi/experimental/work/schema.py +0 -73
  228. lionagi/experimental/work/work_function.py +0 -67
  229. lionagi/experimental/work/worker.py +0 -56
  230. lionagi/experimental/work2/form.py +0 -371
  231. lionagi/experimental/work2/report.py +0 -289
  232. lionagi/experimental/work2/schema.py +0 -30
  233. lionagi/experimental/work2/tests.py +0 -72
  234. lionagi/experimental/work2/work_function.py +0 -89
  235. lionagi/experimental/work2/worker.py +0 -12
  236. lionagi/integrations/bridge/llamaindex_/get_index.py +0 -294
  237. lionagi/tests/test_core/generic/test_component.py +0 -89
  238. lionagi/tests/test_core/test_base_branch.py +0 -426
  239. lionagi/tests/test_core/test_chat_flow.py +0 -63
  240. lionagi/tests/test_core/test_mail_manager.py +0 -75
  241. lionagi/tests/test_core/test_prompts.py +0 -51
  242. lionagi/tests/test_core/test_session.py +0 -254
  243. lionagi/tests/test_core/test_session_base_util.py +0 -313
  244. lionagi/tests/test_core/test_tool_manager.py +0 -95
  245. lionagi-0.1.2.dist-info/LICENSE +0 -9
  246. lionagi-0.1.2.dist-info/METADATA +0 -174
  247. lionagi-0.1.2.dist-info/RECORD +0 -206
  248. /lionagi/core/{branch → _setting}/__init__.py +0 -0
  249. /lionagi/core/{execute → agent/eval}/__init__.py +0 -0
  250. /lionagi/core/{flow → agent/learn}/__init__.py +0 -0
  251. /lionagi/core/{form → agent/plan}/__init__.py +0 -0
  252. /lionagi/core/{branch/executable_branch.py → agent/plan/plan.py} +0 -0
  253. /lionagi/core/{graph → director}/__init__.py +0 -0
  254. /lionagi/core/{messages → engine}/__init__.py +0 -0
  255. /lionagi/{experimental/directive/evaluator → core/engine}/sandbox_.py +0 -0
  256. /lionagi/{experimental/directive/evaluator → core/executor}/__init__.py +0 -0
  257. /lionagi/{experimental/directive/template_ → core/rule}/__init__.py +0 -0
  258. /lionagi/{experimental/report → core/unit/template}/__init__.py +0 -0
  259. /lionagi/{experimental/tool → core/validator}/__init__.py +0 -0
  260. /lionagi/{experimental/validator → core/work}/__init__.py +0 -0
  261. /lionagi/experimental/{work2 → compressor}/__init__.py +0 -0
  262. /lionagi/{core/flow/mono_chat_mixin.py → experimental/directive/template/__init__.py} +0 -0
  263. /lionagi/experimental/directive/{schema.py → template/schema.py} +0 -0
  264. /lionagi/experimental/{work2/util.py → evaluator/__init__.py} +0 -0
  265. /lionagi/experimental/{work2/work.py → knowledge/__init__.py} +0 -0
  266. /lionagi/{tests/libs/test_async.py → experimental/knowledge/graph.py} +0 -0
  267. {lionagi-0.1.2.dist-info → lionagi-0.2.1.dist-info}/WHEEL +0 -0
  268. {lionagi-0.1.2.dist-info → lionagi-0.2.1.dist-info}/top_level.txt +0 -0
@@ -1,285 +1,220 @@
1
- from typing import Any, Type
2
- from pydantic import Field
3
- from lionagi.integrations.bridge import LlamaIndexBridge, LangchainBridge
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
+ """
4
10
 
5
- from lionagi.core.generic.component import BaseNode
6
- from lionagi.core.generic.condition import Condition
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
7
24
  from lionagi.core.generic.edge import Edge
8
- from lionagi.core.generic.relation import Relations
9
- from lionagi.core.generic.mailbox import MailBox
10
25
 
11
26
 
12
- class Node(BaseNode):
27
+ class Node(Component, Relatable):
13
28
  """
14
- Represents a node with relations to other nodes.
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.
15
34
 
16
35
  Attributes:
17
- relations (Relations): The relations of the node, managed through a
18
- `Relations` instance.
19
-
20
- Properties:
21
- related_nodes: A set of IDs representing nodes related to this node.
22
- edges: A dictionary of all edges connected to this node.
23
- node_relations: A dictionary categorizing preceding and succeeding
24
- relations to this node.
25
- precedessors: A list of node IDs that precede this node.
26
- successors: A list of node IDs that succeed this node.
27
-
28
- Methods:
29
- relate(node, self_as, condition, **kwargs): Relates this node to
30
- another node with an edge.
31
- unrelate(node, edge): Removes one or all relations between this node
32
- and another.
33
- to_llama_index(node_type, **kwargs): Serializes this node for
34
- LlamaIndex.
35
- to_langchain(**kwargs): Serializes this node for Langchain.
36
- from_llama_index(llama_node, **kwargs): Deserializes a node from
37
- LlamaIndex data.
38
- from_langchain(lc_doc): Deserializes a node from Langchain data.
39
- __str__(): String representation of the node.
40
-
41
- Raises:
42
- ValueError: When invalid parameters are provided to methods.
36
+ relations (dict[str, Pile]): Dictionary holding 'Pile' instances
37
+ for incoming ('in') and outgoing ('out') edges.
43
38
  """
44
39
 
45
- relations: Relations = Field(
46
- default_factory=Relations,
40
+ relations: dict[str, Pile] = Field(
41
+ default_factory=lambda: {"in": pile(), "out": pile()},
47
42
  description="The relations of the node.",
48
- alias="node_relations",
49
43
  )
50
44
 
51
- mailbox: MailBox = Field(
52
- default_factory=MailBox,
53
- description="The mailbox for incoming and outgoing mails.",
54
- )
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"]
55
54
 
56
55
  @property
57
56
  def related_nodes(self) -> list[str]:
58
- """Returns a set of node IDs related to this node, excluding itself."""
59
- nodes = set(self.relations.all_nodes)
60
- nodes.discard(self.id_)
61
- return list(nodes)
57
+ """
58
+ Get list of all unique node IDs directly related to this node.
62
59
 
63
- @property
64
- def edges(self) -> dict[str, Edge]:
65
- """Returns a dictionary of all edges connected to this node."""
66
- return self.relations.all_edges
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)
67
68
 
68
69
  @property
69
70
  def node_relations(self) -> dict:
70
- """Categorizes preceding and succeeding relations to this node."""
71
-
72
- points_to_nodes = {}
73
- for edge in self.relations.points_to.values():
74
- for i in self.related_nodes:
75
- if edge.tail == i:
76
- if i in points_to_nodes:
77
- points_to_nodes[i].append(edge)
78
- else:
79
- points_to_nodes[i] = [edge]
80
-
81
- pointed_by_nodes = {}
82
- for edge in self.relations.pointed_by.values():
83
- for i in self.related_nodes:
84
- if edge.head == i:
85
- if i in pointed_by_nodes:
86
- pointed_by_nodes[i].append(edge)
87
- else:
88
- pointed_by_nodes[i] = [edge]
89
-
90
- return {"points_to": points_to_nodes, "pointed_by": pointed_by_nodes}
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}
91
93
 
92
94
  @property
93
- def precedessors(self) -> list[str]:
94
- """return a list of nodes id that precede this node"""
95
- return [k for k, v in self.node_relations["pointed_by"].items() if len(v) > 0]
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
+ ]
96
105
 
97
106
  @property
98
107
  def successors(self) -> list[str]:
99
- """return a list of nodes id that succeed this node"""
100
- return [k for k, v in self.node_relations["points_to"].items() if len(v) > 0]
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
+ ]
101
117
 
102
118
  def relate(
103
119
  self,
104
120
  node: "Node",
105
- node_as: str = "head",
121
+ direction: str = "out",
106
122
  condition: Condition | None = None,
107
123
  label: str | None = None,
108
- bundle=False,
124
+ bundle: bool = False,
109
125
  ) -> None:
110
- """Relates this node to another node with an edge.
126
+ """
127
+ Establish directed relationship from this node to another.
111
128
 
112
129
  Args:
113
- node (Node): The node to relate to.
114
- self_as (str): Specifies whether this node is the 'head' or 'tail'
115
- of the relation. Defaults to "head".
116
- condition (Condition | None): The condition associated with the
117
- edge, if any. Defaults to None.
118
- **kwargs: Additional keyword arguments for edge creation.
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.
119
135
 
120
136
  Raises:
121
- ValueError: If `self_as` is not 'head' or 'tail'.
137
+ ValueError: If direction is neither 'in' nor 'out'.
122
138
  """
123
- if node_as == "head":
124
- edge = Edge(
125
- head=self, tail=node, condition=condition, bundle=bundle, label=label
139
+ if direction not in ["in", "out"]:
140
+ raise ValueError(
141
+ f"Invalid value for direction: {direction}, " "must be 'in' or 'out'"
126
142
  )
127
- self.relations.points_to[edge.id_] = edge
128
- node.relations.pointed_by[edge.id_] = edge
129
143
 
130
- elif node_as == "tail":
131
- edge = Edge(
132
- head=node, tail=self, condition=condition, label=label, bundle=bundle
133
- )
134
- self.relations.pointed_by[edge.id_] = edge
135
- node.relations.points_to[edge.id_] = edge
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
+ )
136
151
 
137
- else:
138
- raise ValueError(
139
- f"Invalid value for self_as: {node_as}, must be 'head' or 'tail'"
140
- )
152
+ self.relations[direction].include(edge)
153
+ node.relations["in" if direction == "out" else "out"].include(edge)
141
154
 
142
155
  def remove_edge(self, node: "Node", edge: Edge | str) -> bool:
143
- if node.id_ not in self.related_nodes:
144
- raise ValueError(f"Node {self.id_} is not related to node {node.id_}.")
156
+ """
157
+ Remove specified edge or all edges between this and another node.
145
158
 
146
- edge_id = edge.id_ if isinstance(edge, Edge) else edge
159
+ Args:
160
+ node: Other node involved in edge.
161
+ edge: Specific edge to remove or 'all' to remove all edges.
147
162
 
148
- if (
149
- edge_id not in self.relations.all_edges
150
- or edge_id not in node.relations.all_edges
151
- ):
152
- raise ValueError(
153
- f"Edge {edge_id} does not exist between nodes {self.id_} and "
154
- f"{node.id_}."
155
- )
163
+ Returns:
164
+ True if edge(s) successfully removed, False otherwise.
156
165
 
157
- all_dicts = [
158
- self.relations.points_to,
159
- self.relations.pointed_by,
160
- node.relations.points_to,
161
- node.relations.pointed_by,
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"],
162
174
  ]
163
- try:
164
- for _dict in all_dicts:
165
- edge_id = edge.id_ if isinstance(edge, Edge) else edge
166
- _dict.pop(edge_id, None)
167
- return True
168
175
 
169
- except Exception as e:
170
- raise ValueError(
171
- f"Failed to remove edge between nodes {self.id_} and " f"{node.id_}."
172
- ) from e
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
173
179
 
174
180
  def unrelate(self, node: "Node", edge: Edge | str = "all") -> bool:
175
181
  """
176
- Removes one or all relations between this node and another.
182
+ Remove all or specific relationships between this and another node.
177
183
 
178
184
  Args:
179
- node (Node): The node to unrelate from.
180
- edge (Edge | str): Specific edge or 'all' to remove all relations.
181
- Defaults to "all".
185
+ node: Other node to unrelate from.
186
+ edge: Specific edge to remove or 'all' for all. Default 'all'.
182
187
 
183
188
  Returns:
184
- bool: True if the operation is successful, False otherwise.
189
+ True if relationships successfully removed, False otherwise.
185
190
 
186
191
  Raises:
187
- ValueError: If the node is not related or the edge does not exist.
192
+ RelationError: If operation fails to unrelate nodes.
188
193
  """
189
194
  if edge == "all":
190
- edge = self.node_relations["points_to"].get(
191
- node.id_, []
192
- ) + self.node_relations["pointed_by"].get(node.id_, [])
195
+ edges = self.node_relations["out"].get(
196
+ node.ln_id, []
197
+ ) + self.node_relations["in"].get(node.ln_id, [])
193
198
  else:
194
- edge = [edge.id_] if isinstance(edge, Edge) else [edge]
199
+ edges = [get_lion_id(edge)]
195
200
 
196
- if len(edge) == 0:
197
- raise ValueError(f"Node {self.id_} is not related to node {node.id_}.")
201
+ if not edges:
202
+ raise RelationError(f"Node is not related to {node.ln_id}.")
198
203
 
199
204
  try:
200
- for edge_id in edge:
205
+ for edge_id in edges:
201
206
  self.remove_edge(node, edge_id)
202
207
  return True
203
- except Exception as e:
204
- raise ValueError(
205
- f"Failed to remove edge between nodes {self.id_} and " f"{node.id_}."
206
- ) from e
207
-
208
- def to_llama_index(self, node_type: Type | str | Any = None, **kwargs) -> Any:
209
- """
210
- Serializes this node for LlamaIndex.
211
-
212
- Args:
213
- node_type (Type | str | Any): The type of node in LlamaIndex.
214
- Defaults to None.
215
- **kwargs: Additional keyword arguments for serialization.
216
-
217
- Returns:
218
- Any: The serialized node for LlamaIndex.
219
- """
220
- return LlamaIndexBridge.to_llama_index_node(self, node_type=node_type, **kwargs)
221
-
222
- def to_langchain(self, **kwargs) -> Any:
223
- """
224
- Serializes this node for Langchain.
225
-
226
- Args:
227
- **kwargs: Additional keyword arguments for serialization.
228
-
229
- Returns:
230
- Any: The serialized node for Langchain.
231
- """
232
- return LangchainBridge.to_langchain_document(self, **kwargs)
233
-
234
- @classmethod
235
- def from_llama_index(cls, llama_node: Any, **kwargs) -> "Node":
236
- """
237
- Deserializes a node from LlamaIndex data.
238
-
239
- Args:
240
- llama_node (Any): The LlamaIndex node data.
241
- **kwargs: Additional keyword arguments for deserialization.
242
-
243
- Returns:
244
- Node: The deserialized node.
245
- """
246
- llama_dict = llama_node.to_dict(**kwargs)
247
- return cls.from_obj(llama_dict)
248
-
249
- @classmethod
250
- def from_langchain(cls, lc_doc: Any) -> "Node":
251
- """Deserializes a node from Langchain data.
252
-
253
- Args:
254
- lc_doc (Any): The Langchain document data.
255
-
256
- Returns:
257
- Node: The deserialized node.
258
- """
259
- langchain_json = lc_doc.to_json()
260
- langchain_dict = {"lc_id": langchain_json["id"], **langchain_json["kwargs"]}
261
- return cls.from_obj(langchain_dict)
262
-
263
- def __str__(self) -> str:
264
- """
265
- Provides a string representation of the node.
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__()
266
218
 
267
- Returns:
268
- str: The string representation of the node.
269
- """
270
- timestamp = f" ({self.timestamp})" if self.timestamp else ""
271
- if self.content:
272
- content_preview = (
273
- f"{self.content[:50]}..." if len(self.content) > 50 else self.content
274
- )
275
- else:
276
- content_preview = ""
277
- meta_preview = (
278
- f"{str(self.metadata)[:50]}..."
279
- if len(str(self.metadata)) > 50
280
- else str(self.metadata)
281
- )
282
- return (
283
- f"{self.class_name()}({self.id_}, {content_preview}, {meta_preview},"
284
- f"{timestamp})"
285
- )
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
@@ -0,0 +1,12 @@
1
+ from .mail import Mail
2
+ from .mail_manager import MailManager
3
+ from .package import Package
4
+ from .start_mail import StartMail
5
+
6
+
7
+ __all__ = [
8
+ "Mail",
9
+ "MailManager",
10
+ "Package",
11
+ "StartMail",
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
+ }