""" 子流程节点 """ import logging import time from typing import Any, Dict, List, Optional from sqlalchemy import select from ..base import BaseNode, NodeContext, NodeResult from ..registry import NodeRegistry logger = logging.getLogger(__name__) @NodeRegistry.register class SubflowNode(BaseNode): """ 子流程节点 调用另一个工作流作为子流程执行 """ node_type = 'subflow' node_name = '子流程' node_category = 'flow_control' node_icon = 'workflow' node_description = '调用另一个工作流作为子流程' inputs = [ { 'name': 'input', 'type': 'any', 'description': '输入数据', }, ] outputs = [ { 'name': 'subflow_result', 'type': 'object', 'description': '子流程执行结果', }, ] def execute(self, context: NodeContext) -> NodeResult: """同步执行子流程(不推荐,建议使用异步版本)""" return NodeResult( success=False, error='子流程节点需要异步执行,请使用 execute_async 方法', ) async def execute_async(self, context: NodeContext) -> NodeResult: """异步执行子流程""" try: # 获取配置 subflow_id = self.config.get('subflow_id', '') if not subflow_id: return NodeResult(success=False, error='未配置子流程') # 解析变量引用 subflow_id = context.resolve_template(subflow_id) var_pass_mode = self.config.get('var_pass_mode', 'all') result_pass_mode = self.config.get('result_pass_mode', 'all') result_variable = self.config.get('result_variable', 'subflow_result') max_depth = self.config.get('max_depth', 5) # 检查嵌套深度 current_depth = context.metadata.get('subflow_depth', 0) if current_depth >= max_depth: return NodeResult( success=False, error=f'子流程嵌套深度超过限制 ({max_depth})', ) # 加载子工作流 subflow = await self._load_subflow(context.db_session, subflow_id) if not subflow: return NodeResult(success=False, error=f'子流程不存在: {subflow_id}') # 检查循环依赖 parent_workflows = context.metadata.get('parent_workflows', []) if subflow_id in parent_workflows: return NodeResult( success=False, error=f'检测到循环依赖: 子流程 {subflow_id} 已在调用链中', ) # 准备子流程输入变量 subflow_inputs = self._prepare_inputs(context, var_pass_mode) # 检查是否有恢复状态(从等待中恢复) subflow_state = context.metadata.pop('_subflow_state', None) # 执行子流程 start_time = time.time() if subflow_state: # 从等待状态恢复执行 logger.info(f'[子流程] 从等待状态恢复执行,当前节点: {subflow_state.get("current_node_id")}') subflow_result = await self._resume_subflow( subflow_state, subflow_inputs, context, current_depth + 1, parent_workflows + [subflow_id], ) else: # 正常执行子流程 subflow_result = await self._execute_subflow( subflow, subflow_inputs, context, current_depth + 1, parent_workflows + [subflow_id], ) elapsed_time = int((time.time() - start_time) * 1000) # 检查是否需要转发交互式节点 forward_interactive = self.config.get('forward_interactive', False) if subflow_result.get('waiting_for_input') and forward_interactive: # 转发等待状态到主流程 return self._forward_waiting_state(subflow_result, context, elapsed_time) elif subflow_result.get('waiting_for_input') and not forward_interactive: # 不转发交互式节点,报错 waiting_node = subflow_result.get('logs', [{}])[-1].get('node_label', '未知') return NodeResult( success=False, error=f'子流程中的交互式节点 "{waiting_node}" 需要用户输入,但未开启"转发交互式节点"选项', elapsed_time=elapsed_time, ) # 处理结果 return self._process_result( subflow_result, context, result_pass_mode, result_variable, elapsed_time, ) except Exception as e: logger.exception(f'子流程执行失败: {e}') return NodeResult(success=False, error=f'子流程执行失败: {str(e)}') async def _load_subflow(self, db_session, subflow_id: str): """加载子工作流""" if not db_session: return None from ai_platform.models import AIWorkflow result = await db_session.execute( select(AIWorkflow).where( AIWorkflow.id == subflow_id, AIWorkflow.is_deleted == False ) ) return result.scalar_one_or_none() def _prepare_inputs(self, context: NodeContext, mode: str) -> Dict[str, Any]: """准备子流程输入变量""" if mode == 'none': return {} elif mode == 'all': # 传递所有变量(排除内部变量) return { k: v for k, v in context.variables.items() if not k.startswith('_') } elif mode == 'selected': # 使用新的变量映射结构 var_mappings = self.config.get('var_mappings', []) result = {} for mapping in var_mappings: name = mapping.get('name', '') # 子流程中的变量名 value = mapping.get('value', '') # 主流程中的变量值(可能是模板) if not name: continue # 解析变量值,保留原始数据类型 resolved_value = self._resolve_variable_value(value, context) result[name] = resolved_value return result return {} def _resolve_variable_value(self, value: str, context: NodeContext) -> Any: """ 解析变量值,保留原始数据类型 支持格式: - {{variable_name}} - 直接变量引用 - {{NodeID.key}} - 节点输出引用 """ import re if not isinstance(value, str): return value value = value.strip() # 检查是否是纯变量引用 {{...}} if value.startswith('{{') and value.endswith('}}'): # 检查是否只有一个变量引用(没有其他文本) pattern = r'^\{\{([^}]+)\}\}$' match = re.match(pattern, value) if match: var_ref = match.group(1).strip() # 尝试解析 NodeID.key 格式 if '.' in var_ref: parts = var_ref.split('.', 1) node_id = parts[0] var_key = parts[1] # 先尝试从节点输出命名空间获取 node_outputs = context.variables.get(f'_node_{node_id}') if isinstance(node_outputs, dict) and var_key in node_outputs: return node_outputs[var_key] # 回退:尝试直接从变量获取 node_data = context.variables.get(node_id) if isinstance(node_data, dict) and var_key in node_data: return node_data[var_key] # 直接变量引用 if var_ref in context.variables: return context.variables[var_ref] # 如果包含变量引用但不是纯变量引用,使用 resolve_template(会转换为字符串) if '{{' in value and '}}' in value: return context.resolve_template(value) # 普通字符串值 return value def _get_child_nodes(self, node_map: Dict[str, Any], parent_node_id: str) -> List[str]: """ 获取循环节点的子节点列表 子节点通过 parentNode 属性关联到循环节点 注意:parentNode 可能在节点顶层或 data 中 """ child_nodes = [] for node_id, node_config in node_map.items(): # 检查顶层 parentNode 属性(Vue Flow 标准格式) parent_node = node_config.get('parentNode') # 也检查 data 中的 parentNode(兼容其他格式) if not parent_node: parent_node = node_config.get('data', {}).get('parentNode') if parent_node == parent_node_id: child_nodes.append(node_id) return child_nodes async def _execute_loop_in_subflow( self, node_map: Dict[str, Any], edge_map: Dict[str, List[str]], parallel_edge_map: Dict[str, Dict[str, str]], loop_node_id: str, loop_config: Dict[str, Any], context: NodeContext, logs: List[Dict], subflow_id: str, resume_node_id: str = None, ) -> Dict[str, Any]: """ 在子流程中执行循环体 Args: node_map: 节点映射 edge_map: 边映射 parallel_edge_map: 并行边映射 loop_node_id: 循环节点 ID loop_config: 循环配置 context: 执行上下文 logs: 日志列表 subflow_id: 子流程 ID resume_node_id: 恢复执行的节点 ID(用于从暂停状态恢复) Returns: 循环执行结果 """ loop_mode = context.variables.get('_loop_mode', 'for_each') max_iterations = loop_config.get('max_iterations', 100) item_var_name = loop_config.get('item_variable_name', 'item') index_var_name = loop_config.get('index_variable_name', 'index') # 获取循环体子节点 child_node_ids = self._get_child_nodes(node_map, loop_node_id) logger.info(f'[子流程循环] 通过 parentNode 获取子节点: {child_node_ids}') if not child_node_ids: child_node_ids = loop_config.get('childNodes', []) logger.info(f'[子流程循环] 通过 childNodes 配置获取子节点: {child_node_ids}') if not child_node_ids: logger.warning(f'[子流程循环] 循环节点 {loop_node_id} 没有子节点,node_map keys: {list(node_map.keys())}') return {'success': True, 'total_tokens': 0} # 构建子节点的边映射 child_edge_map = {} for source_id in child_node_ids: targets = edge_map.get(source_id, []) child_targets = [t for t in targets if t in child_node_ids] if child_targets: child_edge_map[source_id] = child_targets # 找到循环体的起始节点 nodes_with_incoming = set() for targets in child_edge_map.values(): nodes_with_incoming.update(targets) start_nodes = [n for n in child_node_ids if n not in nodes_with_incoming] if not start_nodes: start_nodes = [child_node_ids[0]] loop_body_start_id = start_nodes[0] # 从当前循环索引开始 iteration = context.variables.get('_loop_index', 0) total_tokens = 0 logger.info(f'[子流程循环] 开始执行循环,mode={loop_mode}, max_iterations={max_iterations}, start_iteration={iteration}') logger.info(f'[子流程循环] child_node_ids={child_node_ids}, loop_body_start_id={loop_body_start_id}') logger.info(f'[子流程循环] child_edge_map={child_edge_map}') logger.info(f'[子流程循环] _loop_items={context.variables.get("_loop_items", [])}, _loop_index={context.variables.get("_loop_index")}') while iteration < max_iterations: # 检查是否继续循环 if loop_mode == 'for_each': items = context.variables.get('_loop_items', []) current_index = context.variables.get('_loop_index', 0) logger.info(f'[子流程循环] 迭代 {iteration}: items 长度={len(items)}, current_index={current_index}') if current_index >= len(items): logger.info(f'[子流程循环] 退出循环: current_index({current_index}) >= len(items)({len(items)})') break # 设置当前循环项和索引 current_item = items[current_index] context.variables[item_var_name] = current_item context.variables[index_var_name] = current_index context.variables['_current_item'] = current_item context.variables['_current_index'] = current_index context.variables[f'_node_{loop_node_id}'] = { item_var_name: current_item, index_var_name: current_index, 'total': len(items), } elif loop_mode == 'while': from ai_platform.nodes.builtin.loop_node import LoopNode loop_node_instance = LoopNode(loop_config) if not loop_node_instance._evaluate_condition(context): break context.variables[index_var_name] = iteration # 执行循环体 # 如果是恢复执行,从指定节点开始;否则从循环体起始节点开始 current_id = resume_node_id if resume_node_id and resume_node_id in child_node_ids else loop_body_start_id iteration_output = None executed_nodes = set() # 如果是恢复执行,记录日志 if resume_node_id and resume_node_id in child_node_ids: logger.info(f'[子流程循环] 从节点 {resume_node_id} 恢复执行(迭代 {iteration})') # 恢复后只执行一次,然后清除 resume_node_id resume_node_id = None while current_id and current_id in child_node_ids: if current_id in executed_nodes: break executed_nodes.add(current_id) node_config = node_map.get(current_id) if not node_config: break node_type = node_config.get('type', '') node_label = node_config.get('data', {}).get('label', current_id) # 创建节点实例 node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {})) if not node_instance: next_nodes = child_edge_map.get(current_id, []) current_id = next_nodes[0] if next_nodes else None continue # 执行节点 start_time = time.time() result = await node_instance.execute_async(context) elapsed = int((time.time() - start_time) * 1000) # 记录日志 log_entry = { 'node_id': current_id, 'node_type': node_type, 'node_label': node_label, 'status': 'completed' if result.success else 'failed', 'output': result.output, 'error': result.error, 'elapsed_time': elapsed, 'tokens_used': result.tokens_used, 'loop_iteration': iteration, } if result.events: log_entry['events'] = result.events logs.append(log_entry) total_tokens += result.tokens_used # 检查是否需要等待用户输入 if result.waiting_for_input: return { 'success': True, 'waiting_for_input': True, 'waiting_config': result.waiting_config, 'outputs': context.variables, 'logs': logs, 'total_tokens': total_tokens, 'subflow_state': { 'current_node_id': current_id, 'node_map': {k: v for k, v in node_map.items()}, 'edge_map': edge_map, 'parallel_edge_map': parallel_edge_map, 'subflow_id': subflow_id, 'sent_message_nodes': [], 'loop_state': { 'loop_node_id': loop_node_id, 'iteration': iteration, 'current_index': context.variables.get('_loop_index', 0), }, }, } if not result.success: return { 'success': False, 'error': f'子流程循环节点 {node_label} 执行失败: {result.error}', 'outputs': context.variables, 'logs': logs, 'total_tokens': total_tokens, } # 更新上下文 context.variables.update(result.output_variables) context.previous_output = result.output iteration_output = result.output context.variables[f'_node_{current_id}'] = { 'output': result.output, **result.output_variables, } # 下一个节点 if result.next_node_id and result.next_node_id in child_node_ids: current_id = result.next_node_id else: next_nodes = child_edge_map.get(current_id, []) current_id = next_nodes[0] if next_nodes else None # 收集本次迭代结果 loop_results = context.variables.get('_loop_results', []) loop_results.append(iteration_output) context.variables['_loop_results'] = loop_results # 更新循环索引 context.variables['_loop_index'] = context.variables.get('_loop_index', 0) + 1 iteration += 1 # 清理循环变量 context.variables.pop('_loop_items', None) context.variables.pop('_loop_index', None) context.variables.pop('_loop_total', None) context.variables.pop('_loop_mode', None) context.variables.pop('_current_item', None) context.variables.pop('_current_index', None) return {'success': True, 'total_tokens': total_tokens} async def _execute_subflow( self, subflow, inputs: Dict[str, Any], parent_context: NodeContext, depth: int, parent_workflows: List[str], ) -> Dict[str, Any]: """执行子流程""" from ai_platform.nodes import NodeRegistry # 使用已发布版本,如果没有则使用草稿版本 definition = subflow.published_definition or subflow.definition nodes = definition.get('nodes', []) edges = definition.get('edges', []) if not nodes: return {'success': False, 'error': '子流程没有节点', 'logs': []} # 构建节点映射和边映射 node_map = {node['id']: node for node in nodes} edge_map = {} parallel_edge_map = {} for edge in edges: source = edge.get('source', '') target = edge.get('target', '') source_handle = edge.get('sourceHandle', '') if source not in edge_map: edge_map[source] = [] edge_map[source].append(target) if source_handle: if source not in parallel_edge_map: parallel_edge_map[source] = {} parallel_edge_map[source][source_handle] = target # 找到开始节点 start_node = None for node in nodes: if node.get('type') == 'start': start_node = node break if not start_node: return {'success': False, 'error': '子流程没有开始节点', 'logs': []} # 创建子流程上下文 subflow_context = NodeContext( workflow_run_id=f"{parent_context.workflow_run_id}_sub_{subflow.id}", variables=inputs.copy(), user_input=inputs.get('user_input', ''), user_id=parent_context.user_id, conversation_history=parent_context.conversation_history.copy(), db_session=parent_context.db_session, metadata={ 'subflow_depth': depth, 'parent_workflows': parent_workflows, 'parent_run_id': parent_context.workflow_run_id, }, ) # 执行节点序列 logs = [] total_tokens = 0 current_node_id = start_node['id'] while current_node_id: node_config = node_map.get(current_node_id) if not node_config: break node_type = node_config.get('type', '') node_label = node_config.get('data', {}).get('label', current_node_id) # 结束节点 if node_type == 'end': node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {})) if node_instance: result = node_instance.execute(subflow_context) logs.append({ 'node_id': current_node_id, 'node_type': node_type, 'node_label': node_label, 'status': 'completed', 'output': result.output, }) break # 创建并执行节点 node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {})) if not node_instance: logger.warning(f'[子流程] 未知节点类型: {node_type}') next_nodes = edge_map.get(current_node_id, []) current_node_id = next_nodes[0] if next_nodes else None continue # 执行节点 start_time = time.time() result = await node_instance.execute_async(subflow_context) elapsed = int((time.time() - start_time) * 1000) # 循环节点:执行循环体 if node_type == 'loop': loop_node_id = current_node_id loop_config = node_config.get('data', {}) # 记录循环节点初始化日志 logs.append({ 'node_id': current_node_id, 'node_type': node_type, 'node_label': node_label, 'status': 'completed' if result.success else 'failed', 'output': result.output, 'elapsed_time': elapsed, }) if result.success: # 更新上下文变量(循环节点会设置 _loop_items, _loop_index 等) subflow_context.variables.update(result.output_variables) logger.info(f'[子流程] 循环节点初始化完成,_loop_items 长度={len(subflow_context.variables.get("_loop_items", []))}, _loop_index={subflow_context.variables.get("_loop_index")}') # 执行循环体 loop_result = await self._execute_loop_in_subflow( node_map, edge_map, parallel_edge_map, loop_node_id, loop_config, subflow_context, logs, subflow.id ) total_tokens += loop_result.get('total_tokens', 0) # 检查循环是否因等待用户输入而暂停 if loop_result.get('waiting_for_input'): return loop_result # 检查循环是否失败 if not loop_result.get('success', True): return loop_result # 获取循环结果 loop_results = subflow_context.variables.get('_loop_results', []) output_var = loop_config.get('output_variable', 'loop_results') subflow_context.variables[output_var] = loop_results # 循环完成后从循环节点的输出边继续 next_nodes = edge_map.get(loop_node_id, []) # 过滤掉子节点 child_node_ids = self._get_child_nodes(node_map, loop_node_id) next_nodes = [n for n in next_nodes if n not in child_node_ids] current_node_id = next_nodes[0] if next_nodes else None continue # 检查是否需要等待用户输入(交互式节点) if result.waiting_for_input: # 记录等待状态的日志 log_entry = { 'node_id': current_node_id, 'node_type': node_type, 'node_label': node_label, 'status': 'waiting', 'output': result.output, 'elapsed_time': elapsed, } logs.append(log_entry) # 返回等待状态,包含子流程执行状态以便恢复 return { 'success': True, 'waiting_for_input': True, 'waiting_config': result.waiting_config, 'outputs': subflow_context.variables, 'logs': logs, 'total_tokens': total_tokens, # 保存子流程状态以便恢复 'subflow_state': { 'current_node_id': current_node_id, 'node_map': {k: v for k, v in node_map.items()}, 'edge_map': edge_map, 'parallel_edge_map': parallel_edge_map, 'subflow_id': subflow.id, 'sent_message_nodes': [], # 初始化已发送消息节点列表 }, } # 记录日志 log_entry = { 'node_id': current_node_id, 'node_type': node_type, 'node_label': node_label, 'status': 'completed' if result.success else 'failed', 'output': result.output, 'error': result.error, 'elapsed_time': elapsed, 'tokens_used': result.tokens_used, } # 收集消息事件(用于转发到主流程) if result.events: log_entry['events'] = result.events logs.append(log_entry) total_tokens += result.tokens_used if not result.success: return { 'success': False, 'error': f'子流程节点 {node_label} 执行失败: {result.error}', 'outputs': subflow_context.variables, 'logs': logs, 'total_tokens': total_tokens, } # 更新上下文 subflow_context.variables.update(result.output_variables) subflow_context.previous_output = result.output # 保存节点输出到命名空间 subflow_context.variables[f'_node_{current_node_id}'] = { 'output': result.output, **result.output_variables, } # 确定下一个节点 if result.next_node_id: if result.next_node_id in parallel_edge_map.get(current_node_id, {}): current_node_id = parallel_edge_map[current_node_id][result.next_node_id] elif result.next_node_id in node_map: current_node_id = result.next_node_id else: next_nodes = edge_map.get(current_node_id, []) current_node_id = next_nodes[0] if next_nodes else None else: next_nodes = edge_map.get(current_node_id, []) current_node_id = next_nodes[0] if next_nodes else None return { 'success': True, 'outputs': subflow_context.variables, 'logs': logs, 'total_tokens': total_tokens, } def _process_result( self, subflow_result: Dict[str, Any], context: NodeContext, result_pass_mode: str, result_variable: str, elapsed_time: int, ) -> NodeResult: """处理子流程结果""" if not subflow_result.get('success'): return NodeResult( success=False, error=subflow_result.get('error', '子流程执行失败'), elapsed_time=elapsed_time, ) output_vars = {} subflow_outputs = subflow_result.get('outputs', {}) if result_pass_mode == 'all': # 传递所有输出变量(排除内部变量) output_vars = { k: v for k, v in subflow_outputs.items() if not k.startswith('_') } elif result_pass_mode == 'selected': # 传递选定的变量 result_vars = self.config.get('result_vars', []) for var in result_vars: if var in subflow_outputs: output_vars[var] = subflow_outputs[var] # 将完整结果存储到指定变量 output_vars[result_variable] = { 'success': True, 'outputs': {k: v for k, v in subflow_outputs.items() if not k.startswith('_')}, 'logs': subflow_result.get('logs', []), 'total_tokens': subflow_result.get('total_tokens', 0), } # 收集子流程的消息事件(如果配置了显示子流程消息) # 只收集尚未发送的消息(避免重复发送) events = [] show_subflow_messages = self.config.get('show_subflow_messages', False) if show_subflow_messages: subflow_name = self.config.get('subflow_name', '子流程') # 获取已发送的消息节点ID列表(从子流程状态中) subflow_state = subflow_result.get('subflow_state', {}) sent_message_nodes = subflow_state.get('sent_message_nodes', []) for log in subflow_result.get('logs', []): node_id = log.get('node_id') log_events = log.get('events', []) for event in log_events: if event.get('type') == 'message': # 只发送尚未发送过的消息 if node_id not in sent_message_nodes: events.append({ 'type': 'message', 'content': event.get('content', ''), 'message_type': event.get('message_type', 'text'), 'from_subflow': True, 'subflow_name': subflow_name, }) return NodeResult( success=True, output=f"子流程执行成功,共 {len(subflow_result.get('logs', []))} 个节点", output_variables=output_vars, tokens_used=subflow_result.get('total_tokens', 0), elapsed_time=elapsed_time, events=events if events else None, ) def _forward_waiting_state( self, subflow_result: Dict[str, Any], context: NodeContext, elapsed_time: int, ) -> NodeResult: """转发子流程的等待状态到主流程""" subflow_name = self.config.get('subflow_name', '子流程') waiting_config = subflow_result.get('waiting_config', {}) subflow_state = subflow_result.get('subflow_state', {}) # 收集之前节点的消息事件(在等待节点之前的消息应该先显示) # 但只收集尚未发送的消息(避免重复发送) events = [] show_subflow_messages = self.config.get('show_subflow_messages', False) # 获取已发送的消息节点ID列表(从子流程状态中),复制一份避免修改原始列表 sent_message_nodes = list(subflow_state.get('sent_message_nodes', [])) if show_subflow_messages: for log in subflow_result.get('logs', []): # 跳过当前等待节点的日志 if log.get('status') == 'waiting': continue node_id = log.get('node_id') log_events = log.get('events', []) for event in log_events: if event.get('type') == 'message': # 只发送尚未发送过的消息 if node_id not in sent_message_nodes: events.append({ 'type': 'message', 'content': event.get('content', ''), 'message_type': event.get('message_type', 'text'), 'from_subflow': True, 'subflow_name': subflow_name, }) sent_message_nodes.append(node_id) # 在等待配置中添加子流程标识 forwarded_config = { **waiting_config, 'from_subflow': True, 'subflow_name': subflow_name, # 保存子流程状态以便恢复 '_subflow_state': { **subflow_state, 'outputs': subflow_result.get('outputs', {}), 'logs': subflow_result.get('logs', []), 'total_tokens': subflow_result.get('total_tokens', 0), 'sent_message_nodes': sent_message_nodes, # 始终保存,即使为空列表 }, } return NodeResult( success=True, output=f"子流程等待用户输入", waiting_for_input=True, waiting_config=forwarded_config, elapsed_time=elapsed_time, events=events if events else None, ) async def _resume_subflow( self, subflow_state: Dict[str, Any], inputs: Dict[str, Any], parent_context: NodeContext, depth: int, parent_workflows: List[str], ) -> Dict[str, Any]: """从等待状态恢复子流程执行""" # 恢复子流程状态 current_node_id = subflow_state.get('current_node_id') node_map = subflow_state.get('node_map', {}) edge_map = subflow_state.get('edge_map', {}) parallel_edge_map = subflow_state.get('parallel_edge_map', {}) saved_outputs = subflow_state.get('outputs', {}) saved_logs = subflow_state.get('logs', []) saved_tokens = subflow_state.get('total_tokens', 0) loop_state = subflow_state.get('loop_state') # 循环状态 subflow_id = subflow_state.get('subflow_id', '') # 创建子流程上下文,恢复之前的变量 subflow_context = NodeContext( workflow_run_id=f"{parent_context.workflow_run_id}_sub_resume", variables={**saved_outputs, **inputs}, user_input=parent_context.user_input, user_id=parent_context.user_id, conversation_history=parent_context.conversation_history.copy(), db_session=parent_context.db_session, metadata={ 'subflow_depth': depth, 'parent_workflows': parent_workflows, 'parent_run_id': parent_context.workflow_run_id, }, ) # 设置用户输入(交互式节点会读取这个值) subflow_context.variables['__user_input__'] = parent_context.variables.get('__user_input__') logs = saved_logs.copy() total_tokens = saved_tokens # 检查是否从循环中恢复 if loop_state: loop_node_id = loop_state.get('loop_node_id') loop_node_config = node_map.get(loop_node_id, {}) loop_config = loop_node_config.get('data', {}) # 继续执行循环,从暂停的节点恢复 loop_result = await self._execute_loop_in_subflow( node_map, edge_map, parallel_edge_map, loop_node_id, loop_config, subflow_context, logs, subflow_id, resume_node_id=current_node_id # 从暂停的节点继续执行 ) total_tokens += loop_result.get('total_tokens', 0) # 检查循环是否因等待用户输入而暂停 if loop_result.get('waiting_for_input'): return loop_result # 检查循环是否失败 if not loop_result.get('success', True): return loop_result # 获取循环结果 loop_results = subflow_context.variables.get('_loop_results', []) output_var = loop_config.get('output_variable', 'loop_results') subflow_context.variables[output_var] = loop_results # 循环完成后从循环节点的输出边继续 next_nodes = edge_map.get(loop_node_id, []) child_node_ids = self._get_child_nodes(node_map, loop_node_id) next_nodes = [n for n in next_nodes if n not in child_node_ids] current_node_id = next_nodes[0] if next_nodes else None # 清除 __user_input__ if '__user_input__' in subflow_context.variables: del subflow_context.variables['__user_input__'] # 从当前节点继续执行(先重新执行当前等待的节点) while current_node_id: node_config = node_map.get(current_node_id) if not node_config: break node_type = node_config.get('type', '') node_label = node_config.get('data', {}).get('label', current_node_id) # 结束节点 if node_type == 'end': node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {})) if node_instance: result = node_instance.execute(subflow_context) logs.append({ 'node_id': current_node_id, 'node_type': node_type, 'node_label': node_label, 'status': 'completed', 'output': result.output, }) break # 创建并执行节点 node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {})) if not node_instance: logger.warning(f'[子流程恢复] 未知节点类型: {node_type}') next_nodes = edge_map.get(current_node_id, []) current_node_id = next_nodes[0] if next_nodes else None continue # 执行节点 start_time = time.time() result = await node_instance.execute_async(subflow_context) elapsed = int((time.time() - start_time) * 1000) # 循环节点:执行循环体 if node_type == 'loop': loop_node_id = current_node_id loop_config = node_config.get('data', {}) # 记录循环节点初始化日志 logs.append({ 'node_id': current_node_id, 'node_type': node_type, 'node_label': node_label, 'status': 'completed' if result.success else 'failed', 'output': result.output, 'elapsed_time': elapsed, }) if result.success: # 更新上下文变量(循环节点会设置 _loop_items, _loop_index 等) subflow_context.variables.update(result.output_variables) # 执行循环体 loop_result = await self._execute_loop_in_subflow( node_map, edge_map, parallel_edge_map, loop_node_id, loop_config, subflow_context, logs, subflow_id ) total_tokens += loop_result.get('total_tokens', 0) # 检查循环是否因等待用户输入而暂停 if loop_result.get('waiting_for_input'): return loop_result # 检查循环是否失败 if not loop_result.get('success', True): return loop_result # 获取循环结果 loop_results = subflow_context.variables.get('_loop_results', []) output_var = loop_config.get('output_variable', 'loop_results') subflow_context.variables[output_var] = loop_results # 循环完成后从循环节点的输出边继续 next_nodes = edge_map.get(loop_node_id, []) child_node_ids = self._get_child_nodes(node_map, loop_node_id) next_nodes = [n for n in next_nodes if n not in child_node_ids] current_node_id = next_nodes[0] if next_nodes else None continue # 检查是否再次需要等待用户输入 if result.waiting_for_input: log_entry = { 'node_id': current_node_id, 'node_type': node_type, 'node_label': node_label, 'status': 'waiting', 'output': result.output, 'elapsed_time': elapsed, } logs.append(log_entry) return { 'success': True, 'waiting_for_input': True, 'waiting_config': result.waiting_config, 'outputs': subflow_context.variables, 'logs': logs, 'total_tokens': total_tokens, 'subflow_state': { 'current_node_id': current_node_id, 'node_map': node_map, 'edge_map': edge_map, 'parallel_edge_map': parallel_edge_map, 'sent_message_nodes': subflow_state.get('sent_message_nodes', []), }, } # 更新最后一个等待状态的日志为完成 if logs and logs[-1].get('status') == 'waiting' and logs[-1].get('node_id') == current_node_id: logs[-1]['status'] = 'completed' if result.success else 'failed' logs[-1]['output'] = result.output logs[-1]['elapsed_time'] = elapsed else: # 记录新日志 log_entry = { 'node_id': current_node_id, 'node_type': node_type, 'node_label': node_label, 'status': 'completed' if result.success else 'failed', 'output': result.output, 'error': result.error, 'elapsed_time': elapsed, 'tokens_used': result.tokens_used, } if result.events: log_entry['events'] = result.events logs.append(log_entry) total_tokens += result.tokens_used if not result.success: return { 'success': False, 'error': f'子流程节点 {node_label} 执行失败: {result.error}', 'outputs': subflow_context.variables, 'logs': logs, 'total_tokens': total_tokens, } # 更新上下文 subflow_context.variables.update(result.output_variables) subflow_context.previous_output = result.output # 保存节点输出到命名空间 subflow_context.variables[f'_node_{current_node_id}'] = { 'output': result.output, **result.output_variables, } # 清除 __user_input__,避免下一个交互式节点读取到上一个节点的输入 if '__user_input__' in subflow_context.variables: del subflow_context.variables['__user_input__'] # 确定下一个节点 if result.next_node_id: if result.next_node_id in parallel_edge_map.get(current_node_id, {}): current_node_id = parallel_edge_map[current_node_id][result.next_node_id] elif result.next_node_id in node_map: current_node_id = result.next_node_id else: next_nodes = edge_map.get(current_node_id, []) current_node_id = next_nodes[0] if next_nodes else None else: next_nodes = edge_map.get(current_node_id, []) current_node_id = next_nodes[0] if next_nodes else None return { 'success': True, 'outputs': subflow_context.variables, 'logs': logs, 'total_tokens': total_tokens, 'subflow_state': { 'sent_message_nodes': subflow_state.get('sent_message_nodes', []), }, } @classmethod def get_config_schema(cls) -> Dict[str, Any]: """获取配置 Schema""" return { 'type': 'object', 'properties': { 'subflow_id': { 'type': 'string', 'title': '子流程ID', 'description': '要调用的子流程工作流ID', }, 'subflow_name': { 'type': 'string', 'title': '子流程名称', 'description': '子流程显示名称', }, 'var_pass_mode': { 'type': 'string', 'title': '变量传递模式', 'enum': ['all', 'selected', 'none'], 'default': 'all', }, 'selected_vars': { 'type': 'array', 'title': '选择传递的变量', 'items': {'type': 'string'}, }, 'var_mapping': { 'type': 'object', 'title': '变量映射', 'description': '子流程变量名 -> 主流程变量名', }, 'result_pass_mode': { 'type': 'string', 'title': '结果传递模式', 'enum': ['all', 'selected', 'none'], 'default': 'all', }, 'result_vars': { 'type': 'array', 'title': '选择传递的结果变量', 'items': {'type': 'string'}, }, 'result_variable': { 'type': 'string', 'title': '结果变量名', 'default': 'subflow_result', }, 'max_depth': { 'type': 'integer', 'title': '最大嵌套深度', 'default': 5, 'minimum': 1, 'maximum': 10, }, 'show_subflow_messages': { 'type': 'boolean', 'title': '显示子流程消息', 'description': '开启后,子流程中的消息节点会在主流程对话中显示', 'default': False, }, 'forward_interactive': { 'type': 'boolean', 'title': '转发交互式节点', 'description': '开启后,子流程中的问答、选项、确认等节点会在主流程中显示', 'default': False, }, }, 'required': ['subflow_id'], }