Merge branch 'release/v0.2.1' of github.com:SuanmoSuanyangTechnology/MemoryBear into release/v0.2.1
This commit is contained in:
@@ -133,7 +133,7 @@ class WorkflowExecutor:
|
|||||||
for node in self.workflow_config.get("nodes")
|
for node in self.workflow_config.get("nodes")
|
||||||
if node.get("type") in [NodeType.LOOP, NodeType.ITERATION]
|
if node.get("type") in [NodeType.LOOP, NodeType.ITERATION]
|
||||||
], # loop, iteration node id
|
], # loop, iteration node id
|
||||||
"looping": False, # loop runing flag, only use in loop node,not use in main loop
|
"looping": 0, # loop runing flag, only use in loop node,not use in main loop
|
||||||
"activate": {
|
"activate": {
|
||||||
self.start_node_id: True
|
self.start_node_id: True
|
||||||
}
|
}
|
||||||
@@ -358,6 +358,7 @@ class WorkflowExecutor:
|
|||||||
|
|
||||||
elif mode == "updates":
|
elif mode == "updates":
|
||||||
# Handle state updates - store final state
|
# Handle state updates - store final state
|
||||||
|
# TODO:流式输出点
|
||||||
logger.debug(f"[UPDATES] 收到 state 更新 from {list(data.keys())} "
|
logger.debug(f"[UPDATES] 收到 state 更新 from {list(data.keys())} "
|
||||||
f"- execution_id: {self.execution_id}")
|
f"- execution_id: {self.execution_id}")
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,17 @@ from app.core.workflow.variable_pool import VariablePool
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def merget_activate_state(x, y):
|
def merge_activate_state(x, y):
|
||||||
return {
|
return {
|
||||||
k: x.get(k, False) or y.get(k, False)
|
k: x.get(k, False) or y.get(k, False)
|
||||||
for k in set(x) | set(y)
|
for k in set(x) | set(y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def merge_looping_state(x, y):
|
||||||
|
return y if y > x else x
|
||||||
|
|
||||||
|
|
||||||
class WorkflowState(TypedDict):
|
class WorkflowState(TypedDict):
|
||||||
"""Workflow state
|
"""Workflow state
|
||||||
|
|
||||||
@@ -36,7 +40,7 @@ class WorkflowState(TypedDict):
|
|||||||
|
|
||||||
# Set of loop node IDs, used for assigning values in loop nodes
|
# Set of loop node IDs, used for assigning values in loop nodes
|
||||||
cycle_nodes: list
|
cycle_nodes: list
|
||||||
looping: Annotated[bool, lambda x, y: x and y]
|
looping: Annotated[int, merge_looping_state]
|
||||||
|
|
||||||
# Input variables (passed from configured variables)
|
# Input variables (passed from configured variables)
|
||||||
# Uses a deep merge function, supporting nested dict updates (e.g., conv.xxx)
|
# Uses a deep merge function, supporting nested dict updates (e.g., conv.xxx)
|
||||||
@@ -68,7 +72,7 @@ class WorkflowState(TypedDict):
|
|||||||
streaming_buffer: Annotated[dict[str, Any], lambda x, y: {**x, **y}]
|
streaming_buffer: Annotated[dict[str, Any], lambda x, y: {**x, **y}]
|
||||||
|
|
||||||
# node activate status
|
# node activate status
|
||||||
activate: Annotated[dict[str, bool], merget_activate_state]
|
activate: Annotated[dict[str, bool], merge_activate_state]
|
||||||
|
|
||||||
|
|
||||||
class BaseNode(ABC):
|
class BaseNode(ABC):
|
||||||
|
|||||||
@@ -28,6 +28,6 @@ class BreakNode(BaseNode):
|
|||||||
Returns:
|
Returns:
|
||||||
Optional dictionary indicating the loop has been stopped.
|
Optional dictionary indicating the loop has been stopped.
|
||||||
"""
|
"""
|
||||||
state["looping"] = False
|
state["looping"] = 2
|
||||||
logger.info(f"Setting cycle node exit flag, cycle={self.cycle}, looping={state['looping']}")
|
logger.info(f"Setting cycle node exit flag, cycle={self.cycle}, looping={state['looping']}")
|
||||||
|
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ class IterationRuntime:
|
|||||||
idx: Index of the element in the input array.
|
idx: Index of the element in the input array.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A deep copy of the workflow state with iteration-specific variables set.
|
A copy of the workflow state with iteration-specific variables set.
|
||||||
"""
|
"""
|
||||||
loopstate = WorkflowState(
|
loopstate = WorkflowState(
|
||||||
**copy.deepcopy(self.state)
|
**self.state
|
||||||
)
|
)
|
||||||
loopstate["runtime_vars"][self.node_id] = {
|
loopstate["runtime_vars"][self.node_id] = {
|
||||||
"item": item,
|
"item": item,
|
||||||
@@ -71,7 +71,7 @@ class IterationRuntime:
|
|||||||
"item": item,
|
"item": item,
|
||||||
"index": idx,
|
"index": idx,
|
||||||
}
|
}
|
||||||
loopstate["looping"] = True
|
loopstate["looping"] = 1
|
||||||
loopstate["activate"][self.start_id] = True
|
loopstate["activate"][self.start_id] = True
|
||||||
return loopstate
|
return loopstate
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ class IterationRuntime:
|
|||||||
self.result.extend(output)
|
self.result.extend(output)
|
||||||
else:
|
else:
|
||||||
self.result.append(output)
|
self.result.append(output)
|
||||||
if not result["looping"]:
|
if result["looping"] == 2:
|
||||||
self.looping = False
|
self.looping = False
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -150,10 +150,9 @@ class IterationRuntime:
|
|||||||
self.result.extend(output)
|
self.result.extend(output)
|
||||||
else:
|
else:
|
||||||
self.result.append(output)
|
self.result.append(output)
|
||||||
if not result["looping"]:
|
if result["looping"] == 2:
|
||||||
self.looping = False
|
self.looping = False
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
logger.info(f"Iteration node {self.node_id}: execution completed")
|
logger.info(f"Iteration node {self.node_id}: execution completed")
|
||||||
return {
|
return {
|
||||||
"output": self.result,
|
"output": self.result,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class LoopRuntime:
|
|||||||
self.state = state
|
self.state = state
|
||||||
self.node_id = node_id
|
self.node_id = node_id
|
||||||
self.typed_config = LoopNodeConfig(**config)
|
self.typed_config = LoopNodeConfig(**config)
|
||||||
|
self.looping = True
|
||||||
|
|
||||||
def _init_loop_state(self):
|
def _init_loop_state(self):
|
||||||
"""
|
"""
|
||||||
@@ -88,7 +89,7 @@ class LoopRuntime:
|
|||||||
loopstate = WorkflowState(
|
loopstate = WorkflowState(
|
||||||
**self.state
|
**self.state
|
||||||
)
|
)
|
||||||
loopstate["looping"] = True
|
loopstate["looping"] = 1
|
||||||
loopstate["activate"][self.start_id] = True
|
loopstate["activate"][self.start_id] = True
|
||||||
return loopstate
|
return loopstate
|
||||||
|
|
||||||
@@ -179,9 +180,12 @@ class LoopRuntime:
|
|||||||
loopstate = self._init_loop_state()
|
loopstate = self._init_loop_state()
|
||||||
loop_time = self.typed_config.max_loop
|
loop_time = self.typed_config.max_loop
|
||||||
child_state = []
|
child_state = []
|
||||||
while self.evaluate_conditional(loopstate) and loopstate["looping"] and loop_time > 0:
|
while self.evaluate_conditional(loopstate) and self.looping and loop_time > 0:
|
||||||
logger.info(f"loop node {self.node_id}: running")
|
logger.info(f"loop node {self.node_id}: running")
|
||||||
child_state.append(await self.graph.ainvoke(loopstate))
|
result = await self.graph.ainvoke(loopstate)
|
||||||
|
child_state.append(result)
|
||||||
|
if result["looping"] == 2:
|
||||||
|
self.looping = False
|
||||||
loop_time -= 1
|
loop_time -= 1
|
||||||
|
|
||||||
logger.info(f"loop node {self.node_id}: execution completed")
|
logger.info(f"loop node {self.node_id}: execution completed")
|
||||||
|
|||||||
@@ -91,15 +91,11 @@ const VariableSelect: FC<VariableSelectProps> = ({
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
showSearch
|
showSearch
|
||||||
allowClear={allowClear}
|
allowClear={allowClear}
|
||||||
|
optionFilterProp="value"
|
||||||
filterOption={(input, option) => {
|
filterOption={(input, option) => {
|
||||||
if (input === '/') return true;
|
if (input === '/') return true;
|
||||||
if (option?.options) {
|
const value = 'value' in option! ? option.value as string : '';
|
||||||
return option.label?.toLowerCase().includes(input.toLowerCase()) ||
|
return value.toLowerCase().includes(input.toLowerCase());
|
||||||
option.options.some((opt: any) =>
|
|
||||||
opt.value.toLowerCase().includes(input.toLowerCase())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return option?.label?.toLowerCase().includes(input.toLowerCase()) ?? false;
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -135,6 +135,78 @@ export const getCurrentNodeVariables = (nodeData: any, values: any): Suggestion[
|
|||||||
return nodeData.type === 'var-aggregator' && !nodeData.config.group.defaultValue ? [] : list;
|
return nodeData.type === 'var-aggregator' && !nodeData.config.group.defaultValue ? [] : list;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getChildNodeVariables = (
|
||||||
|
selectedNode: Node,
|
||||||
|
graphRef: React.MutableRefObject<Graph | undefined>
|
||||||
|
): Suggestion[] => {
|
||||||
|
const graph = graphRef.current;
|
||||||
|
if (!graph) return [];
|
||||||
|
|
||||||
|
const list: Suggestion[] = [];
|
||||||
|
const nodes = graph.getNodes();
|
||||||
|
const edges = graph.getEdges();
|
||||||
|
const keys = new Set<string>();
|
||||||
|
|
||||||
|
const childNodes = nodes.filter(node => node.getData()?.cycle === selectedNode.id);
|
||||||
|
|
||||||
|
const getConnectedNodes = (nodeId: string, visited = new Set<string>()): string[] => {
|
||||||
|
if (visited.has(nodeId)) return [];
|
||||||
|
visited.add(nodeId);
|
||||||
|
const prev = edges.filter(e => e.getTargetCellId() === nodeId).map(e => e.getSourceCellId());
|
||||||
|
return [...prev, ...prev.flatMap(id => getConnectedNodes(id, visited))];
|
||||||
|
};
|
||||||
|
|
||||||
|
const relevantIds = new Set<string>();
|
||||||
|
childNodes.forEach(child => {
|
||||||
|
relevantIds.add(child.id);
|
||||||
|
getConnectedNodes(child.id).forEach(id => relevantIds.add(id));
|
||||||
|
});
|
||||||
|
|
||||||
|
relevantIds.forEach(id => {
|
||||||
|
const node = nodes.find(n => n.id === id);
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
const nodeData = node.getData();
|
||||||
|
const nodeId = nodeData.id;
|
||||||
|
const { type } = nodeData;
|
||||||
|
|
||||||
|
if (type in NODE_VARIABLES) {
|
||||||
|
NODE_VARIABLES[type as keyof typeof NODE_VARIABLES].forEach(({ label, dataType, field }) => {
|
||||||
|
const varKey = `${nodeId}_${label}`;
|
||||||
|
if (!keys.has(varKey)) {
|
||||||
|
keys.add(varKey);
|
||||||
|
list.push({
|
||||||
|
key: varKey,
|
||||||
|
label,
|
||||||
|
type: 'variable',
|
||||||
|
dataType,
|
||||||
|
value: `${nodeId}.${field}`,
|
||||||
|
nodeData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'parameter-extractor') {
|
||||||
|
(nodeData.config?.params?.defaultValue || []).forEach((p: any) => {
|
||||||
|
if (p?.name && !keys.has(`${nodeId}_${p.name}`)) {
|
||||||
|
keys.add(`${nodeId}_${p.name}`);
|
||||||
|
list.push({
|
||||||
|
key: `${nodeId}_${p.name}`,
|
||||||
|
label: p.name,
|
||||||
|
type: 'variable',
|
||||||
|
dataType: p.type || 'string',
|
||||||
|
value: `${nodeId}.${p.name}`,
|
||||||
|
nodeData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
|
||||||
export const useVariableList = (
|
export const useVariableList = (
|
||||||
selectedNode: Node | null | undefined,
|
selectedNode: Node | null | undefined,
|
||||||
graphRef: React.MutableRefObject<Graph | undefined>,
|
graphRef: React.MutableRefObject<Graph | undefined>,
|
||||||
@@ -187,13 +259,13 @@ export const useVariableList = (
|
|||||||
} else if (pd.type === 'iteration' && pd.config.input.defaultValue) {
|
} else if (pd.type === 'iteration' && pd.config.input.defaultValue) {
|
||||||
let itemType = 'object';
|
let itemType = 'object';
|
||||||
const iv = list.find(v => `{{${v.value}}}` === pd.config.input.defaultValue);
|
const iv = list.find(v => `{{${v.value}}}` === pd.config.input.defaultValue);
|
||||||
if (iv?.dataType.startsWith('array[')) itemType = iv.dataType.replace(/^array\[(.+)\]$/, '$1');
|
if (iv?.dataType.startsWith('array[')) {itemType = iv.dataType.replace(/^array\[(.+)\]$/, '$1');}
|
||||||
addVariable(list, keys, `${pid}_item`, 'item', itemType, `${pid}.item`, pd);
|
addVariable(list, keys, `${pid}_item`, 'item', itemType, `${pid}.item`, pd);
|
||||||
addVariable(list, keys, `${pid}_index`, 'index', 'number', `${pid}.index`, pd);
|
addVariable(list, keys, `${pid}_index`, 'index', 'number', `${pid}.index`, pd);
|
||||||
} else if (pd.type === 'iteration' && !pd.config.input.defaultValue) {
|
} else if (pd.type === 'iteration' && !pd.config.input.defaultValue) {
|
||||||
let itemType = 'object';
|
let itemType = 'object';
|
||||||
const iv = list.find(v => `{{${v.value}}}` === pd.config.input.defaultValue);
|
const iv = list.find(v => `{{${v.value}}}` === pd.config.input.defaultValue);
|
||||||
if (iv?.dataType.startsWith('array[')) itemType = iv.dataType.replace(/^array\[(.+)\]$/, '$1');
|
if (iv?.dataType.startsWith('array[')) {itemType = iv.dataType.replace(/^array\[(.+)\]$/, '$1');}
|
||||||
addVariable(list, keys, `${pid}_item`, 'item', 'string', `${pid}.item`, pd);
|
addVariable(list, keys, `${pid}_item`, 'item', 'string', `${pid}.item`, pd);
|
||||||
addVariable(list, keys, `${pid}_index`, 'index', 'number', `${pid}.index`, pd);
|
addVariable(list, keys, `${pid}_index`, 'index', 'number', `${pid}.index`, pd);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import AssignmentList from './AssignmentList'
|
|||||||
import ToolConfig from './ToolConfig'
|
import ToolConfig from './ToolConfig'
|
||||||
import MemoryConfig from './MemoryConfig'
|
import MemoryConfig from './MemoryConfig'
|
||||||
import VariableList from './VariableList'
|
import VariableList from './VariableList'
|
||||||
import { useVariableList, getCurrentNodeVariables } from './hooks/useVariableList'
|
import { useVariableList, getCurrentNodeVariables, getChildNodeVariables } from './hooks/useVariableList'
|
||||||
import styles from './properties.module.css'
|
import styles from './properties.module.css'
|
||||||
import Editor from "../Editor";
|
import Editor from "../Editor";
|
||||||
import RbSlider from './RbSlider'
|
import RbSlider from './RbSlider'
|
||||||
@@ -290,141 +290,26 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
let filteredList = addParentIterationVars(variableList).filter(variable => variable.dataType === 'string' || variable.dataType === 'number');
|
let filteredList = addParentIterationVars(variableList).filter(variable => variable.dataType === 'string' || variable.dataType === 'number');
|
||||||
return filteredList;
|
return filteredList;
|
||||||
}
|
}
|
||||||
if (nodeType === 'iteration' && key === 'output') {
|
if (nodeType === 'iteration' && key === 'output' || nodeType === 'loop' && key === 'condition') {
|
||||||
let filteredList = variableList.filter(variable => variable.value.includes('sys.'));
|
if (!selectedNode) return [];
|
||||||
// Add child node output variables for loop nodes
|
let filteredList = nodeType === 'iteration'
|
||||||
if (selectedNode) {
|
? variableList.filter(variable => variable.value.includes('sys.'))
|
||||||
const graph = graphRef.current;
|
: addParentIterationVars(variableList).filter(variable => variable.nodeData.type !== 'loop');
|
||||||
if (graph) {
|
|
||||||
const nodes = graph.getNodes();
|
const childVariables = getChildNodeVariables(selectedNode, graphRef);
|
||||||
const childNodes = nodes.filter(node => {
|
const existingKeys = new Set(filteredList.map(v => v.key));
|
||||||
const nodeData = node.getData();
|
childVariables.forEach(v => {
|
||||||
return nodeData?.cycle === selectedNode.id;
|
if (!existingKeys.has(v.key)) {
|
||||||
});
|
filteredList.push(v);
|
||||||
|
existingKeys.add(v.key);
|
||||||
// Add output variables from child nodes
|
|
||||||
childNodes.forEach(childNode => {
|
|
||||||
const childData = childNode.getData();
|
|
||||||
const childNodeId = childData.id;
|
|
||||||
|
|
||||||
// Add child node output variables based on their type
|
|
||||||
switch (childData.type) {
|
|
||||||
case 'llm':
|
|
||||||
case 'jinja-render':
|
|
||||||
case 'tool':
|
|
||||||
const outputKey = `${childNodeId}_output`;
|
|
||||||
const existingOutput = filteredList.find(v => v.key === outputKey);
|
|
||||||
if (!existingOutput) {
|
|
||||||
filteredList.push({
|
|
||||||
key: outputKey,
|
|
||||||
label: 'output',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'string',
|
|
||||||
value: `${childNodeId}.output`,
|
|
||||||
nodeData: childData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'http-request':
|
|
||||||
const bodyKey = `${childNodeId}_body`;
|
|
||||||
const statusKey = `${childNodeId}_status_code`;
|
|
||||||
if (!filteredList.find(v => v.key === bodyKey)) {
|
|
||||||
filteredList.push({
|
|
||||||
key: bodyKey,
|
|
||||||
label: 'body',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'string',
|
|
||||||
value: `${childNodeId}.body`,
|
|
||||||
nodeData: childData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!filteredList.find(v => v.key === statusKey)) {
|
|
||||||
filteredList.push({
|
|
||||||
key: statusKey,
|
|
||||||
label: 'status_code',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'number',
|
|
||||||
value: `${childNodeId}.status_code`,
|
|
||||||
nodeData: childData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return filteredList;
|
return filteredList;
|
||||||
}
|
}
|
||||||
if (nodeType === 'iteration') {
|
if (nodeType === 'iteration') {
|
||||||
return variableList.filter(variable => variable.dataType.includes('array'));
|
return variableList.filter(variable => variable.dataType.includes('array'));
|
||||||
}
|
}
|
||||||
if (nodeType === 'loop' && key === 'condition') {
|
|
||||||
let filteredList = addParentIterationVars(variableList).filter(variable => variable.nodeData.type !== 'loop');
|
|
||||||
|
|
||||||
// Add child node output variables for loop nodes
|
|
||||||
if (selectedNode) {
|
|
||||||
const graph = graphRef.current;
|
|
||||||
if (graph) {
|
|
||||||
const nodes = graph.getNodes();
|
|
||||||
const childNodes = nodes.filter(node => {
|
|
||||||
const nodeData = node.getData();
|
|
||||||
return nodeData?.cycle === selectedNode.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add output variables from child nodes
|
|
||||||
childNodes.forEach(childNode => {
|
|
||||||
const childData = childNode.getData();
|
|
||||||
const childNodeId = childData.id;
|
|
||||||
|
|
||||||
// Add child node output variables based on their type
|
|
||||||
switch(childData.type) {
|
|
||||||
case 'llm':
|
|
||||||
case 'jinja-render':
|
|
||||||
case 'tool':
|
|
||||||
const outputKey = `${childNodeId}_output`;
|
|
||||||
const existingOutput = filteredList.find(v => v.key === outputKey);
|
|
||||||
if (!existingOutput) {
|
|
||||||
filteredList.push({
|
|
||||||
key: outputKey,
|
|
||||||
label: 'output',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'string',
|
|
||||||
value: `${childNodeId}.output`,
|
|
||||||
nodeData: childData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'http-request':
|
|
||||||
const bodyKey = `${childNodeId}_body`;
|
|
||||||
const statusKey = `${childNodeId}_status_code`;
|
|
||||||
if (!filteredList.find(v => v.key === bodyKey)) {
|
|
||||||
filteredList.push({
|
|
||||||
key: bodyKey,
|
|
||||||
label: 'body',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'string',
|
|
||||||
value: `${childNodeId}.body`,
|
|
||||||
nodeData: childData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!filteredList.find(v => v.key === statusKey)) {
|
|
||||||
filteredList.push({
|
|
||||||
key: statusKey,
|
|
||||||
label: 'status_code',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'number',
|
|
||||||
value: `${childNodeId}.status_code`,
|
|
||||||
nodeData: childData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredList;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For all other node types, add parent iteration variables if applicable
|
// For all other node types, add parent iteration variables if applicable
|
||||||
let baseList = variableList;
|
let baseList = variableList;
|
||||||
|
|||||||
Reference in New Issue
Block a user