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.
- lionagi/__init__.py +60 -5
- lionagi/core/__init__.py +0 -25
- lionagi/core/_setting/_setting.py +59 -0
- lionagi/core/action/__init__.py +14 -0
- lionagi/core/action/function_calling.py +136 -0
- lionagi/core/action/manual.py +1 -0
- lionagi/core/action/node.py +109 -0
- lionagi/core/action/tool.py +114 -0
- lionagi/core/action/tool_manager.py +356 -0
- lionagi/core/agent/base_agent.py +27 -13
- lionagi/core/agent/eval/evaluator.py +1 -0
- lionagi/core/agent/eval/vote.py +40 -0
- lionagi/core/agent/learn/learner.py +59 -0
- lionagi/core/agent/plan/unit_template.py +1 -0
- lionagi/core/collections/__init__.py +17 -0
- lionagi/core/{generic/data_logger.py → collections/_logger.py} +69 -55
- lionagi/core/collections/abc/__init__.py +53 -0
- lionagi/core/collections/abc/component.py +615 -0
- lionagi/core/collections/abc/concepts.py +297 -0
- lionagi/core/collections/abc/exceptions.py +150 -0
- lionagi/core/collections/abc/util.py +45 -0
- lionagi/core/collections/exchange.py +161 -0
- lionagi/core/collections/flow.py +426 -0
- lionagi/core/collections/model.py +419 -0
- lionagi/core/collections/pile.py +913 -0
- lionagi/core/collections/progression.py +236 -0
- lionagi/core/collections/util.py +64 -0
- lionagi/core/director/direct.py +314 -0
- lionagi/core/director/director.py +2 -0
- lionagi/core/{execute/branch_executor.py → engine/branch_engine.py} +134 -97
- lionagi/core/{execute/instruction_map_executor.py → engine/instruction_map_engine.py} +80 -55
- lionagi/{experimental/directive/evaluator → core/engine}/script_engine.py +17 -1
- lionagi/core/executor/base_executor.py +90 -0
- lionagi/core/{execute/structure_executor.py → executor/graph_executor.py} +62 -66
- lionagi/core/{execute → executor}/neo4j_executor.py +70 -67
- lionagi/core/generic/__init__.py +3 -33
- lionagi/core/generic/edge.py +29 -79
- lionagi/core/generic/edge_condition.py +16 -0
- lionagi/core/generic/graph.py +236 -0
- lionagi/core/generic/hyperedge.py +1 -0
- lionagi/core/generic/node.py +156 -221
- lionagi/core/generic/tree.py +48 -0
- lionagi/core/generic/tree_node.py +79 -0
- lionagi/core/mail/__init__.py +12 -0
- lionagi/core/mail/mail.py +25 -0
- lionagi/core/mail/mail_manager.py +139 -58
- lionagi/core/mail/package.py +45 -0
- lionagi/core/mail/start_mail.py +36 -0
- lionagi/core/message/__init__.py +19 -0
- lionagi/core/message/action_request.py +133 -0
- lionagi/core/message/action_response.py +135 -0
- lionagi/core/message/assistant_response.py +95 -0
- lionagi/core/message/instruction.py +234 -0
- lionagi/core/message/message.py +101 -0
- lionagi/core/message/system.py +86 -0
- lionagi/core/message/util.py +283 -0
- lionagi/core/report/__init__.py +4 -0
- lionagi/core/report/base.py +217 -0
- lionagi/core/report/form.py +231 -0
- lionagi/core/report/report.py +166 -0
- lionagi/core/report/util.py +28 -0
- lionagi/core/rule/_default.py +16 -0
- lionagi/core/rule/action.py +99 -0
- lionagi/core/rule/base.py +238 -0
- lionagi/core/rule/boolean.py +56 -0
- lionagi/core/rule/choice.py +47 -0
- lionagi/core/rule/mapping.py +96 -0
- lionagi/core/rule/number.py +71 -0
- lionagi/core/rule/rulebook.py +109 -0
- lionagi/core/rule/string.py +52 -0
- lionagi/core/rule/util.py +35 -0
- lionagi/core/session/branch.py +431 -0
- lionagi/core/session/directive_mixin.py +287 -0
- lionagi/core/session/session.py +229 -903
- lionagi/core/structure/__init__.py +1 -0
- lionagi/core/structure/chain.py +1 -0
- lionagi/core/structure/forest.py +1 -0
- lionagi/core/structure/graph.py +1 -0
- lionagi/core/structure/tree.py +1 -0
- lionagi/core/unit/__init__.py +5 -0
- lionagi/core/unit/parallel_unit.py +245 -0
- lionagi/core/unit/template/action.py +81 -0
- lionagi/core/unit/template/base.py +51 -0
- lionagi/core/unit/template/plan.py +84 -0
- lionagi/core/unit/template/predict.py +109 -0
- lionagi/core/unit/template/score.py +124 -0
- lionagi/core/unit/template/select.py +104 -0
- lionagi/core/unit/unit.py +362 -0
- lionagi/core/unit/unit_form.py +305 -0
- lionagi/core/unit/unit_mixin.py +1168 -0
- lionagi/core/unit/util.py +71 -0
- lionagi/core/validator/validator.py +364 -0
- lionagi/core/work/work.py +74 -0
- lionagi/core/work/work_function.py +92 -0
- lionagi/core/work/work_queue.py +81 -0
- lionagi/core/work/worker.py +195 -0
- lionagi/core/work/worklog.py +124 -0
- lionagi/experimental/compressor/base.py +46 -0
- lionagi/experimental/compressor/llm_compressor.py +247 -0
- lionagi/experimental/compressor/llm_summarizer.py +61 -0
- lionagi/experimental/compressor/util.py +70 -0
- lionagi/experimental/directive/__init__.py +19 -0
- lionagi/experimental/directive/parser/base_parser.py +69 -2
- lionagi/experimental/directive/{template_ → template}/base_template.py +17 -1
- lionagi/{libs/ln_tokenizer.py → experimental/directive/tokenizer.py} +16 -0
- lionagi/experimental/{directive/evaluator → evaluator}/ast_evaluator.py +16 -0
- lionagi/experimental/{directive/evaluator → evaluator}/base_evaluator.py +16 -0
- lionagi/experimental/knowledge/base.py +10 -0
- lionagi/experimental/memory/__init__.py +0 -0
- lionagi/experimental/strategies/__init__.py +0 -0
- lionagi/experimental/strategies/base.py +1 -0
- lionagi/integrations/bridge/langchain_/documents.py +4 -0
- lionagi/integrations/bridge/llamaindex_/index.py +30 -0
- lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
- lionagi/integrations/chunker/chunk.py +161 -24
- lionagi/integrations/config/oai_configs.py +34 -3
- lionagi/integrations/config/openrouter_configs.py +14 -2
- lionagi/integrations/loader/load.py +122 -21
- lionagi/integrations/loader/load_util.py +6 -77
- lionagi/integrations/provider/_mapping.py +46 -0
- lionagi/integrations/provider/litellm.py +2 -1
- lionagi/integrations/provider/mlx_service.py +16 -9
- lionagi/integrations/provider/oai.py +91 -4
- lionagi/integrations/provider/ollama.py +6 -5
- lionagi/integrations/provider/openrouter.py +115 -8
- lionagi/integrations/provider/services.py +2 -2
- lionagi/integrations/provider/transformers.py +18 -22
- lionagi/integrations/storage/__init__.py +3 -3
- lionagi/integrations/storage/neo4j.py +52 -60
- lionagi/integrations/storage/storage_util.py +44 -46
- lionagi/integrations/storage/structure_excel.py +43 -26
- lionagi/integrations/storage/to_excel.py +11 -4
- lionagi/libs/__init__.py +22 -1
- lionagi/libs/ln_api.py +75 -20
- lionagi/libs/ln_context.py +37 -0
- lionagi/libs/ln_convert.py +21 -9
- lionagi/libs/ln_func_call.py +69 -28
- lionagi/libs/ln_image.py +107 -0
- lionagi/libs/ln_nested.py +26 -11
- lionagi/libs/ln_parse.py +82 -23
- lionagi/libs/ln_queue.py +16 -0
- lionagi/libs/ln_tokenize.py +164 -0
- lionagi/libs/ln_validate.py +16 -0
- lionagi/libs/special_tokens.py +172 -0
- lionagi/libs/sys_util.py +95 -24
- lionagi/lions/coder/code_form.py +13 -0
- lionagi/lions/coder/coder.py +50 -3
- lionagi/lions/coder/util.py +30 -25
- lionagi/tests/libs/test_func_call.py +23 -21
- lionagi/tests/libs/test_nested.py +36 -21
- lionagi/tests/libs/test_parse.py +1 -1
- lionagi/tests/test_core/collections/__init__.py +0 -0
- lionagi/tests/test_core/collections/test_component.py +206 -0
- lionagi/tests/test_core/collections/test_exchange.py +138 -0
- lionagi/tests/test_core/collections/test_flow.py +145 -0
- lionagi/tests/test_core/collections/test_pile.py +171 -0
- lionagi/tests/test_core/collections/test_progression.py +129 -0
- lionagi/tests/test_core/generic/test_edge.py +67 -0
- lionagi/tests/test_core/generic/test_graph.py +96 -0
- lionagi/tests/test_core/generic/test_node.py +106 -0
- lionagi/tests/test_core/generic/test_tree_node.py +73 -0
- lionagi/tests/test_core/test_branch.py +115 -294
- lionagi/tests/test_core/test_form.py +46 -0
- lionagi/tests/test_core/test_report.py +105 -0
- lionagi/tests/test_core/test_validator.py +111 -0
- lionagi/version.py +1 -1
- lionagi-0.2.0.dist-info/LICENSE +202 -0
- lionagi-0.2.0.dist-info/METADATA +272 -0
- lionagi-0.2.0.dist-info/RECORD +240 -0
- lionagi/core/branch/base.py +0 -653
- lionagi/core/branch/branch.py +0 -474
- lionagi/core/branch/flow_mixin.py +0 -96
- lionagi/core/branch/util.py +0 -323
- lionagi/core/direct/__init__.py +0 -19
- lionagi/core/direct/cot.py +0 -123
- lionagi/core/direct/plan.py +0 -164
- lionagi/core/direct/predict.py +0 -166
- lionagi/core/direct/react.py +0 -171
- lionagi/core/direct/score.py +0 -279
- lionagi/core/direct/select.py +0 -170
- lionagi/core/direct/sentiment.py +0 -1
- lionagi/core/direct/utils.py +0 -110
- lionagi/core/direct/vote.py +0 -64
- lionagi/core/execute/base_executor.py +0 -47
- lionagi/core/flow/baseflow.py +0 -23
- lionagi/core/flow/monoflow/ReAct.py +0 -240
- lionagi/core/flow/monoflow/__init__.py +0 -9
- lionagi/core/flow/monoflow/chat.py +0 -95
- lionagi/core/flow/monoflow/chat_mixin.py +0 -253
- lionagi/core/flow/monoflow/followup.py +0 -215
- lionagi/core/flow/polyflow/__init__.py +0 -1
- lionagi/core/flow/polyflow/chat.py +0 -251
- lionagi/core/form/action_form.py +0 -26
- lionagi/core/form/field_validator.py +0 -287
- lionagi/core/form/form.py +0 -302
- lionagi/core/form/mixin.py +0 -214
- lionagi/core/form/scored_form.py +0 -13
- lionagi/core/generic/action.py +0 -26
- lionagi/core/generic/component.py +0 -532
- lionagi/core/generic/condition.py +0 -46
- lionagi/core/generic/mail.py +0 -90
- lionagi/core/generic/mailbox.py +0 -36
- lionagi/core/generic/relation.py +0 -70
- lionagi/core/generic/signal.py +0 -22
- lionagi/core/generic/structure.py +0 -362
- lionagi/core/generic/transfer.py +0 -20
- lionagi/core/generic/work.py +0 -40
- lionagi/core/graph/graph.py +0 -126
- lionagi/core/graph/tree.py +0 -190
- lionagi/core/mail/schema.py +0 -63
- lionagi/core/messages/schema.py +0 -325
- lionagi/core/tool/__init__.py +0 -5
- lionagi/core/tool/tool.py +0 -28
- lionagi/core/tool/tool_manager.py +0 -283
- lionagi/experimental/report/form.py +0 -64
- lionagi/experimental/report/report.py +0 -138
- lionagi/experimental/report/util.py +0 -47
- lionagi/experimental/tool/function_calling.py +0 -43
- lionagi/experimental/tool/manual.py +0 -66
- lionagi/experimental/tool/schema.py +0 -59
- lionagi/experimental/tool/tool_manager.py +0 -138
- lionagi/experimental/tool/util.py +0 -16
- lionagi/experimental/validator/rule.py +0 -139
- lionagi/experimental/validator/validator.py +0 -56
- lionagi/experimental/work/__init__.py +0 -10
- lionagi/experimental/work/async_queue.py +0 -54
- lionagi/experimental/work/schema.py +0 -73
- lionagi/experimental/work/work_function.py +0 -67
- lionagi/experimental/work/worker.py +0 -56
- lionagi/experimental/work2/form.py +0 -371
- lionagi/experimental/work2/report.py +0 -289
- lionagi/experimental/work2/schema.py +0 -30
- lionagi/experimental/work2/tests.py +0 -72
- lionagi/experimental/work2/work_function.py +0 -89
- lionagi/experimental/work2/worker.py +0 -12
- lionagi/integrations/bridge/llamaindex_/get_index.py +0 -294
- lionagi/tests/test_core/generic/test_component.py +0 -89
- lionagi/tests/test_core/test_base_branch.py +0 -426
- lionagi/tests/test_core/test_chat_flow.py +0 -63
- lionagi/tests/test_core/test_mail_manager.py +0 -75
- lionagi/tests/test_core/test_prompts.py +0 -51
- lionagi/tests/test_core/test_session.py +0 -254
- lionagi/tests/test_core/test_session_base_util.py +0 -313
- lionagi/tests/test_core/test_tool_manager.py +0 -95
- lionagi-0.1.2.dist-info/LICENSE +0 -9
- lionagi-0.1.2.dist-info/METADATA +0 -174
- lionagi-0.1.2.dist-info/RECORD +0 -206
- /lionagi/core/{branch → _setting}/__init__.py +0 -0
- /lionagi/core/{execute → agent/eval}/__init__.py +0 -0
- /lionagi/core/{flow → agent/learn}/__init__.py +0 -0
- /lionagi/core/{form → agent/plan}/__init__.py +0 -0
- /lionagi/core/{branch/executable_branch.py → agent/plan/plan.py} +0 -0
- /lionagi/core/{graph → director}/__init__.py +0 -0
- /lionagi/core/{messages → engine}/__init__.py +0 -0
- /lionagi/{experimental/directive/evaluator → core/engine}/sandbox_.py +0 -0
- /lionagi/{experimental/directive/evaluator → core/executor}/__init__.py +0 -0
- /lionagi/{experimental/directive/template_ → core/rule}/__init__.py +0 -0
- /lionagi/{experimental/report → core/unit/template}/__init__.py +0 -0
- /lionagi/{experimental/tool → core/validator}/__init__.py +0 -0
- /lionagi/{experimental/validator → core/work}/__init__.py +0 -0
- /lionagi/experimental/{work2 → compressor}/__init__.py +0 -0
- /lionagi/{core/flow/mono_chat_mixin.py → experimental/directive/template/__init__.py} +0 -0
- /lionagi/experimental/directive/{schema.py → template/schema.py} +0 -0
- /lionagi/experimental/{work2/util.py → evaluator/__init__.py} +0 -0
- /lionagi/experimental/{work2/work.py → knowledge/__init__.py} +0 -0
- /lionagi/{tests/libs/test_async.py → experimental/knowledge/graph.py} +0 -0
- {lionagi-0.1.2.dist-info → lionagi-0.2.0.dist-info}/WHEEL +0 -0
- {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)
|