mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-26 00:53:54 +08:00
Merge remote-tracking branch 'origin/main' into agent-snapshot-history-fixes
This commit is contained in:
+42
-15
@@ -28,6 +28,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
@@ -52,8 +54,6 @@ import xiaozhi.modules.agent.service.AgentService;
|
|||||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO;
|
import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO;
|
||||||
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
|
||||||
@Tag(name = "智能体管理")
|
@Tag(name = "智能体管理")
|
||||||
@@ -61,15 +61,40 @@ import xiaozhi.modules.security.user.SecurityUser;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/agent")
|
@RequestMapping("/agent")
|
||||||
public class AgentController {
|
public class AgentController {
|
||||||
|
private static final long AUDIO_PLAY_TOKEN_EXPIRE_SECONDS = 300L;
|
||||||
|
|
||||||
private final AgentService agentService;
|
private final AgentService agentService;
|
||||||
private final AgentTemplateService agentTemplateService;
|
private final AgentTemplateService agentTemplateService;
|
||||||
private final DeviceService deviceService;
|
|
||||||
private final AgentChatHistoryService agentChatHistoryService;
|
private final AgentChatHistoryService agentChatHistoryService;
|
||||||
private final AgentChatAudioService agentChatAudioService;
|
private final AgentChatAudioService agentChatAudioService;
|
||||||
private final AgentChatSummaryService agentChatSummaryService;
|
private final AgentChatSummaryService agentChatSummaryService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final AgentTagService agentTagService;
|
private final AgentTagService agentTagService;
|
||||||
|
|
||||||
|
private void requireAgentPermission(String agentId) {
|
||||||
|
if (!agentService.checkAgentPermission(agentId, SecurityUser.getUserId())) {
|
||||||
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requireSessionAgent(String sessionId) {
|
||||||
|
String agentId = agentChatHistoryService.getAgentIdBySessionId(sessionId);
|
||||||
|
if (StringUtils.isBlank(agentId)) {
|
||||||
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
agentService.getAgentById(agentId);
|
||||||
|
return agentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requireAudioPermission(String audioId) {
|
||||||
|
String agentId = agentChatHistoryService.getAgentIdByAudioId(audioId);
|
||||||
|
if (StringUtils.isBlank(agentId)) {
|
||||||
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
|
}
|
||||||
|
requireAgentPermission(agentId);
|
||||||
|
return agentId;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "获取用户智能体列表")
|
@Operation(summary = "获取用户智能体列表")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
@@ -100,7 +125,7 @@ public class AgentController {
|
|||||||
@Operation(summary = "获取智能体详情")
|
@Operation(summary = "获取智能体详情")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<AgentInfoVO> getAgentById(@PathVariable("id") String id) {
|
public Result<AgentInfoVO> getAgentById(@PathVariable("id") String id) {
|
||||||
AgentInfoVO agent = agentService.getAgentById(id);
|
AgentInfoVO agent = agentService.getAgentById(id, SecurityUser.getUserId());
|
||||||
return ResultUtils.success(agent);
|
return ResultUtils.success(agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,20 +139,16 @@ public class AgentController {
|
|||||||
|
|
||||||
@PutMapping("/saveMemory/{macAddress}")
|
@PutMapping("/saveMemory/{macAddress}")
|
||||||
@Operation(summary = "根据设备id更新智能体")
|
@Operation(summary = "根据设备id更新智能体")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> updateByDeviceId(@PathVariable String macAddress, @RequestBody @Valid AgentMemoryDTO dto) {
|
public Result<Void> updateByDeviceId(@PathVariable String macAddress, @RequestBody @Valid AgentMemoryDTO dto) {
|
||||||
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
agentService.updateAgentMemoryByDeviceMacAddress(macAddress, dto, SecurityUser.getUserId());
|
||||||
if (device == null) {
|
return new Result<Void>().ok(null);
|
||||||
return new Result<>();
|
|
||||||
}
|
|
||||||
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
|
|
||||||
agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
|
|
||||||
agentService.updateAgentById(device.getAgentId(), agentUpdateDTO, false);
|
|
||||||
return new Result<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/chat-summary/{sessionId}/save")
|
@PostMapping("/chat-summary/{sessionId}/save")
|
||||||
@Operation(summary = "根据会话ID生成聊天记录总结并保存(异步执行)")
|
@Operation(summary = "根据会话ID生成聊天记录总结并保存(异步执行)")
|
||||||
public Result<Void> generateAndSaveChatSummary(@PathVariable String sessionId) {
|
public Result<Void> generateAndSaveChatSummary(@PathVariable String sessionId) {
|
||||||
|
requireSessionAgent(sessionId);
|
||||||
try {
|
try {
|
||||||
// 异步执行总结生成任务,立即返回成功响应
|
// 异步执行总结生成任务,立即返回成功响应
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
@@ -149,6 +170,7 @@ public class AgentController {
|
|||||||
@PostMapping("/chat-title/{sessionId}/generate")
|
@PostMapping("/chat-title/{sessionId}/generate")
|
||||||
@Operation(summary = "根据会话ID生成聊天标题")
|
@Operation(summary = "根据会话ID生成聊天标题")
|
||||||
public Result<Void> generateAndSaveChatTitle(@PathVariable String sessionId) {
|
public Result<Void> generateAndSaveChatTitle(@PathVariable String sessionId) {
|
||||||
|
requireSessionAgent(sessionId);
|
||||||
agentChatSummaryService.generateAndSaveChatTitle(sessionId);
|
agentChatSummaryService.generateAndSaveChatTitle(sessionId);
|
||||||
return new Result<Void>().ok(null);
|
return new Result<Void>().ok(null);
|
||||||
}
|
}
|
||||||
@@ -157,7 +179,7 @@ public class AgentController {
|
|||||||
@Operation(summary = "更新智能体")
|
@Operation(summary = "更新智能体")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
|
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
|
||||||
agentService.updateAgentById(id, dto);
|
agentService.updateAgentById(id, dto, SecurityUser.getUserId());
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +187,7 @@ public class AgentController {
|
|||||||
@Operation(summary = "删除智能体")
|
@Operation(summary = "删除智能体")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> delete(@PathVariable String id) {
|
public Result<Void> delete(@PathVariable String id) {
|
||||||
agentService.deleteAgent(id);
|
agentService.deleteAgentById(id, SecurityUser.getUserId());
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +210,7 @@ public class AgentController {
|
|||||||
public Result<PageData<AgentChatSessionDTO>> getAgentSessions(
|
public Result<PageData<AgentChatSessionDTO>> getAgentSessions(
|
||||||
@PathVariable("id") String id,
|
@PathVariable("id") String id,
|
||||||
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
requireAgentPermission(id);
|
||||||
params.put("agentId", id);
|
params.put("agentId", id);
|
||||||
PageData<AgentChatSessionDTO> page = agentChatHistoryService.getSessionListByAgentId(params);
|
PageData<AgentChatSessionDTO> page = agentChatHistoryService.getSessionListByAgentId(params);
|
||||||
return new Result<PageData<AgentChatSessionDTO>>().ok(page);
|
return new Result<PageData<AgentChatSessionDTO>>().ok(page);
|
||||||
@@ -235,6 +258,7 @@ public class AgentController {
|
|||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<String> getContentByAudioId(
|
public Result<String> getContentByAudioId(
|
||||||
@PathVariable("id") String id) {
|
@PathVariable("id") String id) {
|
||||||
|
requireAudioPermission(id);
|
||||||
// 查询聊天记录
|
// 查询聊天记录
|
||||||
String data = agentChatHistoryService.getContentByAudioId(id);
|
String data = agentChatHistoryService.getContentByAudioId(id);
|
||||||
return new Result<String>().ok(data);
|
return new Result<String>().ok(data);
|
||||||
@@ -244,12 +268,13 @@ public class AgentController {
|
|||||||
@Operation(summary = "获取音频下载ID")
|
@Operation(summary = "获取音频下载ID")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<String> getAudioId(@PathVariable("audioId") String audioId) {
|
public Result<String> getAudioId(@PathVariable("audioId") String audioId) {
|
||||||
|
requireAudioPermission(audioId);
|
||||||
byte[] audioData = agentChatAudioService.getAudio(audioId);
|
byte[] audioData = agentChatAudioService.getAudio(audioId);
|
||||||
if (audioData == null) {
|
if (audioData == null) {
|
||||||
return new Result<String>().error("音频不存在");
|
return new Result<String>().error("音频不存在");
|
||||||
}
|
}
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId);
|
redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId, AUDIO_PLAY_TOKEN_EXPIRE_SECONDS);
|
||||||
return new Result<String>().ok(uuid);
|
return new Result<String>().ok(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,6 +330,7 @@ public class AgentController {
|
|||||||
@Operation(summary = "获取智能体的标签")
|
@Operation(summary = "获取智能体的标签")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<List<AgentTagDTO>> getAgentTags(@PathVariable String id) {
|
public Result<List<AgentTagDTO>> getAgentTags(@PathVariable String id) {
|
||||||
|
requireAgentPermission(id);
|
||||||
List<AgentTagDTO> tags = agentTagService.getTagsByAgentId(id);
|
List<AgentTagDTO> tags = agentTagService.getTagsByAgentId(id);
|
||||||
return new Result<List<AgentTagDTO>>().ok(tags);
|
return new Result<List<AgentTagDTO>>().ok(tags);
|
||||||
}
|
}
|
||||||
@@ -313,6 +339,7 @@ public class AgentController {
|
|||||||
@Operation(summary = "保存智能体的标签")
|
@Operation(summary = "保存智能体的标签")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> saveAgentTags(@PathVariable String id, @RequestBody Map<String, Object> params) {
|
public Result<Void> saveAgentTags(@PathVariable String id, @RequestBody Map<String, Object> params) {
|
||||||
|
requireAgentPermission(id);
|
||||||
List<String> tagIds = (List<String>) params.get("tagIds");
|
List<String> tagIds = (List<String>) params.get("tagIds");
|
||||||
List<String> tagNames = (List<String>) params.get("tagNames");
|
List<String> tagNames = (List<String>) params.get("tagNames");
|
||||||
AgentUpdateDTO dto = new AgentUpdateDTO();
|
AgentUpdateDTO dto = new AgentUpdateDTO();
|
||||||
|
|||||||
+16
@@ -37,6 +37,14 @@ public interface AgentChatHistoryService extends IService<AgentChatHistoryEntity
|
|||||||
*/
|
*/
|
||||||
List<AgentChatHistoryDTO> getChatHistoryBySessionId(String agentId, String sessionId);
|
List<AgentChatHistoryDTO> getChatHistoryBySessionId(String agentId, String sessionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据会话ID获取智能体ID
|
||||||
|
*
|
||||||
|
* @param sessionId 会话ID
|
||||||
|
* @return 智能体ID
|
||||||
|
*/
|
||||||
|
String getAgentIdBySessionId(String sessionId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据智能体ID删除聊天记录
|
* 根据智能体ID删除聊天记录
|
||||||
*
|
*
|
||||||
@@ -62,6 +70,14 @@ public interface AgentChatHistoryService extends IService<AgentChatHistoryEntity
|
|||||||
*/
|
*/
|
||||||
String getContentByAudioId(String audioId);
|
String getContentByAudioId(String audioId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据音频ID获取智能体ID
|
||||||
|
*
|
||||||
|
* @param audioId 音频ID
|
||||||
|
* @return 智能体ID
|
||||||
|
*/
|
||||||
|
String getAgentIdByAudioId(String audioId);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询此音频id是否属于此智能体
|
* 查询此音频id是否属于此智能体
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import xiaozhi.common.page.PageData;
|
|||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentDTO;
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentMemoryDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
@@ -35,6 +36,15 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
*/
|
*/
|
||||||
AgentInfoVO getAgentById(String id);
|
AgentInfoVO getAgentById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获取当前用户有权访问的智能体
|
||||||
|
*
|
||||||
|
* @param id 智能体ID
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
* @return 智能体实体
|
||||||
|
*/
|
||||||
|
AgentInfoVO getAgentById(String id, Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入智能体
|
* 插入智能体
|
||||||
*
|
*
|
||||||
@@ -100,6 +110,32 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
*/
|
*/
|
||||||
void updateAgentById(String agentId, AgentUpdateDTO dto);
|
void updateAgentById(String agentId, AgentUpdateDTO dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前用户有权访问的智能体
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
* @param dto 更新智能体所需的信息
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
*/
|
||||||
|
void updateAgentById(String agentId, AgentUpdateDTO dto, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备MAC地址更新当前用户有权访问的智能体记忆
|
||||||
|
*
|
||||||
|
* @param macAddress 设备MAC地址
|
||||||
|
* @param dto 智能体记忆
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
*/
|
||||||
|
void updateAgentMemoryByDeviceMacAddress(String macAddress, AgentMemoryDTO dto, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除当前用户有权访问的智能体
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
* @param userId 当前用户ID
|
||||||
|
*/
|
||||||
|
void deleteAgentById(String agentId, Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新智能体
|
* 更新智能体
|
||||||
*
|
*
|
||||||
|
|||||||
+26
@@ -88,6 +88,19 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
|||||||
return ConvertUtils.sourceToTarget(historyList, AgentChatHistoryDTO.class);
|
return ConvertUtils.sourceToTarget(historyList, AgentChatHistoryDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAgentIdBySessionId(String sessionId) {
|
||||||
|
if (sessionId == null || sessionId.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AgentChatHistoryEntity entity = baseMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<AgentChatHistoryEntity>()
|
||||||
|
.select(AgentChatHistoryEntity::getAgentId)
|
||||||
|
.eq(AgentChatHistoryEntity::getSessionId, sessionId)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
return entity == null ? null : entity.getAgentId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText) {
|
public void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText) {
|
||||||
@@ -176,6 +189,19 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
|||||||
return agentChatHistoryEntity == null ? null : agentChatHistoryEntity.getContent();
|
return agentChatHistoryEntity == null ? null : agentChatHistoryEntity.getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAgentIdByAudioId(String audioId) {
|
||||||
|
if (audioId == null || audioId.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AgentChatHistoryEntity entity = baseMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<AgentChatHistoryEntity>()
|
||||||
|
.select(AgentChatHistoryEntity::getAgentId)
|
||||||
|
.eq(AgentChatHistoryEntity::getAudioId, audioId)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
return entity == null ? null : entity.getAgentId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAudioOwnedByAgent(String audioId, String agentId) {
|
public boolean isAudioOwnedByAgent(String audioId, String agentId) {
|
||||||
// 查询是否有指定音频id和智能体id的数据,如果有且只有一条说明此数据属性此智能体
|
// 查询是否有指定音频id和智能体id的数据,如果有且只有一条说明此数据属性此智能体
|
||||||
|
|||||||
+109
-23
@@ -33,6 +33,7 @@ import xiaozhi.modules.agent.dao.AgentDao;
|
|||||||
import xiaozhi.modules.agent.dao.AgentTagDao;
|
import xiaozhi.modules.agent.dao.AgentTagDao;
|
||||||
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentDTO;
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentMemoryDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentTagDTO;
|
import xiaozhi.modules.agent.dto.AgentTagDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
|
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
|
||||||
@@ -93,6 +94,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
if (agent == null) {
|
if (agent == null) {
|
||||||
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
requireCurrentUserPermissionIfPresent(agent);
|
||||||
|
|
||||||
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||||
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
|
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
|
||||||
@@ -116,6 +118,65 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgentInfoVO getAgentById(String id, Long userId) {
|
||||||
|
AgentInfoVO agent = getAgentById(id);
|
||||||
|
requireAgentPermission(agent, userId);
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentEntity getAgentEntityOrThrow(String agentId) {
|
||||||
|
AgentEntity agent = agentDao.selectById(agentId);
|
||||||
|
if (agent == null) {
|
||||||
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCurrentUserSuperAdmin() {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
return user != null && Integer.valueOf(SuperAdminEnum.YES.value()).equals(user.getSuperAdmin());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireCurrentUserPermissionIfPresent(AgentEntity agent) {
|
||||||
|
Long userId = SecurityUser.getUserId();
|
||||||
|
if (userId != null) {
|
||||||
|
requireAgentPermission(agent, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasAgentPermission(AgentEntity agent, Long userId) {
|
||||||
|
if (agent == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isCurrentUserSuperAdmin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return userId != null && userId.equals(agent.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireAgentPermission(AgentEntity agent, Long userId) {
|
||||||
|
if (!hasAgentPermission(agent, userId)) {
|
||||||
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasDevicePermission(DeviceEntity device, Long userId) {
|
||||||
|
if (device == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isCurrentUserSuperAdmin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return userId != null && userId.equals(device.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireDevicePermission(DeviceEntity device, Long userId) {
|
||||||
|
if (!hasDevicePermission(device, userId)) {
|
||||||
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean insert(AgentEntity entity) {
|
public boolean insert(AgentEntity entity) {
|
||||||
// 如果ID为空,自动生成一个UUID作为ID
|
// 如果ID为空,自动生成一个UUID作为ID
|
||||||
@@ -275,25 +336,11 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean checkAgentPermission(String agentId, Long userId) {
|
public boolean checkAgentPermission(String agentId, Long userId) {
|
||||||
if (SecurityUser.getUser() == null || SecurityUser.getUser().getId() == null) {
|
AgentEntity agent = agentDao.selectById(agentId);
|
||||||
return false;
|
return hasAgentPermission(agent, userId);
|
||||||
}
|
}
|
||||||
// 获取智能体信息
|
|
||||||
AgentEntity agent = getAgentById(agentId);
|
|
||||||
if (agent == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是超级管理员,直接返回true
|
|
||||||
if (SecurityUser.getUser().getSuperAdmin() == SuperAdminEnum.YES.value()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是智能体的所有者
|
|
||||||
return userId.equals(agent.getUserId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据id更新智能体信息
|
// 根据id更新智能体信息
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@@ -301,13 +348,29 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
updateAgentById(agentId, dto, true);
|
updateAgentById(agentId, dto, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateAgentById(String agentId, AgentUpdateDTO dto, Long userId) {
|
||||||
|
updateAgentById(agentId, dto, userId, true);
|
||||||
|
}
|
||||||
|
|
||||||
// 根据id更新智能体信息
|
// 根据id更新智能体信息
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void updateAgentById(String agentId, AgentUpdateDTO dto, boolean createSnapshot) {
|
public void updateAgentById(String agentId, AgentUpdateDTO dto, boolean createSnapshot) {
|
||||||
if (agentDao.selectByIdForUpdate(agentId) == null) {
|
updateAgentById(agentId, dto, null, createSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAgentById(String agentId, AgentUpdateDTO dto, Long userId, boolean createSnapshot) {
|
||||||
|
AgentEntity lockedAgent = agentDao.selectByIdForUpdate(agentId);
|
||||||
|
if (lockedAgent == null) {
|
||||||
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
if (userId == null) {
|
||||||
|
requireCurrentUserPermissionIfPresent(lockedAgent);
|
||||||
|
} else {
|
||||||
|
requireAgentPermission(lockedAgent, userId);
|
||||||
|
}
|
||||||
|
|
||||||
// 锁定后查询现有实体和关联配置
|
// 锁定后查询现有实体和关联配置
|
||||||
AgentEntity existingEntity = this.getAgentById(agentId);
|
AgentEntity existingEntity = this.getAgentById(agentId);
|
||||||
@@ -320,9 +383,9 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
if (dto.getAgentName() != null) {
|
if (dto.getAgentName() != null) {
|
||||||
existingEntity.setAgentName(dto.getAgentName());
|
existingEntity.setAgentName(dto.getAgentName());
|
||||||
}
|
}
|
||||||
if (dto.getAgentCode() != null) {
|
if (dto.getAgentCode() != null) {
|
||||||
existingEntity.setAgentCode(dto.getAgentCode());
|
existingEntity.setAgentCode(dto.getAgentCode());
|
||||||
}
|
}
|
||||||
if (dto.getAsrModelId() != null) {
|
if (dto.getAsrModelId() != null) {
|
||||||
existingEntity.setAsrModelId(dto.getAsrModelId());
|
existingEntity.setAsrModelId(dto.getAsrModelId());
|
||||||
}
|
}
|
||||||
@@ -479,6 +542,29 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateAgentMemoryByDeviceMacAddress(String macAddress, AgentMemoryDTO dto, Long userId) {
|
||||||
|
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
||||||
|
if (device == null || StringUtils.isBlank(device.getAgentId()) || dto == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requireDevicePermission(device, userId);
|
||||||
|
|
||||||
|
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
|
||||||
|
agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
|
||||||
|
updateAgentById(device.getAgentId(), agentUpdateDTO, userId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteAgentById(String agentId, Long userId) {
|
||||||
|
AgentEntity agent = getAgentEntityOrThrow(agentId);
|
||||||
|
requireAgentPermission(agent, userId);
|
||||||
|
deleteAgent(agentId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证大语言模型和意图识别的参数是否符合匹配
|
* 验证大语言模型和意图识别的参数是否符合匹配
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ Calling unnecessarily is also wrong: User asks ”明天天气” → you call g
|
|||||||
|
|
||||||
<speaker_recognition>
|
<speaker_recognition>
|
||||||
For the input format `{"speaker":"...", "content":"..."}` (speaker = speaker name, content = text):
|
For the input format `{"speaker":"...", "content":"..."}` (speaker = speaker name, content = text):
|
||||||
1. [Identity known] When `speaker` is a specific name, identity has been recognized. On the first turn you must address them naturally and adjust your response style based on their history.
|
1. [Identity known] When `speaker` is a specific name, identity has been recognized — remember who they are. Do not start every reply by addressing them by name; get straight to the content (a natural greeting by name once, at the very start of a conversation, is fine). If they ask about their identity (e.g. "你知道我是谁吗", "还记得我吗"), answer truthfully using their recognized name.
|
||||||
2. [Identity unknown] When `speaker` is `未知说话人`, the system failed to identify the voice. You must NEVER mention the data inside the `speakers_info` tag to the user. Judge from context whether the speaker is the owner or the owner's friend, and keep the conversation natural.
|
2. [Identity unknown] When `speaker` is `未知说话人`, the system failed to identify the voice. You must NEVER mention the data inside the `speakers_info` tag to the user. Judge from context whether the speaker is the owner or the owner's friend, and keep the conversation natural.
|
||||||
</speaker_recognition>
|
</speaker_recognition>
|
||||||
|
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ class ConnectionHandler:
|
|||||||
self.asr_audio = [] # 存储PCM帧列表,供VAD和ASR共享
|
self.asr_audio = [] # 存储PCM帧列表,供VAD和ASR共享
|
||||||
self.asr_audio_queue = queue.Queue()
|
self.asr_audio_queue = queue.Queue()
|
||||||
self.current_speaker = None # 存储当前说话人
|
self.current_speaker = None # 存储当前说话人
|
||||||
|
self.introduced_speakers = set() # 已"首次引入"的说话人,控制只在首轮带名字
|
||||||
|
self.system_introduced_speakers = set() # 已在 system 注入过身份的说话人,控制 system 身份只首轮出现
|
||||||
|
|
||||||
# llm相关变量
|
# llm相关变量
|
||||||
self.dialogue = Dialogue()
|
self.dialogue = Dialogue()
|
||||||
@@ -1120,12 +1122,20 @@ class ConnectionHandler:
|
|||||||
)
|
)
|
||||||
memory_str = future.result()
|
memory_str = future.result()
|
||||||
|
|
||||||
|
# 仅在该说话人首次出现时把身份注入 system,之后靠对话历史首轮保留,
|
||||||
|
# 避免每轮在 system 重复出现名字诱导模型反复称呼
|
||||||
|
speaker_for_system = None
|
||||||
|
cs = (self.current_speaker or "").strip()
|
||||||
|
if cs and cs != "未知说话人" and cs not in self.system_introduced_speakers:
|
||||||
|
self.system_introduced_speakers.add(cs)
|
||||||
|
speaker_for_system = cs
|
||||||
|
|
||||||
if self.intent_type == "function_call" and functions is not None:
|
if self.intent_type == "function_call" and functions is not None:
|
||||||
# 使用支持functions的streaming接口
|
# 使用支持functions的streaming接口
|
||||||
llm_responses = self.llm.response_with_functions(
|
llm_responses = self.llm.response_with_functions(
|
||||||
self.session_id,
|
self.session_id,
|
||||||
self.dialogue.get_llm_dialogue_with_memory(
|
self.dialogue.get_llm_dialogue_with_memory(
|
||||||
memory_str, self.config.get("voiceprint", {})
|
memory_str, self.config.get("voiceprint", {}), speaker_for_system
|
||||||
),
|
),
|
||||||
functions=functions,
|
functions=functions,
|
||||||
)
|
)
|
||||||
@@ -1133,7 +1143,7 @@ class ConnectionHandler:
|
|||||||
llm_responses = self.llm.response(
|
llm_responses = self.llm.response(
|
||||||
self.session_id,
|
self.session_id,
|
||||||
self.dialogue.get_llm_dialogue_with_memory(
|
self.dialogue.get_llm_dialogue_with_memory(
|
||||||
memory_str, self.config.get("voiceprint", {})
|
memory_str, self.config.get("voiceprint", {}), speaker_for_system
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ async def resume_vad_detection(conn: "ConnectionHandler"):
|
|||||||
async def startToChat(conn: "ConnectionHandler", text):
|
async def startToChat(conn: "ConnectionHandler", text):
|
||||||
# 检查输入是否是JSON格式(包含说话人信息)
|
# 检查输入是否是JSON格式(包含说话人信息)
|
||||||
speaker_name = None
|
speaker_name = None
|
||||||
language_tag = None
|
|
||||||
actual_text = text
|
actual_text = text
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -52,12 +51,16 @@ async def startToChat(conn: "ConnectionHandler", text):
|
|||||||
data = json.loads(text)
|
data = json.loads(text)
|
||||||
if "speaker" in data and "content" in data:
|
if "speaker" in data and "content" in data:
|
||||||
speaker_name = data["speaker"]
|
speaker_name = data["speaker"]
|
||||||
language_tag = data["language"]
|
actual_content = data["content"]
|
||||||
actual_text = data["content"]
|
|
||||||
conn.logger.bind(tag=TAG).info(f"解析到说话人信息: {speaker_name}")
|
conn.logger.bind(tag=TAG).info(f"解析到说话人信息: {speaker_name}")
|
||||||
|
|
||||||
# 直接使用JSON格式的文本,不解析
|
# 仅在该说话人首次出现时保留 {"speaker":...} JSON,让模型自然称呼一次;
|
||||||
actual_text = text
|
# 后续轮降为纯文本,避免每轮重复出现名字诱导模型反复称呼
|
||||||
|
if speaker_name not in conn.introduced_speakers:
|
||||||
|
conn.introduced_speakers.add(speaker_name)
|
||||||
|
actual_text = text
|
||||||
|
else:
|
||||||
|
actual_text = actual_content
|
||||||
except (json.JSONDecodeError, KeyError):
|
except (json.JSONDecodeError, KeyError):
|
||||||
# 如果解析失败,继续使用原始文本
|
# 如果解析失败,继续使用原始文本
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -428,4 +428,3 @@ class ASRProvider(ASRProviderBase):
|
|||||||
pass
|
pass
|
||||||
self.forward_task = None
|
self.forward_task = None
|
||||||
self.is_processing = False
|
self.is_processing = False
|
||||||
|
|
||||||
|
|||||||
@@ -43,11 +43,8 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
self.memory_client = None
|
self.memory_client = None
|
||||||
self.enable_user_profile = False
|
self.enable_user_profile = False
|
||||||
self.last_profile_content = "" # Cache for user profile from UserMemory
|
self.last_profile_content = "" # Cache for user profile from UserMemory
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Check if user profile mode is enabled
|
self.enable_user_profile = str(config.get("enable_user_profile", False)).lower() == 'true'
|
||||||
self.enable_user_profile = config.get("enable_user_profile", False)
|
|
||||||
|
|
||||||
# Get configuration parameters
|
# Get configuration parameters
|
||||||
database_provider = config.get("database_provider", "sqlite")
|
database_provider = config.get("database_provider", "sqlite")
|
||||||
llm_provider = config.get("llm_provider", "qwen")
|
llm_provider = config.get("llm_provider", "qwen")
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ class Dialogue:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def get_llm_dialogue_with_memory(
|
def get_llm_dialogue_with_memory(
|
||||||
self, memory_str: str = None, voiceprint_config: dict = None
|
self, memory_str: str = None, voiceprint_config: dict = None,
|
||||||
|
current_speaker: str = None,
|
||||||
) -> List[Dict[str, str]]:
|
) -> List[Dict[str, str]]:
|
||||||
# 构建对话
|
# 构建对话
|
||||||
dialogue = []
|
dialogue = []
|
||||||
@@ -103,50 +104,31 @@ class Dialogue:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if system_message:
|
if system_message:
|
||||||
# 以 <context> 为分界点,拆分静态 system prompt 和动态上下文
|
|
||||||
# 静态部分(规则、身份等)保持不变,可命中前缀缓存
|
|
||||||
# 动态部分(时间、天气、记忆等)作为第二条 system 消息,保持 system 权威性
|
|
||||||
full_prompt = system_message.content
|
full_prompt = system_message.content
|
||||||
context_match = re.search(r"<context>", full_prompt)
|
|
||||||
if context_match:
|
|
||||||
static_part = full_prompt[:context_match.start()]
|
|
||||||
dynamic_part = full_prompt[context_match.start():]
|
|
||||||
else:
|
|
||||||
static_part = full_prompt
|
|
||||||
dynamic_part = ""
|
|
||||||
|
|
||||||
# 第一段:静态 system prompt(前缀缓存可命中)
|
|
||||||
dialogue.append({"role": "system", "content": static_part})
|
|
||||||
|
|
||||||
# 第二段:few-shot 示例(会话内不变,也是缓存前缀的一部分)
|
|
||||||
non_system_messages = [m for m in self.dialogue if m.role != "system"]
|
|
||||||
fewshot_messages = [m for m in non_system_messages if m.is_temporary]
|
|
||||||
complete_fewshot = self._ensure_tool_calls_complete(fewshot_messages)
|
|
||||||
for m in complete_fewshot:
|
|
||||||
self.getMessages(m, dialogue)
|
|
||||||
|
|
||||||
# 第三段:动态上下文 system prompt(时间、记忆、说话人等)
|
|
||||||
# 保持 system 角色以确保模型权威性,不降级为 user
|
|
||||||
if system_message and dynamic_part:
|
|
||||||
# 替换时间占位符
|
# 替换时间占位符
|
||||||
dynamic_part = dynamic_part.replace(
|
full_prompt = full_prompt.replace(
|
||||||
"{{current_time}}", datetime.now().strftime("%H:%M")
|
"{{current_time}}", datetime.now().strftime("%H:%M")
|
||||||
)
|
)
|
||||||
|
|
||||||
# 填充记忆
|
# 填充记忆
|
||||||
if memory_str is not None:
|
if memory_str is not None:
|
||||||
dynamic_part = re.sub(
|
full_prompt = re.sub(
|
||||||
r"<memory>.*?</memory>",
|
r"<memory>.*?</memory>",
|
||||||
f"<memory>\n{memory_str}\n</memory>",
|
f"<memory>\n{memory_str}\n</memory>",
|
||||||
dynamic_part,
|
full_prompt,
|
||||||
flags=re.DOTALL,
|
flags=re.DOTALL,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 追加说话人信息
|
# 追加说话人信息
|
||||||
try:
|
try:
|
||||||
speakers = voiceprint_config.get("speakers", [])
|
current_speaker_name = (current_speaker or "").strip()
|
||||||
if speakers:
|
# 仅在本轮注入了有效身份时才输出 speakers_info,避免列表里的名字每轮
|
||||||
dynamic_part += "\n<speakers_info>"
|
# 重复出现诱导模型反复称呼;后续轮不再注入身份,靠对话历史首轮保留
|
||||||
|
if current_speaker_name and current_speaker_name != "未知说话人":
|
||||||
|
speakers = voiceprint_config.get("speakers", [])
|
||||||
|
speakers_info = "\n<speakers_info>"
|
||||||
|
speakers_info += f"\n当前说话人:{current_speaker_name}"
|
||||||
for speaker_str in speakers:
|
for speaker_str in speakers:
|
||||||
try:
|
try:
|
||||||
parts = speaker_str.split(",", 2)
|
parts = speaker_str.split(",", 2)
|
||||||
@@ -155,16 +137,24 @@ class Dialogue:
|
|||||||
description = (
|
description = (
|
||||||
parts[2].strip() if len(parts) >= 3 else ""
|
parts[2].strip() if len(parts) >= 3 else ""
|
||||||
)
|
)
|
||||||
dynamic_part += f"\n- {name}:{description}"
|
speakers_info += f"\n- {name}:{description}"
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
dynamic_part += "\n</speakers_info>"
|
speakers_info += "\n</speakers_info>"
|
||||||
|
full_prompt += speakers_info
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
dialogue.append({"role": "system", "content": dynamic_part})
|
dialogue.append({"role": "system", "content": full_prompt})
|
||||||
|
|
||||||
# 第四段:实际对话历史(不含 few-shot)
|
# 第二段:few-shot 示例(会话内不变)
|
||||||
|
non_system_messages = [m for m in self.dialogue if m.role != "system"]
|
||||||
|
fewshot_messages = [m for m in non_system_messages if m.is_temporary]
|
||||||
|
complete_fewshot = self._ensure_tool_calls_complete(fewshot_messages)
|
||||||
|
for m in complete_fewshot:
|
||||||
|
self.getMessages(m, dialogue)
|
||||||
|
|
||||||
|
# 第三段:实际对话历史(不含 few-shot)
|
||||||
actual_messages = [m for m in non_system_messages if not m.is_temporary]
|
actual_messages = [m for m in non_system_messages if not m.is_temporary]
|
||||||
complete_actual = self._ensure_tool_calls_complete(actual_messages)
|
complete_actual = self._ensure_tool_calls_complete(actual_messages)
|
||||||
for m in complete_actual:
|
for m in complete_actual:
|
||||||
|
|||||||
Reference in New Issue
Block a user