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,287 @@
1
+ import json
2
+ import inspect
3
+ import re
4
+
5
+ from lionagi.core.message import System, Instruction, RoledMessage
6
+ from lionagi.core.action import Tool, DirectiveSelection, func_to_tool
7
+ from lionagi.core.action import DirectiveSelection
8
+ from lionagi.core.agent.base_agent import BaseAgent
9
+ from lionagi.core.generic.edge_condition import EdgeCondition
10
+
11
+
12
+ def output_node_list(structure):
13
+ """
14
+ Processes a structure object to extract and format all associated nodes into a summary list and detailed output dictionary.
15
+
16
+ This function traverses a structure, extracting key properties of nodes and organizing them by type into a dictionary for easy access and manipulation.
17
+
18
+ Args:
19
+ structure: The structure object containing nodes and potentially other nested structures.
20
+
21
+ Returns:
22
+ tuple: A tuple containing a summary list of all nodes and a dictionary categorizing nodes by type.
23
+ """
24
+ summary_list = []
25
+ output = {}
26
+
27
+ structure_output = {
28
+ "ln_id": structure.ln_id,
29
+ "timestamp": structure.timestamp,
30
+ "type": structure.class_name,
31
+ }
32
+ summary_list.append(structure_output.copy())
33
+ structure_output["head_nodes"] = json.dumps(
34
+ [i.ln_id for i in structure.get_heads()]
35
+ )
36
+ # structure_output['nodes'] = json.dumps([i for i in structure.internal_nodes.keys()])
37
+ # structure_output['edges'] = json.dumps([i for i in structure.internal_edges.keys()])
38
+ output[structure_output["type"]] = [structure_output]
39
+ for node in structure.internal_nodes.values():
40
+ node_output = {
41
+ "ln_id": node.ln_id,
42
+ "timestamp": node.timestamp,
43
+ "type": node.class_name,
44
+ }
45
+ summary_list.append(node_output.copy())
46
+ if isinstance(node, System) or isinstance(node, Instruction):
47
+ node_output["content"] = json.dumps(node.content)
48
+ # node_output["sender"] = node.sender
49
+ # node_output["recipient"] = node.recipient
50
+ elif isinstance(node, Tool):
51
+ node_output["function"] = inspect.getsource(node.function)
52
+ # node_output['manual'] = node.manual
53
+ node_output["parser"] = (
54
+ inspect.getsource(node.parser) if node.parser else None
55
+ )
56
+ elif isinstance(node, DirectiveSelection):
57
+ node_output["directive"] = node.directive
58
+ node_output["directive_kwargs"] = json.dumps(node.directive_kwargs)
59
+ elif isinstance(node, BaseAgent):
60
+ node_output["structure_id"] = node.structure.ln_id
61
+ node_output["output_parser"] = (
62
+ inspect.getsource(node.output_parser) if node.output_parser else None
63
+ )
64
+ else:
65
+ raise ValueError("Not supported node type detected")
66
+ if node_output["type"] not in output:
67
+ output[node_output["type"]] = []
68
+ output[node_output["type"]].append(node_output)
69
+
70
+ return summary_list, output
71
+
72
+
73
+ def output_edge_list(structure):
74
+ """
75
+ Extracts and formats all edges from a given structure into a list and maps any associated condition classes.
76
+
77
+ This function collects edge data from a structure, including identifiers, timestamps, labels, and conditions. It also compiles any unique condition classes associated with these edges.
78
+
79
+ Args:
80
+ structure: The structure object containing edges.
81
+
82
+ Returns:
83
+ tuple: A tuple containing a list of all edges with their details and a list of unique condition classes.
84
+ """
85
+ edge_list = []
86
+ edge_cls_dict = {}
87
+ for edge in structure.internal_edges.values():
88
+ edge_output = {
89
+ "ln_id": edge.ln_id,
90
+ "timestamp": edge.timestamp,
91
+ "head": edge.head,
92
+ "tail": edge.tail,
93
+ "label": edge.label,
94
+ "bundle": str(edge.bundle),
95
+ }
96
+ if edge.condition:
97
+ cls_name = edge.condition.__class__.__qualname__
98
+ cond = json.dumps({"class": cls_name, "args": edge.condition.model_dump()})
99
+ cls = edge.string_condition()
100
+ if cls is not None and cls_name not in edge_cls_dict:
101
+ edge_cls_dict.update({cls_name: cls})
102
+ else:
103
+ cond = None
104
+ edge_output["condition"] = cond
105
+ edge_list.append(edge_output)
106
+
107
+ edge_cls_list = []
108
+ for key, value in edge_cls_dict.items():
109
+ edge_cls_list.append({"class_name": key, "class": value})
110
+
111
+ return edge_list, edge_cls_list
112
+
113
+
114
+ class ParseNode:
115
+ """
116
+ Provides static methods for converting code strings to functional definitions, classes, and for parsing various types of structured nodes based on dictionary definitions.
117
+
118
+ This utility class facilitates the dynamic execution of code and the instantiation of objects from serialized data.
119
+ """
120
+
121
+ @staticmethod
122
+ def convert_to_def(function_code):
123
+ """
124
+ Converts a string containing a function definition into a callable function object.
125
+
126
+ Args:
127
+ function_code (str): The string code of the function to convert.
128
+
129
+ Returns:
130
+ function: The converted function as a callable.
131
+
132
+ Raises:
133
+ ValueError: If the function code is invalid or the function name cannot be detected.
134
+ """
135
+ import re
136
+
137
+ match = re.search(r"def (\w+)\(", function_code)
138
+ if match:
139
+ class_name = match.group(1)
140
+ try:
141
+ exec(function_code, globals())
142
+ func = globals()[class_name]
143
+ return func
144
+ except Exception as e:
145
+ raise ValueError(f"Failed to convert str to def. Error: {e}")
146
+ else:
147
+ raise ValueError("Failed to detect function name")
148
+
149
+ @staticmethod
150
+ def convert_to_class(cls_code):
151
+ """
152
+ Converts a string containing a class definition into a class object.
153
+
154
+ Args:
155
+ cls_code (str): The string code of the class to convert.
156
+
157
+ Returns:
158
+ class: The converted class.
159
+
160
+ Raises:
161
+ ValueError: If the class code is invalid or the class name cannot be detected.
162
+ """
163
+ import re
164
+
165
+ match = re.search(r"class (\w+)\s*(?:\(([^)]+)\))?:", cls_code)
166
+ if match:
167
+ class_name = match.group(1)
168
+ try:
169
+ exec(cls_code, globals())
170
+ cls = globals()[class_name]
171
+ return cls
172
+ except Exception as e:
173
+ raise ValueError(f"Failed to convert str to class. Error: {e}")
174
+ else:
175
+ raise ValueError("Failed to detect class name")
176
+
177
+ @staticmethod
178
+ def parse_system(info_dict):
179
+ """
180
+ Parses dictionary information into a System node object.
181
+
182
+ Args:
183
+ info_dict (dict): A dictionary containing properties of a system node.
184
+
185
+ Returns:
186
+ System: An instantiated System node filled with properties from info_dict.
187
+ """
188
+ info_dict["system"] = json.loads(info_dict.pop("content"))["system_info"]
189
+ node = System.from_obj(info_dict)
190
+ return node
191
+
192
+ @staticmethod
193
+ def parse_instruction(info_dict):
194
+ """
195
+ Parses dictionary information into an Instruction node object.
196
+
197
+ Args:
198
+ info_dict (dict): A dictionary containing properties of an instruction node.
199
+
200
+ Returns:
201
+ Instruction: An instantiated Instruction node filled with properties from info_dict.
202
+ """
203
+ info_dict["instruction"] = json.loads(info_dict.pop("content"))["instruction"]
204
+ node = Instruction.from_obj(info_dict)
205
+ return node
206
+
207
+ @staticmethod
208
+ def parse_directiveSelection(info_dict):
209
+ """
210
+ Parses dictionary information into an DirectiveSelection node object.
211
+
212
+ Args:
213
+ info_dict (dict): A dictionary containing properties of an action selection node.
214
+
215
+ Returns:
216
+ ActionSelection: An instantiated ActionSelection node filled with properties from info_dict.
217
+ """
218
+ node = DirectiveSelection(ln_id=info_dict["ln_id"])
219
+ node.directive = info_dict["directive"]
220
+ if "directive_kwargs" in info_dict:
221
+ if info_dict["directive_kwargs"]:
222
+ node.directive_kwargs = json.loads(info_dict["directive_kwargs"])
223
+ elif "directiveKwargs" in info_dict:
224
+ if info_dict["directiveKwargs"]:
225
+ node.directive_kwargs = json.loads(info_dict["directiveKwargs"])
226
+ return node
227
+
228
+ @staticmethod
229
+ def parse_tool(info_dict):
230
+ """
231
+ Parses dictionary information into a Tool node object, converting associated function code into a callable.
232
+
233
+ Args:
234
+ info_dict (dict): A dictionary containing properties and function code for a tool node.
235
+
236
+ Returns:
237
+ Tool: An instantiated Tool node with the function converted from code.
238
+
239
+ Raises:
240
+ ValueError: If unsafe code is detected in the function definition.
241
+ """
242
+ func_code = info_dict["function"]
243
+ if "import os" in func_code or "__" in func_code or "import sys" in func_code:
244
+ raise ValueError(
245
+ "Unsafe code detected in Tool function. Please double check or implement explicitly"
246
+ )
247
+
248
+ func = ParseNode.convert_to_def(func_code)
249
+ tool = func_to_tool(
250
+ func, ln_id=info_dict["ln_id"], timestamp=info_dict["timestamp"]
251
+ )
252
+ if func.__doc__:
253
+ if re.search(r":param \w+:", func.__doc__):
254
+ tool = func_to_tool(
255
+ func,
256
+ docstring_style="reST",
257
+ ln_id=info_dict["ln_id"],
258
+ timestamp=info_dict["timestamp"],
259
+ )
260
+
261
+ tool = tool[0]
262
+ return tool
263
+
264
+ @staticmethod
265
+ def parse_condition(condition, cls_code):
266
+ """
267
+ Parses a condition dictionary and corresponding class code into a class instance representing the condition.
268
+
269
+ Args:
270
+ condition (dict): A dictionary containing the serialized form of the condition's arguments.
271
+ cls_code (str): The class code to instantiate the condition class.
272
+
273
+ Returns:
274
+ An instance of the condition class filled with properties from the condition dictionary.
275
+
276
+ Raises:
277
+ ValueError: If the condition or class code is invalid.
278
+ """
279
+ args = condition["args"]
280
+ cls = ParseNode.convert_to_class(cls_code)
281
+
282
+ init_params = {}
283
+ for key in inspect.signature(cls.__init__).parameters.keys():
284
+ if key == "self":
285
+ continue
286
+ init_params[key] = args[key]
287
+ return cls(**init_params)
@@ -0,0 +1,285 @@
1
+ import pandas as pd
2
+ import json
3
+ from pathlib import Path
4
+
5
+ from lionagi.integrations.storage.storage_util import ParseNode
6
+ from lionagi.core.executor.graph_executor import GraphExecutor
7
+ from lionagi.core.agent.base_agent import BaseAgent
8
+ from lionagi.core.executor.base_executor import BaseExecutor
9
+ from lionagi.core.engine.instruction_map_engine import InstructionMapEngine
10
+
11
+
12
+ def excel_reload(structure_name=None, structure_id=None, dir="structure_storage"):
13
+ """
14
+ Loads a structure from an Excel file into a StructureExecutor instance.
15
+
16
+ This function uses the StructureExcel class to handle the reloading process. It identifies the
17
+ Excel file based on the provided structure name or ID and reloads the structure from it.
18
+
19
+ Args:
20
+ structure_name (str, optional): The name of the structure to reload.
21
+ structure_id (str, optional): The unique identifier of the structure to reload.
22
+ dir (str): The directory path where the Excel files are stored.
23
+
24
+ Returns:
25
+ StructureExecutor: An instance of StructureExecutor containing the reloaded structure.
26
+
27
+ Raises:
28
+ ValueError: If neither structure_name nor structure_id is provided, or if multiple or no files
29
+ are found matching the criteria.
30
+ """
31
+ excel_structure = StructureExcel(structure_name, structure_id, dir)
32
+ excel_structure.reload()
33
+ return excel_structure.structure
34
+
35
+
36
+ class StructureExcel:
37
+ """
38
+ Manages the reloading of structures from Excel files.
39
+
40
+ This class handles the identification and parsing of structure data from an Excel workbook. It supports
41
+ loading from specifically named Excel files based on the structure name or ID.
42
+
43
+ Attributes:
44
+ structure (StructureExecutor): The loaded structure, ready for execution.
45
+ default_agent_executable (BaseExecutor): The default executor for agents within the structure.
46
+
47
+ Methods:
48
+ get_heads(): Retrieves the head nodes of the loaded structure.
49
+ parse_node(info_dict): Parses a node from the structure based on the provided dictionary.
50
+ get_next(node_id): Gets the next nodes connected by outgoing edges from a given node.
51
+ relate(parent_node, node): Relates two nodes within the structure based on the edge definitions.
52
+ parse(node_list, parent_node=None): Recursively parses nodes and their relationships from the structure.
53
+ reload(): Reloads the structure from the Excel file based on the initially provided parameters.
54
+ """
55
+
56
+ structure: GraphExecutor = GraphExecutor()
57
+ default_agent_executable: BaseExecutor = InstructionMapEngine()
58
+
59
+ def __init__(
60
+ self, structure_name=None, structure_id=None, file_path="structure_storage"
61
+ ):
62
+ """
63
+ Initializes the StructureExcel class with specified parameters.
64
+
65
+ This method sets up the paths and reads the Excel file, preparing the internal dataframes used for
66
+ structure parsing.
67
+
68
+ Args:
69
+ structure_name (str, optional): The name of the structure to reload.
70
+ structure_id (str, optional): The unique identifier of the structure to reload.
71
+ file_path (str): The base path where the Excel files are stored.
72
+
73
+ Raises:
74
+ ValueError: If both structure_name and structure_id are provided but do not correspond to a valid file,
75
+ or if multiple or no files are found when one of the identifiers is provided.
76
+ """
77
+ self.file_path = file_path
78
+ if not structure_name and not structure_id:
79
+ raise ValueError("Please provide the structure name or id")
80
+ if structure_name and structure_id:
81
+ self.filename = f"{file_path}/{structure_name}_{structure_id}.xlsx"
82
+ self.file = pd.read_excel(self.filename, sheet_name=None)
83
+ elif structure_name and not structure_id:
84
+ dir_path = Path(file_path)
85
+ files = list(dir_path.glob(f"{structure_name}*.xlsx"))
86
+ filename = []
87
+ for file in files:
88
+ try:
89
+ name = file.name
90
+ name = name.rsplit("_", 1)[0]
91
+ if name == structure_name:
92
+ filename.append(file.name)
93
+ except:
94
+ continue
95
+ if len(filename) > 1:
96
+ raise ValueError(
97
+ f"Multiple files starting with the same structure name {structure_name} has found, please specify the structure id"
98
+ )
99
+ self.filename = f"{file_path}/{filename[0]}"
100
+ self.file = pd.read_excel(self.filename, sheet_name=None)
101
+ elif structure_id and not structure_name:
102
+ dir_path = Path(file_path)
103
+ files = list(dir_path.glob(f"*{structure_id}.xlsx"))
104
+ filename = [file.name for file in files]
105
+ if len(filename) > 1:
106
+ raise ValueError(
107
+ f"Multiple files with the same structure id {structure_id} has found, please double check the stored structure"
108
+ )
109
+ self.filename = f"{file_path}/{filename[0]}"
110
+ self.file = pd.read_excel(self.filename, sheet_name=None)
111
+ self.nodes = self.file["Nodes"]
112
+ self.edges = self.file["Edges"]
113
+
114
+ def get_heads(self):
115
+ """
116
+ Retrieves the list of head node identifiers from the loaded structure data.
117
+
118
+ This method parses the 'StructureExecutor' sheet in the loaded Excel file to extract the list of head nodes.
119
+
120
+ Returns:
121
+ list: A list of identifiers for the head nodes in the structure.
122
+ """
123
+ structure_df = self.file["GraphExecutor"]
124
+ head_list = json.loads(structure_df["head_nodes"].iloc[0])
125
+ return head_list
126
+
127
+ def _reload_info_dict(self, node_id):
128
+ """
129
+ Retrieves detailed information about a specific node from the Excel file based on its identifier.
130
+
131
+ This method looks up a node's information within the loaded Excel sheets and returns a dictionary
132
+ containing all the relevant details.
133
+
134
+ Args:
135
+ node_id (str): The identifier of the node to look up.
136
+
137
+ Returns:
138
+ dict: A dictionary containing the properties and values for the specified node.
139
+ """
140
+ node_type = self.nodes[self.nodes["ln_id"] == node_id]["type"].iloc[0]
141
+ node_file = self.file[node_type]
142
+ row = node_file[node_file["ln_id"] == node_id].iloc[0]
143
+ info_dict = row.to_dict()
144
+ return info_dict
145
+
146
+ def parse_agent(self, info_dict):
147
+ """
148
+ Parses an agent node from the structure using the agent's specific details provided in a dictionary.
149
+
150
+ This method creates an agent instance based on the information from the dictionary, which includes
151
+ dynamically loading the output parser code.
152
+
153
+ Args:
154
+ info_dict (dict): A dictionary containing details about the agent node, including its class, structure ID,
155
+ and output parser code.
156
+
157
+ Returns:
158
+ BaseAgent: An initialized agent object.
159
+ """
160
+ output_parser = ParseNode.convert_to_def(info_dict["output_parser"])
161
+
162
+ structure_excel = StructureExcel(
163
+ structure_id=info_dict["structure_id"], file_path=self.file_path
164
+ )
165
+ structure_excel.reload()
166
+ structure = structure_excel.structure
167
+ agent = BaseAgent(
168
+ structure=structure,
169
+ executable=self.default_agent_executable,
170
+ output_parser=output_parser,
171
+ ln_id=info_dict["ln_id"],
172
+ timestamp=info_dict["timestamp"],
173
+ )
174
+ return agent
175
+
176
+ def parse_node(self, info_dict):
177
+ """
178
+ Parses a node from its dictionary representation into a specific node type like System, Instruction, etc.
179
+
180
+ This method determines the type of node from the info dictionary and uses the appropriate parsing method
181
+ to create an instance of that node type.
182
+
183
+ Args:
184
+ info_dict (dict): A dictionary containing node data, including the node type and associated properties.
185
+
186
+ Returns:
187
+ Node: An instance of the node corresponding to the type specified in the info_dict.
188
+ """
189
+ if info_dict["type"] == "System":
190
+ return ParseNode.parse_system(info_dict)
191
+ elif info_dict["type"] == "Instruction":
192
+ return ParseNode.parse_instruction(info_dict)
193
+ elif info_dict["type"] == "Tool":
194
+ return ParseNode.parse_tool(info_dict)
195
+ elif info_dict["type"] == "DirectiveSelection":
196
+ return ParseNode.parse_directiveSelection(info_dict)
197
+ elif info_dict["type"] == "BaseAgent":
198
+ return self.parse_agent(info_dict)
199
+
200
+ def get_next(self, node_id):
201
+ """
202
+ Retrieves the list of identifiers for nodes that are directly connected via outgoing edges from the specified node.
203
+
204
+ This method searches the 'Edges' DataFrame for all entries where the specified node is a head and returns
205
+ a list of the tail node identifiers.
206
+
207
+ Args:
208
+ node_id (str): The identifier of the node whose successors are to be found.
209
+
210
+ Returns:
211
+ list[str]: A list of identifiers for the successor nodes.
212
+ """
213
+ return self.edges[self.edges["head"] == node_id]["tail"].to_list()
214
+
215
+ def relate(self, parent_node, node):
216
+ """
217
+ Establishes a relationship between two nodes in the structure based on the Excel data for edges.
218
+
219
+ This method looks up the edge details connecting the two nodes and applies any conditions associated
220
+ with the edge to the structure being rebuilt.
221
+
222
+ Args:
223
+ parent_node (Node): The parent node in the relationship.
224
+ node (Node): The child node in the relationship.
225
+
226
+ Raises:
227
+ ValueError: If there are issues with the edge data such as multiple undefined edges.
228
+ """
229
+ if not parent_node:
230
+ return
231
+ row = self.edges[
232
+ (self.edges["head"] == parent_node.ln_id)
233
+ & (self.edges["tail"] == node.ln_id)
234
+ ]
235
+ if len(row) > 1:
236
+ raise ValueError(
237
+ f"currently does not support handle multiple edges between two nodes, Error node: from {parent_node.ln_id} to {node.ln_id}"
238
+ )
239
+ if row["condition"].isna().any():
240
+ self.structure.add_edge(parent_node, node)
241
+ else:
242
+ cond = json.loads(row["condition"].iloc[0])
243
+ cond_cls = cond["class"]
244
+ cond_row = self.file["EdgesCondClass"][
245
+ self.file["EdgesCondClass"]["class_name"] == cond_cls
246
+ ]
247
+ cond_code = cond_row["class"].iloc[0]
248
+ condition = ParseNode.parse_condition(cond, cond_code)
249
+ self.structure.add_edge(parent_node, node, condition=condition)
250
+
251
+ def parse(self, node_list, parent_node=None):
252
+ """
253
+ Recursively parses a list of nodes and establishes their interconnections based on the Excel data.
254
+
255
+ This method processes each node ID in the list, parsing individual nodes and relating them according
256
+ to their connections defined in the Excel file.
257
+
258
+ Args:
259
+ node_list (list[str]): A list of node identifiers to be parsed.
260
+ parent_node (Node, optional): The parent node to which the nodes in the list are connected.
261
+
262
+ Raises:
263
+ ValueError: If an error occurs during parsing or relating nodes.
264
+ """
265
+ for node_id in node_list:
266
+ info_dict = self._reload_info_dict(node_id)
267
+ node = self.parse_node(info_dict)
268
+
269
+ if node.ln_id not in self.structure.internal_nodes:
270
+ self.structure.add_node(node)
271
+ self.relate(parent_node, node)
272
+
273
+ next_node_list = self.get_next(node_id)
274
+ self.parse(next_node_list, node)
275
+
276
+ def reload(self):
277
+ """
278
+ Reloads the entire structure from the Excel file.
279
+
280
+ This method initializes a new StructureExecutor and uses the Excel data to rebuild the entire structure,
281
+ starting from the head nodes and recursively parsing and connecting all nodes defined within.
282
+ """
283
+ self.structure = GraphExecutor()
284
+ heads = self.get_heads()
285
+ self.parse(heads)
@@ -0,0 +1,63 @@
1
+ import zipfile
2
+ import pandas as pd
3
+ from pathlib import Path
4
+
5
+ from lionagi.integrations.storage.storage_util import output_node_list, output_edge_list
6
+
7
+
8
+ def _output_csv(
9
+ node_list, node_dict, edge_list, edge_cls_list, zipname="structure_storage"
10
+ ):
11
+ """
12
+ Writes provided node and edge data into multiple CSV files and compresses them into a ZIP archive.
13
+
14
+ This helper function takes lists and dictionaries of nodes and edges, converts them to pandas DataFrames,
15
+ and then writes each DataFrame to a CSV file stored inside a ZIP archive. This includes a separate CSV
16
+ for each type of node and edge, as well as edge conditions if they exist.
17
+
18
+ Args:
19
+ node_list (list): A list of dictionaries where each dictionary contains attributes of a single node.
20
+ node_dict (dict): A dictionary of lists where each key represents a node type and the value is a list of
21
+ node attributes for nodes of that type.
22
+ edge_list (list): A list of dictionaries where each dictionary contains attributes of a single edge.
23
+ edge_cls_list (list): A list of dictionaries where each dictionary contains attributes of edge conditions.
24
+ zipname (str): The base name for the output ZIP file that will store the CSV files.
25
+
26
+ Returns:
27
+ None: This function does not return a value but outputs a ZIP file containing the CSVs.
28
+ """
29
+ tables = {"Nodes": pd.DataFrame(node_list), "Edges": pd.DataFrame(edge_list)}
30
+ if edge_cls_list:
31
+ tables["EdgesCondClass"] = pd.DataFrame(edge_cls_list)
32
+ for i in node_dict:
33
+ tables[i] = pd.DataFrame(node_dict[i])
34
+
35
+ zipname = zipname + ".zip"
36
+
37
+ with zipfile.ZipFile(zipname, "w") as zf:
38
+ for i in tables:
39
+ filename = i + ".csv"
40
+ with zf.open(filename, "w") as file:
41
+ tables[i].to_csv(file, index=False)
42
+
43
+
44
+ def to_csv(structure, filename="structure_storage"):
45
+ """
46
+ Converts a structure into a series of CSV files and stores them in a compressed ZIP archive.
47
+
48
+ This function processes a given structure to extract detailed node and edge information,
49
+ including conditions if applicable. These details are then saved into separate CSV files
50
+ for nodes, edges, and any edge conditions, which are subsequently bundled into a ZIP file.
51
+
52
+ Args:
53
+ structure: An object representing the structure to be serialized. This structure should
54
+ have methods to return lists of nodes and edges.
55
+ filename (str): The base name of the output ZIP file that will store the CSV files.
56
+
57
+ Returns:
58
+ None: This function does not return a value but outputs a ZIP file containing CSVs.
59
+ """
60
+ node_list, node_dict = output_node_list(structure)
61
+ edge_list, edge_cls_list = output_edge_list(structure)
62
+
63
+ _output_csv(node_list, node_dict, edge_list, edge_cls_list, filename)