feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
并行分支节点处理器
|
||||
处理并行分支的执行和汇聚
|
||||
"""
|
||||
import copy
|
||||
import logging
|
||||
from typing import Dict, TYPE_CHECKING
|
||||
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
from online_dev.workflow.engine.handlers.base_handler import BaseNodeHandler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from online_dev.workflow.engine.base import ExecutionContext
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ParallelHandler(BaseNodeHandler):
|
||||
"""
|
||||
并行分支节点处理器
|
||||
|
||||
同时执行所有分支,所有分支完成后汇聚继续
|
||||
"""
|
||||
|
||||
async def execute(self, context: 'ExecutionContext', node: Dict) -> None:
|
||||
"""
|
||||
执行并行节点:同时启动所有分支
|
||||
"""
|
||||
node_id = self.get_node_id(node)
|
||||
node_name = self.get_node_name(node)
|
||||
branches = node.get('branches', [])
|
||||
|
||||
logger.info(f"处理并行分支 - 节点: {node_id}, 分支数: {len(branches)}")
|
||||
|
||||
if not branches:
|
||||
logger.warning(f"并行节点 {node_id} 没有分支,自动跳过")
|
||||
await self.advance_to_next(context, node)
|
||||
return
|
||||
|
||||
# 更新实例当前节点
|
||||
await self.update_instance_node(context, node)
|
||||
|
||||
# 初始化并行分支状态(深拷贝确保 SQLAlchemy 检测到变更)
|
||||
parallel_status = copy.deepcopy(context.instance.parallel_branch_status or {})
|
||||
parallel_status[node_id] = {
|
||||
branch.get('id', ''): 'pending' for branch in branches
|
||||
}
|
||||
context.instance.parallel_branch_status = parallel_status
|
||||
flag_modified(context.instance, 'parallel_branch_status')
|
||||
context.db.add(context.instance)
|
||||
await context.db.flush()
|
||||
|
||||
# 记录进入并行分支
|
||||
await self.create_log(
|
||||
context, node, 'parallel_start',
|
||||
comment=f'进入并行分支,共 {len(branches)} 个分支',
|
||||
extra_data={
|
||||
'branch_count': len(branches),
|
||||
'branch_ids': [b.get('id') for b in branches],
|
||||
},
|
||||
)
|
||||
|
||||
# 同时执行所有分支
|
||||
for branch in branches:
|
||||
branch_children = branch.get('children')
|
||||
if branch_children:
|
||||
await self._execute_branch(context, node, branch, branch_children)
|
||||
else:
|
||||
# 空分支,标记为完成
|
||||
await self.mark_branch_complete(context, node, branch)
|
||||
|
||||
# 检查是否所有分支都已完成
|
||||
await self.check_completion(context, node)
|
||||
|
||||
async def _execute_branch(
|
||||
self,
|
||||
context: 'ExecutionContext',
|
||||
parallel_node: Dict,
|
||||
branch: Dict,
|
||||
first_node: Dict,
|
||||
) -> None:
|
||||
"""
|
||||
执行并行分支中的第一个节点。
|
||||
统一通过引擎 handler 分发,支持所有节点类型(approval, handle, copy, notify,
|
||||
condition, delay, service, subflow, data_update, parallel 等)。
|
||||
非阻塞节点执行后会自动调用 advance_to_next 推进;阻塞节点(approval/handle)
|
||||
在任务完成时由引擎推进。当分支走到末尾时,引擎的 _advance_to_next 会通过
|
||||
find_parallel_branch_for_node 找到所属并行分支并 mark_branch_complete。
|
||||
"""
|
||||
node_type = first_node.get('type')
|
||||
handler = self.engine._get_handler(node_type)
|
||||
|
||||
if handler:
|
||||
await handler.execute(context, first_node)
|
||||
else:
|
||||
logger.warning(f"并行分支内未知节点类型: {node_type},标记分支完成")
|
||||
await self.mark_branch_complete(context, parallel_node, branch)
|
||||
|
||||
async def mark_branch_complete(
|
||||
self,
|
||||
context: 'ExecutionContext',
|
||||
parallel_node: Dict,
|
||||
branch: Dict,
|
||||
) -> None:
|
||||
"""
|
||||
标记并行分支完成
|
||||
"""
|
||||
node_id = self.get_node_id(parallel_node)
|
||||
branch_id = branch.get('id', '')
|
||||
branch_name = branch.get('name', '')
|
||||
|
||||
# 更新分支状态(深拷贝确保 SQLAlchemy 检测到 JSON 字段变更)
|
||||
await context.db.refresh(context.instance)
|
||||
parallel_status = copy.deepcopy(context.instance.parallel_branch_status or {})
|
||||
|
||||
if node_id in parallel_status:
|
||||
parallel_status[node_id][branch_id] = 'completed'
|
||||
context.instance.parallel_branch_status = parallel_status
|
||||
flag_modified(context.instance, 'parallel_branch_status')
|
||||
context.db.add(context.instance)
|
||||
await context.db.flush()
|
||||
|
||||
logger.info(f"并行分支完成 - 节点: {node_id}, 分支: {branch_id}")
|
||||
|
||||
# 记录分支完成
|
||||
await self.create_log(
|
||||
context, parallel_node, 'parallel_branch_complete',
|
||||
comment=f'并行分支 {branch_name} 完成',
|
||||
extra_data={'branch_id': branch_id},
|
||||
)
|
||||
|
||||
# 检查是否所有分支都已完成
|
||||
await self.check_completion(context, parallel_node)
|
||||
|
||||
async def check_completion(self, context: 'ExecutionContext', parallel_node: Dict) -> None:
|
||||
"""
|
||||
检查并行分支是否全部完成
|
||||
"""
|
||||
node_id = self.get_node_id(parallel_node)
|
||||
|
||||
# 刷新实例获取最新状态
|
||||
await context.db.refresh(context.instance)
|
||||
parallel_status = copy.deepcopy(context.instance.parallel_branch_status or {})
|
||||
|
||||
branch_statuses = parallel_status.get(node_id, {})
|
||||
|
||||
if not branch_statuses:
|
||||
logger.warning(f"并行节点 {node_id} 没有分支状态记录")
|
||||
return
|
||||
|
||||
# 检查是否所有分支都已完成
|
||||
all_completed = all(status == 'completed' for status in branch_statuses.values())
|
||||
pending_branches = [bid for bid, status in branch_statuses.items() if status != 'completed']
|
||||
|
||||
logger.info(f"并行节点 {node_id} 状态检查: 全部完成={all_completed}, 待完成={pending_branches}")
|
||||
|
||||
if all_completed:
|
||||
logger.info(f"并行节点 {node_id} 所有分支完成,继续推进")
|
||||
|
||||
# 清理已完成的并行节点状态(深拷贝确保变更检测)
|
||||
parallel_status = copy.deepcopy(parallel_status)
|
||||
del parallel_status[node_id]
|
||||
context.instance.parallel_branch_status = parallel_status
|
||||
flag_modified(context.instance, 'parallel_branch_status')
|
||||
context.db.add(context.instance)
|
||||
await context.db.flush()
|
||||
|
||||
# 记录日志
|
||||
await self.create_log(
|
||||
context, parallel_node, 'parallel_complete',
|
||||
comment='所有并行分支完成',
|
||||
)
|
||||
|
||||
# 推进到下一节点
|
||||
await self.advance_to_next(context, parallel_node)
|
||||
Reference in New Issue
Block a user