feat/multi-channel-framework [AC-INIT]合并功能代码 #12

Merged
MerCry merged 37 commits from feat/multi-channel-framework into main 2026-02-24 03:55:00 +00:00
5 changed files with 32 additions and 248 deletions
Showing only changes of commit 4b1fcf453f - Show all commits

View File

@ -1,30 +0,0 @@
package com.wecom.robot.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "ai")
public class AiConfig {
private boolean enabled;
private String provider;
private DeepSeekConfig deepseek;
private OpenAiConfig openai;
@Data
public static class DeepSeekConfig {
private String apiKey;
private String baseUrl;
private String model;
}
@Data
public static class OpenAiConfig {
private String apiKey;
private String baseUrl;
private String model;
}
}

View File

@ -2,9 +2,7 @@ package com.wecom.robot.controller;
import com.wecom.robot.config.WecomConfig; import com.wecom.robot.config.WecomConfig;
import com.wecom.robot.dto.ApiResponse; import com.wecom.robot.dto.ApiResponse;
import com.wecom.robot.dto.ChatCompletionRequest;
import com.wecom.robot.entity.Message; import com.wecom.robot.entity.Message;
import com.wecom.robot.service.AiService;
import com.wecom.robot.service.SessionManagerService; import com.wecom.robot.service.SessionManagerService;
import com.wecom.robot.util.WXBizMsgCrypt; import com.wecom.robot.util.WXBizMsgCrypt;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -23,7 +21,6 @@ import java.util.Map;
public class DebugController { public class DebugController {
private final WecomConfig wecomConfig; private final WecomConfig wecomConfig;
private final AiService aiService;
private final SessionManagerService sessionManagerService; private final SessionManagerService sessionManagerService;
@GetMapping("/config") @GetMapping("/config")
@ -108,38 +105,13 @@ public class DebugController {
return ApiResponse.success(result); return ApiResponse.success(result);
} }
@GetMapping("/ai/context")
public ApiResponse<Map<String, Object>> getLastAiContext() {
Map<String, Object> result = new HashMap<>();
result.put("systemPrompt", aiService.getSystemPrompt());
result.put("lastRequestJson", aiService.getLastRequestJson());
result.put("lastConfidence", aiService.getLastConfidence());
List<ChatCompletionRequest.Message> messages = aiService.getLastRequestMessages();
result.put("messageCount", messages.size());
List<Map<String, String>> messageList = new java.util.ArrayList<>();
for (ChatCompletionRequest.Message msg : messages) {
Map<String, String> msgMap = new HashMap<>();
msgMap.put("role", msg.getRole());
msgMap.put("content", msg.getContent());
messageList.add(msgMap);
}
result.put("messages", messageList);
return ApiResponse.success(result);
}
@GetMapping("/ai/session/{sessionId}/context") @GetMapping("/ai/session/{sessionId}/context")
public ApiResponse<Map<String, Object>> getSessionAiContext( public ApiResponse<Map<String, Object>> getSessionAiContext(
@PathVariable String sessionId, @PathVariable String sessionId) {
@RequestParam(required = false, defaultValue = "测试消息") String testMessage) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("sessionId", sessionId); result.put("sessionId", sessionId);
result.put("systemPrompt", aiService.getSystemPrompt());
List<Message> history = sessionManagerService.getSessionMessages(sessionId); List<Message> history = sessionManagerService.getSessionMessages(sessionId);
result.put("historyCount", history.size()); result.put("historyCount", history.size());
@ -156,18 +128,6 @@ public class DebugController {
} }
result.put("history", historyList); result.put("history", historyList);
List<ChatCompletionRequest.Message> contextMessages = aiService.buildMessagesForTest(history, testMessage);
result.put("contextMessageCount", contextMessages.size());
List<Map<String, String>> contextList = new java.util.ArrayList<>();
for (ChatCompletionRequest.Message msg : contextMessages) {
Map<String, String> msgMap = new HashMap<>();
msgMap.put("role", msg.getRole());
msgMap.put("content", msg.getContent());
contextList.add(msgMap);
}
result.put("contextMessages", contextList);
return ApiResponse.success(result); return ApiResponse.success(result);
} }

View File

@ -1,155 +0,0 @@
package com.wecom.robot.service;
import com.alibaba.fastjson.JSON;
import com.wecom.robot.config.AiConfig;
import com.wecom.robot.dto.ChatCompletionRequest;
import com.wecom.robot.dto.ChatCompletionResponse;
import com.wecom.robot.entity.Message;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class AiService {
private static final String SYSTEM_PROMPT = "你是ash超脑的客服请用简洁、友好的语言回答客户的问题。" +
"如果不确定答案,请诚实告知客户。" +
"回答要准确、专业,避免过于冗长,要尽量拟人化口语化,自然回答问题。";
private final AiConfig aiConfig;
private final RestTemplate restTemplate = new RestTemplate();
private double lastConfidence = 1.0;
private List<ChatCompletionRequest.Message> lastRequestMessages = new ArrayList<>();
private String lastRequestJson = "";
public String generateReply(String userMessage, List<Message> history) {
if (!aiConfig.isEnabled()) {
return "AI服务暂未开启请联系人工客服。";
}
try {
String provider = aiConfig.getProvider();
String apiUrl;
String apiKey;
String model;
if ("deepseek".equalsIgnoreCase(provider)) {
apiUrl = aiConfig.getDeepseek().getBaseUrl() + "/chat/completions";
apiKey = aiConfig.getDeepseek().getApiKey();
model = aiConfig.getDeepseek().getModel();
} else {
apiUrl = aiConfig.getOpenai().getBaseUrl() + "/chat/completions";
apiKey = aiConfig.getOpenai().getApiKey();
model = aiConfig.getOpenai().getModel();
}
List<ChatCompletionRequest.Message> messages = buildMessages(history, userMessage);
ChatCompletionRequest request = ChatCompletionRequest.create(model, messages);
lastRequestMessages = messages;
lastRequestJson = JSON.toJSONString(request, true);
log.info("===== AI请求上下文 =====");
log.info("Provider: {}, Model: {}", provider, model);
log.info("API URL: {}", apiUrl);
log.info("消息数量: {}", messages.size());
for (int i = 0; i < messages.size(); i++) {
ChatCompletionRequest.Message msg = messages.get(i);
log.info("[{}] {}: {}", i, msg.getRole(), msg.getContent());
}
log.info("===== 完整请求JSON =====");
log.info("\n{}", lastRequestJson);
log.info("========================");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(apiKey);
HttpEntity<String> entity = new HttpEntity<>(JSON.toJSONString(request), headers);
ResponseEntity<String> response = restTemplate.postForEntity(apiUrl, entity, String.class);
log.info("===== AI响应 =====");
log.info("Status: {}", response.getStatusCode());
log.info("Body: {}", response.getBody());
log.info("==================");
ChatCompletionResponse completionResponse = JSON.parseObject(response.getBody(), ChatCompletionResponse.class);
String reply = completionResponse.getContent();
lastConfidence = calculateConfidence(reply);
log.info("AI回复生成成功: confidence={}, reply={}", lastConfidence, reply);
return reply;
} catch (Exception e) {
log.error("AI回复生成失败", e);
lastConfidence = 0.0;
return "抱歉,我暂时无法回答您的问题,正在为您转接人工客服...";
}
}
public double getLastConfidence() {
return lastConfidence;
}
public List<ChatCompletionRequest.Message> getLastRequestMessages() {
return lastRequestMessages;
}
public String getLastRequestJson() {
return lastRequestJson;
}
public String getSystemPrompt() {
return SYSTEM_PROMPT;
}
public List<ChatCompletionRequest.Message> buildMessagesForTest(List<Message> history, String currentMessage) {
return buildMessages(history, currentMessage);
}
private List<ChatCompletionRequest.Message> buildMessages(List<Message> history, String currentMessage) {
List<ChatCompletionRequest.Message> messages = new ArrayList<>();
messages.add(new ChatCompletionRequest.Message("system", SYSTEM_PROMPT));
int startIndex = Math.max(0, history.size() - 10);
for (int i = startIndex; i < history.size(); i++) {
Message msg = history.get(i);
String role = Message.SENDER_TYPE_CUSTOMER.equals(msg.getSenderType()) ? "user" : "assistant";
messages.add(new ChatCompletionRequest.Message(role, msg.getContent()));
}
messages.add(new ChatCompletionRequest.Message("user", currentMessage));
return messages;
}
private double calculateConfidence(String reply) {
if (reply == null || reply.isEmpty()) {
return 0.0;
}
if (reply.contains("不确定") || reply.contains("不清楚") || reply.contains("无法回答")) {
return 0.4;
}
if (reply.contains("转接人工") || reply.contains("人工客服")) {
return 0.5;
}
if (reply.length() < 10) {
return 0.6;
}
return 0.85;
}
}

View File

@ -31,7 +31,6 @@ public class MessageProcessService {
private static final String CHANNEL_TYPE = "wechat"; private static final String CHANNEL_TYPE = "wechat";
private final SessionManagerService sessionManagerService; private final SessionManagerService sessionManagerService;
private final AiService aiService;
private final TransferService transferService; private final TransferService transferService;
private final WecomApiService wecomApiService; private final WecomApiService wecomApiService;
private final WebSocketService webSocketService; private final WebSocketService webSocketService;

View File

@ -4,6 +4,8 @@ import com.wecom.robot.adapter.ChannelAdapter;
import com.wecom.robot.adapter.TransferCapable; import com.wecom.robot.adapter.TransferCapable;
import com.wecom.robot.dto.InboundMessage; import com.wecom.robot.dto.InboundMessage;
import com.wecom.robot.dto.OutboundMessage; import com.wecom.robot.dto.OutboundMessage;
import com.wecom.robot.dto.ai.ChatRequest;
import com.wecom.robot.dto.ai.ChatResponse;
import com.wecom.robot.entity.Message; import com.wecom.robot.entity.Message;
import com.wecom.robot.entity.Session; import com.wecom.robot.entity.Session;
import com.wecom.robot.service.*; import com.wecom.robot.service.*;
@ -17,14 +19,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/**
* 消息路由服务实现 - 渠道无关的消息路由核心逻辑
*
* <p>关联 AC: [AC-MCA-08] 统一消息路由, [AC-MCA-09] 状态路由, [AC-MCA-10] 人工客服分发
*
* @see MessageRouterService
* @see InboundMessage
*/
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -34,7 +28,7 @@ public class MessageRouterServiceImpl implements MessageRouterService {
private static final long IDEMPOTENT_TTL_HOURS = 1; private static final long IDEMPOTENT_TTL_HOURS = 1;
private final SessionManagerService sessionManagerService; private final SessionManagerService sessionManagerService;
private final AiService aiService; private final AiServiceClient aiServiceClient;
private final TransferService transferService; private final TransferService transferService;
private final WebSocketService webSocketService; private final WebSocketService webSocketService;
private final Map<String, ChannelAdapter> channelAdapters; private final Map<String, ChannelAdapter> channelAdapters;
@ -95,20 +89,36 @@ public class MessageRouterServiceImpl implements MessageRouterService {
session.getSessionId(), truncateContent(message.getContent())); session.getSessionId(), truncateContent(message.getContent()));
List<Message> history = sessionManagerService.getSessionMessages(session.getSessionId()); List<Message> history = sessionManagerService.getSessionMessages(session.getSessionId());
String reply = aiService.generateReply(message.getContent(), history);
double confidence = aiService.getLastConfidence(); ChatRequest chatRequest = ChatRequest.fromInboundMessage(message);
ChatResponse chatResponse;
try {
chatResponse = aiServiceClient.generateReply(chatRequest).get();
} catch (Exception e) {
log.error("[AC-MCA-06] AI服务调用失败: {}", e.getMessage());
chatResponse = ChatResponse.fallbackWithTransfer(
"抱歉,我暂时无法回答您的问题,正在为您转接人工客服...",
e.getMessage()
);
}
String reply = chatResponse.getReply();
double confidence = chatResponse.getConfidence() != null ? chatResponse.getConfidence() : 0.0;
int messageCount = sessionManagerService.getMessageCount(session.getSessionId()); int messageCount = sessionManagerService.getMessageCount(session.getSessionId());
boolean shouldTransfer = transferService.shouldTransferToManual( boolean shouldTransfer = chatResponse.getShouldTransfer() != null && chatResponse.getShouldTransfer();
message.getContent(),
confidence, if (!shouldTransfer) {
messageCount, shouldTransfer = transferService.shouldTransferToManual(
session.getCreatedAt() message.getContent(),
); confidence,
messageCount,
session.getCreatedAt()
);
}
if (shouldTransfer) { if (shouldTransfer) {
handleTransferToManual(session, message, reply); handleTransferToManual(session, message, reply, chatResponse.getTransferReason());
} else { } else {
sendReplyToUser(session, message, reply); sendReplyToUser(session, message, reply);
} }
@ -177,10 +187,10 @@ public class MessageRouterServiceImpl implements MessageRouterService {
); );
} }
private void handleTransferToManual(Session session, InboundMessage message, String reply) { private void handleTransferToManual(Session session, InboundMessage message, String reply, String transferReason) {
String reason = transferService.getTransferReason( String reason = transferReason != null ? transferReason : transferService.getTransferReason(
message.getContent(), message.getContent(),
aiService.getLastConfidence(), 0.0,
sessionManagerService.getMessageCount(session.getSessionId()) sessionManagerService.getMessageCount(session.getSessionId())
); );