Spring AI Alibaba JManus:前后端异步消息回显机制深度解析

作者 :小沛
专栏 :Spring AI Alibaba JManus 技术深度解析
标签Spring AI 异步处理 前后端分离 Vue3 消息回显


📖 引言

在现代Web应用开发中,特别是AI对话系统,用户体验的核心在于实时性响应性。当用户发送一个复杂请求时,后端可能需要几秒甚至更长时间来处理AI推理、外部API调用等任务。如何让前端优雅地展示处理进度,并在完成后及时回显结果,是一个关键的技术挑战。

Spring AI Alibaba JManus项目通过精心设计的异步消息回显机制 ,完美解决了这一痛点。本文将以用户输入"帮我查询一下今天的天气"为实际案例,深度解析从前端交互到消息完整渲染的全流程技术实现。


🎯 实战案例:用户查询天气的完整交互流程

场景描述

用户在聊天界面输入:"帮我查询一下今天的天气",系统需要:

  1. 立即响应用户输入
  2. 调用天气API获取数据
  3. 实时显示处理进度
  4. 最终展示天气查询结果

让我们详细追踪这个过程中的每一个技术环节。


🚀 第一阶段:前端用户交互触发

1.1 用户输入处理

当用户在聊天界面输入"帮我查询一下今天的天气"并点击发送按钮时,前端Vue3组件立即执行以下流程:

typescript 复制代码
// ui-vue3/src/components/chat/index.vue
async handleSendMessage() {
  const query = "帮我查询一下今天的天气"
  
  // 1. 立即添加用户消息到聊天界面
  const userMessage: ChatMessage = {
    id: Date.now().toString(),
    content: query,
    isUser: true,
    timestamp: new Date().toLocaleTimeString(),
    avatar: 'user-avatar.png'
  }
  this.messages.push(userMessage)
  
  // 2. 添加AI助手占位消息(显示思考状态)
  const assistantMessage: ChatMessage = {
    id: (Date.now() + 1).toString(),
    content: '',
    isUser: false,
    timestamp: new Date().toLocaleTimeString(),
    thinking: '正在理解您的请求并准备查询天气信息...',
    avatar: 'ai-avatar.png'
  }
  this.messages.push(assistantMessage)
  
  // 3. 滚动到最新消息
  this.$nextTick(() => {
    this.scrollToBottom()
  })
  
  try {
    // 4. 调用后端API
    const response = await DirectApiService.sendMessage(query)
    
    // 5. 启动计划执行管理
    if (response.planId) {
      console.log(`[Chat] 收到planId: ${response.planId},启动轮询管理`)
      planExecutionManager.handlePlanExecutionRequested(response.planId, query)
    }
  } catch (error) {
    console.error('[Chat] 发送消息失败:', error)
    // 错误处理:更新助手消息显示错误状态
    assistantMessage.content = '抱歉,处理您的请求时出现了问题,请稍后重试。'
    assistantMessage.thinking = undefined
  }
}

1.2 界面即时反馈

此时用户界面会立即显示:

复制代码
👤 用户 [14:30:52]
帮我查询一下今天的天气

🤖 AI助手 [14:30:52]
💭 正在理解您的请求并准备查询天气信息...

🌐 第二阶段:前端API调用层

2.1 DirectApiService 实现

typescript 复制代码
// ui-vue3/src/api/direct-api-service.ts
export class DirectApiService {
  private static readonly BASE_URL = '/api/executor'

  /**
   * 发送用户消息到后端执行器
   * @param query 用户查询内容
   * @returns Promise<ApiResponse> 包含planId的响应
   */
  public static async sendMessage(query: string): Promise<any> {
    console.log(`[DirectApiService] 发送消息: "${query}"`)
    
    const requestBody = { query }
    console.log('[DirectApiService] 请求体:', requestBody)
    
    const response = await fetch(`${this.BASE_URL}/execute`, {
      method: 'POST',
      headers: { 
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(requestBody)
    })
    
    if (!response.ok) {
      throw new Error(`API请求失败: ${response.status} ${response.statusText}`)
    }
    
    const result = await response.json()
    console.log('[DirectApiService] 响应结果:', result)
    
    return result
  }
}

2.2 HTTP请求详情

请求信息:

  • URL : POST /api/executor/execute
  • 请求头 : Content-Type: application/json
  • 请求体:
json 复制代码
{
  "query": "帮我查询一下今天的天气"
}

⚙️ 第三阶段:后端接口处理

3.1 ManusController 接收请求

java 复制代码
// src/main/java/.../controller/ManusController.java
@RestController
@RequestMapping("/api/executor")
public class ManusController {

    private static final Logger logger = LoggerFactory.getLogger(ManusController.class);

    @Autowired
    private PlanningFactory planningFactory;
    
    @Autowired
    private PlanIdDispatcher planIdDispatcher;

    /**
     * 异步执行用户查询请求
     * @param request 包含用户查询的请求体
     * @return 立即返回任务ID和状态
     */
    @PostMapping("/execute")
    public ResponseEntity<Map<String, Object>> executeQuery(@RequestBody Map<String, String> request) {
        String query = request.get("query"); // "帮我查询一下今天的天气"
        
        logger.info("收到用户查询请求: {}", query);
        
        if (query == null || query.trim().isEmpty()) {
            return ResponseEntity.badRequest().body(Map.of("error", "查询内容不能为空"));
        }
        
        // 1. 创建执行上下文
        ExecutionContext context = new ExecutionContext();
        context.setUserRequest(query);
        
        // 2. 生成唯一计划ID(时间戳+随机字符)
        String planId = planIdDispatcher.generatePlanId(); 
        // 例如: "plan_20250103_143052_weather_abc123"
        context.setCurrentPlanId(planId);
        context.setRootPlanId(planId);
        context.setNeedSummary(true);
        
        logger.info("为查询请求生成planId: {}", planId);
        
        // 3. 创建计划协调器
        PlanningCoordinator planningFlow = planningFactory.createPlanningCoordinator(planId);
        
        // 4. 异步执行任务(关键:不阻塞HTTP响应)
        CompletableFuture.supplyAsync(() -> {
            try {
                logger.info("开始异步执行计划: {}", planId);
                return planningFlow.executePlan(context);
            } catch (Exception e) {
                logger.error("执行计划失败: {}", planId, e);
                throw new RuntimeException("执行计划失败: " + e.getMessage(), e);
            }
        });
        
        // 5. 立即返回任务ID和状态(不等待执行完成)
        Map<String, Object> response = new HashMap<>();
        response.put("planId", planId);
        response.put("status", "processing");
        response.put("message", "天气查询任务已提交,正在处理中");
        response.put("timestamp", System.currentTimeMillis());
        
        logger.info("立即返回响应,planId: {}", planId);
        return ResponseEntity.ok(response);
    }
}

3.2 后端响应详情

响应信息:

json 复制代码
{
  "planId": "plan_20250103_143052_weather_abc123",
  "status": "processing",
  "message": "天气查询任务已提交,正在处理中",
  "timestamp": 1704268232000
}

响应时间: 通常在 50-100ms 内返回(不等待AI处理)


🔄 第四阶段:后端异步计划执行

4.1 PlanningCoordinator 执行流程

java 复制代码
// src/main/java/.../coordinator/PlanningCoordinator.java
public class PlanningCoordinator {
    
    private static final Logger log = LoggerFactory.getLogger(PlanningCoordinator.class);
    
    /**
     * 执行完整的计划处理流程
     * @param context 执行上下文
     * @return 执行结果
     */
    public ExecutionContext executePlan(ExecutionContext context) {
        String planId = context.getCurrentPlanId();
        log.info("开始执行完整计划流程,planId: {}", planId);
        
        context.setUseMemory(true);
        
        try {
            // 1. 创建执行计划(AI分析用户意图)
            log.info("步骤1: 创建执行计划 - {}", planId);
            planCreator.createPlan(context);
            
            // 2. 选择合适的执行器并执行
            PlanInterface plan = context.getPlan();
            if (plan != null) {
                PlanExecutorInterface executor = planExecutorFactory.createExecutor(plan);
                log.info("步骤2: 选择执行器 {} 处理计划类型: {} - {}", 
                        executor.getClass().getSimpleName(), 
                        plan.getPlanType(), 
                        planId);
                
                // 执行所有步骤(包括调用天气API)
                executor.executeAllSteps(context);
                log.info("步骤3: 执行所有计划步骤完成 - {}", planId);
            } else {
                log.error("计划创建失败,无法找到有效计划 - {}", planId);
                throw new IllegalStateException("计划创建失败");
            }
            
            // 3. 生成执行摘要
            log.info("步骤4: 生成执行摘要 - {}", planId);
            planFinalizer.generateSummary(context);
            
            log.info("计划执行完成 - {}", planId);
            return context;
            
        } catch (Exception e) {
            log.error("计划执行过程中发生错误 - {}: {}", planId, e.getMessage(), e);
            throw e;
        }
    }
}

4.2 执行步骤详解

在后台线程中,系统会执行以下步骤:

  1. 接收请求: AI分析"帮我查询一下今天的天气",识别为天气查询任务
  2. 计划生成: 创建包含天气API调用的执行计划
  3. 工具调用: 调用天气查询工具获取实时天气数据
  4. 结果整理: 将天气数据格式化为用户友好的回复
  5. 摘要生成: 生成最终的回复内容

📊 第五阶段:前端轮询管理启动

5.1 PlanExecutionManager 启动轮询

typescript 复制代码
// ui-vue3/src/utils/plan-execution-manager.ts
export class PlanExecutionManager {
  private static readonly POLL_INTERVAL = 2000 // 2秒轮询间隔
  
  /**
   * 处理计划执行请求
   * @param planId 计划ID
   * @param query 用户查询
   */
  public handlePlanExecutionRequested(planId: string, query: string): void {
    console.log(`[PlanExecutionManager] 计划执行请求: ${planId}`)
    console.log(`[PlanExecutionManager] 查询内容: "${query}"`)
    
    // 1. 设置活跃计划ID
    this.state.activePlanId = planId
    
    // 2. 启动轮询机制
    this.initiatePlanExecutionSequence(query, planId)
  }

  /**
   * 启动计划执行序列
   * @param query 用户查询
   * @param planId 计划ID
   */
  public initiatePlanExecutionSequence(query: string, planId: string): void {
    console.log(`[PlanExecutionManager] 启动执行序列: "${query}"`)
    
    // 3. 触发对话轮次开始事件
    this.emitDialogRoundStart(planId)
    
    // 4. 开始定时轮询
    this.startPolling()
  }

  /**
   * 开始轮询计划执行状态
   */
  public startPolling(): void {
    // 清除之前的轮询定时器
    if (this.state.pollTimer) {
      clearInterval(this.state.pollTimer)
    }
    
    // 设置新的轮询定时器
    this.state.pollTimer = window.setInterval(() => {
      this.pollPlanStatus()
    }, this.POLL_INTERVAL)
    
    console.log(`[PlanExecutionManager] 开始轮询,间隔: ${this.POLL_INTERVAL}ms`)
    
    // 立即执行一次轮询
    this.pollPlanStatus()
  }
}

5.2 轮询状态检查

typescript 复制代码
/**
 * 轮询计划执行状态
 */
private async pollPlanStatus(): Promise<void> {
  if (!this.state.activePlanId) {
    console.warn('[PlanExecutionManager] 没有活跃的planId,跳过轮询')
    return
  }
  
  if (this.state.isPolling) {
    console.log('[PlanExecutionManager] 上次轮询仍在进行中,跳过本次')
    return
  }
  
  try {
    this.state.isPolling = true
    console.log(`[PlanExecutionManager] 轮询状态: ${this.state.activePlanId}`)
    
    // 调用状态查询API
    const details = await CommonApiService.getDetails(this.state.activePlanId)
    
    if (!details) {
      console.warn('[PlanExecutionManager] 未收到API响应数据')
      return
    }
    
    // 更新缓存
    if (details.rootPlanId) {
      this.setCachedPlanRecord(details.rootPlanId, details)
      console.log(`[PlanExecutionManager] 更新缓存: ${details.rootPlanId}`)
    }
    
    // 触发UI更新
    this.emitPlanUpdate(details.rootPlanId ?? "")
    
    // 检查是否完成
    if (details.completed) {
      console.log(`[PlanExecutionManager] 计划执行完成: ${details.rootPlanId}`)
      this.handlePlanCompletion(details)
    } else {
      console.log(`[PlanExecutionManager] 计划仍在执行中,继续轮询`)
    }
    
  } catch (error) {
    console.error('[PlanExecutionManager] 轮询状态失败:', error)
  } finally {
    this.state.isPolling = false
  }
}

🔍 第六阶段:状态查询与数据获取

6.1 后端状态查询接口

java 复制代码
// ManusController.java
/**
 * 获取详细的执行记录
 * @param planId 计划ID
 * @return 执行记录的JSON表示
 */
@GetMapping("/details/{planId}")
public synchronized ResponseEntity<?> getExecutionDetails(@PathVariable("planId") String planId) {
    logger.info("查询执行详情,planId: {}", planId);
    
    // 1. 获取计划执行记录
    PlanExecutionRecord planRecord = planExecutionRecorder.getRootPlanExecutionRecord(planId);
    
    if (planRecord == null) {
        logger.warn("未找到执行记录,planId: {}", planId);
        return ResponseEntity.notFound().build();
    }
    
    // 2. 检查用户输入等待状态
    UserInputWaitState waitState = userInputService.getWaitState(planId);
    if (waitState != null && waitState.isWaiting()) {
        planRecord.setUserInputWaitState(waitState);
        logger.info("计划 {} 正在等待用户输入", planId);
    } else {
        planRecord.setUserInputWaitState(null);
    }
    
    // 3. 序列化为JSON返回
    try {
        String jsonResponse = objectMapper.writeValueAsString(planRecord);
        logger.debug("返回执行详情,planId: {}, 数据大小: {} 字符", planId, jsonResponse.length());
        return ResponseEntity.ok(jsonResponse);
    } catch (JsonProcessingException e) {
        logger.error("序列化执行记录失败,planId: {}", planId, e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body("处理请求时发生错误: " + e.getMessage());
    }
}

6.2 轮询请求详情

轮询请求信息:

  • URL : GET /api/executor/details/{planId}
  • 频率: 每2秒一次
  • 请求头 : Accept: application/json

响应示例(执行中):

json 复制代码
{
  "planId": "plan_20250103_143052_weather_abc123",
  "rootPlanId": "plan_20250103_143052_weather_abc123",
  "title": "天气查询任务",
  "userRequest": "帮我查询一下今天的天气",
  "startTime": "2025-01-03T14:30:52.123",
  "status": "RUNNING",
  "completed": false,
  "steps": [
    {
      "stepId": "step_1",
      "title": "分析用户意图",
      "status": "FINISHED",
      "result": "识别为天气查询请求"
    },
    {
      "stepId": "step_2", 
      "title": "调用天气API",
      "status": "RUNNING",
      "result": "正在获取天气数据..."
    }
  ]
}

🎨 第七阶段:前端渐进式UI更新

7.1 状态更新回调处理

typescript 复制代码
// ui-vue3/src/components/chat/index.vue
/**
 * 处理计划更新事件
 * @param rootPlanId 根计划ID
 */
onPlanUpdate(rootPlanId: string) {
  console.log(`[Chat] 收到计划更新事件: ${rootPlanId}`)
  
  const cachedRecord = planExecutionManager.getCachedPlanRecord(rootPlanId)
  
  if (cachedRecord) {
    // 1. 查找对应的助手消息
    const assistantMessage = this.messages.find(msg => 
      !msg.isUser && msg.planExecution?.currentPlanId === rootPlanId
    )
    
    if (assistantMessage) {
      // 2. 更新执行步骤信息
      assistantMessage.planExecution = cachedRecord
      
      // 3. 更新思考状态
      if (cachedRecord.steps && cachedRecord.steps.length > 0) {
        const runningStep = cachedRecord.steps.find(step => step.status === 'RUNNING')
        if (runningStep) {
          assistantMessage.thinking = `正在执行: ${runningStep.title}`
        }
      }
      
      // 4. 更新最终回复(如果已完成)
      if (cachedRecord.completed && cachedRecord.finalReply) {
        assistantMessage.content = cachedRecord.finalReply
        assistantMessage.thinking = undefined
        console.log(`[Chat] 显示最终回复: ${cachedRecord.finalReply}`)
      }
      
      // 5. 滚动到底部
      this.$nextTick(() => {
        this.scrollToBottom()
      })
    }
  }
}

7.2 UI渐进式更新效果

执行过程中的界面变化:

复制代码
👤 用户 [14:30:52]
帮我查询一下今天的天气

🤖 AI助手 [14:30:52]
💭 正在执行: 分析用户意图
📋 执行步骤:
  ✅ 分析用户意图 - 已完成
  🔄 调用天气API - 执行中
  ⏳ 格式化结果 - 等待中

完成后的界面:

复制代码
👤 用户 [14:30:52]
帮我查询一下今天的天气

🤖 AI助手 [14:30:55]
🌤️ 今天北京的天气情况:

📍 **地点**: 北京市
🌡️ **温度**: 15°C
☁️ **天气**: 多云
💨 **风力**: 东南风 3级
💧 **湿度**: 65%
👁️ **能见度**: 10公里

**温馨提示**: 今天天气较为舒适,适合外出活动,建议穿着轻薄外套。

📋 执行步骤:
  ✅ 分析用户意图 - 已完成
  ✅ 调用天气API - 已完成  
  ✅ 格式化结果 - 已完成

📈 第八阶段:完成处理与资源清理

8.1 计划完成处理

typescript 复制代码
/**
 * 处理计划完成
 * @param details 计划执行详情
 */
private handlePlanCompletion(details: PlanExecutionRecord): void {
  console.log(`[PlanExecutionManager] 处理计划完成: ${details.rootPlanId}`)
  
  // 1. 触发完成事件
  this.emitPlanCompleted(details.rootPlanId ?? "")
  
  // 2. 重置序列大小
  this.state.lastSequenceSize = 0
  
  // 3. 停止轮询
  this.stopPolling()
  
  // 4. 延迟清理资源(5秒后删除执行记录)
  setTimeout(async () => {
    if (this.state.activePlanId) {
      try {
        await CommonApiService.deleteDetails(this.state.activePlanId)
        console.log(`[PlanExecutionManager] 执行记录已清理: ${this.state.activePlanId}`)
      } catch (error) {
        console.warn(`[PlanExecutionManager] 清理执行记录失败: ${error.message}`)
      }
    }
  }, 5000)
  
  // 5. 重置状态
  if (details.completed) {
    this.state.activePlanId = null
    this.emitChatInputUpdateState(details.rootPlanId ?? "")
  }
}

8.2 停止轮询

typescript 复制代码
/**
 * 停止轮询
 */
public stopPolling(): void {
  if (this.state.pollTimer) {
    clearInterval(this.state.pollTimer)
    this.state.pollTimer = null
    console.log('[PlanExecutionManager] 轮询已停止')
  }
}

🔄 完整交互时序图

👤用户 🖥️前端Vue3 🌐DirectApiService ⚙️ManusController 🎯PlanningCoordinator 🌤️天气API 📊轮询管理器 输入"帮我查询一下今天的天气" 立即添加用户消息到界面 添加AI占位消息(思考状态) sendMessage(query) POST /api/executor/execute 生成planId 异步执行executePlan() 立即返回{planId, status: "processing"} 返回planId 启动轮询管理(planId) 1. 分析用户意图 2. 调用天气API 返回天气数据 3. 格式化结果 4. 生成最终回复 GET /api/executor/details/{planId} 返回当前执行状态 更新UI显示进度 渐进式更新消息内容 loop [每2秒轮询] par [后端异步处理] [前端轮询检查] 计划执行完成 最后一次状态查询 返回完成状态+天气结果 触发完成事件 显示完整天气信息 展示天气查询结果 停止轮询,清理资源 👤用户 🖥️前端Vue3 🌐DirectApiService ⚙️ManusController 🎯PlanningCoordinator 🌤️天气API 📊轮询管理器


🎯 核心技术要点总结

1. 异步处理架构

  • 立即响应: 后端接收请求后立即返回planId,不等待处理完成
  • 后台执行: 使用CompletableFuture在独立线程中执行AI推理和API调用
  • 非阻塞: HTTP请求不会因为长时间处理而超时

2. 智能轮询机制

  • 固定间隔: 每2秒查询一次执行状态
  • 状态缓存: 前端缓存执行记录,避免重复处理
  • 自动停止: 任务完成后自动停止轮询

3. 渐进式UI更新

  • 即时反馈: 用户输入后立即显示在界面上
  • 进度展示: 实时显示执行步骤和当前状态
  • 流畅体验: 通过Vue3响应式系统实现平滑更新

4. 错误处理与容错

  • 网络异常: 处理API调用失败的情况
  • 超时处理: 避免长时间等待导致的用户体验问题
  • 状态恢复: 支持页面刷新后的状态恢复

5. 性能优化策略

  • 资源清理: 任务完成后自动清理执行记录
  • 缓存机制: 使用LRU缓存减少重复请求
  • 批量更新: 合并多个状态更新减少DOM操作

🎉 总结

Spring AI Alibaba JManus的异步消息回显机制通过精心设计的前后端协作,实现了优秀的用户体验:

  • 响应迅速: 用户操作后立即获得反馈
  • 🔄 状态透明: 清晰展示处理进度和当前状态
  • 🎯 结果准确: 完整展示AI处理结果
  • 🛡️ 稳定可靠: 完善的错误处理和容错机制

这种架构模式特别适合处理AI对话、复杂计算、外部API调用等耗时操作,为现代Web应用提供了一个优秀的异步处理解决方案。


技术栈 : Spring Boot 3.x + Vue 3 + TypeScript + CompletableFuture
适用场景 : AI对话系统、长时间任务处理、实时状态监控
核心优势: 非阻塞处理、实时反馈、用户体验优化

相关推荐
张较瘦_22 分钟前
[论文阅读] 人工智能 + 软件工程 | 增强RESTful API测试:针对MongoDB的搜索式模糊测试新方法
论文阅读·人工智能·软件工程
Wendy14411 小时前
【边缘填充】——图像预处理(OpenCV)
人工智能·opencv·计算机视觉
钱彬 (Qian Bin)1 小时前
《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——8. AI赋能(下):在Qt中部署YOLOv8模型
人工智能·qt·yolo·qml·qt quick·工业质检·螺丝瑕疵检测
星月昭铭2 小时前
Spring AI调用Embedding模型返回HTTP 400:Invalid HTTP request received分析处理
人工智能·spring boot·python·spring·ai·embedding
大千AI助手3 小时前
直接偏好优化(DPO):原理、演进与大模型对齐新范式
人工智能·神经网络·算法·机器学习·dpo·大模型对齐·直接偏好优化
ReinaXue3 小时前
大模型【进阶】(四)QWen模型架构的解读
人工智能·神经网络·语言模型·transformer·语音识别·迁移学习·audiolm
静心问道3 小时前
Deja Vu: 利用上下文稀疏性提升大语言模型推理效率
人工智能·模型加速·ai技术应用
小妖同学学AI4 小时前
deepseek+飞书多维表格 打造小红书矩阵
人工智能·矩阵·飞书
阿明观察4 小时前
再谈亚马逊云科技(AWS)上海AI研究院7月22日关闭事件
人工智能
没有bug.的程序员4 小时前
《Spring Security源码深度剖析:Filter链与权限控制模型》
java·后端·spring·security·filter·权限控制