From b9a1b154b75c38ce1c3c74119e9fc8cb85720196 Mon Sep 17 00:00:00 2001 From: rainv123 <2148537152@qq.com> Date: Thu, 9 Apr 2026 09:23:54 +0800 Subject: [PATCH] =?UTF-8?q?update:=E6=B7=BB=E5=8A=A0SLM=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=92=8C=E4=BC=9A=E8=AF=9D=E6=A0=87=E9=A2=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xiaozhi/common/constant/Constant.java | 10 ++ .../modules/agent/dao/AgentChatTitleDao.java | 12 ++ .../agent/dto/AgentChatSessionDTO.java | 5 + .../modules/agent/dto/AgentUpdateDTO.java | 3 + .../agent/entity/AgentChatTitleEntity.java | 36 +++++ .../modules/agent/entity/AgentEntity.java | 3 + .../agent/service/AgentChatTitleService.java | 10 ++ .../impl/AgentChatHistoryServiceImpl.java | 8 +- .../impl/AgentChatSummaryServiceImpl.java | 133 ++++++++++++++---- .../impl/AgentChatTitleServiceImpl.java | 60 ++++++++ .../agent/service/impl/AgentServiceImpl.java | 29 ++++ .../modules/llm/service/LLMService.java | 9 ++ .../impl/OpenAIStyleLLMServiceImpl.java | 89 ++++++++++++ .../resources/db/changelog/202604011545.sql | 16 +++ .../db/changelog/db.changelog-master.yaml | 7 + .../main/resources/mapper/agent/AgentDao.xml | 3 + 16 files changed, 406 insertions(+), 27 deletions(-) create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentChatTitleDao.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatTitleEntity.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatTitleService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatTitleServiceImpl.java create mode 100644 main/manager-api/src/main/resources/db/changelog/202604011545.sql diff --git a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java index b438fec1..37029501 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java +++ b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java @@ -156,6 +156,16 @@ public interface Constant { */ String MEMORY_MEM_REPORT_ONLY = "Memory_mem_report_only"; + /** + * Mem0AI记忆 + */ + String MEMORY_MEM0AI = "Memory_mem0ai"; + + /** + * PowerMem记忆 + */ + String MEMORY_POWERMEM = "Memory_powermem"; + /** * 火山引擎双声道语音克隆 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentChatTitleDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentChatTitleDao.java new file mode 100644 index 00000000..0ca51d3f --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentChatTitleDao.java @@ -0,0 +1,12 @@ +package xiaozhi.modules.agent.dao; + +import org.apache.ibatis.annotations.Mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import xiaozhi.modules.agent.entity.AgentChatTitleEntity; + +@Mapper +public interface AgentChatTitleDao extends BaseMapper { + +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java index c65f05c6..841b6976 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java @@ -23,4 +23,9 @@ public class AgentChatSessionDTO { * 聊天条数 */ private Integer chatCount; + + /** + * 会话标题 + */ + private String title; } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java index 8f6dead8..13d95ddc 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java @@ -33,6 +33,9 @@ public class AgentUpdateDTO implements Serializable { @Schema(description = "大语言模型标识", example = "llm_model_02", nullable = true) private String llmModelId; + @Schema(description = "小模型标识", example = "slm_model_02", nullable = true) + private String slmModelId; + @Schema(description = "VLLM模型标识", example = "vllm_model_02", required = false) private String vllmModelId; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatTitleEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatTitleEntity.java new file mode 100644 index 00000000..d9361366 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatTitleEntity.java @@ -0,0 +1,36 @@ +package xiaozhi.modules.agent.entity; + +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "ai_agent_chat_title") +public class AgentChatTitleEntity { + + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + @TableField(value = "session_id") + private String sessionId; + + @TableField(value = "title") + private String title; + + @TableField(value = "created_at") + private Date createdAt; + + @TableField(value = "updated_at") + private Date updatedAt; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java index dff620ca..ce752b01 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java @@ -37,6 +37,9 @@ public class AgentEntity { @Schema(description = "大语言模型标识") private String llmModelId; + @Schema(description = "小模型标识") + private String slmModelId; + @Schema(description = "VLLM模型标识") private String vllmModelId; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatTitleService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatTitleService.java new file mode 100644 index 00000000..71699c58 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatTitleService.java @@ -0,0 +1,10 @@ +package xiaozhi.modules.agent.service; + +import xiaozhi.modules.agent.entity.AgentChatTitleEntity; + +public interface AgentChatTitleService { + + void saveOrUpdateTitle(String sessionId, String title); + + String getTitleBySessionId(String sessionId); +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatHistoryServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatHistoryServiceImpl.java index 494107a2..9cfece89 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatHistoryServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatHistoryServiceImpl.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.stream.Collectors; import cn.hutool.core.collection.ListUtil; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,6 +27,7 @@ import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; import xiaozhi.modules.agent.dto.AgentChatSessionDTO; import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; import xiaozhi.modules.agent.service.AgentChatHistoryService; +import xiaozhi.modules.agent.service.AgentChatTitleService; import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO; /** @@ -36,9 +38,12 @@ import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO; * @since 1.0.0 */ @Service +@RequiredArgsConstructor public class AgentChatHistoryServiceImpl extends ServiceImpl implements AgentChatHistoryService { + private final AgentChatTitleService agentChatTitleService; + @Override public PageData getSessionListByAgentId(Map params) { String agentId = (String) params.get("agentId"); @@ -61,6 +66,7 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl> batch = ListUtil.split(audioIds, 1000); - batch.forEach(dataList->{ + batch.forEach(dataList -> { baseMapper.deleteAudioByIds(dataList); }); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java index 26e601ec..02e54145 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java @@ -22,6 +22,7 @@ import xiaozhi.modules.agent.dto.AgentUpdateDTO; import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; import xiaozhi.modules.agent.service.AgentChatHistoryService; import xiaozhi.modules.agent.service.AgentChatSummaryService; +import xiaozhi.modules.agent.service.AgentChatTitleService; import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.vo.AgentInfoVO; import xiaozhi.modules.device.entity.DeviceEntity; @@ -42,6 +43,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { private final AgentChatHistoryService agentChatHistoryService; private final AgentService agentService; + private final AgentChatTitleService agentChatTitleService; private final DeviceService deviceService; private final LLMService llmService; private final ModelConfigService modelConfigService; @@ -91,40 +93,42 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { @Override public boolean generateAndSaveChatSummary(String sessionId) { try { - // 1. 获取设备信息(通过会话关联的设备) DeviceEntity device = getDeviceBySessionId(sessionId); if (device == null) { log.info("未找到与会话 {} 关联的设备", sessionId); return false; } - // 2. 检查记忆模型类型,如果是仅上报聊天记录模式则跳过总结 - String memModelId = agentService.getAgentById(device.getAgentId()).getMemModelId(); - if (memModelId != null && memModelId.equals(Constant.MEMORY_MEM_REPORT_ONLY)) { + String agentId = device.getAgentId(); + String memModelId = agentService.getAgentById(agentId).getMemModelId(); + + if (memModelId == null || memModelId.equals(Constant.MEMORY_MEM_REPORT_ONLY)) { log.info("会话 {} 使用仅上报聊天记录模式,跳过记忆总结", sessionId); + generateAndSaveChatTitle(sessionId, agentId); return true; } - // 3. 生成总结 - AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId); - if (!summaryDTO.isSuccess()) { - log.info("生成总结失败: {}", summaryDTO.getErrorMessage()); - return false; - } + boolean shouldSummarizeMemory = !memModelId.equals(Constant.MEMORY_NO_MEM) + && !memModelId.equals(Constant.MEMORY_MEM0AI) + && !memModelId.equals(Constant.MEMORY_POWERMEM); - // 4. 更新智能体记忆 - AgentMemoryDTO memoryDTO = new AgentMemoryDTO(); - memoryDTO.setSummaryMemory(summaryDTO.getSummary()); - - // 调用现有接口更新记忆 - agentService.updateAgentById(device.getAgentId(), - new AgentUpdateDTO() { + if (shouldSummarizeMemory) { + AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId); + if (summaryDTO.isSuccess()) { + agentService.updateAgentById(agentId, new AgentUpdateDTO() { { setSummaryMemory(summaryDTO.getSummary()); } }); + log.info("成功保存会话 {} 的聊天记录总结到智能体 {}", sessionId, agentId); + } else { + log.info("生成总结失败: {}", summaryDTO.getErrorMessage()); + } + } else { + log.info("会话 {} 使用 {} 模式,跳过记忆总结", sessionId, memModelId); + } - log.info("成功保存会话 {} 的聊天记录总结到智能体 {}", sessionId, device.getAgentId()); + generateAndSaveChatTitle(sessionId, agentId); return true; } catch (Exception e) { @@ -133,6 +137,87 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { } } + private void generateAndSaveChatTitle(String sessionId, String agentId) { + try { + List chatHistory = getChatHistoryBySessionId(sessionId); + if (chatHistory == null || chatHistory.isEmpty()) { + return; + } + + List meaningfulMessages = extractMeaningfulMessages(chatHistory); + if (meaningfulMessages.isEmpty()) { + return; + } + + StringBuilder conversation = new StringBuilder(); + for (int i = 0; i < meaningfulMessages.size(); i++) { + conversation.append("消息").append(i + 1).append(": ").append(meaningfulMessages.get(i)).append("\n"); + } + + String slmModelId = getSlmModelId(agentId); + String title = llmService.generateTitle(conversation.toString(), slmModelId); + + if (StringUtils.isNotBlank(title)) { + agentChatTitleService.saveOrUpdateTitle(sessionId, title); + log.info("成功保存会话 {} 的标题: {}", sessionId, title); + } + } catch (Exception e) { + log.error("生成会话 {} 的标题时发生错误: {}", sessionId, e.getMessage()); + } + } + + private String getSlmModelId(String agentId) { + try { + if (StringUtils.isBlank(agentId)) { + return null; + } + + AgentInfoVO agentInfo = agentService.getAgentById(agentId); + if (agentInfo == null) { + return null; + } + + String slmModelId = agentInfo.getSlmModelId(); + if (StringUtils.isNotBlank(slmModelId)) { + log.info("会话 {} 使用SLM模型: {}", agentId, slmModelId); + return slmModelId; + } + + ModelConfigEntity defaultLlmConfig = getDefaultLLMConfig(); + if (defaultLlmConfig != null) { + log.info("会话 {} 使用默认LLM模型: {}", agentId, defaultLlmConfig.getId()); + return defaultLlmConfig.getId(); + } + + String llmModelId = agentInfo.getLlmModelId(); + log.info("会话 {} 使用LLM模型(最终回退): {}", agentId, llmModelId); + return llmModelId; + } catch (Exception e) { + log.error("获取智能体slm模型ID失败,agentId: {}, 错误: {}", agentId, e.getMessage()); + return null; + } + } + + private ModelConfigEntity getDefaultLLMConfig() { + try { + List llmConfigs = modelConfigService.getEnabledModelsByType("LLM"); + if (llmConfigs == null || llmConfigs.isEmpty()) { + return null; + } + + for (ModelConfigEntity config : llmConfigs) { + if (config.getIsDefault() != null && config.getIsDefault() == 1) { + return config; + } + } + + return llmConfigs.get(0); + } catch (Exception e) { + log.error("获取默认LLM配置失败: {}", e.getMessage()); + return null; + } + } + /** * 根据会话ID获取聊天记录 */ @@ -313,15 +398,13 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { */ private String callJavaLLMForSummaryWithHistory(String conversation, String historyMemory, String agentId) { try { - // 获取智能体配置,从中提取记忆总结的模型ID - String modelId = getMemorySummaryModelId(agentId); + String modelId = getSlmModelId(agentId); if (StringUtils.isBlank(modelId)) { - log.info("未找到记忆总结的LLM模型配置,使用默认LLM服务"); + log.info("未找到SLM模型,使用默认LLM服务"); return llmService.generateSummaryWithHistory(conversation, historyMemory, null, null); } - // 使用指定的模型ID调用LLM服务(支持历史记忆合并) String summary = llmService.generateSummaryWithHistory(conversation, historyMemory, null, modelId); if (StringUtils.isNotBlank(summary) && !summary.equals("服务暂不可用") && !summary.equals("总结生成失败")) { @@ -341,15 +424,13 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { */ private String callJavaLLMForSummary(String conversation, String agentId) { try { - // 获取智能体配置,从中提取记忆总结的模型ID - String modelId = getMemorySummaryModelId(agentId); + String modelId = getSlmModelId(agentId); if (StringUtils.isBlank(modelId)) { - log.info("未找到记忆总结的LLM模型配置,使用默认LLM服务"); + log.info("未找到SLM模型,使用默认LLM服务"); return llmService.generateSummary(conversation); } - // 使用指定的模型ID调用LLM服务 String summary = llmService.generateSummaryWithModel(conversation, modelId); if (StringUtils.isNotBlank(summary) && !summary.equals("服务暂不可用") && !summary.equals("总结生成失败")) { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatTitleServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatTitleServiceImpl.java new file mode 100644 index 00000000..b875a073 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatTitleServiceImpl.java @@ -0,0 +1,60 @@ +package xiaozhi.modules.agent.service.impl; + +import java.util.Date; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + +import lombok.RequiredArgsConstructor; +import xiaozhi.modules.agent.dao.AgentChatTitleDao; +import xiaozhi.modules.agent.entity.AgentChatTitleEntity; +import xiaozhi.modules.agent.service.AgentChatTitleService; + +@Service +@RequiredArgsConstructor +public class AgentChatTitleServiceImpl implements AgentChatTitleService { + + private final AgentChatTitleDao agentChatTitleDao; + + @Override + public void saveOrUpdateTitle(String sessionId, String title) { + if (StringUtils.isBlank(sessionId) || StringUtils.isBlank(title)) { + return; + } + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("session_id", sessionId); + + AgentChatTitleEntity existing = agentChatTitleDao.selectOne(wrapper); + + if (existing != null) { + existing.setTitle(title); + existing.setUpdatedAt(new Date()); + agentChatTitleDao.updateById(existing); + } else { + AgentChatTitleEntity newEntity = AgentChatTitleEntity.builder() + .id(java.util.UUID.randomUUID().toString().replace("-", "")) + .sessionId(sessionId) + .title(title) + .createdAt(new Date()) + .updatedAt(new Date()) + .build(); + agentChatTitleDao.insert(newEntity); + } + } + + @Override + public String getTitleBySessionId(String sessionId) { + if (StringUtils.isBlank(sessionId)) { + return null; + } + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("session_id", sessionId); + + AgentChatTitleEntity entity = agentChatTitleDao.selectOne(wrapper); + return entity != null ? entity.getTitle() : null; + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java index 8cf187df..ff8add76 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java @@ -300,6 +300,9 @@ public class AgentServiceImpl extends BaseServiceImpl imp if (dto.getLlmModelId() != null) { existingEntity.setLlmModelId(dto.getLlmModelId()); } + if (dto.getSlmModelId() != null) { + existingEntity.setSlmModelId(dto.getSlmModelId()); + } if (dto.getVllmModelId() != null) { existingEntity.setVllmModelId(dto.getVllmModelId()); } @@ -506,6 +509,13 @@ public class AgentServiceImpl extends BaseServiceImpl imp entity.setLanguage(template.getLanguage()); } + if (entity.getSlmModelId() == null) { + String defaultSlmModelId = getDefaultLLMModelId(); + if (defaultSlmModelId != null) { + entity.setSlmModelId(defaultSlmModelId); + } + } + // 设置用户ID和创建者信息 UserDetail user = SecurityUser.getUser(); entity.setUserId(user.getId()); @@ -544,4 +554,23 @@ public class AgentServiceImpl extends BaseServiceImpl imp return entity.getId(); } + private String getDefaultLLMModelId() { + try { + List llmConfigs = modelConfigService.getEnabledModelsByType("LLM"); + if (llmConfigs == null || llmConfigs.isEmpty()) { + return null; + } + + for (ModelConfigEntity config : llmConfigs) { + if (config.getIsDefault() != null && config.getIsDefault() == 1) { + return config.getId(); + } + } + + return llmConfigs.get(0).getId(); + } catch (Exception e) { + return null; + } + } + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java index a3f10e9f..4436f1ca 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java @@ -67,4 +67,13 @@ public interface LLMService { * @return 是否可用 */ boolean isAvailable(String modelId); + + /** + * 生成会话标题 + * + * @param conversation 对话内容 + * @param modelId 模型ID + * @return 标题(约15字) + */ + String generateTitle(String conversation, String modelId); } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java index 55fda70d..585826f3 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java @@ -37,6 +37,8 @@ public class OpenAIStyleLLMServiceImpl implements LLMService { private static final String DEFAULT_SUMMARY_PROMPT = "你是一个经验丰富的记忆总结者,擅长将对话内容进行总结摘要,遵循以下规则:\n1、总结用户的重要信息,以便在未来的对话中提供更个性化的服务\n2、不要重复总结,不要遗忘之前记忆,除非原来的记忆超过了1800字,否则不要遗忘、不要压缩用户的历史记忆\n3、用户操控的设备音量、播放音乐、天气、退出、不想对话等和用户本身无关的内容,这些信息不需要加入到总结中\n4、聊天内容中的今天的日期时间、今天的天气情况与用户事件无关的数据,这些信息如果当成记忆存储会影响后续对话,这些信息不需要加入到总结中\n5、不要把设备操控的成果结果和失败结果加入到总结中,也不要把用户的一些废话加入到总结中\n6、不要为了总结而总结,如果用户的聊天没有意义,请返回原来的历史记录也是可以的\n7、只需要返回总结摘要,严格控制在1800字内\n8、不要包含代码、xml,不需要解释、注释和说明,保存记忆时仅从对话提取信息,不要混入示例内容\n9、如果提供了历史记忆,请将新对话内容与历史记忆进行智能合并,保留有价值的历史信息,同时添加新的重要信息\n\n历史记忆:\n{history_memory}\n\n新对话内容:\n{conversation}"; + private static final String DEFAULT_TITLE_PROMPT = "请根据以下对话内容,生成一个简洁的会话标题(约15字以内),只返回标题,不要包含任何解释或标点符号:\n{conversation}"; + @Override public String generateSummary(String conversation) { return generateSummary(conversation, null, null); @@ -302,4 +304,91 @@ public class OpenAIStyleLLMServiceImpl implements LLMService { return null; } } + + @Override + public String generateTitle(String conversation, String modelId) { + if (!isAvailable()) { + log.warn("LLM服务不可用,无法生成标题"); + return null; + } + + try { + ModelConfigEntity llmConfig; + if (modelId != null && !modelId.trim().isEmpty()) { + llmConfig = modelConfigService.getModelByIdFromCache(modelId); + } else { + llmConfig = getDefaultLLMConfig(); + } + + if (llmConfig == null || llmConfig.getConfigJson() == null) { + log.error("未找到可用的LLM模型配置,modelId: {}", modelId); + return null; + } + + JSONObject configJson = llmConfig.getConfigJson(); + String baseUrl = configJson.getStr("base_url"); + String model = configJson.getStr("model_name"); + String apiKey = configJson.getStr("api_key"); + + if (StringUtils.isBlank(baseUrl) || StringUtils.isBlank(apiKey)) { + log.error("LLM配置不完整,baseUrl或apiKey为空"); + return null; + } + + String prompt = DEFAULT_TITLE_PROMPT.replace("{conversation}", conversation); + + Map requestBody = new HashMap<>(); + requestBody.put("model", model != null ? model : "gpt-3.5-turbo"); + + Map[] messages = new Map[1]; + Map message = new HashMap<>(); + message.put("role", "user"); + message.put("content", prompt); + messages[0] = message; + + requestBody.put("messages", messages); + requestBody.put("temperature", 0.3); + requestBody.put("max_tokens", 50); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + apiKey); + + HttpEntity> entity = new HttpEntity<>(requestBody, headers); + + String apiUrl = baseUrl; + if (!apiUrl.endsWith("/chat/completions")) { + if (!apiUrl.endsWith("/")) { + apiUrl += "/"; + } + apiUrl += "chat/completions"; + } + + ResponseEntity response = restTemplate.exchange( + apiUrl, HttpMethod.POST, entity, String.class); + + if (response.getStatusCode().is2xxSuccessful()) { + JSONObject responseJson = JSONUtil.parseObj(response.getBody()); + JSONArray choices = responseJson.getJSONArray("choices"); + if (choices != null && choices.size() > 0) { + JSONObject choice = choices.getJSONObject(0); + JSONObject messageObj = choice.getJSONObject("message"); + String title = messageObj.getStr("content"); + if (StringUtils.isNotBlank(title)) { + title = title.trim().replaceAll("[,。!?、:;''\"\"【】()]", ""); + if (title.length() > 15) { + title = title.substring(0, 15); + } + return title; + } + } + } else { + log.error("LLM API调用失败,状态码:{},响应:{}", response.getStatusCode(), response.getBody()); + } + } catch (Exception e) { + log.error("调用LLM服务生成标题时发生异常,modelId: {}", modelId, e); + } + + return null; + } } \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202604011545.sql b/main/manager-api/src/main/resources/db/changelog/202604011545.sql new file mode 100644 index 00000000..1ef5be69 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202604011545.sql @@ -0,0 +1,16 @@ +-- 智能体表添加小模型ID字段 +SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_agent' AND COLUMN_NAME = 'slm_model_id'); +SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_agent` ADD COLUMN `slm_model_id` VARCHAR(255) NULL COMMENT ''小模型ID'' AFTER `llm_model_id`', 'SELECT ''Column slm_model_id already exists'' AS msg'); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +-- 创建聊天标题表 +DROP TABLE IF EXISTS `ai_agent_chat_title`; +CREATE TABLE `ai_agent_chat_title` ( + `id` VARCHAR(32) NOT NULL COMMENT '主键ID', + `session_id` VARCHAR(255) NOT NULL COMMENT '会话ID', + `title` VARCHAR(255) DEFAULT NULL COMMENT '聊天标题', + `created_at` DATETIME DEFAULT NULL COMMENT '创建时间', + `updated_at` DATETIME DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_session_id` (`session_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能体聊天标题表'; diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index 4510fa16..49cf28f4 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -599,3 +599,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202604011035.sql + - changeSet: + id: 202604011545 + author: rainv123 + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202604011545.sql \ No newline at end of file diff --git a/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml b/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml index 9c417909..c250afe3 100644 --- a/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml +++ b/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml @@ -14,6 +14,8 @@ + + @@ -46,6 +48,7 @@ a.asr_model_id AS asrModelId, a.vad_model_id AS vadModelId, a.llm_model_id AS llmModelId, + a.slm_model_id AS slmModelId, a.vllm_model_id AS vllmModelId, a.tts_model_id AS ttsModelId, a.tts_voice_id AS ttsVoiceId,