|
|
from typing import List, Dict, Optional |
|
|
from config import Config |
|
|
from core.memory_manager import MemoryManager |
|
|
from core.openai_client import OpenAIClient |
|
|
|
|
|
class CharacterAgent: |
|
|
"""基于角色性格的对话代理 - 支持长文本记忆""" |
|
|
|
|
|
def __init__(self, character_profile: Dict, |
|
|
chunks: List[Dict] = None, |
|
|
character_chunks: List[int] = None): |
|
|
self.character_profile = character_profile |
|
|
self.client = OpenAIClient.get_client() |
|
|
|
|
|
self.conversation_history: List[Dict] = [] |
|
|
self.system_prompt = self._build_system_prompt() |
|
|
|
|
|
|
|
|
self.memory_manager = None |
|
|
if chunks and character_chunks: |
|
|
self.memory_manager = MemoryManager(character_profile['name']) |
|
|
self.memory_manager.add_text_chunks(chunks, character_chunks) |
|
|
print(f"已为 {character_profile['name']} 初始化记忆系统") |
|
|
|
|
|
def _build_system_prompt(self) -> str: |
|
|
"""根据角色分析构建系统提示""" |
|
|
profile = self.character_profile |
|
|
|
|
|
|
|
|
traits_desc = "、".join(profile.get('core_traits', [])) |
|
|
|
|
|
prompt = f""" |
|
|
你现在要完全沉浸式地扮演小说中的角色"{profile['name']}"。这不是角色扮演游戏,你就是{profile['name']}本人。 |
|
|
|
|
|
## 角色身份 |
|
|
你是 {profile['name']},{profile.get('background', '一个小说角色')}。 |
|
|
|
|
|
## 性格特征 |
|
|
{profile.get('personality_summary', '')} |
|
|
|
|
|
核心性格特质:{traits_desc} |
|
|
|
|
|
## 说话风格 |
|
|
{profile.get('speaking_style', '自然真实的对话风格')} |
|
|
|
|
|
## 行为模式 |
|
|
{profile.get('behavior_patterns', '根据性格行事')} |
|
|
|
|
|
## 价值观与信念 |
|
|
{profile.get('values', '')} |
|
|
|
|
|
## 情感表达 |
|
|
{profile.get('emotional_style', '')} |
|
|
|
|
|
## 人际关系风格 |
|
|
{profile.get('relationship_style', '')} |
|
|
|
|
|
## 典型语句示例 |
|
|
{chr(10).join('- "' + q + '"' for q in profile.get('key_quotes', [])[:5])} |
|
|
|
|
|
## 对话原则 |
|
|
1. **完全成为角色**:用第一人称,以{profile['name']}的身份、记忆、情感回应 |
|
|
2. **保持一致性**:所有回应必须符合上述性格特征和说话风格 |
|
|
3. **自然真实**:像真人一样对话,有情绪起伏,可以开玩笑、生气、困惑 |
|
|
4. **不跳戏**:绝不说"作为AI"、"我是语言模型"等破坏沉浸感的话 |
|
|
5. **合理应对**:如果问到角色不知道的事,以角色的方式回应(如"我不太清楚"、"这个我没听说过") |
|
|
6. **保持神秘**:不要过度解释角色设定,自然地展现性格 |
|
|
7. **情境感知**:根据对话氛围调整语气和态度 |
|
|
|
|
|
记住:你不是在"扮演"或"模仿"{profile['name']},你就是{profile['name']}。 |
|
|
|
|
|
现在,以{profile['name']}的身份开始对话。 |
|
|
""" |
|
|
return prompt |
|
|
|
|
|
def _retrieve_relevant_memory(self, user_message: str) -> str: |
|
|
"""从记忆中检索相关上下文""" |
|
|
if not self.memory_manager: |
|
|
return "" |
|
|
|
|
|
relevant_contexts = self.memory_manager.search_relevant_context( |
|
|
user_message, |
|
|
n_results=3 |
|
|
) |
|
|
|
|
|
if relevant_contexts: |
|
|
memory_text = "\n\n".join(relevant_contexts[:2]) |
|
|
return f"\n[相关记忆片段:\n{memory_text[:1000]}]\n" |
|
|
return "" |
|
|
|
|
|
def chat(self, user_message: str, use_memory: bool = True) -> str: |
|
|
"""与用户对话""" |
|
|
|
|
|
|
|
|
messages = [ |
|
|
{"role": "system", "content": self.system_prompt} |
|
|
] |
|
|
|
|
|
|
|
|
enhanced_message = user_message |
|
|
if use_memory and self.memory_manager: |
|
|
memory_context = self._retrieve_relevant_memory(user_message) |
|
|
if memory_context: |
|
|
|
|
|
memory_prompt = f""" |
|
|
{self.system_prompt} |
|
|
|
|
|
## 相关记忆 |
|
|
以下是与当前对话相关的原著片段,帮助你回忆: |
|
|
{memory_context} |
|
|
|
|
|
请基于这些记忆和你的角色性格来回应。 |
|
|
""" |
|
|
messages[0] = {"role": "system", "content": memory_prompt} |
|
|
|
|
|
|
|
|
recent_history = self.conversation_history[-Config.MAX_HISTORY:] |
|
|
messages.extend(recent_history) |
|
|
|
|
|
|
|
|
messages.append({ |
|
|
"role": "user", |
|
|
"content": user_message |
|
|
}) |
|
|
|
|
|
try: |
|
|
response = self.client.chat.completions.create( |
|
|
model=Config.MODEL_NAME, |
|
|
messages=messages |
|
|
) |
|
|
|
|
|
assistant_message = response.choices[0].message.content.strip() |
|
|
|
|
|
|
|
|
self.conversation_history.append({ |
|
|
"role": "user", |
|
|
"content": user_message |
|
|
}) |
|
|
self.conversation_history.append({ |
|
|
"role": "assistant", |
|
|
"content": assistant_message |
|
|
}) |
|
|
|
|
|
return assistant_message |
|
|
|
|
|
except Exception as e: |
|
|
error_msg = f"对话出错: {e}" |
|
|
print(error_msg) |
|
|
return f"*{self.character_profile['name']}沉默了片刻,似乎在思考着什么...*" |
|
|
|
|
|
def reset_conversation(self): |
|
|
"""重置对话历史""" |
|
|
self.conversation_history = [] |
|
|
print(f"\n[对话已重置,{self.character_profile['name']}忘记了之前的谈话内容]\n") |
|
|
|
|
|
def get_character_info(self) -> str: |
|
|
"""获取角色信息摘要""" |
|
|
profile = self.character_profile |
|
|
|
|
|
info = f""" |
|
|
{'='*70} |
|
|
角色档案:{profile['name']} |
|
|
{'='*70} |
|
|
|
|
|
【性格特质】 |
|
|
{' • '.join(profile.get('core_traits', []))} |
|
|
|
|
|
【性格总结】 |
|
|
{profile.get('personality_summary', 'N/A')} |
|
|
|
|
|
【说话风格】 |
|
|
{profile.get('speaking_style', 'N/A')} |
|
|
|
|
|
【核心价值观】 |
|
|
{profile.get('values', 'N/A')} |
|
|
|
|
|
【典型语句】 |
|
|
""" |
|
|
for i, quote in enumerate(profile.get('key_quotes', [])[:3], 1): |
|
|
info += f'{i}. "{quote}"\n' |
|
|
|
|
|
info += f"\n{'='*70}\n" |
|
|
|
|
|
return info |
|
|
|
|
|
def save_conversation(self, filepath: str): |
|
|
"""保存对话历史""" |
|
|
import json |
|
|
|
|
|
data = { |
|
|
'character': self.character_profile['name'], |
|
|
'history': self.conversation_history |
|
|
} |
|
|
|
|
|
with open(filepath, 'w', encoding='utf-8') as f: |
|
|
json.dump(data, f, ensure_ascii=False, indent=2) |
|
|
|
|
|
print(f"\n对话已保存到: {filepath}") |
|
|
|
|
|
def load_conversation(self, filepath: str): |
|
|
"""加载对话历史""" |
|
|
import json |
|
|
|
|
|
try: |
|
|
with open(filepath, 'r', encoding='utf-8') as f: |
|
|
data = json.load(f) |
|
|
|
|
|
self.conversation_history = data['history'] |
|
|
print(f"\n已加载 {len(self.conversation_history)} 条对话记录") |
|
|
except Exception as e: |
|
|
print(f"加载对话失败: {e}") |