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
@@ -0,0 +1,171 @@
1
+ import unittest
2
+ import asyncio
3
+ from lionagi.core.collections.pile import Pile
4
+ from lionagi import Node
5
+
6
+
7
+ class TestPile(unittest.TestCase):
8
+
9
+ def setUp(self):
10
+ """Create initial nodes and piles for testing."""
11
+ self.nodes1 = [Node(content=i) for i in ["A", "B", "C"]]
12
+ self.nodes2 = [Node(content=i) for i in ["D", "E", "F"]]
13
+ self.p1 = Pile(self.nodes1)
14
+ self.p2 = Pile(self.nodes2)
15
+
16
+ def test_add_piles(self):
17
+ """Test adding two piles."""
18
+ combined_pile = self.p1 + self.p2
19
+ self.assertEqual(len(combined_pile), 6)
20
+ contents = [node.content for node in combined_pile.values()]
21
+ self.assertListEqual(contents, ["A", "B", "C", "D", "E", "F"])
22
+
23
+ def test_subtract_piles(self):
24
+ """Test subtracting nodes from a pile."""
25
+ with self.assertRaises(Exception):
26
+ result_pile = self.p1 - self.nodes2[0] # D not in p1
27
+
28
+ def test_containment(self):
29
+ """Test containment check."""
30
+ self.assertIn(self.nodes1[1], self.p1) # B in p1
31
+ self.assertNotIn(self.nodes2[0], self.p1) # D not in p1
32
+
33
+ def test_include_nodes(self):
34
+ """Test including nodes in a pile."""
35
+ self.p1.include(self.nodes2)
36
+ self.assertEqual(len(self.p1), 6)
37
+ contents = [node.content for node in self.p1.values()]
38
+ self.assertListEqual(contents, ["A", "B", "C", "D", "E", "F"])
39
+
40
+ def test_exclude_nodes(self):
41
+ """Test excluding nodes from a pile."""
42
+ self.p1.include(self.nodes2) # Include first to have something to exclude
43
+ self.p1.exclude(self.nodes2)
44
+ self.assertEqual(len(self.p1), 5)
45
+ contents = [node.content for node in self.p1.values()]
46
+ self.assertListEqual(contents, ["A", "B", "C", "E", "F"])
47
+
48
+ def test_get_item_by_index(self):
49
+ """Test getting an item by index."""
50
+ node = self.p1[0]
51
+ self.assertEqual(node.content, "A")
52
+
53
+ def test_set_item_by_index(self):
54
+ """Test setting an item at a specific index."""
55
+ new_node = Node(content="Updated Node")
56
+ self.p1[0] = new_node
57
+ self.assertEqual(self.p1[0].content, "Updated Node")
58
+
59
+ def test_insert_item(self):
60
+ """Test inserting an item at a specific position."""
61
+ new_node = Node(content="Inserted Node")
62
+ self.p1.insert(0, new_node)
63
+ self.assertEqual(self.p1[0].content, "Inserted Node")
64
+ self.assertEqual(len(self.p1), 4)
65
+
66
+ def test_homogenous(self):
67
+ """Test if all items in the pile are of the same type."""
68
+ self.assertTrue(self.p1.is_homogenous())
69
+ with self.assertRaises(AttributeError):
70
+ mixed_pile = Pile(self.nodes1 + [123]) # Adding an int to mix types
71
+
72
+ def test_is_empty(self):
73
+ """Test if the pile is empty."""
74
+ empty_pile = Pile()
75
+ self.assertTrue(empty_pile.is_empty())
76
+ self.assertFalse(self.p1.is_empty())
77
+
78
+ def test_pop_item(self):
79
+ """Test popping an item from the pile."""
80
+ popped_node = self.p1.pop(self.nodes1[0].ln_id)
81
+ self.assertEqual(popped_node.content, "A")
82
+ self.assertEqual(len(self.p1), 2)
83
+
84
+ def test_clear_pile(self):
85
+ """Test clearing the pile."""
86
+ self.p1.clear()
87
+ self.assertTrue(self.p1.is_empty())
88
+
89
+ def test_update_pile(self):
90
+ """Test updating the pile with another pile."""
91
+ self.p1.update(self.p2)
92
+ self.assertEqual(len(self.p1), 6)
93
+ contents = [node.content for node in self.p1.values()]
94
+ self.assertListEqual(contents, ["A", "B", "C", "D", "E", "F"])
95
+
96
+ def test_to_dataframe(self):
97
+ """Test converting the pile to a DataFrame."""
98
+ df = self.p1.to_df()
99
+ self.assertEqual(len(df), 3)
100
+ self.assertListEqual(df["content"].tolist(), ["A", "B", "C"])
101
+
102
+ def test_create_index(self):
103
+ """Test creating an index for the pile."""
104
+ with self.assertRaises(ValueError):
105
+ self.p1.create_index(index_type="llama_index")
106
+
107
+ def test_create_query_engine(self):
108
+ """Test creating a query engine for the pile."""
109
+ with self.assertRaises(ValueError):
110
+ self.p1.create_query_engine(index_type="llama_index")
111
+
112
+ def test_create_chat_engine(self):
113
+ """Test creating a chat engine for the pile."""
114
+ with self.assertRaises(ValueError):
115
+ self.p1.create_chat_engine(index_type="llama_index")
116
+
117
+ # async def test_query_pile(self):
118
+ # """Test querying the pile."""
119
+ # self.p1.create_query_engine(index_type="llama_index")
120
+ # response = await self.p1.query_pile(query="test query")
121
+ # self.assertIsInstance(response, str)
122
+
123
+ # async def test_chat_pile(self):
124
+ # """Test chatting with the pile."""
125
+ # self.p1.create_chat_engine(index_type="llama_index")
126
+ # response = await self.p1.chat_pile(query="test chat")
127
+ # self.assertIsInstance(response, str)
128
+
129
+ # async def test_embed_pile(self):
130
+ # """Test embedding the items in the pile."""
131
+ # await self.p1.embed_pile()
132
+ # for node in self.p1.values():
133
+ # self.assertIn("embedding", node._all_fields)
134
+
135
+ def test_to_csv(self):
136
+ """Test saving the pile to a CSV file."""
137
+ self.p1.to_csv("test_pile.csv")
138
+ with open("test_pile.csv", "r") as f:
139
+ content = f.read()
140
+ self.assertIn("content", content)
141
+
142
+ def test_from_csv(self):
143
+ """Test loading a pile from a CSV file."""
144
+ self.p1.to_csv("test_pile.csv")
145
+ loaded_pile = Pile.from_csv("test_pile.csv")
146
+ self.assertEqual(len(loaded_pile), 3)
147
+ self.assertEqual(loaded_pile[0].content, "A")
148
+
149
+ def test_from_dataframe(self):
150
+ """Test loading a pile from a DataFrame."""
151
+ df = self.p1.to_df()
152
+ loaded_pile = Pile.from_df(df)
153
+ self.assertEqual(len(loaded_pile), 3)
154
+ self.assertEqual(loaded_pile[0].content, "A")
155
+
156
+ def test_as_query_tool(self):
157
+ """Test creating a query tool for the pile."""
158
+ with self.assertRaises(ValueError):
159
+ tool = self.p1.as_query_tool(index_type="llama_index")
160
+
161
+ def test_str_representation(self):
162
+ """Test the string representation of the pile."""
163
+ self.assertIsInstance(str(self.p1), str)
164
+
165
+ def test_repr_representation(self):
166
+ """Test the representation of the pile."""
167
+ self.assertIsInstance(repr(self.p1), str)
168
+
169
+
170
+ if __name__ == "__main__":
171
+ unittest.main()
@@ -0,0 +1,129 @@
1
+ import unittest
2
+ from lionagi.core.collections.abc import ItemNotFoundError
3
+ from lionagi.core.collections import Progression
4
+ from lionagi import Node
5
+
6
+
7
+ class TestProgression(unittest.TestCase):
8
+
9
+ def setUp(self):
10
+ self.nodes = [Node() for _ in range(5)]
11
+ self.p = Progression(order=[node.ln_id for node in self.nodes])
12
+
13
+ def test_initial_order(self):
14
+ self.assertEqual(self.p.order, [node.ln_id for node in self.nodes])
15
+
16
+ def test_append_node(self):
17
+ new_node = Node()
18
+ self.p.append(new_node)
19
+ self.assertIn(new_node.ln_id, self.p.order)
20
+ self.assertEqual(len(self.p), 6)
21
+
22
+ def test_extend_single_node(self):
23
+ new_node = Node()
24
+ self.p.extend(new_node)
25
+ self.assertIn(new_node.ln_id, self.p.order)
26
+ self.assertEqual(len(self.p), 6)
27
+
28
+ def test_extend_multiple_nodes(self):
29
+ new_nodes = [Node() for _ in range(3)]
30
+ self.p.extend(new_nodes)
31
+ for node in new_nodes:
32
+ self.assertIn(node.ln_id, self.p.order)
33
+ self.assertEqual(len(self.p), 8)
34
+
35
+ def test_include_node(self):
36
+ new_node = Node()
37
+ self.p.include(new_node)
38
+ self.assertIn(new_node.ln_id, self.p.order)
39
+
40
+ def test_getitem_slice(self):
41
+ slice_result = self.p[1:5]
42
+ self.assertEqual(slice_result.order, self.p.order[1:5])
43
+
44
+ def test_setitem(self):
45
+ new_node = Node()
46
+ self.p[0] = new_node.ln_id
47
+ self.assertEqual(self.p[0], new_node.ln_id)
48
+
49
+ def test_add_node(self):
50
+ new_node = Node()
51
+ new_prog = self.p + new_node
52
+ self.assertIn(new_node.ln_id, new_prog.order)
53
+
54
+ def test_iadd_node(self):
55
+ new_node = Node()
56
+ self.p += new_node
57
+ self.assertIn(new_node.ln_id, self.p.order)
58
+
59
+ def test_subtract_node(self):
60
+ node_to_remove = self.nodes[0]
61
+ self.p -= node_to_remove.ln_id
62
+ self.assertNotIn(node_to_remove.ln_id, self.p.order)
63
+
64
+ def test_isubtract_node(self):
65
+ node_to_remove = self.nodes[0]
66
+ self.p -= node_to_remove.ln_id
67
+ self.assertNotIn(node_to_remove.ln_id, self.p.order)
68
+
69
+ def test_popleft(self):
70
+ leftmost = self.p.order[0]
71
+ self.assertEqual(self.p.popleft(), leftmost)
72
+ self.assertNotIn(leftmost, self.p.order)
73
+
74
+ def test_exclude_node(self):
75
+ node_to_exclude = self.nodes[0]
76
+ self.p.exclude(node_to_exclude.ln_id)
77
+ self.assertNotIn(node_to_exclude.ln_id, self.p.order)
78
+
79
+ def test_clear(self):
80
+ self.p.clear()
81
+ self.assertEqual(len(self.p), 0)
82
+
83
+ def test_to_dict(self):
84
+ p_dict = self.p.to_dict()
85
+ self.assertEqual(p_dict["order"], self.p.order)
86
+ self.assertEqual(p_dict["name"], self.p.name)
87
+
88
+ def test_bool(self):
89
+ self.assertTrue(bool(self.p))
90
+
91
+ def test_remove_nonexistent_node(self):
92
+ non_existent_node = Node()
93
+ with self.assertRaises(ItemNotFoundError):
94
+ self.p.remove(non_existent_node.ln_id)
95
+
96
+ def test_pop_index_error(self):
97
+ with self.assertRaises(ItemNotFoundError):
98
+ self.p.pop(100)
99
+
100
+ def test_popleft_index_error(self):
101
+ empty_prog = Progression()
102
+ with self.assertRaises(ItemNotFoundError):
103
+ empty_prog.popleft()
104
+
105
+ def test_iteration(self):
106
+ nodes_list = [node.ln_id for node in self.nodes]
107
+ for i, node in enumerate(self.p):
108
+ self.assertEqual(node, nodes_list[i])
109
+
110
+ def test_contains(self):
111
+ node_to_check = self.nodes[0]
112
+ self.assertIn(node_to_check.ln_id, self.p)
113
+
114
+ def test_not_contains(self):
115
+ non_existent_node = Node()
116
+ self.assertNotIn(non_existent_node.ln_id, self.p)
117
+
118
+ def test_equality(self):
119
+ new_prog = Progression(order=[node.ln_id for node in self.nodes])
120
+ self.assertEqual(self.p.order, new_prog.order)
121
+
122
+ def test_inequality(self):
123
+ new_node = Node()
124
+ new_prog = self.p + new_node
125
+ self.assertNotEqual(self.p.order, new_prog)
126
+
127
+
128
+ if __name__ == "__main__":
129
+ unittest.main()
@@ -0,0 +1,67 @@
1
+ import unittest
2
+ from unittest.mock import AsyncMock, patch
3
+ from pydantic import ValidationError
4
+ from lionagi.core.collections.abc import Component, get_lion_id, LionIDable, Condition
5
+ from lionagi.core.generic.edge_condition import EdgeCondition
6
+ from lionagi.core.generic.edge import Edge
7
+
8
+
9
+ class TestEdge(unittest.TestCase):
10
+
11
+ def setUp(self):
12
+ self.node1 = Component()
13
+ self.node2 = Component()
14
+ self.edge = Edge(head=self.node1.ln_id, tail=self.node2.ln_id)
15
+
16
+ def test_initialization(self):
17
+ """Test initialization of the Edge class."""
18
+ self.assertEqual(self.edge.head, self.node1.ln_id)
19
+ self.assertEqual(self.edge.tail, self.node2.ln_id)
20
+ self.assertIsNone(self.edge.condition)
21
+ self.assertIsNone(self.edge.label)
22
+ self.assertFalse(self.edge.bundle)
23
+
24
+ async def test_check_condition_without_condition(self):
25
+ """Test check_condition raises ValueError when condition is not set."""
26
+ with self.assertRaises(ValueError):
27
+ await self.edge.check_condition({})
28
+
29
+ @patch("lionagi.core.models.edge.EdgeCondition.applies", new_callable=AsyncMock)
30
+ async def test_check_condition_with_condition(self, mock_applies):
31
+ """Test check_condition with a set condition."""
32
+ mock_applies.return_value = True
33
+ condition = EdgeCondition()
34
+ self.edge.condition = condition
35
+ result = await self.edge.check_condition({})
36
+ self.assertTrue(result)
37
+ mock_applies.assert_awaited_once()
38
+
39
+ def test_validate_head_tail(self):
40
+ """Test the head and tail validation."""
41
+ try:
42
+ valid_id = get_lion_id("valid_head")
43
+ except Exception as e:
44
+ if e.__class__.__name__ == "LionTypeError":
45
+ return
46
+ self.assertEqual(Edge._validate_head_tail(valid_id), valid_id)
47
+ with self.assertRaises(ValidationError):
48
+ Edge._validate_head_tail("invalid_id")
49
+
50
+ def test_string_condition_none(self):
51
+ """Test string_condition method when condition is None."""
52
+ self.assertIsNone(self.edge.string_condition())
53
+
54
+ def test_len(self):
55
+ """Test the __len__ method."""
56
+ self.assertEqual(len(self.edge), 1)
57
+
58
+ def test_contains(self):
59
+ """Test the __contains__ method."""
60
+ self.assertIn(self.node1.ln_id, self.edge)
61
+ self.assertIn(self.node2.ln_id, self.edge)
62
+ fake_node = Component()
63
+ self.assertNotIn(fake_node.ln_id, self.edge)
64
+
65
+
66
+ if __name__ == "__main__":
67
+ unittest.main()
@@ -0,0 +1,96 @@
1
+ import unittest
2
+ from lionagi.core.collections.abc import LionTypeError, ItemNotFoundError
3
+ from lionagi.core.generic import Graph, Node, Edge
4
+ from lionagi.libs.ln_convert import to_list
5
+
6
+
7
+ class TestGraph(unittest.TestCase):
8
+
9
+ def setUp(self):
10
+ """Set up a basic graph for testing."""
11
+ self.node1 = Node()
12
+ self.node2 = Node()
13
+ self.node3 = Node()
14
+ self.node4 = Node()
15
+ self.g = Graph()
16
+
17
+ def test_initial_size(self):
18
+ """Test the initial size of the graph."""
19
+ self.assertEqual(self.g.size(), 0)
20
+
21
+ def test_initial_internal_edges(self):
22
+ """Test the initial number of internal edges."""
23
+ self.assertEqual(len(self.g.internal_edges), 0)
24
+
25
+ def test_add_node_no_edge(self):
26
+ """Test that adding nodes does not create any edges."""
27
+ self.g.add_node([self.node1, self.node2, self.node3, self.node4])
28
+ self.assertEqual(len(self.g.internal_edges), 0)
29
+
30
+ def test_add_edges(self):
31
+ """Test adding edges between nodes."""
32
+ self.g.add_edge(self.node1, self.node2)
33
+ self.g.add_edge(self.node2, self.node3)
34
+ self.g.add_edge(self.node3, self.node4)
35
+ self.assertEqual(len(self.g.internal_edges), 3)
36
+
37
+ def test_get_node(self):
38
+ """Test retrieving a node by its identifier."""
39
+ self.g.add_node(self.node2)
40
+ node5 = self.g.get_node(self.node2)
41
+ self.assertEqual(node5, self.node2)
42
+
43
+ def test_get_node_edges(self):
44
+ """Test getting the edges of a node."""
45
+ self.g.add_edge(self.node1, self.node2)
46
+ self.assertEqual(len(self.g.get_node_edges(self.node1)), 1)
47
+
48
+ def test_get_heads(self):
49
+ """Test getting head nodes."""
50
+ self.g.add_edge(self.node1, self.node2)
51
+ self.g.add_edge(self.node2, self.node3)
52
+ self.g.add_edge(self.node3, self.node4)
53
+ self.assertEqual(len(self.g.get_heads()), 1)
54
+
55
+ def test_is_acyclic(self):
56
+ """Test if the graph is acyclic."""
57
+ self.g.add_edge(self.node1, self.node2)
58
+ self.g.add_edge(self.node2, self.node3)
59
+ self.g.add_edge(self.node3, self.node4)
60
+ self.assertTrue(self.g.is_acyclic())
61
+
62
+ def test_remove_edge(self):
63
+ """Test removing an edge from the graph."""
64
+ self.g.add_edge(self.node1, self.node2)
65
+ self.g.add_edge(self.node2, self.node3)
66
+ self.g.add_edge(self.node3, self.node4)
67
+ edge = self.g.internal_edges[0]
68
+ self.g.remove_edge(edge)
69
+ self.assertEqual(len(self.g.internal_edges), 2)
70
+
71
+ def test_remove_nonexistent_edge(self):
72
+ """Test removing a nonexistent edge."""
73
+ self.g.add_edge(self.node1, self.node2)
74
+ with self.assertRaises(ItemNotFoundError):
75
+ self.g.remove_edge(edge=Edge(head=self.node2, tail=self.node3))
76
+
77
+ def test_clear(self):
78
+ """Test clearing all nodes and edges from the graph."""
79
+ self.g.add_node([self.node1, self.node2, self.node3, self.node4])
80
+ self.g.add_edge(self.node1, self.node2)
81
+ self.g.add_edge(self.node2, self.node3)
82
+ self.g.add_edge(self.node3, self.node4)
83
+ self.g.clear()
84
+ self.assertTrue(self.g.is_empty())
85
+ self.assertEqual(len(self.g.internal_edges), 0)
86
+
87
+ def test_display(self):
88
+ """Test displaying the graph (visual check)."""
89
+ self.g.add_edge(self.node1, self.node2)
90
+ self.g.add_edge(self.node2, self.node3)
91
+ self.g.add_edge(self.node3, self.node4)
92
+ self.g.display() # This should display the graph visually
93
+
94
+
95
+ if __name__ == "__main__":
96
+ unittest.main()
@@ -0,0 +1,106 @@
1
+ import unittest
2
+ from lionagi.core.generic import Node
3
+ from lionagi.core.collections.abc import RelationError, Condition
4
+ from lionagi.core.generic.edge import Edge
5
+
6
+
7
+ class TestNode(unittest.TestCase):
8
+
9
+ def setUp(self):
10
+ """Setup nodes for testing."""
11
+ self.node_a = Node()
12
+ self.node_b = Node()
13
+ self.node_c = Node()
14
+
15
+ def test_initialization(self):
16
+ """Test that a Node initializes with empty relations."""
17
+ self.assertEqual(len(self.node_a.relations["in"]), 0)
18
+ self.assertEqual(len(self.node_a.relations["out"]), 0)
19
+
20
+ def test_relate_outgoing(self):
21
+ """Test relating nodes with an outgoing edge."""
22
+ self.node_a.relate(self.node_b, "out")
23
+ self.assertIn(self.node_b.ln_id, self.node_a.related_nodes)
24
+ self.assertIn(self.node_a.ln_id, self.node_b.related_nodes)
25
+ self.assertIn(self.node_b.ln_id, self.node_a.successors)
26
+ self.assertIn(self.node_a.ln_id, self.node_b.predecessors)
27
+
28
+ def test_relate_incoming(self):
29
+ """Test relating nodes with an incoming edge."""
30
+ self.node_a.relate(self.node_c, "in")
31
+ self.assertIn(self.node_c.ln_id, self.node_a.related_nodes)
32
+ self.assertIn(self.node_a.ln_id, self.node_c.related_nodes)
33
+ self.assertIn(self.node_a.ln_id, self.node_c.successors)
34
+ self.assertIn(self.node_c.ln_id, self.node_a.predecessors)
35
+
36
+ def test_invalid_relate(self):
37
+ """Test that relating nodes with an invalid direction raises ValueError."""
38
+ with self.assertRaises(ValueError):
39
+ self.node_a.relate(self.node_b, "invalid_direction")
40
+
41
+ def test_remove_edge(self):
42
+ """Test removing a specific edge between nodes."""
43
+ self.node_a.relate(self.node_b, "out")
44
+ edge = next(iter(self.node_a.relations["out"]))
45
+ self.assertTrue(self.node_a.remove_edge(self.node_b, edge))
46
+
47
+ def test_unrelate_all_edges(self):
48
+ """Test unrelating all edges between nodes."""
49
+ self.node_a.relate(self.node_b, "out")
50
+ self.assertTrue(self.node_a.unrelate(self.node_b))
51
+ self.assertNotIn(self.node_b.ln_id, self.node_a.related_nodes)
52
+ self.assertNotIn(self.node_a.ln_id, self.node_b.related_nodes)
53
+
54
+ def test_unrelate_specific_edge(self):
55
+ """Test unrelating a specific edge between nodes."""
56
+ self.node_a.relate(self.node_b, "out")
57
+ edge = next(iter(self.node_a.relations["out"]))
58
+ self.assertTrue(self.node_a.unrelate(self.node_b, edge))
59
+ self.assertNotIn(self.node_b.ln_id, self.node_a.related_nodes)
60
+ self.assertNotIn(self.node_a.ln_id, self.node_b.related_nodes)
61
+
62
+ def test_node_relations(self):
63
+ """Test that node relations are correctly categorized."""
64
+ self.node_a.relate(self.node_b, "out")
65
+ self.node_a.relate(self.node_c, "in")
66
+ relations = self.node_a.node_relations
67
+ self.assertIn(self.node_b.ln_id, relations["out"])
68
+ self.assertIn(self.node_c.ln_id, relations["in"])
69
+
70
+ def test_related_nodes(self):
71
+ """Test that related nodes list is correct."""
72
+ self.node_a.relate(self.node_b, "out")
73
+ self.node_a.relate(self.node_c, "in")
74
+ related_nodes = self.node_a.related_nodes
75
+ self.assertIn(self.node_b.ln_id, related_nodes)
76
+ self.assertIn(self.node_c.ln_id, related_nodes)
77
+
78
+ def test_predecessors(self):
79
+ """Test that predecessors list is correct."""
80
+ self.node_a.relate(self.node_c, "in")
81
+ self.assertIn(self.node_c.ln_id, self.node_a.predecessors)
82
+
83
+ def test_successors(self):
84
+ """Test that successors list is correct."""
85
+ self.node_a.relate(self.node_b, "out")
86
+ self.assertIn(self.node_b.ln_id, self.node_a.successors)
87
+
88
+ def test_str(self):
89
+ """Test the string representation of a node."""
90
+ self.node_a.relate(self.node_b, "out")
91
+ node_str = str(self.node_a)
92
+ self.assertIn(self.node_a.ln_id, node_str)
93
+ self.assertIn("relations", node_str)
94
+ self.assertIn("1", node_str) # 1 relation
95
+
96
+ def test_repr(self):
97
+ """Test the repr representation of a node."""
98
+ self.node_a.relate(self.node_b, "out")
99
+ node_repr = repr(self.node_a)
100
+ self.assertIn(self.node_a.ln_id, node_repr)
101
+ self.assertIn("relations", node_repr)
102
+ self.assertIn("1", node_repr) # 1 relation
103
+
104
+
105
+ if __name__ == "__main__":
106
+ unittest.main()
@@ -0,0 +1,73 @@
1
+ import unittest
2
+ from lionagi.core.generic.tree_node import TreeNode
3
+ from lionagi.core.generic.tree import Tree
4
+
5
+
6
+ class TestTreeNode(unittest.TestCase):
7
+
8
+ def setUp(self):
9
+ self.tree_node1 = TreeNode()
10
+ self.tree_node2 = TreeNode()
11
+ self.tree_node3 = TreeNode()
12
+ self.tree_node4 = TreeNode()
13
+ self.tree = Tree()
14
+
15
+ def test_initialization(self):
16
+ self.assertIsNone(self.tree_node1.parent)
17
+ self.assertEqual(self.tree_node1.children, [])
18
+ self.assertIsInstance(self.tree_node1, TreeNode)
19
+ self.assertIsInstance(self.tree_node1.ln_id, str)
20
+
21
+ def test_relate_child(self):
22
+ self.tree_node1.relate_child([self.tree_node2, self.tree_node3])
23
+ self.assertIn(self.tree_node2.ln_id, self.tree_node1.children)
24
+ self.assertIn(self.tree_node3.ln_id, self.tree_node1.children)
25
+ self.assertEqual(self.tree_node2.parent, self.tree_node1)
26
+ self.assertEqual(self.tree_node3.parent, self.tree_node1)
27
+
28
+ def test_relate_parent(self):
29
+ self.tree_node1.relate_parent(self.tree_node4)
30
+ self.assertEqual(self.tree_node1.parent, self.tree_node4)
31
+ self.assertIn(self.tree_node1.ln_id, self.tree_node4.children)
32
+
33
+ def test_unrelate_parent(self):
34
+ self.tree_node1.relate_parent(self.tree_node4)
35
+ self.tree_node1.unrelate_parent()
36
+ self.assertIsNone(self.tree_node1.parent)
37
+
38
+ def test_unrelate_child(self):
39
+ self.tree_node1.relate_child([self.tree_node2, self.tree_node3])
40
+ self.tree_node1.unrelate_child([self.tree_node2, self.tree_node3])
41
+ self.assertNotIn(self.tree_node2.ln_id, self.tree_node1.children)
42
+ self.assertNotIn(self.tree_node3.ln_id, self.tree_node1.children)
43
+ self.assertIsNone(self.tree_node2.parent)
44
+ self.assertIsNone(self.tree_node3.parent)
45
+
46
+ def test_tree_integration(self):
47
+ self.tree.relate_parent_child(
48
+ self.tree_node1, [self.tree_node2, self.tree_node3, self.tree_node4]
49
+ )
50
+ self.assertEqual(len(self.tree.internal_nodes), 4)
51
+ self.assertEqual(len(self.tree_node1.children), 3)
52
+ self.assertEqual(self.tree.root, self.tree_node1)
53
+
54
+ def test_tree_node_edges(self):
55
+ self.tree_node1.relate_child([self.tree_node2, self.tree_node3])
56
+ df = self.tree_node1.edges.to_df()
57
+ self.assertEqual(len(df), 2)
58
+ self.assertIn(self.tree_node2.ln_id, df["tail"].values)
59
+ self.assertIn(self.tree_node3.ln_id, df["tail"].values)
60
+
61
+ def test_tree_display(self):
62
+ # This test is mainly to check if the display function runs without errors
63
+ self.tree.relate_parent_child(
64
+ self.tree_node1, [self.tree_node2, self.tree_node3, self.tree_node4]
65
+ )
66
+ try:
67
+ self.tree.display()
68
+ except Exception as e:
69
+ self.fail(f"Tree display method raised an exception: {e}")
70
+
71
+
72
+ if __name__ == "__main__":
73
+ unittest.main()