lionagi 0.1.2__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. lionagi/__init__.py +60 -5
  2. lionagi/core/__init__.py +0 -25
  3. lionagi/core/_setting/_setting.py +59 -0
  4. lionagi/core/action/__init__.py +14 -0
  5. lionagi/core/action/function_calling.py +136 -0
  6. lionagi/core/action/manual.py +1 -0
  7. lionagi/core/action/node.py +109 -0
  8. lionagi/core/action/tool.py +114 -0
  9. lionagi/core/action/tool_manager.py +356 -0
  10. lionagi/core/agent/base_agent.py +27 -13
  11. lionagi/core/agent/eval/evaluator.py +1 -0
  12. lionagi/core/agent/eval/vote.py +40 -0
  13. lionagi/core/agent/learn/learner.py +59 -0
  14. lionagi/core/agent/plan/unit_template.py +1 -0
  15. lionagi/core/collections/__init__.py +17 -0
  16. lionagi/core/{generic/data_logger.py → collections/_logger.py} +69 -55
  17. lionagi/core/collections/abc/__init__.py +53 -0
  18. lionagi/core/collections/abc/component.py +615 -0
  19. lionagi/core/collections/abc/concepts.py +297 -0
  20. lionagi/core/collections/abc/exceptions.py +150 -0
  21. lionagi/core/collections/abc/util.py +45 -0
  22. lionagi/core/collections/exchange.py +161 -0
  23. lionagi/core/collections/flow.py +426 -0
  24. lionagi/core/collections/model.py +419 -0
  25. lionagi/core/collections/pile.py +913 -0
  26. lionagi/core/collections/progression.py +236 -0
  27. lionagi/core/collections/util.py +64 -0
  28. lionagi/core/director/direct.py +314 -0
  29. lionagi/core/director/director.py +2 -0
  30. lionagi/core/{execute/branch_executor.py → engine/branch_engine.py} +134 -97
  31. lionagi/core/{execute/instruction_map_executor.py → engine/instruction_map_engine.py} +80 -55
  32. lionagi/{experimental/directive/evaluator → core/engine}/script_engine.py +17 -1
  33. lionagi/core/executor/base_executor.py +90 -0
  34. lionagi/core/{execute/structure_executor.py → executor/graph_executor.py} +62 -66
  35. lionagi/core/{execute → executor}/neo4j_executor.py +70 -67
  36. lionagi/core/generic/__init__.py +3 -33
  37. lionagi/core/generic/edge.py +29 -79
  38. lionagi/core/generic/edge_condition.py +16 -0
  39. lionagi/core/generic/graph.py +236 -0
  40. lionagi/core/generic/hyperedge.py +1 -0
  41. lionagi/core/generic/node.py +156 -221
  42. lionagi/core/generic/tree.py +48 -0
  43. lionagi/core/generic/tree_node.py +79 -0
  44. lionagi/core/mail/__init__.py +12 -0
  45. lionagi/core/mail/mail.py +25 -0
  46. lionagi/core/mail/mail_manager.py +139 -58
  47. lionagi/core/mail/package.py +45 -0
  48. lionagi/core/mail/start_mail.py +36 -0
  49. lionagi/core/message/__init__.py +19 -0
  50. lionagi/core/message/action_request.py +133 -0
  51. lionagi/core/message/action_response.py +135 -0
  52. lionagi/core/message/assistant_response.py +95 -0
  53. lionagi/core/message/instruction.py +234 -0
  54. lionagi/core/message/message.py +101 -0
  55. lionagi/core/message/system.py +86 -0
  56. lionagi/core/message/util.py +283 -0
  57. lionagi/core/report/__init__.py +4 -0
  58. lionagi/core/report/base.py +217 -0
  59. lionagi/core/report/form.py +231 -0
  60. lionagi/core/report/report.py +166 -0
  61. lionagi/core/report/util.py +28 -0
  62. lionagi/core/rule/_default.py +16 -0
  63. lionagi/core/rule/action.py +99 -0
  64. lionagi/core/rule/base.py +238 -0
  65. lionagi/core/rule/boolean.py +56 -0
  66. lionagi/core/rule/choice.py +47 -0
  67. lionagi/core/rule/mapping.py +96 -0
  68. lionagi/core/rule/number.py +71 -0
  69. lionagi/core/rule/rulebook.py +109 -0
  70. lionagi/core/rule/string.py +52 -0
  71. lionagi/core/rule/util.py +35 -0
  72. lionagi/core/session/branch.py +431 -0
  73. lionagi/core/session/directive_mixin.py +287 -0
  74. lionagi/core/session/session.py +229 -903
  75. lionagi/core/structure/__init__.py +1 -0
  76. lionagi/core/structure/chain.py +1 -0
  77. lionagi/core/structure/forest.py +1 -0
  78. lionagi/core/structure/graph.py +1 -0
  79. lionagi/core/structure/tree.py +1 -0
  80. lionagi/core/unit/__init__.py +5 -0
  81. lionagi/core/unit/parallel_unit.py +245 -0
  82. lionagi/core/unit/template/action.py +81 -0
  83. lionagi/core/unit/template/base.py +51 -0
  84. lionagi/core/unit/template/plan.py +84 -0
  85. lionagi/core/unit/template/predict.py +109 -0
  86. lionagi/core/unit/template/score.py +124 -0
  87. lionagi/core/unit/template/select.py +104 -0
  88. lionagi/core/unit/unit.py +362 -0
  89. lionagi/core/unit/unit_form.py +305 -0
  90. lionagi/core/unit/unit_mixin.py +1168 -0
  91. lionagi/core/unit/util.py +71 -0
  92. lionagi/core/validator/validator.py +364 -0
  93. lionagi/core/work/work.py +76 -0
  94. lionagi/core/work/work_function.py +101 -0
  95. lionagi/core/work/work_queue.py +103 -0
  96. lionagi/core/work/worker.py +258 -0
  97. lionagi/core/work/worklog.py +120 -0
  98. lionagi/experimental/compressor/base.py +46 -0
  99. lionagi/experimental/compressor/llm_compressor.py +247 -0
  100. lionagi/experimental/compressor/llm_summarizer.py +61 -0
  101. lionagi/experimental/compressor/util.py +70 -0
  102. lionagi/experimental/directive/__init__.py +19 -0
  103. lionagi/experimental/directive/parser/base_parser.py +69 -2
  104. lionagi/experimental/directive/{template_ → template}/base_template.py +17 -1
  105. lionagi/{libs/ln_tokenizer.py → experimental/directive/tokenizer.py} +16 -0
  106. lionagi/experimental/{directive/evaluator → evaluator}/ast_evaluator.py +16 -0
  107. lionagi/experimental/{directive/evaluator → evaluator}/base_evaluator.py +16 -0
  108. lionagi/experimental/knowledge/base.py +10 -0
  109. lionagi/experimental/memory/__init__.py +0 -0
  110. lionagi/experimental/strategies/__init__.py +0 -0
  111. lionagi/experimental/strategies/base.py +1 -0
  112. lionagi/integrations/bridge/langchain_/documents.py +4 -0
  113. lionagi/integrations/bridge/llamaindex_/index.py +30 -0
  114. lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
  115. lionagi/integrations/chunker/chunk.py +161 -24
  116. lionagi/integrations/config/oai_configs.py +34 -3
  117. lionagi/integrations/config/openrouter_configs.py +14 -2
  118. lionagi/integrations/loader/load.py +122 -21
  119. lionagi/integrations/loader/load_util.py +6 -77
  120. lionagi/integrations/provider/_mapping.py +46 -0
  121. lionagi/integrations/provider/litellm.py +2 -1
  122. lionagi/integrations/provider/mlx_service.py +16 -9
  123. lionagi/integrations/provider/oai.py +91 -4
  124. lionagi/integrations/provider/ollama.py +6 -5
  125. lionagi/integrations/provider/openrouter.py +115 -8
  126. lionagi/integrations/provider/services.py +2 -2
  127. lionagi/integrations/provider/transformers.py +18 -22
  128. lionagi/integrations/storage/__init__.py +3 -3
  129. lionagi/integrations/storage/neo4j.py +52 -60
  130. lionagi/integrations/storage/storage_util.py +44 -46
  131. lionagi/integrations/storage/structure_excel.py +43 -26
  132. lionagi/integrations/storage/to_excel.py +11 -4
  133. lionagi/libs/__init__.py +22 -1
  134. lionagi/libs/ln_api.py +75 -20
  135. lionagi/libs/ln_context.py +37 -0
  136. lionagi/libs/ln_convert.py +21 -9
  137. lionagi/libs/ln_func_call.py +69 -28
  138. lionagi/libs/ln_image.py +107 -0
  139. lionagi/libs/ln_nested.py +26 -11
  140. lionagi/libs/ln_parse.py +82 -23
  141. lionagi/libs/ln_queue.py +16 -0
  142. lionagi/libs/ln_tokenize.py +164 -0
  143. lionagi/libs/ln_validate.py +16 -0
  144. lionagi/libs/special_tokens.py +172 -0
  145. lionagi/libs/sys_util.py +95 -24
  146. lionagi/lions/coder/code_form.py +13 -0
  147. lionagi/lions/coder/coder.py +50 -3
  148. lionagi/lions/coder/util.py +30 -25
  149. lionagi/tests/libs/test_func_call.py +23 -21
  150. lionagi/tests/libs/test_nested.py +36 -21
  151. lionagi/tests/libs/test_parse.py +1 -1
  152. lionagi/tests/test_core/collections/__init__.py +0 -0
  153. lionagi/tests/test_core/collections/test_component.py +206 -0
  154. lionagi/tests/test_core/collections/test_exchange.py +138 -0
  155. lionagi/tests/test_core/collections/test_flow.py +145 -0
  156. lionagi/tests/test_core/collections/test_pile.py +171 -0
  157. lionagi/tests/test_core/collections/test_progression.py +129 -0
  158. lionagi/tests/test_core/generic/test_edge.py +67 -0
  159. lionagi/tests/test_core/generic/test_graph.py +96 -0
  160. lionagi/tests/test_core/generic/test_node.py +106 -0
  161. lionagi/tests/test_core/generic/test_tree_node.py +73 -0
  162. lionagi/tests/test_core/test_branch.py +115 -294
  163. lionagi/tests/test_core/test_form.py +46 -0
  164. lionagi/tests/test_core/test_report.py +105 -0
  165. lionagi/tests/test_core/test_validator.py +111 -0
  166. lionagi/version.py +1 -1
  167. lionagi-0.2.1.dist-info/LICENSE +202 -0
  168. lionagi-0.2.1.dist-info/METADATA +272 -0
  169. lionagi-0.2.1.dist-info/RECORD +240 -0
  170. lionagi/core/branch/base.py +0 -653
  171. lionagi/core/branch/branch.py +0 -474
  172. lionagi/core/branch/flow_mixin.py +0 -96
  173. lionagi/core/branch/util.py +0 -323
  174. lionagi/core/direct/__init__.py +0 -19
  175. lionagi/core/direct/cot.py +0 -123
  176. lionagi/core/direct/plan.py +0 -164
  177. lionagi/core/direct/predict.py +0 -166
  178. lionagi/core/direct/react.py +0 -171
  179. lionagi/core/direct/score.py +0 -279
  180. lionagi/core/direct/select.py +0 -170
  181. lionagi/core/direct/sentiment.py +0 -1
  182. lionagi/core/direct/utils.py +0 -110
  183. lionagi/core/direct/vote.py +0 -64
  184. lionagi/core/execute/base_executor.py +0 -47
  185. lionagi/core/flow/baseflow.py +0 -23
  186. lionagi/core/flow/monoflow/ReAct.py +0 -240
  187. lionagi/core/flow/monoflow/__init__.py +0 -9
  188. lionagi/core/flow/monoflow/chat.py +0 -95
  189. lionagi/core/flow/monoflow/chat_mixin.py +0 -253
  190. lionagi/core/flow/monoflow/followup.py +0 -215
  191. lionagi/core/flow/polyflow/__init__.py +0 -1
  192. lionagi/core/flow/polyflow/chat.py +0 -251
  193. lionagi/core/form/action_form.py +0 -26
  194. lionagi/core/form/field_validator.py +0 -287
  195. lionagi/core/form/form.py +0 -302
  196. lionagi/core/form/mixin.py +0 -214
  197. lionagi/core/form/scored_form.py +0 -13
  198. lionagi/core/generic/action.py +0 -26
  199. lionagi/core/generic/component.py +0 -532
  200. lionagi/core/generic/condition.py +0 -46
  201. lionagi/core/generic/mail.py +0 -90
  202. lionagi/core/generic/mailbox.py +0 -36
  203. lionagi/core/generic/relation.py +0 -70
  204. lionagi/core/generic/signal.py +0 -22
  205. lionagi/core/generic/structure.py +0 -362
  206. lionagi/core/generic/transfer.py +0 -20
  207. lionagi/core/generic/work.py +0 -40
  208. lionagi/core/graph/graph.py +0 -126
  209. lionagi/core/graph/tree.py +0 -190
  210. lionagi/core/mail/schema.py +0 -63
  211. lionagi/core/messages/schema.py +0 -325
  212. lionagi/core/tool/__init__.py +0 -5
  213. lionagi/core/tool/tool.py +0 -28
  214. lionagi/core/tool/tool_manager.py +0 -283
  215. lionagi/experimental/report/form.py +0 -64
  216. lionagi/experimental/report/report.py +0 -138
  217. lionagi/experimental/report/util.py +0 -47
  218. lionagi/experimental/tool/function_calling.py +0 -43
  219. lionagi/experimental/tool/manual.py +0 -66
  220. lionagi/experimental/tool/schema.py +0 -59
  221. lionagi/experimental/tool/tool_manager.py +0 -138
  222. lionagi/experimental/tool/util.py +0 -16
  223. lionagi/experimental/validator/rule.py +0 -139
  224. lionagi/experimental/validator/validator.py +0 -56
  225. lionagi/experimental/work/__init__.py +0 -10
  226. lionagi/experimental/work/async_queue.py +0 -54
  227. lionagi/experimental/work/schema.py +0 -73
  228. lionagi/experimental/work/work_function.py +0 -67
  229. lionagi/experimental/work/worker.py +0 -56
  230. lionagi/experimental/work2/form.py +0 -371
  231. lionagi/experimental/work2/report.py +0 -289
  232. lionagi/experimental/work2/schema.py +0 -30
  233. lionagi/experimental/work2/tests.py +0 -72
  234. lionagi/experimental/work2/work_function.py +0 -89
  235. lionagi/experimental/work2/worker.py +0 -12
  236. lionagi/integrations/bridge/llamaindex_/get_index.py +0 -294
  237. lionagi/tests/test_core/generic/test_component.py +0 -89
  238. lionagi/tests/test_core/test_base_branch.py +0 -426
  239. lionagi/tests/test_core/test_chat_flow.py +0 -63
  240. lionagi/tests/test_core/test_mail_manager.py +0 -75
  241. lionagi/tests/test_core/test_prompts.py +0 -51
  242. lionagi/tests/test_core/test_session.py +0 -254
  243. lionagi/tests/test_core/test_session_base_util.py +0 -313
  244. lionagi/tests/test_core/test_tool_manager.py +0 -95
  245. lionagi-0.1.2.dist-info/LICENSE +0 -9
  246. lionagi-0.1.2.dist-info/METADATA +0 -174
  247. lionagi-0.1.2.dist-info/RECORD +0 -206
  248. /lionagi/core/{branch → _setting}/__init__.py +0 -0
  249. /lionagi/core/{execute → agent/eval}/__init__.py +0 -0
  250. /lionagi/core/{flow → agent/learn}/__init__.py +0 -0
  251. /lionagi/core/{form → agent/plan}/__init__.py +0 -0
  252. /lionagi/core/{branch/executable_branch.py → agent/plan/plan.py} +0 -0
  253. /lionagi/core/{graph → director}/__init__.py +0 -0
  254. /lionagi/core/{messages → engine}/__init__.py +0 -0
  255. /lionagi/{experimental/directive/evaluator → core/engine}/sandbox_.py +0 -0
  256. /lionagi/{experimental/directive/evaluator → core/executor}/__init__.py +0 -0
  257. /lionagi/{experimental/directive/template_ → core/rule}/__init__.py +0 -0
  258. /lionagi/{experimental/report → core/unit/template}/__init__.py +0 -0
  259. /lionagi/{experimental/tool → core/validator}/__init__.py +0 -0
  260. /lionagi/{experimental/validator → core/work}/__init__.py +0 -0
  261. /lionagi/experimental/{work2 → compressor}/__init__.py +0 -0
  262. /lionagi/{core/flow/mono_chat_mixin.py → experimental/directive/template/__init__.py} +0 -0
  263. /lionagi/experimental/directive/{schema.py → template/schema.py} +0 -0
  264. /lionagi/experimental/{work2/util.py → evaluator/__init__.py} +0 -0
  265. /lionagi/experimental/{work2/work.py → knowledge/__init__.py} +0 -0
  266. /lionagi/{tests/libs/test_async.py → experimental/knowledge/graph.py} +0 -0
  267. {lionagi-0.1.2.dist-info → lionagi-0.2.1.dist-info}/WHEEL +0 -0
  268. {lionagi-0.1.2.dist-info → lionagi-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,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,76 @@
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
+ from collections.abc import Coroutine
21
+
22
+ from lionagi.libs import SysUtil
23
+ from lionagi.core.collections.abc import Component
24
+
25
+
26
+ class WorkStatus(str, Enum):
27
+ """Enum to represent different statuses of work."""
28
+
29
+ PENDING = "PENDING"
30
+ IN_PROGRESS = "IN_PROGRESS"
31
+ COMPLETED = "COMPLETED"
32
+ FAILED = "FAILED"
33
+
34
+
35
+ class Work(Component):
36
+ """
37
+ A class representing a unit of work.
38
+
39
+ Attributes:
40
+ status (WorkStatus): The current status of the work.
41
+ result (Any): The result of the work, if completed.
42
+ error (Any): Any error encountered during the work.
43
+ async_task (Coroutine | None): The asynchronous task associated with the work.
44
+ completion_timestamp (str | None): The timestamp when the work was completed.
45
+ duration (float | None): The duration of the work.
46
+ """
47
+
48
+ status: WorkStatus = WorkStatus.PENDING
49
+ result: Any = None
50
+ error: Any = None
51
+ async_task: Coroutine | None = None
52
+ async_task_name: str | None = None
53
+ completion_timestamp: str | None = None
54
+ duration: float | None = None
55
+
56
+ async def perform(self):
57
+ """Perform the work and update the status, result, and duration."""
58
+ try:
59
+ result, duration = await self.async_task
60
+ self.result = result
61
+ self.status = WorkStatus.COMPLETED
62
+ self.duration = duration
63
+ del self.async_task
64
+ except Exception as e:
65
+ self.error = e
66
+ self.status = WorkStatus.FAILED
67
+ finally:
68
+ self.completion_timestamp = SysUtil.get_timestamp(sep=None)[:-6]
69
+
70
+ def __str__(self):
71
+ return (
72
+ f"Work(id={self.ln_id[:8]}.., status={self.status.value}, "
73
+ f"created_at={self.timestamp[:-7]}, "
74
+ f"completed_at={self.completion_timestamp[:-7]}, "
75
+ f"duration={float(self.duration) if self.duration else 0:.04f} sec(s))"
76
+ )
@@ -0,0 +1,101 @@
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
+ import logging
17
+
18
+ from lionagi.libs.ln_func_call import rcall
19
+ from lionagi.libs.ln_parse import ParseUtil
20
+ from lionagi.core.work.worklog import WorkLog
21
+
22
+ from lionagi.core.report.form import Form
23
+
24
+
25
+ class WorkFunction:
26
+ """
27
+ A class representing a work function.
28
+
29
+ Attributes:
30
+ assignment (str): The assignment description of the work function.
31
+ function (Callable): The function to be performed.
32
+ retry_kwargs (dict): The retry arguments for the function.
33
+ worklog (WorkLog): The work log for the function.
34
+ guidance (str): The guidance or documentation for the function.
35
+ """
36
+
37
+ def __init__(
38
+ self, assignment, function, retry_kwargs=None, guidance=None, capacity=10
39
+ ):
40
+ """
41
+ Initializes a WorkFunction instance.
42
+
43
+ Args:
44
+ assignment (str): The assignment description of the work function.
45
+ function (Callable): The function to be performed.
46
+ retry_kwargs (dict, optional): The retry arguments for the function.
47
+ Defaults to None.
48
+ guidance (str, optional): The guidance or documentation for the function.
49
+ Defaults to None.
50
+ capacity (int, optional): The capacity of the work log. Defaults to 10.
51
+ """
52
+ self.assignment = assignment
53
+ self.function = function
54
+ self.retry_kwargs = retry_kwargs or {}
55
+ self.worklog = WorkLog(capacity)
56
+ self.guidance = guidance or self.function.__doc__
57
+
58
+ @property
59
+ def name(self):
60
+ """
61
+ Gets the name of the function.
62
+
63
+ Returns:
64
+ str: The name of the function.
65
+ """
66
+ return self.function.__name__
67
+
68
+ def is_progressable(self):
69
+ """
70
+ Checks if the work function is progressable.
71
+
72
+ Returns:
73
+ bool: True if the work function is progressable, otherwise False.
74
+ """
75
+ return self.worklog.pending_work and not self.worklog.stopped
76
+
77
+ async def perform(self, *args, **kwargs):
78
+ """
79
+ Performs the work function with retry logic.
80
+
81
+ Args:
82
+ *args: Positional arguments for the function.
83
+ **kwargs: Keyword arguments for the function.
84
+
85
+ Returns:
86
+ Any: The result of the function call.
87
+ """
88
+ kwargs = {**self.retry_kwargs, **kwargs}
89
+ return await rcall(self.function, *args, timing=True, **kwargs)
90
+
91
+ async def forward(self):
92
+ """
93
+ Forward the work log to work queue.
94
+ """
95
+ await self.worklog.forward()
96
+
97
+ async def process(self):
98
+ """
99
+ Process the first capacity_size works in the work queue.
100
+ """
101
+ await self.worklog.queue.process()