You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

pyTransitions嵌套并行状态下may_方法返回异常是否为Bug?

关于pyTransitions并行状态中may_*方法返回异常的问题

状态机实现逻辑

我使用pyTransitions实现了一个状态机,核心逻辑如下:

  • 先并行执行Q1的1月、2月、3月任务
  • 完成Q1后,再并行执行Q2的4-6月任务
  • 任意月份任务失败,直接跳转至workflow_failed状态

状态机定义代码

state_machine_def = {
    'states': [
        'workflow_started',
        {
            'name': 'running_qtr_1',
            'parallel': [
                {
                    'name': 'running_jan',
                    'children': ['jan_started', 'jan_succeeded', 'jan_failed'],
                    'initial': 'jan_started',
                    'transitions': [
                        ['end_success_jan', 'jan_started', 'jan_succeeded'],
                        ['end_failure_jan', 'jan_started', 'jan_failed']
                    ]
                },
                {
                    'name': 'running_feb',
                    'children': ['feb_started', 'feb_succeeded', 'feb_failed'],
                    'initial': 'feb_started',
                    'transitions': [
                        ['end_success_feb', 'feb_started', 'feb_succeeded'],
                        ['end_failure_feb', 'feb_started', 'feb_failed']
                    ]
                },
                {
                    'name': 'running_mar',
                    'children': ['mar_started', 'mar_succeeded', 'mar_failed'],
                    'initial': 'mar_started',
                    'transitions': [
                        ['end_success_mar', 'mar_started', 'mar_succeeded'],
                        ['end_failure_mar', 'mar_started', 'mar_failed']
                    ]
                }
            ]
        },
        'completed_qtr_1',
        {
            'name': 'running_qtr_2',
            'parallel': [
                {
                    'name': 'running_apr',
                    'children': ['apr_started', 'apr_succeeded', 'apr_failed'],
                    'initial': 'apr_started',
                    'transitions': [
                        ['end_success_apr', 'apr_started', 'apr_succeeded'],
                        ['end_failure_apr', 'apr_started', 'apr_failed']
                    ]
                },
                {
                    'name': 'running_may',
                    'children': ['may_started', 'may_succeeded', 'may_failed'],
                    'initial': 'may_started',
                    'transitions': [
                        ['end_success_may', 'may_started', 'may_succeeded'],
                        ['end_failure_may', 'may_started', 'may_failed']
                    ]
                },
                {
                    'name': 'running_jun',
                    'children': ['jun_started', 'jun_succeeded', 'jun_failed'],
                    'initial': 'jun_started',
                    'transitions': [
                        ['end_success_jun', 'jun_started', 'jun_succeeded'],
                        ['end_failure_jun', 'jun_started', 'jun_failed']
                    ]
                }
            ]
        },
        'completed_qtr_2',
        'workflow_succeeded',
        'workflow_failed'
    ],
    'transitions': [
        {'trigger': 'next', 'source': 'workflow_started', 'dest': 'running_qtr_1'},
        {'trigger': 'next', 'source': 'running_qtr_1~running_jan~jan_succeeded', 'dest': 'completed_qtr_1', 'conditions': 'qtr1_completed'},
        {'trigger': 'next', 'source': 'running_qtr_1~running_feb~feb_succeeded', 'dest': 'completed_qtr_1', 'conditions': 'qtr1_completed'},
        {'trigger': 'next', 'source': 'running_qtr_1~running_mar~mar_succeeded', 'dest': 'completed_qtr_1', 'conditions': 'qtr1_completed'},
        {'trigger': 'next', 'source': 'completed_qtr_1', 'dest': 'running_qtr_2'},
        {'trigger': 'next', 'source': 'running_qtr_2~running_apr~apr_succeeded', 'dest': 'completed_qtr_2', 'conditions': 'qtr2_completed'},
        {'trigger': 'next', 'source': 'running_qtr_2~running_may~may_succeeded', 'dest': 'completed_qtr_2', 'conditions': 'qtr2_completed'},
        {'trigger': 'next', 'source': 'running_qtr_2~running_jun~jun_succeeded', 'dest': 'completed_qtr_2', 'conditions': 'qtr2_completed'},
        {'trigger': 'next', 'source': 'completed_qtr_2', 'dest': 'workflow_succeeded'},
        {'trigger': 'next', 'source': [
            'running_qtr_1~running_jan~jan_failed',
            'running_qtr_1~running_feb~feb_failed',
            'running_qtr_1~running_mar~mar_failed',
            'running_qtr_2~running_apr~apr_failed',
            'running_qtr_2~running_may~may_failed',
            'running_qtr_2~running_jun~jun_failed'
        ], 'dest': 'workflow_failed'}
    ],
    'initial_state': 'workflow_started'
}

遇到的问题

当状态机切换至jan_succeeded状态后,调用self.model.may_end_success_feb()返回False,但实际能成功调用self.model.end_success_feb()将状态切换至feb_succeeded,测试过程如下:

# 查询是否可触发'end_success_feb',状态机返回False
> self.model.may_end_success_feb()
False
# 但实际可成功触发该方法
> self.model.end_success_feb()
now at state: feb_succeeded

由于Jan、Feb、Mar属于并行状态,Jan的状态变更不应影响Feb触发器的可用性,请问这是否属于pyTransitions的Bug?


问题解答

这不是pyTransitions的Bug,而是may_*方法在并行状态下的判断逻辑特性导致的:

  1. may_<trigger>()的判断逻辑:
    该方法默认会基于当前活跃的顶层状态路径去匹配触发器的源状态。当Jan的状态切换到jan_succeeded后,状态机的活跃状态是running_qtr_1~running_jan~jan_succeeded,may_end_success_feb()会以这个路径为基准检查,找不到对应end_success_feb触发器的匹配项,因此返回False。

  2. 实际触发end_success_feb()的逻辑:
    调用触发器方法时,pyTransitions会遍历所有并行子状态分支,找到定义了该触发器的分支(即running_feb),然后检查该分支的当前状态是否符合触发条件(此时feb_started是活跃状态,满足end_success_feb的源状态要求),因此能成功执行状态切换。

解决方法

  • 手动检查子状态:通过self.model.state获取完整状态路径,解析出running_feb对应的子状态,判断是否为feb_started
  • 自定义判断逻辑:针对并行场景,重写或封装may_*方法,单独处理各并行子分支的触发器可用性判断

内容的提问来源于stack exchange,提问作者Shu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 20:04:53