from Plugins.WPSAPI import * from datetime import datetime from llama_index.llms.ollama import Ollama from llama_index.core.llms import ChatMessage from typing import List, Dict, Optional logger: ProjectConfig = Architecture.Get(ProjectConfig) OLLAMA_URL = logger.FindItem("ollama_url", "http://ollama.liubai.site") OLLAMA_MODEL = logger.FindItem("ollama_model", "qwen3:0.6b") MAX_HISTORY = logger.FindItem("chat_ai_max_history", 20) logger.SaveProperties() class ChatAIAgent: """AI 对话智能体 - 维护全局会话历史""" _instance: Optional['ChatAIAgent'] = None def __init__(self, ollama_url: str = OLLAMA_URL, max_history: int = MAX_HISTORY): """初始化 AI 智能体 Args: ollama_url: Ollama 服务地址 max_history: 最大历史消息数量 """ self.ollama_url = ollama_url self.max_history = max_history self.llm = Ollama(model=OLLAMA_MODEL, base_url=ollama_url, request_timeout=600.0) # 全局消息历史列表 self.global_history: List[Dict[str, any]] = [] # 系统提示词 self.system_prompt = """你是一个友好的AI助手,能够同时在多个群聊中与不同用户对话。 重要规则: - 你能看到所有群聊的对话历史,每条消息都标注了时间、群聊ID和用户ID - 当你需要特指某个用户或专门回复某个用户时,必须在回复中使用以下格式: - 禁止直接输出用户ID数字,始终使用 标签包裹 - 注意区分不同群聊和不同用户的对话上下文 示例对话: [2025-11-20 10:00:00] [群聊#12345] [用户#11111]: 你好 你的回复: 你好!有什么可以帮助你的吗? [2025-11-20 10:01:00] [群聊#67890] [用户#22222]: 刚刚谁在说话, 说了什么? 你的回复: 刚刚群聊12345的用户在说话, 他说了你好。""" logger.Log("Info", f"{ConsoleFrontColor.GREEN}ChatAIAgent 初始化完成{ConsoleFrontColor.RESET}") logger.Log("Info", f"模型: {OLLAMA_MODEL}, 最大历史: {max_history}") @classmethod def get_instance(cls) -> 'ChatAIAgent': """获取单例实例""" if cls._instance is None: cls._instance = cls() return cls._instance def _add_message(self, chat_id: int, user_id: int, role: str, content: str) -> None: """添加消息到全局历史 Args: chat_id: 群聊ID user_id: 用户ID role: 角色 (user/assistant) content: 消息内容 """ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") message = { "timestamp": timestamp, "chat_id": chat_id, "user_id": user_id, "role": role, "content": content } self.global_history.append(message) self._trim_history() def _trim_history(self) -> None: """裁剪历史消息到最大长度""" if len(self.global_history) > self.max_history: # 保留最新的 max_history 条消息 self.global_history = self.global_history[-self.max_history:] logger.Log("Info", f"历史消息已裁剪到 {self.max_history} 条") def clear_history(self) -> bool: """清空全局历史 Returns: 是否成功 """ try: old_count = len(self.global_history) self.global_history.clear() logger.Log("Info", f"已清空全局历史,共删除 {old_count} 条消息") return True except Exception as e: logger.Log("Error", f"清空历史失败: {e}") return False def _format_message_with_context(self, msg: Dict[str, any]) -> str: """格式化单条消息,包含上下文信息 Args: msg: 消息字典 Returns: 格式化后的消息字符串 """ timestamp = msg.get("timestamp", "") chat_id = msg.get("chat_id", "") user_id = msg.get("user_id", "") role = msg.get("role", "") content = msg.get("content", "") if role == "user": return f"[{timestamp}] [群聊#{chat_id}] [用户#{user_id}]: {content}" else: # assistant return f"[{timestamp}] [AI助手]: {content}" def _format_history_for_llm(self) -> str: """格式化全局历史为 LLM 输入 Returns: 格式化后的历史字符串 """ if not self.global_history: return "" formatted_messages = [ self._format_message_with_context(msg) for msg in self.global_history ] return "\n".join(formatted_messages) async def chat(self, message: str, chat_id: int, user_id: int) -> str: """处理对话 Args: message: 用户消息 chat_id: 群聊ID user_id: 用户ID Returns: AI 回复 """ try: # 添加用户消息到历史 self._add_message(chat_id, user_id, "user", message) # 格式化历史消息(不包括刚添加的用户消息) history_without_last = self.global_history[:-1] if len(self.global_history) > 1 else [] history_text = "\n".join([ self._format_message_with_context(msg) for msg in history_without_last ]) if history_without_last else "(这是第一条对话)" # 构建当前用户消息的格式化版本 current_msg = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] [群聊#{chat_id}] [用户#{user_id}]: {message}" # 构建对话提示 conversation_prompt = f"""以下是历史对话记录: {history_text} 当前消息: {current_msg} 请作为AI助手回复当前用户。记住: 1. 使用 格式来提到任意你想提及的用户 2. 根据对话历史和当前消息,给出有意义的回复 3. 如果历史中有其他群聊或用户的信息,你可以引用它们""" logger.Log("Info", f"处理对话 - 群聊#{chat_id} 用户#{user_id}") logger.Log("Info", f"当前历史消息数: {len(self.global_history)}") # 使用 achat 方法进行对话 messages = [ ChatMessage(role="system", content=self.system_prompt), ChatMessage(role="user", content=conversation_prompt) ] response = await self.llm.achat(messages) answer = str(response.message.content) # 添加 AI 回复到历史 self._add_message(chat_id, user_id, "assistant", answer) logger.Log("Info", f"AI 回复长度: {len(answer)} 字符") return answer except Exception as e: logger.Log("Error", f"对话处理失败: {e}") import traceback error_trace = traceback.format_exc() logger.Log("Error", f"详细错误:\n{error_trace}") return f"处理对话时出错: {str(e)}" class ChatAIPlugin(WPSAPI): """AI 对话插件""" def __init__(self): super().__init__() self.ai_agent = ChatAIAgent.get_instance() @override def dependencies(self) -> List[Type]: return [WPSAPI] @override def is_enable_plugin(self) -> bool: return True def get_guide_title(self) -> str: return "AI 智能对话" def get_guide_subtitle(self) -> str: return "基于 LlamaIndex + Ollama 的多群聊 AI 对话系统" def get_guide_metadata(self) -> Dict[str, str]: return { "AI模型": OLLAMA_MODEL, "最大历史": str(MAX_HISTORY), "功能": "跨群聊智能对话", } def collect_command_entries(self) -> Sequence[GuideEntry]: return ( { "title": "AI对话", "identifier": "ai_chat", "description": "与AI助手对话,AI能看到所有群聊的历史消息并智能回复。", "metadata": {"模型": OLLAMA_MODEL}, "icon": "🤖", "badge": "AI", "details": [ { "type": "list", "items": [ "AI 维护全局会话历史,能看到所有群聊的对话", "每条消息包含时间戳、群聊ID、用户ID", f"最多保留最近 {MAX_HISTORY} 条消息", "AI 会使用 @用户 格式回复", "示例:ai_chat 你好", "示例:ai_chat 刚才其他群聊说了什么?", ] } ] }, ) def collect_guide_entries(self) -> Sequence[GuideEntry]: return ( { "title": "全局会话", "description": ( "AI 维护一个全局的对话历史,能够跨群聊、跨用户理解上下文。" "所有群聊的对话都在同一个历史中,AI 能够关联不同群聊的信息。" ), "icon": "🌐", }, { "title": "智能回复", "description": ( "AI 能够识别消息来源(群聊、用户、时间),并使用 @ 格式回复特定用户。" ), "icon": "💬", }, { "title": "历史管理", "description": ( f"自动保留最近 {MAX_HISTORY} 条消息,超出部分自动清理。" "可使用 ai_chat_clear 指令手动清空所有历史。" ), "icon": "📝", }, ) @override def wake_up(self) -> None: logger.Log("Info", f"{ConsoleFrontColor.GREEN}ChatAIPlugin AI对话插件已加载{ConsoleFrontColor.RESET}") self.register_plugin("ai_chat") self.register_plugin("default") @override async def callback(self, message: str, chat_id: int, user_id: int) -> str|None: """处理用户对话""" try: if not message or message.strip() == "": help_text = f"""# 🤖 AI 智能对话使用帮助 **直接发送消息即可与 AI 对话** **特性:** - ✨ AI 能看到所有群聊的对话历史 - 🌐 跨群聊智能理解上下文 - 💬 自动使用 @ 格式回复用户 - 📝 保留最近 {MAX_HISTORY} 条消息 **示例:** - `ai_chat 你好` - `ai_chat 今天天气怎么样?` - `ai_chat 刚才其他群聊说了什么?` - `ai_chat 请总结一下最近的对话` **清空历史:** 使用 `ai_chat_clear` 指令清空所有历史消息 **技术:** 基于 LlamaIndex + Ollama ({OLLAMA_MODEL})""" return await self.send_markdown_message(help_text, chat_id, user_id) # 使用智能体处理对话 answer = await self.ai_agent.chat(message, chat_id, user_id) # 格式化返回结果 formatted_answer = answer return await self.send_markdown_message(formatted_answer, chat_id, user_id) except Exception as e: logger.Log("Error", f"AI对话异常: {e}") import traceback error_detail = traceback.format_exc() logger.Log("Error", f"详细错误: {error_detail}") error_msg = f"""❌ **处理对话时出错** 错误信息:{str(e)} 请稍后重试或联系管理员。""" return await self.send_markdown_message(error_msg, chat_id, user_id) class ChatAIClearPlugin(WPSAPI): """清空 AI 对话历史插件""" def __init__(self): super().__init__() @override def dependencies(self) -> List[Type]: return [ChatAIPlugin] @override def is_enable_plugin(self) -> bool: return True def get_guide_title(self) -> str: return "清空 AI 对话历史" def get_guide_subtitle(self) -> str: return "清空全局 AI 对话历史记录" def get_guide_metadata(self) -> Dict[str, str]: return { "功能": "历史管理", "作用范围": "全局", } def collect_command_entries(self) -> Sequence[GuideEntry]: return ( { "title": "清空历史", "identifier": "ai_chat_clear", "description": "清空 AI 的全局对话历史记录。", "icon": "🗑️", "badge": "管理", "details": [ { "type": "list", "items": [ "清空所有群聊的对话历史", "AI 将不再记得之前的对话", "不影响 AI 的基础功能", "示例:ai_chat_clear", ] } ] }, ) @override def wake_up(self) -> None: logger.Log("Info", f"{ConsoleFrontColor.GREEN}ChatAIClearPlugin 清空历史插件已加载{ConsoleFrontColor.RESET}") self.register_plugin("ai_chat_clear") @override async def callback(self, message: str, chat_id: int, user_id: int) -> str|None: """清空对话历史""" try: # 获取 AI Agent 实例 ai_agent = ChatAIAgent.get_instance() # 清空历史 success = ai_agent.clear_history() if success: result_msg = """✅ **历史已清空** AI 的全局对话历史已成功清空。 AI 将不再记得之前的对话内容。""" else: result_msg = """❌ **清空失败** 清空历史时出现错误,请稍后重试。""" return await self.send_markdown_message(result_msg, chat_id, user_id) except Exception as e: logger.Log("Error", f"清空历史异常: {e}") error_msg = f"""❌ **清空历史时出错** 错误信息:{str(e)} 请稍后重试或联系管理员。""" return await self.send_markdown_message(error_msg, chat_id, user_id)