lionagi 0.1.2__py3-none-any.whl → 0.2.0__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 +74 -0
  94. lionagi/core/work/work_function.py +92 -0
  95. lionagi/core/work/work_queue.py +81 -0
  96. lionagi/core/work/worker.py +195 -0
  97. lionagi/core/work/worklog.py +124 -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.0.dist-info/LICENSE +202 -0
  168. lionagi-0.2.0.dist-info/METADATA +272 -0
  169. lionagi-0.2.0.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.0.dist-info}/WHEEL +0 -0
  268. {lionagi-0.1.2.dist-info → lionagi-0.2.0.dist-info}/top_level.txt +0 -0
@@ -39,7 +39,7 @@ class TestNSet(unittest.TestCase):
39
39
  """Test flattening and then unflattening a nested dictionary."""
40
40
  original = {"a": {"b": {"c": 1}}}
41
41
  flattened = flatten(original)
42
- self.assertEqual(flattened, {"a_b_c": 1})
42
+ self.assertEqual(flattened, {"a[^_^]b[^_^]c": 1})
43
43
  unflattened = unflatten(flattened)
44
44
  self.assertEqual(original, unflattened)
45
45
 
@@ -225,27 +225,36 @@ class TestFlatten(unittest.TestCase):
225
225
  def test_flatten_nested_dict(self):
226
226
  nested_dict = {"a": {"b": 1, "c": {"d": 2}}}
227
227
  result = flatten(nested_dict)
228
- self.assertEqual(result, {"a_b": 1, "a_c_d": 2})
228
+ self.assertEqual(result, {"a[^_^]b": 1, "a[^_^]c[^_^]d": 2})
229
229
 
230
230
  def test_flatten_nested_list(self):
231
231
  nested_list = [[1, 2], [3, [4, 5]]]
232
232
  result = flatten(nested_list)
233
- self.assertEqual(result, {"0_0": 1, "0_1": 2, "1_0": 3, "1_1_0": 4, "1_1_1": 5})
233
+ self.assertEqual(
234
+ result,
235
+ {
236
+ "0[^_^]0": 1,
237
+ "0[^_^]1": 2,
238
+ "1[^_^]0": 3,
239
+ "1[^_^]1[^_^]0": 4,
240
+ "1[^_^]1[^_^]1": 5,
241
+ },
242
+ )
234
243
 
235
244
  def test_flatten_with_max_depth(self):
236
245
  nested_dict = {"a": {"b": {"c": 1}}}
237
246
  result = flatten(nested_dict, max_depth=1)
238
- self.assertEqual(result, {"a_b": {"c": 1}})
247
+ self.assertEqual(result, {"a[^_^]b": {"c": 1}})
239
248
 
240
249
  def test_flatten_dict_only(self):
241
250
  nested_mix = {"a": [1, 2], "b": {"c": 3}}
242
251
  result = flatten(nested_mix, dict_only=True)
243
- self.assertEqual(result, {"a": [1, 2], "b_c": 3})
252
+ self.assertEqual(result, {"a": [1, 2], "b[^_^]c": 3})
244
253
 
245
254
  def test_flatten_inplace(self):
246
255
  nested_dict = {"a": {"b": 1}}
247
256
  flatten(nested_dict, inplace=True)
248
- self.assertEqual(nested_dict, {"a_b": 1})
257
+ self.assertEqual(nested_dict, {"a[^_^]b": 1})
249
258
 
250
259
  def test_flatten_empty_structure(self):
251
260
  self.assertEqual(flatten({}), {})
@@ -254,18 +263,18 @@ class TestFlatten(unittest.TestCase):
254
263
  def test_flatten_deeply_nested_structure(self):
255
264
  deeply_nested = {"a": {"b": {"c": {"d": 1}}}}
256
265
  result = flatten(deeply_nested)
257
- self.assertEqual(result, {"a_b_c_d": 1})
266
+ self.assertEqual(result, {"a[^_^]b[^_^]c[^_^]d": 1})
258
267
 
259
268
 
260
269
  class TestUnflatten(unittest.TestCase):
261
270
 
262
271
  def test_unflatten_to_nested_dict(self):
263
- flat_dict = {"a_b": 1, "a_c_d": 2}
272
+ flat_dict = {"a[^_^]b": 1, "a[^_^]c[^_^]d": 2}
264
273
  result = unflatten(flat_dict)
265
274
  self.assertEqual(result, {"a": {"b": 1, "c": {"d": 2}}})
266
275
 
267
276
  def test_unflatten_to_nested_list(self):
268
- flat_dict = {"0_0": 1, "0_1": 2, "1": [3, 4]}
277
+ flat_dict = {"0[^_^]0": 1, "0[^_^]1": 2, "1": [3, 4]}
269
278
  result = unflatten(flat_dict)
270
279
  self.assertEqual(result, [[1, 2], [3, 4]])
271
280
 
@@ -275,26 +284,26 @@ class TestUnflatten(unittest.TestCase):
275
284
  self.assertEqual(result, {"a": {"b": 1, "c": {"d": 2}}})
276
285
 
277
286
  def test_unflatten_with_custom_logic(self):
278
- flat_dict = {"a_1": "one", "a_2": "two"}
287
+ flat_dict = {"a[^_^]1": "one", "a[^_^]2": "two"}
279
288
  custom_logic = lambda key: int(key) if key.isdigit() else key
280
289
  result = unflatten(flat_dict, custom_logic=custom_logic)
281
290
  self.assertEqual(result, {"a": [None, "one", "two"]})
282
291
 
283
292
  def test_unflatten_with_max_depth(self):
284
- flat_dict = {"a_b_c": 1, "a_b_d": 2}
293
+ flat_dict = {"a[^_^]b[^_^]c": 1, "a[^_^]b[^_^]d": 2}
285
294
  result = unflatten(flat_dict, max_depth=1)
286
- self.assertEqual(result, {"a": {"b_c": 1, "b_d": 2}})
295
+ self.assertEqual(result, {"a": {"b[^_^]c": 1, "b[^_^]d": 2}})
287
296
 
288
297
  def test_unflatten_with_max_depth_int(self):
289
- flat_dict = {"0_0_0": 1, "0_1_0": 2}
298
+ flat_dict = {"0[^_^]0[^_^]0": 1, "0[^_^]1[^_^]0": 2}
290
299
  result = unflatten(flat_dict, max_depth=1)
291
- self.assertEqual(result, [[{"0_0": 1}, {"1_0": 2}]])
300
+ self.assertEqual(result, [[{"0[^_^]0": 1}, {"1[^_^]0": 2}]])
292
301
 
293
302
  def test_unflatten_empty_flat_dict(self):
294
303
  self.assertEqual(unflatten({}), [])
295
304
 
296
305
  def test_unflatten_to_mixed_structure(self):
297
- flat_dict = {"0_0": 1, "1_key": 2}
306
+ flat_dict = {"0[^_^]0": 1, "1[^_^]key": 2}
298
307
  result = unflatten(flat_dict)
299
308
  self.assertEqual(result, [[1], {"key": 2}])
300
309
 
@@ -319,7 +328,7 @@ class TestNInsert(unittest.TestCase):
319
328
  def test_insert_with_max_depth(self):
320
329
  obj = {}
321
330
  ninsert(obj, ["a", "b", "c"], "value", max_depth=1)
322
- self.assertEqual(obj, {"a": {"b_c": "value"}})
331
+ self.assertEqual(obj, {"a": {"b[^_^]c": "value"}})
323
332
 
324
333
  def test_insert_into_empty_structure(self):
325
334
  obj = {}
@@ -331,24 +340,30 @@ class TestGetFlattenedKeys(unittest.TestCase):
331
340
 
332
341
  def test_get_keys_from_nested_dict(self):
333
342
  nested_dict = {"a": {"b": 1, "c": {"d": 2}}}
334
- expected_keys = ["a_b", "a_c_d"]
343
+ expected_keys = ["a[^_^]b", "a[^_^]c[^_^]d"]
335
344
  self.assertEqual(set(get_flattened_keys(nested_dict)), set(expected_keys))
336
345
 
337
346
  def test_get_keys_from_nested_list(self):
338
347
  nested_list = [[1, 2], [3, [4, 5]]]
339
- expected_keys = ["0_0", "0_1", "1_0", "1_1_0", "1_1_1"]
348
+ expected_keys = [
349
+ "0[^_^]0",
350
+ "0[^_^]1",
351
+ "1[^_^]0",
352
+ "1[^_^]1[^_^]0",
353
+ "1[^_^]1[^_^]1",
354
+ ]
340
355
  self.assertEqual(set(get_flattened_keys(nested_list)), set(expected_keys))
341
356
 
342
357
  def test_get_keys_with_max_depth(self):
343
358
  nested_dict = {"a": {"b": {"c": 1}}}
344
- expected_keys = ["a_b"]
359
+ expected_keys = ["a[^_^]b"]
345
360
  self.assertEqual(
346
361
  set(get_flattened_keys(nested_dict, max_depth=1)), set(expected_keys)
347
362
  )
348
363
 
349
364
  def test_get_keys_dict_only(self):
350
365
  nested_mix = {"a": [1, 2], "b": {"c": 3}}
351
- expected_keys = ["a", "b_c"]
366
+ expected_keys = ["a", "b[^_^]c"]
352
367
  self.assertEqual(
353
368
  set(get_flattened_keys(nested_mix, dict_only=True)), set(expected_keys)
354
369
  )
@@ -358,7 +373,7 @@ class TestGetFlattenedKeys(unittest.TestCase):
358
373
 
359
374
  def test_keys_from_deeply_nested_structure(self):
360
375
  deeply_nested = {"a": {"b": {"c": {"d": 1}}}}
361
- expected_keys = ["a_b_c_d"]
376
+ expected_keys = ["a[^_^]b[^_^]c[^_^]d"]
362
377
  self.assertEqual(set(get_flattened_keys(deeply_nested)), set(expected_keys))
363
378
 
364
379
 
@@ -55,7 +55,7 @@ class TestFuzzyParseJson(unittest.TestCase):
55
55
  def test_extract_code_block(self):
56
56
  """Test extracting and parsing code blocks from Markdown."""
57
57
  markdown = '```python\nprint("Hello, world!")\n```'
58
- result = ParseUtil.extract_code_block(
58
+ result = ParseUtil.extract_json_block(
59
59
  markdown, language="python", parser=lambda x: x
60
60
  )
61
61
  self.assertEqual(result, 'print("Hello, world!")')
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()