lionagi 0.0.312__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lionagi/__init__.py +61 -3
- lionagi/core/__init__.py +0 -14
- 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/__init__.py +0 -3
- lionagi/core/agent/base_agent.py +45 -36
- 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/collections/_logger.py +319 -0
- 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/engine/branch_engine.py +333 -0
- lionagi/core/engine/instruction_map_engine.py +204 -0
- lionagi/core/engine/sandbox_.py +14 -0
- lionagi/core/engine/script_engine.py +99 -0
- lionagi/core/executor/base_executor.py +90 -0
- lionagi/core/executor/graph_executor.py +330 -0
- lionagi/core/executor/neo4j_executor.py +384 -0
- lionagi/core/generic/__init__.py +7 -0
- lionagi/core/generic/edge.py +112 -0
- 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 +220 -0
- lionagi/core/generic/tree.py +48 -0
- lionagi/core/generic/tree_node.py +79 -0
- lionagi/core/mail/__init__.py +7 -3
- lionagi/core/mail/mail.py +25 -0
- lionagi/core/mail/mail_manager.py +142 -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/__init__.py +0 -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/__init__.py +0 -3
- lionagi/core/session/branch.py +431 -0
- lionagi/core/session/directive_mixin.py +287 -0
- lionagi/core/session/session.py +230 -902
- 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/__init__.py +0 -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/__init__.py +0 -0
- lionagi/core/validator/validator.py +364 -0
- lionagi/core/work/__init__.py +0 -0
- lionagi/core/work/work.py +76 -0
- lionagi/core/work/work_function.py +101 -0
- lionagi/core/work/work_queue.py +103 -0
- lionagi/core/work/worker.py +258 -0
- lionagi/core/work/worklog.py +120 -0
- lionagi/experimental/__init__.py +0 -0
- lionagi/experimental/compressor/__init__.py +0 -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/__init__.py +0 -0
- lionagi/experimental/directive/parser/base_parser.py +282 -0
- lionagi/experimental/directive/template/__init__.py +0 -0
- lionagi/experimental/directive/template/base_template.py +79 -0
- lionagi/experimental/directive/template/schema.py +36 -0
- lionagi/experimental/directive/tokenizer.py +73 -0
- lionagi/experimental/evaluator/__init__.py +0 -0
- lionagi/experimental/evaluator/ast_evaluator.py +131 -0
- lionagi/experimental/evaluator/base_evaluator.py +218 -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/autogen_/__init__.py +0 -0
- lionagi/integrations/bridge/autogen_/autogen_.py +124 -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/bridge/llamaindex_/llama_pack.py +227 -0
- lionagi/integrations/bridge/llamaindex_/node_parser.py +6 -9
- lionagi/integrations/bridge/pydantic_/pydantic_bridge.py +1 -0
- lionagi/integrations/bridge/transformers_/__init__.py +0 -0
- lionagi/integrations/bridge/transformers_/install_.py +36 -0
- lionagi/integrations/chunker/__init__.py +0 -0
- lionagi/integrations/chunker/chunk.py +312 -0
- lionagi/integrations/config/oai_configs.py +38 -7
- lionagi/integrations/config/ollama_configs.py +1 -1
- lionagi/integrations/config/openrouter_configs.py +14 -2
- lionagi/integrations/loader/__init__.py +0 -0
- lionagi/integrations/loader/load.py +253 -0
- lionagi/integrations/loader/load_util.py +195 -0
- 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 +7 -6
- 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 -0
- lionagi/integrations/storage/neo4j.py +665 -0
- lionagi/integrations/storage/storage_util.py +287 -0
- lionagi/integrations/storage/structure_excel.py +285 -0
- lionagi/integrations/storage/to_csv.py +63 -0
- lionagi/integrations/storage/to_excel.py +83 -0
- lionagi/libs/__init__.py +26 -1
- lionagi/libs/ln_api.py +78 -23
- 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_knowledge_graph.py +405 -0
- lionagi/libs/ln_nested.py +26 -11
- lionagi/libs/ln_parse.py +110 -14
- lionagi/libs/ln_queue.py +117 -0
- lionagi/libs/ln_tokenize.py +164 -0
- lionagi/{core/prompt/field_validator.py → libs/ln_validate.py} +79 -14
- lionagi/libs/special_tokens.py +172 -0
- lionagi/libs/sys_util.py +107 -2
- 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/code_form.py +13 -0
- lionagi/lions/coder/coder.py +168 -0
- lionagi/lions/coder/util.py +96 -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/integrations/__init__.py +0 -0
- lionagi/tests/libs/__init__.py +0 -0
- lionagi/tests/libs/test_field_validators.py +353 -0
- lionagi/tests/{test_libs → libs}/test_func_call.py +23 -21
- lionagi/tests/{test_libs → libs}/test_nested.py +36 -21
- lionagi/tests/{test_libs → libs}/test_parse.py +1 -1
- lionagi/tests/libs/test_queue.py +67 -0
- 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 -292
- 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.0.312.dist-info → lionagi-0.2.1.dist-info}/LICENSE +12 -11
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/METADATA +19 -118
- lionagi-0.2.1.dist-info/RECORD +240 -0
- lionagi/core/branch/__init__.py +0 -4
- lionagi/core/branch/base_branch.py +0 -654
- lionagi/core/branch/branch.py +0 -471
- lionagi/core/branch/branch_flow_mixin.py +0 -96
- lionagi/core/branch/executable_branch.py +0 -347
- lionagi/core/branch/util.py +0 -323
- lionagi/core/direct/__init__.py +0 -6
- lionagi/core/direct/predict.py +0 -161
- lionagi/core/direct/score.py +0 -278
- lionagi/core/direct/select.py +0 -169
- lionagi/core/direct/utils.py +0 -87
- lionagi/core/direct/vote.py +0 -64
- lionagi/core/flow/base/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 -263
- lionagi/core/flow/monoflow/followup.py +0 -214
- lionagi/core/flow/polyflow/__init__.py +0 -1
- lionagi/core/flow/polyflow/chat.py +0 -248
- lionagi/core/mail/schema.py +0 -56
- lionagi/core/messages/__init__.py +0 -3
- lionagi/core/messages/schema.py +0 -533
- lionagi/core/prompt/prompt_template.py +0 -316
- lionagi/core/schema/__init__.py +0 -22
- lionagi/core/schema/action_node.py +0 -29
- lionagi/core/schema/base_mixin.py +0 -296
- lionagi/core/schema/base_node.py +0 -199
- lionagi/core/schema/condition.py +0 -24
- lionagi/core/schema/data_logger.py +0 -354
- lionagi/core/schema/data_node.py +0 -93
- lionagi/core/schema/prompt_template.py +0 -67
- lionagi/core/schema/structure.py +0 -910
- lionagi/core/tool/__init__.py +0 -3
- lionagi/core/tool/tool_manager.py +0 -280
- lionagi/integrations/bridge/pydantic_/base_model.py +0 -7
- lionagi/tests/test_core/test_base_branch.py +0 -427
- 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 -312
- lionagi/tests/test_core/test_tool_manager.py +0 -95
- lionagi-0.0.312.dist-info/RECORD +0 -111
- /lionagi/core/{branch/base → _setting}/__init__.py +0 -0
- /lionagi/core/{flow → agent/eval}/__init__.py +0 -0
- /lionagi/core/{flow/base → agent/learn}/__init__.py +0 -0
- /lionagi/core/{prompt → agent/plan}/__init__.py +0 -0
- /lionagi/core/{tool/manual.py → agent/plan/plan.py} +0 -0
- /lionagi/{tests/test_integrations → core/director}/__init__.py +0 -0
- /lionagi/{tests/test_libs → core/engine}/__init__.py +0 -0
- /lionagi/{tests/test_libs/test_async.py → core/executor/__init__.py} +0 -0
- /lionagi/tests/{test_libs → libs}/test_api.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_convert.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_sys_util.py +0 -0
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/WHEEL +0 -0
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/top_level.txt +0 -0
lionagi/core/schema/structure.py
DELETED
@@ -1,910 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module contains classes for representing and manipulating graph structures.
|
3
|
-
|
4
|
-
The module includes the following classes:
|
5
|
-
- Relationship: Represents a relationship between two nodes in a graph.
|
6
|
-
- Graph: Represents a graph structure, consisting of nodes and their relationships.
|
7
|
-
- Structure: Represents a structure that extends the Graph class with additional functionality.
|
8
|
-
|
9
|
-
The Structure class adds methods for managing and executing a graph structure, including
|
10
|
-
adding nodes and relationships, checking conditions, processing incoming and outgoing mails,
|
11
|
-
and executing the structure.
|
12
|
-
"""
|
13
|
-
|
14
|
-
import time
|
15
|
-
from typing import List, Any, Dict, Callable
|
16
|
-
from collections import deque
|
17
|
-
from pydantic import Field
|
18
|
-
|
19
|
-
from lionagi.libs import SysUtil, func_call, AsyncUtil
|
20
|
-
|
21
|
-
from .base_node import BaseRelatableNode, BaseNode, Tool
|
22
|
-
from lionagi.core.mail.schema import BaseMail
|
23
|
-
|
24
|
-
from lionagi.core.schema.condition import Condition
|
25
|
-
|
26
|
-
from lionagi.core.schema.action_node import ActionNode, ActionSelection
|
27
|
-
from lionagi.core.schema.base_node import Tool
|
28
|
-
|
29
|
-
|
30
|
-
class Relationship(BaseRelatableNode):
|
31
|
-
"""
|
32
|
-
Represents a relationship between two nodes in a graph.
|
33
|
-
|
34
|
-
Attributes:
|
35
|
-
source_node_id (str): The identifier of the source node.
|
36
|
-
target_node_id (str): The identifier of the target node.
|
37
|
-
condition (Dict[str, Any]): A dictionary representing conditions for the relationship.
|
38
|
-
|
39
|
-
Examples:
|
40
|
-
>>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
|
41
|
-
>>> relationship.add_condition({"key": "value"})
|
42
|
-
>>> condition_value = relationship.get_condition("key")
|
43
|
-
>>> relationship.remove_condition("key")
|
44
|
-
"""
|
45
|
-
|
46
|
-
source_node_id: str
|
47
|
-
target_node_id: str
|
48
|
-
bundle: bool = False
|
49
|
-
condition: Callable = None
|
50
|
-
|
51
|
-
def add_condition(self, condition: Condition):
|
52
|
-
"""
|
53
|
-
Adds a condition to the relationship.
|
54
|
-
|
55
|
-
Args:
|
56
|
-
condition (Condition): The condition to add.
|
57
|
-
|
58
|
-
Raises:
|
59
|
-
ValueError: If the condition is not an instance of the Condition class.
|
60
|
-
"""
|
61
|
-
if not isinstance(condition, Condition):
|
62
|
-
raise ValueError(
|
63
|
-
"Invalid condition type, please use Condition class to build a valid condition"
|
64
|
-
)
|
65
|
-
self.condition = condition
|
66
|
-
|
67
|
-
def check_condition(self, source_obj):
|
68
|
-
"""
|
69
|
-
Checks the condition of the relationship.
|
70
|
-
|
71
|
-
Args:
|
72
|
-
source_obj: The source object to evaluate the condition against.
|
73
|
-
|
74
|
-
Returns:
|
75
|
-
The result of evaluating the condition.
|
76
|
-
|
77
|
-
Raises:
|
78
|
-
ValueError: If the relationship condition function is invalid.
|
79
|
-
"""
|
80
|
-
try:
|
81
|
-
return bool(self.condition(source_obj))
|
82
|
-
except:
|
83
|
-
raise ValueError("Invalid relationship condition function")
|
84
|
-
|
85
|
-
def _source_existed(self, obj: Dict[str, Any]) -> bool:
|
86
|
-
"""
|
87
|
-
Checks if the source node exists in a given object.
|
88
|
-
|
89
|
-
Args:
|
90
|
-
obj (Dict[str, Any]): The object to check.
|
91
|
-
|
92
|
-
Returns:
|
93
|
-
bool: True if the source node exists, False otherwise.
|
94
|
-
"""
|
95
|
-
return self.source_node_id in obj.keys()
|
96
|
-
|
97
|
-
def _target_existed(self, obj: Dict[str, Any]) -> bool:
|
98
|
-
"""
|
99
|
-
Checks if the target node exists in a given object.
|
100
|
-
|
101
|
-
Args:
|
102
|
-
obj (Dict[str, Any]): The object to check.
|
103
|
-
|
104
|
-
Returns:
|
105
|
-
bool: True if the target node exists, False otherwise.
|
106
|
-
"""
|
107
|
-
return self.target_node_id in obj.keys()
|
108
|
-
|
109
|
-
def _is_in(self, obj: Dict[str, Any]) -> bool:
|
110
|
-
"""
|
111
|
-
Validates the existence of both source and target nodes in a given object.
|
112
|
-
|
113
|
-
Args:
|
114
|
-
obj (Dict[str, Any]): The object to check.
|
115
|
-
|
116
|
-
Returns:
|
117
|
-
bool: True if both nodes exist.
|
118
|
-
|
119
|
-
Raises:
|
120
|
-
ValueError: If either the source or target node does not exist.
|
121
|
-
"""
|
122
|
-
if self._source_existed(obj) and self._target_existed(obj):
|
123
|
-
return True
|
124
|
-
|
125
|
-
elif self._source_existed(obj):
|
126
|
-
raise ValueError(f"Target node {self.source_node_id} does not exist")
|
127
|
-
else:
|
128
|
-
raise ValueError(f"Source node {self.target_node_id} does not exist")
|
129
|
-
|
130
|
-
def __str__(self) -> str:
|
131
|
-
"""
|
132
|
-
Returns a simple string representation of the Relationship.
|
133
|
-
|
134
|
-
Examples:
|
135
|
-
>>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
|
136
|
-
>>> str(relationship)
|
137
|
-
'Relationship (id_=None, from=node1, to=node2, label=None)'
|
138
|
-
"""
|
139
|
-
return (
|
140
|
-
f"Relationship (id_={self.id_}, from={self.source_node_id}, to={self.target_node_id}, "
|
141
|
-
f"label={self.label})"
|
142
|
-
)
|
143
|
-
|
144
|
-
def __repr__(self) -> str:
|
145
|
-
"""
|
146
|
-
Returns a detailed string representation of the Relationship.
|
147
|
-
|
148
|
-
Examples:
|
149
|
-
>>> relationship = Relationship(source_node_id="node1", target_node_id="node2")
|
150
|
-
>>> repr(relationship)
|
151
|
-
'Relationship(id_=None, from=node1, to=node2, content=None, metadata=None, label=None)'
|
152
|
-
"""
|
153
|
-
return (
|
154
|
-
f"Relationship(id_={self.id_}, from={self.source_node_id}, to={self.target_node_id}, "
|
155
|
-
f"content={self.content}, metadata={self.metadata}, label={self.label})"
|
156
|
-
)
|
157
|
-
|
158
|
-
|
159
|
-
class Graph(BaseRelatableNode):
|
160
|
-
"""
|
161
|
-
Represents a graph structure, consisting of nodes and their relationships.
|
162
|
-
|
163
|
-
Attributes:
|
164
|
-
nodes (Dict[str, BaseNode]): A dictionary of nodes in the graph.
|
165
|
-
relationships (Dict[str, Relationship]): A dictionary of relationships between nodes in the graph.
|
166
|
-
node_relationships (Dict[str, Dict[str, Dict[str, str]]]): A dictionary tracking the relationships of each node.
|
167
|
-
|
168
|
-
Examples:
|
169
|
-
>>> graph = Graph()
|
170
|
-
>>> node = BaseNode(id_='node1')
|
171
|
-
>>> graph.add_node(node)
|
172
|
-
>>> graph.node_exists(node)
|
173
|
-
True
|
174
|
-
>>> relationship = Relationship(id_='rel1', source_node_id='node1', target_node_id='node2')
|
175
|
-
>>> graph.add_relationship(relationship)
|
176
|
-
>>> graph.relationship_exists(relationship)
|
177
|
-
True
|
178
|
-
"""
|
179
|
-
|
180
|
-
nodes: dict = Field(default={})
|
181
|
-
relationships: dict = Field(default={})
|
182
|
-
node_relationships: dict = Field(default={})
|
183
|
-
|
184
|
-
def add_node(self, node: BaseNode) -> None:
|
185
|
-
"""
|
186
|
-
Adds a node to the graph.
|
187
|
-
|
188
|
-
Args:
|
189
|
-
node (BaseNode): The node to add to the graph.
|
190
|
-
"""
|
191
|
-
|
192
|
-
self.nodes[node.id_] = node
|
193
|
-
self.node_relationships[node.id_] = {"in": {}, "out": {}}
|
194
|
-
|
195
|
-
def add_relationship(self, relationship: Relationship) -> None:
|
196
|
-
"""
|
197
|
-
Adds a relationship between nodes in the graph.
|
198
|
-
|
199
|
-
Args:
|
200
|
-
relationship (Relationship): The relationship to add.
|
201
|
-
|
202
|
-
Raises:
|
203
|
-
KeyError: If either the source or target node of the relationship is not found in the graph.
|
204
|
-
"""
|
205
|
-
if relationship.source_node_id not in self.node_relationships.keys():
|
206
|
-
raise KeyError(f"node {relationship.source_node_id} is not found.")
|
207
|
-
if relationship.target_node_id not in self.node_relationships.keys():
|
208
|
-
raise KeyError(f"node {relationship.target_node_id} is not found.")
|
209
|
-
|
210
|
-
self.relationships[relationship.id_] = relationship
|
211
|
-
self.node_relationships[relationship.source_node_id]["out"][
|
212
|
-
relationship.id_
|
213
|
-
] = relationship.target_node_id
|
214
|
-
self.node_relationships[relationship.target_node_id]["in"][
|
215
|
-
relationship.id_
|
216
|
-
] = relationship.source_node_id
|
217
|
-
|
218
|
-
def get_node_relationships(
|
219
|
-
self, node: BaseNode = None, out_edge=True
|
220
|
-
) -> List[Relationship]:
|
221
|
-
"""
|
222
|
-
Retrieves relationships of a specific node or all relationships in the graph.
|
223
|
-
|
224
|
-
Args:
|
225
|
-
node (Optional[BaseNode]): The node whose relationships to retrieve. If None, retrieves all relationships.
|
226
|
-
out_edge (bool): Whether to retrieve outgoing relationships. If False, retrieves incoming relationships.
|
227
|
-
|
228
|
-
Returns:
|
229
|
-
List[Relationship]: A list of relationships.
|
230
|
-
|
231
|
-
Raises:
|
232
|
-
KeyError: If the specified node is not found in the graph.
|
233
|
-
"""
|
234
|
-
if node is None:
|
235
|
-
return list(self.relationships.values())
|
236
|
-
|
237
|
-
if node.id_ not in self.nodes.keys():
|
238
|
-
raise KeyError(f"node {node.id_} is not found")
|
239
|
-
|
240
|
-
if out_edge:
|
241
|
-
relationship_ids = list(self.node_relationships[node.id_]["out"].keys())
|
242
|
-
relationships = func_call.lcall(
|
243
|
-
relationship_ids, lambda i: self.relationships[i]
|
244
|
-
)
|
245
|
-
return relationships
|
246
|
-
else:
|
247
|
-
relationship_ids = list(self.node_relationships[node.id_]["in"].keys())
|
248
|
-
relationships = func_call.lcall(
|
249
|
-
relationship_ids, lambda i: self.relationships[i]
|
250
|
-
)
|
251
|
-
return relationships
|
252
|
-
|
253
|
-
def get_predecessors(self, node: BaseNode):
|
254
|
-
"""
|
255
|
-
Retrieves the predecessor nodes of a given node.
|
256
|
-
|
257
|
-
Args:
|
258
|
-
node (BaseNode): The node whose predecessors to retrieve.
|
259
|
-
|
260
|
-
Returns:
|
261
|
-
list: A list of predecessor nodes.
|
262
|
-
"""
|
263
|
-
node_ids = list(self.node_relationships[node.id_]["in"].values())
|
264
|
-
nodes = func_call.lcall(node_ids, lambda i: self.nodes[i])
|
265
|
-
return nodes
|
266
|
-
|
267
|
-
def get_successors(self, node: BaseNode):
|
268
|
-
"""
|
269
|
-
Retrieves the successor nodes of a given node.
|
270
|
-
|
271
|
-
Args:
|
272
|
-
node (BaseNode): The node whose successors to retrieve.
|
273
|
-
|
274
|
-
Returns:
|
275
|
-
list: A list of successor nodes.
|
276
|
-
"""
|
277
|
-
node_ids = list(self.node_relationships[node.id_]["out"].values())
|
278
|
-
nodes = func_call.lcall(node_ids, lambda i: self.nodes[i])
|
279
|
-
return nodes
|
280
|
-
|
281
|
-
def remove_node(self, node: BaseNode) -> BaseNode:
|
282
|
-
"""
|
283
|
-
Removes a node and its associated relationship from the graph.
|
284
|
-
|
285
|
-
Args:
|
286
|
-
node (BaseNode): The node to remove.
|
287
|
-
|
288
|
-
Returns:
|
289
|
-
BaseNode: The removed node.
|
290
|
-
|
291
|
-
Raises:
|
292
|
-
KeyError: If the node is not found in the graph.
|
293
|
-
"""
|
294
|
-
if node.id_ not in self.nodes.keys():
|
295
|
-
raise KeyError(f"node {node.id_} is not found")
|
296
|
-
|
297
|
-
out_relationship = self.node_relationships[node.id_]["out"]
|
298
|
-
for relationship_id, node_id in out_relationship.items():
|
299
|
-
self.node_relationships[node_id]["in"].pop(relationship_id)
|
300
|
-
self.relationships.pop(relationship_id)
|
301
|
-
|
302
|
-
in_relationship = self.node_relationships[node.id_]["in"]
|
303
|
-
for relationship_id, node_id in in_relationship.items():
|
304
|
-
self.node_relationships[node_id]["out"].pop(relationship_id)
|
305
|
-
self.relationships.pop(relationship_id)
|
306
|
-
|
307
|
-
self.node_relationships.pop(node.id_)
|
308
|
-
return self.nodes.pop(node.id_)
|
309
|
-
|
310
|
-
def remove_relationship(self, relationship: Relationship) -> Relationship:
|
311
|
-
"""
|
312
|
-
Removes a relationship from the graph.
|
313
|
-
|
314
|
-
Args:
|
315
|
-
relationship (Relationship): The relationship to remove.
|
316
|
-
|
317
|
-
Returns:
|
318
|
-
Relationship: The removed relationship.
|
319
|
-
|
320
|
-
Raises:
|
321
|
-
KeyError: If the relationship is not found in the graph.
|
322
|
-
"""
|
323
|
-
if relationship.id_ not in self.relationships.keys():
|
324
|
-
raise KeyError(f"relationship {relationship.id_} is not found")
|
325
|
-
|
326
|
-
self.node_relationships[relationship.source_node_id]["out"].pop(
|
327
|
-
relationship.id_
|
328
|
-
)
|
329
|
-
self.node_relationships[relationship.target_node_id]["in"].pop(relationship.id_)
|
330
|
-
|
331
|
-
return self.relationships.pop(relationship.id_)
|
332
|
-
|
333
|
-
def node_exist(self, node: BaseNode) -> bool:
|
334
|
-
"""
|
335
|
-
Checks if a node exists in the graph.
|
336
|
-
|
337
|
-
Args:
|
338
|
-
node (BaseNode): The node to check.
|
339
|
-
|
340
|
-
Returns:
|
341
|
-
bool: True if the node exists, False otherwise.
|
342
|
-
"""
|
343
|
-
if node.id_ in self.nodes.keys():
|
344
|
-
return True
|
345
|
-
else:
|
346
|
-
return False
|
347
|
-
|
348
|
-
def relationship_exist(self, relationship: Relationship) -> bool:
|
349
|
-
"""
|
350
|
-
Checks if a relationship exists in the graph.
|
351
|
-
|
352
|
-
Args:
|
353
|
-
relationship (Relationship): The relationship to check.
|
354
|
-
|
355
|
-
Returns:
|
356
|
-
bool: True if the relationship exists, False otherwise.
|
357
|
-
"""
|
358
|
-
if relationship.id_ in self.relationships.keys():
|
359
|
-
return True
|
360
|
-
else:
|
361
|
-
return False
|
362
|
-
|
363
|
-
def is_empty(self) -> bool:
|
364
|
-
"""
|
365
|
-
Determines if the graph is empty.
|
366
|
-
|
367
|
-
Returns:
|
368
|
-
bool: True if the graph has no nodes, False otherwise.
|
369
|
-
"""
|
370
|
-
if self.nodes:
|
371
|
-
return False
|
372
|
-
else:
|
373
|
-
return True
|
374
|
-
|
375
|
-
def clear(self) -> None:
|
376
|
-
"""Clears the graph of all nodes and relationship."""
|
377
|
-
self.nodes.clear()
|
378
|
-
self.relationships.clear()
|
379
|
-
self.node_relationships.clear()
|
380
|
-
|
381
|
-
def to_networkx(self, **kwargs) -> Any:
|
382
|
-
"""
|
383
|
-
Converts the graph to a NetworkX graph object.
|
384
|
-
|
385
|
-
Args:
|
386
|
-
**kwargs: Additional keyword arguments to pass to the NetworkX DiGraph constructor.
|
387
|
-
|
388
|
-
Returns:
|
389
|
-
Any: A NetworkX directed graph representing the graph.
|
390
|
-
|
391
|
-
Examples:
|
392
|
-
>>> graph = Graph()
|
393
|
-
>>> nx_graph = graph.to_networkx()
|
394
|
-
"""
|
395
|
-
|
396
|
-
SysUtil.check_import("networkx")
|
397
|
-
|
398
|
-
from networkx import DiGraph
|
399
|
-
|
400
|
-
g = DiGraph(**kwargs)
|
401
|
-
for node_id, node in self.nodes.items():
|
402
|
-
node_info = node.to_dict()
|
403
|
-
node_info.pop("node_id")
|
404
|
-
node_info.update({"class_name": node.__class__.__name__})
|
405
|
-
g.add_node(node_id, **node_info)
|
406
|
-
|
407
|
-
for _, relationship in self.relationships.items():
|
408
|
-
relationship_info = relationship.to_dict()
|
409
|
-
relationship_info.pop("node_id")
|
410
|
-
relationship_info.update({"class_name": relationship.__class__.__name__})
|
411
|
-
source_node_id = relationship_info.pop("source_node_id")
|
412
|
-
target_node_id = relationship_info.pop("target_node_id")
|
413
|
-
g.add_edge(source_node_id, target_node_id, **relationship_info)
|
414
|
-
|
415
|
-
return g
|
416
|
-
|
417
|
-
|
418
|
-
class Structure(BaseRelatableNode):
|
419
|
-
"""
|
420
|
-
Represents a structure that extends the Graph class with additional functionality.
|
421
|
-
|
422
|
-
Attributes:
|
423
|
-
graph (Graph): The underlying graph structure.
|
424
|
-
pending_ins (dict): A dictionary of pending incoming mails.
|
425
|
-
pending_outs (deque): A deque of pending outgoing mails.
|
426
|
-
execute_stop (bool): A flag indicating whether the execution should stop.
|
427
|
-
condition_check_result (bool | None): The result of the last condition check.
|
428
|
-
|
429
|
-
Methods:
|
430
|
-
add_node(node: BaseNode) -> None:
|
431
|
-
Adds a node to the structure's graph.
|
432
|
-
|
433
|
-
add_relationship(from_node: BaseNode, to_node: BaseNode, bundle=False, condition=None, **kwargs) -> None:
|
434
|
-
Adds a relationship between two nodes in the structure's graph.
|
435
|
-
|
436
|
-
get_relationships() -> list[Relationship]:
|
437
|
-
Retrieves all relationships in the structure's graph.
|
438
|
-
|
439
|
-
get_node_relationships(node: BaseNode, out_edge=True, labels=None) -> list[Relationship]:
|
440
|
-
Retrieves relationships of a specific node in the structure's graph.
|
441
|
-
|
442
|
-
get_predecessors(node: BaseNode) -> list[BaseNode]:
|
443
|
-
Retrieves the predecessor nodes of a given node in the structure's graph.
|
444
|
-
|
445
|
-
get_successors(node: BaseNode) -> list[BaseNode]:
|
446
|
-
Retrieves the successor nodes of a given node in the structure's graph.
|
447
|
-
|
448
|
-
node_exist(node: BaseNode) -> bool:
|
449
|
-
Checks if a node exists in the structure's graph.
|
450
|
-
|
451
|
-
relationship_exist(relationship: Relationship) -> bool:
|
452
|
-
Checks if a relationship exists in the structure's graph.
|
453
|
-
|
454
|
-
remove_node(node: BaseNode) -> BaseNode:
|
455
|
-
Removes a node from the structure's graph.
|
456
|
-
|
457
|
-
remove_relationship(relationship: Relationship) -> Relationship:
|
458
|
-
Removes a relationship from the structure's graph.
|
459
|
-
|
460
|
-
is_empty() -> bool:
|
461
|
-
Determines if the structure's graph is empty.
|
462
|
-
|
463
|
-
get_heads() -> list[BaseNode]:
|
464
|
-
Retrieves the head nodes of the structure's graph.
|
465
|
-
|
466
|
-
parse_to_action(instruction: BaseNode, bundled_nodes: deque) -> ActionNode:
|
467
|
-
Parses an instruction and bundled nodes into an ActionNode.
|
468
|
-
|
469
|
-
async check_condition(relationship: Relationship, executable_id: str) -> bool:
|
470
|
-
Checks the condition of a relationship.
|
471
|
-
|
472
|
-
check_condition_structure(relationship: Relationship) -> bool:
|
473
|
-
Checks the condition of a relationship within the structure.
|
474
|
-
|
475
|
-
async get_next_step(current_node: BaseNode, executable_id: str) -> list[BaseNode]:
|
476
|
-
Retrieves the next step nodes based on the current node and executable ID.
|
477
|
-
|
478
|
-
acyclic() -> bool:
|
479
|
-
Checks if the structure's graph is acyclic.
|
480
|
-
|
481
|
-
send(recipient_id: str, category: str, package: Any) -> None:
|
482
|
-
Sends a mail to a recipient.
|
483
|
-
|
484
|
-
process_relationship_condition(relationship_id: str) -> None:
|
485
|
-
Processes the condition of a relationship.
|
486
|
-
|
487
|
-
async process() -> None:
|
488
|
-
Processes the pending incoming mails and performs the corresponding actions.
|
489
|
-
|
490
|
-
async execute(refresh_time=1) -> None:
|
491
|
-
Executes the structure by processing incoming mails and updating the execution state.
|
492
|
-
"""
|
493
|
-
|
494
|
-
graph: Graph = Graph()
|
495
|
-
pending_ins: dict = {}
|
496
|
-
pending_outs: deque = deque()
|
497
|
-
execute_stop: bool = False
|
498
|
-
condition_check_result: bool | None = None
|
499
|
-
|
500
|
-
def add_node(self, node: BaseNode | list[BaseNode]):
|
501
|
-
"""
|
502
|
-
Adds a node to the structure's graph.
|
503
|
-
|
504
|
-
Args:
|
505
|
-
node (BaseNode): The node to add.
|
506
|
-
"""
|
507
|
-
func_call.lcall(node, self.graph.add_node)
|
508
|
-
|
509
|
-
def add_relationship(
|
510
|
-
self,
|
511
|
-
from_node: BaseNode,
|
512
|
-
to_node: BaseNode,
|
513
|
-
bundle=False,
|
514
|
-
condition=None,
|
515
|
-
**kwargs,
|
516
|
-
):
|
517
|
-
"""
|
518
|
-
Adds a relationship between two nodes in the structure's graph.
|
519
|
-
|
520
|
-
Args:
|
521
|
-
from_node (BaseNode): The source node of the relationship.
|
522
|
-
to_node (BaseNode): The target node of the relationship.
|
523
|
-
bundle (bool): Whether the relationship is bundled (default: False).
|
524
|
-
condition (Optional[Condition]): The condition for the relationship (default: None).
|
525
|
-
**kwargs: Additional keyword arguments for the relationship.
|
526
|
-
|
527
|
-
Raises:
|
528
|
-
ValueError: If the source node is a Tool or ActionSelection.
|
529
|
-
"""
|
530
|
-
if isinstance(from_node, Tool) or isinstance(from_node, ActionSelection):
|
531
|
-
raise ValueError(
|
532
|
-
f"type {type(from_node)} should not be the head of the relationship, "
|
533
|
-
f"please switch position and attach it to the tail of the relationship"
|
534
|
-
)
|
535
|
-
if isinstance(to_node, Tool) or isinstance(to_node, ActionSelection):
|
536
|
-
bundle = True
|
537
|
-
relationship = Relationship(
|
538
|
-
source_node_id=from_node.id_,
|
539
|
-
target_node_id=to_node.id_,
|
540
|
-
bundle=bundle,
|
541
|
-
**kwargs,
|
542
|
-
)
|
543
|
-
if condition:
|
544
|
-
relationship.add_condition(condition)
|
545
|
-
self.graph.add_relationship(relationship)
|
546
|
-
|
547
|
-
def get_relationships(self) -> list[Relationship]:
|
548
|
-
"""
|
549
|
-
Retrieves all relationships in the structure's graph.
|
550
|
-
|
551
|
-
Returns:
|
552
|
-
list[Relationship]: A list of all relationships.
|
553
|
-
"""
|
554
|
-
return self.graph.get_node_relationships()
|
555
|
-
|
556
|
-
def get_node_relationships(self, node: BaseNode, out_edge=True, labels=None):
|
557
|
-
"""
|
558
|
-
Retrieves relationships of a specific node in the structure's graph.
|
559
|
-
|
560
|
-
Args:
|
561
|
-
node (BaseNode): The node whose relationships to retrieve.
|
562
|
-
out_edge (bool): Whether to retrieve outgoing relationships (default: True).
|
563
|
-
labels (Optional[list]): The labels of the relationships to retrieve (default: None).
|
564
|
-
|
565
|
-
Returns:
|
566
|
-
list[Relationship]: A list of relationships for the specified node.
|
567
|
-
"""
|
568
|
-
relationships = self.graph.get_node_relationships(node, out_edge)
|
569
|
-
if labels:
|
570
|
-
if not isinstance(labels, list):
|
571
|
-
labels = [labels]
|
572
|
-
result = []
|
573
|
-
for r in relationships:
|
574
|
-
if r.label in labels:
|
575
|
-
result.append(r)
|
576
|
-
relationships = result
|
577
|
-
return relationships
|
578
|
-
|
579
|
-
def get_predecessors(self, node: BaseNode):
|
580
|
-
"""
|
581
|
-
Retrieves the predecessor nodes of a given node in the structure's graph.
|
582
|
-
|
583
|
-
Args:
|
584
|
-
node (BaseNode): The node whose predecessors to retrieve.
|
585
|
-
|
586
|
-
Returns:
|
587
|
-
list[BaseNode]: A list of predecessor nodes.
|
588
|
-
"""
|
589
|
-
return self.graph.get_predecessors(node)
|
590
|
-
|
591
|
-
def get_successors(self, node: BaseNode):
|
592
|
-
"""
|
593
|
-
Retrieves the successor nodes of a given node in the structure's graph.
|
594
|
-
|
595
|
-
Args:
|
596
|
-
node (BaseNode): The node whose successors to retrieve.
|
597
|
-
|
598
|
-
Returns:
|
599
|
-
list[BaseNode]: A list of successor nodes.
|
600
|
-
"""
|
601
|
-
return self.graph.get_successors(node)
|
602
|
-
|
603
|
-
def node_exist(self, node: BaseNode) -> bool:
|
604
|
-
"""
|
605
|
-
Checks if a node exists in the structure's graph.
|
606
|
-
|
607
|
-
Args:
|
608
|
-
node (BaseNode): The node to check.
|
609
|
-
|
610
|
-
Returns:
|
611
|
-
bool: True if the node exists, False otherwise.
|
612
|
-
"""
|
613
|
-
return self.graph.node_exist(node)
|
614
|
-
|
615
|
-
def relationship_exist(self, relationship: Relationship) -> bool:
|
616
|
-
"""
|
617
|
-
Checks if a relationship exists in the structure's graph.
|
618
|
-
|
619
|
-
Args:
|
620
|
-
relationship (Relationship): The relationship to check.
|
621
|
-
|
622
|
-
Returns:
|
623
|
-
bool: True if the relationship exists, False otherwise.
|
624
|
-
"""
|
625
|
-
return self.graph.relationship_exist(relationship)
|
626
|
-
|
627
|
-
def remove_node(self, node: BaseNode) -> BaseNode:
|
628
|
-
"""
|
629
|
-
Removes a node from the structure's graph.
|
630
|
-
|
631
|
-
Args:
|
632
|
-
node (BaseNode): The node to remove.
|
633
|
-
|
634
|
-
Returns:
|
635
|
-
BaseNode: The removed node.
|
636
|
-
"""
|
637
|
-
return self.graph.remove_node(node)
|
638
|
-
|
639
|
-
def remove_relationship(self, relationship: Relationship) -> Relationship:
|
640
|
-
"""
|
641
|
-
Removes a relationship from the structure's graph.
|
642
|
-
|
643
|
-
Args:
|
644
|
-
relationship (Relationship): The relationship to remove.
|
645
|
-
|
646
|
-
Returns:
|
647
|
-
Relationship: The removed relationship.
|
648
|
-
"""
|
649
|
-
return self.graph.remove_relationship(relationship)
|
650
|
-
|
651
|
-
def is_empty(self) -> bool:
|
652
|
-
"""
|
653
|
-
Determines if the structure's graph is empty.
|
654
|
-
|
655
|
-
Returns:
|
656
|
-
bool: True if the graph has no nodes, False otherwise.
|
657
|
-
"""
|
658
|
-
return self.graph.is_empty()
|
659
|
-
|
660
|
-
def get_heads(self):
|
661
|
-
"""
|
662
|
-
Retrieves the head nodes of the structure's graph.
|
663
|
-
|
664
|
-
Returns:
|
665
|
-
list[BaseNode]: A list of head nodes.
|
666
|
-
"""
|
667
|
-
heads = []
|
668
|
-
for key in self.graph.node_relationships:
|
669
|
-
if not self.graph.node_relationships[key]["in"]:
|
670
|
-
heads.append(self.graph.nodes[key])
|
671
|
-
return heads
|
672
|
-
|
673
|
-
@staticmethod
|
674
|
-
def parse_to_action(instruction: BaseNode, bundled_nodes: deque):
|
675
|
-
"""
|
676
|
-
Parses an instruction and bundled nodes into an ActionNode.
|
677
|
-
|
678
|
-
Args:
|
679
|
-
instruction (BaseNode): The instruction node.
|
680
|
-
bundled_nodes (deque): A deque of bundled nodes.
|
681
|
-
|
682
|
-
Returns:
|
683
|
-
ActionNode: The parsed ActionNode.
|
684
|
-
|
685
|
-
Raises:
|
686
|
-
ValueError: If an invalid bundled node is encountered.
|
687
|
-
"""
|
688
|
-
action_node = ActionNode(instruction)
|
689
|
-
while bundled_nodes:
|
690
|
-
node = bundled_nodes.popleft()
|
691
|
-
if isinstance(node, ActionSelection):
|
692
|
-
action_node.action = node.action
|
693
|
-
action_node.action_kwargs = node.action_kwargs
|
694
|
-
elif isinstance(node, Tool):
|
695
|
-
action_node.tools.append(node)
|
696
|
-
else:
|
697
|
-
raise ValueError("Invalid bundles nodes")
|
698
|
-
return action_node
|
699
|
-
|
700
|
-
async def check_condition(self, relationship, executable_id):
|
701
|
-
"""
|
702
|
-
Checks the condition of a relationship.
|
703
|
-
|
704
|
-
Args:
|
705
|
-
relationship (Relationship): The relationship to check the condition for.
|
706
|
-
executable_id (str): The ID of the executable.
|
707
|
-
|
708
|
-
Returns:
|
709
|
-
bool: True if the condition is met, False otherwise.
|
710
|
-
|
711
|
-
Raises:
|
712
|
-
ValueError: If the source type of the condition is invalid.
|
713
|
-
"""
|
714
|
-
if relationship.condition.source_type == "structure":
|
715
|
-
return self.check_condition_structure(relationship)
|
716
|
-
elif relationship.condition.source_type == "executable":
|
717
|
-
self.send(
|
718
|
-
recipient_id=executable_id, category="condition", package=relationship
|
719
|
-
)
|
720
|
-
while self.condition_check_result is None:
|
721
|
-
await AsyncUtil.sleep(0.1)
|
722
|
-
self.process_relationship_condition(relationship.id_)
|
723
|
-
continue
|
724
|
-
check_result = self.condition_check_result
|
725
|
-
self.condition_check_result = None
|
726
|
-
return check_result
|
727
|
-
else:
|
728
|
-
raise ValueError("Invalid source_type.")
|
729
|
-
|
730
|
-
def check_condition_structure(self, relationship):
|
731
|
-
"""
|
732
|
-
Checks the condition of a relationship within the structure.
|
733
|
-
|
734
|
-
Args:
|
735
|
-
relationship (Relationship): The relationship to check the condition for.
|
736
|
-
|
737
|
-
Returns:
|
738
|
-
bool: The result of the condition check.
|
739
|
-
"""
|
740
|
-
return relationship.condition(self)
|
741
|
-
|
742
|
-
async def get_next_step(self, current_node: BaseNode, executable_id):
|
743
|
-
"""
|
744
|
-
Retrieves the next step nodes based on the current node and executable ID.
|
745
|
-
|
746
|
-
Args:
|
747
|
-
current_node (BaseNode): The current node.
|
748
|
-
executable_id (str): The ID of the executable.
|
749
|
-
|
750
|
-
Returns:
|
751
|
-
list[BaseNode]: A list of next step nodes.
|
752
|
-
"""
|
753
|
-
next_nodes = []
|
754
|
-
next_relationships = self.get_node_relationships(current_node)
|
755
|
-
for relationship in next_relationships:
|
756
|
-
if relationship.bundle:
|
757
|
-
continue
|
758
|
-
if relationship.condition:
|
759
|
-
check = await self.check_condition(relationship, executable_id)
|
760
|
-
if not check:
|
761
|
-
continue
|
762
|
-
node = self.graph.nodes[relationship.target_node_id]
|
763
|
-
further_relationships = self.get_node_relationships(node)
|
764
|
-
bundled_nodes = deque()
|
765
|
-
for f_relationship in further_relationships:
|
766
|
-
if f_relationship.bundle:
|
767
|
-
bundled_nodes.append(
|
768
|
-
self.graph.nodes[f_relationship.target_node_id]
|
769
|
-
)
|
770
|
-
if bundled_nodes:
|
771
|
-
node = self.parse_to_action(node, bundled_nodes)
|
772
|
-
next_nodes.append(node)
|
773
|
-
return next_nodes
|
774
|
-
|
775
|
-
def acyclic(self):
|
776
|
-
"""
|
777
|
-
Checks if the structure's graph is acyclic.
|
778
|
-
|
779
|
-
Returns:
|
780
|
-
bool: True if the graph is acyclic, False otherwise.
|
781
|
-
"""
|
782
|
-
check_deque = deque(self.graph.nodes.keys())
|
783
|
-
check_dict = {
|
784
|
-
key: 0 for key in self.graph.nodes.keys()
|
785
|
-
} # 0: not visited, 1: temp, 2: perm
|
786
|
-
|
787
|
-
def visit(key):
|
788
|
-
if check_dict[key] == 2:
|
789
|
-
return True
|
790
|
-
elif check_dict[key] == 1:
|
791
|
-
return False
|
792
|
-
|
793
|
-
check_dict[key] = 1
|
794
|
-
|
795
|
-
out_relationships = self.graph.get_node_relationships(self.graph.nodes[key])
|
796
|
-
for node in out_relationships:
|
797
|
-
check = visit(node.target_node_id)
|
798
|
-
if not check:
|
799
|
-
return False
|
800
|
-
|
801
|
-
check_dict[key] = 2
|
802
|
-
return True
|
803
|
-
|
804
|
-
while check_deque:
|
805
|
-
key = check_deque.pop()
|
806
|
-
check = visit(key)
|
807
|
-
if not check:
|
808
|
-
return False
|
809
|
-
return True
|
810
|
-
|
811
|
-
def send(self, recipient_id: str, category: str, package: Any) -> None:
|
812
|
-
"""
|
813
|
-
Sends a mail to a recipient.
|
814
|
-
|
815
|
-
Args:
|
816
|
-
recipient_id (str): The ID of the recipient.
|
817
|
-
category (str): The category of the mail.
|
818
|
-
package (Any): The package to send.
|
819
|
-
"""
|
820
|
-
mail = BaseMail(
|
821
|
-
sender_id=self.id_,
|
822
|
-
recipient_id=recipient_id,
|
823
|
-
category=category,
|
824
|
-
package=package,
|
825
|
-
)
|
826
|
-
self.pending_outs.append(mail)
|
827
|
-
|
828
|
-
def process_relationship_condition(self, relationship_id):
|
829
|
-
"""
|
830
|
-
Processes the condition of a relationship.
|
831
|
-
|
832
|
-
Args:
|
833
|
-
relationship_id (str): The ID of the relationship to process the condition for.
|
834
|
-
"""
|
835
|
-
for key in list(self.pending_ins.keys()):
|
836
|
-
skipped_requests = deque()
|
837
|
-
while self.pending_ins[key]:
|
838
|
-
mail = self.pending_ins[key].popleft()
|
839
|
-
if (
|
840
|
-
mail.category == "condition"
|
841
|
-
and mail.package["relationship_id"] == relationship_id
|
842
|
-
):
|
843
|
-
self.condition_check_result = mail.package["check_result"]
|
844
|
-
else:
|
845
|
-
skipped_requests.append(mail)
|
846
|
-
self.pending_ins[key] = skipped_requests
|
847
|
-
|
848
|
-
async def process(self) -> None:
|
849
|
-
"""
|
850
|
-
Processes the pending incoming mails and performs the corresponding actions.
|
851
|
-
"""
|
852
|
-
for key in list(self.pending_ins.keys()):
|
853
|
-
while self.pending_ins[key]:
|
854
|
-
mail = self.pending_ins[key].popleft()
|
855
|
-
if mail.category == "start":
|
856
|
-
next_nodes = self.get_heads()
|
857
|
-
elif mail.category == "end":
|
858
|
-
self.execute_stop = True
|
859
|
-
return
|
860
|
-
elif mail.category == "node_id":
|
861
|
-
if mail.package not in self.graph.nodes:
|
862
|
-
raise ValueError(
|
863
|
-
f"Node {mail.package} does not exist in the structure {self.id_}"
|
864
|
-
)
|
865
|
-
next_nodes = await self.get_next_step(
|
866
|
-
self.graph.nodes[mail.package], mail.sender_id
|
867
|
-
)
|
868
|
-
elif mail.category == "node" and isinstance(mail.package, BaseNode):
|
869
|
-
if not self.node_exist(mail.package):
|
870
|
-
raise ValueError(
|
871
|
-
f"Node {mail.package} does not exist in the structure {self.id_}"
|
872
|
-
)
|
873
|
-
next_nodes = await self.get_next_step(mail.package, mail.sender_id)
|
874
|
-
else:
|
875
|
-
raise ValueError(f"Invalid mail type for structure")
|
876
|
-
|
877
|
-
if not next_nodes: # tail
|
878
|
-
self.send(
|
879
|
-
recipient_id=mail.sender_id, category="end", package="end"
|
880
|
-
)
|
881
|
-
else:
|
882
|
-
if len(next_nodes) == 1:
|
883
|
-
self.send(
|
884
|
-
recipient_id=mail.sender_id,
|
885
|
-
category="node",
|
886
|
-
package=next_nodes[0],
|
887
|
-
)
|
888
|
-
else:
|
889
|
-
self.send(
|
890
|
-
recipient_id=mail.sender_id,
|
891
|
-
category="node_list",
|
892
|
-
package=next_nodes,
|
893
|
-
)
|
894
|
-
|
895
|
-
async def execute(self, refresh_time=1):
|
896
|
-
"""
|
897
|
-
Executes the structure by processing incoming mails and updating the execution state.
|
898
|
-
|
899
|
-
Args:
|
900
|
-
refresh_time (int): The refresh time for execution (default: 1).
|
901
|
-
|
902
|
-
Raises:
|
903
|
-
ValueError: If the structure's graph is not acyclic.
|
904
|
-
"""
|
905
|
-
if not self.acyclic():
|
906
|
-
raise ValueError("Structure is not acyclic")
|
907
|
-
|
908
|
-
while not self.execute_stop:
|
909
|
-
await self.process()
|
910
|
-
await AsyncUtil.sleep(refresh_time)
|