1
1
"""
2
- Module for creating the basic node
2
+ Module for defining BaseNode, an abstract base class for nodes in a graph-based workflow.
3
3
"""
4
+
4
5
from abc import ABC , abstractmethod
5
6
from typing import Optional , List
6
7
import re
7
8
8
9
9
10
class BaseNode (ABC ):
10
11
"""
11
- An abstract base class for nodes in a graph-based workflow. Each node is
12
- intended to perform a specific action when executed as part of the graph's
13
- processing flow.
12
+ An abstract base class for nodes in a graph-based workflow, designed to perform specific actions when executed.
14
13
15
14
Attributes:
16
- node_name (str): A unique identifier for the node.
17
- node_type (str): Specifies the node's type, which influences how the
18
- node interacts within the graph. Valid values are
19
- "node" for standard nodes and "conditional_node" for
20
- nodes that determine the flow based on conditions.
21
-
22
- Methods:
23
- execute(state): An abstract method that subclasses must implement. This
24
- method should contain the logic that the node executes
25
- when it is reached in the graph's flow. It takes the
26
- graph's current state as input and returns the updated
27
- state after execution.
28
-
29
- Args:
30
- node_name (str): The unique identifier name for the node. This name is
31
- used to reference the node within the graph.
32
- node_type (str): The type of the node, limited to "node" or
33
- "conditional_node". This categorization helps in
34
- determining the node's role and behavior within the
35
- graph.
36
-
37
- Raises:
38
- ValueError: If the provided `node_type` is not one of the allowed
39
- values ("node" or "conditional_node"), a ValueError is
40
- raised to indicate the incorrect usage.
15
+ node_name (str): The unique identifier name for the node.
16
+ input (str): Boolean expression defining the input keys needed from the state.
17
+ output (List[str]): List of
18
+ min_input_len (int): Minimum required number of input keys.
19
+ node_config (Optional[dict]): Additional configuration for the node.
20
+
21
+ Example:
22
+ >>> class MyNode(BaseNode):
23
+ ... def execute(self, state):
24
+ ... # Implementation of node logic here
25
+ ... return state
26
+ ...
27
+ >>> my_node = MyNode("ExampleNode", "node", "input_spec", ["output_spec"])
28
+ >>> updated_state = my_node.execute({'key': 'value'})
29
+ {'key': 'value'}
41
30
"""
42
31
43
32
def __init__ (self , node_name : str , node_type : str , input : str , output : List [str ],
44
33
min_input_len : int = 1 , node_config : Optional [dict ] = None ):
45
34
"""
46
- Initialize the node with a unique identifier and a specified node type .
35
+ Initialize the instance with the node's name, type, input/output specifications, and configuration details .
47
36
48
37
Args:
49
- node_name (str): The unique identifier name for the node.
50
- node_type (str): The type of the node, limited to "node" or "conditional_node".
38
+ node_name (str): Name for identifying the node.
39
+ node_type (str): Type of the node; must be 'node' or 'conditional_node'.
40
+ input (str): Expression defining the input keys needed from the state.
41
+ output (List[str]): List of output keys to be updated in the state.
42
+ min_input_len (int, optional): Minimum required number of input keys; defaults to 1.
43
+ node_config (Optional[dict], optional): Additional configuration for the node; defaults to None.
51
44
52
45
Raises:
53
- ValueError: If node_type is not "node" or "conditional_node" .
46
+ ValueError: If ` node_type` is not one of the allowed types .
54
47
"""
48
+
55
49
self .node_name = node_name
56
50
self .input = input
57
51
self .output = output
@@ -66,17 +60,31 @@ def __init__(self, node_name: str, node_type: str, input: str, output: List[str]
66
60
@abstractmethod
67
61
def execute (self , state : dict ) -> dict :
68
62
"""
69
- Execute the node's logic and return the updated state.
63
+ Execute the node's logic based on the current state and update it accordingly.
64
+
70
65
Args:
71
66
state (dict): The current state of the graph.
72
- :return: The updated state after executing this node.
67
+
68
+ Returns:
69
+ dict: The updated state after executing the node's logic.
73
70
"""
71
+
74
72
pass
75
73
76
74
def get_input_keys (self , state : dict ) -> List [str ]:
77
- """Use the _parse_input_keys method to identify which state keys are
78
- needed based on the input attribute
79
75
"""
76
+ Determines the necessary state keys based on the input specification.
77
+
78
+ Args:
79
+ state (dict): The current state of the graph used to parse input keys.
80
+
81
+ Returns:
82
+ List[str]: A list of input keys required for node operation.
83
+
84
+ Raises:
85
+ ValueError: If error occurs in parsing input keys.
86
+ """
87
+
80
88
try :
81
89
input_keys = self ._parse_input_keys (state , self .input )
82
90
self ._validate_input_keys (input_keys )
@@ -86,23 +94,37 @@ def get_input_keys(self, state: dict) -> List[str]:
86
94
f"Error parsing input keys for { self .node_name } : { str (e )} " )
87
95
88
96
def _validate_input_keys (self , input_keys ):
97
+ """
98
+ Validates if the provided input keys meet the minimum length requirement.
99
+
100
+ Args:
101
+ input_keys (List[str]): The list of input keys to validate.
102
+
103
+ Raises:
104
+ ValueError: If the number of input keys is less than the minimum required.
105
+ """
106
+
89
107
if len (input_keys ) < self .min_input_len :
90
108
raise ValueError (
91
109
f"""{ self .node_name } requires at least { self .min_input_len } input keys,
92
110
got { len (input_keys )} .""" )
93
111
94
112
def _parse_input_keys (self , state : dict , expression : str ) -> List [str ]:
95
113
"""
96
- Parses the input keys expression and identifies the corresponding keys
97
- from the state that match the expression logic .
114
+ Parses the input keys expression to extract relevant keys from the state based on logical conditions.
115
+ The expression can contain AND (&), OR (|), and parentheses to group conditions .
98
116
99
117
Args:
100
118
state (dict): The current state of the graph.
101
119
expression (str): The input keys expression to parse.
102
120
103
121
Returns:
104
122
List[str]: A list of key names that match the input keys expression logic.
123
+
124
+ Raises:
125
+ ValueError: If the expression is invalid or if no state keys match the expression.
105
126
"""
127
+
106
128
# Check for empty expression
107
129
if not expression :
108
130
raise ValueError ("Empty expression." )
@@ -142,23 +164,30 @@ def _parse_input_keys(self, state: dict, expression: str) -> List[str]:
142
164
"Missing or unbalanced parentheses in expression." )
143
165
144
166
# Helper function to evaluate an expression without parentheses
145
- def evaluate_simple_expression (exp ):
167
+ def evaluate_simple_expression (exp : str ) -> List [str ]:
168
+ """Evaluate an expression without parentheses."""
169
+
146
170
# Split the expression by the OR operator and process each segment
147
171
for or_segment in exp .split ('|' ):
172
+
148
173
# Check if all elements in an AND segment are in state
149
174
and_segment = or_segment .split ('&' )
150
175
if all (elem .strip () in state for elem in and_segment ):
151
176
return [elem .strip () for elem in and_segment if elem .strip () in state ]
152
177
return []
153
178
154
179
# Helper function to evaluate expressions with parentheses
155
- def evaluate_expression (expression ):
180
+ def evaluate_expression (expression : str ) -> List [str ]:
181
+ """Evaluate an expression with parentheses."""
182
+
156
183
while '(' in expression :
157
184
start = expression .rfind ('(' )
158
185
end = expression .find (')' , start )
159
186
sub_exp = expression [start + 1 :end ]
187
+
160
188
# Replace the evaluated part with a placeholder and then evaluate it
161
189
sub_result = evaluate_simple_expression (sub_exp )
190
+
162
191
# For simplicity in handling, join sub-results with OR to reprocess them later
163
192
expression = expression [:start ] + \
164
193
'|' .join (sub_result ) + expression [end + 1 :]
0 commit comments