dialogue.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import uuid
  2. import re
  3. from typing import List, Dict
  4. from core.utils.current_time import get_current_time, get_now
  5. class Message:
  6. def __init__(
  7. self,
  8. role: str,
  9. content: str = None,
  10. uniq_id: str = None,
  11. tool_calls=None,
  12. tool_call_id=None,
  13. is_temporary=False,
  14. ):
  15. self.uniq_id = uniq_id if uniq_id is not None else str(uuid.uuid4())
  16. self.role = role
  17. self.content = content
  18. self.tool_calls = tool_calls
  19. self.tool_call_id = tool_call_id
  20. self.is_temporary = is_temporary # 标记临时消息(如工具调用提醒)
  21. class Dialogue:
  22. def __init__(self):
  23. self.dialogue: List[Message] = []
  24. # 获取当前时间
  25. self.current_time = get_now().strftime("%Y-%m-%d %H:%M:%S")
  26. def put(self, message: Message):
  27. self.dialogue.append(message)
  28. def getMessages(self, m, dialogue):
  29. if m.tool_calls is not None:
  30. dialogue.append({"role": m.role, "tool_calls": m.tool_calls})
  31. elif m.role == "tool":
  32. dialogue.append(
  33. {
  34. "role": m.role,
  35. "tool_call_id": (
  36. str(uuid.uuid4()) if m.tool_call_id is None else m.tool_call_id
  37. ),
  38. "content": m.content,
  39. }
  40. )
  41. else:
  42. dialogue.append({"role": m.role, "content": m.content})
  43. def get_llm_dialogue(self) -> List[Dict[str, str]]:
  44. # 直接调用get_llm_dialogue_with_memory,传入None作为memory_str
  45. # 这样确保说话人功能在所有调用路径下都生效
  46. return self.get_llm_dialogue_with_memory(None, None)
  47. def update_system_message(self, new_content: str):
  48. """更新或添加系统消息"""
  49. # 查找第一个系统消息
  50. system_msg = next((msg for msg in self.dialogue if msg.role == "system"), None)
  51. if system_msg:
  52. system_msg.content = new_content
  53. else:
  54. self.put(Message(role="system", content=new_content))
  55. def trim_history(self, max_turns: int = 10) -> int:
  56. """
  57. 智能截断对话历史,保留工具调用的完整性
  58. Args:
  59. max_turns: 保留的最大对话轮数(每轮 = user + assistant/tool 相关消息)
  60. Returns:
  61. int: 被移除的消息数量
  62. """
  63. if len(self.dialogue) <= max_turns * 2 + 1: # +1 是系统消息
  64. return 0
  65. # 分离系统消息和对话消息
  66. system_messages = [msg for msg in self.dialogue if msg.role == "system"]
  67. conversation_messages = [msg for msg in self.dialogue if msg.role != "system"]
  68. if len(conversation_messages) <= max_turns * 2:
  69. return 0
  70. # 智能截断:保留完整的工具调用链路
  71. keep_messages = []
  72. i = len(conversation_messages) - 1
  73. turn_count = 0
  74. while i >= 0 and turn_count < max_turns:
  75. msg = conversation_messages[i]
  76. # 从后向前收集消息
  77. if msg.role == "user":
  78. # 遇到 user 消息,说明一轮对话开始
  79. keep_messages.insert(0, msg)
  80. turn_count += 1
  81. i -= 1
  82. elif msg.role == "assistant":
  83. # 收集 assistant 消息
  84. keep_messages.insert(0, msg)
  85. # 如果这个 assistant 有 tool_calls,需要收集对应的 tool 响应
  86. if msg.tool_calls is not None:
  87. i -= 1
  88. # 继续向后收集所有相关的 tool 消息
  89. while i >= 0 and conversation_messages[i].role == "tool":
  90. keep_messages.insert(0, conversation_messages[i])
  91. i -= 1
  92. else:
  93. i -= 1
  94. elif msg.role == "tool":
  95. # tool 消息应该已经被上面的逻辑收集了
  96. # 如果单独遇到,也要保留(防止边界情况)
  97. keep_messages.insert(0, msg)
  98. i -= 1
  99. else:
  100. i -= 1
  101. removed_count = len(conversation_messages) - len(keep_messages)
  102. # 重建对话列表
  103. self.dialogue = system_messages + keep_messages
  104. return removed_count
  105. def get_llm_dialogue_with_memory(
  106. self, memory_str: str = None, voiceprint_config: dict = None
  107. ) -> List[Dict[str, str]]:
  108. # 构建对话
  109. dialogue = []
  110. # 添加系统提示和记忆
  111. system_message = next(
  112. (msg for msg in self.dialogue if msg.role == "system"), None
  113. )
  114. if system_message:
  115. # 基础系统提示
  116. enhanced_system_prompt = system_message.content
  117. # 替换时间占位符
  118. enhanced_system_prompt = enhanced_system_prompt.replace(
  119. "{{current_time}}", get_current_time()
  120. )
  121. # 添加说话人个性化描述
  122. try:
  123. speakers = voiceprint_config.get("speakers", [])
  124. if speakers:
  125. enhanced_system_prompt += "\n\n<speakers_info>"
  126. for speaker_str in speakers:
  127. try:
  128. parts = speaker_str.split(",", 2)
  129. if len(parts) >= 2:
  130. name = parts[1].strip()
  131. # 如果描述为空,则为""
  132. description = (
  133. parts[2].strip() if len(parts) >= 3 else ""
  134. )
  135. enhanced_system_prompt += f"\n- {name}:{description}"
  136. except:
  137. pass
  138. enhanced_system_prompt += "\n\n</speakers_info>"
  139. except:
  140. # 配置读取失败时忽略错误,不影响其他功能
  141. pass
  142. # 使用正则表达式匹配 <memory> 标签,不管中间有什么内容
  143. if memory_str is not None:
  144. enhanced_system_prompt = re.sub(
  145. r"<memory>.*?</memory>",
  146. f"<memory>\n{memory_str}\n</memory>",
  147. enhanced_system_prompt,
  148. flags=re.DOTALL,
  149. )
  150. dialogue.append({"role": "system", "content": enhanced_system_prompt})
  151. # 添加用户和助手的对话
  152. for m in self.dialogue:
  153. if m.role != "system": # 跳过原始的系统消息
  154. self.getMessages(m, dialogue)
  155. return dialogue