AI对话bug修复

This commit is contained in:
2025-12-23 23:04:32 +08:00
parent 1eb1a61b0b
commit 700438ca42
2 changed files with 155 additions and 24 deletions
@@ -86,6 +86,11 @@ public class AiChatServiceImpl implements AiChatService {
private static final String USAGE_SCENARIO_SUMMARY = "summary"; private static final String USAGE_SCENARIO_SUMMARY = "summary";
private static final String DEFAULT_ENVIRONMENT = "production"; private static final String DEFAULT_ENVIRONMENT = "production";
/**
* Coze工作流配置键 - 聊天场景
*/
private static final String COZE_CHAT_CONFIG_KEY = "coze.chat.default";
// API 相关常量 // API 相关常量
private static final String CONTENT_KEY = "content"; private static final String CONTENT_KEY = "content";
private static final String ROLE_KEY = "role"; private static final String ROLE_KEY = "role";
@@ -744,23 +749,98 @@ public class AiChatServiceImpl implements AiChatService {
/** /**
* 发送消息到Coze AI(带messageId * 发送消息到Coze AI(带messageId
* 使用通用工作流调用方式,通过配置键获取AI配置
*
* @param conversationId 会话ID
* @param messageId 消息ID
* @param userMessage 用户消息
* @param userId 用户ID
* @return AI回复内容
*/ */
private String sendMessageWithMessageId(String conversationId, String messageId, String userMessage, private String sendMessageWithMessageId(String conversationId, String messageId, String userMessage,
String userId) { String userId) {
log.info("发送消息到Coze AI: conversationId={}, messageId={}, userId={}", conversationId, messageId, userId); log.info("发送消息到Coze AI: conversationId={}, messageId={}, userId={}", conversationId, messageId, userId);
// 创建API调用记录(包含messageId // 1. 获取AI配置
CozeApiCall apiCall = createApiCallRecord(conversationId, messageId, userMessage, userId, "chat"); AiConfig config = aiConfigService.getByConfigKey(COZE_CHAT_CONFIG_KEY);
if (config == null) {
log.error("未找到聊天场景的AI配置或配置已禁用: configKey={}", COZE_CHAT_CONFIG_KEY);
return "抱歉,AI服务暂时不可用,请稍后再试。";
}
// 2. 创建API调用记录(包含conversationId和messageId
CozeApiCall apiCall = createChatWorkflowApiCallRecord(config, conversationId, messageId, userMessage, userId);
try { try {
return executeCozeApiCall(apiCall, conversationId, userMessage, userId); // 3. 构建工作流请求参数
Map<String, Object> parameters = new HashMap<>();
parameters.put("input", userMessage);
parameters.put("user_id", userId);
// 4. 构建工作流请求体
Map<String, Object> requestBody = buildWorkflowRequest(config, parameters, userId);
// 5. 执行工作流调用(带API调用记录)
return executeWorkflowCallWithRecord(config, requestBody, COZE_CHAT_CONFIG_KEY, userId, apiCall);
} catch (Exception e) { } catch (Exception e) {
log.error("发送消息失败", e); log.error("发送消息失败: conversationId={}, messageId={}, error={}",
conversationId, messageId, e.getMessage(), e);
updateApiCallFailure(apiCall, e.getMessage()); updateApiCallFailure(apiCall, e.getMessage());
return "抱歉,AI服务暂时不可用,请稍后再试。"; return "抱歉,AI服务暂时不可用,请稍后再试。";
} }
} }
/**
* 创建聊天工作流API调用记录(包含conversationId和messageId
*
* @param config AI配置
* @param conversationId 会话ID
* @param messageId 消息ID
* @param userMessage 用户消息
* @param userId 用户ID
* @return API调用记录
*/
private CozeApiCall createChatWorkflowApiCallRecord(AiConfig config, String conversationId,
String messageId, String userMessage, String userId) {
CozeApiCall apiCall = CozeApiCall.builder()
.id(snowflakeIdGenerator.nextIdAsString())
.conversationId(conversationId)
.messageId(messageId)
.workflowId(config.getWorkflowId())
.botId(config.getBotId())
.userId(userId)
.requestType("workflow")
.userMessage(userMessage)
.userMessageType("text")
.status("pending")
.startTime(LocalDateTime.now())
.traceId(UUID.randomUUID().toString().replace("-", ""))
.metadata(JSON.toJSONString(Map.of("configKey", COZE_CHAT_CONFIG_KEY)))
.createBy(userId)
.updateBy(userId)
.build();
// 获取客户端信息
try {
HttpServletRequest request = getCurrentRequest();
if (request != null) {
apiCall.setClientIp(getClientIp(request));
apiCall.setUserAgent(request.getHeader("User-Agent"));
apiCall.setSessionId(request.getSession().getId());
}
} catch (Exception e) {
log.warn("获取客户端信息失败: {}", e.getMessage());
}
// 保存API调用记录
cozeApiCallService.save(apiCall);
log.info("创建聊天工作流API调用记录: id={}, conversationId={}, messageId={}, workflowId={}, traceId={}",
apiCall.getId(), conversationId, messageId, config.getWorkflowId(), apiCall.getTraceId());
return apiCall;
}
/** /**
* 执行Coze API调用的公共逻辑 * 执行Coze API调用的公共逻辑
*/ */
+69 -18
View File
@@ -59,6 +59,9 @@ export const useChatStore = defineStore('chat', () => {
) )
}) })
// 已处理的消息ID集合 - 用于防止重复处理
const processedMessageIds = ref<Set<string>>(new Set())
// 处理消息队列 - 批量处理消息以避免竞态条件 // 处理消息队列 - 批量处理消息以避免竞态条件
const processMessageQueue = async () => { const processMessageQueue = async () => {
if (isProcessingQueue.value || messageQueue.value.length === 0) { if (isProcessingQueue.value || messageQueue.value.length === 0) {
@@ -70,11 +73,24 @@ export const useChatStore = defineStore('chat', () => {
// 一次性取出所有待处理消息 // 一次性取出所有待处理消息
const batch = messageQueue.value.splice(0, messageQueue.value.length) const batch = messageQueue.value.splice(0, messageQueue.value.length)
// 批量添加消息到数组 // 过滤掉已存在的消息(基于ID去重)
const newMessages = batch.map(item => item.message) const newMessages = batch
messages.value.push(...newMessages) .map(item => item.message)
.filter(msg => {
// 检查是否已存在于当前消息列表中
const exists = messages.value.some(m => m.id === msg.id)
if (exists) {
console.log(`⚠️ 消息已存在,跳过: ${msg.id}`)
}
return !exists
})
if (newMessages.length > 0) {
messages.value.push(...newMessages)
console.log(`✅ 消息队列处理完成,添加了 ${newMessages.length} 条消息,当前总数: ${messages.value.length}`) console.log(`✅ 消息队列处理完成,添加了 ${newMessages.length} 条消息,当前总数: ${messages.value.length}`)
} else {
console.log(`⏭️ 队列中的消息都已存在,无需添加`)
}
} finally { } finally {
isProcessingQueue.value = false isProcessingQueue.value = false
} }
@@ -82,6 +98,15 @@ export const useChatStore = defineStore('chat', () => {
// 将消息加入队列而不是直接添加 // 将消息加入队列而不是直接添加
const queueMessage = (message: ChatMessage) => { const queueMessage = (message: ChatMessage) => {
// 先检查是否已处理过该消息ID
if (processedMessageIds.value.has(message.id)) {
console.log(`⚠️ 消息已处理过,跳过队列: ${message.id}`)
return
}
// 标记消息ID为已处理
processedMessageIds.value.add(message.id)
messageQueue.value.push({ messageQueue.value.push({
message, message,
timestamp: Date.now() timestamp: Date.now()
@@ -90,11 +115,11 @@ export const useChatStore = defineStore('chat', () => {
} }
// 添加消息 - 现在返回消息但不直接添加到数组 // 添加消息 - 现在返回消息但不直接添加到数组
const addMessage = (message: Omit<ChatMessage, 'id' | 'timestamp'>) => { const addMessage = (message: Omit<ChatMessage, 'id' | 'timestamp'> & { id?: string, timestamp?: string }) => {
const newMessage: ChatMessage = { const newMessage: ChatMessage = {
...message, ...message,
id: Date.now().toString(), id: message.id || Date.now().toString(), // 优先使用传入的ID
timestamp: new Date().toISOString(), timestamp: message.timestamp || new Date().toISOString(), // 优先使用传入的时间戳
status: message.type === 'user' ? 'sending' : 'sent' status: message.type === 'user' ? 'sending' : 'sent'
} }
// 将消息加入队列而不是直接添加 // 将消息加入队列而不是直接添加
@@ -228,12 +253,17 @@ export const useChatStore = defineStore('chat', () => {
// 如果需要过滤特定会话的消息,可以在这里添加过滤逻辑 // 如果需要过滤特定会话的消息,可以在这里添加过滤逻辑
// const sessionMessages = sortedMessages.filter(msg => msg.sessionId === sessionId) // const sessionMessages = sortedMessages.filter(msg => msg.sessionId === sessionId)
// 清空并重新设置已处理消息ID集合
processedMessageIds.value.clear()
sortedMessages.forEach(msg => processedMessageIds.value.add(msg.id))
messages.value = sortedMessages messages.value = sortedMessages
console.log('📨 会话消息加载完成,消息数量:', messages.value.length) console.log('📨 会话消息加载完成,消息数量:', messages.value.length, '已处理ID数量:', processedMessageIds.value.size)
} catch (error) { } catch (error) {
console.error('❌ 加载会话消息失败:', error) console.error('❌ 加载会话消息失败:', error)
messages.value = [] messages.value = []
processedMessageIds.value.clear()
} }
} }
@@ -261,6 +291,9 @@ export const useChatStore = defineStore('chat', () => {
// 清空消息 // 清空消息
const clearMessages = () => { const clearMessages = () => {
messages.value = [] messages.value = []
// 同时清空已处理消息ID集合,避免后续加载时被错误过滤
processedMessageIds.value.clear()
console.log('✅ 消息和已处理ID集合已清空')
} }
// 搜索消息:支持本地搜索和远程搜索 // 搜索消息:支持本地搜索和远程搜索
@@ -390,31 +423,47 @@ export const useChatStore = defineStore('chat', () => {
// 使用优化的排序和去重方法 // 使用优化的排序和去重方法
const sortedMessages = MessageService.sortAndDeduplicateMessages(chatMessages) const sortedMessages = MessageService.sortAndDeduplicateMessages(chatMessages)
// 清空并重新设置已处理消息ID集合
processedMessageIds.value.clear()
sortedMessages.forEach(msg => processedMessageIds.value.add(msg.id))
// 直接设置消息数组(初始加载时不使用队列) // 直接设置消息数组(初始加载时不使用队列)
messages.value = sortedMessages messages.value = sortedMessages
console.log('📝 最近消息已加载并排序,消息总数:', messages.value.length) console.log('📝 最近消息已加载并排序,消息总数:', messages.value.length, '已处理ID数量:', processedMessageIds.value.size)
return chatMessages return chatMessages
} catch (error) { } catch (error) {
console.error('❌ 加载最近消息失败:', error) console.error('❌ 加载最近消息失败:', error)
messages.value = [] messages.value = []
processedMessageIds.value.clear()
return [] return []
} }
} }
// 添加AI回复消息(使用队列处理) // 添加AI回复消息(使用队列处理)
const addAiReplyMessages = async (content: string) => { const addAiReplyMessages = async (wsMessage: WebSocketMessage) => {
// 停止输入状态 // 停止输入状态
isTyping.value = false isTyping.value = false
// 添加AI消息到队列 // 使用后端提供的messageId,确保去重能正常工作
const messageId = wsMessage.messageId || Date.now().toString()
// 检查是否已处理过该消息(基于后端messageId去重)
if (processedMessageIds.value.has(messageId)) {
console.log(`⚠️ AI消息已处理过,跳过: ${messageId}`)
return
}
// 添加AI消息到队列,使用后端的messageId
const aiMessage = addMessage({ const aiMessage = addMessage({
content: content.trim(), id: messageId,
content: wsMessage.content.trim(),
type: 'ai', type: 'ai',
conversationId: currentSession.value?.id conversationId: wsMessage.conversationId || currentSession.value?.id,
timestamp: wsMessage.createTime || new Date().toISOString()
}) })
console.log('✅ AI消息已加入队列,消息ID:', aiMessage.id) console.log('✅ AI消息已加入队列,消息ID:', aiMessage.id, '(后端ID:', wsMessage.messageId, ')')
// 立即处理队列 // 立即处理队列
await processMessageQueue() await processMessageQueue()
@@ -422,13 +471,13 @@ export const useChatStore = defineStore('chat', () => {
// WebSocket消息处理 - 使用队列处理所有消息 // WebSocket消息处理 - 使用队列处理所有消息
let handleWebSocketMessage = async (wsMessage: WebSocketMessage) => { let handleWebSocketMessage = async (wsMessage: WebSocketMessage) => {
console.log('收到WebSocket消息:', wsMessage.type, wsMessage.senderType) console.log('收到WebSocket消息:', wsMessage.type, wsMessage.senderType, '消息ID:', wsMessage.messageId)
switch (wsMessage.type) { switch (wsMessage.type) {
case 'TEXT': case 'TEXT':
if (wsMessage.senderType === 'AI') { if (wsMessage.senderType === 'AI') {
// AI回复消息 - 加入队列处理 // AI回复消息 - 传递完整的wsMessage以获取后端messageId
await addAiReplyMessages(wsMessage.content) await addAiReplyMessages(wsMessage)
} }
break break
@@ -443,8 +492,9 @@ export const useChatStore = defineStore('chat', () => {
break break
case 'ERROR': case 'ERROR':
// 错误消息 - 加入队列处理 // 错误消息 - 加入队列处理,使用后端messageId
addMessage({ addMessage({
id: wsMessage.messageId || Date.now().toString(),
content: wsMessage.content, content: wsMessage.content,
type: 'ai', type: 'ai',
sessionId: currentSession.value?.id sessionId: currentSession.value?.id
@@ -453,8 +503,9 @@ export const useChatStore = defineStore('chat', () => {
break break
case 'SYSTEM': case 'SYSTEM':
// 系统消息 - 加入队列处理 // 系统消息 - 加入队列处理,使用后端messageId
addMessage({ addMessage({
id: wsMessage.messageId || Date.now().toString(),
content: wsMessage.content, content: wsMessage.content,
type: 'ai', type: 'ai',
sessionId: currentSession.value?.id sessionId: currentSession.value?.id