Java 8老系统AI Workflow实战:把一次性AI对话升级成可恢复工作流

Java 8 老系统如何接入 AI:第 9 讲 - 企业 AI 应用不是一次聊天,而是一条可恢复工作流

很多 AI 应用刚开始都是一个聊天框。

用户输入需求,模型输出结果。

这适合原型验证,但进入企业流程后,很快会遇到问题:

text 复制代码
中途失败怎么办?
关键节点谁确认?
上一次输出在哪里?
能不能从失败节点恢复?
每一步输入输出能不能审计?

所以第 9 讲要把一次性 AI 对话升级成工作流。

最终效果

代码目录:

text 复制代码
code/spring-ai-enterprise-lab/labs/chapter09-ai-workflow

运行:

powershell 复制代码
.\compile-and-run.ps1

Demo 会演示一个"需求到测试用例"的工作流:

text 复制代码
需求提取
        ↓
接口设计
        ↓
人工审批
        ↓
测试用例生成
        ↓
发布检查清单

启动后,流程会停在 api-design 节点。

审批后,流程继续执行到 COMPLETED

代码结构

核心代码分三块:

text 复制代码
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 方法做了四件事:

text 复制代码
创建 WorkflowRun
        ↓
运行 RequirementExtractNode,写入第一个快照
        ↓
把 lastOutput 交给 ApiDesignNode,写入第二个快照
        ↓
把流程状态改成 WAITING_APPROVAL,waitingNodeId=api-design

这里的 lastOutput() 很关键。

它表示节点之间不是靠一大段聊天上下文传递信息,而是靠上一个节点的结构化输出继续往下走:

text 复制代码
需求原文
        ↓
RequirementExtractNode.output
        ↓
ApiDesignNode.input
        ↓
ApiDesignNode.output
        ↓
审批通过后进入 TestcaseGenerateNode.input

节点之间的数据传递靠 lastOutput() 完成:它返回最近一个 SUCCESSAPPROVED 快照的 output 字段,作为下一个节点的 input。快照格式里包含 nodeIdstatusinputoutputapprovedByerrorCoderetryCount------失败时 Runtime 靠这些字段定位恢复点:status=SUCCESS/APPROVED 的快照是安全恢复点,output 是重跑失败节点的输入,retryCount 用于控制重试次数。这样设计是为了避免失败后从头重跑整个工作流。

审批发生在 api-design 节点。

审批前,流程停住:

text 复制代码
status=WAITING_APPROVAL
waitingNodeId=api-design

审批后,approve 方法会:

text 复制代码
找到 api-design 快照
        ↓
写入 approvedBy,并把节点状态改成 APPROVED
        ↓
把流程状态恢复为 RUNNING
        ↓
运行 TestcaseGenerateNode
        ↓
运行 ReleaseChecklistNode
        ↓
把流程状态改成 COMPLETED

所以这个 Demo 真正想表达的是:企业 AI 工作流要能暂停、能继续、能看到每一步的输入输出,而不是把所有事情塞进一次模型调用。

当前 Stub 还没有实现失败节点回退,但它已经把恢复所需的最小结构留出来了:每个节点都有 inputoutputstatus。真实落地时,失败节点应该记录 FAILED、错误原因和重试次数,Runtime 从最近一个 SUCCESSAPPROVED 快照继续,而不是从头重跑。

每个节点都要有快照

启动工作流后,输出里会看到:

json 复制代码
{
  "status": "WAITING_APPROVAL",
  "waitingNodeId": "api-design",
  "snapshots": [
    {
      "nodeId": "requirement-extract",
      "status": "SUCCESS"
    },
    {
      "nodeId": "api-design",
      "status": "WAITING_APPROVAL"
    }
  ]
}

这就是企业工作流的基本形态。

不是模型一次性吐出全部内容,而是每一步都有记录:

text 复制代码
节点 ID
节点输入
节点输出
节点状态
审批人

对应到当前 WorkflowNodeSnapshot,快照字段是:

json 复制代码
{
  "nodeId": "api-design",
  "status": "WAITING_APPROVAL",
  "input": "需求点:订单超过 48 小时未发货时触发延迟预警,并提醒客服介入。",
  "output": "POST /api/orders/delay-alerts,输入 thresholdHours=48,输出待介入订单列表。",
  "approvedBy": ""
}

这样才能审计、复盘、重试和恢复。

四个节点的职责边界也应该讲清楚:

节点 输入 输出 卡住或失败时的处理重点
RequirementExtractNode 用户需求原文 结构化需求点 + 不确定项列表 需求含糊时输出不确定项,转人工确认;自身失败记录 errorCode,需求作为 input 重跑
ApiDesignNode 需求摘要(lastOutput) 接口草案(请求/响应/错误码) 卡在 WAITING_APPROVAL;被驳回则带着修改意见重跑;自身失败从 requirement-extract 快照恢复
TestcaseGenerateNode 审批后的接口设计(approved snapshot output) 测试用例集合 前置节点未审批则拒绝执行;自身失败记录 errorCode,从 api-design 快照恢复重跑
ReleaseChecklistNode 测试用例输出(lastOutput) 发布检查清单 + 待办项 发布条件不完整时输出待办清单;自身失败从 testcase-generate 快照恢复;始终不输出"强制通过"

这张表比类名列表更重要。

因为企业工作流里,每个节点都要回答三个问题:接收什么、产出什么、什么时候停下来。

当前 Demo 完整跑一遍

本讲 Demo 的启动输入是:

text 复制代码
tenantId=demo
operatorId=u1001
requirementText=新增订单延迟预警功能:当订单超过 48 小时未发货时,系统需要提醒客服介入。

第一步,RequirementExtractNode 接收原始需求,输出需求摘要:

text 复制代码
input=新增订单延迟预警功能:当订单超过 48 小时未发货时,系统需要提醒客服介入。
output=需求点:订单超过 48 小时未发货时触发延迟预警,并提醒客服介入。
status=SUCCESS

第二步,WorkflowRuntime 调用 run.lastOutput(),把这段需求摘要交给 ApiDesignNode

text 复制代码
input=需求点:订单超过 48 小时未发货时触发延迟预警,并提醒客服介入。
output=POST /api/orders/delay-alerts,输入 thresholdHours=48,输出待介入订单列表。
status=WAITING_APPROVAL

然后 Runtime 把整个流程停住:

text 复制代码
WorkflowRun.status=WAITING_APPROVAL
WorkflowRun.waitingNodeId=api-design

这时还不会生成测试用例,也不会生成发布清单。

只有当审批调用发生:

text 复制代码
runtime.approve(run, "api-design", true, "tech-lead")

Runtime 才继续执行后两个节点。

第三步,TestcaseGenerateNode 接收已经审批过的接口设计:

text 复制代码
input=POST /api/orders/delay-alerts,输入 thresholdHours=48,输出待介入订单列表。
output=测试用例:48 小时边界、未发货订单、已发货订单、重复提醒幂等、客服可见性。
status=SUCCESS

第四步,ReleaseChecklistNode 接收测试用例输出:

text 复制代码
input=测试用例:48 小时边界、未发货订单、已发货订单、重复提醒幂等、客服可见性。
output=发布检查:灰度租户、SQL 索引、告警阈值、客服通知模板、回滚开关。
status=SUCCESS

最后流程完成:

text 复制代码
WorkflowRun.status=COMPLETED
WorkflowRun.waitingNodeId=""

这条时间线说明了一件事:节点之间传递的是上一个节点的 output,不是把所有原始上下文无限追加给模型。

失败恢复应该怎么落地

当前 Stub 只演示了成功和审批暂停,没有真正模拟 FAILED。但它的快照结构已经能说明恢复设计。

真实项目里,节点快照至少应该扩展成:

json 复制代码
{
  "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"
}

恢复时不是从需求提取重新开始,而是:

text 复制代码
找到最后一个 SUCCESS / APPROVED 快照
        ↓
读取它的 output
        ↓
作为失败节点的 input
        ↓
重跑失败节点
        ↓
写入新的快照版本

比如 TestcaseGenerateNode 失败,最近的稳定节点是 api-design

text 复制代码
api-design.status=APPROVED
api-design.output=POST /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 内嵌了 HumanApprovalNode

text 复制代码
ApiDesignNode 执行
        ↓
输出接口草案(请求/响应/错误码)
        ↓
HumanApprovalNode 暂停
        ↓
审批人看到草案 + 上一节点输出
        ↓
审批(通过 / 驳回)
        ↓
APPROVED → 继续下一个节点
REJECTED → 打回修改

审批人看到的是结构化输出,不是原始提示词界面。这是企业协作的基础------让人看到该看的,不需要理解 AI 内部逻辑。

不确定项要显式输出

本讲 Demo 会输出不确定项:

text 复制代码
客服提醒渠道是短信、企微还是站内信,需要业务确认。

这类信息不能藏在正文里。

工作流报告应该显式列出:

text 复制代码
哪些事情已确定
哪些事情待确认
哪些节点需要人工审批
哪些输出不能直接执行

企业 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 应用的成熟形态不是:

text 复制代码
用户提问
        ↓
模型回答

而是:

text 复制代码
任务输入
        ↓
节点执行(快照持久化)
        ↓
人工审批(关键节点)
        ↓
失败恢复(从上一个成功节点)
        ↓
最终报告

把 AI 放进工作流里,它才更像企业系统的一部分。

相关推荐
血小溅1 小时前
飞书 CLI 集成基础教程
后端
ihgry1 小时前
SpringBoot+Redis限流
后端
晚安code1 小时前
Nacos 注解全解析:7 个核心注解 + 5 个生产踩坑清单(2026 实测)
后端
wei_shuo1 小时前
KES 备份恢复与数据灾备实战:物理备份、逻辑备份与PITR完全指南
后端
Ai拆代码的曹操1 小时前
Netty 堆外内存泄漏从 0 到 1 排查实录:RES 1.2G 堆只有 256M
后端
敲代码的彭于晏2 小时前
Bean 生命周期完全图解:前端同学也能看懂的 Spring 核心机制
java·前端·后端
IT_陈寒2 小时前
Redis内存飙升的锅,原来是我没搞懂这个过期策略
前端·人工智能·后端
铁皮饭盒3 小时前
26年bunjs, elysia+pg一把梭, redis都省了
前端·javascript·后端
plainGeekDev3 小时前
ButterKnife → ViewBinding
android·java·kotlin