Merge remote-tracking branch 'origin/main' into agent-snapshot-history-fixes

This commit is contained in:
Tyke Chen
2026-07-10 20:26:30 +08:00
11 changed files with 275 additions and 85 deletions
@@ -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<AgentInfoVO> 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<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());
agentService.updateAgentById(device.getAgentId(), agentUpdateDTO, false);
return new Result<>();
agentService.updateAgentMemoryByDeviceMacAddress(macAddress, dto, SecurityUser.getUserId());
return new Result<Void>().ok(null);
}
@PostMapping("/chat-summary/{sessionId}/save")
@Operation(summary = "根据会话ID生成聊天记录总结并保存(异步执行)")
public Result<Void> 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<Void> generateAndSaveChatTitle(@PathVariable String sessionId) {
requireSessionAgent(sessionId);
agentChatSummaryService.generateAndSaveChatTitle(sessionId);
return new Result<Void>().ok(null);
}
@@ -157,7 +179,7 @@ public class AgentController {
@Operation(summary = "更新智能体")
@RequiresPermissions("sys:role:normal")
public Result<Void> 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<Void> delete(@PathVariable String id) {
agentService.deleteAgent(id);
agentService.deleteAgentById(id, SecurityUser.getUserId());
return new Result<>();
}
@@ -188,6 +210,7 @@ public class AgentController {
public Result<PageData<AgentChatSessionDTO>> getAgentSessions(
@PathVariable("id") String id,
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
requireAgentPermission(id);
params.put("agentId", id);
PageData<AgentChatSessionDTO> page = agentChatHistoryService.getSessionListByAgentId(params);
return new Result<PageData<AgentChatSessionDTO>>().ok(page);
@@ -235,6 +258,7 @@ public class AgentController {
@RequiresPermissions("sys:role:normal")
public Result<String> getContentByAudioId(
@PathVariable("id") String id) {
requireAudioPermission(id);
// 查询聊天记录
String data = agentChatHistoryService.getContentByAudioId(id);
return new Result<String>().ok(data);
@@ -244,12 +268,13 @@ public class AgentController {
@Operation(summary = "获取音频下载ID")
@RequiresPermissions("sys:role:normal")
public Result<String> getAudioId(@PathVariable("audioId") String audioId) {
requireAudioPermission(audioId);
byte[] audioData = agentChatAudioService.getAudio(audioId);
if (audioData == null) {
return new Result<String>().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<String>().ok(uuid);
}
@@ -305,6 +330,7 @@ public class AgentController {
@Operation(summary = "获取智能体的标签")
@RequiresPermissions("sys:role:normal")
public Result<List<AgentTagDTO>> getAgentTags(@PathVariable String id) {
requireAgentPermission(id);
List<AgentTagDTO> tags = agentTagService.getTagsByAgentId(id);
return new Result<List<AgentTagDTO>>().ok(tags);
}
@@ -313,6 +339,7 @@ public class AgentController {
@Operation(summary = "保存智能体的标签")
@RequiresPermissions("sys:role:normal")
public Result<Void> saveAgentTags(@PathVariable String id, @RequestBody Map<String, Object> params) {
requireAgentPermission(id);
List<String> tagIds = (List<String>) params.get("tagIds");
List<String> tagNames = (List<String>) params.get("tagNames");
AgentUpdateDTO dto = new AgentUpdateDTO();
@@ -37,6 +37,14 @@ public interface AgentChatHistoryService extends IService<AgentChatHistoryEntity
*/
List<AgentChatHistoryDTO> 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<AgentChatHistoryEntity
*/
String getContentByAudioId(String audioId);
/**
* 根据音频ID获取智能体ID
*
* @param audioId 音频ID
* @return 智能体ID
*/
String getAgentIdByAudioId(String audioId);
/**
* 查询此音频id是否属于此智能体
@@ -7,6 +7,7 @@ import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
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.vo.AgentInfoVO;
@@ -35,6 +36,15 @@ public interface AgentService extends BaseService<AgentEntity> {
*/
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);
/**
* 更新当前用户有权访问的智能体
*
* @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);
/**
* 更新智能体
*
@@ -88,6 +88,19 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
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
@Transactional(rollbackFor = Exception.class)
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();
}
@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
public boolean isAudioOwnedByAgent(String audioId, String agentId) {
// 查询是否有指定音频id和智能体id的数据,如果有且只有一条说明此数据属性此智能体
@@ -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<AgentDao, AgentEntity> 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<AgentDao, AgentEntity> 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<AgentDao, AgentEntity> 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<AgentDao, AgentEntity> 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<AgentDao, AgentEntity> 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<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);
}
/**
* 验证大语言模型和意图识别的参数是否符合匹配
*
+1 -1
View File
@@ -65,7 +65,7 @@ Calling unnecessarily is also wrong: User asks ”明天天气” → you call g
<speaker_recognition>
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.
</speaker_recognition>
+12 -2
View File
@@ -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:
@@ -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
@@ -428,4 +428,3 @@ class ASRProvider(ASRProviderBase):
pass
self.forward_task = None
self.is_processing = False
@@ -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")
+24 -34
View File
@@ -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:
# 以 <context> 为分界点,拆分静态 system prompt 和动态上下文
# 静态部分(规则、身份等)保持不变,可命中前缀缓存
# 动态部分(时间、天气、记忆等)作为第二条 system 消息,保持 system 权威性
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")
)
# 填充记忆
if memory_str is not None:
dynamic_part = re.sub(
full_prompt = re.sub(
r"<memory>.*?</memory>",
f"<memory>\n{memory_str}\n</memory>",
dynamic_part,
full_prompt,
flags=re.DOTALL,
)
# 追加说话人信息
try:
speakers = voiceprint_config.get("speakers", [])
if speakers:
dynamic_part += "\n<speakers_info>"
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>"
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>"
speakers_info += "\n</speakers_info>"
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: