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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (268) hide show
  1. lionagi/__init__.py +61 -3
  2. lionagi/core/__init__.py +0 -14
  3. lionagi/core/_setting/_setting.py +59 -0
  4. lionagi/core/action/__init__.py +14 -0
  5. lionagi/core/action/function_calling.py +136 -0
  6. lionagi/core/action/manual.py +1 -0
  7. lionagi/core/action/node.py +109 -0
  8. lionagi/core/action/tool.py +114 -0
  9. lionagi/core/action/tool_manager.py +356 -0
  10. lionagi/core/agent/__init__.py +0 -3
  11. lionagi/core/agent/base_agent.py +45 -36
  12. lionagi/core/agent/eval/evaluator.py +1 -0
  13. lionagi/core/agent/eval/vote.py +40 -0
  14. lionagi/core/agent/learn/learner.py +59 -0
  15. lionagi/core/agent/plan/unit_template.py +1 -0
  16. lionagi/core/collections/__init__.py +17 -0
  17. lionagi/core/collections/_logger.py +319 -0
  18. lionagi/core/collections/abc/__init__.py +53 -0
  19. lionagi/core/collections/abc/component.py +615 -0
  20. lionagi/core/collections/abc/concepts.py +297 -0
  21. lionagi/core/collections/abc/exceptions.py +150 -0
  22. lionagi/core/collections/abc/util.py +45 -0
  23. lionagi/core/collections/exchange.py +161 -0
  24. lionagi/core/collections/flow.py +426 -0
  25. lionagi/core/collections/model.py +419 -0
  26. lionagi/core/collections/pile.py +913 -0
  27. lionagi/core/collections/progression.py +236 -0
  28. lionagi/core/collections/util.py +64 -0
  29. lionagi/core/director/direct.py +314 -0
  30. lionagi/core/director/director.py +2 -0
  31. lionagi/core/engine/branch_engine.py +333 -0
  32. lionagi/core/engine/instruction_map_engine.py +204 -0
  33. lionagi/core/engine/sandbox_.py +14 -0
  34. lionagi/core/engine/script_engine.py +99 -0
  35. lionagi/core/executor/base_executor.py +90 -0
  36. lionagi/core/executor/graph_executor.py +330 -0
  37. lionagi/core/executor/neo4j_executor.py +384 -0
  38. lionagi/core/generic/__init__.py +7 -0
  39. lionagi/core/generic/edge.py +112 -0
  40. lionagi/core/generic/edge_condition.py +16 -0
  41. lionagi/core/generic/graph.py +236 -0
  42. lionagi/core/generic/hyperedge.py +1 -0
  43. lionagi/core/generic/node.py +220 -0
  44. lionagi/core/generic/tree.py +48 -0
  45. lionagi/core/generic/tree_node.py +79 -0
  46. lionagi/core/mail/__init__.py +7 -3
  47. lionagi/core/mail/mail.py +25 -0
  48. lionagi/core/mail/mail_manager.py +142 -58
  49. lionagi/core/mail/package.py +45 -0
  50. lionagi/core/mail/start_mail.py +36 -0
  51. lionagi/core/message/__init__.py +19 -0
  52. lionagi/core/message/action_request.py +133 -0
  53. lionagi/core/message/action_response.py +135 -0
  54. lionagi/core/message/assistant_response.py +95 -0
  55. lionagi/core/message/instruction.py +234 -0
  56. lionagi/core/message/message.py +101 -0
  57. lionagi/core/message/system.py +86 -0
  58. lionagi/core/message/util.py +283 -0
  59. lionagi/core/report/__init__.py +4 -0
  60. lionagi/core/report/base.py +217 -0
  61. lionagi/core/report/form.py +231 -0
  62. lionagi/core/report/report.py +166 -0
  63. lionagi/core/report/util.py +28 -0
  64. lionagi/core/rule/__init__.py +0 -0
  65. lionagi/core/rule/_default.py +16 -0
  66. lionagi/core/rule/action.py +99 -0
  67. lionagi/core/rule/base.py +238 -0
  68. lionagi/core/rule/boolean.py +56 -0
  69. lionagi/core/rule/choice.py +47 -0
  70. lionagi/core/rule/mapping.py +96 -0
  71. lionagi/core/rule/number.py +71 -0
  72. lionagi/core/rule/rulebook.py +109 -0
  73. lionagi/core/rule/string.py +52 -0
  74. lionagi/core/rule/util.py +35 -0
  75. lionagi/core/session/__init__.py +0 -3
  76. lionagi/core/session/branch.py +431 -0
  77. lionagi/core/session/directive_mixin.py +287 -0
  78. lionagi/core/session/session.py +230 -902
  79. lionagi/core/structure/__init__.py +1 -0
  80. lionagi/core/structure/chain.py +1 -0
  81. lionagi/core/structure/forest.py +1 -0
  82. lionagi/core/structure/graph.py +1 -0
  83. lionagi/core/structure/tree.py +1 -0
  84. lionagi/core/unit/__init__.py +5 -0
  85. lionagi/core/unit/parallel_unit.py +245 -0
  86. lionagi/core/unit/template/__init__.py +0 -0
  87. lionagi/core/unit/template/action.py +81 -0
  88. lionagi/core/unit/template/base.py +51 -0
  89. lionagi/core/unit/template/plan.py +84 -0
  90. lionagi/core/unit/template/predict.py +109 -0
  91. lionagi/core/unit/template/score.py +124 -0
  92. lionagi/core/unit/template/select.py +104 -0
  93. lionagi/core/unit/unit.py +362 -0
  94. lionagi/core/unit/unit_form.py +305 -0
  95. lionagi/core/unit/unit_mixin.py +1168 -0
  96. lionagi/core/unit/util.py +71 -0
  97. lionagi/core/validator/__init__.py +0 -0
  98. lionagi/core/validator/validator.py +364 -0
  99. lionagi/core/work/__init__.py +0 -0
  100. lionagi/core/work/work.py +76 -0
  101. lionagi/core/work/work_function.py +101 -0
  102. lionagi/core/work/work_queue.py +103 -0
  103. lionagi/core/work/worker.py +258 -0
  104. lionagi/core/work/worklog.py +120 -0
  105. lionagi/experimental/__init__.py +0 -0
  106. lionagi/experimental/compressor/__init__.py +0 -0
  107. lionagi/experimental/compressor/base.py +46 -0
  108. lionagi/experimental/compressor/llm_compressor.py +247 -0
  109. lionagi/experimental/compressor/llm_summarizer.py +61 -0
  110. lionagi/experimental/compressor/util.py +70 -0
  111. lionagi/experimental/directive/__init__.py +19 -0
  112. lionagi/experimental/directive/parser/__init__.py +0 -0
  113. lionagi/experimental/directive/parser/base_parser.py +282 -0
  114. lionagi/experimental/directive/template/__init__.py +0 -0
  115. lionagi/experimental/directive/template/base_template.py +79 -0
  116. lionagi/experimental/directive/template/schema.py +36 -0
  117. lionagi/experimental/directive/tokenizer.py +73 -0
  118. lionagi/experimental/evaluator/__init__.py +0 -0
  119. lionagi/experimental/evaluator/ast_evaluator.py +131 -0
  120. lionagi/experimental/evaluator/base_evaluator.py +218 -0
  121. lionagi/experimental/knowledge/__init__.py +0 -0
  122. lionagi/experimental/knowledge/base.py +10 -0
  123. lionagi/experimental/knowledge/graph.py +0 -0
  124. lionagi/experimental/memory/__init__.py +0 -0
  125. lionagi/experimental/strategies/__init__.py +0 -0
  126. lionagi/experimental/strategies/base.py +1 -0
  127. lionagi/integrations/bridge/autogen_/__init__.py +0 -0
  128. lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
  129. lionagi/integrations/bridge/langchain_/documents.py +4 -0
  130. lionagi/integrations/bridge/llamaindex_/index.py +30 -0
  131. lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
  132. lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
  133. lionagi/integrations/bridge/llamaindex_/node_parser.py +6 -9
  134. lionagi/integrations/bridge/pydantic_/pydantic_bridge.py +1 -0
  135. lionagi/integrations/bridge/transformers_/__init__.py +0 -0
  136. lionagi/integrations/bridge/transformers_/install_.py +36 -0
  137. lionagi/integrations/chunker/__init__.py +0 -0
  138. lionagi/integrations/chunker/chunk.py +312 -0
  139. lionagi/integrations/config/oai_configs.py +38 -7
  140. lionagi/integrations/config/ollama_configs.py +1 -1
  141. lionagi/integrations/config/openrouter_configs.py +14 -2
  142. lionagi/integrations/loader/__init__.py +0 -0
  143. lionagi/integrations/loader/load.py +253 -0
  144. lionagi/integrations/loader/load_util.py +195 -0
  145. lionagi/integrations/provider/_mapping.py +46 -0
  146. lionagi/integrations/provider/litellm.py +2 -1
  147. lionagi/integrations/provider/mlx_service.py +16 -9
  148. lionagi/integrations/provider/oai.py +91 -4
  149. lionagi/integrations/provider/ollama.py +7 -6
  150. lionagi/integrations/provider/openrouter.py +115 -8
  151. lionagi/integrations/provider/services.py +2 -2
  152. lionagi/integrations/provider/transformers.py +18 -22
  153. lionagi/integrations/storage/__init__.py +3 -0
  154. lionagi/integrations/storage/neo4j.py +665 -0
  155. lionagi/integrations/storage/storage_util.py +287 -0
  156. lionagi/integrations/storage/structure_excel.py +285 -0
  157. lionagi/integrations/storage/to_csv.py +63 -0
  158. lionagi/integrations/storage/to_excel.py +83 -0
  159. lionagi/libs/__init__.py +26 -1
  160. lionagi/libs/ln_api.py +78 -23
  161. lionagi/libs/ln_context.py +37 -0
  162. lionagi/libs/ln_convert.py +21 -9
  163. lionagi/libs/ln_func_call.py +69 -28
  164. lionagi/libs/ln_image.py +107 -0
  165. lionagi/libs/ln_knowledge_graph.py +405 -0
  166. lionagi/libs/ln_nested.py +26 -11
  167. lionagi/libs/ln_parse.py +110 -14
  168. lionagi/libs/ln_queue.py +117 -0
  169. lionagi/libs/ln_tokenize.py +164 -0
  170. lionagi/{core/prompt/field_validator.py → libs/ln_validate.py} +79 -14
  171. lionagi/libs/special_tokens.py +172 -0
  172. lionagi/libs/sys_util.py +107 -2
  173. lionagi/lions/__init__.py +0 -0
  174. lionagi/lions/coder/__init__.py +0 -0
  175. lionagi/lions/coder/add_feature.py +20 -0
  176. lionagi/lions/coder/base_prompts.py +22 -0
  177. lionagi/lions/coder/code_form.py +13 -0
  178. lionagi/lions/coder/coder.py +168 -0
  179. lionagi/lions/coder/util.py +96 -0
  180. lionagi/lions/researcher/__init__.py +0 -0
  181. lionagi/lions/researcher/data_source/__init__.py +0 -0
  182. lionagi/lions/researcher/data_source/finhub_.py +191 -0
  183. lionagi/lions/researcher/data_source/google_.py +199 -0
  184. lionagi/lions/researcher/data_source/wiki_.py +96 -0
  185. lionagi/lions/researcher/data_source/yfinance_.py +21 -0
  186. lionagi/tests/integrations/__init__.py +0 -0
  187. lionagi/tests/libs/__init__.py +0 -0
  188. lionagi/tests/libs/test_field_validators.py +353 -0
  189. lionagi/tests/{test_libs → libs}/test_func_call.py +23 -21
  190. lionagi/tests/{test_libs → libs}/test_nested.py +36 -21
  191. lionagi/tests/{test_libs → libs}/test_parse.py +1 -1
  192. lionagi/tests/libs/test_queue.py +67 -0
  193. lionagi/tests/test_core/collections/__init__.py +0 -0
  194. lionagi/tests/test_core/collections/test_component.py +206 -0
  195. lionagi/tests/test_core/collections/test_exchange.py +138 -0
  196. lionagi/tests/test_core/collections/test_flow.py +145 -0
  197. lionagi/tests/test_core/collections/test_pile.py +171 -0
  198. lionagi/tests/test_core/collections/test_progression.py +129 -0
  199. lionagi/tests/test_core/generic/__init__.py +0 -0
  200. lionagi/tests/test_core/generic/test_edge.py +67 -0
  201. lionagi/tests/test_core/generic/test_graph.py +96 -0
  202. lionagi/tests/test_core/generic/test_node.py +106 -0
  203. lionagi/tests/test_core/generic/test_tree_node.py +73 -0
  204. lionagi/tests/test_core/test_branch.py +115 -292
  205. lionagi/tests/test_core/test_form.py +46 -0
  206. lionagi/tests/test_core/test_report.py +105 -0
  207. lionagi/tests/test_core/test_validator.py +111 -0
  208. lionagi/version.py +1 -1
  209. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/LICENSE +12 -11
  210. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/METADATA +19 -118
  211. lionagi-0.2.1.dist-info/RECORD +240 -0
  212. lionagi/core/branch/__init__.py +0 -4
  213. lionagi/core/branch/base_branch.py +0 -654
  214. lionagi/core/branch/branch.py +0 -471
  215. lionagi/core/branch/branch_flow_mixin.py +0 -96
  216. lionagi/core/branch/executable_branch.py +0 -347
  217. lionagi/core/branch/util.py +0 -323
  218. lionagi/core/direct/__init__.py +0 -6
  219. lionagi/core/direct/predict.py +0 -161
  220. lionagi/core/direct/score.py +0 -278
  221. lionagi/core/direct/select.py +0 -169
  222. lionagi/core/direct/utils.py +0 -87
  223. lionagi/core/direct/vote.py +0 -64
  224. lionagi/core/flow/base/baseflow.py +0 -23
  225. lionagi/core/flow/monoflow/ReAct.py +0 -238
  226. lionagi/core/flow/monoflow/__init__.py +0 -9
  227. lionagi/core/flow/monoflow/chat.py +0 -95
  228. lionagi/core/flow/monoflow/chat_mixin.py +0 -263
  229. lionagi/core/flow/monoflow/followup.py +0 -214
  230. lionagi/core/flow/polyflow/__init__.py +0 -1
  231. lionagi/core/flow/polyflow/chat.py +0 -248
  232. lionagi/core/mail/schema.py +0 -56
  233. lionagi/core/messages/__init__.py +0 -3
  234. lionagi/core/messages/schema.py +0 -533
  235. lionagi/core/prompt/prompt_template.py +0 -316
  236. lionagi/core/schema/__init__.py +0 -22
  237. lionagi/core/schema/action_node.py +0 -29
  238. lionagi/core/schema/base_mixin.py +0 -296
  239. lionagi/core/schema/base_node.py +0 -199
  240. lionagi/core/schema/condition.py +0 -24
  241. lionagi/core/schema/data_logger.py +0 -354
  242. lionagi/core/schema/data_node.py +0 -93
  243. lionagi/core/schema/prompt_template.py +0 -67
  244. lionagi/core/schema/structure.py +0 -910
  245. lionagi/core/tool/__init__.py +0 -3
  246. lionagi/core/tool/tool_manager.py +0 -280
  247. lionagi/integrations/bridge/pydantic_/base_model.py +0 -7
  248. lionagi/tests/test_core/test_base_branch.py +0 -427
  249. lionagi/tests/test_core/test_chat_flow.py +0 -63
  250. lionagi/tests/test_core/test_mail_manager.py +0 -75
  251. lionagi/tests/test_core/test_prompts.py +0 -51
  252. lionagi/tests/test_core/test_session.py +0 -254
  253. lionagi/tests/test_core/test_session_base_util.py +0 -312
  254. lionagi/tests/test_core/test_tool_manager.py +0 -95
  255. lionagi-0.0.312.dist-info/RECORD +0 -111
  256. /lionagi/core/{branch/base → _setting}/__init__.py +0 -0
  257. /lionagi/core/{flow → agent/eval}/__init__.py +0 -0
  258. /lionagi/core/{flow/base → agent/learn}/__init__.py +0 -0
  259. /lionagi/core/{prompt → agent/plan}/__init__.py +0 -0
  260. /lionagi/core/{tool/manual.py → agent/plan/plan.py} +0 -0
  261. /lionagi/{tests/test_integrations → core/director}/__init__.py +0 -0
  262. /lionagi/{tests/test_libs → core/engine}/__init__.py +0 -0
  263. /lionagi/{tests/test_libs/test_async.py → core/executor/__init__.py} +0 -0
  264. /lionagi/tests/{test_libs → libs}/test_api.py +0 -0
  265. /lionagi/tests/{test_libs → libs}/test_convert.py +0 -0
  266. /lionagi/tests/{test_libs → libs}/test_sys_util.py +0 -0
  267. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/WHEEL +0 -0
  268. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,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)