From 69ffda358ff3cc489543153139400436305f9a4d Mon Sep 17 00:00:00 2001 From: huazhongmin Date: Thu, 30 Oct 2025 17:34:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E8=AF=9Dbug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/AiChatServiceImpl.java | 188 ++++++++++++++---- 1 file changed, 148 insertions(+), 40 deletions(-) diff --git a/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java index c2cd444..686cef9 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java @@ -901,6 +901,7 @@ public class AiChatServiceImpl implements AiChatService { // 发送请求并处理流式响应 StringBuilder responseBuilder = new StringBuilder(); StringBuilder fullStreamData = new StringBuilder(); + final String[] currentEvent = {null}; // 使用数组来解决lambda中的final问题 java.net.http.HttpResponse> response = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofLines()); @@ -913,72 +914,143 @@ public class AiChatServiceImpl implements AiChatService { return "流式请求失败,状态码: " + response.statusCode(); } - // 处理流式数据 + // 处理流式数据 - 正确处理SSE格式 response.body().forEach(line -> { fullStreamData.append(line).append("\n"); + log.debug("收到流式数据行: {}", line); - if (line.startsWith("data: ")) { + // 处理SSE事件格式 + if (line.startsWith("event:")) { + currentEvent[0] = line.substring(6).trim(); + log.debug("当前事件类型: {}", currentEvent[0]); + } else if (line.startsWith("data: ")) { String data = line.substring(6).trim(); + log.debug("解析流式数据: {}", data); + // 检查是否为结束标记 if ("[DONE]".equals(data)) { log.info("流式响应完成"); return; } - try { - JSONObject jsonData = JSON.parseObject(data); - - // 提取消息内容 - if (jsonData.containsKey("choices")) { - com.alibaba.fastjson2.JSONArray choices = jsonData.getJSONArray("choices"); - if (choices != null && !choices.isEmpty()) { - JSONObject choice = choices.getJSONObject(0); - if (choice != null && choice.containsKey("delta")) { - JSONObject delta = choice.getJSONObject("delta"); - if (delta != null && delta.containsKey("content")) { - String content = delta.getString("content"); - if (content != null) { - responseBuilder.append(content); - } - } + // 只处理消息增量事件 + if ("conversation.message.delta".equals(currentEvent[0])) { + try { + JSONObject jsonData = JSON.parseObject(data); + log.debug("解析后的JSON数据: {}", jsonData); + + // 提取content字段 + if (jsonData.containsKey("content")) { + String content = jsonData.getString("content"); + if (content != null && !content.trim().isEmpty()) { + log.debug("提取增量内容: {}", content); + responseBuilder.append(content); } } + + } catch (Exception e) { + log.warn("解析流式数据失败: {}, 数据: {}", e.getMessage(), data); } - - // Coze特定格式处理 - if (jsonData.containsKey("event")) { - String event = jsonData.getString("event"); - if ("conversation.message.delta".equals(event) && jsonData.containsKey("data")) { - JSONObject eventData = jsonData.getJSONObject("data"); - if (eventData != null && eventData.containsKey("content")) { - String content = eventData.getString("content"); - if (content != null) { - responseBuilder.append(content); - } + } else if ("conversation.message.completed".equals(currentEvent[0])) { + // 处理完整消息事件,可以用作备用方案 + try { + JSONObject jsonData = JSON.parseObject(data); + + // 检查是否为answer类型的消息 + if (jsonData.containsKey("type") && "answer".equals(jsonData.getString("type"))) { + String content = jsonData.getString("content"); + if (content != null && !content.trim().isEmpty() && responseBuilder.length() == 0) { + log.debug("使用完整消息内容作为备用: {}", content); + responseBuilder.append(content); } } + + } catch (Exception e) { + log.warn("解析完整消息数据失败: {}, 数据: {}", e.getMessage(), data); } - - } catch (Exception e) { - log.warn("解析流式数据失败: {}, 数据: {}", e.getMessage(), data); } + + // 重置当前事件 + currentEvent[0] = null; + } else if (line.trim().isEmpty()) { + // 空行表示事件结束 + currentEvent[0] = null; } }); // 记录完整的流式数据用于调试 updateApiCallStreamData(apiCall, fullStreamData.toString()); + log.info("流式响应处理完成,提取内容长度: {}", responseBuilder.length()); String finalResponse = responseBuilder.toString(); if (finalResponse.isEmpty()) { - log.warn("流式响应为空,返回默认消息"); - return "收到了流式响应,但内容为空。"; + log.warn("流式响应为空,尝试降级到非流式处理"); + // 降级到非流式处理 + return handleNonStreamFallback(url, headers, requestBody, apiCall); } return finalResponse; } catch (Exception e) { log.error("处理流式响应失败", e); - throw new RuntimeException("处理流式响应失败: " + e.getMessage(), e); + // 降级到非流式处理 + return handleNonStreamFallback(url, headers, requestBody, apiCall); + } + } + + /** + * 非流式处理降级方案 + */ + private String handleNonStreamFallback(String url, HttpHeaders headers, Map requestBody, CozeApiCall apiCall) { + try { + log.info("执行非流式降级处理"); + + // 移除stream参数,改为非流式请求 + Map nonStreamBody = new HashMap<>(requestBody); + nonStreamBody.put("stream", false); + + // 构建请求头(移除流式相关头) + HttpHeaders nonStreamHeaders = new HttpHeaders(); + headers.forEach((key, values) -> { + if (!"Accept".equals(key) || !"text/event-stream".equals(values.get(0))) { + nonStreamHeaders.put(key, values); + } + }); + nonStreamHeaders.set("Content-Type", "application/json"); + + HttpEntity> request = new HttpEntity<>(nonStreamBody, nonStreamHeaders); + log.info("发送非流式降级请求到: {}", url); + + // 发送请求 + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.POST, + request, + String.class); + + log.info("收到非流式降级响应: {}", response.getBody()); + + // 解析响应获取chat_id和conversation_id + JSONObject responseJson = JSON.parseObject(response.getBody()); + String chatId = extractChatIdFromResponse(responseJson); + String cozeConversationId = extractConversationIdFromResponse(responseJson); + + if (chatId != null && cozeConversationId != null) { + // 更新API调用记录的Coze ID信息 + updateApiCallCozeIds(apiCall, chatId, cozeConversationId); + + // 轮询聊天状态直到完成并获取回复内容 + String aiReply = waitForChatCompletionWithTracking(chatId, cozeConversationId, apiCall); + log.info("非流式降级处理成功: reply={}", aiReply); + return aiReply; + } else { + log.error("非流式降级处理失败:无法从响应中获取chat_id或conversation_id"); + return "抱歉,AI服务暂时不可用,请稍后再试。"; + } + + } catch (Exception e) { + log.error("非流式降级处理失败", e); + return "抱歉,AI服务暂时不可用,请稍后再试。"; } } @@ -987,8 +1059,14 @@ public class AiChatServiceImpl implements AiChatService { */ private void updateApiCallStreamData(CozeApiCall apiCall, String streamData) { try { - // 可以将流式数据存储到响应体字段中,用于调试和分析 - apiCall.setResponseBody(streamData); + // 将流式数据包装成有效的JSON格式存储 + Map streamDataWrapper = new HashMap<>(); + streamDataWrapper.put("type", "stream"); + streamDataWrapper.put("data", streamData); + streamDataWrapper.put("timestamp", System.currentTimeMillis()); + + String jsonStreamData = JSON.toJSONString(streamDataWrapper); + apiCall.setResponseBody(jsonStreamData); cozeApiCallService.updateById(apiCall); } catch (Exception e) { log.error("更新API调用记录流式数据失败: {}", e.getMessage(), e); @@ -1096,6 +1174,13 @@ public class AiChatServiceImpl implements AiChatService { // 根据配置决定是否使用流式输出 boolean useStream = config.getSupportStream() != null && config.getSupportStream() == 1; cozeRequest.put("stream", useStream); + + // 添加auto_save_history参数,确保对话历史被保存 + cozeRequest.put("auto_save_history", true); + + // 如果有conversationId,添加到请求中(但不是必需的,Coze会自动创建) + // 注意:这里的conversationId是我们系统内部的ID,不是Coze的conversation_id + // Coze会自动创建新的conversation_id // 构建消息列表 - 按照 Coze API 标准格式 java.util.List> messages = new java.util.ArrayList<>(); @@ -1105,11 +1190,14 @@ public class AiChatServiceImpl implements AiChatService { currentMsg.put(ROLE_KEY, USER_ROLE); currentMsg.put(CONTENT_KEY, userMessage); currentMsg.put(CONTENT_TYPE_KEY, TEXT_TYPE); - currentMsg.put("type", "question"); + // 移除type字段,可能不是必需的 messages.add(currentMsg); cozeRequest.put("additional_messages", messages); - cozeRequest.put("parameters", new HashMap<>()); + + // 确保parameters不为空,添加一些默认参数 + Map parameters = new HashMap<>(); + cozeRequest.put("parameters", parameters); return cozeRequest; } @@ -1435,7 +1523,27 @@ public class AiChatServiceImpl implements AiChatService { private void updateApiCallResponse(CozeApiCall apiCall, ResponseEntity response) { try { apiCall.setResponseStatus(response.getStatusCodeValue()); - apiCall.setResponseBody(response.getBody()); + + // 确保响应体是有效的JSON格式 + String responseBody = response.getBody(); + if (responseBody != null && !responseBody.trim().isEmpty()) { + try { + // 尝试解析为JSON,如果失败则包装成JSON + JSON.parseObject(responseBody); + apiCall.setResponseBody(responseBody); + } catch (Exception jsonException) { + // 如果不是有效JSON,则包装成JSON格式 + Map wrapper = new HashMap<>(); + wrapper.put("type", "raw_response"); + wrapper.put("data", responseBody); + wrapper.put("timestamp", System.currentTimeMillis()); + apiCall.setResponseBody(JSON.toJSONString(wrapper)); + } + } else { + // 空响应体,设置为空JSON对象 + apiCall.setResponseBody("{}"); + } + apiCall.setResponseHeaders(JSON.toJSONString(response.getHeaders().toSingleValueMap())); cozeApiCallService.updateById(apiCall); } catch (Exception e) {