Spring AI Alibaba实战:构建人机协同的智能审核工作流

📅 2026/8/13 9:26:04
Spring AI Alibaba实战:构建人机协同的智能审核工作流
1. 项目概述当AI决策需要一双“人眼”在AI应用开发尤其是大模型集成项目中我们常常面临一个核心矛盾一方面我们追求自动化与智能化希望模型能自主、高效地处理复杂任务另一方面我们又对模型输出的准确性、安全性和合规性抱有极高的要求尤其是在金融、法律、医疗、内容审核等关键领域。一个完全“黑盒”的、无法干预的AI流程在真实业务场景中往往意味着风险。这就是“Human-in-the-Loop”人机回环简称HITL理念的价值所在——它不是要取代AI而是将人类的判断力作为关键控制节点嵌入到AI的工作流中形成一种协同增强的闭环。Spring AI Alibaba作为将大模型能力便捷引入Spring Boot应用的技术栈其核心价值在于简化集成。但仅仅完成API调用是远远不够的。真正的“实战”意味着要将AI能力无缝、可靠、可控地融入企业级业务流程。“人工介入”正是实现这种可控性的关键手段。它允许我们在AI生成内容、做出建议或执行操作的关键时刻引入人工审核、修正或确认从而确保最终输出的质量符合业务标准。想象一下这样的场景一个基于大模型的智能客服系统自动生成了对用户复杂技术问题的回复。如果直接发送可能存在技术细节不准确或表述不当的风险。通过HITL机制这条回复会先进入一个待审核队列由资深技术支持工程师快速浏览并做必要修正然后才发送给用户。这个过程既利用了AI的快速生成能力又保证了信息的专业性和准确性。Spring AI Alibaba为我们提供了构建此类工作流的基础框架而如何设计、实现和优化这个“回环”就是本次实战要深入探讨的内容。本文将基于Spring AI Alibaba拆解如何设计并实现一个高效、非侵入式的人工介入流程让AI应用变得更可靠、更值得信赖。2. 核心设计思路构建非侵入式的协同工作流实现HITL绝不是简单地在代码里加几个if-else判断然后弹出个页面让人工处理。一个健壮的、可维护的HITL系统需要精心的架构设计。我们的核心目标是在尽可能不影响主业务逻辑流畅性的前提下在关键决策点嵌入人工干预能力。基于Spring AI Alibaba我总结出以下几条核心设计原则。2.1 原则一关注点分离拦截而非侵入这是最重要的原则。我们不能让AI调用代码和人工审核逻辑强耦合在一起。试想如果每个调用大模型的地方都写满了“生成-判断-是否人工审核-等待-处理结果”的代码那将是一场维护噩梦。正确的做法是利用Spring强大的AOP面向切面编程或过滤器/拦截器机制。我们可以定义一个HumanReviewInterceptor或使用Spring AI可能提供的ClientInterceptor接口。它的职责是“拦截”AI客户端的调用结果。在拦截器中我们根据预设的规则规则引擎判断本次输出是否需要人工审核。如果需要则将AI的原始输出、本次调用的上下文如用户问题、会话ID、模型参数等封装成一个“审核任务”持久化到数据库或消息队列并立即向客户端返回一个“任务已提交正在审核中”的响应。整个AI调用的主流程代码对此几乎无感知它只是发起了一个普通的请求并接收了一个响应可能是最终结果也可能是任务ID。这种设计的好处是清晰的业务逻辑保持纯净HITL能力可以作为一个可插拔的模块通过配置动态启用或关闭也可以针对不同的AI模型或接口应用不同的审核策略。2.2 原则二上下文保全让审核者有据可依人工审核者不是神仙他需要足够的信息来做判断。仅仅把AI生成的一段文本扔给他是不够的。我们必须保全并传递完整的“调用上下文”。这至少包括用户输入Prompt用户到底问了什么这是理解AI输出意图的基础。对话历史如果是多轮对话之前的交流内容是什么调用参数使用了哪个模型温度temperature设置是多少这有助于判断输出是“创造性发挥”还是“确定性回答”。业务元数据当前用户ID、订单号、所属部门等。这些信息可能决定审核的紧急程度或分配给哪个特定的审核人员。AI原始输出未经任何处理的、完整的模型响应。在Spring AI Alibaba中Prompt对象和ChatResponse对象天然承载了大部分信息。我们需要设计一个ReviewTask实体将这些信息序列化如转为JSON后存储。审核界面在加载任务时能清晰地展示这些上下文让审核者做出精准决策。2.3 原则三异步化与状态管理保障系统响应人工审核是需要时间的从几秒到几小时不等。我们不能让用户的请求线程一直阻塞等待。因此整个HITL流程必须是异步的。当拦截器判定需要人工审核时系统应快速生成一个唯一的任务ID如UUID。将任务数据异步写入存储数据库、Redis等。立即向用户返回一个包含任务ID和提示信息如“您的问题已提交审核请稍后使用此ID查询结果”的响应。同时通过WebSocket、服务器推送事件SSE或消息队列通知审核端有新任务到来。另一方面我们需要一个清晰的任务状态机来管理整个生命周期。通常包括PENDING待审核、REVIEWING审核中、APPROVED已通过并附上最终内容、REJECTED已驳回可附上驳回原因和修改建议、MODIFIED审核者已修改并提交、EXPIRED超时未处理可配置自动处理策略等。状态的设计直接关系到后续的流程处理和用户体验。2.4 原则四灵活可配的触发规则不是所有AI输出都需要审核。全量审核会极大拖累效率失去AI的意义。我们需要一个灵活可配置的规则引擎来决定何时触发人工介入。常见的触发规则包括内容安全规则当AI输出中包含敏感词、涉及特定领域如医疗建议、投资建议或情绪极端负面时触发。置信度阈值如果模型能输出置信度分数某些模型或场景下当分数低于某个阈值时触发审核。业务规则例如当AI建议的退款金额超过1000元或生成的合同条款涉及特定法律领域时触发。抽样规则随机抽取一定比例如5%的请求进行人工复核用于持续监控模型质量。这些规则可以配置在数据库或配置中心拦截器加载这些规则并执行判断。Spring的SpELSpring Expression Language或引入轻量级规则引擎如Easy Rules都是实现动态规则评估的不错选择。3. 技术实现详解基于Spring AI Alibaba构建HITL系统理论说完了我们进入实战环节。我将基于Spring Boot 3.x 和 Spring AI Alibaba的最新稳定版本演示如何一步步搭建这个系统。假设我们有一个简单的智能问答服务现在需要为其增加对“技术解决方案类”回答的人工审核能力。3.1 环境准备与核心依赖首先创建一个标准的Spring Boot项目。在pom.xml中引入关键依赖dependencies !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring AI Alibaba - 假设我们使用通义千问 -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-qwen-spring-boot-starter/artifactId version最新版本/version !-- 请替换为实际版本 -- /dependency !-- 数据持久化 (使用JPA H2示例) -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency !-- 消息队列 (使用Redis作为简单MQ) -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- WebSocket 支持 (用于审核端实时通知) -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-websocket/artifactId /dependency !-- 工具类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies在application.yml中配置Spring AI Alibaba以通义千问为例和数据库spring: ai: alibaba: qwen: api-key: ${ALIBABA_AI_API_KEY} # 建议从环境变量读取 chat: options: model: qwen-max # 指定模型 datasource: url: jdbc:h2:mem:reviewdb driver-class-name: org.h2.Driver username: sa password: jpa: hibernate: ddl-auto: update show-sql: true redis: host: localhost port: 63793.2 定义数据模型与审核任务状态机首先我们定义审核任务的核心实体HumanReviewTask。import jakarta.persistence.*; import lombok.Data; import java.time.LocalDateTime; Entity Data Table(name human_review_task) public class HumanReviewTask { Id private String taskId; // UUID private String sessionId; // 会话ID用于关联用户 Lob // 大文本字段 private String originalPrompt; // 用户原始输入 Lob private String originalAiResponse; // AI原始输出 Lob private String reviewContext; // 额外的上下文JSON如历史消息、参数等 Enumerated(EnumType.STRING) private ReviewStatus status; // 状态 private String assignedTo; // 分配给哪位审核员 private String finalContent; // 审核后的最终内容审核员可能修改 private String reviewComment; // 审核意见 private LocalDateTime createdAt; private LocalDateTime reviewedAt; private String triggerRule; // 触发本次审核的规则标识 } // 审核状态枚举 public enum ReviewStatus { PENDING, // 待审核 REVIEWING, // 审核中 APPROVED, // 已通过直接采纳AI结果 MODIFIED, // 已修改审核员编辑后提交 REJECTED, // 已驳回需AI重新生成或终止 EXPIRED // 已超时 }这个实体记录了审核任务的全貌。reviewContext字段可以存储一个JSON字符串里面包含了MapString, Object序列化后的完整上下文为审核界面提供丰富信息。3.3 实现AI响应拦截器这是HITL系统的“大脑”。我们将实现一个ClientInterceptor在AI响应返回后立即进行拦截和判断。import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.ClientRequest; import org.springframework.ai.chat.client.ClientResponse; import org.springframework.ai.chat.client.ClientInterceptor; import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; Component Slf4j RequiredArgsConstructor public class HumanReviewInterceptor implements ClientInterceptor { private final ReviewRuleEngine ruleEngine; // 规则引擎 private final ReviewTaskService taskService; // 任务服务 private final TaskNotificationService notificationService; // 通知服务 Override public ClientResponse intercept(ClientRequest request, Chain chain) { // 1. 执行原始AI调用 ClientResponse response chain.next(request); // 2. 提取关键信息 String promptText request.prompt().text(); String aiResponseText response.content(); MapString, Object context new HashMap(); context.put(model, request.options().getModel()); context.put(temperature, request.options().getTemperature()); // 可以放入更多业务上下文... // 3. 调用规则引擎判断是否需要人工审核 ReviewRuleTrigger trigger ruleEngine.evaluate(promptText, aiResponseText, context); if (trigger.isTriggered()) { // 4. 需要审核创建异步审核任务 log.info(触发人工审核规则: {}, trigger.getRuleName()); String taskId UUID.randomUUID().toString(); HumanReviewTask task new HumanReviewTask(); task.setTaskId(taskId); task.setOriginalPrompt(promptText); task.setOriginalAiResponse(aiResponseText); task.setReviewContext(objectMapper.writeValueAsString(context)); task.setStatus(ReviewStatus.PENDING); task.setTriggerRule(trigger.getRuleName()); task.setCreatedAt(LocalDateTime.now()); // 异步保存任务可以使用Async或消息队列 taskService.createTaskAsync(task); // 通知审核端如通过Redis发布消息 notificationService.notifyNewTask(taskId); // 5. 返回一个特殊响应告知客户端已进入审核流程 // 这里可以构造一个包含taskId和提示信息的标准响应 // 为了简单我们返回一个指示性的内容实际项目应定义更丰富的响应体 return ClientResponse.from(response) .withContent(String.format(【系统提示】您的请求已提交人工审核任务ID: %s。请稍后查询结果。, taskId)) .build(); } // 6. 无需审核直接返回AI原始响应 return response; } }注意这里的ReviewRuleEngine是一个自定义的规则评估组件。它可以从数据库或配置文件中加载规则并使用SpEL或自定义逻辑进行评估。例如一个简单的关键词触发规则可以这样实现Component public class SimpleKeywordRuleEngine implements ReviewRuleEngine { private ListString sensitiveKeywords Arrays.asList(法律后果, 投资建议, 医疗诊断); Override public ReviewRuleTrigger evaluate(String prompt, String response, MapString, Object context) { for (String keyword : sensitiveKeywords) { if (response.contains(keyword)) { return new ReviewRuleTrigger(true, 敏感关键词触发: keyword); } } return ReviewRuleTrigger.notTriggered(); } }3.4 构建审核管理后端与前端界面审核端需要一个后台管理系统来展示待处理任务并允许审核员进行操作。后端控制器示例RestController RequestMapping(/api/review) RequiredArgsConstructor public class ReviewTaskController { private final ReviewTaskService taskService; GetMapping(/pending) public ListHumanReviewTask getPendingTasks() { return taskService.findTasksByStatus(ReviewStatus.PENDING); } GetMapping(/{taskId}) public HumanReviewTask getTaskDetail(PathVariable String taskId) { return taskService.getTask(taskId).orElseThrow(() - new TaskNotFoundException(taskId)); } PostMapping(/{taskId}/process) public ResponseEntityVoid processTask(PathVariable String taskId, RequestBody ReviewAction action) { // action 包含操作类型APPROVE/MODIFY/REJECT和修改后的内容/评论 taskService.processTask(taskId, action); // 处理完成后可能需要通知原请求方如通过WebSocket回调 notificationService.notifyTaskCompleted(taskId); return ResponseEntity.ok().build(); } }前端界面概念描述前端可以是一个简单的Vue/React单页应用。主界面是一个任务列表展示PENDING状态的任务ID、触发规则和创建时间。点击一个任务进入详情页详情页应分为左右两栏左栏上下文清晰展示用户原始问题Prompt、对话历史、调用模型和参数。右栏审核区展示AI原始输出在一个可编辑的文本域中。审核员可以直接通过点击“通过”按钮AI原始内容将作为最终结果。编辑后通过在文本域中直接修改AI的输出内容然后点击“修改后通过”。驳回选择“驳回”并输入驳回理由如“事实错误”、“表述不清”。系统可根据配置尝试让AI重新生成或直接终止流程。 操作完成后后端更新任务状态并将最终内容或驳回信号传递回原始的业务流程。3.5 设计结果回调与流程衔接审核完成后原始的请求方可能是用户前端也可能是另一个后台服务如何获取最终结果这里有几种模式轮询查询用户前端在收到“已提交审核”的响应后持有一个taskId可以定期调用一个查询接口如GET /api/result/{taskId}直到任务状态变为APPROVED或MODIFIED然后获取finalContent。WebSocket推送更优的体验是使用WebSocket。当用户请求被拦截并创建任务时服务端可以建立一个与用户会话关联的WebSocket通道。当审核完成时通过这个通道主动将最终结果推送给用户。回调通知如果请求方是另一个微服务可以在创建审核任务时注册一个回调URL。审核完成后HITL系统向该URL发送一个POST请求携带任务结果。在我们的示例中可以在TaskNotificationService中实现WebSocket推送逻辑。当processTask方法被调用时除了更新数据库还通过WebSocket向关联的会话ID发送消息。Service RequiredArgsConstructor public class TaskNotificationService { private final SimpMessagingTemplate messagingTemplate; public void notifyTaskCompleted(String taskId) { HumanReviewTask task taskRepository.findById(taskId).orElse(null); if (task ! null (task.getStatus() ReviewStatus.APPROVED || task.getStatus() ReviewStatus.MODIFIED)) { // 构建推送消息 TaskResultMessage message new TaskResultMessage(taskId, task.getFinalContent(), task.getStatus()); // 假设sessionId存储在任务中用于定位推送目标 messagingTemplate.convertAndSendToUser(task.getSessionId(), /queue/review-result, message); } } }4. 高级策略与性能优化一个基础的HITL系统搭建完成后我们需要考虑如何在生产环境中让它更高效、更智能。4.1 动态规则引擎与机器学习集成最初的规则引擎可能是基于关键词或简单逻辑的。我们可以将其升级集成轻量级规则引擎使用Drools或Easy Rules将审核规则写成可动态加载的脚本支持复杂的逻辑组合。与模型置信度结合如果使用的AI模型能输出每个token或整体的置信度分数可以将其作为一个重要规则。低置信度回答自动进入审核队列。引入ML模型预筛训练一个小的分类模型如基于BERT用于判断AI回答是否可能存在问题如事实性错误、毒性内容。这个模型可以作为一个高效的“预过滤器”只有它判断为“可疑”的回答才会走完整的人工审核流程从而减少不必要的人工工作量。4.2 任务分配与负载均衡当审核任务量大时需要智能的任务分配。基于技能的分配根据触发规则如“法律问题”、“技术问题”将任务分配给具有相应领域知识的审核员。工作量均衡实时计算每个审核员的待处理任务数新任务优先分配给最空闲的审核员。优先级队列为任务设置优先级如来自VIP用户的请求、超时风险高的任务优先级更高。可以使用Redis的ZSET有序集合来实现优先级队列。4.3 超时与降级策略人工审核是不可控的。必须设计超时处理机制。任务超时每个任务设置一个超时时间如5分钟。如果超时仍未处理系统自动执行降级策略。降级策略可配置。例如a) 自动通过AI原始结果风险较高b) 返回一个默认的、安全的回复如“您的问题正在处理中请稍后再试”c) 转交给备用AI模型重新生成。在ReviewTask实体上可以增加expiresAt字段并有一个后台定时任务扫描超时的PENDING任务并执行降级。4.4 审计、溯源与持续改进所有审核操作必须留痕用于审计和质量分析。操作日志记录每一个任务的完整生命周期谁、在何时、对哪个任务、执行了什么操作、修改了什么内容。这不仅是安全要求也是优化规则和审核质量的宝贵数据。反馈循环建立反馈机制。审核员在驳回或修改时标注的原因如“事实错误”、“表述冗余”可以反向用于优化Prompt工程甚至作为微调AI模型的训练数据。我们可以定期分析这些数据找出AI模型的常见弱点从而迭代改进。5. 常见问题与实战避坑指南在实际开发和运维中我踩过不少坑这里分享一些关键的经验。5.1 上下文丢失与序列化问题问题在拦截器中Prompt对象可能包含复杂的Message列表系统消息、用户消息、历史消息直接调用toString()或简单序列化可能会丢失结构信息导致审核界面无法正确还原对话。解决使用可靠的序列化工具如Jackson的ObjectMapper将整个ClientRequest或其中关键的上下文对象转换为JSON字符串存储。在审核界面反序列化时可以重建一个简化的对话视图。务必处理好可能存在的循环引用和自定义类型。// 示例构建一个简化的上下文对象用于存储 MapString, Object contextMap new HashMap(); contextMap.put(promptMessages, request.prompt().getMessages().stream() .map(m - Map.of(type, m.getMessageType(), content, m.getContent())) .collect(Collectors.toList())); contextMap.put(modelOptions, request.options()); // ... 其他上下文 String contextJson objectMapper.writeValueAsString(contextMap); task.setReviewContext(contextJson);5.2 拦截器对性能的影响问题拦截器在每个AI调用后同步执行规则评估如果规则复杂或IO操作如查数据库慢会显著增加接口响应时间。解决规则预加载与缓存将审核规则缓存在内存中避免每次请求都查询数据库。异步化处理规则评估本身应快速。创建审核任务、保存数据库、发送通知这些IO密集型操作务必使用异步方式如Async、CompletableFuture或消息队列。确保拦截器主链路的耗时在毫秒级。采样与熔断在高并发场景下可以对规则评估本身进行采样或者设置熔断器在系统压力大时暂时跳过部分非核心规则的评估。5.3 审核端体验与效率问题审核界面信息杂乱审核员难以快速抓住重点导致审核效率低下。解决高亮关键部分在前端根据触发规则高亮显示AI回答中相关的部分。例如如果是“敏感词”规则触发则高亮标出敏感词。提供辅助信息在审核界面侧边栏提供一些辅助决策的信息例如该类型问题的历史审核通过率、相似问题的标准答案链接、相关知识点文档等。支持快捷键与批量操作为“通过”、“驳回”等常用操作设置键盘快捷键。对于简单明确的审核任务支持批量勾选后统一操作。5.4 状态一致性与并发控制问题多个审核员可能同时看到并试图处理同一个任务导致“一题多改”的混乱。解决在任务分配时采用“抢占式”锁定。当审核员A点击“处理”一个PENDING任务时后端立即执行一个原子操作将任务状态从PENDING更新为REVIEWING并设置assignedTo为A。这个更新操作需要基于乐观锁或数据库行锁如SELECT ... FOR UPDATE来实现确保只有一个人能抢到处理权。其他审核员刷新列表时该任务将不再出现在PENDING列表中。Transactional public boolean acquireTask(String taskId, String reviewer) { // 使用JPA的Lock(LockModeType.PESSIMISTIC_WRITE)或在Repository方法上加锁 HumanReviewTask task taskRepository.findByTaskIdAndStatus(taskId, ReviewStatus.PENDING); if (task ! null) { task.setStatus(ReviewStatus.REVIEWING); task.setAssignedTo(reviewer); taskRepository.save(task); return true; } return false; }5.5 与现有业务逻辑的集成复杂度问题业务系统原本是同步调用AI并立即使用结果改为HITL后流程变为异步需要重构多处业务逻辑来等待或查询审核结果。解决这是引入HITL的最大挑战之一。建议采用“防腐层”或“适配器”模式。定义一个统一的AIService接口它有两个实现一个是直接的DirectAIService直接调用AI无审核另一个是HumanInTheLoopAIService包含审核流程。通过配置或特性开关Feature Flag来控制使用哪个实现。这样业务代码依赖于抽象的接口切换和回滚都非常方便。在HumanInTheLoopAIService内部它负责处理异步等待、轮询或WebSocket回调对上层业务提供看似同步实际可能是异步Future或明确的异步回调接口。构建一个成熟的Human-in-the-Loop系统绝非一日之功它涉及前后端协作、异步编程、状态管理和用户体验设计等多个方面。Spring AI Alibaba提供了强大的AI集成能力而围绕它构建的HITL架构才是让AI能力在企业中安全、可靠落地的关键保障。从简单的关键词审核开始逐步迭代到基于机器学习的智能预筛和动态规则这个演进过程本身就是人机协同不断深化、相互增强的最佳实践。