"""
AI 工作流服务

数据权限：
- 使用 list_workflows_with_data_scope() 自动应用数据权限
- 支持本人、本部门、本部门及下级、全部等数据范围
"""
import copy
import logging
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from queue import Queue
from threading import Lock
from datetime import datetime
from typing import Any, Dict, Generator, List, Optional

from sqlalchemy import select, func, and_
from sqlalchemy.ext.asyncio import AsyncSession

from ai_platform.models import AIWorkflow, AIWorkflowVersion, AIWorkflowRun
from ai_platform.nodes import NodeContext, NodeRegistry, NodeResult
from app.data_scope_utils import get_data_scope_filter, apply_data_scope_to_conditions

logger = logging.getLogger(__name__)


def _apply_node_result_warnings(event: Dict[str, Any], result: NodeResult) -> Dict[str, Any]:
    """将节点 metadata.warnings 透传到 SSE node_complete 事件"""
    warnings = (result.metadata or {}).get('warnings')
    if warnings:
        event['warnings'] = warnings
    return event


def _resolve_workflow_definition(workflow: AIWorkflow, use_draft: bool) -> dict:
    if use_draft:
        return workflow.definition or {}
    return workflow.published_definition or workflow.definition or {}


def _resolve_trigger_type(use_draft: bool, trigger_type: Optional[str] = None) -> str:
    if trigger_type:
        return trigger_type
    return 'editor_draft' if use_draft else 'editor_published'


def _node_label_from_map(node_map: Dict[str, Any], node_id: str) -> str:
    node = node_map.get(node_id) or {}
    data = node.get('data') or {}
    return data.get('label') or node.get('label') or node_id


def _make_execution_log_entry(
    node_map: Dict[str, Any],
    node_id: str,
    node_type: str,
    **extra: Any,
) -> dict:
    return {
        'node_id': node_id,
        'node_type': node_type,
        'node_label': _node_label_from_map(node_map, node_id),
        **extra,
    }


def _snapshot_node_inputs(context: NodeContext) -> dict:
    return {
        'variables': dict(context.variables),
        'previous_output': context.previous_output,
    }

# 资源类型（用于数据权限配置）
RESOURCE_TYPE = "ai_workflow"
RESOURCE_DISPLAY_NAME = "AI工作流"


class AIWorkflowService:
    """
    AI 工作流服务
    
    管理工作流的执行
    """
    
    def __init__(self, db: Optional[AsyncSession] = None):
        self._db = db

    def _create_workflow_run_record(
        self,
        workflow: AIWorkflow,
        inputs: Dict[str, Any],
        *,
        app_id: Optional[str] = None,
        conversation_id: Optional[str] = None,
        use_draft: bool = False,
        trigger_type: Optional[str] = None,
    ) -> AIWorkflowRun:
        from utils.context import get_current_user_id_from_context

        definition = _resolve_workflow_definition(workflow, use_draft)
        return AIWorkflowRun(
            workflow_id=workflow.id,
            app_id=app_id,
            conversation_id=conversation_id,
            user_id=get_current_user_id_from_context(),
            status='running',
            inputs=inputs,
            trigger_type=_resolve_trigger_type(use_draft, trigger_type),
            use_draft=use_draft,
            workflow_version=workflow.published_version if not use_draft else None,
            definition_snapshot=copy.deepcopy(definition),
            started_at=datetime.now(),
        )
    
    async def get_workflow(self, workflow_id: str) -> Optional[AIWorkflow]:
        """获取工作流"""
        if not self._db:
            return None
        result = await self._db.execute(
            select(AIWorkflow).where(AIWorkflow.id == workflow_id, AIWorkflow.is_deleted == False)
        )
        return result.scalar_one_or_none()
    
    async def get_workflow_by_code(self, code: str) -> Optional[AIWorkflow]:
        """根据编码获取工作流"""
        if not self._db:
            return None
        result = await self._db.execute(
            select(AIWorkflow).where(AIWorkflow.code == code, AIWorkflow.is_deleted == False)
        )
        return result.scalar_one_or_none()
    
    async def list_workflows(
        self,
        page: int = 1,
        page_size: int = 20,
        status: str = None,
        name: str = None,
    ) -> tuple:
        """
        获取工作流列表
        
        Returns:
            (workflows, total)
        """
        if not self._db:
            return [], 0
        
        query = select(AIWorkflow).where(AIWorkflow.is_deleted == False)
        
        if status:
            query = query.where(AIWorkflow.status == status)
        if name:
            query = query.where(AIWorkflow.name.ilike(f"%{name}%"))
        
        # 获取总数
        count_result = await self._db.execute(
            select(func.count()).select_from(query.subquery())
        )
        total = count_result.scalar() or 0
        
        # 分页
        offset = (page - 1) * page_size
        query = query.offset(offset).limit(page_size)
        
        result = await self._db.execute(query)
        workflows = list(result.scalars().all())
        
        return workflows, total

    async def list_workflows_with_data_scope(
        self,
        page: int = 1,
        page_size: int = 20,
        status: str = None,
        name: str = None,
        workflow_type: str = None,
        application_id: str = None,
    ) -> tuple:
        """
        获取工作流列表（带数据权限过滤）
        
        自动从上下文获取当前用户信息，应用数据权限过滤
        
        Returns:
            (workflows, total)
        """
        if not self._db:
            return [], 0
        
        conditions = [AIWorkflow.is_deleted == False]
        
        if status:
            conditions.append(AIWorkflow.status == status)
        if name:
            conditions.append(AIWorkflow.name.ilike(f"%{name}%"))
        if workflow_type:
            conditions.append(AIWorkflow.workflow_type == workflow_type)
        if application_id:
            conditions.append(AIWorkflow.application_id == application_id)
        else:
            conditions.append(AIWorkflow.application_id.is_(None))
        
        # 获取数据权限过滤条件并应用
        data_scope_filter = await get_data_scope_filter(self._db, RESOURCE_TYPE)
        scope_conditions = apply_data_scope_to_conditions(AIWorkflow, data_scope_filter)
        conditions.extend(scope_conditions)
        
        # 获取总数
        query = select(AIWorkflow).where(and_(*conditions))
        count_result = await self._db.execute(
            select(func.count()).select_from(query.subquery())
        )
        total = count_result.scalar() or 0
        
        # 分页
        offset = (page - 1) * page_size
        query = query.order_by(AIWorkflow.sort.desc(), AIWorkflow.sys_create_datetime.desc())
        query = query.offset(offset).limit(page_size)
        
        result = await self._db.execute(query)
        workflows = list(result.scalars().all())
        
        return workflows, total
    
    async def create_workflow(self, data: Dict) -> AIWorkflow:
        """创建工作流"""
        if not self._db:
            raise ValueError("数据库会话未初始化")
        
        workflow = AIWorkflow(
            name=data.get('name', ''),
            code=data.get('code', ''),
            description=data.get('description', ''),
            definition=data.get('definition', {}),
            input_variables=data.get('input_variables', []),
            output_variables=data.get('output_variables', []),
        )
        
        # 自动填充创建人和部门
        from utils.context import get_current_user_info_from_context
        user_info = get_current_user_info_from_context()
        if user_info:
            if not workflow.sys_creator_id:
                workflow.sys_creator_id = user_info.get('user_id')
            if not workflow.sys_dept_id and user_info.get('dept_id'):
                workflow.sys_dept_id = user_info.get('dept_id')
        
        self._db.add(workflow)
        await self._db.commit()
        await self._db.refresh(workflow)
        return workflow
    
    async def update_workflow(self, workflow_id: str, data: Dict) -> Optional[AIWorkflow]:
        """更新工作流"""
        if not self._db:
            return None
        
        result = await self._db.execute(
            select(AIWorkflow).where(AIWorkflow.id == workflow_id, AIWorkflow.is_deleted == False)
        )
        workflow = result.scalar_one_or_none()
        if not workflow:
            return None
        
        for key, value in data.items():
            if hasattr(workflow, key) and key not in ('id', 'code'):
                setattr(workflow, key, value)
        
        await self._db.commit()
        await self._db.refresh(workflow)
        return workflow
    
    async def delete_workflow(self, workflow_id: str) -> bool:
        """删除工作流"""
        if not self._db:
            return False
        
        result = await self._db.execute(
            select(AIWorkflow).where(AIWorkflow.id == workflow_id, AIWorkflow.is_deleted == False)
        )
        workflow = result.scalar_one_or_none()
        if not workflow:
            return False
        
        workflow.is_deleted = True
        await self._db.commit()
        return True
    
    async def publish_workflow(self, workflow_id: str, description: str = '') -> Optional[AIWorkflow]:
        """
        发布工作流
        
        1. 创建版本记录
        2. 更新工作流的发布状态
        """
        if not self._db:
            return None
        
        result = await self._db.execute(
            select(AIWorkflow).where(AIWorkflow.id == workflow_id, AIWorkflow.is_deleted == False)
        )
        workflow = result.scalar_one_or_none()
        if not workflow:
            return None
        
        # 计算新版本号
        new_version = workflow.version or 1
        
        # 创建版本记录
        version = AIWorkflowVersion(
            workflow_id=workflow_id,
            version=new_version,
            definition=workflow.definition,
            description=description,
        )
        self._db.add(version)
        
        # 更新工作流状态
        workflow.status = 'published'
        workflow.published_version = new_version
        workflow.published_at = datetime.now()
        workflow.published_definition = workflow.definition
        workflow.version = new_version + 1  # 草稿版本号 +1
        
        await self._db.commit()
        await self._db.refresh(workflow)
        
        return workflow
    
    async def rollback_workflow(self, workflow_id: str, version: int) -> Optional[AIWorkflow]:
        """
        回滚到指定版本
        
        将指定版本的定义复制到当前草稿
        """
        if not self._db:
            return None
        
        result = await self._db.execute(
            select(AIWorkflow).where(AIWorkflow.id == workflow_id, AIWorkflow.is_deleted == False)
        )
        workflow = result.scalar_one_or_none()
        if not workflow:
            return None
        
        version_result = await self._db.execute(
            select(AIWorkflowVersion).where(
                AIWorkflowVersion.workflow_id == workflow_id,
                AIWorkflowVersion.version == version,
                AIWorkflowVersion.is_deleted == False
            )
        )
        version_obj = version_result.scalar_one_or_none()
        if not version_obj:
            return None
        
        # 将版本定义复制到当前草稿
        workflow.definition = version_obj.definition
        
        await self._db.commit()
        await self._db.refresh(workflow)
        
        return workflow
    
    async def run_workflow(
        self,
        workflow_id: str,
        inputs: Dict[str, Any],
        app_id: str = None,
        conversation_id: str = None,
        use_draft: bool = False,
        trigger_type: Optional[str] = None,
    ) -> AIWorkflowRun:
        """
        运行工作流
        
        Args:
            workflow_id: 工作流 ID
            inputs: 输入数据
            app_id: 关联应用 ID
            conversation_id: 关联对话 ID
            use_draft: 是否使用草稿版本（编辑器调试用）
            
        Returns:
            AIWorkflowRun
        """
        if not self._db:
            raise ValueError("数据库会话未初始化")
        
        workflow = await self.get_workflow(workflow_id)
        if not workflow:
            raise ValueError(f'工作流不存在: {workflow_id}')
        
        if not use_draft and workflow.status != 'published':
            raise ValueError('工作流未发布')
        
        # 创建运行记录
        run = self._create_workflow_run_record(
            workflow,
            inputs,
            app_id=app_id,
            conversation_id=conversation_id,
            use_draft=use_draft,
            trigger_type=trigger_type or 'api',
        )
        self._db.add(run)
        await self._db.flush()
        
        try:
            # 执行工作流（异步）
            result = await self._execute_workflow(
                workflow, inputs, run, use_draft=use_draft
            )
            
            # 更新运行记录
            run.status = 'completed'
            run.outputs = dict(result.get('outputs', {}))
            run.execution_log = list(result.get('logs', []))
            run.total_tokens = result.get('total_tokens', 0)
            run.total_steps = result.get('total_steps', 0)
            run.completed_at = datetime.now()
            run.elapsed_time = int((run.completed_at - run.started_at).total_seconds() * 1000)
            
            # 更新工作流统计
            workflow.run_count = (workflow.run_count or 0) + 1
            workflow.success_count = (workflow.success_count or 0) + 1
            
            await self._db.commit()
            
        except Exception as e:
            logger.exception(f'工作流执行失败: {e}')
            run.status = 'failed'
            run.error_message = str(e)
            run.completed_at = datetime.now()
            run.elapsed_time = int((run.completed_at - run.started_at).total_seconds() * 1000)
            
            workflow.run_count = (workflow.run_count or 0) + 1
            
            await self._db.commit()
            
            raise
        
        return run
    
    async def _execute_workflow(
        self,
        workflow: AIWorkflow,
        inputs: Dict[str, Any],
        run: AIWorkflowRun,
        use_draft: bool = False,
    ) -> Dict[str, Any]:
        """
        执行工作流（异步版本）
        
        Args:
            workflow: 工作流定义
            inputs: 输入数据
            run: 运行记录
            use_draft: 是否使用草稿版本（编辑器调试用）
            
        Returns:
            执行结果
        """
        # 编辑器调试模式使用草稿版本，否则使用已发布版本
        if use_draft:
            definition = workflow.definition
        else:
            definition = workflow.published_definition or workflow.definition
        nodes = definition.get('nodes', [])
        edges = definition.get('edges', [])
        
        if not nodes:
            raise ValueError('工作流没有节点')
        
        # 构建节点映射和边映射
        node_map = {node['id']: node for node in nodes}
        edge_map = {}  # source_id -> [target_ids]
        # 并行节点的分支映射：source_id -> {sourceHandle -> target_id}
        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)
            # 记录 sourceHandle 映射（用于并行节点）
            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:
            raise ValueError('找不到开始节点')
        
        # 初始化上下文
        context = NodeContext(
            workflow_run_id=str(run.id),
            variables=inputs.copy(),
            user_input=inputs.get('user_input', ''),
            user_id=str(run.user_id) if run.user_id else '',
            db_session=self._db,
        )
        
        # 执行节点
        logs = []
        total_tokens = 0
        total_steps = 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_inputs = _snapshot_node_inputs(context)
            
            # 结束节点
            if node_type == 'end':
                node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {}))
                if node_instance:
                    result = node_instance.execute(context)
                    logs.append(_make_execution_log_entry(
                        node_map,
                        current_node_id,
                        node_type,
                        status='completed',
                        output=result.output,
                        inputs=copy.deepcopy(node_inputs),
                    ))
                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(context)
            elapsed = int((time.time() - start_time) * 1000)
            
            # 记录日志
            logs.append(_make_execution_log_entry(
                node_map,
                current_node_id,
                node_type,
                status='completed' if result.success else 'failed',
                output=result.output,
                error=result.error,
                elapsed_time=elapsed,
                tokens_used=result.tokens_used,
                inputs=copy.deepcopy(node_inputs),
            ))
            
            total_tokens += result.tokens_used
            total_steps += 1
            
            # 更新运行记录的当前节点并提交
            run.current_node_id = current_node_id
            run.execution_log = logs.copy()
            if self._db:
                await self._db.commit()
            
            if not result.success:
                raise ValueError(f'节点执行失败: {result.error}')
            
            # 合并输出变量到上下文
            context.variables.update(result.output_variables)
            context.previous_output = result.output
            
            # 保存节点输出到命名空间（支持 {{NodeID.key}} 格式引用）
            context.variables[f'_node_{current_node_id}'] = {
                'output': result.output,
                **result.output_variables,
            }
            
            # 确定下一个节点
            if node_type == 'parallel':
                # 并行节点：执行所有分支
                parallel_results = await self._execute_parallel_branches(
                    node_map, edge_map, parallel_edge_map, current_node_id, context, run, logs
                )
                # 将并行结果存入上下文
                context.set_variable('_parallel_results', parallel_results['results'])
                total_tokens += parallel_results['total_tokens']
                total_steps += parallel_results['total_steps']
                logs.extend(parallel_results['logs'])
                
                # 找到合并节点继续执行
                current_node_id = parallel_results.get('merge_node_id')
            elif result.next_node_id:
                # 检查是否是分支 ID（条件/意图分支节点返回的是分支 ID，需要通过边映射找到实际目标节点）
                logger.info(f'节点 {current_node_id} 返回 next_node_id={result.next_node_id}')
                logger.info(f'parallel_edge_map[{current_node_id}]={parallel_edge_map.get(current_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]
                    logger.info(f'通过分支映射找到目标节点: {current_node_id}')
                elif result.next_node_id in node_map:
                    # 是实际的节点 ID
                    current_node_id = result.next_node_id
                    logger.info(f'直接使用节点 ID: {current_node_id}')
                else:
                    # 分支 ID 未找到对应边，尝试按普通边查找
                    logger.warning(f'分支 ID {result.next_node_id} 未找到对应边，回退到普通边查找')
                    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 {
            'outputs': context.variables,
            'logs': logs,
            'total_tokens': total_tokens,
            'total_steps': total_steps,
        }
    
    async def _execute_parallel_branches(
        self,
        node_map: Dict[str, Any],
        edge_map: Dict[str, List[str]],
        parallel_edge_map: Dict[str, Dict[str, str]],
        parallel_node_id: str,
        context: NodeContext,
        run: AIWorkflowRun,
        parent_logs: List[Dict],
    ) -> Dict[str, Any]:
        """
        执行并行分支（异步版本）
        
        Args:
            node_map: 节点映射
            edge_map: 边映射
            parallel_edge_map: 并行节点的分支映射 {source_id: {sourceHandle: target_id}}
            parallel_node_id: 并行节点 ID
            context: 执行上下文
            run: 运行记录
            parent_logs: 父级日志列表
            
        Returns:
            并行执行结果
        """
        import asyncio
        
        # 获取并行节点的分支映射（sourceHandle -> target_id）
        branch_mapping = parallel_edge_map.get(parallel_node_id, {})
        
        # 如果没有分支映射，回退到普通边映射
        if not branch_mapping:
            branch_start_nodes = edge_map.get(parallel_node_id, [])
            # 创建默认的分支映射
            branch_mapping = {f'branch_{i}': node_id for i, node_id in enumerate(branch_start_nodes)}
        
        branch_start_nodes = list(branch_mapping.values())
        
        if not branch_start_nodes:
            return {
                'results': {},
                'logs': [],
                'total_tokens': 0,
                'total_steps': 0,
                'merge_node_id': None,
            }
        
        # 找到合并节点（所有分支最终汇聚的节点）
        merge_node_id = self._find_merge_node(node_map, edge_map, branch_start_nodes)
        
        # 并行执行各分支
        branch_results = {}
        all_logs = []
        total_tokens = 0
        total_steps = 0
        
        async def execute_branch(branch_id: str, branch_start_id: str, branch_context: NodeContext) -> Dict:
            """执行单个分支（异步）"""
            branch_logs = []
            branch_tokens = 0
            branch_steps = 0
            current_id = branch_start_id
            branch_output = None
            
            while current_id and current_id != merge_node_id:
                node_config = node_map.get(current_id)
                if not node_config:
                    break
                
                node_type = node_config.get('type', '')
                
                # 跳过结束节点和合并节点
                if node_type in ('end', 'merge'):
                    break
                
                # 创建节点实例
                node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {}))
                if not node_instance:
                    next_nodes = 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(branch_context)
                elapsed = int((time.time() - start_time) * 1000)
                
                branch_logs.append({
                    'node_id': current_id,
                    'node_type': node_type,
                    'status': 'completed' if result.success else 'failed',
                    'output': result.output,
                    'error': result.error,
                    'elapsed_time': elapsed,
                    'tokens_used': result.tokens_used,
                    'branch': branch_start_id,
                })
                
                branch_tokens += result.tokens_used
                branch_steps += 1
                
                if not result.success:
                    return {
                        'branch_id': branch_id,
                        'success': False,
                        'output': None,
                        'error': result.error,
                        'logs': branch_logs,
                        'tokens': branch_tokens,
                        'steps': branch_steps,
                    }
                
                # 更新分支上下文
                branch_context.variables.update(result.output_variables)
                branch_context.previous_output = result.output
                branch_output = result.output
                
                # 下一个节点
                if result.next_node_id:
                    current_id = result.next_node_id
                else:
                    next_nodes = edge_map.get(current_id, [])
                    # 过滤掉合并节点
                    next_nodes = [n for n in next_nodes if n != merge_node_id]
                    current_id = next_nodes[0] if next_nodes else None
            
            return {
                'branch_id': branch_id,
                'success': True,
                'output': branch_output,
                'logs': branch_logs,
                'tokens': branch_tokens,
                'steps': branch_steps,
                'variables': branch_context.variables,
            }
        
        # 创建反向映射：target_node_id -> branch_id (sourceHandle)
        target_to_branch = {v: k for k, v in branch_mapping.items()}
        
        # 创建所有分支的任务
        tasks = []
        for target_node_id in branch_start_nodes:
            # 为每个分支创建独立的上下文副本
            branch_context = NodeContext(
                workflow_run_id=context.workflow_run_id,
                variables=copy.deepcopy(context.variables),
                user_input=context.user_input,
                user_id=context.user_id,
                conversation_history=context.conversation_history.copy(),
                previous_output=context.previous_output,
                db_session=context.db_session,
            )
            # 获取分支 ID（sourceHandle）
            branch_id = target_to_branch.get(target_node_id, target_node_id)
            tasks.append(execute_branch(branch_id, target_node_id, branch_context))
        
        # 并行执行所有分支
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        for result in results:
            if isinstance(result, Exception):
                logger.exception(f'分支执行失败: {result}')
                all_logs.append({
                    'node_id': 'unknown',
                    'node_type': 'branch',
                    'status': 'failed',
                    'error': str(result),
                })
            else:
                branch_id = result.get('branch_id')
                branch_results[branch_id] = result.get('output')
                all_logs.extend(result.get('logs', []))
                total_tokens += result.get('tokens', 0)
                total_steps += result.get('steps', 0)
                
                # 合并分支变量到主上下文
                if result.get('variables'):
                    for key, value in result['variables'].items():
                        if not key.startswith('_'):  # 不合并内部变量
                            context.variables[f'{branch_id}_{key}'] = value
        
        return {
            'results': branch_results,
            'logs': all_logs,
            'total_tokens': total_tokens,
            'total_steps': total_steps,
            'merge_node_id': merge_node_id,
        }
    
    async def _execute_parallel_branches_stream(
        self,
        node_map: Dict[str, Any],
        edge_map: Dict[str, List[str]],
        parallel_edge_map: Dict[str, Dict[str, str]],
        parallel_node_id: str,
        context: NodeContext,
        run: AIWorkflowRun,
        parent_logs: List[Dict],
    ):
        """
        流式执行并行分支，yield 每个节点的执行事件（异步版本）
        
        Args:
            node_map: 节点映射
            edge_map: 边映射
            parallel_edge_map: 并行节点的分支映射
            parallel_node_id: 并行节点 ID
            context: 执行上下文
            run: 运行记录
            parent_logs: 父级日志列表
            
        Yields:
            dict: 节点执行事件
        """
        import asyncio
        
        # 获取并行节点的分支映射
        branch_mapping = parallel_edge_map.get(parallel_node_id, {})
        
        if not branch_mapping:
            branch_start_nodes = edge_map.get(parallel_node_id, [])
            branch_mapping = {f'branch_{i}': node_id for i, node_id in enumerate(branch_start_nodes)}
        
        branch_start_nodes = list(branch_mapping.values())
        
        if not branch_start_nodes:
            yield {
                'type': '_parallel_result',
                'data': {
                    'results': {},
                    'logs': [],
                    'total_tokens': 0,
                    'total_steps': 0,
                    'merge_node_id': None,
                }
            }
            return
        
        merge_node_id = self._find_merge_node(node_map, edge_map, branch_start_nodes)
        
        branch_results = {}
        all_logs = []
        total_tokens = 0
        total_steps = 0
        
        target_to_branch = {v: k for k, v in branch_mapping.items()}
        
        # 顺序执行各分支（为了能够yield事件）
        for target_node_id in branch_start_nodes:
            branch_id = target_to_branch.get(target_node_id, target_node_id)
            branch_context = NodeContext(
                workflow_run_id=context.workflow_run_id,
                variables=copy.deepcopy(context.variables),
                user_input=context.user_input,
                user_id=context.user_id,
                conversation_history=context.conversation_history.copy(),
                previous_output=context.previous_output,
                db_session=context.db_session,
            )
            
            branch_logs = []
            branch_tokens = 0
            branch_steps = 0
            current_id = target_node_id
            branch_output = None
            
            while current_id and current_id != merge_node_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)
                
                if node_type in ('end', 'merge'):
                    break
                
                node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {}))
                if not node_instance:
                    next_nodes = edge_map.get(current_id, [])
                    current_id = next_nodes[0] if next_nodes else None
                    continue
                
                # 发送节点开始事件
                yield {
                    'type': 'node_start',
                    'node_id': current_id,
                    'node_type': node_type,
                    'node_label': node_label,
                    'branch_id': branch_id,
                    'timestamp': datetime.now().isoformat(),
                    'inputs': {
                        'variables': dict(branch_context.variables),
                        'previous_output': branch_context.previous_output,
                    },
                }
                
                # 执行节点（异步）
                start_time = time.time()
                result = await node_instance.execute_async(branch_context)
                elapsed = int((time.time() - start_time) * 1000)
                
                branch_logs.append({
                    'node_id': current_id,
                    'node_type': node_type,
                    'status': 'completed' if result.success else 'failed',
                    'output': result.output,
                    'error': result.error,
                    'elapsed_time': elapsed,
                    'tokens_used': result.tokens_used,
                    'branch': branch_id,
                })
                
                branch_tokens += result.tokens_used
                branch_steps += 1
                
                # 发送节点完成事件
                yield _apply_node_result_warnings({
                    'type': 'node_complete',
                    'node_id': current_id,
                    'node_type': node_type,
                    'branch_id': branch_id,
                    'status': 'success' if result.success else 'failed',
                    'elapsed_time': elapsed,
                    'tokens_used': result.tokens_used,
                    'error_message': result.error if not result.success else None,
                    'outputs': {
                        'output': result.output,
                        'output_variables': result.metadata.get('frontend_output_variables', result.output_variables),
                    },
                }, result)
                
                if not result.success:
                    break
                
                branch_context.variables.update(result.output_variables)
                branch_context.previous_output = result.output
                branch_output = result.output
                
                # 保存节点输出到命名空间
                branch_context.variables[f'_node_{current_id}'] = {
                    'output': result.output,
                    **result.output_variables,
                }
                
                if result.next_node_id:
                    current_id = result.next_node_id
                else:
                    next_nodes = edge_map.get(current_id, [])
                    next_nodes = [n for n in next_nodes if n != merge_node_id]
                    current_id = next_nodes[0] if next_nodes else None
            
            # 收集分支结果
            branch_results[branch_id] = branch_output
            all_logs.extend(branch_logs)
            total_tokens += branch_tokens
            total_steps += branch_steps
            
            # 合并分支变量到主上下文
            for key, value in branch_context.variables.items():
                if not key.startswith('_'):
                    context.variables[f'{branch_id}_{key}'] = value
        
        # 返回最终结果
        yield {
            'type': '_parallel_result',
            'data': {
                'results': branch_results,
                'logs': all_logs,
                'total_tokens': total_tokens,
                'total_steps': total_steps,
                'merge_node_id': merge_node_id,
            }
        }
    
    def _find_merge_node(
        self,
        node_map: Dict[str, Any],
        edge_map: Dict[str, List[str]],
        branch_start_nodes: List[str],
    ) -> Optional[str]:
        """
        查找合并节点
        
        遍历所有分支，找到它们共同汇聚的合并节点
        """
        # 简单实现：查找类型为 merge 的节点
        for node_id, node_config in node_map.items():
            if node_config.get('type') == 'merge':
                return node_id
        
        # 如果没有显式的合并节点，尝试找到所有分支汇聚的节点
        # 这里使用简单的方法：找到第一个被多个节点指向的节点
        incoming_count = {}
        for source, targets in edge_map.items():
            for target in targets:
                incoming_count[target] = incoming_count.get(target, 0) + 1
        
        for node_id, count in incoming_count.items():
            if count >= len(branch_start_nodes):
                return node_id
        
        return None
    
    def _get_child_nodes(
        self,
        node_map: Dict[str, Any],
        parent_node_id: str,
    ) -> List[str]:
        """
        获取循环节点的子节点列表
        
        子节点通过 parentNode 属性关联到循环节点
        """
        child_nodes = []
        for node_id, node_config in node_map.items():
            # 检查节点配置中的 parentNode 属性
            parent_node = node_config.get('parentNode')
            if parent_node == parent_node_id:
                child_nodes.append(node_id)
                logger.info(f'[Loop] Found child node: {node_id} with parentNode={parent_node}')
        
        logger.info(f'[Loop] Parent node {parent_node_id} has {len(child_nodes)} child nodes: {child_nodes}')
        return child_nodes
    
    async def _execute_loop(
        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,
        run: AIWorkflowRun,
        parent_logs: List[Dict],
    ):
        """
        执行循环体（单节点设计，异步版本）
        
        循环体的子节点通过 parentNode 属性关联到循环节点，
        或者通过 childNodes 配置指定
        
        Args:
            node_map: 节点映射
            edge_map: 边映射
            parallel_edge_map: 并行边映射
            loop_node_id: 循环节点 ID
            loop_config: 循环配置
            context: 执行上下文
            run: 运行记录
            parent_logs: 父级日志列表
            
        Yields:
            循环执行事件
        """
        import asyncio
        loop_mode = context.get_variable('_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')
        
        # 获取循环体子节点
        # 方式1: 通过 parentNode 属性关联
        child_node_ids = self._get_child_nodes(node_map, loop_node_id)
        
        # 方式2: 通过 childNodes 配置指定
        if not child_node_ids:
            child_node_ids = loop_config.get('childNodes', [])
        
        if not child_node_ids:
            logger.warning(f'循环节点 {loop_node_id} 没有子节点')
            return
        
        # 构建子节点的边映射（只包含子节点之间的边）
        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.get_variable('_loop_index', 0)
        logger.info(f'[Loop] Starting loop execution, mode={loop_mode}, max_iterations={max_iterations}, start_iteration={iteration}')
        logger.info(f'[Loop] _loop_items={context.get_variable("_loop_items", [])}')
        logger.info(f'[Loop] _loop_index={context.get_variable("_loop_index", 0)}')
        
        while iteration < max_iterations:
            # 检查是否继续循环
            if loop_mode == 'for_each':
                items = context.get_variable('_loop_items', [])
                current_index = context.get_variable('_loop_index', 0)
                logger.info(f'[Loop] Iteration {iteration}: items={items}, current_index={current_index}')
                if current_index >= len(items):
                    logger.info(f'[Loop] Breaking loop: current_index({current_index}) >= len(items)({len(items)})')
                    break
                # 设置当前循环项和索引
                current_item = items[current_index]
                context.set_variable(item_var_name, current_item)
                context.set_variable(index_var_name, current_index)
                context.set_variable('_current_item', current_item)
                context.set_variable('_current_index', current_index)
                # 同时存储到循环节点的命名空间，支持 {{loop_node_id.item.xxx}} 格式
                context.variables[f'_node_{loop_node_id}'] = {
                    item_var_name: current_item,
                    index_var_name: current_index,
                    'total': len(items),
                }
            elif loop_mode == 'while':
                # 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.set_variable(index_var_name, iteration)
            
            # 发送迭代开始事件
            total_iterations = len(items) if loop_mode == 'for_each' else None
            yield {
                'type': 'loop_iteration_start',
                'node_id': loop_node_id,
                'iteration': iteration,
                'total': total_iterations,
                'item': context.get_variable(item_var_name) if loop_mode == 'for_each' else None,
            }
            
            # 执行循环体
            iteration_logs = []
            iteration_tokens = 0
            iteration_steps = 0
            current_id = loop_body_start_id
            iteration_output = None
            executed_nodes = set()
            
            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_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
                
                # 发送节点开始事件
                yield {
                    'type': 'node_start',
                    'node_id': current_id,
                    'node_type': node_type,
                    'node_label': node_config.get('data', {}).get('label', ''),
                    'loop_iteration': iteration,
                    'inputs': {
                        'previous_output': context.previous_output,
                        'variables': {
                            '_current_item': context.get_variable('_current_item'),
                            '_current_index': context.get_variable('_current_index'),
                            '_loop_total': context.get_variable('_loop_total'),
                        }
                    },
                }
                
                # 执行节点（异步）
                start_time = time.time()
                logger.info(f'[Loop] Executing child node: {current_id}, type={node_type}')
                result = await node_instance.execute_async(context)
                elapsed = int((time.time() - start_time) * 1000)
                logger.info(f'[Loop] Child node result: success={result.success}, output={result.output}, error={result.error}')
                
                iteration_logs.append({
                    'node_id': current_id,
                    'node_type': node_type,
                    'status': 'completed' if result.success else 'failed',
                    'output': result.output,
                    'error': result.error,
                    'elapsed_time': elapsed,
                    'tokens_used': result.tokens_used,
                    'loop_iteration': iteration,
                })
                
                iteration_tokens += result.tokens_used
                iteration_steps += 1
                
                # 发送节点执行完成事件
                yield _apply_node_result_warnings({
                    'type': 'node_complete',
                    'node_id': current_id,
                    'node_type': node_type,
                    'status': 'success' if result.success else 'failed',
                    'elapsed_time': elapsed,
                    'tokens_used': result.tokens_used,
                    'error_message': result.error,
                    'loop_iteration': iteration,
                    'outputs': {
                        'output': result.output,
                        'output_variables': result.metadata.get('frontend_output_variables', result.output_variables),
                    },
                    'inputs': {
                        'previous_output': context.previous_output,
                        'variables': {
                            '_current_item': context.get_variable('_current_item'),
                            '_current_index': context.get_variable('_current_index'),
                            '_loop_total': context.get_variable('_loop_total'),
                        }
                    },
                }, result)
                
                # 发送节点产生的事件（如消息事件）
                if result.events:
                    for event in result.events:
                        yield {
                            'type': 'node_event',
                            'node_id': current_id,
                            'event': event,
                            'loop_iteration': iteration,
                        }
                
                # 检查是否需要等待用户输入（设计预览节点等）
                if result.waiting_for_input:
                    # 记录日志
                    iteration_logs.append({
                        'node_id': current_id,
                        'node_type': node_type,
                        'status': 'waiting',
                        'elapsed_time': elapsed,
                        'loop_iteration': iteration,
                    })
                    
                    # 更新运行记录为等待状态
                    run.status = 'waiting'
                    run.current_node_id = current_id
                    run.execution_log = parent_logs + iteration_logs
                    run.outputs = dict(context.variables)
                    run.waiting_config = dict(result.waiting_config) if result.waiting_config else {}
                    # 保存循环状态以便恢复
                    run.waiting_config['_loop_state'] = {
                        'loop_node_id': loop_node_id,
                        'iteration': iteration,
                        'current_index': context.get_variable('_loop_index', 0),
                    }
                    if self._db:
                        await self._db.commit()
                    
                    # 发送等待事件
                    yield {
                        'type': 'waiting_input',
                        'node_id': current_id,
                        'node_type': node_type,
                        'config': result.waiting_config,
                        'loop_iteration': iteration,
                    }
                    # 暂停循环执行，等待用户输入
                    return
                
                if not result.success:
                    logger.error(f'[Loop] 子节点执行失败，发送 loop_iteration_error 事件: {result.error}')
                    yield {
                        'type': 'loop_iteration_error',
                        'node_id': loop_node_id,
                        'iteration': iteration,
                        'error': result.error,
                    }
                    # 循环中断
                    return
                
                # 更新上下文
                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.get_variable('_loop_results', [])
            loop_results.append(iteration_output)
            context.set_variable('_loop_results', loop_results)
            
            # 更新循环索引
            context.set_variable('_loop_index', context.get_variable('_loop_index', 0) + 1)
            
            # 添加日志
            parent_logs.extend(iteration_logs)
            
            # 发送迭代完成事件
            yield {
                'type': 'loop_iteration_complete',
                'node_id': loop_node_id,
                'iteration': iteration,
                'total': total_iterations,
                'output': iteration_output,
                'tokens_used': iteration_tokens,
                'steps': iteration_steps,
            }
            
            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)
    
    async def run_workflow_stream_async(
        self,
        workflow_id: str,
        inputs: Dict[str, Any],
        app_id: str = None,
        conversation_id: str = None,
        conversation_history: List[Dict[str, str]] = None,
        use_draft: bool = False,
        trigger_type: Optional[str] = None,
    ):
        """
        异步流式运行工作流
        
        Args:
            workflow_id: 工作流 ID
            inputs: 输入变量
            app_id: 应用 ID
            conversation_id: 对话 ID
            conversation_history: 对话历史
            use_draft: 是否使用草稿版本
        
        Yields:
            dict: 节点执行状态事件
        """
        if not self._db:
            yield {'type': 'error', 'message': '数据库会话未初始化'}
            return
        
        workflow = await self.get_workflow(workflow_id)
        if not workflow:
            yield {'type': 'error', 'message': f'工作流不存在: {workflow_id}'}
            return
        
        # 编辑器调试模式不检查发布状态
        if not use_draft and workflow.status != 'published':
            yield {'type': 'error', 'message': '工作流未发布'}
            return
        
        # 创建运行记录
        run = self._create_workflow_run_record(
            workflow,
            inputs,
            app_id=app_id,
            conversation_id=conversation_id,
            use_draft=use_draft,
            trigger_type=trigger_type,
        )
        self._db.add(run)
        await self._db.flush()
        
        try:
            # 使用异步生成器执行工作流
            async for event in self._execute_workflow_stream(workflow, inputs, run, conversation_history, use_draft):
                yield event
                
                # 在关键事件后提交数据库状态
                event_type = event.get('type')
                if event_type in ('node_complete', 'waiting_input', 'complete', 'error'):
                    await self._db.commit()
            
            # 更新运行记录
            if run.status == 'waiting':
                # 等待状态，已在循环中提交
                pass
            elif run.status != 'completed':
                run.status = 'completed'
                run.completed_at = datetime.now()
                run.elapsed_time = int((run.completed_at - run.started_at).total_seconds() * 1000)
                
                # 更新工作流统计
                workflow.run_count = (workflow.run_count or 0) + 1
                workflow.success_count = (workflow.success_count or 0) + 1
                
                await self._db.commit()
            
        except Exception as e:
            logger.exception(f'工作流执行失败: {e}')
            run.status = 'failed'
            run.error_message = str(e)
            run.completed_at = datetime.now()
            run.elapsed_time = int((run.completed_at - run.started_at).total_seconds() * 1000)
            
            workflow.run_count = (workflow.run_count or 0) + 1
            
            await self._db.commit()
            
            # 发送错误事件给前端
            yield {
                'type': 'error',
                'message': str(e),
                'error_message': str(e),
                'run_id': run.id,
                'workflow_id': workflow.id,
            }
    
    async def _execute_workflow_stream(
        self,
        workflow: AIWorkflow,
        inputs: Dict[str, Any],
        run: AIWorkflowRun,
        conversation_history: List[Dict[str, str]] = None,
        use_draft: bool = False,
    ):
        """
        流式执行工作流，逐节点 yield 状态（异步版本）
        
        Args:
            workflow: 工作流对象
            inputs: 输入变量
            run: 工作流运行记录
            conversation_history: 对话历史（用于 LLM 节点的上下文记忆）
            use_draft: 是否使用草稿版本（编辑器调试用）
        """
        import asyncio
        # 编辑器调试模式使用草稿版本，否则使用已发布版本
        if use_draft:
            definition = workflow.definition
        else:
            definition = workflow.published_definition or workflow.definition
        nodes = definition.get('nodes', [])
        edges = definition.get('edges', [])
        
        if not nodes:
            raise ValueError('工作流没有节点')
        
        # 构建节点映射和边映射
        node_map = {node['id']: node for node in nodes}
        edge_map = {}
        # 并行节点的分支映射：source_id -> {sourceHandle -> target_id}
        parallel_edge_map = {}
        
        # 调试：打印所有节点的 parentNode 属性
        for node in nodes:
            if node.get('parentNode'):
                logger.info(f'[流式] 节点 {node["id"]} 的 parentNode={node.get("parentNode")}')
        
        logger.info(f'[流式] 边列表: {edges}')
        for edge in edges:
            source = edge.get('source', '')
            target = edge.get('target', '')
            source_handle = edge.get('sourceHandle', '')
            logger.info(f'[流式] 边: source={source}, target={target}, sourceHandle={source_handle}')
            if source not in edge_map:
                edge_map[source] = []
            edge_map[source].append(target)
            # 记录 sourceHandle 映射（用于并行节点）
            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:
            raise ValueError('找不到开始节点')
        
        # 初始化上下文
        context = NodeContext(
            workflow_run_id=str(run.id),
            variables=inputs.copy(),
            user_input=inputs.get('user_input', ''),
            user_id=str(run.user_id) if run.user_id else '',
            conversation_history=conversation_history or [],
            db_session=self._db,
        )
        
        logs = []
        total_tokens = 0
        total_steps = 0
        current_node_id = start_node['id']
        
        # 发送工作流开始事件（包含 run_id，用于对话流恢复）
        yield {
            'type': 'start',
            'run_id': str(run.id),
            'workflow_id': str(workflow.id),
            'workflow_name': workflow.name,
        }
        
        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)
            
            # 获取节点输入（当前上下文变量的快照）
            node_inputs = {
                'variables': dict(context.variables),
                'previous_output': context.previous_output,
            }
            
            # 发送节点开始事件（包含时间戳和输入数据）
            yield {
                'type': 'node_start',
                'node_id': current_node_id,
                'node_type': node_type,
                'node_label': node_label,
                'timestamp': datetime.now().isoformat(),
                'inputs': node_inputs,
            }
            
            # 结束节点
            if node_type == 'end':
                node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {}))
                if node_instance:
                    result = node_instance.execute(context)
                    logs.append(_make_execution_log_entry(
                        node_map,
                        current_node_id,
                        node_type,
                        status='completed',
                        output=result.output,
                        inputs=copy.deepcopy(node_inputs),
                    ))
                    yield {
                        'type': 'node_complete',
                        'node_id': current_node_id,
                        'node_type': node_type,
                        'status': 'success',
                        'elapsed_time': 0,
                        'tokens_used': 0,
                    }
                    
                    # 保存结束节点输出和上下文变量到运行记录
                    run.execution_log = logs.copy()
                    # 只有当结束节点有输出时才包含 output 字段
                    if result.output is not None:
                        run.outputs = {
                            **context.variables,
                            'output': result.output,  # 结束节点的输出内容
                        }
                    else:
                        run.outputs = dict(context.variables)
                    
                    # 更新运行状态为完成
                    run.status = 'completed'
                    run.completed_at = datetime.now()
                    run.elapsed_time = int((run.completed_at - run.started_at).total_seconds() * 1000)
                    
                    # 发送 complete 事件
                    yield {
                        'type': 'complete',
                        'run_id': str(run.id),
                        'status': 'completed',
                        'outputs': run.outputs,
                        'elapsed_time': run.elapsed_time,
                        'total_tokens': total_tokens,
                    }
                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()
            
            # LLM 节点：根据配置决定是否使用流式执行
            node_data = node_config.get('data', {})
            use_streaming = node_data.get('use_streaming', False) if node_type == 'llm' else False
            if node_type == 'llm' and use_streaming and hasattr(node_instance, 'execute_stream'):
                result = None
                stream_gen = node_instance.execute_stream(context)
                
                try:
                    while True:
                        # 在线程池中获取下一个chunk
                        chunk_event = await asyncio.get_event_loop().run_in_executor(
                            None, next, stream_gen
                        )
                        # 发送 LLM 流式内容事件
                        if chunk_event.content:
                            yield {
                                'type': 'llm_chunk',
                                'node_id': current_node_id,
                                'content': chunk_event.content,
                                'accumulated_content': chunk_event.accumulated_content,
                            }
                except StopIteration as e:
                    # 生成器结束，获取返回值（NodeResult）
                    result = e.value
                
                if result is None:
                    result = NodeResult(success=False, error='LLM 节点执行异常')
            else:
                # 其他节点使用异步执行
                result = await node_instance.execute_async(context)
            
            elapsed = int((time.time() - start_time) * 1000)
            
            # 处理节点事件（如消息节点发送消息）
            if result.events:
                for event in result.events:
                    if event.get('type') == 'message':
                        # 消息事件直接发送为 answer 类型
                        yield {
                            'type': 'answer',
                            'node_id': current_node_id,
                            'content': event.get('content', ''),
                        }
                    else:
                        # 其他事件保持 node_event 格式
                        yield {
                            'type': 'node_event',
                            'node_id': current_node_id,
                            'event': event,
                        }
            
            # 检查是否需要等待用户输入（对话流节点）
            if result.waiting_for_input:
                # 记录日志
                log_entry = _make_execution_log_entry(
                    node_map,
                    current_node_id,
                    node_type,
                    status='waiting',
                    output=result.output,
                    elapsed_time=elapsed,
                    inputs=copy.deepcopy(node_inputs),
                )
                logs.append(log_entry)
                
                # 调试：打印等待时保存的变量
                logger.info(f'等待用户输入，保存变量到 run.outputs，变量键: {list(context.variables.keys())}')
                node_keys = [k for k in context.variables.keys() if k.startswith('_node_')]
                logger.info(f'等待用户输入，_node_ 前缀的变量: {node_keys}')
                
                # 更新运行记录为等待状态
                run.status = 'waiting'
                run.current_node_id = current_node_id
                run.execution_log = logs.copy()  # 使用 copy() 确保 SQLAlchemy 检测到变化
                run.outputs = dict(context.variables)  # 使用 dict() 创建新对象，确保 SQLAlchemy 检测到变化
                run.waiting_config = dict(result.waiting_config) if result.waiting_config else {}
                # 注意：save由调用方处理
                
                # 发送等待输入事件
                yield {
                    'type': 'waiting_input',
                    'node_id': current_node_id,
                    'node_type': node_type,
                    'config': result.waiting_config,
                }
                
                # 暂停工作流执行，等待用户输入后续流
                return
            
            # 记录日志
            log_entry = _make_execution_log_entry(
                node_map,
                current_node_id,
                node_type,
                status='completed' if result.success else 'failed',
                output=result.output,
                error=result.error,
                elapsed_time=elapsed,
                tokens_used=result.tokens_used,
                inputs=copy.deepcopy(node_inputs),
            )
            logs.append(log_entry)
            
            total_tokens += result.tokens_used
            total_steps += 1
            
            # 更新运行记录
            run.current_node_id = current_node_id
            run.execution_log = logs.copy()  # 使用 copy() 确保 SQLAlchemy 检测到变化
            run.total_tokens = total_tokens
            run.total_steps = total_steps
            run.outputs = dict(context.variables)  # 使用 dict() 创建新对象，确保 SQLAlchemy 检测到变化
            # 注意：save由调用方处理
            
            # 发送节点完成事件（包含输出数据）
            yield _apply_node_result_warnings({
                'type': 'node_complete',
                'node_id': current_node_id,
                'node_type': node_type,
                'status': 'success' if result.success else 'failed',
                'elapsed_time': elapsed,
                'tokens_used': result.tokens_used,
                'error_message': result.error if not result.success else None,
                'outputs': {
                    'output': result.output,
                    'output_variables': result.metadata.get('frontend_output_variables', result.output_variables),
                },
            }, result)
            
            if not result.success:
                raise ValueError(f'节点执行失败: {result.error}')
            
            # 合并输出变量到上下文
            context.variables.update(result.output_variables)
            context.previous_output = result.output
            
            # 保存节点输出到命名空间（支持 {{NodeID.key}} 格式引用）
            context.variables[f'_node_{current_node_id}'] = {
                'output': result.output,
                **result.output_variables,
            }
            
            # 确定下一个节点
            if node_type == 'parallel':
                # 并行节点：执行所有分支（流式）
                branch_ids = list(parallel_edge_map.get(current_node_id, {}).keys()) or edge_map.get(current_node_id, [])
                yield {
                    'type': 'parallel_start',
                    'node_id': current_node_id,
                    'branches': branch_ids,
                }
                
                # 使用流式版本的并行分支执行（异步）
                async for event in self._execute_parallel_branches_stream(
                    node_map, edge_map, parallel_edge_map, current_node_id, context, run, logs
                ):
                    if event.get('type') == '_parallel_result':
                        # 内部结果事件，不 yield 给前端
                        parallel_results = event['data']
                        context.set_variable('_parallel_results', parallel_results['results'])
                        total_tokens += parallel_results['total_tokens']
                        total_steps += parallel_results['total_steps']
                        logs.extend(parallel_results['logs'])
                        
                        yield {
                            'type': 'parallel_complete',
                            'node_id': current_node_id,
                            'branch_results': parallel_results['results'],
                            'total_tokens': parallel_results['total_tokens'],
                        }
                        
                        # 找到合并节点继续执行
                        current_node_id = parallel_results.get('merge_node_id')
                    else:
                        # 其他事件（node_start, node_complete 等）直接 yield
                        yield event
            elif node_type == 'loop':
                # 循环节点：执行循环体
                loop_node_id = current_node_id
                loop_config = node_config.get('data', {})
                
                yield {
                    'type': 'loop_start',
                    'node_id': loop_node_id,
                    'loop_mode': context.get_variable('_loop_mode'),
                    'total_iterations': context.get_variable('_loop_total', 0),
                }
                
                # 执行循环（异步）
                loop_waiting = False
                loop_error = None
                async for loop_event in self._execute_loop(
                    node_map, edge_map, parallel_edge_map, loop_node_id, 
                    loop_config, context, run, logs
                ):
                    yield loop_event
                    # 累加循环中的 token 和步骤
                    if loop_event.get('type') == 'loop_iteration_complete':
                        total_tokens += loop_event.get('tokens_used', 0)
                        total_steps += loop_event.get('steps', 0)
                    # 检查是否因等待用户输入而暂停
                    if loop_event.get('type') == 'waiting_input':
                        loop_waiting = True
                    # 检查是否有循环迭代错误
                    if loop_event.get('type') == 'loop_iteration_error':
                        loop_error = loop_event.get('error', '循环执行失败')
                        logger.error(f'[流式] 检测到循环迭代错误: {loop_error}')
                
                # 如果循环因等待用户输入而暂停，停止主工作流执行
                if loop_waiting:
                    return
                
                # 如果循环中有错误，抛出异常
                if loop_error:
                    raise ValueError(f'循环节点执行失败: {loop_error}')
                
                # 获取循环结果
                loop_results = context.get_variable('_loop_results', [])
                output_var = loop_config.get('output_variable', 'loop_results')
                context.set_variable(output_var, loop_results)
                
                yield {
                    'type': 'loop_complete',
                    'node_id': loop_node_id,
                    'total_iterations': context.get_variable('_loop_index', 0),
                    'results_count': len(loop_results),
                    'loop_results': loop_results,
                    'output_variable': output_var,
                }
                
                # 单节点循环设计：循环完成后从循环节点的输出边继续
                next_nodes = edge_map.get(loop_node_id, [])
                # 过滤掉子节点（parentNode 为循环节点的节点）
                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
            elif result.next_node_id:
                # 检查是否是分支 ID（条件分支节点返回的是分支 ID，需要通过边映射找到实际目标节点）
                logger.info(f'[流式] 节点 {current_node_id} 返回 next_node_id={result.next_node_id}')
                logger.info(f'[流式] parallel_edge_map[{current_node_id}]={parallel_edge_map.get(current_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]
                    logger.info(f'[流式] 通过分支映射找到目标节点: {current_node_id}')
                elif result.next_node_id in node_map:
                    # 是实际的节点 ID
                    current_node_id = result.next_node_id
                    logger.info(f'[流式] 直接使用节点 ID: {current_node_id}')
                else:
                    # 尝试从边映射中查找
                    logger.warning(f'[流式] 分支 ID {result.next_node_id} 未找到对应边，回退到普通边查找')
                    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
    
    async def resume_workflow_stream(
        self,
        workflow: AIWorkflow,
        run: AIWorkflowRun,
        user_input: Any,
        conversation_history: List[Dict[str, str]] = None,
    ):
        """
        恢复等待中的工作流执行（异步生成器）
        
        注意：此方法需要预先加载好workflow和run对象
        
        Args:
            workflow: 工作流对象
            run: 工作流运行记录
            user_input: 用户输入的值
            conversation_history: 对话历史（用于 LLM 节点的上下文记忆）
            
        Yields:
            dict: 节点执行状态事件
        """
        # 发送恢复事件
        yield {
            'type': 'resume',
            'run_id': str(run.id),
            'node_id': run.current_node_id,
        }
        
        try:
            # 恢复执行（异步）
            async for event in self._resume_workflow_stream(workflow, run, user_input, conversation_history):
                yield event
            
            # 发送完成事件（如果已完成）
            if run.status == 'completed':
                yield {
                    'type': 'complete',
                    'run_id': str(run.id),
                    'status': 'completed',
                    'outputs': run.outputs,
                    'elapsed_time': run.elapsed_time,
                    'total_tokens': run.total_tokens,
                }
                
        except Exception as e:
            logger.exception(f'工作流恢复执行失败: {e}')
            yield {
                'type': 'error',
                'run_id': str(run.id),
                'node_id': run.current_node_id,
                'message': str(e),
            }
    
    async def resume_workflow_stream_async(
        self,
        run_id: str,
        user_input: Any,
        conversation_history: List[Dict[str, str]] = None,
    ):
        """
        异步恢复等待中的工作流执行
        
        Args:
            run_id: 运行记录 ID
            user_input: 用户输入的值
            conversation_history: 对话历史
            
        Yields:
            dict: 节点执行状态事件
        """
        if not self._db:
            yield {'type': 'error', 'message': '数据库会话未初始化'}
            return
        
        # 查询运行记录
        result = await self._db.execute(
            select(AIWorkflowRun).where(AIWorkflowRun.id == run_id)
        )
        run = result.scalar_one_or_none()
        if not run:
            yield {'type': 'error', 'message': f'运行记录不存在: {run_id}'}
            return
        
        if run.status != 'waiting':
            yield {'type': 'error', 'message': f'工作流不在等待状态: {run.status}'}
            return
        
        # 查询工作流
        workflow = await self.get_workflow(run.workflow_id)
        if not workflow:
            yield {'type': 'error', 'message': '工作流不存在'}
            return
        
        try:
            # 使用异步生成器恢复执行
            async for event in self.resume_workflow_stream(workflow, run, user_input, conversation_history):
                yield event
            
            # 更新运行记录
            if run.status == 'completed':
                # 更新工作流统计
                workflow.run_count = (workflow.run_count or 0) + 1
                workflow.success_count = (workflow.success_count or 0) + 1
            
            await self._db.commit()
            
        except Exception as e:
            logger.exception(f'工作流恢复执行失败: {e}')
            run.status = 'failed'
            run.error_message = str(e)
            run.completed_at = datetime.now()
            run.elapsed_time = int((run.completed_at - run.started_at).total_seconds() * 1000)
            
            await self._db.commit()
            
            # 发送错误事件给前端
            yield {
                'type': 'error',
                'message': str(e),
                'error_message': str(e),
                'run_id': run.id,
                'workflow_id': workflow.id,
            }
    
    async def _resume_workflow_stream(
        self,
        workflow: AIWorkflow,
        run: AIWorkflowRun,
        user_input: Any,
        conversation_history: List[Dict[str, str]] = None,
    ):
        """
        恢复工作流执行的内部实现（异步版本）
        
        Args:
            workflow: 工作流对象
            run: 工作流运行记录
            user_input: 用户输入
            conversation_history: 对话历史（用于 LLM 节点的上下文记忆）
        """
        import asyncio
        definition = workflow.definition
        nodes = definition.get('nodes', [])
        edges = definition.get('edges', [])
        
        # 构建节点映射和边映射
        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
        
        # 恢复上下文
        logger.info(f'[WorkflowService] 恢复工作流，接收到的 user_input 类型: {type(user_input)}, 值: {user_input}')
        
        context = NodeContext(
            workflow_run_id=str(run.id),
            variables=run.outputs.copy() if run.outputs else {},
            user_input=str(user_input) if user_input is not None else '',
            user_id=str(run.user_id) if run.user_id else '',
            conversation_history=conversation_history or [],
            db_session=self._db,
        )
        
        logger.info(f'[WorkflowService] context.user_input 设置为: {context.user_input}')
        
        # 调试：打印恢复时加载的变量
        logger.info(f'恢复工作流，从 run.outputs 加载变量，变量键: {list(context.variables.keys())}')
        node_keys = [k for k in context.variables.keys() if k.startswith('_node_')]
        logger.info(f'恢复工作流，_node_ 前缀的变量: {node_keys}')
        # 检查循环相关变量
        loop_items = context.variables.get('_loop_items')
        logger.info(f'恢复工作流，_loop_items 长度={len(loop_items) if loop_items else 0}, _loop_index={context.variables.get("_loop_index")}, _loop_mode={context.variables.get("_loop_mode")}')
        
        # 检查是否有旧的 __user_input__
        old_user_input = context.variables.get('__user_input__')
        logger.info(f'[WorkflowService] variables 中的旧 __user_input__: {old_user_input}')
        
        # 设置用户输入到上下文（对话流节点会读取这个值）
        context.variables['__user_input__'] = user_input
        logger.info(f'[WorkflowService] 更新 variables["__user_input__"] 为: {user_input}')
        
        logs = run.execution_log or []
        total_tokens = run.total_tokens
        total_steps = run.total_steps
        current_node_id = run.current_node_id
        
        # 检查是否有循环状态需要恢复
        loop_state = run.waiting_config.get('_loop_state') if run.waiting_config else None
        # 检查是否有子流程状态需要恢复
        subflow_state = run.waiting_config.get('_subflow_state') if run.waiting_config else None
        logger.info(f'恢复工作流，loop_state={loop_state}, subflow_state={bool(subflow_state)}, waiting_config={run.waiting_config}')
        
        # 如果有子流程状态，将其添加到上下文的 metadata 中
        if subflow_state:
            context.metadata['_subflow_state'] = subflow_state
        
        # 更新状态为运行中
        run.status = 'running'
        run.waiting_config = {}
        
        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)
            
            # 获取节点输入
            node_inputs = {
                'variables': dict(context.variables),
                'previous_output': context.previous_output,
            }
            
            # 发送节点开始事件
            node_start_event = {
                'type': 'node_start',
                'node_id': current_node_id,
                'node_type': node_type,
                'node_label': node_label,
                'timestamp': datetime.now().isoformat(),
                'inputs': node_inputs,
            }
            # 如果在循环中，添加迭代信息
            if loop_state:
                node_start_event['loop_iteration'] = loop_state.get('iteration', 0)
            yield node_start_event
            
            # 结束节点
            if node_type == 'end':
                node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {}))
                if node_instance:
                    result = node_instance.execute(context)
                    logs.append(_make_execution_log_entry(
                        node_map,
                        current_node_id,
                        node_type,
                        status='completed',
                        output=result.output,
                        inputs=copy.deepcopy(node_inputs),
                    ))
                    yield {
                        'type': 'node_complete',
                        'node_id': current_node_id,
                        'node_type': node_type,
                        'status': 'success',
                        'elapsed_time': 0,
                        'outputs': {'output': result.output},
                    }
                    
                    # 更新运行记录，保存结束节点的输出
                    run.status = 'completed'
                    run.completed_at = datetime.now()
                    run.elapsed_time = int((run.completed_at - run.started_at).total_seconds() * 1000)
                    run.execution_log = logs.copy()
                    # 只有当结束节点有输出时才包含 output 字段
                    if result.output is not None:
                        run.outputs = {
                            **context.variables,
                            'output': result.output,  # 结束节点的输出内容
                        }
                    else:
                        run.outputs = dict(context.variables)
                    # 注意：save由调用方处理
                break
            
            # 创建节点实例并执行（在线程池中运行同步代码）
            node_instance = NodeRegistry.create_instance(node_type, node_config.get('data', {}))
            if not node_instance:
                raise ValueError(f'未知节点类型: {node_type}')
            
            start_time = time.time()
            result = await node_instance.execute_async(context)
            elapsed = int((time.time() - start_time) * 1000)
            
            # 处理节点事件
            if result.events:
                for event in result.events:
                    if event.get('type') == 'message':
                        # 消息事件直接发送为 answer 类型
                        yield {
                            'type': 'answer',
                            'node_id': current_node_id,
                            'content': event.get('content', ''),
                        }
                    else:
                        # 其他事件保持 node_event 格式
                        yield {
                            'type': 'node_event',
                            'node_id': current_node_id,
                            'event': event,
                        }
            
            # 检查是否需要等待用户输入
            if result.waiting_for_input:
                log_entry = {
                    'node_id': current_node_id,
                    'node_type': node_type,
                    'status': 'waiting',
                    'output': result.output,
                    'elapsed_time': elapsed,
                }
                logs.append(log_entry)
                
                run.status = 'waiting'
                run.current_node_id = current_node_id
                run.execution_log = logs.copy()  # 使用 copy() 确保 SQLAlchemy 检测到变化
                run.outputs = dict(context.variables)  # 使用 dict() 创建新对象，确保 SQLAlchemy 检测到变化
                run.waiting_config = dict(result.waiting_config) if result.waiting_config else {}
                # 如果在循环中，保存循环状态以便恢复
                if loop_state:
                    run.waiting_config['_loop_state'] = loop_state
                # 注意：save由调用方处理
                
                waiting_event = {
                    'type': 'waiting_input',
                    'node_id': current_node_id,
                    'node_type': node_type,
                    'config': result.waiting_config,
                }
                # 如果在循环中，添加迭代信息
                if loop_state:
                    waiting_event['loop_iteration'] = loop_state.get('iteration', 0)
                yield waiting_event
                return
            
            # 记录日志
            log_entry = {
                'node_id': current_node_id,
                'node_type': node_type,
                'status': 'completed' if result.success else 'failed',
                'output': result.output,
                'error': result.error,
                'elapsed_time': elapsed,
                'tokens_used': result.tokens_used,
            }
            logs.append(log_entry)
            
            total_tokens += result.tokens_used
            total_steps += 1
            
            # 清除用户输入（只在第一个节点使用）
            context.variables.pop('__user_input__', None)
            
            # 更新运行记录
            run.current_node_id = current_node_id
            run.execution_log = logs.copy()  # 使用 copy() 确保 SQLAlchemy 检测到变化
            run.total_tokens = total_tokens
            run.total_steps = total_steps
            run.outputs = dict(context.variables)  # 使用 dict() 创建新对象，确保 SQLAlchemy 检测到变化
            # 注意：save由调用方处理
            
            # 发送节点完成事件
            node_complete_event = _apply_node_result_warnings({
                'type': 'node_complete',
                'node_id': current_node_id,
                'node_type': node_type,
                'status': 'success' if result.success else 'failed',
                'elapsed_time': elapsed,
                'tokens_used': result.tokens_used,
                'error_message': result.error if not result.success else None,
                'outputs': {
                    'output': result.output,
                    'output_variables': result.metadata.get('frontend_output_variables', result.output_variables),
                },
            }, result)
            # 如果在循环中，添加迭代信息
            if loop_state:
                node_complete_event['loop_iteration'] = loop_state.get('iteration', 0)
            yield node_complete_event
            
            if not result.success:
                raise ValueError(f'节点执行失败: {result.error}')
            
            # 合并输出变量到上下文
            context.variables.update(result.output_variables)
            context.previous_output = result.output
            
            # 保存节点输出到命名空间
            context.variables[f'_node_{current_node_id}'] = {
                'output': result.output,
                **result.output_variables,
            }
            
            # 确定下一个节点
            # 检查当前节点是否是循环节点
            if node_type == 'loop':
                # 循环节点：执行循环体
                loop_node_id = current_node_id
                loop_config = node_config.get('data', {})
                
                yield {
                    'type': 'loop_start',
                    'node_id': loop_node_id,
                    'loop_mode': context.get_variable('_loop_mode'),
                    'total_iterations': context.get_variable('_loop_total', 0),
                }
                
                # 执行循环（异步）
                loop_waiting = False
                async for loop_event in self._execute_loop(
                    node_map, edge_map, parallel_edge_map, loop_node_id, 
                    loop_config, context, run, logs
                ):
                    yield loop_event
                    # 累加循环中的 token 和步骤
                    if loop_event.get('type') == 'loop_iteration_complete':
                        total_tokens += loop_event.get('tokens_used', 0)
                        total_steps += loop_event.get('steps', 0)
                    # 检查是否因等待用户输入而暂停
                    if loop_event.get('type') == 'waiting_input':
                        loop_waiting = True
                
                # 如果循环因等待用户输入而暂停，停止主工作流执行
                if loop_waiting:
                    return
                
                # 获取循环结果
                loop_results = context.get_variable('_loop_results', [])
                output_var = loop_config.get('output_variable', 'loop_results')
                context.set_variable(output_var, loop_results)
                
                yield {
                    'type': 'loop_complete',
                    'node_id': loop_node_id,
                    'total_iterations': context.get_variable('_loop_index', 0),
                    'results_count': len(loop_results),
                    'loop_results': loop_results,
                    'output_variable': output_var,
                }
                
                # 单节点循环设计：循环完成后从循环节点的输出边继续
                next_nodes = edge_map.get(loop_node_id, [])
                # 过滤掉子节点（parentNode 为循环节点的节点）
                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
            # 检查是否在循环中恢复执行
            elif 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', {})
                
                # 获取循环体子节点
                child_node_ids = self._get_child_nodes(node_map, loop_node_id)
                if not child_node_ids:
                    child_node_ids = loop_config.get('childNodes', [])
                
                # 构建子节点的边映射
                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
                
                # 找循环体内的下一个节点
                if result.next_node_id and result.next_node_id in child_node_ids:
                    current_node_id = result.next_node_id
                else:
                    next_nodes = child_edge_map.get(current_node_id, [])
                    if next_nodes:
                        current_node_id = next_nodes[0]
                    else:
                        # 循环体内没有更多节点，完成当前迭代并继续循环
                        logger.info(f'[Resume] 循环体内节点执行完毕，完成当前迭代')
                        
                        # 收集当前迭代结果
                        loop_results = context.get_variable('_loop_results', [])
                        loop_results.append(result.output)
                        context.set_variable('_loop_results', loop_results)
                        
                        # 更新循环索引（进入下一次迭代）
                        current_loop_index = context.get_variable('_loop_index', 0)
                        context.set_variable('_loop_index', current_loop_index + 1)
                        logger.info(f'[Resume] 更新 _loop_index: {current_loop_index} -> {current_loop_index + 1}')
                        
                        # 发送迭代完成事件
                        loop_items = context.get_variable('_loop_items', [])
                        yield {
                            'type': 'loop_iteration_complete',
                            'node_id': loop_node_id,
                            'iteration': current_loop_index,
                            'total': len(loop_items) if loop_items else None,
                            'output': result.output,
                        }
                        
                        # 继续循环执行
                        loop_items = context.get_variable('_loop_items', [])
                        loop_index = context.get_variable('_loop_index', 0)
                        logger.info(f'[Resume] 继续循环执行, _loop_items 长度={len(loop_items) if loop_items else 0}, _loop_index={loop_index}')
                        logger.info(f'[Resume] context.variables 中的循环变量: _loop_items={bool(context.variables.get("_loop_items"))}, _loop_mode={context.variables.get("_loop_mode")}')
                        loop_waiting = False
                        async for loop_event in self._execute_loop(
                            node_map, edge_map, parallel_edge_map, loop_node_id,
                            loop_config, context, run, logs
                        ):
                            yield loop_event
                            if loop_event.get('type') == 'waiting_input':
                                loop_waiting = True
                        
                        # 如果循环因等待用户输入而暂停，停止执行
                        if loop_waiting:
                            return
                        
                        # 循环执行完成，找循环节点之后的节点
                        next_nodes = edge_map.get(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
                        loop_state = None  # 清除循环状态
            elif result.next_node_id:
                # 检查是否是分支 ID（条件分支节点返回的是分支 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:
                    # 是实际的节点 ID
                    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
    
    @staticmethod
    def get_node_schemas() -> List[Dict]:
        """获取所有节点 Schema"""
        return NodeRegistry.get_all_schemas()
    
    @staticmethod
    def get_node_schemas_by_category() -> Dict[str, List[Dict]]:
        """按分类获取节点 Schema"""
        return NodeRegistry.get_schemas_by_category()
