想象你是宴席总厨,接到“举办家宴”这道最高级任务。你绝不会冲进厨房直接开炒,而是先把它拆成“定菜单、采购、烹饪、摆台”几个子任务;“烹饪”再拆成“主菜、汤品、甜点”;“主菜”又拆成“切菜、焯水、爆炒”这些能直接上手的具体动作。这套“大任务拆小任务、小任务拆到能动手”的思路,正是分层任务网络(HTN)规划器的核心。机器人在复杂环境里做决策与导航,同样需要这样一位“总厨”:它把“把货物送到B区”一层层拆成可执行动作,还能在突发状况下换一套菜谱重新规划。本文将带你从概念、算法到工程应用,看懂这位“机器总厨”的脑内运作。

岗位关联

掌握 HTN,你就掌握了让机器人“分得清轻重、排得顺顺序”的决策内核——这是机器人导航、自动驾驶与智能仓储等岗位的核心技能之一。

目录
  1. 可视化演示
  2. HTN核心概念:任务与方法的“菜谱库”
  3. 规划器工作机制:总厨的待办清单
  4. 与传统规划对比:外卖员与总厨
  5. 算法优化策略:少走弯路的招数
  6. 机器人应用场景:会分解任务的导航员
  7. 局限与前沿趋势:菜谱库的边界
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示分层任务网络HTN的任务分解树、与STRIPS规划对比及机器人导航应用,交互切换理解HTN核心原理。

本节配有交互动效(进入视口后自动加载,约 3KB)。

'+modeData.decompose.caption+'

HTN核心概念:任务与方法的“菜谱库”

设想你是一家餐厅的总厨,老板下达指令:“本周六举办一场二十人的家宴。”你不会直接抓起锅铲炒菜,而是先翻自己的菜谱库:家宴可以拆成“定菜单、采购食材、烹饪、摆台”四件事;“烹饪”又拆成“准备主菜、准备汤品、准备甜点”;“主菜”则拆成“切菜、焯水、爆炒”这些一看就能动手的动作。

在这套思路里,“家宴”“烹饪”“主菜”这类需要继续拆分的,叫复合任务;“切菜”“爆炒”这类能直接执行的动作,叫原子任务。而“菜谱”在 HTN 中对应一个核心概念——方法(method):它告诉规划器,某个复合任务在什么条件下、按什么顺序拆成哪些子任务。方法不是唯一答案,同一种任务往往有多个备选方法,就像红烧肉、清蒸鱼都能撑起“主菜”这个位置。

形式化地看,一个方法可以用三元组表示:目标复合任务、前置条件、子任务序列。前置条件用来判断该方法当前是否适用——比如“若有素食客人,则选择素菜谱”。规划器每选择一次方法,就是把一个复合任务“展开”成一行子任务:

总厨脑中的菜谱库越大,家宴办得越从容;机器人的方法库越完善,能应对的新情况越多——这正是 HTN 用“知识换效率”的哲学。

方法:m=(t, pre, ⟨s1,s2,⋯ ,sn⟩)\text{方法}: \quad m = (t, \ \text{pre}, \ \langle s_1, s_2, \cdots, s_n \rangle)
这个公式描述了方法的三个组成部分:作用于谁、什么条件下能用、拆成什么。
  1. 方法标识 m方法在知识库中的唯一名字,相当于一本菜谱的编号。
    mm
  2. 目标复合任务 t该方法负责分解的对象,例如“烹饪”。
    tt
  3. 前置条件 pre世界状态必须满足的事实集合,例如“有素食客人”,不满足就不能选这份菜谱。
    pre⊆S\text{pre} \subseteq S
  4. 子任务序列方法将复合任务展开成的有序子任务列表,可以包含复合任务与原子任务。
    ⟨s1,s2,⋯ ,sn⟩\langle s_1, s_2, \cdots, s_n \rangle

规划器工作机制:总厨的待办清单

总厨开工时会在小黑板上写下一行任务:“举办家宴”。他不断重复一个动作:拿起最上面的任务——如果是“切菜”这种能直接动手的事,就立刻去做;如果是“烹饪”这种大事,就查菜谱挑一个适用的方法,把任务划掉、把子任务写回黑板。这个“取下—拆解—写回”的循环,正是 HTN 规划器的主循环。

规划器的输入有三样:初始世界状态(厨房里有什么、客人有谁)、初始任务列表(黑板上的第一行字)、领域知识库(方法库加原子动作库)。规划器维护一个任务集合,专业上叫任务网络,循环进行任务分解,直到所有任务都被处理成原子动作。最终输出的原子动作序列,就是机器人要执行的计划(plan)。

用公式表示一次“方法替换”动作:当任务序列队首是复合任务、且所选方法的前置条件满足时,就用该方法的子任务序列替换队首任务,得到新的任务序列:

循环往复,黑板上的复合任务越来越少、原子任务越来越多,直到“举办家宴”变成一行行“切菜、焯水、爆炒”——计划出炉,总厨长舒一口气。

T=⟨t1⟩⊕Trest  ⟹  T′=subtasks(m)⊕TrestT = \langle t_1 \rangle \oplus T_{\text{rest}} \;\Longrightarrow\; T' = \text{subtasks}(m) \oplus T_{\text{rest}}
规划的核心循环就是反复做这种替换,直到任务序列里只剩原子动作。
  1. T 当前任务网络待处理任务组成的有序序列,等价于黑板上所有待办事项。
    T=⟨t1,t2,⋯ ,tn⟩T = \langle t_1, t_2, \cdots, t_n \rangle
  2. 拆分队首任务⊕ 表示序列拼接,这里把队首任务与剩余任务分开,便于单独处理。
    ⟨t1⟩⊕Trest\langle t_1 \rangle \oplus T_{\text{rest}}
  3. 方法替换队首复合任务被方法 m 的子任务序列原位替换,剩余任务保持原顺序。
    T′=subtasks(m)⊕TrestT' = \text{subtasks}(m) \oplus T_{\text{rest}}

与传统规划对比:外卖员与总厨

假设厨房里还有一位助手——一位“只认目的地”的外卖员式规划器。你告诉他“我要一桌宴席”,他不懂菜谱,只能靠试:先试试摆个盘子、再试试烧个水,一步步探索哪个动作能让厨房接近目标。这种从初始状态向目标状态盲搜的方法,对应经典规划 STRIPS。

STRIPS 的问题描述是三元组:初始状态、目标条件、动作集合;HTN 的问题描述则是四元组:初始状态、初始任务、方法集合、动作集合。差别看似只多了一个要素,本质却是天壤之别:STRIPS 搜索的是“状态空间”,节点是整个世界状态,分支是每个可执行动作;HTN 搜索的是“任务网络空间”,节点是任务列表,分支只是候选方法的选择:

总厨的经验直接告诉他先蒸鱼还是先煲汤,外卖员则要把所有可能路线试一遍。代价是总厨不会做菜谱外的菜——这正是 HTN 知识驱动的双刃剑。

PSTRIPS=(S0, G, A),PHTN=(S0, T0, M, A)P_{\text{STRIPS}} = (S_0,\ G,\ A), \qquad P_{\text{HTN}} = (S_0,\ T_0,\ \mathcal{M},\ A)
一个是“目标导向的盲试者”,一个是“知识驱动的大厨”。
  1. S0 初始状态两者都需要描述世界最初的事实集合,例如“厨房里有食材”。
    S0S_0
  2. G 目标条件STRIPS 以逻辑条件描述“要到哪里”,例如“宴席已备好”。
    GG
  3. T0 初始任务HTN 以任务描述“要做什么”,例如“举办家宴”。
    T0T_0
  4. M 方法集合带 A 动作集合HTN 独有的领域知识来源,即“怎么做”的经验。
    M, A\mathcal{M},\ A

算法优化策略:少走弯路的招数

家宴规模一大,总厨也得讲策略:先做费时的炖菜再做快炒;菜谱数量多时,先排除明显不合客人口味的;一次只盯一个灶台,而不是所有锅同时开火。这些朴素经验,对应 HTN 的几大算法优化。

工程上最常见的实现是前向分解搜索:严格按任务序列从左到右处理,世界状态随原子动作不断更新,实现简单、因果链清晰。另一种偏序任务网络则允许子任务间只保留必要约束,如“先买菜再切菜”,而把无关子任务的先后顺序推迟到执行阶段,从而减少分解时的选择。配合启发式剪枝——例如用剩余复合任务数量估计代价,优先展开更有希望的方法——搜索效率大幅提升:

总厨抽屉里每道菜只备两三份菜谱,而不是把所有食材排列组合——HTN 用领域知识把分支因子压小,搜索树就“瘦”下来,这是它比盲目搜索省算力的根本原因。

搜索代价=O(bd),b=平均候选方法数\text{搜索代价} = O(b^{d}), \qquad b = \text{平均候选方法数}
候选方法越少、分解层数越浅,规划就越快——这正是知识驱动的价值所在。
  1. O 复杂度记号表示代价随参数增长的上界阶数,这里指递归分解的次数量级。
    O(⋅)O(\cdot)
  2. b 分支因子每个复合任务平均可用的候选方法数,决定搜索树的分叉密度。
    bb
  3. d 分解深度从根任务到原子动作的递归层数,层数越深搜索开销越大。
    dd

机器人应用场景:会分解任务的导航员

把总厨请进工厂和马路,就变成了机器人。比如仓储机器人接到根任务“把货架上的箱子搬到B区”,HTN 帮它拆成“导航到货架—拾取—导航到B区—放置”;每个“导航”又拆成“定位—规划路径—避障移动”。这些原子动作不再需要 HTN 操心,底层运动规划与控制器会接手执行。

在机器人领域,HTN 通常作为任务级规划器位于决策层:它接收高层目标,输出一串原子任务序列。真正的挑战是实时性——环境是动态的,路障、堵车、抓取失败随时可能发生。因此实用系统普遍采用“时间预算加重规划”策略:HTN 只在时间预算内做有限深度的分解,一旦执行中发现异常,就在剩余任务网络上重新规划,就像总厨突然发现没酱油,立刻换一份菜谱:

机器人的“家宴”每天都在开:订单在变、路况在变,但总厨的分解心法不变——大任务化小、小事化动作、应付不了就换菜谱,这正是 HTN 在决策与导航中被广泛运用的原因。

π=⟨a1,a2,⋯ ,an⟩,tplan+texec<ΔT\pi = \langle a_1, a_2, \cdots, a_n \rangle, \qquad t_{\text{plan}} + t_{\text{exec}} < \Delta T
这条约束是“实时规划”的底线:规划与执行的总时间必须赶得上任务周期,否则就重规划。
  1. π 计划输出HTN 最终产出的原子动作序列,交给运动控制层按序执行。
    π=⟨a1,a2,⋯ ,an⟩\pi = \langle a_1, a_2, \cdots, a_n \rangle
  2. t_plan 规划耗时一次分解消耗的计算时间,受时间预算严格限制。
    tplant_{\text{plan}}
  3. t_exec 执行耗时原子动作的执行时间,与底层运动控制强相关。
    texect_{\text{exec}}
  4. ΔT 实时周期任务允许的决策周期,若总和超期就触发重规划。
    tplan+texec<ΔTt_{\text{plan}} + t_{\text{exec}} < \Delta T

局限与前沿趋势:菜谱库的边界

总厨再厉害也有短板:菜谱库里没有的菜,他做不出来;菜谱写错了步骤,宴席就砸了。HTN 同样如此,它的一切智慧都来自领域知识(方法库),这既是它高效的根源,也是它最大的局限。

首先是知识工程瓶颈:为复杂领域编写覆盖全面的方法库要耗费大量人力,而且方法库需要随环境更新维护。其次是动态环境的适应能力:方法库是静态的,面对从未见过的情况,HTN 往往束手无策。正因如此,学术界正探索学习型 HTN,从专家示范或历史执行轨迹中自动归纳方法;也出现了将大语言模型与 HTN 结合的混合规划,让模型根据自然语言指令现场生成候选方法,再交给 HTN 做结构化分解。总成本可以用一个式子概括:

未来的机器总厨或许还是 HTN 的大脑,但菜谱不再只是老师傅手写——而是从海量视频里自动学来、由大模型临场生成。总厨的框架还在,只是菜谱库会越来越聪明、越来越厚。

EHTN=Emodel+Emethods+EmaintainE_{\text{HTN}} = E_{\text{model}} + E_{\text{methods}} + E_{\text{maintain}}
想让“机器总厨”上岗,不仅要建模,更要持续投入方法库的编写与维护。
  1. E_model 状态建模成本定义动作、效果与状态变量的工程量。
    EmodelE_{\text{model}}
  2. E_methods 方法编写成本为每个复合任务编写候选方法的代价,是知识工程的主体。
    EmethodsE_{\text{methods}}
  3. E_maintain 维护更新成本环境升级或需求变化后修订方法库的开销。
    EmaintainE_{\text{maintain}}

AI 应用场景

场景一:智能仓储中的机器人搬运调度

阶段:任务级决策与规划

解决的问题:传统做法用 if-else 硬编码把“先到哪、再到哪”写死,订单一变就得改代码,且无法处理突发拥堵。HTN 把“完成订单”当作根任务动态拆成取货、运输、上架等子任务,遇到异常在剩余任务上重规划,代码大幅瘦身、适应性显著提升。

具体案例:某电商仓库中,AMR 收到“拣选订单 A 的货箱”任务。HTN 规划器先分解出“导航至货架→识别商品→抓取→导航至打包台→放置”,当货架通道被占道时,规划器检测到“避障移动”失败,自动换用备选方法,从剩余任务处重新分解,绕开拥堵路径。

场景二:自动驾驶的无保护左转决策

阶段:行为决策与任务规划

解决的问题:无保护左转是最危险的场景之一,要求严格的时序约束。HTN 将“安全通过路口”分解为“减速→观察对向车流→判断间隙→加速汇入→完成转向”,每个子任务带前置条件,如“间隙大于安全阈值”,让决策过程可解释、可回滚。

具体案例:某自动驾驶团队在交叉路口使用 HTN 作为行为决策层:红灯时触发“停车等待”分支,绿灯且对向无车时走“直接通过”分支;执行中若突然出现行人,立即在剩余任务网络上重规划,从“观察”重新开始分解。

场景三:多机器人协同下的任务再分配

阶段:多智能体任务分配与规划

解决的问题:多台机器人共享资源时,单机规划容易互相抢占。HTN 把“协作搬运”作为复合任务,通过前置条件检查互斥资源,确保伙伴未就位时不会贸然执行下一步。

具体案例:无人工厂中,多台 AGV 协作搬运大件物料:HTN 将“协作搬运”分解为“集合→同步行走→联合抬升→到达放置”,每台 AGV 各自维护任务网络,并检查“伙伴已就位”这类前置条件。

Python 演示

python72 行
# -*- coding: utf-8 -*-"""极简 HTN 分层任务网络规划器演示: 把机器人搬运任务逐层分解为原子动作。"""  class HTNPlanner:    def __init__(self):        # 原子动作库: {动作名: (前置条件, 效果)}        self.actions = {            '定位': ({'有地图'}, {'已定位'}),            '路径规划': ({'已定位', '有地图'}, {'路径已生成'}),            '避障移动': ({'路径已生成'}, {'已到达'}),            '拾取': ({'已到达', '机械臂正常'}, {'已拾取'}),            '放置': ({'已拾取', '已到达'}, {'已放置'}),            '报告维护': ({'机械臂异常'}, {'已上报'}),            '等待': ({'已上报'}, {'机械臂正常'}),        }        # 方法库: {(复合任务, 前置条件): [子任务序列]}        self.methods = {            ('完成任务', frozenset({'货架就绪'})):                ['导航到货架', '拾取货箱', '导航到目标点', '放置'],            ('导航到货架', frozenset({'有地图'})):                ['定位', '路径规划', '避障移动'],            ('导航到目标点', frozenset({'有地图'})):                ['定位', '路径规划', '避障移动'],            ('拾取货箱', frozenset({'机械臂正常'})):                ['拾取'],            ('拾取货箱', frozenset({'机械臂异常'})):                ['报告维护', '等待', '拾取'],        }     def decompose(self, task, state, depth=0):        """递归分解: 原子动作直接执行, 复合任务按方法替换。"""        if depth > 10:            return None        if task in self.actions:            pre, effects = self.actions[task]            if not pre.issubset(state):                return None            state |= effects            return [task]        for name, pre in self.methods:            if name != task or not pre.issubset(state):                continue            branch = set(state)            plan = []            for st in self.methods[(name, pre)]:                sub_plan = self.decompose(st, branch, depth + 1)                if sub_plan is None:                    break                plan.extend(sub_plan)            else:                state.clear()                state.update(branch)                return plan        return None  def main():    planner = HTNPlanner()    state = {'有地图', '货架就绪', '机械臂正常'}    print('正常状态:', sorted(state))    plan = planner.decompose('完成任务', state)    print('计划:', ' -> '.join(plan))     state2 = {'有地图', '货架就绪', '机械臂异常'}    print('异常状态:', sorted(state2))    plan2 = planner.decompose('完成任务', state2)    print('计划:', ' -> '.join(plan2))  if __name__ == '__main__':    main()
目的:实现一个极简 HTN 规划器,演示“完成任务”如何被方法递归分解为原子动作序列,并展示机械臂异常时如何自动切换备用方法。
  1. self.actions = { ... }定义原子动作库,每个动作带前置条件与效果
  2. self.methods = { ... }定义方法库,每个复合任务可对应多套分解方案
  3. if task in self.actions:遇到原子任务时直接查动作库
  4. if not pre.issubset(state): return None前置条件不满足则当前分支失败
  5. state |= effects原子动作执行后更新世界状态
  6. for name, pre in self.methods:遍历方法库寻找适用方法
  7. sub_plan = self.decompose(st, branch, depth + 1)递归分解每个子任务
  8. plan.extend(sub_plan)按深度优先先序拼接子计划
  9. state.clear(); state.update(branch)整条分支成功后把新状态提交回去
  10. planner.decompose('完成任务', state)以根任务为起点启动整个规划过程

常见误区

误区HTN 和 STRIPS 一样,都是在状态空间里搜索目标,只是换了个名字而已。

正解两者问题结构完全不同。STRIPS 从初始状态出发,用动作逼近目标条件,搜索节点是“世界状态”;HTN 从初始任务出发,用方法递归拆解到原子动作,搜索节点是“任务网络”。区别就像外卖员对着地图找路,而总厨手里有一本菜谱——前者搜索量大,后者靠知识直给。

为何常见:HTN 最终输出的也是动作序列,看起来与 STRIPS 的解形态一致,容易让人误以为只是换个叫法。

误区复合任务的方法就是几条 if-else 规则,随随便便就能写好。

正解方法包含前置条件、子任务序列以及顺序与资源约束,真实领域的方法库可能有成百上千条,编写、调试和维护成本很高,这就是著名的“知识工程瓶颈”。菜谱写错一个字,整桌宴席都可能砸掉。

为何常见:教材示例中方法只有两三行,让人低估了真实规模的复杂度。

误区HTN 因为有领域知识,所以总能找到最优计划。

正解HTN 通常只保证找到“可行解”而非“最优解”。解的质量取决于方法库设计:如果每个复合任务只提供一份粗糙菜谱,得出的计划可能绕远路;方法库不完整时甚至直接无解。

为何常见:HTN 搜索速度确实快于盲目搜索,容易让人误把“快速”当作“最优”。

误区HTN 只能离线慢慢规划,不能用于机器人的实时决策。

正解机器人领域常用“时间预算加重规划”让 HTN 在线工作:限制分解深度保证时耗,执行中检测到环境变化就在剩余任务网络上重新分解,完全能满足实时导航需求。

为何常见:早期 HTN 大多离线运行,形成了“HTN 等于慢”的刻板印象。

误区HTN 返回无解,一定是规划器(搜索算法)代码写得有问题。

正解真实工程中,绝大多数无解源于方法库缺陷:前置条件写错、子任务缺失、状态建模不完整。就像总厨发现“客人不吃辣”这条信息没进菜谱,责任在菜谱编写者。排查顺序应当先审方法库,再查规划器。

为何常见:人们习惯把失败归因于“算法”,而忽略了领域知识才是 HTN 的命门。

面试题

Q1. 复合任务与原子任务的关键区别是什么?

单选题 · easy

  • A.复合任务通常执行耗时更长
  • B.复合任务需要分解方可执行← 正确答案
  • C.原子任务无需任何前置条件
  • D.二者本质上是相同概念

答案:B。复合任务是“举办家宴”这类还要拆的大任务,原子任务是“切菜”这类能直接上手的动作。A 磨时间不是本质区别,C 不对——原子任务同样要满足前置条件,D 明显错误。答案为 B。

Q2. 方法(method)在 HTN 中描述的是什么?

单选题 · easy

  • A.复合任务的分解方式← 正确答案
  • B.状态到达目标的动作
  • C.原子动作执行效果
  • D.机器人底层运动控制

答案:A。方法回答“这个复合任务怎么拆”,如主菜拆成切菜、焯水、爆炒。B 是 STRIPS 式描述,C 是动作效果,D 属于底层控制,都不属于方法内容。答案为 A。

Q3. 规划过程中,世界状态的正确作用是什么?

单选题 · easy

  • A.只用于初始化不再更新
  • B.供前置条件检查与效果更新← 正确答案
  • C.决定任务网络拓扑顺序
  • D.仅在输出计划时展示

答案:B。世界状态是“事实记录本”:前置条件检查它,原子动作的效果更新它。A、D 忽略了执行过程对状态的推进,C 把状态与任务网络结构混为一谈。答案为 B。

Q4. 与 STRIPS 相比,HTN 的核心优势是什么?

单选题 · mid

  • A.用方法知识缩小搜索空间← 正确答案
  • B.不需要任何领域知识支持
  • C.一定能够找到全局最优解
  • D.不需要维护世界状态

答案:A。HTN 用方法知识把复合任务直接展开,候选分支远少于 STRIPS 对动作的盲目尝试。B、D 与 HTN 的实际情况相反,C 夸大了其最优性保证。答案为 A。

Q5. 关于 HTN 的递归分解过程,下列说法正确的是?

单选题 · mid

  • A.复合任务被方法子任务序列替换← 正确答案
  • B.复合任务必须先于原子任务执行
  • C.原子任务也需方法继续分解
  • D.分解过程不允许修改世界状态

答案:A。递归分解的核心动作是“用方法的子任务序列替换当前复合任务”。B 颠倒了先后关系,C 说原子任务还要分解是错的,D 与“原子动作更新世界状态”矛盾。答案为 A。

Q6. 动态环境下机器人用 HTN 实时导航的关键机制是什么?

单选题 · mid

  • A.时间预算内分解并触发重规划← 正确答案
  • B.提前穷举场景并离线求解
  • C.禁止任何原子动作执行失败
  • D.不采用任何启发式剪枝

答案:A。实时导航要求 HTN 在时间预算内完成分解,并在失败时对剩余任务重规划。B 穷举离线不可行,C 禁止失败不现实,D 放弃剪枝会拖慢搜索。答案为 A。

Q7. HTN 规划器返回无解,最可能的原因是什么?

单选题 · mid

  • A.规划器内存计算资源不足
  • B.方法库不完整或条件错误← 正确答案
  • C.原子动作数量超过十步
  • D.初始状态包含事实过多

答案:B。HTN 无解最常见的原因是方法库不完整或前置条件写错,使某复合任务找不到适用方法。内存、步数、事实多少在合理设计中不构成首要问题。答案为 B。

Q8. 典型 HTN 方法库中通常包含哪些内容?(多选)

多选题 · mid

  • A.复合任务的前置条件← 正确答案
  • B.分解后的子任务序列← 正确答案
  • C.子任务间的顺序约束← 正确答案
  • D.传感器的实时采集数据

答案:ABC。方法库记录复合任务的前置条件、子任务序列以及子任务间的约束,这些决定分解能否进行。传感器实时读数是执行层数据,不属于方法库内容。答案为 [0,1,2]。

Q9. 下列哪些是 HTN 规划器的常见优化策略?(多选)

多选题 · hard

  • A.启发式筛选候选方法← 正确答案
  • B.偏序网络延迟任务定序← 正确答案
  • C.限制递归分解深度← 正确答案
  • D.手工枚举全部动作序列

答案:ABC。启发式筛选、偏序网络延迟定序、限制分解深度都是常见优化。D 手工枚举全部动作序列恰恰违背 HTN 用知识压缩搜索空间的初衷。答案为 [0,1,2]。

Q10. 针对 HTN 局限性的前沿研究方向包括哪些?(多选)

多选题 · hard

  • A.从历史轨迹学习方法库← 正确答案
  • B.用大模型现场生成方法← 正确答案
  • C.与强化学习混合规划← 正确答案
  • D.放弃分解改用查表法

答案:ABC。从轨迹学习方法库、用大模型生成方法、与强化学习混合,都是针对知识工程瓶颈的主流探索。D 放弃分解改用查表法回归硬编码,并非前沿方向。答案为 [0,1,2]。