
Java 8老系统AI Workflow实战把一次性AI对话升级成可恢复工作流很多 AI 应用刚开始都是一个聊天框。用户输入需求模型输出结果。这适合原型验证但进入企业流程后很快会遇到问题中途失败怎么办 关键节点谁确认 上一次输出在哪里 能不能从失败节点恢复 每一步输入输出能不能审计所以第 9 讲要把一次性 AI 对话升级成工作流。最终效果代码目录code/spring-ai-enterprise-lab/labs/chapter09-ai-workflow运行.\compile-and-run.ps1Demo 会演示一个需求到测试用例的工作流需求提取 ↓ 接口设计 ↓ 人工审批 ↓ 测试用例生成 ↓ 发布检查清单启动后流程会停在api-design节点。审批后流程继续执行到COMPLETED。代码结构核心代码分三块src/main/java/com/ynzz/lab/chapter09 ├── common │ ├── WorkflowStartRequest ← 工作流启动参数 │ ├── WorkflowRun ← 流程实例运行时状态 │ └── WorkflowNodeSnapshot ← 节点快照 ├── nodes │ ├── RequirementExtractNode ← 需求提取节点 │ ├── ApiDesignNode ← 接口设计节点含 HumanApproval │ ├── TestcaseGenerateNode ← 测试用例生成节点 │ └── ReleaseChecklistNode ← 发布检查清单节点 └── runtime └── WorkflowRuntime ← 运行时引擎WorkflowRuntime负责控制流程何时继续何时停住等待审批。WorkflowRuntime 的控制流WorkflowRuntime的核心不是调用几个 AI 节点而是控制状态怎么流动。当前 Demo 的start方法做了四件事创建 WorkflowRun ↓ 运行 RequirementExtractNode写入第一个快照 ↓ 把 lastOutput 交给 ApiDesignNode写入第二个快照 ↓ 把流程状态改成 WAITING_APPROVALwaitingNodeIdapi-design这里的lastOutput()很关键。它表示节点之间不是靠一大段聊天上下文传递信息而是靠上一个节点的结构化输出继续往下走需求原文 ↓ RequirementExtractNode.output ↓ ApiDesignNode.input ↓ ApiDesignNode.output ↓ 审批通过后进入 TestcaseGenerateNode.input节点之间的数据传递靠lastOutput()完成它返回最近一个SUCCESS或APPROVED快照的output字段作为下一个节点的input。快照格式里包含nodeId、status、input、output、approvedBy、errorCode、retryCount——失败时 Runtime 靠这些字段定位恢复点statusSUCCESS/APPROVED的快照是安全恢复点output是重跑失败节点的输入retryCount用于控制重试次数。这样设计是为了避免失败后从头重跑整个工作流。审批发生在api-design节点。审批前流程停住statusWAITING_APPROVAL waitingNodeIdapi-design审批后approve方法会找到 api-design 快照 ↓ 写入 approvedBy并把节点状态改成 APPROVED ↓ 把流程状态恢复为 RUNNING ↓ 运行 TestcaseGenerateNode ↓ 运行 ReleaseChecklistNode ↓ 把流程状态改成 COMPLETED所以这个 Demo 真正想表达的是企业 AI 工作流要能暂停、能继续、能看到每一步的输入输出而不是把所有事情塞进一次模型调用。当前 Stub 还没有实现失败节点回退但它已经把恢复所需的最小结构留出来了每个节点都有input、output和status。真实落地时失败节点应该记录FAILED、错误原因和重试次数Runtime 从最近一个SUCCESS或APPROVED快照继续而不是从头重跑。每个节点都要有快照启动工作流后输出里会看到{status:WAITING_APPROVAL,waitingNodeId:api-design,snapshots:[{nodeId:requirement-extract,status:SUCCESS},{nodeId:api-design,status:WAITING_APPROVAL}]}这就是企业工作流的基本形态。不是模型一次性吐出全部内容而是每一步都有记录节点 ID 节点输入 节点输出 节点状态 审批人对应到当前WorkflowNodeSnapshot快照字段是{nodeId:api-design,status:WAITING_APPROVAL,input:需求点订单超过 48 小时未发货时触发延迟预警并提醒客服介入。,output:POST /api/orders/delay-alerts输入 thresholdHours48输出待介入订单列表。,approvedBy:}这样才能审计、复盘、重试和恢复。四个节点的职责边界也应该讲清楚节点输入输出卡住或失败时的处理重点RequirementExtractNode用户需求原文结构化需求点 不确定项列表需求含糊时输出不确定项转人工确认自身失败记录 errorCode需求作为 input 重跑ApiDesignNode需求摘要lastOutput接口草案请求/响应/错误码卡在 WAITING_APPROVAL被驳回则带着修改意见重跑自身失败从 requirement-extract 快照恢复TestcaseGenerateNode审批后的接口设计approved snapshot output测试用例集合前置节点未审批则拒绝执行自身失败记录 errorCode从 api-design 快照恢复重跑ReleaseChecklistNode测试用例输出lastOutput发布检查清单 待办项发布条件不完整时输出待办清单自身失败从 testcase-generate 快照恢复始终不输出强制通过这张表比类名列表更重要。因为企业工作流里每个节点都要回答三个问题接收什么、产出什么、什么时候停下来。当前 Demo 完整跑一遍本讲 Demo 的启动输入是tenantIddemo operatorIdu1001 requirementText新增订单延迟预警功能当订单超过 48 小时未发货时系统需要提醒客服介入。第一步RequirementExtractNode接收原始需求输出需求摘要input新增订单延迟预警功能当订单超过 48 小时未发货时系统需要提醒客服介入。 output需求点订单超过 48 小时未发货时触发延迟预警并提醒客服介入。 statusSUCCESS第二步WorkflowRuntime调用run.lastOutput()把这段需求摘要交给ApiDesignNodeinput需求点订单超过 48 小时未发货时触发延迟预警并提醒客服介入。 outputPOST /api/orders/delay-alerts输入 thresholdHours48输出待介入订单列表。 statusWAITING_APPROVAL然后 Runtime 把整个流程停住WorkflowRun.statusWAITING_APPROVAL WorkflowRun.waitingNodeIdapi-design这时还不会生成测试用例也不会生成发布清单。只有当审批调用发生runtime.approve(run, api-design, true, tech-lead)Runtime 才继续执行后两个节点。第三步TestcaseGenerateNode接收已经审批过的接口设计inputPOST /api/orders/delay-alerts输入 thresholdHours48输出待介入订单列表。 output测试用例48 小时边界、未发货订单、已发货订单、重复提醒幂等、客服可见性。 statusSUCCESS第四步ReleaseChecklistNode接收测试用例输出input测试用例48 小时边界、未发货订单、已发货订单、重复提醒幂等、客服可见性。 output发布检查灰度租户、SQL 索引、告警阈值、客服通知模板、回滚开关。 statusSUCCESS最后流程完成WorkflowRun.statusCOMPLETED WorkflowRun.waitingNodeId这条时间线说明了一件事节点之间传递的是上一个节点的output不是把所有原始上下文无限追加给模型。失败恢复应该怎么落地当前 Stub 只演示了成功和审批暂停没有真正模拟FAILED。但它的快照结构已经能说明恢复设计。真实项目里节点快照至少应该扩展成{workflowId:wf-001,nodeId:testcase-generate,status:FAILED,input:POST /api/orders/delay-alerts...,output:,errorCode:MODEL_TIMEOUT,retryCount:2,createdAt:2026-06-15T10:00:00,updatedAt:2026-06-15T10:02:00}恢复时不是从需求提取重新开始而是找到最后一个 SUCCESS / APPROVED 快照 ↓ 读取它的 output ↓ 作为失败节点的 input ↓ 重跑失败节点 ↓ 写入新的快照版本比如TestcaseGenerateNode失败最近的稳定节点是api-designapi-design.statusAPPROVED api-design.outputPOST /api/orders/delay-alerts... ↓ 重试 testcase-generate这样才能做到失败可恢复而不是失败后从头再问一遍模型。WorkflowStateRepository为什么当前是 Stub持久化后有什么不同当前 Demo 的WorkflowStateRepository是内存 Stub故意不依赖外部存储。原因有两个第一降低上手成本。不需要装数据库、不需要配 Redis跑compile-and-run.ps1就能看到完整工作流效果。第二先跑通流程再落地存储。工作流引擎的正确性是第一步——节点拆分对不对、快照字段全不全、恢复逻辑合不合理。这些问题不依赖存储也能验证。但内存 Stub 有明显局限服务重启所有快照丢失。真实落地时WorkflowStateRepository应该持久化到数据库或 Redis。持久化后的差异维度内存 Stub持久化DB / Redis服务重启快照全丢工作流只能重新开始快照不丢工作流从断点恢复多实例部署不支持每个实例内存独立支持共享存储审计与复盘无法实现可以查询历史快照长时间运行的工作流不适合可能 OOM适合存储外包当前 Stub 的定位很明确先把工作流跑通验证节点拆分和快照设计持久化是下一步但快照字段设计从一开始就要为持久化做准备——这也是为什么WorkflowNodeSnapshot里有一组结构化字段而不是随便写一个output就完事。关键节点必须经过 HumanApprovalNode接口设计是一个关键节点。如果接口设计错了后面的测试用例、发布检查清单都会跟着错。ApiDesignNode内嵌了HumanApprovalNodeApiDesignNode 执行 ↓ 输出接口草案请求/响应/错误码 ↓ HumanApprovalNode 暂停 ↓ 审批人看到草案 上一节点输出 ↓ 审批通过 / 驳回 ↓ APPROVED → 继续下一个节点 REJECTED → 打回修改审批人看到的是结构化输出不是原始提示词界面。这是企业协作的基础——让人看到该看的不需要理解 AI 内部逻辑。不确定项要显式输出本讲 Demo 会输出不确定项客服提醒渠道是短信、企微还是站内信需要业务确认。这类信息不能藏在正文里。工作流报告应该显式列出哪些事情已确定 哪些事情待确认 哪些节点需要人工审批 哪些输出不能直接执行企业 AI 应用不是追求模型看起来很自信而是追求流程可靠。企业避坑第一个坑不要把复杂任务都做成一次聊天。复杂任务应该拆节点。第二个坑不要让关键节点自动越过审批。接口设计、发布计划、生产变更都应该停一下。第三个坑不要丢失中间过程。没有节点快照失败后就很难恢复——更不要在内存里存快照服务重启就全丢了。第四个坑不要隐藏不确定项。AI 不确定的地方应该变成流程里的待办。从 Demo 到落地还差什么本讲 Demo 验证了节点拆分 快照记录 人工审批 失败可恢复的工作流基础但企业 AI Workflow 落地还差几步状态持久化当前 Stub 用内存模拟WorkflowStateRepository真实项目需要把快照落库PostgreSQL / MySQL或存 Redis配合定时清理策略避免快照膨胀。可视化审批界面当前审批是代码里调用 API 触发真实项目需要一个审批控制台参考approval-console子模块让非技术人员在页面上做审批操作。工作流定义 DSL当前节点定义是 Java 类真实项目需要把工作流定义抽成 YAML 或 JSON让业务人员可以配置流程而不需要改代码。节点失败自动告警某个节点连续失败 N 次后应该自动通知工作流发起人而不是等到人工发现工作流卡住了。并行节点支持当前 Demo 是串行节点一个接一个真实项目经常有接口设计和测试用例生成可以并行的需求这需要WorkflowRuntime支持 DAG 调度。第 9 讲的工作流编排能力也是第 10 讲多 Agent 编排的基础——多 Agent 的输出最终也需要进入工作流、由人类做审批决策。小结企业 AI 应用的成熟形态不是用户提问 ↓ 模型回答而是任务输入 ↓ 节点执行快照持久化 ↓ 人工审批关键节点 ↓ 失败恢复从上一个成功节点 ↓ 最终报告把 AI 放进工作流里它才更像企业系统的一部分。