1. 从“上下文模式”这个命名说起:它到底在解决什么问题
第一次看到“context-mode”这个词,很多人会下意识觉得它是个抽象概念,甚至怀疑它是不是又一个被过度包装的技术名词。但如果你真正在工程一线待过,尤其是在做对话系统、状态机、或者任何需要“记住当前处境”的模块时,就会明白这个词背后藏着一个非常具体、非常痛的现实问题:系统在运行时,到底该以什么样的姿态去理解当前这一刻的输入?
举个最直观的例子。你写一个客服机器人,用户第一句说“我要退货”,第二句说“订单号是12345”,第三句说“算了,改成换货”。如果系统没有上下文模式的概念,它会把这三句话当成三个独立请求,第三句里的“算了”和“改成”就完全丢失了指向,系统根本不知道用户在否定什么、修改什么。而有了上下文模式,系统会维护一个“当前对话状态”,知道用户正在处理订单12345,意图从退货切换到了换货。这就是 context-mode 最朴素的价值:让系统知道“现在处于什么场景下”,并据此决定如何解析输入、如何选择响应策略。
但问题远不止“记住历史”这么简单。真正做过这类系统的人都知道,上下文模式的核心难点在于模式切换的时机与粒度。你不可能把所有历史都无差别地塞进当前决策,那样会导致噪声爆炸;你也不能只保留最近一句,那样会丢失关键约束。所以 context-mode 本质上是一套状态管理策略,它要回答三个问题:当前处于哪个模式?这个模式由哪些信号触发切换?切换后哪些上下文该保留、哪些该丢弃?
我在实际项目里踩过最典型的一个坑,就是早期把“模式”设计得太粗。当时做一个多轮表单填写系统,只分了“填写中”和“确认中”两个模式。结果用户在中途问了一句“你们支持哪些支付方式”,系统直接把这句当成表单字段值填进去了,因为“填写中”模式下所有输入都被当作字段内容。后来我们引入了更细的模式划分,比如“字段填写模式”“问答模式”“纠错模式”,并且给每个模式定义了明确的进入和退出条件,问题才解决。这个经历让我深刻体会到:context-mode 的设计质量,直接决定了系统是“智能”还是“智障”。
所以这篇文章,我想从实际工程落地的角度,把 context-mode 这个东西拆开来讲。不堆砌学术定义,也不搞那些看起来高大上但落不了地的架构图。我会围绕几个核心问题展开:模式到底该怎么划分?切换信号怎么设计才可靠?上下文窗口怎么管理才不会爆?以及在不同业务场景下,这套思路该怎么调整。如果你正在做对话系统、工作流引擎、或者任何需要“状态感知”的模块,这些内容应该能帮你少走不少弯路。
2. 模式划分的粒度:粗了不够用,细了管不住
2.1 为什么不能只有“开”和“关”两种模式
很多初学者在设计 context-mode 时,最容易犯的错误就是模式太少。最常见的就是只分“空闲”和“忙碌”,或者“正常”和“异常”。这种二分法在简单场景下能跑,但只要业务稍微复杂一点,立刻就会捉襟见肘。
我见过一个做智能家居中控的项目,最初只定义了“待机模式”和“执行模式”。用户说“打开客厅灯”,系统进入执行模式,执行完回到待机。听起来没问题。但后来用户说“打开客厅灯,然后把卧室灯调暗一点,对了,空调也开到26度”。这时候系统在执行第一个动作后,收到“然后把卧室灯调暗”时,它已经回到了待机模式,于是把这句话当成了新的独立指令,丢失了“然后”这个顺序关系。更糟的是,“对了”这种插入语直接让系统懵了,因为它不知道这是对前文的补充还是新话题。
这就是模式粒度过粗的典型症状:系统无法区分“连续指令”“补充说明”“话题切换”这些不同的交互意图。要解决这个问题,就必须把模式拆得更细。比如至少要有“指令接收模式”“指令执行模式”“指令追加模式”“话题游离模式”。每个模式对应不同的输入解析策略和上下文保留策略。
但这里马上会遇到另一个问题:模式拆细了,切换逻辑就复杂了。如果每个模式都要定义一堆进入退出条件,代码会变得极其难维护。所以关键不是“拆得越细越好”,而是找到业务语义上的自然边界。
2.2 用“意图簇”来指导模式划分
我的经验是,不要凭空拍脑袋想模式,而是先梳理业务里到底有哪些“意图簇”。所谓意图簇,就是一组在语义上紧密相关、处理逻辑相似的意图。比如在电商客服场景里,“查订单”“改地址”“催发货”可以归为“订单操作簇”;“问优惠”“问保修”“问发票”可以归为“信息咨询簇”;“投诉”“差评”“转人工”可以归为“升级处理簇”。
每个意图簇天然对应一个上下文模式。因为同一簇内的意图,需要的上下文信息是高度重叠的。比如订单操作簇都需要订单号、用户身份、当前订单状态;信息咨询簇可能只需要商品ID和知识库;升级处理簇则需要完整的对话历史和情绪标记。
这样划分的好处是,模式切换的信号也变得清晰了:当系统检测到用户意图从一个簇跳到另一个簇时,就触发模式切换。而簇内切换意图,比如从“查订单”变成“改地址”,不需要切换模式,只需要更新当前模式内的槽位信息。
下面这张表是我在一个实际项目里用过的模式划分方案,你可以参考这个思路来设计自己的:
| 模式名称 | 覆盖意图簇 | 核心保留上下文 | 典型切换信号 |
|---|---|---|---|
| 订单操作模式 | 查单、改址、退换、催发 | 订单号、用户ID、订单状态 | 用户提及订单相关关键词 |
| 信息咨询模式 | 优惠、保修、发票、参数 | 商品ID、知识库命中记录 | 用户提问句式且无订单指代 |
| 升级处理模式 | 投诉、差评、转人工 | 完整对话历史、情绪值 | 负面情绪词或明确转人工请求 |
| 闲聊模式 | 问候、感谢、无关话题 | 仅保留最近一轮 | 连续两轮无业务意图 |
这张表的关键在于最后一列:切换信号必须是可检测的、低误报的。比如“用户提及订单相关关键词”这个信号,就需要维护一个关键词表,并且要处理否定形式(“我没有订单”不应该触发订单模式)。这些细节后面会展开讲。
2.3 模式嵌套与优先级:当多个模式同时“想”生效
现实业务里,模式往往不是平铺的,而是有嵌套和优先级关系的。比如“升级处理模式”可能嵌套在“订单操作模式”之上——用户正在处理订单,突然情绪激动要投诉,这时候系统应该同时保持订单上下文和升级处理策略。
我处理这种情况的做法是:把模式分成“基础模式”和“叠加模式”两层。基础模式决定核心的输入解析和上下文保留策略,同一时间只能有一个。叠加模式则是在基础模式之上附加的行为修饰,可以有多个,按优先级排序。
比如基础模式是“订单操作模式”,叠加模式可能有“情绪安抚叠加”“多轮确认叠加”“超时提醒叠加”。当用户说“你们这什么破系统,订单12345到现在还没发货”,系统会:基础模式识别到订单号,进入订单操作模式;同时情绪检测触发“情绪安抚叠加”,在响应生成时优先使用安抚话术;如果用户之前已经问过三次发货时间,还会触发“超时提醒叠加”,主动提供补偿方案。
这种分层设计的好处是解耦。基础模式的切换逻辑不需要关心情绪、超时这些横切关注点,叠加模式可以独立演进。但要注意,叠加模式不能改变基础模式的核心上下文保留策略,否则会导致状态混乱。比如“情绪安抚叠加”不应该让系统忘记订单号,它只影响响应风格和附加动作。
3. 切换信号的设计:什么时候该“换挡”,什么时候该“稳住”
3.1 显式信号与隐式信号的取舍
模式切换的信号来源,大致可以分为显式和隐式两类。显式信号就是用户明确表达的动作,比如“我要投诉”“转人工”“换个话题”。隐式信号则是从用户输入里推断出来的,比如关键词命中、句式特征、情绪分值、对话轮次等。
显式信号的优点是准确率高,几乎不会误触发。但缺点是覆盖率低——用户不会每次都告诉你“我要换模式了”。更多时候,用户是自然而然地从一个话题滑到另一个话题,没有任何显式标记。所以一个健壮的 context-mode 系统,必须同时依赖显式和隐式信号。
我的做法是:显式信号直接触发切换,隐式信号累积到阈值才触发。比如用户说“转人工”,这是显式信号,立刻切到升级处理模式。而如果用户只是连续两轮都在问优惠相关的问题,系统通过隐式信号累积,判断意图簇从订单操作偏移到了信息咨询,才触发模式切换。
这里有个细节要注意:隐式信号的阈值不能设得太低,否则系统会频繁“跳模式”,导致上下文反复重建,用户体验反而更差。我一般会设置一个“模式稳定期”,比如切换后至少保持3轮对话,除非遇到显式信号,否则不轻易再切。这个稳定期在代码里就是一个计数器,简单但有效。
3.2 关键词表的维护:别让“否定词”毁了一切
用关键词命中来做隐式信号,是最常见也最容易翻车的方式。翻车的重灾区就是否定形式。比如你定义“退货”“退款”“退单”触发订单操作模式,结果用户说“我不需要退货,我只是想问下保修”。如果系统只做简单字符串匹配,“退货”这个词命中了,模式就切错了。
解决这个问题,需要在关键词匹配前加一层否定检测。具体做法是:在命中关键词的位置前后各取一个短窗口(比如前后5个字符),检查窗口内是否出现否定词,如“不”“没”“别”“无需”“不用”等。如果出现否定词,这次命中就作废。
但否定检测也不是万能的。中文里还有“反语”和“假设”的情况。比如“要是你们再不发货,我就只能退货了”——这里“退货”是威胁,不是真的要退货,但系统如果切到退货模式,也不算完全错,因为用户确实在表达不满。这种边界情况,我的建议是不要追求100%准确,而是让模式切换的代价可控。也就是说,即使切错了,系统也能通过后续的纠错机制快速恢复。比如切到退货模式后,系统可以主动问一句“您是想办理退货,还是想咨询其他问题?”用户说“不是退货”,系统再切回去。这比追求完美匹配要现实得多。
3.3 情绪信号的量化:从“感觉”到“分值”
情绪信号在模式切换里非常关键,尤其是从正常模式切到升级处理模式。但情绪是个很虚的东西,怎么把它变成可计算的信号?我的做法是维护一个情绪分值,初始为0,每轮对话根据用户输入调整。
调整规则可以很简单:出现负面词(“差”“慢”“垃圾”“投诉”)加1分;出现强烈负面词(“骗子”“曝光”“315”)加3分;出现正面词(“谢谢”“好的”“满意”)减1分;出现安抚性表达(“理解”“抱歉”“马上处理”)减2分。分值超过阈值(比如5分)就触发升级处理模式。
这个分值不需要很精确,它的核心作用是提供一个可累积、可衰减的量化指标,避免单轮情绪波动就触发模式切换。比如用户只是随口说了一句“有点慢”,分值加1,没到阈值,系统继续正常处理。但如果用户连续三轮都在抱怨,分值累积到5,系统就切到升级模式,主动提供补偿或转人工。
我在实际项目里还加了一个“情绪衰减”机制:如果连续两轮没有负面词,分值自动减1。这样可以防止用户早期的不满一直累积,导致后面正常对话也被误判为升级场景。
4. 上下文窗口的管理:记住该记的,忘掉该忘的
4.1 上下文不是越多越好:噪声与成本的平衡
很多人以为上下文模式就是“把所有历史都记住”,这其实是个误解。上下文越多,噪声越大,系统越容易被无关信息干扰。而且从工程成本看,上下文越长,存储和计算开销越大,响应延迟也越高。
所以 context-mode 里的上下文管理,核心是选择性保留。每个模式应该定义自己的“上下文保留策略”,明确哪些信息必须保留、哪些可以丢弃、哪些需要压缩。
比如在订单操作模式里,必须保留的是订单号、用户ID、当前订单状态、用户已表达的诉求。可以丢弃的是寒暄内容、与订单无关的闲聊。需要压缩的是多轮确认过程,比如用户反复确认地址,只需要保留最终确认的地址,中间的确认轮次可以压缩成一条“地址已确认”的记录。
我通常会用三个容器来管理上下文:核心槽位(必须保留的结构化信息)、近期对话(最近N轮的原始文本)、摘要记忆(对更早对话的压缩摘要)。核心槽位随模式切换而更新,近期对话滚动窗口,摘要记忆定期生成。这样既保证了关键信息不丢,又控制了上下文长度。
4.2 槽位填充与模式切换的联动
槽位是上下文模式里最结构化的部分。每个模式应该定义自己关心的槽位集合。比如订单操作模式关心“订单号”“操作类型”“期望时间”;信息咨询模式关心“商品ID”“问题类型”“知识库命中”。
模式切换时,槽位的处理策略很关键。我的经验是:新模式的槽位从旧模式继承可复用的部分,不可复用的部分清空或标记为待确认。比如从订单操作模式切到信息咨询模式,“订单号”这个槽位可以保留,因为用户可能接下来问“这个订单的保修政策”。但“操作类型”槽位就应该清空,因为信息咨询不涉及操作。
这里有个容易忽略的点:槽位继承可能带来误导。比如用户先问“订单12345的保修”,系统进入信息咨询模式,保留了订单号。然后用户说“那算了,帮我退了吧”,系统切回订单操作模式,订单号还在,这没问题。但如果用户说“那算了,我想问下别的商品”,系统如果还保留订单号,就可能把“别的商品”和订单12345关联起来,造成错误。所以槽位继承要加一个“时效性”判断:如果槽位信息超过一定轮次没有被引用,就应该降权或清除。
4.3 摘要记忆的生成时机与粒度
摘要记忆是控制上下文长度的关键手段,但什么时候生成摘要、摘要粒度多粗,都需要仔细设计。生成太频繁,摘要质量差;生成太稀疏,上下文又太长。
我的做法是按模式切换点来生成摘要。当一个模式结束、切换到另一个模式时,对刚刚结束的模式内的对话生成一段摘要,存入摘要记忆。这样摘要的粒度天然就是“一个模式周期”,语义完整,不会把半截对话截断。
摘要的内容也有讲究。不是简单地把对话复述一遍,而是提取对后续可能有用的信息。比如订单操作模式的摘要,应该包含:用户最终确认的操作、涉及的订单号、操作结果、用户情绪变化。而寒暄、重复确认、系统提示这些内容,摘要里可以省略。
生成摘要可以用规则模板,也可以用模型生成。规则模板的好处是稳定可控,缺点是灵活性差。模型生成的好处是自然,缺点是有时候会“编造”信息。我在对准确性要求高的场景里,会用规则模板先提取结构化信息,再用模型生成自然语言摘要,两者结合。比如先提取“订单号=12345,操作=退货,结果=已受理”,然后生成“用户就订单12345申请退货,系统已受理”。这样既保证了信息准确,又让摘要可读。
5. 不同业务场景下的 context-mode 变体
5.1 对话系统:多轮交互中的模式保持与切换
对话系统是 context-mode 最典型的应用场景。这里的核心挑战是多轮交互中的指代消解和意图延续。用户说“我要退货”,系统问“请提供订单号”,用户说“12345”,系统问“退货原因是什么”,用户说“质量有问题”。这四轮对话里,系统必须始终保持“退货模式”,并且把“12345”正确填入订单号槽位,把“质量有问题”填入原因槽位。
如果中间用户插了一句“你们运费谁出”,系统应该怎么处理?我的做法是:不切换模式,但在当前模式内开启一个“子问答”。系统回答运费问题后,主动把话题拉回“退货原因”。这样既回答了用户问题,又没有丢失主流程。实现上,就是在退货模式里加一个“临时问答”标记,问答结束后清除标记,继续原流程。
这种“子问答”机制的关键是要有回归主流程的引导语。不能回答完运费就完了,用户可能以为系统忘了退货的事。所以回答完要加一句“关于退货原因,您刚才提到质量有问题,能具体说下吗?”这样用户就知道主流程还在继续。
5.2 工作流引擎:状态机与上下文模式的映射
工作流引擎里的 context-mode,更多体现为状态机中的状态与上下文变量的绑定。比如一个审批流程,有“待提交”“审批中”“已通过”“已驳回”几个状态。每个状态对应一个上下文模式,决定哪些变量可读写、哪些操作可执行。
这里容易踩的坑是状态与上下文的耦合过紧。比如“审批中”状态需要读取“申请人”“审批人”“申请内容”,但如果把这些变量直接硬编码在状态逻辑里,后续增加新变量就要改状态机。更好的做法是把上下文变量做成可扩展的键值对,状态只定义“需要哪些键”,具体值由外部注入。这样增加变量时,只需要在配置里加一个键,不需要改状态机核心逻辑。
另一个坑是状态回退时的上下文恢复。比如审批被驳回,回到“待提交”状态,这时候上下文应该恢复到提交前的样子,还是保留驳回意见?我的经验是:保留驳回意见,但清空审批过程中的临时变量。因为驳回意见是用户需要看到的信息,而审批过程中的中间状态(比如“审批人已查看”)对申请人没有意义。
5.3 游戏 AI:行为树中的上下文模式
游戏 AI 里的 context-mode 通常和行为树结合。每个行为节点可以看作一个模式,节点激活时进入对应模式,节点结束时退出。但游戏 AI 的特殊之处在于实时性要求高,模式切换不能有太复杂的计算。
我在做游戏 AI 时,会把模式切换信号做得非常轻量。比如用“黑板”系统来共享上下文,每个行为节点读写黑板上的键值。模式切换就是切换当前活跃的行为节点,上下文通过黑板传递。黑板上只保留最必要的状态,比如“当前目标”“威胁等级”“血量阈值”,其他细节由各行为节点自己管理。
这种设计的优点是响应快,因为黑板读写是内存操作,没有复杂的序列化。缺点是上下文容易变得碎片化,不同节点各管各的,缺乏全局视图。所以我会定期做“黑板整理”,把不再需要的键清理掉,把相关的键合并成复合键。这个整理过程可以放在帧间隙做,不影响实时性。
6. 实操中的坑与应对:那些文档里不会写的事
6.1 模式震荡:为什么系统会“反复横跳”
模式震荡是 context-mode 最烦人的问题之一。表现就是系统在两个模式之间反复切换,每轮对话都换模式,导致上下文不断重建,用户体验极差。
造成震荡的原因通常有两个:切换信号太敏感,或者模式边界太模糊。比如用户说“我想退货,但是先问下运费”,这句话同时命中了“退货”和“运费”两个信号。如果系统先检测到“退货”切到退货模式,又检测到“运费”切到咨询模式,下一轮用户说“运费太贵了,还是退了吧”,又切回退货模式。这就是典型的信号冲突导致的震荡。
我的应对方法是给模式切换加一个“冷却期”。切换后至少保持N轮(我一般用3轮),期间即使有新的切换信号,也只记录不执行。冷却期结束后,再根据累积的信号决定是否切换。这样可以过滤掉短期的信号波动,让模式保持稳定。
另一个方法是给模式加优先级。当多个信号同时命中时,按优先级选择目标模式。比如“退货”的优先级高于“运费咨询”,因为退货是主诉求,运费是附带问题。优先级可以在配置里定义,也可以根据业务规则动态调整。
6.2 上下文丢失:用户觉得系统“失忆”了
上下文丢失是另一个高频问题。用户明明刚才说了订单号,系统下一轮又问“请提供订单号”。这种体验非常糟糕,用户会觉得系统根本没在听。
造成上下文丢失的原因,除了前面说的模式切换导致槽位清空,还有一个常见原因是上下文存储的键不一致。比如一个模块用“order_id”存订单号,另一个模块用“orderId”读,读不到就以为没有。这种问题在多人协作的项目里特别常见。
我的做法是定义统一的上下文键名规范,并且在代码里用常量而不是字符串字面量。比如定义CONTEXT_KEY_ORDER_ID = "order_id",所有地方都用这个常量。这样即使键名需要改,也只需要改一处。另外,在模式切换时,加一个上下文完整性检查,如果发现关键槽位为空,主动从摘要记忆或近期对话里尝试恢复。恢复不了再问用户,而不是直接问。
6.3 性能瓶颈:上下文管理拖慢了响应
当上下文变得很大时,管理操作本身可能成为性能瓶颈。比如每轮对话都要遍历整个对话历史来提取槽位,或者每次模式切换都要重新生成摘要,这些操作如果太频繁,会明显增加响应延迟。
我的优化经验是把上下文管理做成增量式的。槽位提取不是每轮都全量扫描,而是只处理新增的对话轮次,把提取结果合并到已有槽位。摘要生成也不是每次切换都做,而是当对话轮次超过阈值(比如10轮)才触发一次。这样大部分轮次只需要做轻量的增量更新,只有少数轮次做重操作。
另外,上下文存储要选对数据结构。槽位用哈希表,O(1)读写;近期对话用环形缓冲区,固定长度,自动淘汰旧数据;摘要记忆用列表,按时间顺序追加。这些数据结构的选择看起来是小事,但在高并发场景下,对性能的影响非常明显。
7. 从零搭建一个最小可用的 context-mode 框架
7.1 核心数据结构设计
如果你要自己实现一个 context-mode 框架,不需要一上来就搞得很复杂。核心数据结构只需要三个:模式定义、上下文存储、切换规则。
模式定义可以用一个字典来表示,键是模式名,值是该模式的配置,包括:关心的槽位列表、上下文保留策略、切换信号列表、优先级。比如:
MODES = { "order_operation": { "slots": ["order_id", "user_id", "operation_type"], "keep_recent": 5, "switch_signals": ["order_keywords", "explicit_switch"], "priority": 2 }, "info_consult": { "slots": ["product_id", "question_type"], "keep_recent": 3, "switch_signals": ["question_pattern", "explicit_switch"], "priority": 1 } }上下文存储用一个类来管理,内部维护槽位字典、近期对话队列、摘要列表。对外提供get_slot、set_slot、add_turn、get_recent、summarize等方法。
切换规则用一个函数来实现,输入是当前模式、用户输入、上下文存储,输出是目标模式(可能不变)。函数内部先检查显式信号,再检查隐式信号,最后根据优先级和冷却期决定是否切换。
7.2 切换逻辑的伪代码与注释
def decide_mode(current_mode, user_input, context): # 1. 检查显式切换信号 if has_explicit_switch(user_input): return get_explicit_target(user_input) # 2. 检查冷却期 if context.turns_since_switch < COOLDOWN_TURNS: return current_mode # 3. 收集隐式信号 signals = [] for mode_name, mode_config in MODES.items(): for signal in mode_config["switch_signals"]: if check_signal(signal, user_input, context): signals.append((mode_name, mode_config["priority"])) # 4. 按优先级选择目标模式 if signals: signals.sort(key=lambda x: x[1], reverse=True) target = signals[0][0] if target != current_mode: context.turns_since_switch = 0 return target # 5. 无切换信号,保持当前模式 context.turns_since_switch += 1 return current_mode这段伪代码的关键在于冷却期的位置:它在显式信号检查之后、隐式信号检查之前。这意味着显式信号可以突破冷却期,但隐式信号不能。这是合理的,因为用户明确说“转人工”时,系统应该立即响应,而不是等冷却期结束。
7.3 测试策略:怎么验证模式切换是对的
context-mode 的测试比普通功能测试要难,因为它的行为依赖多轮交互的状态。我的测试策略是场景化测试:预先定义一组对话场景,每个场景包含多轮用户输入和期望的模式序列,然后跑测试看实际模式序列是否匹配。
比如一个测试场景:
| 轮次 | 用户输入 | 期望模式 |
|---|---|---|
| 1 | 我要退货 | order_operation |
| 2 | 订单号12345 | order_operation |
| 3 | 你们运费谁出 | order_operation(子问答) |
| 4 | 质量有问题 | order_operation |
| 5 | 算了,先不退了,问下保修 | info_consult |
| 6 | 就是那个订单的保修 | info_consult |
这个场景覆盖了模式保持、子问答、模式切换、槽位继承等多个行为。跑通这个场景,基本就能验证核心逻辑是对的。
除了正常场景,还要设计边界场景:空输入、超长输入、纯符号输入、否定句、反语句。这些场景不一定有明确的期望模式,但至少要保证系统不崩溃、不进入死循环。
8. 一些个人体会:context-mode 的哲学
做了这么多年的状态管理和上下文设计,我越来越觉得 context-mode 本质上是在回答一个哲学问题:系统如何理解“现在”?这个问题没有标准答案,因为“现在”的定义取决于你的业务目标。对客服系统来说,“现在”是用户当前最迫切的诉求;对工作流来说,“现在”是流程所处的阶段;对游戏 AI 来说,“现在”是战场上的即时态势。
所以设计 context-mode 时,不要一上来就想“我要支持多少种模式”,而是先问“我的业务里,什么信号代表‘现在’变了”。这个信号可能是一个关键词、一个情绪峰值、一个时间阈值、一个外部事件。找到这个信号,模式划分和切换逻辑自然就出来了。
另一个体会是:不要追求完美的模式切换。用户是复杂的,语言是模糊的,任何规则都会有误判。与其花大量精力去消除所有误判,不如设计一个容错机制:切错了能快速恢复,上下文丢了能重新问,用户不满意能转人工。一个能容错的简单系统,比一个追求完美但脆弱的复杂系统,在实际业务里活得久得多。
最后,context-mode 不是越复杂越好。我见过一些项目,模式定义了二十几种,切换规则写了上千行,结果维护成本极高,新人根本看不懂。后来我们做了一次大重构,把模式压缩到五种,切换规则简化到两百行,效果反而更好。好的设计是刚好够用,而不是面面俱到。这个度怎么把握,只能靠在业务里不断试错和调整。