diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java index eb56be23..407a3e45 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java @@ -28,6 +28,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.ErrorCode; +import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; import xiaozhi.common.redis.RedisKeys; 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.vo.AgentChatHistoryUserVO; import xiaozhi.modules.agent.vo.AgentInfoVO; -import xiaozhi.modules.device.entity.DeviceEntity; -import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.security.user.SecurityUser; @Tag(name = "智能体管理") @@ -61,15 +61,40 @@ import xiaozhi.modules.security.user.SecurityUser; @RestController @RequestMapping("/agent") public class AgentController { + private static final long AUDIO_PLAY_TOKEN_EXPIRE_SECONDS = 300L; + private final AgentService agentService; private final AgentTemplateService agentTemplateService; - private final DeviceService deviceService; private final AgentChatHistoryService agentChatHistoryService; private final AgentChatAudioService agentChatAudioService; private final AgentChatSummaryService agentChatSummaryService; private final RedisUtils redisUtils; 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") @Operation(summary = "获取用户智能体列表") @RequiresPermissions("sys:role:normal") @@ -100,7 +125,7 @@ public class AgentController { @Operation(summary = "获取智能体详情") @RequiresPermissions("sys:role:normal") public Result getAgentById(@PathVariable("id") String id) { - AgentInfoVO agent = agentService.getAgentById(id); + AgentInfoVO agent = agentService.getAgentById(id, SecurityUser.getUserId()); return ResultUtils.success(agent); } @@ -114,20 +139,16 @@ public class AgentController { @PutMapping("/saveMemory/{macAddress}") @Operation(summary = "根据设备id更新智能体") + @RequiresPermissions("sys:role:normal") public Result 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()); - agentService.updateAgentById(device.getAgentId(), agentUpdateDTO, false); - return new Result<>(); + agentService.updateAgentMemoryByDeviceMacAddress(macAddress, dto, SecurityUser.getUserId()); + return new Result().ok(null); } @PostMapping("/chat-summary/{sessionId}/save") @Operation(summary = "根据会话ID生成聊天记录总结并保存(异步执行)") public Result generateAndSaveChatSummary(@PathVariable String sessionId) { + requireSessionAgent(sessionId); try { // 异步执行总结生成任务,立即返回成功响应 new Thread(() -> { @@ -149,6 +170,7 @@ public class AgentController { @PostMapping("/chat-title/{sessionId}/generate") @Operation(summary = "根据会话ID生成聊天标题") public Result generateAndSaveChatTitle(@PathVariable String sessionId) { + requireSessionAgent(sessionId); agentChatSummaryService.generateAndSaveChatTitle(sessionId); return new Result().ok(null); } @@ -157,7 +179,7 @@ public class AgentController { @Operation(summary = "更新智能体") @RequiresPermissions("sys:role:normal") public Result update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) { - agentService.updateAgentById(id, dto); + agentService.updateAgentById(id, dto, SecurityUser.getUserId()); return new Result<>(); } @@ -165,7 +187,7 @@ public class AgentController { @Operation(summary = "删除智能体") @RequiresPermissions("sys:role:normal") public Result delete(@PathVariable String id) { - agentService.deleteAgent(id); + agentService.deleteAgentById(id, SecurityUser.getUserId()); return new Result<>(); } @@ -188,6 +210,7 @@ public class AgentController { public Result> getAgentSessions( @PathVariable("id") String id, @Parameter(hidden = true) @RequestParam Map params) { + requireAgentPermission(id); params.put("agentId", id); PageData page = agentChatHistoryService.getSessionListByAgentId(params); return new Result>().ok(page); @@ -235,6 +258,7 @@ public class AgentController { @RequiresPermissions("sys:role:normal") public Result getContentByAudioId( @PathVariable("id") String id) { + requireAudioPermission(id); // 查询聊天记录 String data = agentChatHistoryService.getContentByAudioId(id); return new Result().ok(data); @@ -244,12 +268,13 @@ public class AgentController { @Operation(summary = "获取音频下载ID") @RequiresPermissions("sys:role:normal") public Result getAudioId(@PathVariable("audioId") String audioId) { + requireAudioPermission(audioId); byte[] audioData = agentChatAudioService.getAudio(audioId); if (audioData == null) { return new Result().error("音频不存在"); } 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().ok(uuid); } @@ -305,6 +330,7 @@ public class AgentController { @Operation(summary = "获取智能体的标签") @RequiresPermissions("sys:role:normal") public Result> getAgentTags(@PathVariable String id) { + requireAgentPermission(id); List tags = agentTagService.getTagsByAgentId(id); return new Result>().ok(tags); } @@ -313,6 +339,7 @@ public class AgentController { @Operation(summary = "保存智能体的标签") @RequiresPermissions("sys:role:normal") public Result saveAgentTags(@PathVariable String id, @RequestBody Map params) { + requireAgentPermission(id); List tagIds = (List) params.get("tagIds"); List tagNames = (List) params.get("tagNames"); AgentUpdateDTO dto = new AgentUpdateDTO(); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java index 459b5a5a..48fc0539 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java @@ -37,6 +37,14 @@ public interface AgentChatHistoryService extends IService getChatHistoryBySessionId(String agentId, String sessionId); + /** + * 根据会话ID获取智能体ID + * + * @param sessionId 会话ID + * @return 智能体ID + */ + String getAgentIdBySessionId(String sessionId); + /** * 根据智能体ID删除聊天记录 * @@ -62,6 +70,14 @@ public interface AgentChatHistoryService extends IService { */ 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 { */ 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); + /** * 更新智能体 * 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 9cfece89..d2e8e905 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 @@ -88,6 +88,19 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl() + .select(AgentChatHistoryEntity::getAgentId) + .eq(AgentChatHistoryEntity::getSessionId, sessionId) + .last("LIMIT 1")); + return entity == null ? null : entity.getAgentId(); + } + @Override @Transactional(rollbackFor = Exception.class) public void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText) { @@ -176,6 +189,19 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl() + .select(AgentChatHistoryEntity::getAgentId) + .eq(AgentChatHistoryEntity::getAudioId, audioId) + .last("LIMIT 1")); + return entity == null ? null : entity.getAgentId(); + } + @Override public boolean isAudioOwnedByAgent(String audioId, String agentId) { // 查询是否有指定音频id和智能体id的数据,如果有且只有一条说明此数据属性此智能体 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 34da49be..615b80c2 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 @@ -33,6 +33,7 @@ import xiaozhi.modules.agent.dao.AgentDao; import xiaozhi.modules.agent.dao.AgentTagDao; import xiaozhi.modules.agent.dto.AgentCreateDTO; import xiaozhi.modules.agent.dto.AgentDTO; +import xiaozhi.modules.agent.dto.AgentMemoryDTO; import xiaozhi.modules.agent.dto.AgentTagDTO; import xiaozhi.modules.agent.dto.AgentUpdateDTO; import xiaozhi.modules.agent.entity.AgentContextProviderEntity; @@ -93,6 +94,7 @@ public class AgentServiceImpl extends BaseServiceImpl imp if (agent == null) { throw new RenException(ErrorCode.AGENT_NOT_FOUND); } + requireCurrentUserPermissionIfPresent(agent); if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode()); @@ -116,6 +118,65 @@ public class AgentServiceImpl extends BaseServiceImpl imp 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 public boolean insert(AgentEntity entity) { // 如果ID为空,自动生成一个UUID作为ID @@ -275,25 +336,11 @@ public class AgentServiceImpl extends BaseServiceImpl imp } @Override - public boolean checkAgentPermission(String agentId, Long userId) { - if (SecurityUser.getUser() == null || SecurityUser.getUser().getId() == null) { - return false; - } - // 获取智能体信息 - AgentEntity agent = getAgentById(agentId); - if (agent == null) { - return false; - } - - // 如果是超级管理员,直接返回true - if (SecurityUser.getUser().getSuperAdmin() == SuperAdminEnum.YES.value()) { - return true; - } - - // 检查是否是智能体的所有者 - return userId.equals(agent.getUserId()); - } - + public boolean checkAgentPermission(String agentId, Long userId) { + AgentEntity agent = agentDao.selectById(agentId); + return hasAgentPermission(agent, userId); + } + // 根据id更新智能体信息 @Override @Transactional(rollbackFor = Exception.class) @@ -301,13 +348,29 @@ public class AgentServiceImpl extends BaseServiceImpl imp updateAgentById(agentId, dto, true); } + @Override + @Transactional(rollbackFor = Exception.class) + public void updateAgentById(String agentId, AgentUpdateDTO dto, Long userId) { + updateAgentById(agentId, dto, userId, true); + } + // 根据id更新智能体信息 @Override @Transactional(rollbackFor = Exception.class) 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); } + if (userId == null) { + requireCurrentUserPermissionIfPresent(lockedAgent); + } else { + requireAgentPermission(lockedAgent, userId); + } // 锁定后查询现有实体和关联配置 AgentEntity existingEntity = this.getAgentById(agentId); @@ -320,9 +383,9 @@ public class AgentServiceImpl extends BaseServiceImpl imp if (dto.getAgentName() != null) { existingEntity.setAgentName(dto.getAgentName()); } - if (dto.getAgentCode() != null) { - existingEntity.setAgentCode(dto.getAgentCode()); - } + if (dto.getAgentCode() != null) { + existingEntity.setAgentCode(dto.getAgentCode()); + } if (dto.getAsrModelId() != null) { existingEntity.setAsrModelId(dto.getAsrModelId()); } @@ -479,6 +542,29 @@ public class AgentServiceImpl extends BaseServiceImpl 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); + } + /** * 验证大语言模型和意图识别的参数是否符合匹配 * diff --git a/main/xiaozhi-server/agent-base-prompt.txt b/main/xiaozhi-server/agent-base-prompt.txt index 7b54792c..7c6f84b7 100644 --- a/main/xiaozhi-server/agent-base-prompt.txt +++ b/main/xiaozhi-server/agent-base-prompt.txt @@ -65,7 +65,7 @@ Calling unnecessarily is also wrong: User asks ”明天天气” → you call g 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. diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index fcf5183b..e7b853d3 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -159,6 +159,8 @@ class ConnectionHandler: self.asr_audio = [] # 存储PCM帧列表,供VAD和ASR共享 self.asr_audio_queue = queue.Queue() self.current_speaker = None # 存储当前说话人 + self.introduced_speakers = set() # 已"首次引入"的说话人,控制只在首轮带名字 + self.system_introduced_speakers = set() # 已在 system 注入过身份的说话人,控制 system 身份只首轮出现 # llm相关变量 self.dialogue = Dialogue() @@ -1120,12 +1122,20 @@ class ConnectionHandler: ) 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: # 使用支持functions的streaming接口 llm_responses = self.llm.response_with_functions( self.session_id, self.dialogue.get_llm_dialogue_with_memory( - memory_str, self.config.get("voiceprint", {}) + memory_str, self.config.get("voiceprint", {}), speaker_for_system ), functions=functions, ) @@ -1133,7 +1143,7 @@ class ConnectionHandler: llm_responses = self.llm.response( self.session_id, 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: diff --git a/main/xiaozhi-server/core/handle/receiveAudioHandle.py b/main/xiaozhi-server/core/handle/receiveAudioHandle.py index 63a9f610..504669b8 100644 --- a/main/xiaozhi-server/core/handle/receiveAudioHandle.py +++ b/main/xiaozhi-server/core/handle/receiveAudioHandle.py @@ -43,7 +43,6 @@ async def resume_vad_detection(conn: "ConnectionHandler"): async def startToChat(conn: "ConnectionHandler", text): # 检查输入是否是JSON格式(包含说话人信息) speaker_name = None - language_tag = None actual_text = text try: @@ -52,12 +51,16 @@ async def startToChat(conn: "ConnectionHandler", text): data = json.loads(text) if "speaker" in data and "content" in data: speaker_name = data["speaker"] - language_tag = data["language"] - actual_text = data["content"] + actual_content = data["content"] conn.logger.bind(tag=TAG).info(f"解析到说话人信息: {speaker_name}") - # 直接使用JSON格式的文本,不解析 - actual_text = text + # 仅在该说话人首次出现时保留 {"speaker":...} JSON,让模型自然称呼一次; + # 后续轮降为纯文本,避免每轮重复出现名字诱导模型反复称呼 + 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): # 如果解析失败,继续使用原始文本 pass diff --git a/main/xiaozhi-server/core/providers/asr/doubao_stream.py b/main/xiaozhi-server/core/providers/asr/doubao_stream.py index 0e5e6c32..130729ba 100644 --- a/main/xiaozhi-server/core/providers/asr/doubao_stream.py +++ b/main/xiaozhi-server/core/providers/asr/doubao_stream.py @@ -428,4 +428,3 @@ class ASRProvider(ASRProviderBase): pass self.forward_task = None self.is_processing = False - diff --git a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py index 72d6526f..6c581058 100644 --- a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py +++ b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py @@ -43,11 +43,8 @@ class MemoryProvider(MemoryProviderBase): self.memory_client = None self.enable_user_profile = False self.last_profile_content = "" # Cache for user profile from UserMemory - try: - # Check if user profile mode is enabled - self.enable_user_profile = config.get("enable_user_profile", False) - + self.enable_user_profile = str(config.get("enable_user_profile", False)).lower() == 'true' # Get configuration parameters database_provider = config.get("database_provider", "sqlite") llm_provider = config.get("llm_provider", "qwen") diff --git a/main/xiaozhi-server/core/utils/dialogue.py b/main/xiaozhi-server/core/utils/dialogue.py index 4039f01e..ccc1e2c2 100644 --- a/main/xiaozhi-server/core/utils/dialogue.py +++ b/main/xiaozhi-server/core/utils/dialogue.py @@ -92,7 +92,8 @@ class Dialogue: return result 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]]: # 构建对话 dialogue = [] @@ -103,50 +104,31 @@ class Dialogue: ) if system_message: - # 以 为分界点,拆分静态 system prompt 和动态上下文 - # 静态部分(规则、身份等)保持不变,可命中前缀缓存 - # 动态部分(时间、天气、记忆等)作为第二条 system 消息,保持 system 权威性 full_prompt = system_message.content - context_match = re.search(r"", 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") ) # 填充记忆 if memory_str is not None: - dynamic_part = re.sub( + full_prompt = re.sub( r".*?", f"\n{memory_str}\n", - dynamic_part, + full_prompt, flags=re.DOTALL, ) # 追加说话人信息 try: - speakers = voiceprint_config.get("speakers", []) - if speakers: - dynamic_part += "\n" + current_speaker_name = (current_speaker or "").strip() + # 仅在本轮注入了有效身份时才输出 speakers_info,避免列表里的名字每轮 + # 重复出现诱导模型反复称呼;后续轮不再注入身份,靠对话历史首轮保留 + if current_speaker_name and current_speaker_name != "未知说话人": + speakers = voiceprint_config.get("speakers", []) + speakers_info = "\n" + speakers_info += f"\n当前说话人:{current_speaker_name}" for speaker_str in speakers: try: parts = speaker_str.split(",", 2) @@ -155,16 +137,24 @@ class Dialogue: description = ( parts[2].strip() if len(parts) >= 3 else "" ) - dynamic_part += f"\n- {name}:{description}" + speakers_info += f"\n- {name}:{description}" except: pass - dynamic_part += "\n" + speakers_info += "\n" + full_prompt += speakers_info except: 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] complete_actual = self._ensure_tool_calls_complete(actual_messages) for m in complete_actual: