lionagi 0.1.1__py3-none-any.whl → 0.2.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- lionagi/__init__.py +60 -5
- lionagi/core/__init__.py +0 -25
- lionagi/core/_setting/_setting.py +59 -0
- lionagi/core/action/__init__.py +14 -0
- lionagi/core/action/function_calling.py +136 -0
- lionagi/core/action/manual.py +1 -0
- lionagi/core/action/node.py +109 -0
- lionagi/core/action/tool.py +114 -0
- lionagi/core/action/tool_manager.py +356 -0
- lionagi/core/agent/base_agent.py +27 -13
- lionagi/core/agent/eval/evaluator.py +1 -0
- lionagi/core/agent/eval/vote.py +40 -0
- lionagi/core/agent/learn/learner.py +59 -0
- lionagi/core/agent/plan/unit_template.py +1 -0
- lionagi/core/collections/__init__.py +17 -0
- lionagi/core/{generic/data_logger.py → collections/_logger.py} +69 -55
- lionagi/core/collections/abc/__init__.py +53 -0
- lionagi/core/collections/abc/component.py +615 -0
- lionagi/core/collections/abc/concepts.py +297 -0
- lionagi/core/collections/abc/exceptions.py +150 -0
- lionagi/core/collections/abc/util.py +45 -0
- lionagi/core/collections/exchange.py +161 -0
- lionagi/core/collections/flow.py +426 -0
- lionagi/core/collections/model.py +419 -0
- lionagi/core/collections/pile.py +913 -0
- lionagi/core/collections/progression.py +236 -0
- lionagi/core/collections/util.py +64 -0
- lionagi/core/director/direct.py +314 -0
- lionagi/core/director/director.py +2 -0
- lionagi/core/{execute/branch_executor.py → engine/branch_engine.py} +134 -97
- lionagi/core/{execute/instruction_map_executor.py → engine/instruction_map_engine.py} +80 -55
- lionagi/{experimental/directive/evaluator → core/engine}/script_engine.py +17 -1
- lionagi/core/executor/base_executor.py +90 -0
- lionagi/core/{execute/structure_executor.py → executor/graph_executor.py} +83 -67
- lionagi/core/{execute → executor}/neo4j_executor.py +70 -67
- lionagi/core/generic/__init__.py +3 -33
- lionagi/core/generic/edge.py +42 -92
- lionagi/core/generic/edge_condition.py +16 -0
- lionagi/core/generic/graph.py +236 -0
- lionagi/core/generic/hyperedge.py +1 -0
- lionagi/core/generic/node.py +156 -221
- lionagi/core/generic/tree.py +48 -0
- lionagi/core/generic/tree_node.py +79 -0
- lionagi/core/mail/__init__.py +12 -0
- lionagi/core/mail/mail.py +25 -0
- lionagi/core/mail/mail_manager.py +139 -58
- lionagi/core/mail/package.py +45 -0
- lionagi/core/mail/start_mail.py +36 -0
- lionagi/core/message/__init__.py +19 -0
- lionagi/core/message/action_request.py +133 -0
- lionagi/core/message/action_response.py +135 -0
- lionagi/core/message/assistant_response.py +95 -0
- lionagi/core/message/instruction.py +234 -0
- lionagi/core/message/message.py +101 -0
- lionagi/core/message/system.py +86 -0
- lionagi/core/message/util.py +283 -0
- lionagi/core/report/__init__.py +4 -0
- lionagi/core/report/base.py +217 -0
- lionagi/core/report/form.py +231 -0
- lionagi/core/report/report.py +166 -0
- lionagi/core/report/util.py +28 -0
- lionagi/core/rule/_default.py +16 -0
- lionagi/core/rule/action.py +99 -0
- lionagi/core/rule/base.py +238 -0
- lionagi/core/rule/boolean.py +56 -0
- lionagi/core/rule/choice.py +47 -0
- lionagi/core/rule/mapping.py +96 -0
- lionagi/core/rule/number.py +71 -0
- lionagi/core/rule/rulebook.py +109 -0
- lionagi/core/rule/string.py +52 -0
- lionagi/core/rule/util.py +35 -0
- lionagi/core/session/branch.py +431 -0
- lionagi/core/session/directive_mixin.py +287 -0
- lionagi/core/session/session.py +229 -903
- lionagi/core/structure/__init__.py +1 -0
- lionagi/core/structure/chain.py +1 -0
- lionagi/core/structure/forest.py +1 -0
- lionagi/core/structure/graph.py +1 -0
- lionagi/core/structure/tree.py +1 -0
- lionagi/core/unit/__init__.py +5 -0
- lionagi/core/unit/parallel_unit.py +245 -0
- lionagi/core/unit/template/action.py +81 -0
- lionagi/core/unit/template/base.py +51 -0
- lionagi/core/unit/template/plan.py +84 -0
- lionagi/core/unit/template/predict.py +109 -0
- lionagi/core/unit/template/score.py +124 -0
- lionagi/core/unit/template/select.py +104 -0
- lionagi/core/unit/unit.py +362 -0
- lionagi/core/unit/unit_form.py +305 -0
- lionagi/core/unit/unit_mixin.py +1168 -0
- lionagi/core/unit/util.py +71 -0
- lionagi/core/validator/validator.py +364 -0
- lionagi/core/work/work.py +74 -0
- lionagi/core/work/work_function.py +92 -0
- lionagi/core/work/work_queue.py +81 -0
- lionagi/core/work/worker.py +195 -0
- lionagi/core/work/worklog.py +124 -0
- lionagi/experimental/compressor/base.py +46 -0
- lionagi/experimental/compressor/llm_compressor.py +247 -0
- lionagi/experimental/compressor/llm_summarizer.py +61 -0
- lionagi/experimental/compressor/util.py +70 -0
- lionagi/experimental/directive/__init__.py +19 -0
- lionagi/experimental/directive/parser/base_parser.py +69 -2
- lionagi/experimental/directive/{template_ → template}/base_template.py +17 -1
- lionagi/{libs/ln_tokenizer.py → experimental/directive/tokenizer.py} +16 -0
- lionagi/experimental/{directive/evaluator → evaluator}/ast_evaluator.py +16 -0
- lionagi/experimental/{directive/evaluator → evaluator}/base_evaluator.py +16 -0
- lionagi/experimental/knowledge/__init__.py +0 -0
- lionagi/experimental/knowledge/base.py +10 -0
- lionagi/experimental/knowledge/graph.py +0 -0
- lionagi/experimental/memory/__init__.py +0 -0
- lionagi/experimental/strategies/__init__.py +0 -0
- lionagi/experimental/strategies/base.py +1 -0
- lionagi/integrations/bridge/langchain_/documents.py +4 -0
- lionagi/integrations/bridge/llamaindex_/index.py +30 -0
- lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
- lionagi/integrations/chunker/chunk.py +161 -24
- lionagi/integrations/config/oai_configs.py +34 -3
- lionagi/integrations/config/openrouter_configs.py +14 -2
- lionagi/integrations/loader/load.py +122 -21
- lionagi/integrations/loader/load_util.py +6 -77
- lionagi/integrations/provider/_mapping.py +46 -0
- lionagi/integrations/provider/litellm.py +2 -1
- lionagi/integrations/provider/mlx_service.py +16 -9
- lionagi/integrations/provider/oai.py +91 -4
- lionagi/integrations/provider/ollama.py +6 -5
- lionagi/integrations/provider/openrouter.py +115 -8
- lionagi/integrations/provider/services.py +2 -2
- lionagi/integrations/provider/transformers.py +18 -22
- lionagi/integrations/storage/__init__.py +3 -3
- lionagi/integrations/storage/neo4j.py +52 -60
- lionagi/integrations/storage/storage_util.py +45 -47
- lionagi/integrations/storage/structure_excel.py +285 -0
- lionagi/integrations/storage/to_excel.py +23 -7
- lionagi/libs/__init__.py +26 -1
- lionagi/libs/ln_api.py +75 -20
- lionagi/libs/ln_context.py +37 -0
- lionagi/libs/ln_convert.py +21 -9
- lionagi/libs/ln_func_call.py +69 -28
- lionagi/libs/ln_image.py +107 -0
- lionagi/libs/ln_nested.py +26 -11
- lionagi/libs/ln_parse.py +82 -23
- lionagi/libs/ln_queue.py +16 -0
- lionagi/libs/ln_tokenize.py +164 -0
- lionagi/libs/ln_validate.py +16 -0
- lionagi/libs/special_tokens.py +172 -0
- lionagi/libs/sys_util.py +95 -24
- lionagi/lions/coder/code_form.py +13 -0
- lionagi/lions/coder/coder.py +50 -3
- lionagi/lions/coder/util.py +30 -25
- lionagi/tests/libs/test_func_call.py +23 -21
- lionagi/tests/libs/test_nested.py +36 -21
- lionagi/tests/libs/test_parse.py +1 -1
- lionagi/tests/test_core/collections/__init__.py +0 -0
- lionagi/tests/test_core/collections/test_component.py +206 -0
- lionagi/tests/test_core/collections/test_exchange.py +138 -0
- lionagi/tests/test_core/collections/test_flow.py +145 -0
- lionagi/tests/test_core/collections/test_pile.py +171 -0
- lionagi/tests/test_core/collections/test_progression.py +129 -0
- lionagi/tests/test_core/generic/__init__.py +0 -0
- lionagi/tests/test_core/generic/test_edge.py +67 -0
- lionagi/tests/test_core/generic/test_graph.py +96 -0
- lionagi/tests/test_core/generic/test_node.py +106 -0
- lionagi/tests/test_core/generic/test_tree_node.py +73 -0
- lionagi/tests/test_core/test_branch.py +115 -294
- lionagi/tests/test_core/test_form.py +46 -0
- lionagi/tests/test_core/test_report.py +105 -0
- lionagi/tests/test_core/test_validator.py +111 -0
- lionagi/version.py +1 -1
- lionagi-0.2.0.dist-info/LICENSE +202 -0
- lionagi-0.2.0.dist-info/METADATA +272 -0
- lionagi-0.2.0.dist-info/RECORD +240 -0
- lionagi/core/branch/base.py +0 -653
- lionagi/core/branch/branch.py +0 -474
- lionagi/core/branch/flow_mixin.py +0 -96
- lionagi/core/branch/util.py +0 -323
- lionagi/core/direct/__init__.py +0 -19
- lionagi/core/direct/cot.py +0 -123
- lionagi/core/direct/plan.py +0 -164
- lionagi/core/direct/predict.py +0 -166
- lionagi/core/direct/react.py +0 -171
- lionagi/core/direct/score.py +0 -279
- lionagi/core/direct/select.py +0 -170
- lionagi/core/direct/sentiment.py +0 -1
- lionagi/core/direct/utils.py +0 -110
- lionagi/core/direct/vote.py +0 -64
- lionagi/core/execute/base_executor.py +0 -47
- lionagi/core/flow/baseflow.py +0 -23
- lionagi/core/flow/monoflow/ReAct.py +0 -238
- lionagi/core/flow/monoflow/__init__.py +0 -9
- lionagi/core/flow/monoflow/chat.py +0 -95
- lionagi/core/flow/monoflow/chat_mixin.py +0 -253
- lionagi/core/flow/monoflow/followup.py +0 -213
- lionagi/core/flow/polyflow/__init__.py +0 -1
- lionagi/core/flow/polyflow/chat.py +0 -251
- lionagi/core/form/action_form.py +0 -26
- lionagi/core/form/field_validator.py +0 -287
- lionagi/core/form/form.py +0 -302
- lionagi/core/form/mixin.py +0 -214
- lionagi/core/form/scored_form.py +0 -13
- lionagi/core/generic/action.py +0 -26
- lionagi/core/generic/component.py +0 -455
- lionagi/core/generic/condition.py +0 -44
- lionagi/core/generic/mail.py +0 -90
- lionagi/core/generic/mailbox.py +0 -36
- lionagi/core/generic/relation.py +0 -70
- lionagi/core/generic/signal.py +0 -22
- lionagi/core/generic/structure.py +0 -362
- lionagi/core/generic/transfer.py +0 -20
- lionagi/core/generic/work.py +0 -40
- lionagi/core/graph/graph.py +0 -126
- lionagi/core/graph/tree.py +0 -190
- lionagi/core/mail/schema.py +0 -63
- lionagi/core/messages/schema.py +0 -325
- lionagi/core/tool/__init__.py +0 -5
- lionagi/core/tool/tool.py +0 -28
- lionagi/core/tool/tool_manager.py +0 -282
- lionagi/experimental/tool/function_calling.py +0 -43
- lionagi/experimental/tool/manual.py +0 -66
- lionagi/experimental/tool/schema.py +0 -59
- lionagi/experimental/tool/tool_manager.py +0 -138
- lionagi/experimental/tool/util.py +0 -16
- lionagi/experimental/work/_logger.py +0 -25
- lionagi/experimental/work/schema.py +0 -30
- lionagi/experimental/work/tests.py +0 -72
- lionagi/experimental/work/work_function.py +0 -89
- lionagi/experimental/work/worker.py +0 -12
- lionagi/integrations/bridge/llamaindex_/get_index.py +0 -294
- lionagi/tests/test_core/test_base_branch.py +0 -426
- lionagi/tests/test_core/test_chat_flow.py +0 -63
- lionagi/tests/test_core/test_mail_manager.py +0 -75
- lionagi/tests/test_core/test_prompts.py +0 -51
- lionagi/tests/test_core/test_session.py +0 -254
- lionagi/tests/test_core/test_session_base_util.py +0 -313
- lionagi/tests/test_core/test_tool_manager.py +0 -95
- lionagi-0.1.1.dist-info/LICENSE +0 -9
- lionagi-0.1.1.dist-info/METADATA +0 -174
- lionagi-0.1.1.dist-info/RECORD +0 -190
- /lionagi/core/{branch → _setting}/__init__.py +0 -0
- /lionagi/core/{execute → agent/eval}/__init__.py +0 -0
- /lionagi/core/{flow → agent/learn}/__init__.py +0 -0
- /lionagi/core/{form → agent/plan}/__init__.py +0 -0
- /lionagi/core/{branch/executable_branch.py → agent/plan/plan.py} +0 -0
- /lionagi/core/{graph → director}/__init__.py +0 -0
- /lionagi/core/{messages → engine}/__init__.py +0 -0
- /lionagi/{experimental/directive/evaluator → core/engine}/sandbox_.py +0 -0
- /lionagi/{experimental/directive/evaluator → core/executor}/__init__.py +0 -0
- /lionagi/{experimental/directive/template_ → core/rule}/__init__.py +0 -0
- /lionagi/{experimental/tool → core/unit/template}/__init__.py +0 -0
- /lionagi/{experimental/work → core/validator}/__init__.py +0 -0
- /lionagi/core/{flow/mono_chat_mixin.py → work/__init__.py} +0 -0
- /lionagi/experimental/{work/exchange.py → compressor/__init__.py} +0 -0
- /lionagi/experimental/{work/util.py → directive/template/__init__.py} +0 -0
- /lionagi/experimental/directive/{schema.py → template/schema.py} +0 -0
- /lionagi/{tests/libs/test_async.py → experimental/evaluator/__init__.py} +0 -0
- {lionagi-0.1.1.dist-info → lionagi-0.2.0.dist-info}/WHEEL +0 -0
- {lionagi-0.1.1.dist-info → lionagi-0.2.0.dist-info}/top_level.txt +0 -0
@@ -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)
|
@@ -5,7 +5,12 @@ from lionagi.integrations.storage.storage_util import output_node_list, output_e
|
|
5
5
|
|
6
6
|
|
7
7
|
def _output_excel(
|
8
|
-
node_list,
|
8
|
+
node_list,
|
9
|
+
node_dict,
|
10
|
+
edge_list,
|
11
|
+
edge_cls_list,
|
12
|
+
structure_name,
|
13
|
+
dir="structure_storage",
|
9
14
|
):
|
10
15
|
"""
|
11
16
|
Writes provided node and edge data into multiple sheets of a single Excel workbook.
|
@@ -20,7 +25,7 @@ def _output_excel(
|
|
20
25
|
node attributes for nodes of that type.
|
21
26
|
edge_list (list): A list of dictionaries where each dictionary contains attributes of a single edge.
|
22
27
|
edge_cls_list (list): A list of dictionaries where each dictionary contains attributes of edge conditions.
|
23
|
-
|
28
|
+
structure_name (str): The base name for the output Excel file. The '.xlsx' extension will be added
|
24
29
|
automatically if not included.
|
25
30
|
|
26
31
|
Returns:
|
@@ -31,20 +36,31 @@ def _output_excel(
|
|
31
36
|
"""
|
32
37
|
SysUtil.check_import("openpyxl")
|
33
38
|
|
39
|
+
structure_id = ""
|
40
|
+
|
34
41
|
tables = {"Nodes": pd.DataFrame(node_list), "Edges": pd.DataFrame(edge_list)}
|
35
42
|
if edge_cls_list:
|
36
43
|
tables["EdgesCondClass"] = pd.DataFrame(edge_cls_list)
|
37
44
|
for i in node_dict:
|
45
|
+
if i == "GraphExecutor":
|
46
|
+
structure_node = node_dict[i][0]
|
47
|
+
structure_node["name"] = structure_name
|
48
|
+
structure_id = structure_node["ln_id"]
|
38
49
|
tables[i] = pd.DataFrame(node_dict[i])
|
39
50
|
|
40
|
-
|
51
|
+
import os
|
52
|
+
|
53
|
+
filepath = os.path.join(dir, f"{structure_name}_{structure_id}.xlsx")
|
54
|
+
|
55
|
+
if not os.path.exists(dir):
|
56
|
+
os.makedirs(dir)
|
41
57
|
|
42
|
-
with pd.ExcelWriter(
|
58
|
+
with pd.ExcelWriter(filepath) as writer:
|
43
59
|
for i in tables:
|
44
60
|
tables[i].to_excel(writer, sheet_name=i, index=False)
|
45
61
|
|
46
62
|
|
47
|
-
def to_excel(structure,
|
63
|
+
def to_excel(structure, structure_name, dir="structure_storage"):
|
48
64
|
"""
|
49
65
|
Converts a structure into a series of Excel sheets within a single workbook.
|
50
66
|
|
@@ -55,7 +71,7 @@ def to_excel(structure, filename="structure_storage"):
|
|
55
71
|
Args:
|
56
72
|
structure: An object representing the structure to be serialized. This should have methods
|
57
73
|
to return lists of nodes and edges suitable for output.
|
58
|
-
|
74
|
+
structure_name (str): The base name of the output Excel file. The '.xlsx' extension will be added
|
59
75
|
automatically if not included.
|
60
76
|
|
61
77
|
Returns:
|
@@ -64,4 +80,4 @@ def to_excel(structure, filename="structure_storage"):
|
|
64
80
|
node_list, node_dict = output_node_list(structure)
|
65
81
|
edge_list, edge_cls_list = output_edge_list(structure)
|
66
82
|
|
67
|
-
_output_excel(node_list, node_dict, edge_list, edge_cls_list,
|
83
|
+
_output_excel(node_list, node_dict, edge_list, edge_cls_list, structure_name, dir)
|
lionagi/libs/__init__.py
CHANGED
@@ -2,10 +2,19 @@ from lionagi.libs.sys_util import SysUtil
|
|
2
2
|
from lionagi.libs.ln_async import AsyncUtil
|
3
3
|
|
4
4
|
import lionagi.libs.ln_convert as convert
|
5
|
+
from lionagi.libs.ln_convert import (
|
6
|
+
to_str,
|
7
|
+
to_list,
|
8
|
+
to_dict,
|
9
|
+
to_df,
|
10
|
+
to_readable_dict,
|
11
|
+
to_num,
|
12
|
+
)
|
5
13
|
import lionagi.libs.ln_dataframe as dataframe
|
6
14
|
import lionagi.libs.ln_func_call as func_call
|
7
|
-
from lionagi.libs.ln_func_call import CallDecorator
|
15
|
+
from lionagi.libs.ln_func_call import lcall, CallDecorator
|
8
16
|
import lionagi.libs.ln_nested as nested
|
17
|
+
from lionagi.libs.ln_nested import nget, nset, ninsert
|
9
18
|
from lionagi.libs.ln_parse import ParseUtil, StringMatch
|
10
19
|
|
11
20
|
from lionagi.libs.ln_api import (
|
@@ -16,6 +25,10 @@ from lionagi.libs.ln_api import (
|
|
16
25
|
PayloadPackage,
|
17
26
|
)
|
18
27
|
|
28
|
+
from lionagi.libs.ln_image import ImageUtil
|
29
|
+
from lionagi.libs.ln_validate import validation_funcs
|
30
|
+
|
31
|
+
|
19
32
|
__all__ = [
|
20
33
|
"SysUtil",
|
21
34
|
"convert",
|
@@ -31,4 +44,16 @@ __all__ = [
|
|
31
44
|
"StatusTracker",
|
32
45
|
"SimpleRateLimiter",
|
33
46
|
"CallDecorator",
|
47
|
+
"validation_funcs",
|
48
|
+
"ImageUtil",
|
49
|
+
"to_str",
|
50
|
+
"to_list",
|
51
|
+
"to_dict",
|
52
|
+
"to_df",
|
53
|
+
"lcall",
|
54
|
+
"to_readable_dict",
|
55
|
+
"to_num",
|
56
|
+
"nget",
|
57
|
+
"nset",
|
58
|
+
"ninsert",
|
34
59
|
]
|
lionagi/libs/ln_api.py
CHANGED
@@ -1,8 +1,25 @@
|
|
1
|
+
"""
|
2
|
+
Copyright 2024 HaiyangLi
|
3
|
+
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
you may not use this file except in compliance with the License.
|
6
|
+
You may obtain a copy of the License at
|
7
|
+
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
See the License for the specific language governing permissions and
|
14
|
+
limitations under the License.
|
15
|
+
"""
|
16
|
+
|
1
17
|
from collections.abc import Sequence, Mapping
|
2
18
|
|
3
19
|
from abc import ABC
|
4
20
|
from dataclasses import dataclass
|
5
21
|
|
22
|
+
import contextlib
|
6
23
|
import logging
|
7
24
|
import re
|
8
25
|
import asyncio
|
@@ -11,9 +28,9 @@ import aiohttp
|
|
11
28
|
from typing import Any, NoReturn, Type, Callable
|
12
29
|
|
13
30
|
from lionagi.libs.ln_async import AsyncUtil
|
14
|
-
|
15
|
-
import lionagi.libs.ln_nested as nested
|
31
|
+
from lionagi.libs.ln_convert import to_dict, strip_lower, to_str
|
16
32
|
import lionagi.libs.ln_func_call as func_call
|
33
|
+
from lionagi.libs.ln_nested import nget
|
17
34
|
|
18
35
|
|
19
36
|
class APIUtil:
|
@@ -175,7 +192,7 @@ class APIUtil:
|
|
175
192
|
"""
|
176
193
|
import hashlib
|
177
194
|
|
178
|
-
param_str =
|
195
|
+
param_str = to_str(params, sort_keys=True) if params else ""
|
179
196
|
return hashlib.md5((url + param_str).encode("utf-8")).hexdigest()
|
180
197
|
|
181
198
|
@staticmethod
|
@@ -355,25 +372,48 @@ class APIUtil:
|
|
355
372
|
# Expected token calculation for the given payload and endpoint.
|
356
373
|
"""
|
357
374
|
import tiktoken
|
375
|
+
from .ln_image import ImageUtil
|
358
376
|
|
377
|
+
token_encoding_name = token_encoding_name or "cl100k_base"
|
359
378
|
encoding = tiktoken.get_encoding(token_encoding_name)
|
360
379
|
if api_endpoint.endswith("completions"):
|
361
380
|
max_tokens = payload.get("max_tokens", 15)
|
362
381
|
n = payload.get("n", 1)
|
363
382
|
completion_tokens = n * max_tokens
|
364
|
-
|
365
|
-
# chat completions
|
366
383
|
if api_endpoint.startswith("chat/"):
|
367
384
|
num_tokens = 0
|
385
|
+
|
368
386
|
for message in payload["messages"]:
|
369
387
|
num_tokens += 4 # every message follows <im_start>{role/name}\n{content}<im_end>\n
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
388
|
+
|
389
|
+
_content = message.get("content")
|
390
|
+
if isinstance(_content, str):
|
391
|
+
num_tokens += len(encoding.encode(_content))
|
392
|
+
|
393
|
+
elif isinstance(_content, list):
|
394
|
+
for item in _content:
|
395
|
+
if isinstance(item, dict):
|
396
|
+
if "text" in item:
|
397
|
+
num_tokens += len(
|
398
|
+
encoding.encode(to_str(item["text"]))
|
399
|
+
)
|
400
|
+
elif "image_url" in item:
|
401
|
+
a: str = item["image_url"]["url"]
|
402
|
+
if "data:image/jpeg;base64," in a:
|
403
|
+
a = a.split("data:image/jpeg;base64,")[
|
404
|
+
1
|
405
|
+
].strip()
|
406
|
+
num_tokens += ImageUtil.calculate_image_token_usage_from_base64(
|
407
|
+
a, item.get("detail", "low")
|
408
|
+
)
|
409
|
+
num_tokens += (
|
410
|
+
20 # for every image we add 20 tokens buffer
|
411
|
+
)
|
412
|
+
elif isinstance(item, str):
|
413
|
+
num_tokens += len(encoding.encode(item))
|
414
|
+
else:
|
415
|
+
num_tokens += len(encoding.encode(str(item)))
|
416
|
+
|
377
417
|
num_tokens += 2 # every reply is primed with <im_start>assistant
|
378
418
|
return num_tokens + completion_tokens
|
379
419
|
else:
|
@@ -412,7 +452,7 @@ class APIUtil:
|
|
412
452
|
payload[key] = config[key]
|
413
453
|
|
414
454
|
for key in optional_:
|
415
|
-
if bool(config[key]) and
|
455
|
+
if bool(config[key]) and strip_lower(config[key]) != "none":
|
416
456
|
payload[key] = config[key]
|
417
457
|
|
418
458
|
return payload
|
@@ -527,6 +567,7 @@ class BaseRateLimiter(ABC):
|
|
527
567
|
max_attempts: int = 3,
|
528
568
|
method: str = "post",
|
529
569
|
payload: Mapping[str, any] = None,
|
570
|
+
required_tokens: int = None,
|
530
571
|
**kwargs,
|
531
572
|
) -> Mapping[str, any] | None:
|
532
573
|
"""
|
@@ -552,9 +593,11 @@ class BaseRateLimiter(ABC):
|
|
552
593
|
): # Minimum token count
|
553
594
|
await AsyncUtil.sleep(1) # Wait for capacity
|
554
595
|
continue
|
555
|
-
|
556
|
-
|
557
|
-
|
596
|
+
|
597
|
+
if not required_tokens:
|
598
|
+
required_tokens = APIUtil.calculate_num_token(
|
599
|
+
payload, endpoint, self.token_encoding_name, **kwargs
|
600
|
+
)
|
558
601
|
|
559
602
|
if await self.request_permission(required_tokens):
|
560
603
|
request_headers = {"Authorization": f"Bearer {api_key}"}
|
@@ -748,7 +791,7 @@ class BaseService:
|
|
748
791
|
)
|
749
792
|
|
750
793
|
if ep not in self.endpoints:
|
751
|
-
endpoint_config =
|
794
|
+
endpoint_config = nget(self.schema, [ep, "config"])
|
752
795
|
self.schema.get(ep, {})
|
753
796
|
if isinstance(ep, EndPoint):
|
754
797
|
self.endpoints[ep.endpoint] = ep
|
@@ -792,7 +835,7 @@ class BaseService:
|
|
792
835
|
|
793
836
|
else:
|
794
837
|
for ep in self.available_endpoints:
|
795
|
-
endpoint_config =
|
838
|
+
endpoint_config = nget(self.schema, [ep, "config"])
|
796
839
|
self.schema.get(ep, {})
|
797
840
|
if ep not in self.endpoints:
|
798
841
|
self.endpoints[ep] = EndPoint(
|
@@ -806,7 +849,7 @@ class BaseService:
|
|
806
849
|
if not self.endpoints[ep]._has_initialized:
|
807
850
|
await self.endpoints[ep].init_rate_limiter()
|
808
851
|
|
809
|
-
async def call_api(self, payload, endpoint, method, **kwargs):
|
852
|
+
async def call_api(self, payload, endpoint, method, required_tokens=None, **kwargs):
|
810
853
|
"""
|
811
854
|
Calls the specified API endpoint with the given payload and method.
|
812
855
|
|
@@ -831,12 +874,24 @@ class BaseService:
|
|
831
874
|
api_key=self.api_key,
|
832
875
|
method=method,
|
833
876
|
payload=payload,
|
877
|
+
required_tokens=required_tokens,
|
834
878
|
**kwargs,
|
835
879
|
)
|
836
880
|
|
837
881
|
|
838
882
|
class PayloadPackage:
|
839
883
|
|
884
|
+
@classmethod
|
885
|
+
def embeddings(cls, embed_str, llmconfig, schema, **kwargs):
|
886
|
+
return APIUtil.create_payload(
|
887
|
+
input_=embed_str,
|
888
|
+
config=llmconfig,
|
889
|
+
required_=schema["required"],
|
890
|
+
optional_=schema["optional"],
|
891
|
+
input_key="input",
|
892
|
+
**kwargs,
|
893
|
+
)
|
894
|
+
|
840
895
|
@classmethod
|
841
896
|
def chat_completion(cls, messages, llmconfig, schema, **kwargs):
|
842
897
|
"""
|
@@ -874,7 +929,7 @@ class PayloadPackage:
|
|
874
929
|
Returns:
|
875
930
|
The constructed payload.
|
876
931
|
"""
|
877
|
-
return APIUtil.
|
932
|
+
return APIUtil.create_payload(
|
878
933
|
input_=training_file,
|
879
934
|
config=llmconfig,
|
880
935
|
required_=schema["required"],
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
from contextlib import asynccontextmanager
|
4
|
+
|
5
|
+
# import builtins
|
6
|
+
# import traceback
|
7
|
+
|
8
|
+
# original_print = builtins.print
|
9
|
+
|
10
|
+
|
11
|
+
# def new_print(*args, **kwargs):
|
12
|
+
# # Get the last frame in the stack
|
13
|
+
# frame = traceback.extract_stack(limit=2)[0]
|
14
|
+
# # Insert the filename and line number
|
15
|
+
# original_print(f"Called from {frame.filename}:{frame.lineno}")
|
16
|
+
# # Call the original print
|
17
|
+
# original_print(*args, **kwargs)
|
18
|
+
|
19
|
+
|
20
|
+
# Replace the built-in print with the new print
|
21
|
+
# builtins.print = new_print
|
22
|
+
|
23
|
+
|
24
|
+
@asynccontextmanager
|
25
|
+
async def async_suppress_print():
|
26
|
+
"""
|
27
|
+
An asynchronous context manager that redirects stdout to /dev/null to suppress print output.
|
28
|
+
"""
|
29
|
+
original_stdout = sys.stdout # Save the reference to the original standard output
|
30
|
+
with open(os.devnull, "w") as devnull:
|
31
|
+
sys.stdout = devnull
|
32
|
+
try:
|
33
|
+
yield
|
34
|
+
finally:
|
35
|
+
sys.stdout = (
|
36
|
+
original_stdout # Restore standard output to the original value
|
37
|
+
)
|