mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 15:13:55 +08:00
Merge pull request #1248 from xinnan-tech/manager-local-mem
增加智控台管理【本地记忆】功能
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package xiaozhi.common.config;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||
public class AsyncConfig {
|
||||
|
||||
@Bean(name = "taskExecutor")
|
||||
public Executor taskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(2);
|
||||
executor.setMaxPoolSize(4);
|
||||
executor.setQueueCapacity(1000);
|
||||
executor.setThreadNamePrefix("AsyncThread-");
|
||||
// 设置拒绝策略:由调用线程执行
|
||||
executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||
try {
|
||||
// 如果线程池已满,则由调用线程执行
|
||||
r.run();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("执行异步任务失败", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
+24
-3
@@ -39,6 +39,7 @@ import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentMemoryDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||
@@ -46,6 +47,7 @@ import xiaozhi.modules.agent.service.AgentChatAudioService;
|
||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||
import xiaozhi.modules.agent.service.AgentService;
|
||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
|
||||
@@ -109,6 +111,7 @@ public class AgentController {
|
||||
entity.setMemModelId(template.getMemModelId());
|
||||
entity.setIntentModelId(template.getIntentModelId());
|
||||
entity.setSystemPrompt(template.getSystemPrompt());
|
||||
entity.setSummaryMemory(template.getSummaryMemory());
|
||||
entity.setChatHistoryConf(template.getChatHistoryConf());
|
||||
entity.setLangCode(template.getLangCode());
|
||||
entity.setLanguage(template.getLanguage());
|
||||
@@ -126,10 +129,26 @@ public class AgentController {
|
||||
return new Result<String>().ok(entity.getId());
|
||||
}
|
||||
|
||||
@PutMapping("/saveMemory/{macAddress}")
|
||||
@Operation(summary = "根据设备id更新智能体")
|
||||
public Result<Void> updateByDeviceId(@PathVariable String macAddress, @RequestBody @Valid AgentMemoryDTO dto) {
|
||||
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
||||
if (device == null) {
|
||||
return new Result<>();
|
||||
}
|
||||
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
|
||||
agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
|
||||
return updateAgentById(device.getAgentId(), agentUpdateDTO);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新智能体")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
|
||||
return updateAgentById(id, dto);
|
||||
}
|
||||
|
||||
private Result<Void> updateAgentById(String id, AgentUpdateDTO dto) {
|
||||
// 先查询现有实体
|
||||
AgentEntity existingEntity = agentService.getAgentById(id);
|
||||
if (existingEntity == null) {
|
||||
@@ -167,6 +186,9 @@ public class AgentController {
|
||||
if (dto.getSystemPrompt() != null) {
|
||||
existingEntity.setSystemPrompt(dto.getSystemPrompt());
|
||||
}
|
||||
if (dto.getSummaryMemory() != null) {
|
||||
existingEntity.setSummaryMemory(dto.getSummaryMemory());
|
||||
}
|
||||
if (dto.getChatHistoryConf() != null) {
|
||||
existingEntity.setChatHistoryConf(dto.getChatHistoryConf());
|
||||
}
|
||||
@@ -185,17 +207,16 @@ public class AgentController {
|
||||
existingEntity.setUpdater(user.getId());
|
||||
existingEntity.setUpdatedAt(new Date());
|
||||
|
||||
agentService.updateById(existingEntity);
|
||||
|
||||
// 更新记忆策略
|
||||
if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||
// 删除所有记录
|
||||
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true);
|
||||
existingEntity.setSummaryMemory("");
|
||||
} else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) {
|
||||
// 删除音频数据
|
||||
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false);
|
||||
}
|
||||
|
||||
agentService.updateById(existingEntity);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ public class AgentDTO {
|
||||
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助")
|
||||
private String systemPrompt;
|
||||
|
||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
||||
private String summaryMemory;
|
||||
|
||||
@Schema(description = "最后连接时间", example = "2024-03-20 10:00:00")
|
||||
private Date lastConnectedAt;
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package xiaozhi.modules.agent.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 智能体记忆更新DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "智能体记忆更新对象")
|
||||
public class AgentMemoryDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
||||
private String summaryMemory;
|
||||
}
|
||||
@@ -45,6 +45,10 @@ public class AgentUpdateDTO implements Serializable {
|
||||
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false)
|
||||
private String systemPrompt;
|
||||
|
||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
||||
private String summaryMemory;
|
||||
|
||||
@Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", required = false)
|
||||
private Integer chatHistoryConf;
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ public class AgentEntity {
|
||||
@Schema(description = "角色设定参数")
|
||||
private String systemPrompt;
|
||||
|
||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
||||
private String summaryMemory;
|
||||
|
||||
@Schema(description = "语言编码")
|
||||
private String langCode;
|
||||
|
||||
|
||||
@@ -79,6 +79,10 @@ public class AgentTemplateEntity implements Serializable {
|
||||
*/
|
||||
private String systemPrompt;
|
||||
|
||||
/**
|
||||
* 总结记忆
|
||||
*/
|
||||
private String summaryMemory;
|
||||
/**
|
||||
* 语言编码
|
||||
*/
|
||||
|
||||
+4
@@ -65,6 +65,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
agent.getVadModelId(),
|
||||
agent.getAsrModelId(),
|
||||
null,
|
||||
@@ -134,6 +135,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
buildModuleConfig(
|
||||
agent.getAgentName(),
|
||||
agent.getSystemPrompt(),
|
||||
agent.getSummaryMemory(),
|
||||
voice,
|
||||
agent.getVadModelId(),
|
||||
agent.getAsrModelId(),
|
||||
@@ -234,6 +236,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
private void buildModuleConfig(
|
||||
String assistantName,
|
||||
String prompt,
|
||||
String summaryMemory,
|
||||
String voice,
|
||||
String vadModelId,
|
||||
String asrModelId,
|
||||
@@ -294,5 +297,6 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
prompt = prompt.replace("{{assistant_name}}", StringUtils.isBlank(assistantName) ? "小智" : assistantName);
|
||||
}
|
||||
result.put("prompt", prompt);
|
||||
result.put("summaryMemory", summaryMemory);
|
||||
}
|
||||
}
|
||||
|
||||
+26
-7
@@ -9,6 +9,8 @@ import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.aop.framework.AopContext;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
@@ -54,6 +56,24 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
private final RedisUtils redisUtils;
|
||||
private final OtaService otaService;
|
||||
|
||||
@Async
|
||||
public void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion) {
|
||||
try {
|
||||
DeviceEntity device = new DeviceEntity();
|
||||
device.setId(deviceId);
|
||||
device.setLastConnectedAt(new Date());
|
||||
if (StringUtils.isNotBlank(appVersion)) {
|
||||
device.setAppVersion(appVersion);
|
||||
}
|
||||
deviceDao.updateById(device);
|
||||
if (StringUtils.isNotBlank(agentId)) {
|
||||
redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), new Date());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("异步更新设备连接信息失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deviceActivation(String agentId, String activationCode) {
|
||||
if (StringUtils.isBlank(activationCode)) {
|
||||
@@ -156,13 +176,12 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
response.setWebsocket(websocket);
|
||||
|
||||
if (deviceById != null) {
|
||||
// 如果设备存在,则更新上次连接时间
|
||||
deviceById.setLastConnectedAt(new Date());
|
||||
if (deviceReport.getApplication() != null
|
||||
&& StringUtils.isNotBlank(deviceReport.getApplication().getVersion())) {
|
||||
deviceById.setAppVersion(deviceReport.getApplication().getVersion());
|
||||
}
|
||||
deviceDao.updateById(deviceById);
|
||||
// 如果设备存在,则异步更新上次连接时间和版本信息
|
||||
String appVersion = deviceReport.getApplication() != null ? deviceReport.getApplication().getVersion()
|
||||
: null;
|
||||
// 通过Spring代理调用异步方法
|
||||
((DeviceServiceImpl) AopContext.currentProxy()).updateDeviceConnectionInfo(deviceById.getAgentId(),
|
||||
deviceById.getId(), appVersion);
|
||||
} else {
|
||||
// 如果设备不存在,则生成激活码
|
||||
DeviceReportRespDTO.Activation code = buildActivation(macAddress, deviceReport);
|
||||
|
||||
@@ -86,6 +86,7 @@ public class ShiroConfig {
|
||||
// 将config路径使用server服务过滤器
|
||||
filterMap.put("/config/**", "server");
|
||||
filterMap.put("/agent/chat-history/report", "server");
|
||||
filterMap.put("/agent/saveMemory/**", "server");
|
||||
filterMap.put("/agent/play/**", "anon");
|
||||
filterMap.put("/**", "oauth2");
|
||||
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 添加总结记忆字段
|
||||
ALTER TABLE `ai_agent`
|
||||
ADD COLUMN `summary_memory` text COMMENT '总结记忆' AFTER `system_prompt`;
|
||||
|
||||
ALTER TABLE `ai_agent_template`
|
||||
ADD COLUMN `summary_memory` text COMMENT '总结记忆' AFTER `system_prompt`;
|
||||
@@ -0,0 +1,7 @@
|
||||
update ai_agent_template set system_prompt = replace(system_prompt, '我是', '你是');
|
||||
|
||||
delete from sys_params where id in (500,501,402);
|
||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (500, 'end_prompt.enable', 'true', 'boolean', 1, '是否开启结束语');
|
||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (501, 'end_prompt.prompt', '请你以“时间过得真快”未来头,用富有感情、依依不舍的话来结束这场对话吧!', 'string', 1, '结束提示词');
|
||||
|
||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (402, 'plugins.get_weather.api_host', 'mj7p3y7naa.re.qweatherapi.com', 'string', 1, '开发者apihost');
|
||||
@@ -128,3 +128,17 @@ databaseChangeLog:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202505111914.sql
|
||||
- changeSet:
|
||||
id: 202505122348
|
||||
author: ljwwd2
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202505122348.sql
|
||||
- changeSet:
|
||||
id: 202505142037
|
||||
author: hrz
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202505142037.sql
|
||||
|
||||
Reference in New Issue
Block a user