006、规划模块(三):分层任务网络与自动化规划器
昨天深夜调一个机器人抓取流程代码逻辑堆了三百多行if-else。当需求变成“先检查电池再决定是否取货”时整个模块几乎重写。那一刻我盯着屏幕想这堆面条代码不就是缺个正经规划器吗从if-else地狱说起很多项目起步时都这样任务简单直接硬编码。传感器触发A执行动作X。条件B成立跳转到Y。三个月后需求变更代码变成迷宫添加新功能像在雷区排雷。上周看到同事的仓储机器人代码判断逻辑里嵌套了五层条件。我问他“如果中途需要充电怎么办”他沉默了一会儿“得加个标志位再重写状态判断。”这就是典型的手动规划困境——人类大脑在代码里模拟状态机迟早会溢出。分层任务网络HTN是什么味道HTN不像传统规划器那样搜索“动作序列”它思考的是“任务分解”。想象你指挥一个机器人泡茶传统规划可能是走到厨房-烧水-拿茶叶…HTN的思维是“完成泡茶任务”这个高层任务可以分解为“准备热水”和“准备茶杯”两个子任务。“准备热水”又能分解为“接水”和“加热”。实际代码里HTN的吸引力在于它匹配人类思维。我们不会从头规划每个肌肉动作而是自然分层思考。# 一个简化的HTN结构示例classHTNMethod:def__init__(self,task,subtasks):self.tasktask# 要分解的高层任务self.subtaskssubtasks# 分解后的子任务列表# 定义“递送包裹”的分解方法deliver_methods[HTNMethod(递送包裹,[取包裹,规划路径,移动,交付]),HTNMethod(规划路径,[检测障碍,计算轨迹]),# 这里踩过坑子任务顺序很重要但别写死依赖用前置条件更灵活]真正的HTN规划器会维护任务栈不断展开高层任务直到所有任务都变成可执行的原语操作。这个展开过程可以很智能——同一个高层任务可能有多种分解方式规划器根据当前世界状态选择最合适的。自动化规划器怎么落地实验室里的规划算法论文都很美但产线环境是另一回事。去年给AGV部署规划模块时发现学术界的标准HTN实现太“重”每次重新规划要几百毫秒机器人早撞墙了。我们最后做了个混合架构高层用HTN做任务分解秒级底层用行为树做实时执行毫秒级中间加了个状态缓存层避免重复规划classProductionPlanner:def__init__(self):self.htn_solverHTNSolver()# 处理“做什么”self.bt_executorBehaviorTree()# 处理“怎么做”self.world_state{}# 别用全局变量这里吃过亏defreplan_if_needed(self,state_change):# 只有关键状态变化才触发重新规划ifself._is_significant_change(state_change):# 从当前失败的任务层级开始重新分解不是从头开始partial_planself.htn_solver.replan_from_failure(self.current_task_stack,self.world_state)# 保留还能用的部分规划节省计算量self._merge_partial_plan(partial_plan)这个架构的妙处在于当传感器发现新障碍时底层行为树立即避障反射式同时标记“路径受阻”状态规划器在后台计算新路线算好了无缝切换过去。用户只觉得机器人绕了一下不知道内部已经重构了整个任务树。调试HTN的实战技巧HTN调试最头疼的是“规划结果看起来合理执行起来出问题”。分享几个血泪教训给任务分解加日志钩子每个方法分解时打印完整上下文包括被拒绝的其他分解选项。很多时候问题不是选了错的分解而是漏了正确的分解方法。可视化任务栈我们写了个简单的ASCII图形化工具实时显示任务栈展开过程。看到某个任务反复分解又失败基本能定位到前置条件没写对。世界状态要版本化规划器基于的世界状态必须是快照不能是实时变化的。有次遇到规划时电池电量是30%执行时变成29%导致前置条件不满足。后来给状态加了时间戳规划时用“规划时刻的状态”执行时再检查状态是否依然有效。设置规划超时复杂任务可能有无穷的分解方式必须设置超时和深度限制。超时后可以回退到“降级模式”——用更粗糙但确定的分解方法。个人经验包HTN不是银弹。简单任务用状态机中等复杂度用行为树只有那些需要多层次抽象、频繁变更需求的长流程任务才值得上HTN。很多团队过早引入HTN结果只是把if-else地狱变成了方法定义地狱。好的HTN设计有个标志领域专家非程序员能看懂你的任务分解方法库。他们可能会说“这个‘质检流程’的分解少了返工分支。”——这说明你的抽象层级对了。最后提醒规划器的输出一定要经过“可行性检查”。有次规划器让机械臂先松手再移动去抓取理论上任务分解正确实际执行会掉零件。后来加了物理约束检查层专门捕捉这种逻辑正确但物理荒谬的规划。规划模块的终极测试是当产品经理第10次改需求时你只需要增减几个方法定义而不是重写半个系统。那种感觉比深夜调试通过还要爽。