mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
Merge pull request #3090 from xinnan-tech/slm-and-title
update:添加SLM模型配置和会话标题功能
This commit is contained in:
@@ -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";
|
||||
|
||||
/**
|
||||
* 火山引擎双声道语音克隆
|
||||
*/
|
||||
|
||||
@@ -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<AgentChatTitleEntity> {
|
||||
|
||||
}
|
||||
@@ -23,4 +23,9 @@ public class AgentChatSessionDTO {
|
||||
* 聊天条数
|
||||
*/
|
||||
private Integer chatCount;
|
||||
|
||||
/**
|
||||
* 会话标题
|
||||
*/
|
||||
private String title;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -37,6 +37,9 @@ public class AgentEntity {
|
||||
@Schema(description = "大语言模型标识")
|
||||
private String llmModelId;
|
||||
|
||||
@Schema(description = "小模型标识")
|
||||
private String slmModelId;
|
||||
|
||||
@Schema(description = "VLLM模型标识")
|
||||
private String vllmModelId;
|
||||
|
||||
|
||||
+10
@@ -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);
|
||||
}
|
||||
+7
-1
@@ -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<AiAgentChatHistoryDao, AgentChatHistoryEntity>
|
||||
implements AgentChatHistoryService {
|
||||
|
||||
private final AgentChatTitleService agentChatTitleService;
|
||||
|
||||
@Override
|
||||
public PageData<AgentChatSessionDTO> getSessionListByAgentId(Map<String, Object> params) {
|
||||
String agentId = (String) params.get("agentId");
|
||||
@@ -61,6 +66,7 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
||||
dto.setSessionId((String) map.get("session_id"));
|
||||
dto.setCreatedAt((LocalDateTime) map.get("created_at"));
|
||||
dto.setChatCount(((Number) map.get("chat_count")).intValue());
|
||||
dto.setTitle(agentChatTitleService.getTitleBySessionId(dto.getSessionId()));
|
||||
return dto;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
@@ -91,7 +97,7 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
||||
if (ToolUtil.isNotEmpty(audioIds)) {
|
||||
// 每批删除1000条
|
||||
List<List<String>> batch = ListUtil.split(audioIds, 1000);
|
||||
batch.forEach(dataList->{
|
||||
batch.forEach(dataList -> {
|
||||
baseMapper.deleteAudioByIds(dataList);
|
||||
});
|
||||
}
|
||||
|
||||
+107
-26
@@ -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<AgentChatHistoryDTO> chatHistory = getChatHistoryBySessionId(sessionId);
|
||||
if (chatHistory == null || chatHistory.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> 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<ModelConfigEntity> 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("总结生成失败")) {
|
||||
|
||||
+60
@@ -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<AgentChatTitleEntity> 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<AgentChatTitleEntity> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("session_id", sessionId);
|
||||
|
||||
AgentChatTitleEntity entity = agentChatTitleDao.selectOne(wrapper);
|
||||
return entity != null ? entity.getTitle() : null;
|
||||
}
|
||||
}
|
||||
+29
@@ -300,6 +300,9 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> 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<AgentDao, AgentEntity> 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<AgentDao, AgentEntity> imp
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
private String getDefaultLLMModelId() {
|
||||
try {
|
||||
List<ModelConfigEntity> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
+89
@@ -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<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
||||
|
||||
Map<String, Object>[] messages = new Map[1];
|
||||
Map<String, Object> 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<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
String apiUrl = baseUrl;
|
||||
if (!apiUrl.endsWith("/chat/completions")) {
|
||||
if (!apiUrl.endsWith("/")) {
|
||||
apiUrl += "/";
|
||||
}
|
||||
apiUrl += "chat/completions";
|
||||
}
|
||||
|
||||
ResponseEntity<String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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='智能体聊天标题表';
|
||||
@@ -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
|
||||
@@ -14,6 +14,8 @@
|
||||
<result column="asrModelId" property="asrModelId"/>
|
||||
<result column="vadModelId" property="vadModelId"/>
|
||||
<result column="llmModelId" property="llmModelId"/>
|
||||
<result column="slmModelId" property="slmModelId"/>
|
||||
<result column="vllmModelId" property="vllmModelId"/>
|
||||
<result column="ttsModelId" property="ttsModelId"/>
|
||||
<result column="ttsVoiceId" property="ttsVoiceId"/>
|
||||
<result column="ttsLanguage" property="ttsLanguage"/>
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user