lionagi 0.0.312__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lionagi/__init__.py +61 -3
- lionagi/core/__init__.py +0 -14
- 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/__init__.py +0 -3
- lionagi/core/agent/base_agent.py +45 -36
- 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/collections/_logger.py +319 -0
- 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/engine/branch_engine.py +333 -0
- lionagi/core/engine/instruction_map_engine.py +204 -0
- lionagi/core/engine/sandbox_.py +14 -0
- lionagi/core/engine/script_engine.py +99 -0
- lionagi/core/executor/base_executor.py +90 -0
- lionagi/core/executor/graph_executor.py +330 -0
- lionagi/core/executor/neo4j_executor.py +384 -0
- lionagi/core/generic/__init__.py +7 -0
- lionagi/core/generic/edge.py +112 -0
- 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 +220 -0
- lionagi/core/generic/tree.py +48 -0
- lionagi/core/generic/tree_node.py +79 -0
- lionagi/core/mail/__init__.py +7 -3
- lionagi/core/mail/mail.py +25 -0
- lionagi/core/mail/mail_manager.py +142 -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/__init__.py +0 -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/__init__.py +0 -3
- lionagi/core/session/branch.py +431 -0
- lionagi/core/session/directive_mixin.py +287 -0
- lionagi/core/session/session.py +230 -902
- 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/__init__.py +0 -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/__init__.py +0 -0
- lionagi/core/validator/validator.py +364 -0
- lionagi/core/work/__init__.py +0 -0
- lionagi/core/work/work.py +76 -0
- lionagi/core/work/work_function.py +101 -0
- lionagi/core/work/work_queue.py +103 -0
- lionagi/core/work/worker.py +258 -0
- lionagi/core/work/worklog.py +120 -0
- lionagi/experimental/__init__.py +0 -0
- lionagi/experimental/compressor/__init__.py +0 -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/__init__.py +0 -0
- lionagi/experimental/directive/parser/base_parser.py +282 -0
- lionagi/experimental/directive/template/__init__.py +0 -0
- lionagi/experimental/directive/template/base_template.py +79 -0
- lionagi/experimental/directive/template/schema.py +36 -0
- lionagi/experimental/directive/tokenizer.py +73 -0
- lionagi/experimental/evaluator/__init__.py +0 -0
- lionagi/experimental/evaluator/ast_evaluator.py +131 -0
- lionagi/experimental/evaluator/base_evaluator.py +218 -0
- lionagi/experimental/knowledge/__init__.py +0 -0
- lionagi/experimental/knowledge/base.py +10 -0
- lionagi/experimental/knowledge/graph.py +0 -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/autogen_/__init__.py +0 -0
- lionagi/integrations/bridge/autogen_/autogen_.py +124 -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/bridge/llamaindex_/llama_pack.py +227 -0
- lionagi/integrations/bridge/llamaindex_/node_parser.py +6 -9
- lionagi/integrations/bridge/pydantic_/pydantic_bridge.py +1 -0
- lionagi/integrations/bridge/transformers_/__init__.py +0 -0
- lionagi/integrations/bridge/transformers_/install_.py +36 -0
- lionagi/integrations/chunker/__init__.py +0 -0
- lionagi/integrations/chunker/chunk.py +312 -0
- lionagi/integrations/config/oai_configs.py +38 -7
- lionagi/integrations/config/ollama_configs.py +1 -1
- lionagi/integrations/config/openrouter_configs.py +14 -2
- lionagi/integrations/loader/__init__.py +0 -0
- lionagi/integrations/loader/load.py +253 -0
- lionagi/integrations/loader/load_util.py +195 -0
- 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 +7 -6
- 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 -0
- lionagi/integrations/storage/neo4j.py +665 -0
- lionagi/integrations/storage/storage_util.py +287 -0
- lionagi/integrations/storage/structure_excel.py +285 -0
- lionagi/integrations/storage/to_csv.py +63 -0
- lionagi/integrations/storage/to_excel.py +83 -0
- lionagi/libs/__init__.py +26 -1
- lionagi/libs/ln_api.py +78 -23
- 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_knowledge_graph.py +405 -0
- lionagi/libs/ln_nested.py +26 -11
- lionagi/libs/ln_parse.py +110 -14
- lionagi/libs/ln_queue.py +117 -0
- lionagi/libs/ln_tokenize.py +164 -0
- lionagi/{core/prompt/field_validator.py → libs/ln_validate.py} +79 -14
- lionagi/libs/special_tokens.py +172 -0
- lionagi/libs/sys_util.py +107 -2
- lionagi/lions/__init__.py +0 -0
- lionagi/lions/coder/__init__.py +0 -0
- lionagi/lions/coder/add_feature.py +20 -0
- lionagi/lions/coder/base_prompts.py +22 -0
- lionagi/lions/coder/code_form.py +13 -0
- lionagi/lions/coder/coder.py +168 -0
- lionagi/lions/coder/util.py +96 -0
- lionagi/lions/researcher/__init__.py +0 -0
- lionagi/lions/researcher/data_source/__init__.py +0 -0
- lionagi/lions/researcher/data_source/finhub_.py +191 -0
- lionagi/lions/researcher/data_source/google_.py +199 -0
- lionagi/lions/researcher/data_source/wiki_.py +96 -0
- lionagi/lions/researcher/data_source/yfinance_.py +21 -0
- lionagi/tests/integrations/__init__.py +0 -0
- lionagi/tests/libs/__init__.py +0 -0
- lionagi/tests/libs/test_field_validators.py +353 -0
- lionagi/tests/{test_libs → libs}/test_func_call.py +23 -21
- lionagi/tests/{test_libs → libs}/test_nested.py +36 -21
- lionagi/tests/{test_libs → libs}/test_parse.py +1 -1
- lionagi/tests/libs/test_queue.py +67 -0
- 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/__init__.py +0 -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 -292
- 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.0.312.dist-info → lionagi-0.2.1.dist-info}/LICENSE +12 -11
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/METADATA +19 -118
- lionagi-0.2.1.dist-info/RECORD +240 -0
- lionagi/core/branch/__init__.py +0 -4
- lionagi/core/branch/base_branch.py +0 -654
- lionagi/core/branch/branch.py +0 -471
- lionagi/core/branch/branch_flow_mixin.py +0 -96
- lionagi/core/branch/executable_branch.py +0 -347
- lionagi/core/branch/util.py +0 -323
- lionagi/core/direct/__init__.py +0 -6
- lionagi/core/direct/predict.py +0 -161
- lionagi/core/direct/score.py +0 -278
- lionagi/core/direct/select.py +0 -169
- lionagi/core/direct/utils.py +0 -87
- lionagi/core/direct/vote.py +0 -64
- lionagi/core/flow/base/baseflow.py +0 -23
- lionagi/core/flow/monoflow/ReAct.py +0 -238
- lionagi/core/flow/monoflow/__init__.py +0 -9
- lionagi/core/flow/monoflow/chat.py +0 -95
- lionagi/core/flow/monoflow/chat_mixin.py +0 -263
- lionagi/core/flow/monoflow/followup.py +0 -214
- lionagi/core/flow/polyflow/__init__.py +0 -1
- lionagi/core/flow/polyflow/chat.py +0 -248
- lionagi/core/mail/schema.py +0 -56
- lionagi/core/messages/__init__.py +0 -3
- lionagi/core/messages/schema.py +0 -533
- lionagi/core/prompt/prompt_template.py +0 -316
- lionagi/core/schema/__init__.py +0 -22
- lionagi/core/schema/action_node.py +0 -29
- lionagi/core/schema/base_mixin.py +0 -296
- lionagi/core/schema/base_node.py +0 -199
- lionagi/core/schema/condition.py +0 -24
- lionagi/core/schema/data_logger.py +0 -354
- lionagi/core/schema/data_node.py +0 -93
- lionagi/core/schema/prompt_template.py +0 -67
- lionagi/core/schema/structure.py +0 -910
- lionagi/core/tool/__init__.py +0 -3
- lionagi/core/tool/tool_manager.py +0 -280
- lionagi/integrations/bridge/pydantic_/base_model.py +0 -7
- lionagi/tests/test_core/test_base_branch.py +0 -427
- 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 -312
- lionagi/tests/test_core/test_tool_manager.py +0 -95
- lionagi-0.0.312.dist-info/RECORD +0 -111
- /lionagi/core/{branch/base → _setting}/__init__.py +0 -0
- /lionagi/core/{flow → agent/eval}/__init__.py +0 -0
- /lionagi/core/{flow/base → agent/learn}/__init__.py +0 -0
- /lionagi/core/{prompt → agent/plan}/__init__.py +0 -0
- /lionagi/core/{tool/manual.py → agent/plan/plan.py} +0 -0
- /lionagi/{tests/test_integrations → core/director}/__init__.py +0 -0
- /lionagi/{tests/test_libs → core/engine}/__init__.py +0 -0
- /lionagi/{tests/test_libs/test_async.py → core/executor/__init__.py} +0 -0
- /lionagi/tests/{test_libs → libs}/test_api.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_convert.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_sys_util.py +0 -0
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/WHEEL +0 -0
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,333 @@
|
|
1
|
+
import contextlib
|
2
|
+
from lionagi.libs import convert, AsyncUtil, ParseUtil
|
3
|
+
from lionagi.core.generic.edge import Edge
|
4
|
+
from lionagi.core.action import ActionNode
|
5
|
+
from lionagi.core.mail.mail import Mail
|
6
|
+
from lionagi.core.message import System, Instruction
|
7
|
+
from lionagi.core.collections import Pile, Progression
|
8
|
+
|
9
|
+
from lionagi.core.session.branch import Branch
|
10
|
+
from lionagi.core.executor.base_executor import BaseExecutor
|
11
|
+
|
12
|
+
|
13
|
+
class BranchExecutor(Branch, BaseExecutor):
|
14
|
+
|
15
|
+
def __init__(
|
16
|
+
self,
|
17
|
+
context=None,
|
18
|
+
verbose=True,
|
19
|
+
system=None,
|
20
|
+
user=None,
|
21
|
+
messages=None,
|
22
|
+
progress=None,
|
23
|
+
tool_manager=None,
|
24
|
+
tools=None,
|
25
|
+
imodel=None,
|
26
|
+
**kwargs,
|
27
|
+
):
|
28
|
+
super().__init__(
|
29
|
+
system=system,
|
30
|
+
user=user,
|
31
|
+
messages=messages,
|
32
|
+
progress=progress,
|
33
|
+
tool_manager=tool_manager,
|
34
|
+
tools=tools,
|
35
|
+
imodel=imodel,
|
36
|
+
**kwargs,
|
37
|
+
)
|
38
|
+
self.context = context
|
39
|
+
self.verbose = verbose
|
40
|
+
|
41
|
+
async def forward(self) -> None:
|
42
|
+
"""
|
43
|
+
Forwards the execution by processing all pending incoming mails in each branch. Depending on the category of the mail,
|
44
|
+
it processes starts, nodes, node lists, conditions, or ends, accordingly executing different functions.
|
45
|
+
"""
|
46
|
+
for key in list(self.mailbox.pending_ins.keys()):
|
47
|
+
while self.mailbox.pending_ins[key].size() > 0:
|
48
|
+
mail_id = self.mailbox.pending_ins[key].popleft()
|
49
|
+
mail = self.mailbox.pile.pop(mail_id)
|
50
|
+
if mail.category == "start":
|
51
|
+
self._process_start(mail)
|
52
|
+
elif mail.category == "node":
|
53
|
+
await self._process_node(mail)
|
54
|
+
elif mail.category == "node_list":
|
55
|
+
self._process_node_list(mail)
|
56
|
+
elif mail.category == "condition":
|
57
|
+
await self._process_condition(mail)
|
58
|
+
elif mail.category == "end":
|
59
|
+
self._process_end(mail)
|
60
|
+
if self.mailbox.pending_ins[key].size() == 0:
|
61
|
+
self.mailbox.pending_ins.pop(key)
|
62
|
+
|
63
|
+
async def execute(self, refresh_time=1) -> None:
|
64
|
+
"""
|
65
|
+
Executes the forward process repeatedly at specified time intervals until execution is instructed to stop.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
refresh_time (int): The interval, in seconds, at which the forward method is called repeatedly.
|
69
|
+
"""
|
70
|
+
while not self.execute_stop:
|
71
|
+
await self.forward()
|
72
|
+
await AsyncUtil.sleep(refresh_time)
|
73
|
+
|
74
|
+
async def _process_node(self, mail: Mail):
|
75
|
+
"""
|
76
|
+
Processes a single node based on the node type specified in the mail's package. It handles different types of nodes such as System,
|
77
|
+
Instruction, ActionNode, and generic nodes through separate processes.
|
78
|
+
|
79
|
+
Args:
|
80
|
+
mail (Mail): The mail containing the node to be processed along with associated details.
|
81
|
+
|
82
|
+
Raises:
|
83
|
+
ValueError: If an invalid mail is encountered or the process encounters errors.
|
84
|
+
"""
|
85
|
+
node = mail.package.package
|
86
|
+
if isinstance(node, System):
|
87
|
+
self._system_process(node, verbose=self.verbose)
|
88
|
+
self.send(
|
89
|
+
recipient=mail.sender,
|
90
|
+
category="node_id",
|
91
|
+
package=node.ln_id,
|
92
|
+
request_source=self.ln_id,
|
93
|
+
)
|
94
|
+
|
95
|
+
elif isinstance(node, Instruction):
|
96
|
+
await self._instruction_process(node, verbose=self.verbose)
|
97
|
+
self.send(
|
98
|
+
recipient=mail.sender,
|
99
|
+
category="node_id",
|
100
|
+
package=node.ln_id,
|
101
|
+
request_source=self.ln_id,
|
102
|
+
)
|
103
|
+
|
104
|
+
elif isinstance(node, ActionNode):
|
105
|
+
await self._action_process(node, verbose=self.verbose)
|
106
|
+
self.send(
|
107
|
+
recipient=mail.sender,
|
108
|
+
category="node_id",
|
109
|
+
package=node.instruction.ln_id,
|
110
|
+
request_source=self.ln_id,
|
111
|
+
)
|
112
|
+
else:
|
113
|
+
try:
|
114
|
+
await self._agent_process(node, verbose=self.verbose)
|
115
|
+
self.send(
|
116
|
+
recipient=mail.sender,
|
117
|
+
category="node_id",
|
118
|
+
package=node.ln_id,
|
119
|
+
request_source=self.ln_id,
|
120
|
+
)
|
121
|
+
except:
|
122
|
+
raise ValueError(f"Invalid mail to process. Mail:{mail}")
|
123
|
+
|
124
|
+
def _process_node_list(self, mail: Mail):
|
125
|
+
"""
|
126
|
+
Processes a list of nodes provided in the mail, but currently only sends an end signal as multiple path selection is not supported.
|
127
|
+
|
128
|
+
Args:
|
129
|
+
mail (BaseMail): The mail containing a list of nodes to be processed.
|
130
|
+
|
131
|
+
Raises:
|
132
|
+
ValueError: When trying to process multiple paths which is currently unsupported.
|
133
|
+
"""
|
134
|
+
self.send(mail.sender, category="end", package="end", request_source=self.ln_id)
|
135
|
+
self.execute_stop = True
|
136
|
+
raise ValueError("Multiple path selection is not supported in BranchExecutor")
|
137
|
+
|
138
|
+
async def _process_condition(self, mail: Mail):
|
139
|
+
"""
|
140
|
+
Processes a condition associated with an edge based on the mail's package, setting up the result of the condition check.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
mail (BaseMail): The mail containing the condition to be processed.
|
144
|
+
"""
|
145
|
+
edge: Edge = mail.package.package
|
146
|
+
check_result = await edge.check_condition(self)
|
147
|
+
back_mail = {
|
148
|
+
"from": self.ln_id,
|
149
|
+
"edge_id": edge.ln_id,
|
150
|
+
"check_result": check_result,
|
151
|
+
}
|
152
|
+
self.send(
|
153
|
+
recipient=mail.sender,
|
154
|
+
category="condition",
|
155
|
+
package=back_mail,
|
156
|
+
request_source=self.ln_id,
|
157
|
+
)
|
158
|
+
|
159
|
+
def _system_process(self, system: System, verbose=True, context_verbose=False):
|
160
|
+
"""
|
161
|
+
Processes a system node, possibly displaying its content and context if verbose is enabled.
|
162
|
+
|
163
|
+
Args:
|
164
|
+
system (System): The system node to process.
|
165
|
+
verbose (bool): Flag to enable verbose output.
|
166
|
+
context_verbose (bool): Flag to enable verbose output specifically for context.
|
167
|
+
"""
|
168
|
+
from lionagi.libs import SysUtil
|
169
|
+
|
170
|
+
SysUtil.check_import("IPython")
|
171
|
+
from IPython.display import Markdown, display
|
172
|
+
|
173
|
+
if verbose:
|
174
|
+
print(f"------------------Welcome: {system.sender}--------------------")
|
175
|
+
with contextlib.suppress(Exception):
|
176
|
+
system.content = ParseUtil.fuzzy_parse_json(system.content)
|
177
|
+
display(Markdown(f"system: {convert.to_str(system.system_info)}"))
|
178
|
+
if self.context and context_verbose:
|
179
|
+
display(Markdown(f"context: {convert.to_str(self.context)}"))
|
180
|
+
|
181
|
+
self.add_message(system=system)
|
182
|
+
|
183
|
+
async def _instruction_process(
|
184
|
+
self, instruction: Instruction, verbose=True, **kwargs
|
185
|
+
):
|
186
|
+
"""
|
187
|
+
Processes an instruction node, possibly displaying its content if verbose is enabled, and handling any additional keyword arguments.
|
188
|
+
|
189
|
+
Args:
|
190
|
+
instruction (Instruction): The instruction node to process.
|
191
|
+
verbose (bool): Flag to enable verbose output.
|
192
|
+
**kwargs: Additional keyword arguments that might affect how instructions are processed.
|
193
|
+
"""
|
194
|
+
from lionagi.libs import SysUtil
|
195
|
+
|
196
|
+
SysUtil.check_import("IPython")
|
197
|
+
from IPython.display import Markdown, display
|
198
|
+
|
199
|
+
if verbose:
|
200
|
+
with contextlib.suppress(Exception):
|
201
|
+
instruction.content = ParseUtil.fuzzy_parse_json(instruction.content)
|
202
|
+
display(
|
203
|
+
Markdown(
|
204
|
+
f"{instruction.sender}: {convert.to_str(instruction.instruct)}"
|
205
|
+
)
|
206
|
+
)
|
207
|
+
|
208
|
+
if self.context:
|
209
|
+
result = await self.chat(
|
210
|
+
instruction=instruction.instruct, context=self.context, **kwargs
|
211
|
+
)
|
212
|
+
self.context = None
|
213
|
+
else:
|
214
|
+
result = await self.chat(instruction=instruction.instruct, **kwargs)
|
215
|
+
# instruction._add_context(context=self.context)
|
216
|
+
# self.context_log.append(self.context)
|
217
|
+
# self.context = None
|
218
|
+
|
219
|
+
with contextlib.suppress(Exception):
|
220
|
+
result = ParseUtil.fuzzy_parse_json(result)
|
221
|
+
if "assistant_response" in result.keys():
|
222
|
+
result = result["assistant_response"]
|
223
|
+
if verbose:
|
224
|
+
display(Markdown(f"assistant {self.ln_id}: {convert.to_str(result)}"))
|
225
|
+
print("-----------------------------------------------------")
|
226
|
+
|
227
|
+
self.execution_responses.append(result)
|
228
|
+
|
229
|
+
async def _action_process(self, action: ActionNode, verbose=True):
|
230
|
+
"""
|
231
|
+
Processes an action node, executing the defined action along with any tools specified within the node.
|
232
|
+
|
233
|
+
Args:
|
234
|
+
action (ActionNode): The action node to process.
|
235
|
+
verbose (bool): Flag to enable verbose output of the action results.
|
236
|
+
"""
|
237
|
+
from lionagi.libs import SysUtil
|
238
|
+
|
239
|
+
SysUtil.check_import("IPython")
|
240
|
+
from IPython.display import Markdown, display
|
241
|
+
|
242
|
+
# try:
|
243
|
+
# func = getattr(self, action.action)
|
244
|
+
# except:
|
245
|
+
# raise ValueError(f"{action.action} is not a valid action")
|
246
|
+
|
247
|
+
if verbose:
|
248
|
+
display(
|
249
|
+
Markdown(
|
250
|
+
f"{action.instruction.sender}: {convert.to_str(action.instruction.instruct)}"
|
251
|
+
)
|
252
|
+
)
|
253
|
+
|
254
|
+
# if action.tools:
|
255
|
+
# self.register_tools(action.tools)
|
256
|
+
# if self.context:
|
257
|
+
# result = await self.direct(
|
258
|
+
# action.directive,
|
259
|
+
# instruction=action.instruction.instruct,
|
260
|
+
# context=self.context,
|
261
|
+
# tools=action.tools,
|
262
|
+
# **action.directive_kwargs,
|
263
|
+
# )
|
264
|
+
result = await action.invoke(branch=self, context=self.context)
|
265
|
+
self.context = None
|
266
|
+
# else:
|
267
|
+
# result = await self.direct(
|
268
|
+
# action.directive,
|
269
|
+
# instruction=action.instruction.content,
|
270
|
+
# tools=action.tools,
|
271
|
+
# **action.directive_kwargs
|
272
|
+
# )
|
273
|
+
|
274
|
+
if verbose:
|
275
|
+
if action.directive == "chat":
|
276
|
+
display(Markdown(f"assistant {self.ln_id}: {convert.to_str(result)}"))
|
277
|
+
else:
|
278
|
+
display(Markdown(f"assistant {self.ln_id}:\n"))
|
279
|
+
for k, v in result.work_fields.items():
|
280
|
+
display(Markdown(f"{k}: \n{v}\n"))
|
281
|
+
print("-----------------------------------------------------")
|
282
|
+
|
283
|
+
self.execution_responses.append(result)
|
284
|
+
|
285
|
+
async def _agent_process(self, agent, verbose=True):
|
286
|
+
"""
|
287
|
+
Processes an agent.
|
288
|
+
|
289
|
+
Args:
|
290
|
+
agent: The agent to process.
|
291
|
+
verbose (bool): A flag indicating whether to provide verbose output (default: True).
|
292
|
+
"""
|
293
|
+
context = [msg["content"] for msg in self.to_chat_messages()]
|
294
|
+
if verbose:
|
295
|
+
print("*****************************************************")
|
296
|
+
result = await agent.execute(context)
|
297
|
+
|
298
|
+
if verbose:
|
299
|
+
print("*****************************************************")
|
300
|
+
|
301
|
+
self.context = result
|
302
|
+
self.execution_responses.append(result)
|
303
|
+
|
304
|
+
def _process_start(self, mail):
|
305
|
+
"""
|
306
|
+
Processes a start mail.
|
307
|
+
|
308
|
+
Args:
|
309
|
+
mail (BaseMail): The start mail to process.
|
310
|
+
"""
|
311
|
+
start_mail_content = mail.package.package
|
312
|
+
self.context = start_mail_content["context"]
|
313
|
+
self.send(
|
314
|
+
recipient=start_mail_content["structure_id"],
|
315
|
+
category="start",
|
316
|
+
package="start",
|
317
|
+
request_source=self.ln_id,
|
318
|
+
)
|
319
|
+
|
320
|
+
def _process_end(self, mail: Mail):
|
321
|
+
"""
|
322
|
+
Processes an end mail.
|
323
|
+
|
324
|
+
Args:
|
325
|
+
mail (BaseMail): The end mail to process.
|
326
|
+
"""
|
327
|
+
self.execute_stop = True
|
328
|
+
self.send(
|
329
|
+
recipient=mail.sender,
|
330
|
+
category="end",
|
331
|
+
package="end",
|
332
|
+
request_source=self.ln_id,
|
333
|
+
)
|
@@ -0,0 +1,204 @@
|
|
1
|
+
import asyncio
|
2
|
+
from pydantic import Field
|
3
|
+
|
4
|
+
from lionagi.core.mail.mail import Mail, Package
|
5
|
+
from lionagi.core.collections import Exchange
|
6
|
+
from lionagi.core.mail.mail_manager import MailManager
|
7
|
+
from lionagi.core.executor.base_executor import BaseExecutor
|
8
|
+
from lionagi.core.engine.branch_engine import BranchExecutor
|
9
|
+
from lionagi.core.collections import progression, pile, Pile
|
10
|
+
|
11
|
+
|
12
|
+
class InstructionMapEngine(BaseExecutor):
|
13
|
+
"""
|
14
|
+
Manages the execution of a mapped set of instructions across multiple branches within an executable structure.
|
15
|
+
|
16
|
+
Attributes:
|
17
|
+
branches (dict[str, BranchExecutor]): A dictionary of branch executors managing individual instruction flows.
|
18
|
+
structure_id (str): The identifier for the structure within which these branches operate.
|
19
|
+
mail_transfer (Exchange): Handles the transfer of mail between branches and other components.
|
20
|
+
branch_kwargs (dict): Keyword arguments used for initializing branches.
|
21
|
+
num_end_branches (int): Tracks the number of branches that have completed execution.
|
22
|
+
mail_manager (MailManager): Manages the distribution and collection of mails across branches.
|
23
|
+
"""
|
24
|
+
|
25
|
+
branches: Pile[BranchExecutor] = Field(
|
26
|
+
default_factory=dict, description="The branches of the instruction mapping."
|
27
|
+
)
|
28
|
+
structure_id: str = Field("", description="The ID of the executable structure.")
|
29
|
+
mail_transfer: Exchange = Field(
|
30
|
+
default_factory=Exchange, description="The mail transfer."
|
31
|
+
)
|
32
|
+
branch_kwargs: dict = Field(
|
33
|
+
default_factory=dict,
|
34
|
+
description="The keyword arguments for the initializing the branches.",
|
35
|
+
)
|
36
|
+
num_end_branches: int = Field(0, description="The number of end branches.")
|
37
|
+
mail_manager: MailManager = Field(
|
38
|
+
default_factory=MailManager, description="The mail manager."
|
39
|
+
)
|
40
|
+
|
41
|
+
def __init__(self, **kwargs):
|
42
|
+
"""
|
43
|
+
Initializes an InstructionMapExecutor with the given parameters.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
**kwargs: Arbitrary keyword arguments passed to the base executor and used for initializing branch executors.
|
47
|
+
"""
|
48
|
+
super().__init__(**kwargs)
|
49
|
+
self.mail_manager = MailManager([self.mail_transfer])
|
50
|
+
|
51
|
+
def transfer_ins(self):
|
52
|
+
"""
|
53
|
+
Processes incoming mails, directing them appropriately based on their categories, and handles the initial setup
|
54
|
+
of branches or the routing of node and condition mails.
|
55
|
+
"""
|
56
|
+
for key in list(self.mailbox.pending_ins.keys()):
|
57
|
+
while self.mailbox.pending_ins[key].size() > 0:
|
58
|
+
mail_id = self.mailbox.pending_ins[key].popleft()
|
59
|
+
mail = self.mailbox.pile.pop(mail_id)
|
60
|
+
if mail.category == "start":
|
61
|
+
self._process_start(mail)
|
62
|
+
elif mail.category == "node_list":
|
63
|
+
self._process_node_list(mail)
|
64
|
+
elif (
|
65
|
+
(mail.category == "node")
|
66
|
+
or (mail.category == "condition")
|
67
|
+
or (mail.category == "end")
|
68
|
+
):
|
69
|
+
mail.sender = self.mail_transfer.ln_id
|
70
|
+
mail.recipient = mail.package.request_source
|
71
|
+
self.mail_transfer.include(mail, "out")
|
72
|
+
|
73
|
+
def transfer_outs(self):
|
74
|
+
"""
|
75
|
+
Processes outgoing mails from the central mail transfer, handling end-of-execution notifications and routing
|
76
|
+
other mails to appropriate recipients.
|
77
|
+
"""
|
78
|
+
for key in list(self.mail_transfer.pending_ins.keys()):
|
79
|
+
while self.mail_transfer.pending_ins[key].size() > 0:
|
80
|
+
mail_id = self.mail_transfer.pending_ins[key].popleft()
|
81
|
+
mail = self.mail_transfer.pile.pop(mail_id)
|
82
|
+
if mail.category == "end":
|
83
|
+
self.num_end_branches += 1
|
84
|
+
if self.num_end_branches == len(
|
85
|
+
self.branches
|
86
|
+
): # tell when structure should stop
|
87
|
+
mail.sender = self.ln_id
|
88
|
+
mail.recipient = self.structure_id
|
89
|
+
self.mailbox.include(mail, "out")
|
90
|
+
self.execute_stop = True
|
91
|
+
else:
|
92
|
+
mail.sender = self.ln_id
|
93
|
+
mail.recipient = self.structure_id
|
94
|
+
self.mailbox.include(mail, "out")
|
95
|
+
|
96
|
+
def _process_start(self, start_mail: Mail):
|
97
|
+
"""
|
98
|
+
Processes a start mail to initialize a new branch executor and configures it based on the mail's package content.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
start_mail (BaseMail): The mail initiating the start of a new branch execution.
|
102
|
+
"""
|
103
|
+
branch = BranchExecutor(verbose=self.verbose, **self.branch_kwargs)
|
104
|
+
branch.context = start_mail.package.package["context"]
|
105
|
+
self.branches[branch.ln_id] = branch
|
106
|
+
self.mail_manager.add_sources([branch])
|
107
|
+
self.structure_id = start_mail.package.package["structure_id"]
|
108
|
+
|
109
|
+
pack = Package(category="start", package="start", request_source=branch.ln_id)
|
110
|
+
mail = Mail(
|
111
|
+
sender=self.ln_id,
|
112
|
+
recipient=self.structure_id,
|
113
|
+
package=pack,
|
114
|
+
)
|
115
|
+
self.mailbox.include(mail, "out")
|
116
|
+
|
117
|
+
def _process_node_list(self, nl_mail: Mail):
|
118
|
+
"""
|
119
|
+
Processes a node list mail, setting up new branches or propagating the execution context based on the node list
|
120
|
+
provided in the mail.
|
121
|
+
|
122
|
+
Args:
|
123
|
+
nl_mail (BaseMail): The mail containing a list of nodes to be processed in subsequent branches.
|
124
|
+
"""
|
125
|
+
source_branch_id = nl_mail.package.request_source
|
126
|
+
node_list = nl_mail.package.package
|
127
|
+
shared_context = self.branches[source_branch_id].context
|
128
|
+
shared_context_log = self.branches[source_branch_id].context_log
|
129
|
+
base_branch = self.branches[source_branch_id]
|
130
|
+
|
131
|
+
pack = Package(
|
132
|
+
category="node", package=node_list[0], request_source=source_branch_id
|
133
|
+
)
|
134
|
+
mail = Mail(
|
135
|
+
sender=self.mail_transfer.ln_id,
|
136
|
+
recipient=source_branch_id,
|
137
|
+
package=pack,
|
138
|
+
)
|
139
|
+
self.mail_transfer.include(mail, "out")
|
140
|
+
|
141
|
+
for i in range(1, len(node_list)):
|
142
|
+
system = base_branch.system.clone() if base_branch.system else None
|
143
|
+
if system:
|
144
|
+
system.sender = base_branch.ln_id
|
145
|
+
progress = progression()
|
146
|
+
messages = pile()
|
147
|
+
|
148
|
+
for id_ in base_branch.progress:
|
149
|
+
clone_message = base_branch.messages[id_].clone()
|
150
|
+
progress.append(clone_message.ln_id)
|
151
|
+
messages.append(clone_message)
|
152
|
+
|
153
|
+
branch = BranchExecutor(
|
154
|
+
verbose=self.verbose,
|
155
|
+
messages=messages,
|
156
|
+
user=base_branch.user,
|
157
|
+
system=base_branch.system.clone(),
|
158
|
+
progress=progress,
|
159
|
+
imodel=base_branch.imodel,
|
160
|
+
)
|
161
|
+
for message in branch.messages:
|
162
|
+
message.sender = base_branch.ln_id
|
163
|
+
message.recipient = branch.ln_id
|
164
|
+
|
165
|
+
branch.context = shared_context
|
166
|
+
branch.context_log = shared_context_log
|
167
|
+
self.branches[branch.ln_id] = branch
|
168
|
+
self.mail_manager.add_sources([branch])
|
169
|
+
node_pacakge = Package(
|
170
|
+
category="node", package=node_list[i], request_source=source_branch_id
|
171
|
+
)
|
172
|
+
node_mail = Mail(
|
173
|
+
sender=self.mail_transfer.ln_id,
|
174
|
+
recipient=branch.ln_id,
|
175
|
+
package=node_pacakge,
|
176
|
+
)
|
177
|
+
self.mail_transfer.include(node_mail, "out")
|
178
|
+
|
179
|
+
async def forward(self):
|
180
|
+
"""
|
181
|
+
Forwards the execution by processing all incoming and outgoing mails and advancing the state of all active branches.
|
182
|
+
"""
|
183
|
+
self.transfer_ins()
|
184
|
+
self.transfer_outs()
|
185
|
+
self.mail_manager.collect_all()
|
186
|
+
self.mail_manager.send_all()
|
187
|
+
tasks = [
|
188
|
+
branch.forward()
|
189
|
+
for branch in self.branches.values()
|
190
|
+
if branch.mailbox.pending_ins
|
191
|
+
]
|
192
|
+
await asyncio.gather(*tasks)
|
193
|
+
return
|
194
|
+
|
195
|
+
async def execute(self, refresh_time=1):
|
196
|
+
"""
|
197
|
+
Continuously executes the forward process at specified intervals until instructed to stop.
|
198
|
+
|
199
|
+
Args:
|
200
|
+
refresh_time (int): The time in seconds between execution cycles.
|
201
|
+
"""
|
202
|
+
while not self.execute_stop:
|
203
|
+
await self.forward()
|
204
|
+
await asyncio.sleep(refresh_time)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# filename: enhanced_script_engine.py
|
2
|
+
import ast
|
3
|
+
|
4
|
+
|
5
|
+
class SandboxTransformer(ast.NodeTransformer):
|
6
|
+
"""AST transformer to enforce restrictions in sandbox mode."""
|
7
|
+
|
8
|
+
def visit_Import(self, node):
|
9
|
+
raise RuntimeError("Import statements are not allowed in sandbox mode.")
|
10
|
+
|
11
|
+
def visit_Exec(self, node):
|
12
|
+
raise RuntimeError("Exec statements are not allowed in sandbox mode.")
|
13
|
+
|
14
|
+
# Add other visit methods for disallowed operations or nodes
|
@@ -0,0 +1,99 @@
|
|
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 ast
|
18
|
+
from functools import lru_cache
|
19
|
+
from lionagi.libs import AsyncUtil
|
20
|
+
from ..evaluator.base_evaluator import BaseEvaluator
|
21
|
+
from .sandbox_ import SandboxTransformer
|
22
|
+
|
23
|
+
|
24
|
+
class ScriptEngine:
|
25
|
+
def __init__(self):
|
26
|
+
self.variables = {}
|
27
|
+
self.safe_evaluator = BaseEvaluator()
|
28
|
+
self.functions = {
|
29
|
+
"processData": self.process_data,
|
30
|
+
}
|
31
|
+
|
32
|
+
def process_data(self, data):
|
33
|
+
return data * 2
|
34
|
+
|
35
|
+
def _evaluate_expression(self, expression):
|
36
|
+
return self.safe_evaluator.evaluate(expression, self.variables)
|
37
|
+
|
38
|
+
def _assign_variable(self, var_name, value):
|
39
|
+
self.variables[var_name] = value
|
40
|
+
|
41
|
+
def _execute_function(self, func_name, arg):
|
42
|
+
if func_name in self.functions:
|
43
|
+
return self.functions[func_name](arg)
|
44
|
+
else:
|
45
|
+
raise ValueError(f"Function {func_name} not defined.")
|
46
|
+
|
47
|
+
def execute(self, script):
|
48
|
+
tree = ast.parse(script)
|
49
|
+
for stmt in tree.body:
|
50
|
+
if isinstance(stmt, ast.Assign):
|
51
|
+
var_name = stmt.targets[0].id
|
52
|
+
value = self._evaluate_expression(ast.unparse(stmt.value))
|
53
|
+
self._assign_variable(var_name, value)
|
54
|
+
elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
|
55
|
+
func_name = stmt.value.func.id
|
56
|
+
arg = self._evaluate_expression(ast.unparse(stmt.value.args[0]))
|
57
|
+
result = self._execute_function(func_name, arg)
|
58
|
+
# For demonstration, manually update 'x' to simulate expected behavior
|
59
|
+
if func_name == "processData":
|
60
|
+
self._assign_variable("x", result)
|
61
|
+
|
62
|
+
def add_hook(self, event_name, callback):
|
63
|
+
"""Subscribe a callback function to a specific event."""
|
64
|
+
self.hooks[event_name].append(callback)
|
65
|
+
|
66
|
+
def trigger_hooks(self, event_name, *args, **kwargs):
|
67
|
+
"""Trigger all callbacks attached to a specific event."""
|
68
|
+
for callback in self.hooks[event_name]:
|
69
|
+
callback(*args, **kwargs)
|
70
|
+
|
71
|
+
async def process_data(self, data):
|
72
|
+
# Example asynchronous function
|
73
|
+
return data * 2
|
74
|
+
|
75
|
+
@lru_cache(maxsize=128)
|
76
|
+
def evaluate_expression(self, expression):
|
77
|
+
return self.safe_evaluator.evaluate(expression, self.variables)
|
78
|
+
|
79
|
+
async def _execute_function_async(self, func_name, arg):
|
80
|
+
if func_name in self.functions:
|
81
|
+
func = self.functions[func_name]
|
82
|
+
if AsyncUtil.is_coroutine_func(func):
|
83
|
+
return await func(arg)
|
84
|
+
else:
|
85
|
+
return func(arg)
|
86
|
+
else:
|
87
|
+
raise ValueError(f"Function {func_name} not defined.")
|
88
|
+
|
89
|
+
def execute_sandboxed(self, script):
|
90
|
+
# Parse and sanitize the script
|
91
|
+
tree = ast.parse(script, mode="exec")
|
92
|
+
sanitized_tree = SandboxTransformer().visit(tree)
|
93
|
+
ast.fix_missing_locations(sanitized_tree)
|
94
|
+
|
95
|
+
# Compile the sanitized AST
|
96
|
+
code = compile(sanitized_tree, "<sandbox>", "exec")
|
97
|
+
|
98
|
+
# Execute the code in a restricted namespace
|
99
|
+
exec(code, {"__builtins__": None}, self.variables)
|