公司动态
Spring AI Alibaba实战:构建Human-in-the-Loop人机协同系统
1. 项目概述当AI需要一双“人眼”最近在折腾一个智能客服的POC项目用上了Spring AI Alibaba。模型回答的流畅度没问题但一到涉及具体业务规则、价格计算或者敏感信息确认时就有点“放飞自我”要么答非所问要么给出一个模糊的、需要二次确认的答案。直接全自动吧怕出错捅娄子完全不让AI参与吧又浪费了它的效率。这个矛盾点就是“Human-in-the-Loop”人机回环简称HITL要解决的核心问题。简单来说HITL不是取代AI也不是让人工全盘接管而是在AI决策流程的关键节点上巧妙地插入人工审核或确认环节。让AI负责处理海量、重复、模式化的任务而把那些需要经验、判断力、创造力和承担责任的“硬骨头”留给人类专家。Spring AI Alibaba作为一套企业级的AI应用开发框架它提供的HITL能力本质上是一套标准化的“拦截与转交”机制。当AI模型生成的回答触发了预设的规则比如低置信度、涉及关键词、属于特定业务类别流程会自动暂停将当前上下文和AI的初步结果推送给指定的人工处理接口或界面待人处理完毕后再将最终结果返回给用户。这解决的远不止是“答案对不对”的问题。在风控场景它能防止模型误批贷款或交易在内容创作场景它能确保文案风格和品牌调性一致在代码生成场景资深工程师可以审查AI生成的代码片段是否有安全漏洞或架构缺陷。它的价值在于将AI的“广度”与人类的“深度”结合构建出一个既高效又可靠的协同系统。无论你是开发AI增强型应用的工程师还是负责AI落地的产品经理理解并实现HITL都是让AI从“玩具”走向“工具”的关键一步。2. 核心设计构建可插拔的“决策拦截器”实现HITL听起来像是在代码里到处写if-else来调用人工审核但这会迅速导致代码臃肿且难以维护。Spring AI Alibaba的思路是提供一套声明式的、基于策略的拦截框架让我们能像配置路由规则一样定义哪些AI请求需要“过一遍人手”。2.1 策略定义何时需要人工介入介入的时机是设计的灵魂。Spring AI Alibaba允许我们通过实现HumanInterventionPolicy接口来定义策略。常见的策略维度有以下几个你可以根据业务需求组合使用置信度阈值策略这是最直接的策略。AI模型尤其是大语言模型在生成每个token或整个回答时通常会有一个置信度分数。我们可以设定一个阈值例如0.7当模型对整个回答或其中关键实体如金额、日期、产品型号的置信度低于该阈值时触发人工审核。Component public class ConfidenceThresholdPolicy implements HumanInterventionPolicy { private static final double THRESHOLD 0.7; Override public boolean requiresIntervention(AiResponse aiResponse, ConversationContext context) { // 假设AiResponse中能获取到整体或分段的置信度 Double overallConfidence aiResponse.getMetadata().getConfidence(); return overallConfidence ! null overallConfidence THRESHOLD; } }关键词/正则匹配策略适用于高风险或高确定性领域。例如在客服场景中一旦用户提问包含“投诉”、“赔偿”、“法律”等关键词或符合“我要告你们”这类正则模式无论AI回答得多好都强制转人工。Component public class KeywordPolicy implements HumanInterventionPolicy { private final ListString highRiskKeywords Arrays.asList(投诉, 赔偿, 起诉, 监管); Override public boolean requiresIntervention(AiResponse aiResponse, ConversationContext context) { String userQuery context.getLastUserMessage(); return highRiskKeywords.stream().anyMatch(userQuery::contains); } }业务规则策略这是最体现业务复杂性的地方。策略可能需要调用外部服务或数据库。例如在金融问答中如果用户询问的理财产品风险等级为R5最高风险或者查询的转账金额超过单日限额则必须人工复核。Component RequiredArgsConstructor public class BusinessRulePolicy implements HumanInterventionPolicy { private final ProductService productService; Override public boolean requiresIntervention(AiResponse aiResponse, ConversationContext context) { String productCode extractProductCode(context); // 从上下文中提取产品代码 ProductInfo product productService.getProductInfo(productCode); return product ! null R5.equals(product.getRiskLevel()); } }输出格式/结构验证策略当AI需要生成结构化数据如JSON、XML或特定格式的文本如邮件标题、固定报告时可以用此策略验证其输出是否符合schema或模板要求不符合则转人工修正。设计心得策略应该尽量保持单一职责。一个策略只判断一个维度的条件。然后通过一个PolicyAggregator策略聚合器来组合这些策略聚合逻辑可以是“任一满足即触发”或“全部满足才触发”。这样便于独立测试、复用和动态调整。2.2 流程编排介入后发生了什么一旦策略判定需要人工介入标准的HITL流程便开始了。Spring AI Alibaba的HumanInTheLoopInterceptor会接管后续流程流程挂起与状态保存当前的AI对话上下文包括历史消息、用户问题、AI的初步回答、置信度等元数据会被完整地序列化并存储到一个持久化介质中比如Redis或数据库。同时生成一个唯一的interventionTicketId介入工单ID。异步通知系统通过预配置的渠道如内部消息队列、Webhook、邮件、钉钉/飞书机器人向人工处理平台或指定的处理人员发送通知内容包含工单ID、问题摘要和快速链接。人工处理处理人员在专属的管理后台查看工单详情。后台界面会清晰地展示用户原始问题、AI的初步回答、以及相关的上下文。处理人员可以直接采纳认为AI回答无误点击确认。编辑修正在AI回答的基础上进行修改和完善。完全重写丢弃AI回答提供全新的人工回答。补充信息可能要求AI根据补充信息重新生成这需要更复杂的循环设计。结果回调与流程恢复人工处理完成后处理平台调用Spring AI应用提供的回调接口通常是一个REST端点传入工单ID和最终处理结果。拦截器根据工单ID找回挂起的上下文用人工处理的结果替换掉原先的AI回答然后将这个“增强后”的响应返回给最初的用户请求。学习与反馈可选但重要最终被采纳的结果无论是AI原答案还是人工修改版可以被标记为“优质答案”并反哺到AI模型的微调数据集中实现闭环学习让AI在未来遇到类似问题时表现更好。关键点整个介入流程必须是异步和非阻塞的。用户的本次请求在触发介入后应立即得到一个友好的提示如“您的问题已提交给专家处理稍后将通过[消息]通知您结果”而不是让用户前端一直等待。这关乎用户体验。3. 实战集成在Spring AI Alibaba中落地HITL理论讲完我们来看如何在一个Spring Boot应用中具体实现。假设我们有一个简单的智能问答服务。3.1 环境与依赖准备首先确保你的pom.xml引入了必要的依赖。除了Spring AI Alibaba的核心starter我们还需要持久化如JPA MySQL和消息通知如Spring for Apache Kafka或钉钉SDK的相关依赖。dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-spring-boot-starter/artifactId version最新版本/version !-- 请替换为实际版本 -- /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.kafka/groupId artifactIdspring-kafka/artifactId !-- 用于异步通知 -- /dependency3.2 定义数据模型与仓储我们需要一个实体来保存每次人工介入的工单状态。Entity Table(name human_intervention_ticket) Data public class InterventionTicket { Id private String ticketId; // 工单唯一ID private String sessionId; // 对话会话ID Lob private String originalContext; // 序列化后的完整对话上下文 private String userQuery; private String aiInitialResponse; private String status; // PENDING, PROCESSING, RESOLVED, CANCELLED private String assignedTo; // 分配的处理人 private String finalResponse; // 人工处理后的最终回答 private LocalDateTime createdAt; private LocalDateTime resolvedAt; }对应的JpaRepositoryInterventionTicketRepository用于工单的增删改查。3.3 实现核心拦截器与策略这是最核心的部分。我们需要实现HumanInterventionInterceptor接口并在其中注入我们定义的策略。Component Slf4j RequiredArgsConstructor public class CustomHumanInterventionInterceptor implements HumanInterventionInterceptor { private final ListHumanInterventionPolicy policies; // 所有策略Bean会自动注入 private final InterventionTicketRepository ticketRepository; private final KafkaTemplateString, InterventionAlert kafkaTemplate; // 通知用 Override public AiResponse intercept(AiResponse aiResponse, ConversationContext context) { // 1. 聚合策略判断是否需要介入 boolean needsIntervention policies.stream() .anyMatch(policy - policy.requiresIntervention(aiResponse, context)); if (!needsIntervention) { return aiResponse; // 无需介入直接返回AI响应 } log.info(Human intervention triggered for session: {}, context.getSessionId()); // 2. 创建介入工单 InterventionTicket ticket new InterventionTicket(); ticket.setTicketId(UUID.randomUUID().toString()); ticket.setSessionId(context.getSessionId()); ticket.setOriginalContext(serializeContext(context)); // 序列化方法 ticket.setUserQuery(context.getLastUserMessage()); ticket.setAiInitialResponse(aiResponse.getContent()); ticket.setStatus(PENDING); ticket.setCreatedAt(LocalDateTime.now()); ticketRepository.save(ticket); // 3. 发送异步通知例如到Kafka由另一个服务消费并发送钉钉消息 InterventionAlert alert new InterventionAlert(ticket.getTicketId(), ticket.getUserQuery()); kafkaTemplate.send(human-intervention-alerts, alert); // 4. 抛出特定异常或返回一个等待中的响应告知上游需要人工处理 throw new HumanInterventionRequiredException(Request requires human review., ticket.getTicketId()); // 注意更优雅的方式是修改AiResponse返回一个提示信息而不是抛异常。 // 这取决于你的上游如何处理。这里用异常示意流程中断。 } }注意事项在实际项目中intercept方法可能不会直接抛异常而是返回一个特殊的AiResponse其内容为“您的问题已提交审核工单号XXX”。这需要前后端协议配合。抛异常是一种让全局异常处理器统一处理并转换响应的方式。3.4 构建人工处理后台与回调接口人工处理后台可以是一个独立的Web应用。它提供一个列表页展示所有PENDING状态的工单以及一个详情页供处理人员操作。核心的回调接口由AI服务提供可能如下RestController RequestMapping(/api/intervention) RequiredArgsConstructor public class InterventionCallbackController { private final InterventionTicketRepository ticketRepository; private final ConversationService conversationService; // 假设有服务能恢复对话 PostMapping(/resolve/{ticketId}) public ResponseEntityString resolveTicket(PathVariable String ticketId, RequestBody ResolutionRequest request) { InterventionTicket ticket ticketRepository.findById(ticketId) .orElseThrow(() - new RuntimeException(Ticket not found)); // 更新工单状态和最终答案 ticket.setFinalResponse(request.getFinalResponse()); ticket.setStatus(RESOLVED); ticket.setResolvedAt(LocalDateTime.now()); ticketRepository.save(ticket); // 关键恢复原对话流程。这里需要将最终答案“注入”回原会话。 // 一种方式是将答案存入一个临时存储如Redis键为sessionId原服务轮询或通过事件获取。 conversationService.completePendingResponse(ticket.getSessionId(), request.getFinalResponse()); return ResponseEntity.ok(Ticket resolved successfully.); } }处理后台的设计要点界面应把AI的初步回答和用户问题并排显示高亮显示可能有问题或低置信度的部分。提供便捷的编辑工具和预设的常用修正短语按钮提升处理效率。3.5 配置与启用拦截器最后在配置类中将我们的自定义拦截器注册到Spring AI的对话链中。Configuration EnableAiClients public class AiConfig { Bean public ChatClient chatClient(AiClient aiClient, CustomHumanInterventionInterceptor interceptor) { // 假设使用流式ChatClient return ChatClient.builder(aiClient) .interceptors(interceptor) // 注册HITL拦截器 .build(); } }4. 进阶考量与性能优化实现基础功能后我们需要关注一些进阶问题以确保系统在生产环境稳定可靠。4.1 超时、降级与熔断人工处理超时不能无限期等待人工处理。需要为每个工单设置超时时间如30分钟。超时后系统可以执行降级策略例如自动发送一条“问题已升级请稍后”的消息给用户或者尝试用一个更保守、安全的AI预设答案进行回复。拦截器本身熔断如果策略判断或工单创建服务出现故障如数据库连接超时拦截器应有熔断机制。可以记录错误日志并放行本次AI回答可能伴随告警而不是阻塞所有请求。这符合“Fail-Open”失败时开放的设计原则保证核心问答功能不中断。4.2 上下文管理与序列化对话上下文可能很大特别是支持长上下文模型后。完整序列化存储成本高。选择性存储并非所有历史消息都需要。可以只存储最近N轮对话或者只存储与触发策略强相关的消息。压缩与清理对存储的上下文进行压缩。工单解决后根据数据保留策略定期清理旧的上下文数据避免存储膨胀。4.3 策略的动态配置将策略的阈值如置信度、关键词列表等配置外置到配置中心如Nacos、Apollo。这样可以在不重启应用的情况下动态调整介入的敏感度。例如大促期间客服压力大可以临时调高置信度阈值减少人工介入量在模型刚上线或更新后可以调低阈值加强人工复核。4.4 监控与度量必须建立完善的监控体系介入率触发人工介入的请求占总请求的比例。这是衡量AI模型在该场景下成熟度的关键指标。平均处理时间从触发介入到人工解决的平均耗时。用于评估人工处理团队的效率和用户体验。采纳率与修改率人工直接采纳AI答案的比例 vs 需要修改的比例。高采纳率说明AI质量好高修改率则指明了模型需要优化的具体方向。策略触发统计每个策略分别触发了多少次。这能帮你分析哪些规则最常被触发是否合理是否需要优化。5. 避坑指南与常见问题在实际开发和运维中我踩过不少坑这里总结几个关键点策略过载导致“拦截风暴”初期由于担心出错设置了过多、过严的策略导致超过50%的请求都走了人工完全失去了AI提效的意义。建议从小范围、高风险场景开始试点逐步增加策略。密切监控介入率目标是将其控制在一个可接受的较低水平例如5%-10%并持续通过反馈优化模型来降低这个比例。上下文丢失或错乱在异步回调恢复对话时如果会话管理不当可能出现“张冠李戴”把工单A的答案回复给了用户B。解决方案确保ticketId与sessionId的强关联并在恢复时做严格校验。使用线程局部存储或显式的会话存储管理器来隔离上下文。人工处理体验差如果处理后台加载慢、信息展示不全、编辑困难会严重影响处理人员的效率和意愿。心得把处理后台当成一个重要的产品来设计。提供全文搜索、批量操作、常用语模板、与内部知识库联动等功能。处理效率直接关系到系统的整体响应时间。忽略反馈闭环人工处理完就结束了没有把修正后的优质数据系统性地收集起来用于模型优化。建议建立数据管道将(用户问题AI原始回答人工修正后答案)这样的三元组自动存入特定数据集定期用于模型的监督微调SFT。这是HITL长期价值最大化的关键。对用户体验的冲击如果每次介入都让用户等待很久体验会很差。优化对于明确需要较长时间处理的问题在触发介入时立即给用户一个预期“您的问题需要专家核实预计30分钟内通过短信答复您”并提供工单号供查询。对于可以快速判断的尝试实现“实时协作”即AI给出答案的同时在后台同步发送给人工复核如果几秒内人工无异议则答案不变如有修改可通过推送等方式及时更新给用户适用于IM场景。实现Human-in-the-Loop不是一劳永逸的工程而是一个需要持续调优的协同系统。它始于对AI能力局限性的坦诚认知成于对业务流程的深刻理解与精巧设计。通过Spring AI Alibaba提供的框架能力我们可以更聚焦于业务策略本身构建出真正可靠、可信的AI增强型应用。