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.
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)