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.
- lionagi/core/agent/base_agent.py +2 -3
- lionagi/core/branch/base.py +1 -1
- lionagi/core/branch/branch.py +2 -1
- lionagi/core/branch/flow_mixin.py +1 -1
- lionagi/core/branch/util.py +1 -1
- lionagi/core/execute/base_executor.py +1 -4
- lionagi/core/execute/branch_executor.py +66 -3
- lionagi/core/execute/instruction_map_executor.py +48 -0
- lionagi/core/execute/neo4j_executor.py +381 -0
- lionagi/core/execute/structure_executor.py +120 -4
- lionagi/core/flow/monoflow/ReAct.py +21 -19
- lionagi/core/flow/monoflow/chat_mixin.py +1 -1
- lionagi/core/flow/monoflow/followup.py +14 -13
- lionagi/core/flow/polyflow/__init__.py +1 -1
- lionagi/core/generic/component.py +197 -122
- lionagi/core/generic/condition.py +3 -1
- lionagi/core/generic/edge.py +77 -25
- lionagi/core/graph/graph.py +1 -1
- lionagi/core/mail/mail_manager.py +3 -2
- lionagi/core/session/session.py +1 -1
- lionagi/core/tool/tool_manager.py +10 -9
- lionagi/experimental/__init__.py +0 -0
- lionagi/experimental/directive/__init__.py +0 -0
- lionagi/experimental/directive/evaluator/__init__.py +0 -0
- lionagi/experimental/directive/evaluator/ast_evaluator.py +115 -0
- lionagi/experimental/directive/evaluator/base_evaluator.py +202 -0
- lionagi/experimental/directive/evaluator/sandbox_.py +14 -0
- lionagi/experimental/directive/evaluator/script_engine.py +83 -0
- lionagi/experimental/directive/parser/__init__.py +0 -0
- lionagi/experimental/directive/parser/base_parser.py +215 -0
- lionagi/experimental/directive/schema.py +36 -0
- lionagi/experimental/directive/template_/__init__.py +0 -0
- lionagi/experimental/directive/template_/base_template.py +63 -0
- lionagi/experimental/report/__init__.py +0 -0
- lionagi/experimental/report/form.py +64 -0
- lionagi/experimental/report/report.py +138 -0
- lionagi/experimental/report/util.py +47 -0
- lionagi/experimental/tool/__init__.py +0 -0
- lionagi/experimental/tool/function_calling.py +43 -0
- lionagi/experimental/tool/manual.py +66 -0
- lionagi/experimental/tool/schema.py +59 -0
- lionagi/experimental/tool/tool_manager.py +138 -0
- lionagi/experimental/tool/util.py +16 -0
- lionagi/experimental/validator/__init__.py +0 -0
- lionagi/experimental/validator/rule.py +139 -0
- lionagi/experimental/validator/validator.py +56 -0
- lionagi/experimental/work/__init__.py +10 -0
- lionagi/experimental/work/async_queue.py +54 -0
- lionagi/experimental/work/schema.py +73 -0
- lionagi/experimental/work/work_function.py +67 -0
- lionagi/experimental/work/worker.py +56 -0
- lionagi/experimental/work2/__init__.py +0 -0
- lionagi/experimental/work2/form.py +371 -0
- lionagi/experimental/work2/report.py +289 -0
- lionagi/experimental/work2/schema.py +30 -0
- lionagi/experimental/work2/tests.py +72 -0
- lionagi/experimental/work2/util.py +0 -0
- lionagi/experimental/work2/work.py +0 -0
- lionagi/experimental/work2/work_function.py +89 -0
- lionagi/experimental/work2/worker.py +12 -0
- lionagi/integrations/bridge/autogen_/__init__.py +0 -0
- lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
- lionagi/integrations/bridge/llamaindex_/get_index.py +294 -0
- lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
- lionagi/integrations/bridge/transformers_/__init__.py +0 -0
- lionagi/integrations/bridge/transformers_/install_.py +36 -0
- lionagi/integrations/config/oai_configs.py +1 -1
- lionagi/integrations/config/ollama_configs.py +1 -1
- lionagi/integrations/config/openrouter_configs.py +1 -1
- lionagi/integrations/storage/__init__.py +3 -0
- lionagi/integrations/storage/neo4j.py +673 -0
- lionagi/integrations/storage/storage_util.py +289 -0
- lionagi/integrations/storage/structure_excel.py +268 -0
- lionagi/integrations/storage/to_csv.py +63 -0
- lionagi/integrations/storage/to_excel.py +76 -0
- lionagi/libs/__init__.py +4 -0
- lionagi/libs/ln_knowledge_graph.py +405 -0
- lionagi/libs/ln_queue.py +101 -0
- lionagi/libs/ln_tokenizer.py +57 -0
- lionagi/libs/sys_util.py +1 -1
- lionagi/lions/__init__.py +0 -0
- lionagi/lions/coder/__init__.py +0 -0
- lionagi/lions/coder/add_feature.py +20 -0
- lionagi/lions/coder/base_prompts.py +22 -0
- lionagi/lions/coder/coder.py +121 -0
- lionagi/lions/coder/util.py +91 -0
- lionagi/lions/researcher/__init__.py +0 -0
- lionagi/lions/researcher/data_source/__init__.py +0 -0
- lionagi/lions/researcher/data_source/finhub_.py +191 -0
- lionagi/lions/researcher/data_source/google_.py +199 -0
- lionagi/lions/researcher/data_source/wiki_.py +96 -0
- lionagi/lions/researcher/data_source/yfinance_.py +21 -0
- lionagi/tests/libs/test_queue.py +67 -0
- lionagi/tests/test_core/generic/__init__.py +0 -0
- lionagi/tests/test_core/generic/test_component.py +89 -0
- lionagi/tests/test_core/test_branch.py +0 -1
- lionagi/version.py +1 -1
- {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/METADATA +1 -1
- lionagi-0.1.2.dist-info/RECORD +206 -0
- lionagi-0.1.0.dist-info/RECORD +0 -136
- {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/LICENSE +0 -0
- {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/WHEEL +0 -0
- {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)
|