qinghuan
← 让长任务能暂停和恢复 / 全部笔记

单元 22 / 让长任务能暂停和恢复

P0 · 必会主线L2 · 进阶

用状态机决定哪里需要 Agent

先写清业务要经过哪些步骤,再把需要临场判断的选择交给模型,并限制它能走的路径。

本篇目录 · 按需要跳转

读完这篇,你能做什么

能实现检索、草稿、校验工作流,并通过测试确认非法跳转和超过修订预算的操作会被拒绝。

学习理由、准备要求与知识点

为什么学它

先画清任务允许怎样前进、失败后怎样处理,再决定哪几步可以让模型临场选择,流程会更容易检查。

先读这些内容

知识点清单

  • 工作流与 Agent 控制权
  • 固定节点与动态决策
  • 显式状态数据
  • 枚举路由白名单
  • 节点前置条件和产出
  • 合法状态转换
  • 循环修订上限
  • 假模型驱动流程测试

运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。

01先弄懂它是什么

有些步骤可以提前写死,有些步骤需要根据情况选择。工作流由开发者预先安排主要步骤和走向,Agent 则把部分下一步选择交给模型。两者可以一起用:先固定校验输入,再让模型在有限工具里选择,最后按固定规则验证和提交。设计时要说清每个决定由谁负责,以及程序必须强制限制哪些动作。

任务进度最好单独保存成数据,避免只能从聊天文字里猜。例如 question、evidence、draft、attempts 和 status 分别记录问题、证据、草稿、尝试次数和状态。每个节点先检查能否执行,再更新这些数据;路由只能从允许的下一节点中选择。使用枚举和白名单,就能在执行前拦住不存在或当前不允许的路径。

状态图存在通往终态的路径,并不意味着实际运行一定终止。模型可能总是选择修订,工具可能不断失败;应用还需要循环次数、截止时间和取消规则。本篇图算法检查“哪些可达状态根本没有任何终态出路”,这是结构诊断的一部分。

文中这几个词,先认一认

状态
能核对任务当前进度和事实的数据,例如已找到的证据、尝试次数和是否完成。
节点与路由
节点负责做一步处理,路由负责按规则选择下一步可以去哪里。
终态
任务已经完成、失败或取消等状态,到达后不再继续普通处理流程。

还有陌生词?到术语小抄里看一个具体例子 ↗

02看看它怎么工作

建立正向图表示允许转移,从起点搜索得到实际可能到达的状态。再反转每条边,从所有终态搜索,得到存在某条路径可以到达终态的状态。两集合之差,就是从起点可达但结构上没有终态出路的区域。

例如START到RETRIEVE,RETRIEVE和RETRY互相循环且没有其他边,这三个状态都没有终态出路;若RETRIEVE另有到DONE的边,则结构检查不再报无出口,但运行仍可能永远选择RETRY。区分“存在路径”和“所有策略都终止”,避免给新手过强保证。

每个节点还需要明确“拿到什么才能开始、完成后交出什么”。例如 GENERATE 至少要有一条授权证据,VERIFY 要有完整草稿,COMMIT 要有已确认的操作版本。流程图允许走到这个节点,不代表所需数据已经齐全,因此测试要同时检查路线是否合法,以及节点必需条件是否满足。

正在加载流程图…

图中状态只描述本篇可执行机制;业务调用仍需由外层应用负责。
查看流程图源码(Mermaid)
flowchart TD
  S["START"] --> R["检索证据"]
  R --> G["生成草稿"]
  G --> V["确定性验证"]
  V -->|"通过"| D["DONE"]
  V -->|"可修复且有预算"| R
  V -->|"终止条件"| F["FAILED 或 CANCELLED"]

按顺序看,每一步做了什么

  1. 定义状态字段、节点输入产出和有限路由枚举。
  2. 画出正常、失败、取消及修订路径。
  3. 从起点做可达性检查,识别无法到达的配置节点。
  4. 反向从终态搜索,发现可达但无出口区域。
  5. 使用假模型与固定事件测试数据条件和循环预算。

03对照实现代码

OpenJDK 17 ArrayDeque 源码 (新标签页)↗

OpenJDK 17 GA,jdk-17+35 · ArrayDeque.addLast / removeFirst

下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。

核心机制代码 · java
Deque<Integer> queue = new ArrayDeque<>();
queue.addLast(start); visited[start] = true; // 入队时标记,避免循环重复入队。
while (!queue.isEmpty()) {
    int node = queue.removeFirst();
    for (int next : graph.get(node)) if (!visited[next]) {
        visited[next] = true; queue.addLast(next); // 队列只保存尚待展开的可达节点。
    }
}

ArrayDeque提供先进先出的双端操作,用于广度优先搜索的待处理队列。容器不理解状态图,也不会替应用检测合法节点或终态。

入队时标记visited保证每个节点最多入队一次,循环边因此不会造成无限搜索。若出队后才标记,同一节点可能被多个前驱反复入队。

正向和反向搜索使用相同队列机制,但回答不同问题:从起点能去哪,与哪些节点能到终态。图的方向不能在构建时混淆。

Java模型只做静态结构检查,不调用模型与工具。运行时条件、网络故障和模型选择策略不在图搜索证明中,需通过状态机与预算另行约束。

代码先调用谁,再调用谁

  1. 构造正向与反向邻接表
  2. 从起点入队搜索
  3. 从所有终态在反图搜索
  4. 比较两组可达标记
  5. 报告结构无出口状态

04动手写一个例子

工作流可达无出口状态检测

给定有限有向图、起点和终态集合,返回从起点可达但不存在任何终态路径的节点。它不证明实际策略必然终止。Java17 Main.java内置图结构自测。

输入 1
0到1,1到1,终态2
输出 1
[0,1]
输入 2
0到1,1到1且1到2,终态2
输出 2
[]

正向搜索找出起点可达集合R,无关孤立节点不应被报告为当前任务堵点。所有节点和边索引先验证,防止坏配置造成数组异常。

在反向图从终态集合多源搜索得到集合T。节点能在反图被终态到达,当且仅当原图中它存在通往终态的路径。

输出R减T即可。算法检测不存在出口的强制失败区域;有出口但策略总绕圈的情况需要额外终止预算或策略证明,不能用空诊断列表代替。

为什么这样写是对的正向visited恰好为起点可达节点,反向visited恰好为能到任一终态的节点;输出每项同时满足前者且不满足后者。

时间 O(V+E)额外空间 O(V+E)

两份邻接表保存每条边,两个BFS各访问节点和边至多一次。输出和访问数组O(V),不包含运行具体节点的成本。

保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。

Main.java · 完整可运行程序 · Java
import java.util.*;
public class Main {
    static boolean[] reach(List<List<Integer>> graph,Collection<Integer> starts){
        boolean[] seen=new boolean[graph.size()];Deque<Integer> q=new ArrayDeque<>();
        for(int s:starts)if(!seen[s]){seen[s]=true;q.addLast(s);}
        while(!q.isEmpty())for(int v:graph.get(q.removeFirst()))if(!seen[v]){
            seen[v]=true;q.addLast(v); // 入队时标记,每个节点最多展开一次。
        }
        return seen;
    }
    static List<Integer> trapped(int n,int[][] edges,int start,Set<Integer> terminal){
        if(n<=0||start<0||start>=n)throw new IllegalArgumentException("start");
        List<List<Integer>> g=new ArrayList<>(),r=new ArrayList<>();for(int i=0;i<n;i++){g.add(new ArrayList<>());r.add(new ArrayList<>());}
        for(int t:terminal)if(t<0||t>=n)throw new IllegalArgumentException("terminal");
        for(int[] e:edges){if(e.length!=2||e[0]<0||e[0]>=n||e[1]<0||e[1]>=n)throw new IllegalArgumentException("edge");g.get(e[0]).add(e[1]);r.get(e[1]).add(e[0]);}
        boolean[] fromStart=reach(g,List.of(start)),toEnd=reach(r,terminal); // 反向搜索表示存在终态路径。
        List<Integer> out=new ArrayList<>();for(int i=0;i<n;i++)if(fromStart[i]&&!toEnd[i])out.add(i);
        return out; // 空结果只排除结构无出口,不保证策略一定终止。
    }
    static void eq(List<Integer> e,List<Integer> a){if(!e.equals(a))throw new AssertionError(a);}
    public static void main(String[] args){
        eq(List.of(0,1),trapped(3,new int[][]{{0,1},{1,1}},0,Set.of(2)));
        eq(List.of(),trapped(3,new int[][]{{0,1},{1,1},{1,2}},0,Set.of(2)));
        eq(List.of(),trapped(3,new int[][]{{0,1}},0,Set.of(1)));// 孤立2不影响当前起点。
        eq(List.of(0),trapped(1,new int[][]{},0,Set.of()));
        eq(List.of(),trapped(1,new int[][]{},0,Set.of(0)));
        try{trapped(2,new int[][]{{0,2}},0,Set.of(1));throw new AssertionError();}catch(IllegalArgumentException expected){}
        System.out.println("PASS 6 tests");
    }
}

这些特殊情况也要试一试

  • 起点本身是终态时存在长度为零的终态路径。
  • 不可达孤立节点不属于当前任务无出口报告。
  • 带出口的循环仍可能在运行策略下无限循环。
进阶选读 · 后续再读:模型检查与公平性

场景需要证明所有允许运行都满足安全条件或最终结束。

做法把状态数据有限抽象,检查时序性质和反例路径;明确调度公平性与环境假设,或通过严格递减预算给出终止证明。

复杂度有限状态显式检查通常与状态图规模相关,但状态组合可能指数增长;不能把原代码行数当作状态数量。

好处与代价形式化检查能提供更强证据,却依赖抽象正确和假设完整;实际外部故障仍需集成演练。

05项目里怎么用

先用确定工作流实现可行基线,再把确实需要动态判断的节点交给模型。节点越多不自动代表更聪明,过细划分可能增加调用延迟与状态传递成本。保持每个节点的输入产出清楚,比一开始引入复杂多Agent更容易定位失败。

路由返回值限定在枚举集合,非法选择进入明确失败或补充信息分支。状态更新使用版本与可验证转换,避免并发节点互相覆盖证据或草稿。循环修订要累计全任务尝试次数,不能每次进入节点都把计数归零。

测试使用假模型产生固定路由序列,包括重复修订、未知节点、缺证据生成和取消。这样可以证明控制逻辑对已覆盖序列的行为,再将真实模型作为外部变量评估;一次自然语言回答成功不替代状态机测试。

容易踩的坑

  • 把工作流与Agent当成互斥产品名称。
  • 允许模型直接给任意节点名并执行。
  • 图有终态路径就宣称所有运行一定结束。
  • 修订节点每次重置计数,预算形同虚设。

几种做法,怎么选

表格有四列,窄屏可左右滑动查看代价和适用条件。

方案优势代价与限制适用条件
固定工作流步骤可预测,便于验证处理未知任务灵活性较低明确业务流程
受控模型路由能适应输入差异需白名单、状态和预算少量需要判断的分支
形式化状态分析可找结构反例与性质问题状态空间和建模成本较高高复杂度或关键流程

06检验是否读懂了

先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。

先用自己的话解释

01Agent与工作流的核心差别是什么?

展开参考答案与推导

部分下一步决策是否由模型动态选择;实际系统可以把两者组合,不必按名称二选一。

02反向搜索从终态开始得到什么?

展开参考答案与推导

得到原图中存在路径能到达终态的节点集合,属于存在性而非必然性结论。

03状态字段为何不能只放聊天历史?

展开参考答案与推导

程序需要直接核对证据、版本、预算和终态,不能每次依赖模型从长文本推断这些控制事实。

换个情况,还会处理吗

01模型一直修订但图检查通过,怎么办?

展开参考答案与推导

图只证明存在出口,运行需严格轮数或截止时间预算;检查修订计数是否跨节点和重试保持。

02生成节点没有证据仍被执行,查哪里?

展开参考答案与推导

检查节点前置条件与路由数据,不仅检查边是否存在;合法图边不能保证状态字段满足要求。

03两个节点覆盖同一草稿怎么解决?

展开参考答案与推导

定义写入所有权或版本条件更新,冲突后重新核对状态;共享Map并行写入不构成正确工作流。

写代码试一试

01有出口循环应被报告吗?

输入 / 条件
1到1且1到终态2
输出 / 预期
不属于无出口

提示 存在路径就通过此项。

展开参考答案与推导

反图从2可达1,因此1不在差集中;仍需另测实际策略是否总选自环。

02空终态集合意味着什么?

输入 / 条件
单节点0,无边
输出 / 预期
[0]

提示 没有任何完成路径。

展开参考答案与推导

反向搜索起点为空,能到终态集合为空,所有从起点可达节点都会被报告。

03为何入队时标记?

输入 / 条件
菱形路径两个父节点指向同一子节点
输出 / 预期
子节点只入队一次

提示 避免重复队列工作。

展开参考答案与推导

首个父节点发现时标记,后续父节点跳过;循环图也因此只处理有限节点。

读完之后,独立完成

课后作业:自己做一遍

本篇共 12 道作业,先完成 9 道基础与综合任务,再按前置知识挑战进阶题。其中包含 2 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。

这次练什么

能实现检索、草稿、校验工作流,并通过测试确认非法跳转和超过修订预算的操作会被拒绝。

本篇重点加练

状态数据与合法迁移是流程可靠性的核心,增加任务分支后仍要能解释每一次状态变化。

  • 显式状态数据
  • 合法状态转换

重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。

先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。

基础必做

  1. 作业 01原理问答

    把“读取资料、生成初稿、检查引用、提交草稿”拆成固定工作流,另找一处需要动态决策的节点;说明两种控制权的判定依据。

    提交内容
    一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
    怎样算完成
    • 逐项回应题目要求,区分事实、假设与个人判断。
    • 能用自己的例子解释适用条件,记录尚未弄懂的点。

    练到的知识点工作流与 Agent 控制权 · 固定节点与动态决策

  2. 作业 02代码推演

    定义运行状态和路由枚举,用三个合法与两个非法模型输出推演路由过程;记录每一步读取、修改的字段及拒绝条件。

    提交内容
    手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
    怎样算完成
    • 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
    • 主动检查至少一个边界或异常条件,能解释手算与运行差异。

    练到的知识点显式状态数据 · 枚举路由白名单

  3. 作业 03原理问答

    为“节点产出已保存但完成事件尚未处理”定义一个精确状态,区分业务事实、执行阶段与展示状态,说明哪些状态组合应被拒绝。

    提交内容
    概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
    怎样算完成
    • 关键概念有明确边界,能用案例检验声明。
    • 未获证据支持的假设单独标注。

    练到的知识点显式状态数据 · 合法状态转换

综合应用

  1. 作业 04面试场景

    审核节点收到没有来源列表的草稿,生成节点又声称已经审核。设计前置条件、产出契约和状态转换表,处理这种不一致。

    提交内容
    场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
    怎样算完成
    • 方案回应给定约束,列出至少一种替代方案及代价。
    • 每项关键结论说明所需证据,不能核实的部分明确标注。

    练到的知识点节点前置条件和产出 · 合法状态转换

  2. 作业 05编程实践

    用假模型序列构造“连续要求修订、路由未知节点、成功退出”三组运行,写自动测试验证循环上限和终态不再执行节点。

    提交内容
    可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
    怎样算完成
    • 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
    • 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。

    练到的知识点循环修订上限 · 假模型驱动流程测试

  3. 作业 06排错实验

    故意删去一次状态持久化或重复发送一个完成事件,在本地回放流程;提交首次违反业务不变量的步骤及最小复现输入。

    提交内容
    最小故障输入、复现步骤、观测记录、定位依据及回归用例。
    怎样算完成
    • 其他人可按记录复现故障,并区分现象与已验证原因。
    • 定位过程有观测依据,回归用例可以再次暴露原问题。

    练到的知识点显式状态数据 · 节点前置条件和产出 · 合法状态转换

  4. 作业 07编程实践

    写一个状态快照校验器,只读取快照、不运行模型。自造缺少必需产物、含矛盾标志和合法暂停的样本,检查是否可从快照恢复合法流程。

    提交内容
    可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
    怎样算完成
    • 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
    • 验证目标由合同确定,模拟结果不冒充真实服务证据。

    练到的知识点显式状态数据 · 合法状态转换

  5. 作业 08排错实验

    某节点修改共享状态后校验失败,调用方尝试继续执行另一节点。用最小程序检验失败迁移是否残留部分数据,并记录后续路径受何影响。

    提交内容
    最小故障序列、观测记录、原因核对与可再次触发原问题的测试。
    怎样算完成
    • 故障可以稳定重现,现象与已验证原因分开。
    • 观测记录足以判断声明的合同是否被违反。

    练到的知识点显式状态数据 · 合法状态转换

  6. 作业 09面试场景

    现有流程新增“人工驳回后回到草稿”,同时有旧运行正在等待审核。给出旧状态解释、新事件合法性和升级兼容的评审表。

    提交内容
    场景评审记录,包含约束、备选方案、未决信息和验证计划。
    怎样算完成
    • 方案回应题目约束,取舍能对应到可核验的事实。
    • 给出失败或条件变化时的检查方式。

    练到的知识点显式状态数据 · 合法状态转换

进阶挑战 · 掌握前置后选做

  1. 作业 10设计评审

    选择一个原本固定的节点改为模型决策,制定对照实验,比较成功率、调用数和失败类型,解释是否值得增加动态性。

    提交内容
    设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
    怎样算完成
    • 比较使用一致条件与事先声明的标准,记录失败与未知结果。
    • 说明方案的适用前提、未覆盖风险及后续验证方法。

    练到的知识点工作流与 Agent 控制权 · 固定节点与动态决策 · 循环修订上限

思维拓展

先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。

  1. 作业 11思维拓展 · 反例构造

    为“所有节点单测通过,所以完整工作流不会出现非法状态”设计一条跨节点反例轨迹,区分局部合同与组合约束。

    提交内容
    具体反例、原主张的适用前提、复现或逻辑证明及结论边界。
    怎样算完成
    • 反例满足原主张所声称的前提,确实针对其结论。
    • 说明一个反例能推翻什么、不能进一步证明什么。

    练到的知识点显式状态数据 · 合法状态转换

  2. 作业 12思维拓展 · 跨域迁移

    将任务状态机迁移到订单售后流程,找出可复用的状态建模规则和必须重新定义的业务不变量,不要求实现完整订单系统。

    提交内容
    原场景与目标场景的对应表、可迁移的机制、不能迁移的前提及验证设计。
    怎样算完成
    • 逐项核对两种场景的前提,不只比较相似术语。
    • 至少一个不能直接迁移的边界有具体检验方法。

    练到的知识点显式状态数据 · 合法状态转换

完成后,再检验一次

给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。

继续阅读

先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。

打开资料库,找下一份学习材料 ↗