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,353 @@
1
+ import unittest
2
+ from unittest.mock import patch
3
+ from lionagi.libs.ln_validate import (
4
+ check_dict_field,
5
+ check_action_field,
6
+ check_number_field,
7
+ check_bool_field,
8
+ check_str_field,
9
+ check_enum_field,
10
+ _fix_action_field,
11
+ _fix_number_field,
12
+ _fix_bool_field,
13
+ _fix_str_field,
14
+ _fix_enum_field,
15
+ )
16
+
17
+
18
+ class TestValidationFunctions(unittest.TestCase):
19
+ def test_check_dict_field_valid(self):
20
+ x = {"key": "value"}
21
+ keys = ["key"]
22
+ result = check_dict_field(x, keys)
23
+ self.assertEqual(result, x)
24
+
25
+ def test_fix_action_field_invalid_string(self):
26
+ x = '[{"invalid_key": "value"}]'
27
+ try:
28
+ _fix_action_field(x)
29
+ except ValueError as e:
30
+ self.assertEqual(str(e), "Invalid action field: {'invalid_key': 'value'}")
31
+
32
+ def test_fix_action_field_invalid_discard_disabled(self):
33
+ x = '[{"function": "func1", "arguments": {}}, {"invalid_key": "value"}]'
34
+ try:
35
+ _fix_action_field(x, discard_=False)
36
+ except ValueError as e:
37
+ self.assertEqual(str(e), "Invalid action field: {'invalid_key': 'value'}")
38
+
39
+ def test_check_dict_field_invalid_fixable(self):
40
+ x = '{"key": "value"}'
41
+ keys = ["key"]
42
+ result = check_dict_field(x, keys, fix_=True)
43
+ self.assertEqual(result, {"key": "value"})
44
+
45
+ def test_check_dict_field_invalid_not_fixable(self):
46
+ x = '{"invalid_key": "value"}'
47
+ keys = ["key"]
48
+ with self.assertRaises(ValueError) as cm:
49
+ check_dict_field(x, keys, fix_=False)
50
+ self.assertEqual(
51
+ str(cm.exception), "Default value for DICT must be a dict, got str"
52
+ )
53
+
54
+ def test_check_action_field_invalid_not_fixable(self):
55
+ x = '[{"invalid_key": "value"}]'
56
+ try:
57
+ check_action_field(x, fix_=False)
58
+ except ValueError as e:
59
+ self.assertEqual(str(e), "Invalid action field type.")
60
+
61
+ def test_check_action_field_invalid_fix_disabled(self):
62
+ x = '[{"function": "func1", "arguments": {}}, {"invalid_key": "value"}]'
63
+ try:
64
+ check_action_field(x, fix_=False)
65
+ except ValueError as e:
66
+ self.assertEqual(str(e), "Invalid action field type.")
67
+
68
+ def test_check_action_field_valid(self):
69
+ x = [
70
+ {"function": "func1", "arguments": {}},
71
+ {"function": "func2", "arguments": {}},
72
+ ]
73
+ result = check_action_field(x)
74
+ self.assertEqual(result, x)
75
+
76
+ def test_check_action_field_invalid_fixable(self):
77
+ x = '[{"function": "func1", "arguments": {}}, {"function": "func2", "arguments": {}}]'
78
+ result = check_action_field(x, fix_=True)
79
+ self.assertEqual(
80
+ result,
81
+ [
82
+ {"function": "func1", "arguments": {}},
83
+ {"function": "func2", "arguments": {}},
84
+ ],
85
+ )
86
+
87
+ def test_check_action_field_invalid_fix_disabled(self):
88
+ x = '[{"function": "func1", "arguments": {}}, {"invalid_key": "value"}]'
89
+ try:
90
+ check_action_field(x, fix_=False)
91
+ except ValueError as e:
92
+ self.assertEqual(str(e), "Invalid action field type.")
93
+
94
+ def test_check_number_field_valid(self):
95
+ x = 42
96
+ result = check_number_field(x)
97
+ self.assertEqual(result, x)
98
+
99
+ def test_check_number_field_invalid_fixable(self):
100
+ x = "42"
101
+ result = check_number_field(x, fix_=True)
102
+ self.assertEqual(result, 42)
103
+
104
+ def test_check_number_field_invalid_not_fixable(self):
105
+ x = "not_a_number"
106
+ with self.assertRaises(ValueError) as cm:
107
+ check_number_field(x, fix_=True)
108
+ self.assertEqual(
109
+ str(cm.exception), "Failed to convert not_a_number into a numeric value"
110
+ )
111
+
112
+ def test_check_number_field_invalid_fix_disabled(self):
113
+ x = "42"
114
+ with self.assertRaises(ValueError) as cm:
115
+ check_number_field(x, fix_=False)
116
+ self.assertEqual(
117
+ str(cm.exception),
118
+ "Default value for NUMERIC must be an int or float, got str",
119
+ )
120
+
121
+ def test_check_bool_field_valid(self):
122
+ x = True
123
+ result = check_bool_field(x)
124
+ self.assertEqual(result, x)
125
+
126
+ def test_check_bool_field_invalid_fixable(self):
127
+ x = "true"
128
+ result = check_bool_field(x, fix_=True)
129
+ self.assertTrue(result)
130
+
131
+ def test_check_bool_field_invalid_not_fixable(self):
132
+ x = "not_a_boolean"
133
+ with self.assertRaises(ValueError) as cm:
134
+ check_bool_field(x, fix_=True)
135
+ self.assertEqual(
136
+ str(cm.exception), "Failed to convert not_a_boolean into a boolean value"
137
+ )
138
+
139
+ def test_check_bool_field_invalid_fix_disabled(self):
140
+ x = "true"
141
+ with self.assertRaises(ValueError) as cm:
142
+ check_bool_field(x, fix_=False)
143
+ self.assertEqual(
144
+ str(cm.exception), "Default value for BOOLEAN must be a bool, got str"
145
+ )
146
+
147
+ def test_check_str_field_valid(self):
148
+ x = "hello"
149
+ result = check_str_field(x)
150
+ self.assertEqual(result, x)
151
+
152
+ def test_check_str_field_invalid_fixable(self):
153
+ x = 42
154
+ result = check_str_field(x, fix_=True)
155
+ self.assertEqual(result, "42")
156
+
157
+ def test_check_str_field_invalid_not_fixable(self):
158
+ x = object()
159
+ try:
160
+ check_str_field(x, fix_=False)
161
+ except ValueError as e:
162
+ self.assertEqual(
163
+ str(e), "Default value for STRING must be a str, got object"
164
+ )
165
+
166
+ def test_check_str_field_invalid_fix_disabled(self):
167
+ x = 42
168
+ with self.assertRaises(ValueError) as cm:
169
+ check_str_field(x, fix_=False)
170
+ self.assertEqual(
171
+ str(cm.exception), "Default value for STRING must be a str, got int"
172
+ )
173
+
174
+ def test_check_enum_field_valid(self):
175
+ x = "option1"
176
+ choices = ["option1", "option2"]
177
+ result = check_enum_field(x, choices)
178
+ self.assertEqual(result, x)
179
+
180
+ def test_check_enum_field_invalid_fixable(self):
181
+ x = "option3"
182
+ choices = ["option1", "option2"]
183
+ with patch("lionagi.libs.ln_validate._fix_enum_field", return_value="option1"):
184
+ result = check_enum_field(x, choices, fix_=True)
185
+ self.assertEqual(result, "option1")
186
+
187
+ def test_check_enum_field_invalid_not_fixable(self):
188
+ x = "option3"
189
+ choices = ["option1", "option2"]
190
+ with self.assertRaises(ValueError) as cm:
191
+ check_enum_field(x, choices, fix_=False)
192
+ self.assertEqual(
193
+ str(cm.exception),
194
+ "Default value for ENUM must be one of the ['option1', 'option2'], got option3",
195
+ )
196
+
197
+ def test_check_enum_field_invalid_choices(self):
198
+ x = "option1"
199
+ choices = ["option1", 42]
200
+ with self.assertRaises(ValueError) as cm:
201
+ check_enum_field(x, choices)
202
+ self.assertEqual(
203
+ str(cm.exception),
204
+ "Field type ENUM requires all choices to be of the same type, got ['option1', 42]",
205
+ )
206
+
207
+ def test_check_enum_field_invalid_type(self):
208
+ x = 42
209
+ choices = ["option1", "option2"]
210
+ with self.assertRaises(ValueError) as cm:
211
+ check_enum_field(x, choices)
212
+ self.assertEqual(
213
+ str(cm.exception),
214
+ "Default value for ENUM must be an instance of the str, got int",
215
+ )
216
+
217
+ def test_fix_action_field_string(self):
218
+ x = '[{"function": "func1", "arguments": {}}, {"function": "func2", "arguments": {}}]'
219
+ result = _fix_action_field(x)
220
+ self.assertEqual(
221
+ result,
222
+ [
223
+ {"function": "func1", "arguments": {}},
224
+ {"function": "func2", "arguments": {}},
225
+ ],
226
+ )
227
+
228
+ def test_fix_action_field_invalid_string(self):
229
+ x = '[{"invalid_key": "value"}]'
230
+ try:
231
+ _fix_action_field(x)
232
+ except ValueError as e:
233
+ self.assertEqual(str(e), "Invalid action field: {'invalid_key': 'value'}")
234
+
235
+ def test_fix_action_field_invalid_discard_disabled(self):
236
+ x = '[{"function": "func1", "arguments": {}}, {"invalid_key": "value"}]'
237
+ try:
238
+ _fix_action_field(x, discard_=False)
239
+ except ValueError as e:
240
+ self.assertEqual(str(e), "Invalid action field: {'invalid_key': 'value'}")
241
+
242
+ def test_fix_number_field_valid(self):
243
+ x = "42"
244
+ result = _fix_number_field(x)
245
+ self.assertEqual(result, 42)
246
+
247
+ def test_fix_number_field_invalid(self):
248
+ x = "not_a_number"
249
+ with self.assertRaises(ValueError) as cm:
250
+ _fix_number_field(x)
251
+ self.assertEqual(
252
+ str(cm.exception), "Failed to convert not_a_number into a numeric value"
253
+ )
254
+
255
+ def test_fix_bool_field_true(self):
256
+ x = "true"
257
+ result = _fix_bool_field(x)
258
+ self.assertTrue(result)
259
+
260
+ def test_fix_bool_field_false(self):
261
+ x = "false"
262
+ result = _fix_bool_field(x)
263
+ self.assertFalse(result)
264
+
265
+ def test_fix_bool_field_invalid(self):
266
+ x = "not_a_boolean"
267
+ with self.assertRaises(ValueError) as cm:
268
+ _fix_bool_field(x)
269
+ self.assertEqual(
270
+ str(cm.exception), "Failed to convert not_a_boolean into a boolean value"
271
+ )
272
+
273
+ def test_fix_str_field_valid(self):
274
+ x = 42
275
+ result = _fix_str_field(x)
276
+ self.assertEqual(result, "42")
277
+
278
+ def test_fix_str_field_invalid(self):
279
+ x = object()
280
+ try:
281
+ _fix_str_field(x)
282
+ except ValueError as e:
283
+ self.assertEqual(
284
+ str(e),
285
+ "Failed to convert <object object at 0x{:x}> into a string value".format(
286
+ id(x)
287
+ ),
288
+ )
289
+
290
+ def test_fix_enum_field_valid(self):
291
+ x = "option3"
292
+ choices = ["option1", "option2"]
293
+ with patch(
294
+ "lionagi.libs.StringMatch.choose_most_similar", return_value="option1"
295
+ ):
296
+ result = _fix_enum_field(x, choices)
297
+ self.assertEqual(result, "option1")
298
+
299
+ def test_fix_enum_field_invalid(self):
300
+ x = "option3"
301
+ choices = ["option1", "option2"]
302
+ with patch(
303
+ "lionagi.libs.StringMatch.choose_most_similar",
304
+ side_effect=ValueError("No match found"),
305
+ ):
306
+ with self.assertRaises(ValueError) as cm:
307
+ _fix_enum_field(x, choices)
308
+ self.assertEqual(
309
+ str(cm.exception), "Failed to convert option3 into one of the choices"
310
+ )
311
+
312
+ def test_fix_bool_field_false(self):
313
+ x = "false"
314
+ result = _fix_bool_field(x)
315
+ self.assertFalse(result)
316
+
317
+ def test_fix_str_field_invalid(self):
318
+ x = object()
319
+ try:
320
+ _fix_str_field(x)
321
+ except ValueError as e:
322
+ self.assertEqual(
323
+ str(e),
324
+ "Failed to convert <object object at 0x{:x}> into a string value".format(
325
+ id(x)
326
+ ),
327
+ )
328
+
329
+ def test_fix_enum_field_valid(self):
330
+ x = "option3"
331
+ choices = ["option1", "option2"]
332
+ with patch(
333
+ "lionagi.libs.StringMatch.choose_most_similar", return_value="option1"
334
+ ):
335
+ result = _fix_enum_field(x, choices)
336
+ self.assertEqual(result, "option1")
337
+
338
+ def test_fix_enum_field_invalid(self):
339
+ x = "option3"
340
+ choices = ["option1", "option2"]
341
+ with patch(
342
+ "lionagi.libs.StringMatch.choose_most_similar",
343
+ side_effect=ValueError("No match found"),
344
+ ):
345
+ with self.assertRaises(ValueError) as cm:
346
+ _fix_enum_field(x, choices)
347
+ self.assertEqual(
348
+ str(cm.exception), "Failed to convert option3 into one of the choices"
349
+ )
350
+
351
+
352
+ if __name__ == "__main__":
353
+ unittest.main()
@@ -296,8 +296,8 @@ class TestRCall(unittest.IsolatedAsyncioTestCase):
296
296
  await asyncio.sleep(2)
297
297
  return x
298
298
 
299
- with self.assertRaises(asyncio.TimeoutError):
300
- await rcall(async_func, 5, timeout=1)
299
+ with self.assertRaises(RuntimeError):
300
+ await rcall(async_func, 5, timeout=0.1)
301
301
 
302
302
  async def test_retry_mechanism(self):
303
303
  attempt_count = 0
@@ -307,22 +307,22 @@ class TestRCall(unittest.IsolatedAsyncioTestCase):
307
307
  attempt_count += 1
308
308
  raise ValueError("Test Error")
309
309
 
310
- with self.assertRaises(ValueError):
311
- await rcall(sync_func, 5, retries=3)
310
+ with self.assertRaises(RuntimeError):
311
+ await rcall(sync_func, 5, retries=3, delay=0.1, backoff_factor=1.1)
312
312
  self.assertEqual(attempt_count, 3) # Initial call + 3 retries
313
313
 
314
314
  async def test_default_value_on_exception(self):
315
315
  def sync_func(x):
316
316
  raise ValueError("Test Error")
317
317
 
318
- result = await rcall(sync_func, 5, default=10)
318
+ result = await rcall(sync_func, 5, default=10, delay=0)
319
319
  self.assertEqual(result, 10)
320
320
 
321
321
  async def test_exception_propagation(self):
322
322
  def sync_func(x):
323
323
  raise ValueError("Test Error")
324
324
 
325
- with self.assertRaises(ValueError):
325
+ with self.assertRaises(RuntimeError):
326
326
  await rcall(sync_func, 5)
327
327
 
328
328
 
@@ -581,22 +581,22 @@ class TestCallDecorator(unittest.IsolatedAsyncioTestCase):
581
581
  class TestThrottleClass(unittest.TestCase):
582
582
 
583
583
  def test_throttling_behavior_sync(self):
584
- throttle_decorator = Throttle(2) # 2 seconds throttle period
584
+ throttle_decorator = Throttle(0.1) # 2 seconds throttle period
585
585
 
586
586
  @throttle_decorator
587
587
  def test_func():
588
588
  return time.time()
589
589
 
590
590
  first_call_time = test_func()
591
- time.sleep(1) # Sleep less than the throttle period
591
+ time.sleep(0.1) # Sleep less than the throttle period
592
592
  second_call_time = test_func()
593
593
 
594
594
  self.assertGreaterEqual(
595
- second_call_time - first_call_time, 2
595
+ second_call_time - first_call_time, 0.1
596
596
  ) # Second call should be throttled
597
597
 
598
598
  def test_throttling_behavior_async(self):
599
- throttle_decorator = Throttle(2) # 2 seconds throttle period
599
+ throttle_decorator = Throttle(1) # 2 seconds throttle period
600
600
 
601
601
  @throttle_decorator
602
602
  async def test_func():
@@ -604,28 +604,28 @@ class TestThrottleClass(unittest.TestCase):
604
604
 
605
605
  async def async_test():
606
606
  first_call_time = await test_func()
607
- await asyncio.sleep(1) # Sleep less than the throttle period
607
+ await asyncio.sleep(0.1) # Sleep less than the throttle period
608
608
  second_call_time = await test_func()
609
609
 
610
610
  self.assertGreaterEqual(
611
- second_call_time - first_call_time, 2
611
+ second_call_time - first_call_time, 1
612
612
  ) # Second call should be throttled
613
613
 
614
614
  asyncio.run(async_test())
615
615
 
616
616
  def test_successive_calls_with_sufficient_delay(self):
617
- throttle_decorator = Throttle(1) # 1 second throttle period
617
+ throttle_decorator = Throttle(0.1) # 1 second throttle period
618
618
 
619
619
  @throttle_decorator
620
620
  def test_func():
621
621
  return time.time()
622
622
 
623
623
  first_call_time = test_func()
624
- time.sleep(1.1) # Sleep more than the throttle period
624
+ time.sleep(0.12) # Sleep more than the throttle period
625
625
  second_call_time = test_func()
626
626
 
627
627
  self.assertLess(
628
- second_call_time - first_call_time, 1.5
628
+ second_call_time - first_call_time, 0.2
629
629
  ) # Second call should not be throttled
630
630
 
631
631
 
@@ -633,7 +633,7 @@ class TestAsyncRetryDecorator(unittest.IsolatedAsyncioTestCase):
633
633
  async def test_successful_retry(self):
634
634
  attempt = 0
635
635
 
636
- @CallDecorator.retry(retries=3, delay=1, backoff_factor=2)
636
+ @CallDecorator.retry(retries=3, delay=0.1, backoff_factor=2)
637
637
  async def test_func():
638
638
  nonlocal attempt
639
639
  attempt += 1
@@ -648,20 +648,22 @@ class TestAsyncRetryDecorator(unittest.IsolatedAsyncioTestCase):
648
648
  async def test_retry_limit(self):
649
649
  attempt = 0
650
650
 
651
- @CallDecorator.retry(retries=2, delay=1, backoff_factor=2)
651
+ @CallDecorator.retry(retries=2, delay=0.1, backoff_factor=2)
652
652
  async def test_func():
653
653
  nonlocal attempt
654
654
  attempt += 1
655
655
  raise ValueError("Test failure")
656
656
 
657
- with self.assertRaises(ValueError):
657
+ try:
658
658
  await test_func()
659
+ except:
660
+ pass
659
661
  self.assertEqual(attempt, 2)
660
662
 
661
663
  async def test_immediate_success(self):
662
664
  attempt = 0
663
665
 
664
- @CallDecorator.retry(retries=3, delay=1, backoff_factor=2)
666
+ @CallDecorator.retry(retries=3, delay=0.1, backoff_factor=2)
665
667
  async def test_func():
666
668
  nonlocal attempt
667
669
  attempt += 1
@@ -675,7 +677,7 @@ class TestAsyncRetryDecorator(unittest.IsolatedAsyncioTestCase):
675
677
  attempt = 0
676
678
  start_time = time.time()
677
679
 
678
- @CallDecorator.retry(retries=3, delay=1, backoff_factor=2)
680
+ @CallDecorator.retry(retries=3, delay=0.1, backoff_factor=2)
679
681
  async def test_func():
680
682
  nonlocal attempt
681
683
  attempt += 1
@@ -690,7 +692,7 @@ class TestAsyncRetryDecorator(unittest.IsolatedAsyncioTestCase):
690
692
  self.assertEqual(result, "Success")
691
693
  # Note: The actual delay might be slightly longer than expected due to asyncio's event loop scheduling.
692
694
  self.assertTrue(
693
- elapsed_time >= 3, f"Elapsed time was {elapsed_time}, expected >= 3"
695
+ elapsed_time >= 0.3, f"Elapsed time was {elapsed_time}, expected >= 3"
694
696
  )
695
697
  self.assertEqual(attempt, 3)
696
698
 
@@ -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!")')