lionagi 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. lionagi/core/agent/base_agent.py +2 -3
  2. lionagi/core/branch/base.py +1 -1
  3. lionagi/core/branch/branch.py +2 -1
  4. lionagi/core/branch/flow_mixin.py +1 -1
  5. lionagi/core/branch/util.py +1 -1
  6. lionagi/core/execute/base_executor.py +1 -4
  7. lionagi/core/execute/branch_executor.py +66 -3
  8. lionagi/core/execute/instruction_map_executor.py +48 -0
  9. lionagi/core/execute/neo4j_executor.py +381 -0
  10. lionagi/core/execute/structure_executor.py +120 -4
  11. lionagi/core/flow/monoflow/ReAct.py +21 -19
  12. lionagi/core/flow/monoflow/chat_mixin.py +1 -1
  13. lionagi/core/flow/monoflow/followup.py +14 -13
  14. lionagi/core/flow/polyflow/__init__.py +1 -1
  15. lionagi/core/generic/component.py +197 -122
  16. lionagi/core/generic/condition.py +3 -1
  17. lionagi/core/generic/edge.py +77 -25
  18. lionagi/core/graph/graph.py +1 -1
  19. lionagi/core/mail/mail_manager.py +3 -2
  20. lionagi/core/session/session.py +1 -1
  21. lionagi/core/tool/tool_manager.py +10 -9
  22. lionagi/experimental/__init__.py +0 -0
  23. lionagi/experimental/directive/__init__.py +0 -0
  24. lionagi/experimental/directive/evaluator/__init__.py +0 -0
  25. lionagi/experimental/directive/evaluator/ast_evaluator.py +115 -0
  26. lionagi/experimental/directive/evaluator/base_evaluator.py +202 -0
  27. lionagi/experimental/directive/evaluator/sandbox_.py +14 -0
  28. lionagi/experimental/directive/evaluator/script_engine.py +83 -0
  29. lionagi/experimental/directive/parser/__init__.py +0 -0
  30. lionagi/experimental/directive/parser/base_parser.py +215 -0
  31. lionagi/experimental/directive/schema.py +36 -0
  32. lionagi/experimental/directive/template_/__init__.py +0 -0
  33. lionagi/experimental/directive/template_/base_template.py +63 -0
  34. lionagi/experimental/report/__init__.py +0 -0
  35. lionagi/experimental/report/form.py +64 -0
  36. lionagi/experimental/report/report.py +138 -0
  37. lionagi/experimental/report/util.py +47 -0
  38. lionagi/experimental/tool/__init__.py +0 -0
  39. lionagi/experimental/tool/function_calling.py +43 -0
  40. lionagi/experimental/tool/manual.py +66 -0
  41. lionagi/experimental/tool/schema.py +59 -0
  42. lionagi/experimental/tool/tool_manager.py +138 -0
  43. lionagi/experimental/tool/util.py +16 -0
  44. lionagi/experimental/validator/__init__.py +0 -0
  45. lionagi/experimental/validator/rule.py +139 -0
  46. lionagi/experimental/validator/validator.py +56 -0
  47. lionagi/experimental/work/__init__.py +10 -0
  48. lionagi/experimental/work/async_queue.py +54 -0
  49. lionagi/experimental/work/schema.py +73 -0
  50. lionagi/experimental/work/work_function.py +67 -0
  51. lionagi/experimental/work/worker.py +56 -0
  52. lionagi/experimental/work2/__init__.py +0 -0
  53. lionagi/experimental/work2/form.py +371 -0
  54. lionagi/experimental/work2/report.py +289 -0
  55. lionagi/experimental/work2/schema.py +30 -0
  56. lionagi/experimental/work2/tests.py +72 -0
  57. lionagi/experimental/work2/util.py +0 -0
  58. lionagi/experimental/work2/work.py +0 -0
  59. lionagi/experimental/work2/work_function.py +89 -0
  60. lionagi/experimental/work2/worker.py +12 -0
  61. lionagi/integrations/bridge/autogen_/__init__.py +0 -0
  62. lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
  63. lionagi/integrations/bridge/llamaindex_/get_index.py +294 -0
  64. lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
  65. lionagi/integrations/bridge/transformers_/__init__.py +0 -0
  66. lionagi/integrations/bridge/transformers_/install_.py +36 -0
  67. lionagi/integrations/config/oai_configs.py +1 -1
  68. lionagi/integrations/config/ollama_configs.py +1 -1
  69. lionagi/integrations/config/openrouter_configs.py +1 -1
  70. lionagi/integrations/storage/__init__.py +3 -0
  71. lionagi/integrations/storage/neo4j.py +673 -0
  72. lionagi/integrations/storage/storage_util.py +289 -0
  73. lionagi/integrations/storage/structure_excel.py +268 -0
  74. lionagi/integrations/storage/to_csv.py +63 -0
  75. lionagi/integrations/storage/to_excel.py +76 -0
  76. lionagi/libs/__init__.py +4 -0
  77. lionagi/libs/ln_knowledge_graph.py +405 -0
  78. lionagi/libs/ln_queue.py +101 -0
  79. lionagi/libs/ln_tokenizer.py +57 -0
  80. lionagi/libs/sys_util.py +1 -1
  81. lionagi/lions/__init__.py +0 -0
  82. lionagi/lions/coder/__init__.py +0 -0
  83. lionagi/lions/coder/add_feature.py +20 -0
  84. lionagi/lions/coder/base_prompts.py +22 -0
  85. lionagi/lions/coder/coder.py +121 -0
  86. lionagi/lions/coder/util.py +91 -0
  87. lionagi/lions/researcher/__init__.py +0 -0
  88. lionagi/lions/researcher/data_source/__init__.py +0 -0
  89. lionagi/lions/researcher/data_source/finhub_.py +191 -0
  90. lionagi/lions/researcher/data_source/google_.py +199 -0
  91. lionagi/lions/researcher/data_source/wiki_.py +96 -0
  92. lionagi/lions/researcher/data_source/yfinance_.py +21 -0
  93. lionagi/tests/libs/test_queue.py +67 -0
  94. lionagi/tests/test_core/generic/__init__.py +0 -0
  95. lionagi/tests/test_core/generic/test_component.py +89 -0
  96. lionagi/tests/test_core/test_branch.py +0 -1
  97. lionagi/version.py +1 -1
  98. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/METADATA +1 -1
  99. lionagi-0.1.2.dist-info/RECORD +206 -0
  100. lionagi-0.1.0.dist-info/RECORD +0 -136
  101. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/LICENSE +0 -0
  102. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/WHEEL +0 -0
  103. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,289 @@
1
+ import json
2
+ import inspect
3
+ import re
4
+
5
+ from lionagi.core import System, Instruction
6
+ from lionagi.core.tool import Tool
7
+ from lionagi.core.generic.action import ActionSelection
8
+ from lionagi.core.agent.base_agent import BaseAgent
9
+ from lionagi.core.generic.condition import Condition
10
+
11
+ from lionagi.core import func_to_tool
12
+
13
+
14
+ def output_node_list(structure):
15
+ """
16
+ Processes a structure object to extract and format all associated nodes into a summary list and detailed output dictionary.
17
+
18
+ This function traverses a structure, extracting key properties of nodes and organizing them by type into a dictionary for easy access and manipulation.
19
+
20
+ Args:
21
+ structure: The structure object containing nodes and potentially other nested structures.
22
+
23
+ Returns:
24
+ tuple: A tuple containing a summary list of all nodes and a dictionary categorizing nodes by type.
25
+ """
26
+ summary_list = []
27
+ output = {}
28
+
29
+ structure_output = {
30
+ "id": structure.id_,
31
+ "timestamp": structure.timestamp,
32
+ "type": structure.class_name,
33
+ }
34
+ summary_list.append(structure_output.copy())
35
+ structure_output["head_nodes"] = json.dumps([i.id_ for i in structure.get_heads()])
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
+ "id": node.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.func)
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, ActionSelection):
57
+ node_output["action"] = node.action
58
+ node_output["action_kwargs"] = json.dumps(node.action_kwargs)
59
+ elif isinstance(node, BaseAgent):
60
+ node_output["structure_id"] = node.structure.id_
61
+ node_output["output_parser"] = inspect.getsource(node.output_parser)
62
+ else:
63
+ raise ValueError("Not supported node type detected")
64
+ if node_output["type"] not in output:
65
+ output[node_output["type"]] = []
66
+ output[node_output["type"]].append(node_output)
67
+
68
+ return summary_list, output
69
+
70
+
71
+ def output_edge_list(structure):
72
+ """
73
+ Extracts and formats all edges from a given structure into a list and maps any associated condition classes.
74
+
75
+ 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.
76
+
77
+ Args:
78
+ structure: The structure object containing edges.
79
+
80
+ Returns:
81
+ tuple: A tuple containing a list of all edges with their details and a list of unique condition classes.
82
+ """
83
+ edge_list = []
84
+ edge_cls_dict = {}
85
+ for edge in structure.internal_edges.values():
86
+ edge_output = {
87
+ "id": edge.id_,
88
+ "timestamp": edge.timestamp,
89
+ "head": edge.head,
90
+ "tail": edge.tail,
91
+ "label": edge.label,
92
+ "bundle": str(edge.bundle),
93
+ }
94
+ if edge.condition:
95
+ cls_name = edge.condition.__class__.__qualname__
96
+ cond = json.dumps({"class": cls_name, "args": edge.condition.model_dump()})
97
+ cls = edge.string_condition()
98
+ if cls is not None and cls_name not in edge_cls_dict:
99
+ edge_cls_dict.update({cls_name: cls})
100
+ else:
101
+ cond = None
102
+ edge_output["condition"] = cond
103
+ edge_list.append(edge_output)
104
+
105
+ edge_cls_list = []
106
+ for key, value in edge_cls_dict.items():
107
+ edge_cls_list.append({"class_name": key, "class": value})
108
+
109
+ return edge_list, edge_cls_list
110
+
111
+
112
+ class ParseNode:
113
+ """
114
+ Provides static methods for converting code strings to functional definitions, classes, and for parsing various types of structured nodes based on dictionary definitions.
115
+
116
+ This utility class facilitates the dynamic execution of code and the instantiation of objects from serialized data.
117
+ """
118
+
119
+ @staticmethod
120
+ def convert_to_def(function_code):
121
+ """
122
+ Converts a string containing a function definition into a callable function object.
123
+
124
+ Args:
125
+ function_code (str): The string code of the function to convert.
126
+
127
+ Returns:
128
+ function: The converted function as a callable.
129
+
130
+ Raises:
131
+ ValueError: If the function code is invalid or the function name cannot be detected.
132
+ """
133
+ import re
134
+
135
+ match = re.search(r"def (\w+)\(", function_code)
136
+ if match:
137
+ class_name = match.group(1)
138
+ try:
139
+ exec(function_code, globals())
140
+ func = globals()[class_name]
141
+ return func
142
+ except Exception as e:
143
+ raise ValueError(f"Failed to convert str to def. Error: {e}")
144
+ else:
145
+ raise ValueError("Failed to detect function name")
146
+
147
+ @staticmethod
148
+ def convert_to_class(cls_code):
149
+ """
150
+ Converts a string containing a class definition into a class object.
151
+
152
+ Args:
153
+ cls_code (str): The string code of the class to convert.
154
+
155
+ Returns:
156
+ class: The converted class.
157
+
158
+ Raises:
159
+ ValueError: If the class code is invalid or the class name cannot be detected.
160
+ """
161
+ import re
162
+
163
+ match = re.search(r"class (\w+)\s*(?:\(([^)]+)\))?:", cls_code)
164
+ if match:
165
+ class_name = match.group(1)
166
+ try:
167
+ exec(cls_code, globals())
168
+ cls = globals()[class_name]
169
+ return cls
170
+ except Exception as e:
171
+ raise ValueError(f"Failed to convert str to class. Error: {e}")
172
+ else:
173
+ raise ValueError("Failed to detect class name")
174
+
175
+ @staticmethod
176
+ def parse_system(info_dict):
177
+ """
178
+ Parses dictionary information into a System node object.
179
+
180
+ Args:
181
+ info_dict (dict): A dictionary containing properties of a system node.
182
+
183
+ Returns:
184
+ System: An instantiated System node filled with properties from info_dict.
185
+ """
186
+ node = System(" ")
187
+ node.id_ = info_dict["id"]
188
+ node.timestamp = info_dict["timestamp"]
189
+ node.content = json.loads(info_dict["content"])
190
+ node.sender = info_dict["sender"]
191
+ node.recipient = info_dict["recipient"]
192
+ return node
193
+
194
+ @staticmethod
195
+ def parse_instruction(info_dict):
196
+ """
197
+ Parses dictionary information into an Instruction node object.
198
+
199
+ Args:
200
+ info_dict (dict): A dictionary containing properties of an instruction node.
201
+
202
+ Returns:
203
+ Instruction: An instantiated Instruction node filled with properties from info_dict.
204
+ """
205
+ node = Instruction(" ")
206
+ node.id_ = info_dict["id"]
207
+ node.timestamp = info_dict["timestamp"]
208
+ node.content = json.loads(info_dict["content"])
209
+ node.sender = info_dict["sender"]
210
+ node.recipient = info_dict["recipient"]
211
+ return node
212
+
213
+ @staticmethod
214
+ def parse_actionSelection(info_dict):
215
+ """
216
+ Parses dictionary information into an ActionSelection node object.
217
+
218
+ Args:
219
+ info_dict (dict): A dictionary containing properties of an action selection node.
220
+
221
+ Returns:
222
+ ActionSelection: An instantiated ActionSelection node filled with properties from info_dict.
223
+ """
224
+ node = ActionSelection()
225
+ node.id_ = info_dict["id"]
226
+ node.action = info_dict["action"]
227
+ if "action_kwargs" in info_dict:
228
+ if info_dict["action_kwargs"]:
229
+ node.action_kwargs = json.loads(info_dict["action_kwargs"])
230
+ elif "actionKwargs" in info_dict:
231
+ if info_dict["actionKwargs"]:
232
+ node.action_kwargs = json.loads(info_dict["actionKwargs"])
233
+ return node
234
+
235
+ @staticmethod
236
+ def parse_tool(info_dict):
237
+ """
238
+ Parses dictionary information into a Tool node object, converting associated function code into a callable.
239
+
240
+ Args:
241
+ info_dict (dict): A dictionary containing properties and function code for a tool node.
242
+
243
+ Returns:
244
+ Tool: An instantiated Tool node with the function converted from code.
245
+
246
+ Raises:
247
+ ValueError: If unsafe code is detected in the function definition.
248
+ """
249
+ func_code = info_dict["function"]
250
+ if "import os" in func_code or "__" in func_code or "import sys" in func_code:
251
+ raise ValueError(
252
+ "Unsafe code detected in Tool function. Please double check or implement explicitly"
253
+ )
254
+
255
+ func = ParseNode.convert_to_def(func_code)
256
+ tool = func_to_tool(func)
257
+ if func.__doc__:
258
+ if re.search(r":param \w+:", func.__doc__):
259
+ tool = func_to_tool(func, docstring_style="reST")
260
+
261
+ tool = tool[0]
262
+ tool.id_ = info_dict["id"]
263
+ tool.timestamp = info_dict["timestamp"]
264
+ return tool
265
+
266
+ @staticmethod
267
+ def parse_condition(condition, cls_code):
268
+ """
269
+ Parses a condition dictionary and corresponding class code into a class instance representing the condition.
270
+
271
+ Args:
272
+ condition (dict): A dictionary containing the serialized form of the condition's arguments.
273
+ cls_code (str): The class code to instantiate the condition class.
274
+
275
+ Returns:
276
+ An instance of the condition class filled with properties from the condition dictionary.
277
+
278
+ Raises:
279
+ ValueError: If the condition or class code is invalid.
280
+ """
281
+ args = condition["args"]
282
+ cls = ParseNode.convert_to_class(cls_code)
283
+
284
+ init_params = {}
285
+ for key in inspect.signature(cls.__init__).parameters.keys():
286
+ if key == "self":
287
+ continue
288
+ init_params[key] = args[key]
289
+ return cls(**init_params)
@@ -0,0 +1,268 @@
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.execute.structure_executor import StructureExecutor
7
+ from lionagi.core.agent.base_agent import BaseAgent
8
+ from lionagi.core.execute.base_executor import BaseExecutor
9
+ from lionagi.core.execute.instruction_map_executor import InstructionMapExecutor
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
+ structure: StructureExecutor = StructureExecutor()
56
+ default_agent_executable: BaseExecutor = InstructionMapExecutor()
57
+
58
+ def __init__(self, structure_name=None, structure_id=None, file_path="structure_storage"):
59
+ """
60
+ Initializes the StructureExcel class with specified parameters.
61
+
62
+ This method sets up the paths and reads the Excel file, preparing the internal dataframes used for
63
+ structure parsing.
64
+
65
+ Args:
66
+ structure_name (str, optional): The name of the structure to reload.
67
+ structure_id (str, optional): The unique identifier of the structure to reload.
68
+ file_path (str): The base path where the Excel files are stored.
69
+
70
+ Raises:
71
+ ValueError: If both structure_name and structure_id are provided but do not correspond to a valid file,
72
+ or if multiple or no files are found when one of the identifiers is provided.
73
+ """
74
+ self.file_path = file_path
75
+ if not structure_name and not structure_id:
76
+ raise ValueError("Please provide the structure name or id")
77
+ if structure_name and structure_id:
78
+ self.filename = f"{file_path}/{structure_name}_{structure_id}.xlsx"
79
+ self.file = pd.read_excel(self.filename, sheet_name=None)
80
+ elif structure_name and not structure_id:
81
+ dir_path = Path(file_path)
82
+ files = list(dir_path.glob(f"{structure_name}*.xlsx"))
83
+ filename = []
84
+ for file in files:
85
+ try:
86
+ name = file.name
87
+ name = name.rsplit("_", 1)[0]
88
+ if name == structure_name:
89
+ filename.append(file.name)
90
+ except:
91
+ continue
92
+ if len(filename) > 1:
93
+ raise ValueError(
94
+ f"Multiple files starting with the same structure name {structure_name} has found, please specify the structure id")
95
+ self.filename = f"{file_path}/{filename[0]}"
96
+ self.file = pd.read_excel(self.filename, sheet_name=None)
97
+ elif structure_id and not structure_name:
98
+ dir_path = Path(file_path)
99
+ files = list(dir_path.glob(f"*{structure_id}.xlsx"))
100
+ filename = [file.name for file in files]
101
+ if len(filename) > 1:
102
+ raise ValueError(
103
+ f"Multiple files with the same structure id {structure_id} has found, please double check the stored structure")
104
+ self.filename = f"{file_path}/{filename[0]}"
105
+ self.file = pd.read_excel(self.filename, sheet_name=None)
106
+ self.nodes = self.file["Nodes"]
107
+ self.edges = self.file["Edges"]
108
+
109
+ def get_heads(self):
110
+ """
111
+ Retrieves the list of head node identifiers from the loaded structure data.
112
+
113
+ This method parses the 'StructureExecutor' sheet in the loaded Excel file to extract the list of head nodes.
114
+
115
+ Returns:
116
+ list: A list of identifiers for the head nodes in the structure.
117
+ """
118
+ structure_df = self.file["StructureExecutor"]
119
+ head_list = json.loads(structure_df["head_nodes"].iloc[0])
120
+ return head_list
121
+
122
+ def _reload_info_dict(self, node_id):
123
+ """
124
+ Retrieves detailed information about a specific node from the Excel file based on its identifier.
125
+
126
+ This method looks up a node's information within the loaded Excel sheets and returns a dictionary
127
+ containing all the relevant details.
128
+
129
+ Args:
130
+ node_id (str): The identifier of the node to look up.
131
+
132
+ Returns:
133
+ dict: A dictionary containing the properties and values for the specified node.
134
+ """
135
+ node_type = self.nodes[self.nodes["id"] == node_id]["type"].iloc[0]
136
+ node_file = self.file[node_type]
137
+ row = node_file[node_file["id"] == node_id].iloc[0]
138
+ info_dict = row.to_dict()
139
+ return info_dict
140
+
141
+ def parse_agent(self, info_dict):
142
+ """
143
+ Parses an agent node from the structure using the agent's specific details provided in a dictionary.
144
+
145
+ This method creates an agent instance based on the information from the dictionary, which includes
146
+ dynamically loading the output parser code.
147
+
148
+ Args:
149
+ info_dict (dict): A dictionary containing details about the agent node, including its class, structure ID,
150
+ and output parser code.
151
+
152
+ Returns:
153
+ BaseAgent: An initialized agent object.
154
+ """
155
+ output_parser = ParseNode.convert_to_def(info_dict["output_parser"])
156
+
157
+ structure_excel = StructureExcel(structure_id=info_dict["structure_id"], file_path=self.file_path)
158
+ structure_excel.reload()
159
+ structure = structure_excel.structure
160
+ agent = BaseAgent(structure=structure, executable=self.default_agent_executable, output_parser=output_parser)
161
+ agent.id_ = info_dict["id"]
162
+ agent.timestamp = info_dict["timestamp"]
163
+ return agent
164
+
165
+ def parse_node(self, info_dict):
166
+ """
167
+ Parses a node from its dictionary representation into a specific node type like System, Instruction, etc.
168
+
169
+ This method determines the type of node from the info dictionary and uses the appropriate parsing method
170
+ to create an instance of that node type.
171
+
172
+ Args:
173
+ info_dict (dict): A dictionary containing node data, including the node type and associated properties.
174
+
175
+ Returns:
176
+ Node: An instance of the node corresponding to the type specified in the info_dict.
177
+ """
178
+ if info_dict["type"] == "System":
179
+ return ParseNode.parse_system(info_dict)
180
+ elif info_dict["type"] == "Instruction":
181
+ return ParseNode.parse_instruction(info_dict)
182
+ elif info_dict["type"] == "Tool":
183
+ return ParseNode.parse_tool(info_dict)
184
+ elif info_dict["type"] == "ActionSelection":
185
+ return ParseNode.parse_actionSelection(info_dict)
186
+ elif info_dict["type"] == "BaseAgent":
187
+ return self.parse_agent(info_dict)
188
+
189
+ def get_next(self, node_id):
190
+ """
191
+ Retrieves the list of identifiers for nodes that are directly connected via outgoing edges from the specified node.
192
+
193
+ This method searches the 'Edges' DataFrame for all entries where the specified node is a head and returns
194
+ a list of the tail node identifiers.
195
+
196
+ Args:
197
+ node_id (str): The identifier of the node whose successors are to be found.
198
+
199
+ Returns:
200
+ list[str]: A list of identifiers for the successor nodes.
201
+ """
202
+ return self.edges[self.edges["head"] == node_id]["tail"].to_list()
203
+
204
+ def relate(self, parent_node, node):
205
+ """
206
+ Establishes a relationship between two nodes in the structure based on the Excel data for edges.
207
+
208
+ This method looks up the edge details connecting the two nodes and applies any conditions associated
209
+ with the edge to the structure being rebuilt.
210
+
211
+ Args:
212
+ parent_node (Node): The parent node in the relationship.
213
+ node (Node): The child node in the relationship.
214
+
215
+ Raises:
216
+ ValueError: If there are issues with the edge data such as multiple undefined edges.
217
+ """
218
+ if not parent_node:
219
+ return
220
+ row = self.edges[(self.edges["head"] == parent_node.id_) & (self.edges["tail"] == node.id_)]
221
+ if len(row) > 1:
222
+ raise ValueError(
223
+ f"currently does not support handle multiple edges between two nodes, Error node: from {parent_node.id_} to {node.id_}")
224
+ if row['condition'].isna().any():
225
+ self.structure.relate_nodes(parent_node, node)
226
+ else:
227
+ cond = json.loads(row["condition"].iloc[0])
228
+ cond_cls = cond["class"]
229
+ cond_row = self.file["EdgesCondClass"][self.file["EdgesCondClass"]["class_name"] == cond_cls]
230
+ cond_code = cond_row["class"].iloc[0]
231
+ condition = ParseNode.parse_condition(cond, cond_code)
232
+ self.structure.relate_nodes(parent_node, node, condition=condition)
233
+
234
+ def parse(self, node_list, parent_node=None):
235
+ """
236
+ Recursively parses a list of nodes and establishes their interconnections based on the Excel data.
237
+
238
+ This method processes each node ID in the list, parsing individual nodes and relating them according
239
+ to their connections defined in the Excel file.
240
+
241
+ Args:
242
+ node_list (list[str]): A list of node identifiers to be parsed.
243
+ parent_node (Node, optional): The parent node to which the nodes in the list are connected.
244
+
245
+ Raises:
246
+ ValueError: If an error occurs during parsing or relating nodes.
247
+ """
248
+ for node_id in node_list:
249
+ info_dict = self._reload_info_dict(node_id)
250
+ node = self.parse_node(info_dict)
251
+
252
+ if node.id_ not in self.structure.internal_nodes:
253
+ self.structure.add_node(node)
254
+ self.relate(parent_node, node)
255
+
256
+ next_node_list = self.get_next(node_id)
257
+ self.parse(next_node_list, node)
258
+
259
+ def reload(self):
260
+ """
261
+ Reloads the entire structure from the Excel file.
262
+
263
+ This method initializes a new StructureExecutor and uses the Excel data to rebuild the entire structure,
264
+ starting from the head nodes and recursively parsing and connecting all nodes defined within.
265
+ """
266
+ self.structure = StructureExecutor()
267
+ heads = self.get_heads()
268
+ 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)