写在前面
审批流里最费人的环节不是"判断该不该批",是初审:材料齐不齐、事由合不合规、格式对不对——这些机械判断消耗了审批人大量的注意力。AI 预审想接的就是这一段:申请提交后先由模型过一遍,给出建议,人只处理值得处理的部分。
但把模型放进审批流,第一件事不是写提示词,是回答一个更根本的问题:**模型说"我觉得可以",这个"觉得"要被信任到什么程度?**这篇记录我的答案:三值决策、输出重建、超时驳回——三道设计,每一道都在教模型说"我不知道"。
一、预审在流程里的位置
系列之前写过审批接入层,提过提交时的分叉:配置了预审的模板,申请先进入 PENDING_REVIEW 状态,AI 任务预审材料;没配置就直进人工审批。
触发逻辑的关键在前两个判断:
WorkflowAgentResultCallback callback = workflowCallbackProvider.getIfAvailable();
AgentRow agent = agentRepository.findActivePreReviewAgent();
if (callback == null || agent == null || agent.publishedVersionId() == null) {
return null; // 没接入回调、没有启用的预审助手 → 跳过预审
}
预审是增强,不是依赖。 没有配置 AI 的部署形态下,审批流完整可用;AI 缺席时系统优雅降级,而不是报错。这个 return null 是整篇里我最看重的一行——AI 增强的功能,必须能在 AI 关闭时原样工作,否则 AI 就从"功能"变成了"单点"。
任务创建后照例走 Agent 模块的老三样:落库 PENDING、写审计、事务提交后异步执行。执行细节(状态护栏、版本快照)在 Agent 链路那篇讲过,不重复。
二、三值决策:给模型一个"逃跑出口"
预审的输出协议是三个值:
decision: APPROVED、REJECTED、TO_HUMAN 三选一。
而系统提示词里最重要的一句是:
只有明显缺少核心信息或明显违规时才能 REJECTED;不确定时输出 TO_HUMAN。
TO_HUMAN 这个第三值,是整个设计的支点。没有它,模型面对"拿不准的申请"只有两个出口——通过或驳回,而模型对"不确定"的默认行为是编一个理由选一个。有了 TO_HUMAN,"我不确定"成了合法且被鼓励的输出,模型的压力小了,错误率反而降了。
对应的规则设计是:APPROVED 的申请进入人工审批(AI 建议作为参考卡片展示);REJECTED 直接驳回(仅限"明显缺信息或明显违规");TO_HUMAN 正常进入人工。三个值最终都汇入人工审批流——只有 REJECTED 是例外,而它的门槛被提示词抬到最高。预审系统的第一版本从不直接驳回任何东西,运行数据积累出信心后,才逐步放宽。
三、不信任模型输出:重建一份干净的决策
模型返回的 JSON 不能直接用,要过三道处理:
第一道:容忍格式噪音。 提示词明明说了"不要输出 Markdown",模型还是时不时包一层 ```json。解析器按最坏情况处理:
private String extractJsonObject(String text) {
String trimmed = text.trim();
if (trimmed.startsWith("```")) { // 剥掉 Markdown 围栏
...
}
int start = trimmed.indexOf('{');
int end = trimmed.lastIndexOf('}');
if (start < 0 || end <= start) {
throw new BizException(502, "AI预审返回不是JSON对象");
}
return trimmed.substring(start, end + 1);
}
第二道:字段级重建。 拿到 JSON 后不是原样存库,而是逐字段取出、补默认值、重新构造:
String decisionValue = normalizeDecision(node.path("decision").asText());
decision.put("decision", decisionValue);
decision.put("conclusion", textOrDefault(node, "conclusion", defaultConclusion(decisionValue)));
decision.put("reason", textOrDefault(node, "reason", "模型已完成预审"));
decision.put("suggestion", textOrDefault(node, "suggestion", "建议直属上级结合业务安排复核后审批"));
decision.put("riskLevel", textOrDefault(node, "riskLevel", "中"));
模型漏了 conclusion?按决策值生成兜底文案。漏了 suggestion?用通用的审批建议补上。下游永远收到结构完整、语义齐全的决策——缺字段的概率被摊销到了一个构造函数里,而不是散落在每个展示它的页面上。
第三道:决策白名单归一。
private String normalizeDecision(String value) {
String normalized = value == null ? "" : value.trim().toUpperCase();
if ("APPROVED".equals(normalized) || "REJECTED".equals(normalized) || "TO_HUMAN".equals(normalized)) {
return normalized;
}
return "TO_HUMAN"; // 任何不认识的值 → 转人工
}
模型输出 “Approve”、"approved "、“PASS”、甚至一段感慨——统统归一成 TO_HUMAN。决策的失效方向被钉死在"转人工"上:模型表达不清时,系统的行为是多花一个人工,而不是错误地通过或驳回一个申请。这个细节和插件体系里的"默认拒绝"是同一条原则:安全机制的失败,必须落到代价最小的那个分支。
四、超时收口:等不到的预审按驳回处理
预审卡住(模型挂了、任务异常、超时收口扫描命中)怎么办?看这段回调:
public void onPreReviewTimeout(Long requestId, Long agentTaskId, String decisionJson) {
terminateAgentPreReview(requestId, agentTaskId, decisionJson);
}
private void terminateAgentPreReview(Long requestId, Long agentTaskId, String decisionJson) {
WorkflowRequestRow workflowRequest = workflowRepository.getRequestByIdForUpdateForThrow(requestId);
ensurePendingAgentPreReview(workflowRequest, agentTaskId);
releaseFrozenLeaveIfNeeded(workflowRequest, STATUS_REJECTED, agentTaskId);
workflowRepository.finishRequest(requestId, STATUS_REJECTED, agentTaskId);
workflowRepository.closeAllPendingTodos(requestId);
}
超时的预审按驳回处理,且完整走驳回的全部账务:释放冻结的假期余额、主表置 REJECTED、关闭所有待办。
为什么不是"超时转人工"?推演一下:请假单冻结着余额等预审,预审卡住三天,审批人收到了一个"等待中"的单子——期间员工的新申请全被冻结额度挡住。超时转人工把系统故障的代价转嫁给了申请人的资金占用,超时驳回则把状态立即收口、资金立即释放,申请人重新提交的成本是一次点击。两害相权,驳回的代价更小、更确定。
FOR UPDATE 读取和状态校验(ensurePendingAgentPreReview)依然是老搭档:超时回调可能和预审完成回调并发到达,行锁加状态检查保证只有一方生效。
五、决策要能被看见
AI 的预审结果最终以卡片形式出现在审批单详情里:结论、依据、风险等级、检查项列表,连同"知识库模型预审"的模式标识。审批人看到的不只是"AI 说可以",是AI 为什么说可以——每一项 checks(如"请假事由是否完整:通过")都对应提示词里要求的结构化检查。
这个可视化的意义超出体验:它是信任的校准器。审批人用得多了,自然知道哪类申请的 AI 建议可信、哪类要警惕;运营方也能基于这些卡片评估"要不要把 REJECTED 的门槛再放宽一点"。黑盒的 AI 进不了严肃流程,可解释的那部分必须留下。
六、小结
三点体会:
- 先给模型一个逃跑出口,再谈准确性。 TO_HUMAN 的存在让"不确定"有了合法表达,预审的错误率下降靠的是这个出口,不是更长的提示词。
- 模型输出当作不可信输入处理。 格式要容错、字段要补全、决策要归一,三个环节共同保证:无论模型说什么,流入下游的都是一份结构完整、语义保守的决策。
- 超时要有确定性的善后。 预审超时按驳回处理并立即释放冻结——宁可让申请人多点一次提交,不让资金在故障里无期限冻结。
预审上线后,初审通过的人工处理量下降了不少——但更重要的是没有出现过一次"AI 错误驳回"的投诉。三道设计保住的不只是准确率,是审批人对这个系统的基本信任。
默认评论
Halo系统提供的评论