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
@@ -0,0 +1,71 @@
1
+ retry_kwargs = {
2
+ "retries": 0, # kwargs for rcall, number of retries if failed
3
+ "delay": 0, # number of seconds to delay before retrying
4
+ "backoff_factor": 1, # exponential backoff factor, default 1 (no backoff)
5
+ "default": None, # default value to return if all retries failed
6
+ "timeout": None, # timeout for the rcall, default None (no timeout)
7
+ "timing": False, # if timing will return a tuple (output, duration)
8
+ }
9
+
10
+ oai_fields = [
11
+ "id",
12
+ "object",
13
+ "created",
14
+ "model",
15
+ "choices",
16
+ "usage",
17
+ "system_fingerprint",
18
+ ]
19
+
20
+ choices_fields = ["index", "message", "logprobs", "finish_reason"]
21
+
22
+ usage_fields = ["prompt_tokens", "completion_tokens", "total_tokens"]
23
+
24
+ from typing import Callable
25
+ from lionagi.core.action.tool import Tool
26
+ from lionagi.core.action.tool_manager import func_to_tool
27
+
28
+
29
+ def process_tools(tool_obj, branch):
30
+ if isinstance(tool_obj, Callable):
31
+ _process_tool(tool_obj, branch)
32
+ else:
33
+ if isinstance(tool_obj, bool):
34
+ return
35
+ for i in tool_obj:
36
+ _process_tool(i, branch)
37
+
38
+
39
+ def _process_tool(tool_obj, branch):
40
+ if (
41
+ isinstance(tool_obj, Tool)
42
+ and tool_obj.schema_["function"]["name"] not in branch.tool_manager.registry
43
+ ):
44
+ branch.register_tools(tool_obj)
45
+ if isinstance(tool_obj, Callable):
46
+ tool = func_to_tool(tool_obj)[0]
47
+ if tool.schema_["function"]["name"] not in branch.tool_manager.registry:
48
+ branch.register_tools(tool)
49
+
50
+
51
+ async def _direct(
52
+ directive,
53
+ form=None,
54
+ template=None,
55
+ reason=False,
56
+ confidence_score=None,
57
+ instruction=None,
58
+ context=None,
59
+ template_kwargs={},
60
+ **kwargs,
61
+ ):
62
+ if not form:
63
+ form = template(
64
+ instruction=instruction,
65
+ context=context,
66
+ confidence_score=confidence_score,
67
+ reason=reason,
68
+ **template_kwargs,
69
+ )
70
+
71
+ return await directive.direct(form=form, return_form=True, **kwargs)
@@ -0,0 +1,364 @@
1
+ """
2
+ Copyright 2024 HaiyangLi
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ from typing import Any, Dict, List, Union
18
+ from lionagi.libs import SysUtil
19
+ from lionagi.libs.ln_func_call import lcall
20
+ from lionagi.core.collections.abc import FieldError
21
+ from ..rule.base import Rule
22
+ from ..rule._default import DEFAULT_RULES
23
+ from ..rule.rulebook import RuleBook
24
+ from ..report.form import Form
25
+ from ..report.report import Report
26
+
27
+ _DEFAULT_RULEORDER = [
28
+ "choice",
29
+ "actionrequest",
30
+ "number",
31
+ "mapping",
32
+ "str",
33
+ "bool",
34
+ ]
35
+
36
+ _DEFAULT_RULES = {
37
+ "choice": DEFAULT_RULES.CHOICE.value,
38
+ "actionrequest": DEFAULT_RULES.ACTION.value,
39
+ "bool": DEFAULT_RULES.BOOL.value,
40
+ "number": DEFAULT_RULES.NUMBER.value,
41
+ "mapping": DEFAULT_RULES.MAPPING.value,
42
+ "str": DEFAULT_RULES.STR.value,
43
+ }
44
+
45
+
46
+ class Validator:
47
+ """
48
+ Validator class to manage the validation of forms using a RuleBook.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ *,
54
+ rulebook: RuleBook = None,
55
+ rules: Dict[str, Rule] = None,
56
+ order: List[str] = None,
57
+ init_config: Dict[str, Dict] = None,
58
+ active_rules: Dict[str, Rule] = None,
59
+ ):
60
+ """
61
+ Initialize the Validator.
62
+
63
+ Args:
64
+ rulebook (RuleBook, optional): The RuleBook containing validation rules.
65
+ rules (Dict[str, Rule], optional): Dictionary of validation rules.
66
+ order (List[str], optional): List defining the order of rule application.
67
+ init_config (Dict[str, Dict], optional): Configuration for initializing rules.
68
+ active_rules (Dict[str, Rule], optional): Dictionary of currently active rules.
69
+ """
70
+
71
+ self.ln_id: str = SysUtil.create_id()
72
+ self.timestamp: str = SysUtil.get_timestamp(sep=None)[:-6]
73
+ self.rulebook = rulebook or RuleBook(
74
+ rules or _DEFAULT_RULES, order or _DEFAULT_RULEORDER, init_config
75
+ )
76
+ self.active_rules: Dict[str, Rule] = active_rules or self._initiate_rules()
77
+ self.validation_log = []
78
+
79
+ def _initiate_rules(self) -> Dict[str, Rule]:
80
+ """
81
+ Initialize rules from the rulebook.
82
+
83
+ Returns:
84
+ dict: A dictionary of active rules.
85
+ """
86
+
87
+ def _init_rule(rule_name: str) -> Rule:
88
+
89
+ if not issubclass(self.rulebook[rule_name], Rule):
90
+ raise FieldError(
91
+ f"Invalid rule class for {rule_name}, must be a subclass of Rule"
92
+ )
93
+
94
+ _config = self.rulebook.rule_config[rule_name] or {}
95
+ if not isinstance(_config, dict):
96
+ raise FieldError(
97
+ f"Invalid config for {rule_name}, must be a dictionary"
98
+ )
99
+
100
+ _rule = self.rulebook.rules[rule_name](**_config.get("config", {}))
101
+ _rule.fields = _config.get("fields", [])
102
+ _rule._is_init = True
103
+ return _rule
104
+
105
+ _rules = lcall(self.rulebook.ruleorder, _init_rule)
106
+
107
+ return {
108
+ rule_name: _rules[idx]
109
+ for idx, rule_name in enumerate(self.rulebook.ruleorder)
110
+ if getattr(_rules[idx], "_is_init", None)
111
+ }
112
+
113
+ async def validate_field(
114
+ self,
115
+ field: str,
116
+ value: Any,
117
+ form: Form,
118
+ *args,
119
+ annotation=None,
120
+ strict=True,
121
+ use_annotation=True,
122
+ **kwargs,
123
+ ) -> Any:
124
+ """
125
+ Validate a specific field in a form.
126
+
127
+ Args:
128
+ field (str): The field to validate.
129
+ value (Any): The value of the field.
130
+ form (Form): The form containing the field.
131
+ *args: Additional arguments.
132
+ annotation (list[str], optional): Annotations for the field.
133
+ strict (bool): Whether to enforce strict validation.
134
+ use_annotation (bool): Whether to use annotations for validation.
135
+ **kwargs: Additional keyword arguments.
136
+
137
+ Returns:
138
+ Any: The validated value.
139
+
140
+ Raises:
141
+ LionFieldError: If validation fails.
142
+ """
143
+ for rule in self.active_rules.values():
144
+ try:
145
+ if await rule.applies(
146
+ field,
147
+ value,
148
+ form,
149
+ *args,
150
+ annotation=annotation,
151
+ use_annotation=use_annotation,
152
+ **kwargs,
153
+ ):
154
+ return await rule.invoke(field, value, form)
155
+ except Exception as e:
156
+ self.log_validation_error(field, value, str(e))
157
+ raise FieldError(f"Failed to validate {field}") from e
158
+
159
+ if strict:
160
+ error_message = (
161
+ f"Failed to validate {field} because no rule applied. To return the "
162
+ f"original value directly when no rule applies, set strict=False."
163
+ )
164
+ self.log_validation_error(field, value, error_message)
165
+ raise FieldError(error_message)
166
+
167
+ async def validate_report(
168
+ self, report: Report, forms: List[Form], strict: bool = True
169
+ ) -> Report:
170
+ """
171
+ Validate a report based on active rules.
172
+
173
+ Args:
174
+ report (Report): The report to validate.
175
+ forms (list[Form]): A list of forms to include in the report.
176
+ strict (bool): Whether to enforce strict validation.
177
+
178
+ Returns:
179
+ Report: The validated report.
180
+ """
181
+ report.fill(forms, strict=strict)
182
+ return report
183
+
184
+ async def validate_response(
185
+ self,
186
+ form: Form,
187
+ response: Union[dict, str],
188
+ strict: bool = True,
189
+ use_annotation: bool = True,
190
+ ) -> Form:
191
+ """
192
+ Validate a response for a given form.
193
+
194
+ Args:
195
+ form (Form): The form to validate against.
196
+ response (dict | str): The response to validate.
197
+ strict (bool): Whether to enforce strict validation.
198
+ use_annotation (bool): Whether to use annotations for validation.
199
+
200
+ Returns:
201
+ Form: The validated form.
202
+
203
+ Raises:
204
+ ValueError: If the response format is invalid.
205
+ """
206
+ if isinstance(response, str):
207
+ if len(form.requested_fields) == 1:
208
+ response = {form.requested_fields[0]: response}
209
+ else:
210
+ raise ValueError(
211
+ "Response is a string, but form has multiple fields to be filled"
212
+ )
213
+
214
+ dict_ = {}
215
+ for k, v in response.items():
216
+ if k in form.requested_fields:
217
+ kwargs = form.validation_kwargs.get(k, {})
218
+ _annotation = form._field_annotations[k]
219
+ if (keys := form._get_field_attr(k, "choices", None)) is not None:
220
+ v = await self.validate_field(
221
+ field=k,
222
+ value=v,
223
+ form=form,
224
+ annotation=_annotation,
225
+ strict=strict,
226
+ keys=keys,
227
+ use_annotation=use_annotation,
228
+ **kwargs,
229
+ )
230
+
231
+ elif (_keys := form._get_field_attr(k, "keys", None)) is not None:
232
+
233
+ v = await self.validate_field(
234
+ field=k,
235
+ value=v,
236
+ form=form,
237
+ annotation=_annotation,
238
+ strict=strict,
239
+ keys=_keys,
240
+ use_annotation=use_annotation,
241
+ **kwargs,
242
+ )
243
+
244
+ else:
245
+ v = await self.validate_field(
246
+ field=k,
247
+ value=v,
248
+ form=form,
249
+ annotation=_annotation,
250
+ strict=strict,
251
+ use_annotation=use_annotation,
252
+ **kwargs,
253
+ )
254
+ dict_[k] = v
255
+ form.fill(**dict_)
256
+ return form
257
+
258
+ def add_rule(self, rule_name: str, rule: Rule, config: dict = None):
259
+ """
260
+ Add a new rule to the validator.
261
+
262
+ Args:
263
+ rule_name (str): The name of the rule.
264
+ rule (Rule): The rule object.
265
+ config (dict, optional): Configuration for the rule.
266
+ """
267
+ if rule_name in self.active_rules:
268
+ raise ValueError(f"Rule '{rule_name}' already exists.")
269
+ self.active_rules[rule_name] = rule
270
+ self.rulebook.rules[rule_name] = rule
271
+ self.rulebook.ruleorder.append(rule_name)
272
+ self.rulebook.rule_config[rule_name] = config or {}
273
+
274
+ def remove_rule(self, rule_name: str):
275
+ """
276
+ Remove an existing rule from the validator.
277
+
278
+ Args:
279
+ rule_name (str): The name of the rule to remove.
280
+ """
281
+ if rule_name not in self.active_rules:
282
+ raise ValueError(f"Rule '{rule_name}' does not exist.")
283
+ del self.active_rules[rule_name]
284
+ del self.rulebook.rules[rule_name]
285
+ self.rulebook.ruleorder.remove(rule_name)
286
+ del self.rulebook.rule_config[rule_name]
287
+
288
+ def list_active_rules(self) -> list:
289
+ """
290
+ List all active rules.
291
+
292
+ Returns:
293
+ list: A list of active rule names.
294
+ """
295
+ return list(self.active_rules.keys())
296
+
297
+ def enable_rule(self, rule_name: str, enable: bool = True):
298
+ """
299
+ Enable a specific rule.
300
+
301
+ Args:
302
+ rule_name (str): The name of the rule.
303
+ enable (bool): Whether to enable or disable the rule.
304
+ """
305
+ if rule_name not in self.active_rules:
306
+ raise ValueError(f"Rule '{rule_name}' does not exist.")
307
+ self.active_rules[rule_name].enabled = enable
308
+
309
+ def disable_rule(self, rule_name: str):
310
+ """
311
+ Disable a specific rule.
312
+
313
+ Args:
314
+ rule_name (str): The name of the rule to disable.
315
+ """
316
+ self.enable_rule(rule_name, enable=False)
317
+
318
+ def log_validation_attempt(self, form: Form, result: dict):
319
+ """
320
+ Log a validation attempt.
321
+
322
+ Args:
323
+ form (Form): The form being validated.
324
+ result (dict): The result of the validation.
325
+ """
326
+ log_entry = {
327
+ "form_id": form.ln_id,
328
+ "timestamp": SysUtil.get_timestamp(),
329
+ "result": result,
330
+ }
331
+ self.validation_log.append(log_entry)
332
+
333
+ def log_validation_error(self, field: str, value: Any, error: str):
334
+ """
335
+ Log a validation error.
336
+
337
+ Args:
338
+ field (str): The field that failed validation.
339
+ value (Any): The value of the field.
340
+ error (str): The error message.
341
+ """
342
+ log_entry = {
343
+ "field": field,
344
+ "value": value,
345
+ "error": error,
346
+ "timestamp": SysUtil.get_timestamp(),
347
+ }
348
+ self.validation_log.append(log_entry)
349
+
350
+ def get_validation_summary(self) -> Dict[str, Any]:
351
+ """
352
+ Get a summary of validation results.
353
+
354
+ Returns:
355
+ dict: A summary of validation attempts, errors, and successful attempts.
356
+ """
357
+ summary = {
358
+ "total_attempts": len(self.validation_log),
359
+ "errors": [log for log in self.validation_log if "error" in log],
360
+ "successful_attempts": [
361
+ log for log in self.validation_log if "result" in log
362
+ ],
363
+ }
364
+ return summary
@@ -0,0 +1,74 @@
1
+ """
2
+ Copyright 2024 HaiyangLi
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ from enum import Enum
18
+ import asyncio
19
+ from typing import Any
20
+
21
+ from lionagi.libs import SysUtil
22
+ from lionagi.core.collections.abc import Component
23
+
24
+
25
+ class WorkStatus(str, Enum):
26
+ """Enum to represent different statuses of work."""
27
+
28
+ PENDING = "PENDING"
29
+ IN_PROGRESS = "IN_PROGRESS"
30
+ COMPLETED = "COMPLETED"
31
+ FAILED = "FAILED"
32
+
33
+
34
+ class Work(Component):
35
+ """
36
+ A class representing a unit of work.
37
+
38
+ Attributes:
39
+ status (WorkStatus): The current status of the work.
40
+ result (Any): The result of the work, if completed.
41
+ error (Any): Any error encountered during the work.
42
+ async_task (asyncio.Task | None): The asynchronous task associated with the work.
43
+ completion_timestamp (str | None): The timestamp when the work was completed.
44
+ duration (float | None): The duration of the work.
45
+ """
46
+
47
+ status: WorkStatus = WorkStatus.PENDING
48
+ result: Any = None
49
+ error: Any = None
50
+ async_task: asyncio.Task | None = None
51
+ completion_timestamp: str | None = None
52
+ duration: float | None = None
53
+
54
+ async def perform(self):
55
+ """Perform the work and update the status, result, and duration."""
56
+ try:
57
+ result, duration = await self.async_task
58
+ self.result = result
59
+ self.status = WorkStatus.COMPLETED
60
+ self.duration = duration
61
+ del self.async_task
62
+ except Exception as e:
63
+ self.error = e
64
+ self.status = WorkStatus.FAILED
65
+ finally:
66
+ self.completion_timestamp = SysUtil.get_timestamp(sep=None)[:-6]
67
+
68
+ def __str__(self):
69
+ return (
70
+ f"Work(id={self.ln_id[:8]}.., status={self.status.value}, "
71
+ f"created_at={self.timestamp[:-7]}, "
72
+ f"completed_at={self.completion_timestamp[:-7]}, "
73
+ f"duration={float(self.duration) if self.duration else 0:.04f} sec(s))"
74
+ )
@@ -0,0 +1,92 @@
1
+ """
2
+ Copyright 2024 HaiyangLi
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ from lionagi.libs.ln_func_call import rcall
18
+ from lionagi.core.work.worklog import WorkLog
19
+
20
+
21
+ class WorkFunction:
22
+ """
23
+ A class representing a work function.
24
+
25
+ Attributes:
26
+ assignment (str): The assignment description of the work function.
27
+ function (Callable): The function to be performed.
28
+ retry_kwargs (dict): The retry arguments for the function.
29
+ worklog (WorkLog): The work log for the function.
30
+ guidance (str): The guidance or documentation for the function.
31
+ """
32
+
33
+ def __init__(
34
+ self, assignment, function, retry_kwargs=None, guidance=None, capacity=10
35
+ ):
36
+ """
37
+ Initializes a WorkFunction instance.
38
+
39
+ Args:
40
+ assignment (str): The assignment description of the work function.
41
+ function (Callable): The function to be performed.
42
+ retry_kwargs (dict, optional): The retry arguments for the function.
43
+ Defaults to None.
44
+ guidance (str, optional): The guidance or documentation for the function.
45
+ Defaults to None.
46
+ capacity (int, optional): The capacity of the work log. Defaults to 10.
47
+ """
48
+ self.assignment = assignment
49
+ self.function = function
50
+ self.retry_kwargs = retry_kwargs or {}
51
+ self.worklog = WorkLog(capacity)
52
+ self.guidance = guidance or self.function.__doc__
53
+
54
+ @property
55
+ def name(self):
56
+ """
57
+ Gets the name of the function.
58
+
59
+ Returns:
60
+ str: The name of the function.
61
+ """
62
+ return self.function.__name__
63
+
64
+ def is_progressable(self):
65
+ """
66
+ Checks if the work function is progressable.
67
+
68
+ Returns:
69
+ bool: True if the work function is progressable, otherwise False.
70
+ """
71
+ return self.worklog.pending_work and not self.worklog.stopped
72
+
73
+ async def perform(self, *args, **kwargs):
74
+ """
75
+ Performs the work function with retry logic.
76
+
77
+ Args:
78
+ *args: Positional arguments for the function.
79
+ **kwargs: Keyword arguments for the function.
80
+
81
+ Returns:
82
+ Any: The result of the function call.
83
+ """
84
+ kwargs = {**self.retry_kwargs, **kwargs}
85
+ return await rcall(self.function, *args, timing=True, **kwargs)
86
+
87
+ async def forward(self):
88
+ """
89
+ Forwards the work log and processes the work queue.
90
+ """
91
+ await self.worklog.forward()
92
+ await self.worklog.queue.process()
@@ -0,0 +1,81 @@
1
+ """
2
+ Copyright 2024 HaiyangLi
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ import asyncio
18
+
19
+
20
+ class WorkQueue:
21
+ """
22
+ A class representing a queue for managing work.
23
+
24
+ Attributes:
25
+ capacity (int): The maximum number of tasks the queue can handle.
26
+ queue (asyncio.Queue): The queue holding the tasks.
27
+ _stop_event (asyncio.Event): Event to signal stopping of the queue.
28
+ semaphore (asyncio.Semaphore): Semaphore to control access based on capacity.
29
+ """
30
+
31
+ def __init__(self, capacity=5):
32
+
33
+ self.queue = asyncio.Queue()
34
+ self._stop_event = asyncio.Event()
35
+ self.capacity = capacity
36
+ self.semaphore = asyncio.Semaphore(capacity)
37
+
38
+ async def enqueue(self, work) -> None:
39
+ """Enqueue a work item."""
40
+ await self.queue.put(work)
41
+
42
+ async def dequeue(self):
43
+ """Dequeue a work item."""
44
+ return await self.queue.get()
45
+
46
+ async def join(self) -> None:
47
+ """Block until all items in the queue have been processed."""
48
+ await self.queue.join()
49
+
50
+ async def stop(self) -> None:
51
+ """Signal the queue to stop processing."""
52
+ self._stop_event.set()
53
+
54
+ @property
55
+ def available_capacity(self):
56
+ """Return the available capacity of the queue."""
57
+ available = self.capacity - self.queue.qsize()
58
+ return available if available > 0 else None
59
+
60
+ @property
61
+ def stopped(self) -> bool:
62
+ """Return whether the queue has been stopped."""
63
+ return self._stop_event.is_set()
64
+
65
+ async def process(self) -> None:
66
+ """Process the work items in the queue."""
67
+ tasks = set()
68
+ while self.queue.qsize() > 0 and not self.stopped:
69
+ if not self.available_capacity and tasks:
70
+ _, done = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
71
+ tasks.difference_update(done)
72
+
73
+ async with self.semaphore:
74
+ next = await self.dequeue()
75
+ if next is None:
76
+ break
77
+ task = asyncio.create_task(next.perform())
78
+ tasks.add(task)
79
+
80
+ if tasks:
81
+ await asyncio.wait(tasks)