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,67 @@
1
+ import asyncio
2
+ import unittest
3
+ from unittest.mock import patch
4
+ from lionagi.libs.ln_queue import AsyncQueue
5
+
6
+
7
+ class TestAsyncQueue(unittest.TestCase):
8
+ def setUp(self):
9
+ self.loop = asyncio.new_event_loop()
10
+ asyncio.set_event_loop(self.loop)
11
+ self.queue = AsyncQueue(max_concurrent_tasks=3)
12
+
13
+ def tearDown(self):
14
+ self.loop.close()
15
+
16
+ def test_enqueue_dequeue(self):
17
+ async def test():
18
+ await self.queue.enqueue("task1")
19
+ result = await self.queue.dequeue()
20
+ self.assertEqual(result, "task1")
21
+
22
+ self.loop.run_until_complete(test())
23
+
24
+ def test_stop(self):
25
+ async def test():
26
+ await self.queue.stop()
27
+ self.assertTrue(self.queue.stopped())
28
+
29
+ self.loop.run_until_complete(test())
30
+
31
+ @patch("lionagi.libs.ln_func_call.rcall", autospec=True)
32
+ def test_process_requests(self, mock_rcall):
33
+ future = asyncio.Future()
34
+ future.set_result("Processed successfully")
35
+ mock_rcall.return_value = future
36
+
37
+ async def processor(task):
38
+ return f"Processed {task}"
39
+
40
+ async def add_tasks():
41
+ for i in range(5):
42
+ await self.queue.enqueue(f"task{i}")
43
+ # This ensures that the queue stops after all tasks are added
44
+ await self.queue.stop()
45
+
46
+ async def process():
47
+ await self.queue.process_requests(processor, retry_kwargs={"retries": 2})
48
+
49
+ self.loop.create_task(add_tasks())
50
+ self.loop.run_until_complete(process())
51
+ # mock_rcall.assert_called()
52
+
53
+ def test_concurrency_limit(self):
54
+ async def add_tasks():
55
+ for i in range(10):
56
+ await self.queue.enqueue(f"task{i}")
57
+
58
+ async def process():
59
+ await asyncio.sleep(0.1)
60
+ await self.queue.stop()
61
+
62
+ self.loop.run_until_complete(add_tasks())
63
+ self.loop.run_until_complete(process())
64
+
65
+
66
+ if __name__ == "__main__":
67
+ unittest.main()
File without changes
@@ -0,0 +1,206 @@
1
+ import unittest
2
+ from lionagi.core.collections.abc.component import (
3
+ Component,
4
+ LionValueError,
5
+ LionTypeError,
6
+ )
7
+ import pandas as pd
8
+ from datetime import datetime
9
+ import json
10
+
11
+
12
+ class TestComponent(unittest.TestCase):
13
+
14
+ def setUp(self):
15
+ """Set up a basic Component instance for testing."""
16
+ self.component = Component()
17
+
18
+ def test_initialization(self):
19
+ """Test basic initialization and attributes."""
20
+ self.assertIsInstance(self.component.ln_id, str)
21
+ self.assertIsInstance(self.component.timestamp, str)
22
+ self.assertIsInstance(self.component.metadata, dict)
23
+ self.assertIsNone(self.component.content)
24
+ self.assertEqual(self.component.embedding, [])
25
+
26
+ def test_setting_attributes(self):
27
+ """Test setting and updating attributes."""
28
+ self.component.content = 1
29
+ self.assertEqual(self.component.content, 1)
30
+ self.assertIn("content", self.component.metadata["last_updated"])
31
+
32
+ def test_class_name(self):
33
+ """Test the class_name property."""
34
+ self.assertEqual(self.component.class_name, "Component")
35
+
36
+ def test_to_dict(self):
37
+ """Test converting to dictionary."""
38
+ self.component.content = "example content"
39
+ dict_repr = self.component.to_dict()
40
+ self.assertEqual(dict_repr["content"], "example content")
41
+ self.assertEqual(dict_repr["lion_class"], "Component")
42
+
43
+ def test_to_json_str(self):
44
+ """Test converting to JSON string."""
45
+ self.component.content = "example content"
46
+ json_str = self.component.to_json_str()
47
+ self.assertIn('"content": "example content"', json_str)
48
+
49
+ def test_to_xml(self):
50
+ """Test converting to XML string."""
51
+ self.component.content = "example content"
52
+ xml_str = self.component.to_xml()
53
+ self.assertIn("<content>example content</content>", xml_str)
54
+
55
+ def test_to_pd_series(self):
56
+ """Test converting to Pandas Series."""
57
+ self.component.content = "example content"
58
+ series = self.component.to_pd_series()
59
+ self.assertEqual(series["content"], "example content")
60
+
61
+ def test_from_obj_dict(self):
62
+ """Test creating a Component from a dictionary."""
63
+ dict_obj = {"a": 1, "b": 2}
64
+ new_component = Component.from_obj(dict_obj)
65
+ self.assertEqual(new_component.metadata["a"], 1)
66
+ self.assertEqual(new_component.metadata["b"], 2)
67
+
68
+ def test_from_obj_str(self):
69
+ """Test creating a Component from a JSON string."""
70
+ json_str = '{"a": 1, "b": 2}'
71
+ new_component = Component.from_obj(json_str)
72
+ self.assertEqual(new_component.metadata["a"], 1)
73
+ self.assertEqual(new_component.metadata["b"], 2)
74
+
75
+ def test_from_obj_fuzzy_str(self):
76
+ """Test creating a Component from a fuzzy JSON string."""
77
+ fuzzy_json_str = '{"name": "John", "age": 30, "city": ["New York", "DC", "LA"]'
78
+ new_component = Component.from_obj(fuzzy_json_str, fuzzy_parse=True)
79
+ self.assertEqual(new_component.metadata["name"], "John")
80
+ self.assertEqual(new_component.metadata["age"], 30)
81
+ self.assertEqual(new_component.metadata["city"], ["New York", "DC", "LA"])
82
+
83
+ def test_from_obj_series(self):
84
+ """Test creating a Component from a Pandas Series."""
85
+ series_obj = pd.Series({"a": 1, "b": 2})
86
+ new_component = Component.from_obj(series_obj)
87
+ self.assertEqual(new_component.metadata["a"], 1)
88
+ self.assertEqual(new_component.metadata["b"], 2)
89
+
90
+ def test_from_obj_dataframe(self):
91
+ """Test creating Components from a Pandas DataFrame."""
92
+ df = pd.DataFrame({"a": [1, 2], "b": [3, 4]}, index=["row1", "row2"])
93
+ components = Component.from_obj(df)
94
+ self.assertEqual(len(components), 2)
95
+ self.assertEqual(components[0].metadata["a"], 1)
96
+ self.assertEqual(components[0].metadata["b"], 3)
97
+ self.assertEqual(components[1].metadata["a"], 2)
98
+ self.assertEqual(components[1].metadata["b"], 4)
99
+
100
+ def test_metadata_manipulation(self):
101
+ """Test manipulation of metadata."""
102
+ self.component._meta_insert(["new_key"], "new_value")
103
+ self.assertEqual(self.component.metadata["new_key"], "new_value")
104
+ self.component._meta_set(["new_key"], "updated_value")
105
+ self.assertEqual(self.component.metadata["new_key"], "updated_value")
106
+ nested_value = {"a": 1, "b": 2}
107
+ self.component._meta_insert(["nested", 0], nested_value)
108
+ self.assertEqual(self.component.metadata["nested"][0], nested_value)
109
+ self.assertEqual(self.component._meta_get(["nested", 0, "a"]), 1)
110
+
111
+ def test_invalid_metadata_assignment(self):
112
+ """Test invalid direct assignment to metadata."""
113
+ with self.assertRaises(AttributeError):
114
+ self.component.metadata = {}
115
+
116
+ def test_field_annotations(self):
117
+ """Test field annotations."""
118
+ annotations = self.component._field_annotations
119
+ self.assertIn("ln_id", annotations)
120
+ self.assertIn("timestamp", annotations)
121
+
122
+ def test_dynamic_field_addition(self):
123
+ """Test adding fields dynamically to the Component."""
124
+ self.component._add_field(
125
+ "welcome", str, default="new value", value="hello world again"
126
+ )
127
+ self.assertEqual(
128
+ self.component._get_field_attr("welcome", "default"), "new value"
129
+ )
130
+ self.assertEqual(getattr(self.component, "welcome"), "hello world again")
131
+
132
+ def test_validation_error_handling(self):
133
+ """Test handling of validation errors."""
134
+ with self.assertRaises(LionValueError):
135
+ Component.from_obj({"ln_id": 12345}) # Invalid ln_id type
136
+
137
+ def test_str_repr_methods(self):
138
+ """Test __str__ and __repr__ methods."""
139
+ self.component.content = "example content"
140
+ self.assertIn("example content", str(self.component))
141
+ self.assertIn("example content", repr(self.component))
142
+
143
+ def test_embedded_content(self):
144
+ """Test embedded content handling."""
145
+ embedding_str = "[1.0, 2.0, 3.0]"
146
+ self.component.embedding = self.component._validate_embedding(embedding_str)
147
+ self.assertEqual(self.component.embedding, [1.0, 2.0, 3.0])
148
+
149
+ def test_invalid_embedded_content(self):
150
+ """Test invalid embedded content handling."""
151
+ with self.assertRaises(ValueError):
152
+ self.component._validate_embedding("[1.0, 'invalid', 3.0]")
153
+
154
+ def test_timestamp_format(self):
155
+ """Test if the timestamp is in the correct format."""
156
+ timestamp = self.component.timestamp
157
+ try:
158
+ datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%f")
159
+ except ValueError:
160
+ self.fail("Timestamp is not in the correct format")
161
+
162
+ def test_default_field_values(self):
163
+ """Test the default values of fields."""
164
+ default_fields = self.component._all_fields
165
+ self.assertEqual(default_fields["embedding"].default, [])
166
+ self.assertEqual(default_fields["metadata"].default_factory(), {})
167
+ self.assertEqual(default_fields["extra_fields"].default_factory(), {})
168
+ self.assertIsNone(default_fields["content"].default)
169
+
170
+ def test_deeply_nested_metadata(self):
171
+ """Test setting and getting deeply nested metadata."""
172
+ nested_value = {"level1": {"level2": {"level3": "deep_value"}}}
173
+ self.component._meta_insert(["nested", 0], nested_value)
174
+ self.assertEqual(
175
+ self.component._meta_get(["nested", 0, "level1", "level2", "level3"]),
176
+ "deep_value",
177
+ )
178
+
179
+ def test_invalid_from_obj_type(self):
180
+ """Test invalid type in from_obj method."""
181
+ with self.assertRaises(LionTypeError):
182
+ Component.from_obj(12345) # Invalid type
183
+
184
+ def test_from_obj_pydantic_model(self):
185
+ """Test creating a Component from a Pydantic BaseModel."""
186
+ from pydantic import BaseModel
187
+
188
+ class SampleModel(BaseModel):
189
+ name: str
190
+ age: int
191
+
192
+ sample_instance = SampleModel(name="John Doe", age=30)
193
+ new_component = Component.from_obj(sample_instance)
194
+ self.assertEqual(new_component.metadata["name"], "John Doe")
195
+ self.assertEqual(new_component.metadata["age"], 30)
196
+
197
+ def test_json_serialization_deserialization(self):
198
+ """Test JSON serialization and deserialization."""
199
+ original_dict = self.component.to_dict()
200
+ json_str = json.dumps(original_dict)
201
+ new_component = Component.from_obj(json_str)
202
+ self.assertEqual(original_dict, new_component.to_dict())
203
+
204
+
205
+ if __name__ == "__main__":
206
+ unittest.main()
@@ -0,0 +1,138 @@
1
+ import unittest
2
+ from lionagi import Node, pile, progression
3
+ from lionagi.core.collections.abc import Element
4
+ from lionagi.core.collections import Exchange
5
+
6
+
7
+ class TestExchange(unittest.TestCase):
8
+
9
+ def setUp(self):
10
+ # Setup for creating initial nodes, piles, and progressions
11
+ self.nodes = [Node(content=i) for i in range(10)]
12
+ self.pile1 = pile(self.nodes)
13
+ self.prog1 = progression(self.nodes[:5], name="left")
14
+ self.prog2 = progression(self.nodes[5:], name="right")
15
+ self.exchange = Exchange()
16
+
17
+ def test_initialization(self):
18
+ self.assertEqual(len(self.exchange.pile), 0)
19
+ self.assertEqual(len(self.exchange.pending_ins), 0)
20
+ self.assertEqual(len(self.exchange.pending_outs), 0)
21
+
22
+ def test_include_in(self):
23
+ sender_id = "sender1"
24
+ for node in self.nodes[:5]:
25
+ node.sender = sender_id
26
+ self.exchange.include(node, "in")
27
+
28
+ self.assertEqual(len(self.exchange.pile), 5)
29
+ self.assertIn(sender_id, self.exchange.pending_ins)
30
+ self.assertEqual(len(self.exchange.pending_ins[sender_id]), 5)
31
+
32
+ def test_include_out(self):
33
+ for node in self.nodes[:5]:
34
+ self.exchange.include(node, "out")
35
+
36
+ self.assertEqual(len(self.exchange.pile), 5)
37
+ self.assertEqual(len(self.exchange.pending_outs), 5)
38
+
39
+ def test_exclude(self):
40
+ for node in self.nodes[:5]:
41
+ self.exchange.include(node, "out")
42
+
43
+ node_to_remove = self.nodes[0]
44
+ self.exchange.exclude(node_to_remove)
45
+ self.assertNotIn(node_to_remove, self.exchange.pile)
46
+ self.assertNotIn(node_to_remove, self.exchange.pending_outs)
47
+
48
+ def test_exclude_from_in(self):
49
+ sender_id = "sender1"
50
+ for node in self.nodes[:5]:
51
+ node.sender = sender_id
52
+ self.exchange.include(node, "in")
53
+
54
+ node_to_remove = self.nodes[0]
55
+ self.exchange.exclude(node_to_remove)
56
+ self.assertNotIn(node_to_remove, self.exchange.pile)
57
+ self.assertNotIn(node_to_remove, self.exchange.pending_ins[sender_id])
58
+
59
+ def test_senders(self):
60
+ sender_id = "sender1"
61
+ for node in self.nodes[:5]:
62
+ node.sender = sender_id
63
+ self.exchange.include(node, "in")
64
+
65
+ self.assertIn(sender_id, self.exchange.senders)
66
+
67
+ def test_to_dict(self):
68
+ for node in self.nodes[:5]:
69
+ self.exchange.include(node, "out")
70
+
71
+ exchange_dict = self.exchange.to_dict()
72
+ self.assertIn("pile", exchange_dict)
73
+ self.assertIn("pending_ins", exchange_dict)
74
+ self.assertIn("pending_outs", exchange_dict)
75
+
76
+ def test_bool(self):
77
+ self.assertTrue(self.exchange)
78
+
79
+ # Additional Tests for Edge Cases
80
+ def test_include_non_sendable(self):
81
+ non_sendable = Element()
82
+ with self.assertRaises(AttributeError):
83
+ self.exchange.include(non_sendable, "in")
84
+
85
+ def test_exclude_nonexistent_item(self):
86
+ non_existent_node = Node(content="nonexistent")
87
+ result = self.exchange.exclude(non_existent_node)
88
+ self.assertTrue(result)
89
+
90
+ def test_include_multiple_items_in(self):
91
+ sender_id = "sender2"
92
+ new_nodes = [Node(content="new_node1"), Node(content="new_node2")]
93
+ for new_node in new_nodes:
94
+ new_node.sender = sender_id
95
+ self.exchange.include(new_node, "in")
96
+
97
+ self.assertEqual(len(self.exchange.pile), 2)
98
+ self.assertEqual(len(self.exchange.pending_ins[sender_id]), 2)
99
+
100
+ def test_include_multiple_items_out(self):
101
+ new_nodes = [Node(content="new_node1"), Node(content="new_node2")]
102
+ for new_node in new_nodes:
103
+ self.exchange.include(new_node, "out")
104
+
105
+ self.assertEqual(len(self.exchange.pile), 2)
106
+ self.assertEqual(len(self.exchange.pending_outs), 2)
107
+
108
+ def test_exclude_from_empty_exchange(self):
109
+ result = self.exchange.exclude(Node(content="nonexistent"))
110
+ self.assertTrue(result)
111
+
112
+ def test_clear_empty_exchange(self):
113
+ self.exchange.pile.clear()
114
+ self.exchange.pending_ins.clear()
115
+ self.exchange.pending_outs.clear()
116
+ self.assertEqual(len(self.exchange.pile), 0)
117
+ self.assertEqual(len(self.exchange.pending_ins), 0)
118
+ self.assertEqual(len(self.exchange.pending_outs), 0)
119
+
120
+ def test_clear_exchange(self):
121
+ sender_id = "sender3"
122
+ for node in self.nodes[:5]:
123
+ node.sender = sender_id
124
+ self.exchange.include(node, "in")
125
+ for node in self.nodes[5:]:
126
+ self.exchange.include(node, "out")
127
+
128
+ self.exchange.pile.clear()
129
+ self.exchange.pending_ins.clear()
130
+ self.exchange.pending_outs.clear()
131
+
132
+ self.assertEqual(len(self.exchange.pile), 0)
133
+ self.assertEqual(len(self.exchange.pending_ins), 0)
134
+ self.assertEqual(len(self.exchange.pending_outs), 0)
135
+
136
+
137
+ if __name__ == "__main__":
138
+ unittest.main()
@@ -0,0 +1,145 @@
1
+ import unittest
2
+ from lionagi import Node, pile, progression, flow
3
+
4
+
5
+ class TestFlow(unittest.TestCase):
6
+
7
+ def setUp(self):
8
+ # Setup for creating initial nodes, piles, and progressions
9
+ self.nodes = [Node(content=i) for i in range(10)]
10
+ self.pile1 = pile(self.nodes)
11
+ self.prog1 = progression(self.nodes[:5], name="left")
12
+ self.prog2 = progression(self.nodes[5:], name="right")
13
+ self.flow = flow([self.prog1, self.prog2])
14
+
15
+ def test_initialization(self):
16
+ self.assertEqual(len(self.flow.sequences), 2)
17
+ self.assertIn("left", self.flow.registry)
18
+ self.assertIn("right", self.flow.registry)
19
+
20
+ def test_append_to_flow(self):
21
+ new_node = Node(content="new_node")
22
+ self.flow.append(new_node, "left")
23
+ self.assertIn(new_node.ln_id, self.flow.get("left"))
24
+
25
+ def test_exclude_from_flow(self):
26
+ node_to_remove = self.nodes[0]
27
+ self.flow.exclude("left", node_to_remove)
28
+ self.assertNotIn(node_to_remove.ln_id, self.flow.get("left"))
29
+
30
+ def test_get_sequence(self):
31
+ seq = self.flow.get("left")
32
+ self.assertEqual(seq, self.prog1)
33
+ seq_default = self.flow.get("nonexistent", default=None)
34
+ self.assertIsNone(seq_default)
35
+
36
+ def test_register_sequence(self):
37
+ new_prog = progression(self.nodes[2:4], name="middle")
38
+ self.flow.register(new_prog)
39
+ self.assertIn("middle", self.flow.registry)
40
+
41
+ def test_popleft(self):
42
+ first_node_id = self.prog1[0]
43
+ removed_node_id = self.flow.popleft("left")
44
+ self.assertEqual(first_node_id, removed_node_id)
45
+ self.assertNotIn(removed_node_id, self.flow.get("left"))
46
+
47
+ def test_shape_and_size(self):
48
+ shape = self.flow.shape()
49
+ self.assertEqual(shape["left"], 5)
50
+ self.assertEqual(shape["right"], 5)
51
+ size = self.flow.size()
52
+ self.assertEqual(size, 10)
53
+
54
+ def test_clear(self):
55
+ self.flow.clear()
56
+ self.assertEqual(len(self.flow.sequences), 0)
57
+ self.assertEqual(len(self.flow.registry), 0)
58
+
59
+ def test_iteration(self):
60
+ items = list(self.flow)
61
+ self.assertEqual(len(items), 2)
62
+
63
+ def test_to_df(self):
64
+ df = self.flow.to_df()
65
+ self.assertEqual(df.shape[0], 2)
66
+ self.assertEqual(df.columns.tolist(), ["order", "name"])
67
+
68
+ def test_inclusion_check(self):
69
+ self.assertTrue(self.prog1 in self.flow)
70
+ self.assertTrue(self.prog2 in self.flow)
71
+ try:
72
+ a = Node(content="nonexistent") in self.flow
73
+ except TypeError:
74
+ pass
75
+
76
+ # Additional Tests for Edge Cases
77
+ def test_empty_flow_initialization(self):
78
+ empty_flow = flow()
79
+ self.assertEqual(len(empty_flow.sequences), 0)
80
+ self.assertEqual(len(empty_flow.registry), 0)
81
+
82
+ def test_append_to_nonexistent_sequence(self):
83
+ new_node = Node(content="new_node")
84
+ self.flow.append(new_node, "nonexistent")
85
+ self.assertIn(new_node.ln_id, self.flow.get("nonexistent"))
86
+
87
+ def test_exclude_nonexistent_item(self):
88
+ non_existent_node = Node(content="nonexistent")
89
+ result = self.flow.exclude("left", non_existent_node)
90
+ self.assertTrue(result)
91
+
92
+ def test_exclude_nonexistent_sequence(self):
93
+ result = self.flow.exclude("nonexistent")
94
+ self.assertFalse(result)
95
+
96
+ def test_get_nonexistent_sequence(self):
97
+ try:
98
+ self.flow.get("nonexistent")
99
+ except Exception as e:
100
+ if e.__class__.__name__ == "LionTypeError":
101
+ return
102
+ raise AssertionError("LionTypeError not raised")
103
+
104
+ def test_register_existing_sequence_name(self):
105
+ with self.assertRaises(ValueError):
106
+ self.flow.register(self.prog1, name="right")
107
+
108
+ def test_popleft_nonexistent_sequence(self):
109
+ try:
110
+ self.flow.popleft("nonexistent")
111
+ except Exception as e:
112
+ if e.__class__.__name__ == "LionTypeError":
113
+ return
114
+ raise AssertionError("LionTypeError not raised")
115
+
116
+ def test_append_multiple_items(self):
117
+ new_nodes = [Node(content="new_node1"), Node(content="new_node2")]
118
+ for new_node in new_nodes:
119
+ self.flow.append(new_node, "left")
120
+ for new_node in new_nodes:
121
+ self.assertIn(new_node.ln_id, self.flow.get("left"))
122
+
123
+ def test_remove_from_all_sequences(self):
124
+ node_to_remove = self.nodes[0]
125
+ self.flow.append(node_to_remove, "right")
126
+ self.flow.remove(node_to_remove)
127
+ self.assertNotIn(node_to_remove.ln_id, self.flow.get("right"))
128
+
129
+ def test_shape_empty_flow(self):
130
+ empty_flow = flow()
131
+ self.assertEqual(empty_flow.shape(), {})
132
+
133
+ def test_size_empty_flow(self):
134
+ empty_flow = flow()
135
+ self.assertEqual(empty_flow.size(), 0)
136
+
137
+ def test_clear_empty_flow(self):
138
+ empty_flow = flow()
139
+ empty_flow.clear()
140
+ self.assertEqual(len(empty_flow.sequences), 0)
141
+ self.assertEqual(len(empty_flow.registry), 0)
142
+
143
+
144
+ if __name__ == "__main__":
145
+ unittest.main()