From c1983172af031a59efe502d0060bc42a7025807c Mon Sep 17 00:00:00 2001 From: shihao <3127647737@qq.com> Date: Fri, 12 Dec 2025 18:35:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BC=98=E5=8C=96=E5=B1=8E=E5=B1=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/AIChat.zip | Bin 84937 -> 87764 bytes plugins/AIChat/main.py | 1029 +++++++++++++++++----------------- plugins/AIChat_Gemini.zip | Bin 117802 -> 0 bytes plugins/AutoReply/main.py | 102 ++-- plugins/SignInPlugin/main.py | 3 +- plugins/Weather/main.py | 2 +- plugins/ZImageTurbo/main.py | 2 +- utils/context_store.py | 470 ++++++++++++++++ utils/llm_tooling.py | 183 ++++++ utils/redis_cache.py | 48 +- 10 files changed, 1257 insertions(+), 582 deletions(-) delete mode 100644 plugins/AIChat_Gemini.zip create mode 100644 utils/context_store.py create mode 100644 utils/llm_tooling.py diff --git a/plugins/AIChat.zip b/plugins/AIChat.zip index be810733f2839ff37f277d2f3486c906fce8784d..183a623ee70c2d4e67128e0b413802c554990857 100644 GIT binary patch delta 31682 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 diff --git a/plugins/AIChat/main.py b/plugins/AIChat/main.py index 5986cdd..ba7322a 100644 --- a/plugins/AIChat/main.py +++ b/plugins/AIChat/main.py @@ -8,13 +8,14 @@ AI 聊天插件 import asyncio import tomllib import aiohttp -import sqlite3 +import json from pathlib import Path from datetime import datetime from loguru import logger from utils.plugin_base import PluginBase from utils.decorators import on_text_message, on_quote_message, on_image_message, on_emoji_message from utils.redis_cache import get_cache +from utils.llm_tooling import ToolResult, collect_tools_with_plugins, collect_tools, get_tool_schema_map, validate_tool_arguments import xml.etree.ElementTree as ET import base64 import uuid @@ -46,6 +47,7 @@ class AIChat(PluginBase): self.image_desc_queue = asyncio.Queue() # 图片描述任务队列 self.image_desc_workers = [] # 工作协程列表 self.persistent_memory_db = None # 持久记忆数据库路径 + self.store = None # ContextStore 实例(统一存储) async def async_init(self): """插件异步初始化""" @@ -88,82 +90,68 @@ class AIChat(PluginBase): self.image_desc_workers.append(worker) logger.info("已启动 2 个图片描述工作协程") - # 初始化持久记忆数据库 - self._init_persistent_memory_db() - - logger.info(f"AI 聊天插件已加载,模型: {self.config['api']['model']}") - - def _init_persistent_memory_db(self): - """初始化持久记忆数据库""" + # 初始化持久记忆数据库与统一存储 + from utils.context_store import ContextStore db_dir = Path(__file__).parent / "data" db_dir.mkdir(exist_ok=True) self.persistent_memory_db = db_dir / "persistent_memory.db" + self.store = ContextStore( + self.config, + self.history_dir, + self.memory, + self.history_locks, + self.persistent_memory_db, + ) + self.store.init_persistent_memory_db() - conn = sqlite3.connect(self.persistent_memory_db) - cursor = conn.cursor() - cursor.execute(""" - CREATE TABLE IF NOT EXISTS memories ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - chat_id TEXT NOT NULL, - chat_type TEXT NOT NULL, - user_wxid TEXT NOT NULL, - user_nickname TEXT, - content TEXT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - """) - cursor.execute("CREATE INDEX IF NOT EXISTS idx_chat_id ON memories(chat_id)") - conn.commit() - conn.close() - logger.info(f"持久记忆数据库已初始化: {self.persistent_memory_db}") + logger.info(f"AI 聊天插件已加载,模型: {self.config['api']['model']}") + + async def on_disable(self): + """插件禁用时调用,清理后台任务和队列""" + await super().on_disable() + + # 取消图片描述工作协程,避免重载后叠加 + if self.image_desc_workers: + for worker in self.image_desc_workers: + worker.cancel() + await asyncio.gather(*self.image_desc_workers, return_exceptions=True) + self.image_desc_workers.clear() + + # 清空图片描述队列 + try: + while self.image_desc_queue and not self.image_desc_queue.empty(): + self.image_desc_queue.get_nowait() + self.image_desc_queue.task_done() + except Exception: + pass + self.image_desc_queue = asyncio.Queue() + + logger.info("AIChat 已清理后台图片描述任务") def _add_persistent_memory(self, chat_id: str, chat_type: str, user_wxid: str, user_nickname: str, content: str) -> int: - """添加持久记忆,返回记忆ID""" - conn = sqlite3.connect(self.persistent_memory_db) - cursor = conn.cursor() - cursor.execute(""" - INSERT INTO memories (chat_id, chat_type, user_wxid, user_nickname, content) - VALUES (?, ?, ?, ?, ?) - """, (chat_id, chat_type, user_wxid, user_nickname, content)) - memory_id = cursor.lastrowid - conn.commit() - conn.close() - return memory_id + """添加持久记忆,返回记忆ID(委托 ContextStore)""" + if not self.store: + return -1 + return self.store.add_persistent_memory(chat_id, chat_type, user_wxid, user_nickname, content) def _get_persistent_memories(self, chat_id: str) -> list: - """获取指定会话的所有持久记忆""" - conn = sqlite3.connect(self.persistent_memory_db) - cursor = conn.cursor() - cursor.execute(""" - SELECT id, user_nickname, content, created_at - FROM memories - WHERE chat_id = ? - ORDER BY created_at ASC - """, (chat_id,)) - rows = cursor.fetchall() - conn.close() - return [{"id": r[0], "nickname": r[1], "content": r[2], "time": r[3]} for r in rows] + """获取指定会话的所有持久记忆(委托 ContextStore)""" + if not self.store: + return [] + return self.store.get_persistent_memories(chat_id) def _delete_persistent_memory(self, chat_id: str, memory_id: int) -> bool: - """删除指定的持久记忆""" - conn = sqlite3.connect(self.persistent_memory_db) - cursor = conn.cursor() - cursor.execute("DELETE FROM memories WHERE id = ? AND chat_id = ?", (memory_id, chat_id)) - deleted = cursor.rowcount > 0 - conn.commit() - conn.close() - return deleted + """删除指定的持久记忆(委托 ContextStore)""" + if not self.store: + return False + return self.store.delete_persistent_memory(chat_id, memory_id) def _clear_persistent_memories(self, chat_id: str) -> int: - """清空指定会话的所有持久记忆,返回删除数量""" - conn = sqlite3.connect(self.persistent_memory_db) - cursor = conn.cursor() - cursor.execute("DELETE FROM memories WHERE chat_id = ?", (chat_id,)) - deleted_count = cursor.rowcount - conn.commit() - conn.close() - return deleted_count + """清空指定会话的所有持久记忆(委托 ContextStore)""" + if not self.store: + return 0 + return self.store.clear_persistent_memories(chat_id) def _get_chat_id(self, from_wxid: str, sender_wxid: str = None, is_group: bool = False) -> str: """获取会话ID""" @@ -270,69 +258,21 @@ class AIChat(PluginBase): content: 消息内容(可以是字符串或列表) image_base64: 可选的图片base64数据 """ - if not self.config.get("memory", {}).get("enabled", False): + if not self.store: return - - # 如果有图片,构建多模态内容 - if image_base64: - message_content = [ - {"type": "text", "text": content if isinstance(content, str) else ""}, - {"type": "image_url", "image_url": {"url": image_base64}} - ] - else: - message_content = content - - # 优先使用 Redis 存储 - redis_config = self.config.get("redis", {}) - if redis_config.get("use_redis_history", True): - redis_cache = get_cache() - if redis_cache and redis_cache.enabled: - ttl = redis_config.get("chat_history_ttl", 86400) - redis_cache.add_chat_message(chat_id, role, message_content, ttl=ttl) - # 裁剪历史 - max_messages = self.config["memory"]["max_messages"] - redis_cache.trim_chat_history(chat_id, max_messages) - return - - # 降级到内存存储 - if chat_id not in self.memory: - self.memory[chat_id] = [] - - self.memory[chat_id].append({"role": role, "content": message_content}) - - # 限制记忆长度 - max_messages = self.config["memory"]["max_messages"] - if len(self.memory[chat_id]) > max_messages: - self.memory[chat_id] = self.memory[chat_id][-max_messages:] + self.store.add_private_message(chat_id, role, content, image_base64=image_base64) def _get_memory_messages(self, chat_id: str) -> list: """获取记忆中的消息""" - if not self.config.get("memory", {}).get("enabled", False): + if not self.store: return [] - - # 优先从 Redis 获取 - redis_config = self.config.get("redis", {}) - if redis_config.get("use_redis_history", True): - redis_cache = get_cache() - if redis_cache and redis_cache.enabled: - max_messages = self.config["memory"]["max_messages"] - return redis_cache.get_chat_history(chat_id, max_messages) - - # 降级到内存 - return self.memory.get(chat_id, []) + return self.store.get_private_messages(chat_id) def _clear_memory(self, chat_id: str): """清空指定会话的记忆""" - # 清空 Redis - redis_config = self.config.get("redis", {}) - if redis_config.get("use_redis_history", True): - redis_cache = get_cache() - if redis_cache and redis_cache.enabled: - redis_cache.clear_chat_history(chat_id) - - # 同时清空内存 - if chat_id in self.memory: - del self.memory[chat_id] + if not self.store: + return + self.store.clear_private_messages(chat_id) async def _download_and_encode_image(self, bot, cdnurl: str, aeskey: str) -> str: """下载图片并转换为base64,优先从缓存获取""" @@ -498,127 +438,129 @@ class AIChat(PluginBase): Returns: 图片描述文本,失败返回空字符串 """ - try: - api_config = self.config["api"] - description_model = config.get("model", api_config["model"]) + api_config = self.config["api"] + description_model = config.get("model", api_config["model"]) - # 构建消息 - messages = [ - { - "role": "user", - "content": [ - {"type": "text", "text": prompt}, - {"type": "image_url", "image_url": {"url": image_base64}} - ] - } - ] - - payload = { - "model": description_model, - "messages": messages, - "max_tokens": config.get("max_tokens", 1000), - "stream": True + # 构建消息 + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": prompt}, + {"type": "image_url", "image_url": {"url": image_base64}} + ] } + ] - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {api_config['api_key']}" - } + payload = { + "model": description_model, + "messages": messages, + "max_tokens": config.get("max_tokens", 1000), + "stream": True + } - timeout = aiohttp.ClientTimeout(total=api_config["timeout"]) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {api_config['api_key']}" + } - # 配置代理 - connector = None - proxy_config = self.config.get("proxy", {}) - if proxy_config.get("enabled", False): - proxy_type = proxy_config.get("type", "socks5").upper() - proxy_host = proxy_config.get("host", "127.0.0.1") - proxy_port = proxy_config.get("port", 7890) - proxy_username = proxy_config.get("username") - proxy_password = proxy_config.get("password") + max_retries = int(config.get("retries", 2)) + last_error = None - if proxy_username and proxy_password: - proxy_url = f"{proxy_type}://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}" - else: - proxy_url = f"{proxy_type}://{proxy_host}:{proxy_port}" + for attempt in range(max_retries + 1): + try: + timeout = aiohttp.ClientTimeout(total=api_config["timeout"]) - if PROXY_SUPPORT: - try: - connector = ProxyConnector.from_url(proxy_url) - except Exception as e: - logger.warning(f"代理配置失败,将直连: {e}") - connector = None + # 配置代理(每次重试单独构造 connector) + connector = None + proxy_config = self.config.get("proxy", {}) + if proxy_config.get("enabled", False): + proxy_type = proxy_config.get("type", "socks5").upper() + proxy_host = proxy_config.get("host", "127.0.0.1") + proxy_port = proxy_config.get("port", 7890) + proxy_username = proxy_config.get("username") + proxy_password = proxy_config.get("password") - async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: - async with session.post( - api_config["url"], - json=payload, - headers=headers - ) as resp: - if resp.status != 200: - error_text = await resp.text() - logger.error(f"图片描述 API 返回错误: {resp.status}, {error_text[:200]}") - return "" + if proxy_username and proxy_password: + proxy_url = f"{proxy_type}://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}" + else: + proxy_url = f"{proxy_type}://{proxy_host}:{proxy_port}" - # 流式接收响应 - import json - description = "" - async for line in resp.content: - line = line.decode('utf-8').strip() - if not line or line == "data: [DONE]": - continue + if PROXY_SUPPORT: + try: + connector = ProxyConnector.from_url(proxy_url) + except Exception as e: + logger.warning(f"代理配置失败,将直连: {e}") + connector = None - if line.startswith("data: "): - try: - data = json.loads(line[6:]) - delta = data.get("choices", [{}])[0].get("delta", {}) - content = delta.get("content", "") - if content: - description += content - except: - pass + async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: + async with session.post( + api_config["url"], + json=payload, + headers=headers + ) as resp: + if resp.status != 200: + error_text = await resp.text() + raise Exception(f"图片描述 API 返回错误: {resp.status}, {error_text[:200]}") - logger.debug(f"图片描述生成成功: {description}") - return description.strip() + # 流式接收响应 + description = "" + async for line in resp.content: + line = line.decode('utf-8').strip() + if not line or line == "data: [DONE]": + continue - except Exception as e: - logger.error(f"生成图片描述失败: {e}") - import traceback - logger.error(f"详细错误: {traceback.format_exc()}") - return "" + if line.startswith("data: "): + try: + data = json.loads(line[6:]) + delta = data.get("choices", [{}])[0].get("delta", {}) + content = delta.get("content", "") + if content: + description += content + except Exception: + pass + + logger.debug(f"图片描述生成成功: {description}") + return description.strip() + + except asyncio.CancelledError: + raise + except (aiohttp.ClientError, asyncio.TimeoutError) as e: + last_error = str(e) + if attempt < max_retries: + logger.warning(f"图片描述网络错误: {e},重试 {attempt + 1}/{max_retries}") + await asyncio.sleep(1 * (attempt + 1)) + continue + except Exception as e: + last_error = str(e) + if attempt < max_retries: + logger.warning(f"图片描述生成异常: {e},重试 {attempt + 1}/{max_retries}") + await asyncio.sleep(1 * (attempt + 1)) + continue + + logger.error(f"生成图片描述失败,已重试 {max_retries + 1} 次: {last_error}") + return "" + + def _collect_tools_with_plugins(self) -> dict: + """收集所有插件的 LLM 工具,并保留来源插件名""" + from utils.plugin_manager import PluginManager + tools_config = self.config.get("tools", {}) + return collect_tools_with_plugins(tools_config, PluginManager().plugins) def _collect_tools(self): """收集所有插件的LLM工具(支持白名单/黑名单过滤)""" from utils.plugin_manager import PluginManager - tools = [] - - # 获取工具过滤配置 tools_config = self.config.get("tools", {}) - mode = tools_config.get("mode", "all") - whitelist = set(tools_config.get("whitelist", [])) - blacklist = set(tools_config.get("blacklist", [])) + return collect_tools(tools_config, PluginManager().plugins) - for plugin in PluginManager().plugins.values(): - if hasattr(plugin, 'get_llm_tools'): - plugin_tools = plugin.get_llm_tools() - if plugin_tools: - for tool in plugin_tools: - tool_name = tool.get("function", {}).get("name", "") + def _get_tool_schema_map(self, tools_map: dict | None = None) -> dict: + """构建工具名到参数 schema 的映射""" + tools_map = tools_map or self._collect_tools_with_plugins() + return get_tool_schema_map(tools_map) - # 根据模式过滤 - if mode == "whitelist": - if tool_name in whitelist: - tools.append(tool) - logger.debug(f"[白名单] 启用工具: {tool_name}") - elif mode == "blacklist": - if tool_name not in blacklist: - tools.append(tool) - else: - logger.debug(f"[黑名单] 禁用工具: {tool_name}") - else: # all - tools.append(tool) - - return tools + def _validate_tool_arguments(self, tool_name: str, arguments: dict, schema: dict) -> tuple: + """轻量校验并补全默认参数""" + return validate_tool_arguments(tool_name, arguments, schema) async def _handle_list_prompts(self, bot, from_wxid: str): """处理人设列表指令""" @@ -698,6 +640,55 @@ class AIChat(PluginBase): return total return 0 + def _extract_text_from_multimodal(self, content) -> str: + """从多模态 content 中提取文本,模型不支持时用于降级""" + if isinstance(content, list): + texts = [item.get("text", "") for item in content if item.get("type") == "text"] + text = "".join(texts).strip() + return text or "[图片]" + if content is None: + return "" + return str(content) + + def _append_group_history_messages(self, messages: list, recent_history: list): + """将群聊历史按 role 追加到 LLM messages""" + for msg in recent_history: + role = msg.get("role") or "user" + msg_nickname = msg.get("nickname", "") + msg_content = msg.get("content", "") + + # 机器人历史回复 + if role == "assistant": + if isinstance(msg_content, list): + msg_content = self._extract_text_from_multimodal(msg_content) + messages.append({ + "role": "assistant", + "content": msg_content + }) + continue + + # 用户历史消息 + if isinstance(msg_content, list): + content_with_nickname = [] + for item in msg_content: + if item.get("type") == "text": + content_with_nickname.append({ + "type": "text", + "text": f"[{msg_nickname}] {item.get('text', '')}" + }) + else: + content_with_nickname.append(item) + + messages.append({ + "role": "user", + "content": content_with_nickname + }) + else: + messages.append({ + "role": "user", + "content": f"[{msg_nickname}] {msg_content}" + }) + async def _handle_context_stats(self, bot, from_wxid: str, user_wxid: str, is_group: bool): """处理上下文统计指令""" try: @@ -867,6 +858,34 @@ class AIChat(PluginBase): await self._handle_context_stats(bot, from_wxid, user_wxid, is_group) return False + # 旧群历史 key 扫描/清理(仅管理员) + if content in ("/旧群历史", "/legacy_history"): + if user_wxid in admins and self.store: + legacy_keys = self.store.find_legacy_group_history_keys() + if legacy_keys: + await bot.send_text( + from_wxid, + f"⚠️ 检测到 {len(legacy_keys)} 个旧版群历史 key(safe_id 写入)。\n" + f"如需清理请发送 /清理旧群历史", + ) + else: + await bot.send_text(from_wxid, "✅ 未发现旧版群历史 key") + else: + await bot.send_text(from_wxid, "❌ 仅管理员可执行该指令") + return False + + if content in ("/清理旧群历史", "/clean_legacy_history"): + if user_wxid in admins and self.store: + legacy_keys = self.store.find_legacy_group_history_keys() + deleted = self.store.delete_legacy_group_history_keys(legacy_keys) + await bot.send_text( + from_wxid, + f"✅ 已清理旧版群历史 key: {deleted} 个", + ) + else: + await bot.send_text(from_wxid, "❌ 仅管理员可执行该指令") + return False + # 检查是否是记忆状态指令(仅管理员) if content == "/记忆状态": if user_wxid in admins: @@ -953,8 +972,9 @@ class AIChat(PluginBase): nickname = await self._get_user_nickname(bot, from_wxid, user_wxid, is_group) # 保存到群组历史记录(所有消息都保存,不管是否回复) - if is_group: - await self._add_to_history(from_wxid, nickname, content) + # 但如果是 AutoReply 触发的,跳过保存(消息已经在正常流程中保存过了) + if is_group and not message.get('_auto_reply_triggered'): + await self._add_to_history(from_wxid, nickname, content, sender_wxid=user_wxid) # 如果不需要回复,直接返回 if not should_reply: @@ -980,7 +1000,9 @@ class AIChat(PluginBase): try: # 获取会话ID并添加用户消息到记忆 chat_id = self._get_chat_id(from_wxid, user_wxid, is_group) - self._add_to_memory(chat_id, "user", actual_content) + # 如果是 AutoReply 触发的,不重复添加用户消息(已在正常流程中添加) + if not message.get('_auto_reply_triggered'): + self._add_to_memory(chat_id, "user", actual_content) # 调用 AI API(带重试机制) max_retries = self.config.get("api", {}).get("max_retries", 2) @@ -1025,7 +1047,7 @@ class AIChat(PluginBase): with open("main_config.toml", "rb") as f: main_config = tomllib.load(f) bot_nickname = main_config.get("Bot", {}).get("nickname", "机器人") - await self._add_to_history(from_wxid, bot_nickname, response) + await self._add_to_history(from_wxid, bot_nickname, response, role="assistant") logger.success(f"AI 回复成功: {response[:50]}...") else: logger.info("AI 回复为空或已通过其他方式发送(如聊天记录)") @@ -1159,36 +1181,8 @@ class AIChat(PluginBase): # 取最近的 N 条消息作为上下文 recent_history = history[-max_context:] if len(history) > max_context else history - # 转换为 AI 消息格式 - for msg in recent_history: - msg_nickname = msg.get("nickname", "") - msg_content = msg.get("content", "") - - # 检查是否是多模态内容(包含图片) - if isinstance(msg_content, list): - # 多模态内容:添加昵称前缀到文本部分 - content_with_nickname = [] - for item in msg_content: - if item.get("type") == "text": - # 在文本前添加昵称 - content_with_nickname.append({ - "type": "text", - "text": f"[{msg_nickname}] {item.get('text', '')}" - }) - else: - # 图片等其他类型直接保留 - content_with_nickname.append(item) - - messages.append({ - "role": "user", - "content": content_with_nickname - }) - else: - # 纯文本内容:简单格式 [昵称] 内容 - messages.append({ - "role": "user", - "content": f"[{msg_nickname}] {msg_content}" - }) + # 转换为 AI 消息格式(按 role) + self._append_group_history_messages(messages, recent_history) else: # 私聊使用原有的 memory 机制 if chat_id: @@ -1199,10 +1193,6 @@ class AIChat(PluginBase): # 添加当前用户消息 messages.append({"role": "user", "content": f"[{nickname}] {user_message}" if is_group and nickname else user_message}) - # 保存用户信息供工具调用使用 - self._current_user_wxid = user_wxid - self._current_is_group = is_group - payload = { "model": api_config["model"], "messages": messages, @@ -1346,7 +1336,7 @@ class AIChat(PluginBase): asyncio.create_task( self._execute_tools_async( tool_calls_data, bot, from_wxid, chat_id, - nickname, is_group, messages + user_wxid, nickname, is_group, messages ) ) # 返回 None 表示工具调用已异步处理,不需要重试 @@ -1369,238 +1359,142 @@ class AIChat(PluginBase): raise Exception(f"API 响应格式错误: {e}") - def _get_history_file(self, chat_id: str) -> Path: - """获取群聊历史记录文件路径""" - if not self.history_dir: - return None - safe_name = chat_id.replace("@", "_").replace(":", "_") - return self.history_dir / f"{safe_name}.json" - - def _get_history_lock(self, chat_id: str) -> asyncio.Lock: - """获取指定会话的锁, 每个会话一把""" - lock = self.history_locks.get(chat_id) - if lock is None: - lock = asyncio.Lock() - self.history_locks[chat_id] = lock - return lock - - def _read_history_file(self, history_file: Path) -> list: - try: - import json - with open(history_file, "r", encoding="utf-8") as f: - return json.load(f) - except FileNotFoundError: - return [] - except Exception as e: - logger.error(f"读取历史记录失败: {e}") - return [] - - def _write_history_file(self, history_file: Path, history: list): - import json - history_file.parent.mkdir(parents=True, exist_ok=True) - temp_file = Path(str(history_file) + ".tmp") - with open(temp_file, "w", encoding="utf-8") as f: - json.dump(history, f, ensure_ascii=False, indent=2) - temp_file.replace(history_file) - - def _use_redis_for_group_history(self) -> bool: - """检查是否使用 Redis 存储群聊历史""" - redis_config = self.config.get("redis", {}) - if not redis_config.get("use_redis_history", True): - return False - redis_cache = get_cache() - return redis_cache and redis_cache.enabled - async def _load_history(self, chat_id: str) -> list: - """异步读取群聊历史, 优先使用 Redis""" - # 优先使用 Redis - if self._use_redis_for_group_history(): - redis_cache = get_cache() - max_history = self.config.get("history", {}).get("max_history", 100) - return redis_cache.get_group_history(chat_id, max_history) - - # 降级到文件存储 - history_file = self._get_history_file(chat_id) - if not history_file: + """异步读取群聊历史(委托 ContextStore)""" + if not self.store: return [] - lock = self._get_history_lock(chat_id) - async with lock: - return self._read_history_file(history_file) + return await self.store.load_group_history(chat_id) - async def _save_history(self, chat_id: str, history: list): - """异步写入群聊历史, 包含长度截断""" - # Redis 模式下不需要单独保存,add_group_message 已经处理 - if self._use_redis_for_group_history(): + async def _add_to_history( + self, + chat_id: str, + nickname: str, + content: str, + image_base64: str = None, + *, + role: str = "user", + sender_wxid: str = None, + ): + """将消息存入群聊历史(委托 ContextStore)""" + if not self.store: return + await self.store.add_group_message( + chat_id, + nickname, + content, + image_base64=image_base64, + role=role, + sender_wxid=sender_wxid, + ) - history_file = self._get_history_file(chat_id) - if not history_file: + async def _add_to_history_with_id( + self, + chat_id: str, + nickname: str, + content: str, + record_id: str, + *, + role: str = "user", + sender_wxid: str = None, + ): + """带ID的历史追加, 便于后续更新(委托 ContextStore)""" + if not self.store: return - - max_history = self.config.get("history", {}).get("max_history", 100) - if len(history) > max_history: - history = history[-max_history:] - - lock = self._get_history_lock(chat_id) - async with lock: - self._write_history_file(history_file, history) - - async def _add_to_history(self, chat_id: str, nickname: str, content: str, image_base64: str = None): - """ - 将消息存入群聊历史 - - Args: - chat_id: 群聊ID - nickname: 用户昵称 - content: 消息内容 - image_base64: 可选的图片base64 - """ - if not self.config.get("history", {}).get("enabled", True): - return - - # 构建消息内容 - if image_base64: - message_content = [ - {"type": "text", "text": content}, - {"type": "image_url", "image_url": {"url": image_base64}} - ] - else: - message_content = content - - # 优先使用 Redis - if self._use_redis_for_group_history(): - redis_cache = get_cache() - redis_config = self.config.get("redis", {}) - ttl = redis_config.get("group_history_ttl", 172800) - redis_cache.add_group_message(chat_id, nickname, message_content, ttl=ttl) - # 裁剪历史 - max_history = self.config.get("history", {}).get("max_history", 100) - redis_cache.trim_group_history(chat_id, max_history) - return - - # 降级到文件存储 - history_file = self._get_history_file(chat_id) - if not history_file: - return - - lock = self._get_history_lock(chat_id) - async with lock: - history = self._read_history_file(history_file) - - message_record = { - "nickname": nickname, - "timestamp": datetime.now().isoformat(), - "content": message_content - } - - history.append(message_record) - max_history = self.config.get("history", {}).get("max_history", 100) - if len(history) > max_history: - history = history[-max_history:] - - self._write_history_file(history_file, history) - - async def _add_to_history_with_id(self, chat_id: str, nickname: str, content: str, record_id: str): - """带ID的历史追加, 便于后续更新""" - if not self.config.get("history", {}).get("enabled", True): - return - - # 优先使用 Redis - if self._use_redis_for_group_history(): - redis_cache = get_cache() - redis_config = self.config.get("redis", {}) - ttl = redis_config.get("group_history_ttl", 172800) - redis_cache.add_group_message(chat_id, nickname, content, record_id=record_id, ttl=ttl) - # 裁剪历史 - max_history = self.config.get("history", {}).get("max_history", 100) - redis_cache.trim_group_history(chat_id, max_history) - return - - # 降级到文件存储 - history_file = self._get_history_file(chat_id) - if not history_file: - return - - lock = self._get_history_lock(chat_id) - async with lock: - history = self._read_history_file(history_file) - message_record = { - "id": record_id, - "nickname": nickname, - "timestamp": datetime.now().isoformat(), - "content": content - } - history.append(message_record) - max_history = self.config.get("history", {}).get("max_history", 100) - if len(history) > max_history: - history = history[-max_history:] - self._write_history_file(history_file, history) + await self.store.add_group_message( + chat_id, + nickname, + content, + record_id=record_id, + role=role, + sender_wxid=sender_wxid, + ) async def _update_history_by_id(self, chat_id: str, record_id: str, new_content: str): - """根据ID更新历史记录""" - if not self.config.get("history", {}).get("enabled", True): + """根据ID更新历史记录(委托 ContextStore)""" + if not self.store: return - - # 优先使用 Redis - if self._use_redis_for_group_history(): - redis_cache = get_cache() - redis_cache.update_group_message_by_id(chat_id, record_id, new_content) - return - - # 降级到文件存储 - history_file = self._get_history_file(chat_id) - if not history_file: - return - - lock = self._get_history_lock(chat_id) - async with lock: - history = self._read_history_file(history_file) - for record in history: - if record.get("id") == record_id: - record["content"] = new_content - break - max_history = self.config.get("history", {}).get("max_history", 100) - if len(history) > max_history: - history = history[-max_history:] - self._write_history_file(history_file, history) + await self.store.update_group_message_by_id(chat_id, record_id, new_content) - async def _execute_tool_and_get_result(self, tool_name: str, arguments: dict, bot, from_wxid: str): + async def _execute_tool_and_get_result( + self, + tool_name: str, + arguments: dict, + bot, + from_wxid: str, + user_wxid: str = None, + is_group: bool = False, + tools_map: dict | None = None, + ): """执行工具调用并返回结果""" from utils.plugin_manager import PluginManager # 添加用户信息到 arguments - arguments["user_wxid"] = getattr(self, "_current_user_wxid", from_wxid) - arguments["is_group"] = getattr(self, "_current_is_group", False) + arguments["user_wxid"] = user_wxid or from_wxid + arguments["is_group"] = bool(is_group) logger.info(f"开始执行工具: {tool_name}") plugins = PluginManager().plugins logger.info(f"检查 {len(plugins)} 个插件") - for plugin_name, plugin in plugins.items(): - logger.debug(f"检查插件: {plugin_name}, 有execute_llm_tool: {hasattr(plugin, 'execute_llm_tool')}") - if hasattr(plugin, 'execute_llm_tool'): + async def _normalize_result(raw, plugin_name: str): + if raw is None: + return None + + if not isinstance(raw, dict): + raw = {"success": True, "message": str(raw)} + else: + raw.setdefault("success", True) + + if raw.get("success"): + logger.success(f"工具执行成功: {tool_name} ({plugin_name})") + else: + logger.warning(f"工具执行失败: {tool_name} ({plugin_name})") + return raw + + # 先尝试直达目标插件(来自 get_llm_tools 的映射) + if tools_map and tool_name in tools_map: + target_plugin_name, _tool_def = tools_map[tool_name] + target_plugin = plugins.get(target_plugin_name) + if target_plugin and hasattr(target_plugin, "execute_llm_tool"): try: - logger.info(f"调用 {plugin_name}.execute_llm_tool") - result = await plugin.execute_llm_tool(tool_name, arguments, bot, from_wxid) - logger.info(f"{plugin_name} 返回: {result}") - if result is not None: - if result.get("success"): - logger.success(f"工具执行成功: {tool_name}") - return result - else: - logger.debug(f"{plugin_name} 不处理此工具,继续检查下一个插件") + logger.info(f"直达调用 {target_plugin_name}.execute_llm_tool") + result = await target_plugin.execute_llm_tool(tool_name, arguments, bot, from_wxid) + logger.info(f"{target_plugin_name} 返回: {result}") + normalized = await _normalize_result(result, target_plugin_name) + if normalized is not None: + return normalized except Exception as e: - logger.error(f"工具执行异常 ({plugin_name}): {tool_name}, {e}") + logger.error(f"工具执行异常 ({target_plugin_name}): {tool_name}, {e}") import traceback logger.error(f"详细错误: {traceback.format_exc()}") + else: + logger.warning(f"工具 {tool_name} 期望插件 {target_plugin_name} 不存在或不支持 execute_llm_tool,回退全量扫描") + + # 回退:遍历所有插件 + for plugin_name, plugin in plugins.items(): + logger.debug(f"检查插件: {plugin_name}, 有execute_llm_tool: {hasattr(plugin, 'execute_llm_tool')}") + if not hasattr(plugin, "execute_llm_tool"): + continue + + try: + logger.info(f"调用 {plugin_name}.execute_llm_tool") + result = await plugin.execute_llm_tool(tool_name, arguments, bot, from_wxid) + logger.info(f"{plugin_name} 返回: {result}") + normalized = await _normalize_result(result, plugin_name) + if normalized is not None: + return normalized + except Exception as e: + logger.error(f"工具执行异常 ({plugin_name}): {tool_name}, {e}") + import traceback + logger.error(f"详细错误: {traceback.format_exc()}") logger.warning(f"未找到工具: {tool_name}") return {"success": False, "message": f"未找到工具: {tool_name}"} async def _execute_tools_async(self, tool_calls_data: list, bot, from_wxid: str, - chat_id: str, nickname: str, is_group: bool, + chat_id: str, user_wxid: str, nickname: str, is_group: bool, messages: list): """ 异步执行工具调用(不阻塞主流程) @@ -1608,14 +1502,14 @@ class AIChat(PluginBase): AI 已经先回复用户,这里异步执行工具,完成后发送结果 支持 need_ai_reply 标记:工具结果回传给 AI 继续对话(保留上下文和人设) """ - import json - try: logger.info(f"开始异步执行 {len(tool_calls_data)} 个工具调用") # 并行执行所有工具 tasks = [] tool_info_list = [] # 保存工具信息用于后续处理 + tools_map = self._collect_tools_with_plugins() + schema_map = self._get_tool_schema_map(tools_map) for tool_call in tool_calls_data: function_name = tool_call.get("function", {}).get("name", "") @@ -1627,13 +1521,31 @@ class AIChat(PluginBase): try: arguments = json.loads(arguments_str) - except: + except Exception: arguments = {} + schema = schema_map.get(function_name) + ok, err, arguments = self._validate_tool_arguments(function_name, arguments, schema) + if not ok: + logger.warning(f"[异步] 工具 {function_name} 参数校验失败: {err}") + try: + await bot.send_text(from_wxid, f"❌ 工具 {function_name} 参数错误: {err}") + except Exception: + pass + continue + logger.info(f"[异步] 准备执行工具: {function_name}, 参数: {arguments}") # 创建异步任务 - task = self._execute_tool_and_get_result(function_name, arguments, bot, from_wxid) + task = self._execute_tool_and_get_result( + function_name, + arguments, + bot, + from_wxid, + user_wxid=user_wxid, + is_group=is_group, + tools_map=tools_map, + ) tasks.append(task) tool_info_list.append({ "tool_call_id": tool_call_id, @@ -1656,38 +1568,45 @@ class AIChat(PluginBase): if isinstance(result, Exception): logger.error(f"[异步] 工具 {function_name} 执行异常: {result}") - # 发送错误提示 - await bot.send_text(from_wxid, f"❌ {function_name} 执行失败") + try: + await bot.send_text(from_wxid, f"❌ {function_name} 执行失败: {result}") + except Exception: + pass continue - if result and result.get("success"): + tool_result = ToolResult.from_raw(result) + if not tool_result: + continue + + if tool_result.success: logger.success(f"[异步] 工具 {function_name} 执行成功") - - # 检查是否需要 AI 基于工具结果继续回复 - if result.get("need_ai_reply"): - need_ai_reply_results.append({ - "tool_call_id": tool_call_id, - "function_name": function_name, - "result": result.get("message", "") - }) - continue # 不直接发送,等待 AI 处理 - - # 如果工具没有自己发送内容,且有消息需要发送 - if not result.get("already_sent") and result.get("message"): - # 某些工具可能需要发送结果消息 - msg = result.get("message", "") - if msg and not result.get("no_reply"): - # 检查是否需要发送文本结果 - if result.get("send_result_text"): - await bot.send_text(from_wxid, msg) - - # 保存工具结果到记忆(可选) - if result.get("save_to_memory") and chat_id: - self._add_to_memory(chat_id, "assistant", f"[工具 {function_name} 结果]: {result.get('message', '')}") else: - logger.warning(f"[异步] 工具 {function_name} 执行失败: {result}") - if result and result.get("message"): - await bot.send_text(from_wxid, f"❌ {result.get('message')}") + logger.warning(f"[异步] 工具 {function_name} 执行失败") + + # 需要 AI 继续处理的结果 + if tool_result.need_ai_reply: + need_ai_reply_results.append({ + "tool_call_id": tool_call_id, + "function_name": function_name, + "result": tool_result.message + }) + continue + + # 工具成功且需要回文本时发送 + if tool_result.success and not tool_result.already_sent and tool_result.message and not tool_result.no_reply: + if tool_result.send_result_text: + await bot.send_text(from_wxid, tool_result.message) + + # 工具失败默认回一条错误提示 + if not tool_result.success and tool_result.message and not tool_result.no_reply: + try: + await bot.send_text(from_wxid, f"❌ {tool_result.message}") + except Exception: + pass + + # 保存工具结果到记忆(可选) + if tool_result.save_to_memory and chat_id: + self._add_to_memory(chat_id, "assistant", f"[工具 {function_name} 结果]: {tool_result.message}") # 如果有需要 AI 回复的工具结果,调用 AI 继续对话 if need_ai_reply_results: @@ -1833,21 +1752,21 @@ class AIChat(PluginBase): pass async def _execute_tools_async_with_image(self, tool_calls_data: list, bot, from_wxid: str, - chat_id: str, nickname: str, is_group: bool, + chat_id: str, user_wxid: str, nickname: str, is_group: bool, messages: list, image_base64: str): """ 异步执行工具调用(带图片参数,用于图生图等场景) AI 已经先回复用户,这里异步执行工具,完成后发送结果 """ - import json - try: logger.info(f"[异步-图片] 开始执行 {len(tool_calls_data)} 个工具调用") # 并行执行所有工具 tasks = [] tool_info_list = [] + tools_map = self._collect_tools_with_plugins() + schema_map = self._get_tool_schema_map(tools_map) for tool_call in tool_calls_data: function_name = tool_call.get("function", {}).get("name", "") @@ -1859,7 +1778,7 @@ class AIChat(PluginBase): try: arguments = json.loads(arguments_str) - except: + except Exception: arguments = {} # 如果是图生图工具,添加图片 base64 @@ -1867,9 +1786,27 @@ class AIChat(PluginBase): arguments["image_base64"] = image_base64 logger.info(f"[异步-图片] 图生图工具,已添加图片数据") + schema = schema_map.get(function_name) + ok, err, arguments = self._validate_tool_arguments(function_name, arguments, schema) + if not ok: + logger.warning(f"[异步-图片] 工具 {function_name} 参数校验失败: {err}") + try: + await bot.send_text(from_wxid, f"❌ 工具 {function_name} 参数错误: {err}") + except Exception: + pass + continue + logger.info(f"[异步-图片] 准备执行工具: {function_name}") - task = self._execute_tool_and_get_result(function_name, arguments, bot, from_wxid) + task = self._execute_tool_and_get_result( + function_name, + arguments, + bot, + from_wxid, + user_wxid=user_wxid, + is_group=is_group, + tools_map=tools_map, + ) tasks.append(task) tool_info_list.append({ "tool_call_id": tool_call_id, @@ -1887,23 +1824,33 @@ class AIChat(PluginBase): if isinstance(result, Exception): logger.error(f"[异步-图片] 工具 {function_name} 执行异常: {result}") - await bot.send_text(from_wxid, f"❌ {function_name} 执行失败") + try: + await bot.send_text(from_wxid, f"❌ {function_name} 执行失败: {result}") + except Exception: + pass continue - if result and result.get("success"): + tool_result = ToolResult.from_raw(result) + if not tool_result: + continue + + if tool_result.success: logger.success(f"[异步-图片] 工具 {function_name} 执行成功") - - if not result.get("already_sent") and result.get("message"): - msg = result.get("message", "") - if msg and not result.get("no_reply") and result.get("send_result_text"): - await bot.send_text(from_wxid, msg) - - if result.get("save_to_memory") and chat_id: - self._add_to_memory(chat_id, "assistant", f"[工具 {function_name} 结果]: {result.get('message', '')}") else: - logger.warning(f"[异步-图片] 工具 {function_name} 执行失败: {result}") - if result and result.get("message"): - await bot.send_text(from_wxid, f"❌ {result.get('message')}") + logger.warning(f"[异步-图片] 工具 {function_name} 执行失败") + + if tool_result.success and not tool_result.already_sent and tool_result.message and not tool_result.no_reply: + if tool_result.send_result_text: + await bot.send_text(from_wxid, tool_result.message) + + if not tool_result.success and tool_result.message and not tool_result.no_reply: + try: + await bot.send_text(from_wxid, f"❌ {tool_result.message}") + except Exception: + pass + + if tool_result.save_to_memory and chat_id: + self._add_to_memory(chat_id, "assistant", f"[工具 {function_name} 结果]: {tool_result.message}") logger.info(f"[异步-图片] 所有工具执行完成") @@ -2125,7 +2072,7 @@ class AIChat(PluginBase): with open("main_config.toml", "rb") as f: main_config = tomllib.load(f) bot_nickname = main_config.get("Bot", {}).get("nickname", "机器人") - await self._add_to_history(from_wxid, bot_nickname, response) + await self._add_to_history(from_wxid, bot_nickname, response, role="assistant") logger.success(f"AI回复成功: {response[:50]}...") return False @@ -2243,7 +2190,7 @@ class AIChat(PluginBase): with open("main_config.toml", "rb") as f: main_config = tomllib.load(f) bot_nickname = main_config.get("Bot", {}).get("nickname", "机器人") - await self._add_to_history(from_wxid, bot_nickname, response) + await self._add_to_history(from_wxid, bot_nickname, response, role="assistant") logger.success(f"[聊天记录] AI 回复成功: {response[:50]}...") else: await bot.send_text(from_wxid, "❌ AI 回复生成失败") @@ -2318,7 +2265,7 @@ class AIChat(PluginBase): await self._add_to_history(from_wxid, nickname, f"[发送了一个视频] {user_question}") # 调用主AI生成回复(使用现有的 _call_ai_api 方法,继承完整上下文) - response = await self._call_ai_api(combined_message, chat_id, from_wxid, is_group, nickname) + response = await self._call_ai_api(combined_message, bot, from_wxid, chat_id, nickname, user_wxid, is_group) if response: await bot.send_text(from_wxid, response) @@ -2329,7 +2276,7 @@ class AIChat(PluginBase): with open("main_config.toml", "rb") as f: main_config = tomllib.load(f) bot_nickname = main_config.get("Bot", {}).get("nickname", "机器人") - await self._add_to_history(from_wxid, bot_nickname, response) + await self._add_to_history(from_wxid, bot_nickname, response, role="assistant") logger.success(f"[视频识别] 主AI回复成功: {response[:50]}...") else: await bot.send_text(from_wxid, "❌ AI 回复生成失败") @@ -2882,27 +2829,40 @@ class AIChat(PluginBase): if nickname: system_content += f"\n当前对话用户的昵称是:{nickname}" + # 加载持久记忆(与文本模式一致) + memory_chat_id = from_wxid if is_group else user_wxid + if memory_chat_id: + persistent_memories = self._get_persistent_memories(memory_chat_id) + if persistent_memories: + system_content += "\n\n【持久记忆】以下是用户要求你记住的重要信息:\n" + for m in persistent_memories: + mem_time = m['time'][:10] if m['time'] else "" + system_content += f"- [{mem_time}] {m['nickname']}: {m['content']}\n" + messages = [{"role": "system", "content": system_content}] - # 添加历史记忆 - if chat_id: - memory_messages = self._get_memory_messages(chat_id) - if memory_messages and len(memory_messages) > 1: - messages.extend(memory_messages[:-1]) + # 添加历史上下文 + if is_group and from_wxid: + history = await self._load_history(from_wxid) + max_context = self.config.get("history", {}).get("max_context", 50) + recent_history = history[-max_context:] if len(history) > max_context else history + self._append_group_history_messages(messages, recent_history) + else: + if chat_id: + memory_messages = self._get_memory_messages(chat_id) + if memory_messages and len(memory_messages) > 1: + messages.extend(memory_messages[:-1]) # 添加当前用户消息(带图片) + text_value = f"[{nickname}] {user_message}" if is_group and nickname else user_message messages.append({ "role": "user", "content": [ - {"type": "text", "text": user_message}, + {"type": "text", "text": text_value}, {"type": "image_url", "image_url": {"url": image_base64}} ] }) - # 保存用户信息供工具调用使用 - self._current_user_wxid = user_wxid - self._current_is_group = is_group - payload = { "model": api_config["model"], "messages": messages, @@ -3031,7 +2991,7 @@ class AIChat(PluginBase): asyncio.create_task( self._execute_tools_async_with_image( tool_calls_data, bot, from_wxid, chat_id, - nickname, is_group, messages, image_base64 + user_wxid, nickname, is_group, messages, image_base64 ) ) return "" @@ -3211,14 +3171,25 @@ class AIChat(PluginBase): while True: try: task = await self.image_desc_queue.get() + except asyncio.CancelledError: + logger.info("图片描述工作协程收到取消信号,退出") + break + + try: await self._generate_and_update_image_description( task["bot"], task["from_wxid"], task["nickname"], task["cdnbigimgurl"], task["aeskey"], task["is_emoji"], task["placeholder_id"], task["config"] ) - self.image_desc_queue.task_done() + except asyncio.CancelledError: + raise except Exception as e: logger.error(f"图片描述工作协程异常: {e}") + finally: + try: + self.image_desc_queue.task_done() + except ValueError: + pass async def _generate_and_update_image_description(self, bot, from_wxid: str, nickname: str, cdnbigimgurl: str, aeskey: str, is_emoji: bool, @@ -3247,6 +3218,8 @@ class AIChat(PluginBase): await self._update_history_by_id(from_wxid, placeholder_id, "[图片]") logger.warning(f"图片描述生成失败") + except asyncio.CancelledError: + raise except Exception as e: logger.error(f"异步生成图片描述失败: {e}") await self._update_history_by_id(from_wxid, placeholder_id, "[图片]") diff --git a/plugins/AIChat_Gemini.zip b/plugins/AIChat_Gemini.zip deleted file mode 100644 index 5ddb8921cdff5ef2dd4b1bca51a78e42c4346bff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117802 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

HL!jl&Am_NO=dTiQk9K4LF<2GET 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 str: + async def _get_recent_context_for_reply(self, group_id: str) -> str: """获取最近的上下文用于生成回复""" try: - history = await self._get_history(chat_id) + history = await self._get_history(group_id) if not history: return "" @@ -353,35 +367,10 @@ class AutoReply(PluginBase): count = self.config.get('context', {}).get('messages_count', 5) recent = history[-count:] if len(history) > count else history - # 构建上下文摘要 - context_lines = [] - for record in recent: - nickname = record.get('nickname', '未知') - content = record.get('content', '') - if isinstance(content, list): - # 多模态内容,提取文本 - for item in content: - if item.get('type') == 'text': - content = item.get('text', '') - break - else: - content = '[图片]' - if len(content) > 50: - content = content[:50] + "..." - context_lines.append(f"{nickname}: {content}") - - # 返回最后一条消息作为触发内容(AIChat 会读取完整历史) - if recent: - last = recent[-1] - last_content = last.get('content', '') - if isinstance(last_content, list): - for item in last_content: - if item.get('type') == 'text': - return item.get('text', '') - return '[图片]' - return last_content - - return "" + # 自动回复触发不再把最后一条用户消息再次发给 AI, + # 避免在上下文里出现“同一句话重复两遍”的错觉。 + # AIChat 会读取完整历史 recent_history。 + return "(自动回复触发)请基于最近群聊内容,自然地回复一句,不要复述提示本身。" except Exception as e: logger.error(f"[AutoReply] 获取上下文失败: {e}") @@ -389,12 +378,13 @@ class AutoReply(PluginBase): async def _judge_with_small_model(self, from_wxid: str, content: str) -> JudgeResult: """使用小模型判断是否需要回复""" - chat_id = self._normalize_chat_id(from_wxid) - chat_state = self._get_chat_state(chat_id) + group_id = from_wxid + state_id = self._normalize_chat_id(group_id) + chat_state = self._get_chat_state(state_id) # 获取最近消息历史 - recent_messages = await self._get_recent_messages(chat_id) - last_bot_reply = await self._get_last_bot_reply(chat_id) + recent_messages = await self._get_recent_messages(group_id) + last_bot_reply = await self._get_last_bot_reply(group_id) # 构建判断提示词 reasoning_part = ',\n "reasoning": "简短分析原因(20字内)"' if self.config["judge"]["include_reasoning"] else "" @@ -403,7 +393,7 @@ class AutoReply(PluginBase): ## 当前状态 - 精力: {chat_state.energy:.1f}/1.0 -- 上次发言: {self._get_minutes_since_last_reply(chat_id)}分钟前 +- 上次发言: {self._get_minutes_since_last_reply(state_id)}分钟前 ## 最近对话 {recent_messages} @@ -531,6 +521,13 @@ class AutoReply(PluginBase): if not aichat_plugin: return [] + # 优先使用 AIChat 的统一 ContextStore + if hasattr(aichat_plugin, "store") and aichat_plugin.store: + try: + return await aichat_plugin.store.load_group_history(chat_id) + except Exception as e: + logger.debug(f"[AutoReply] ContextStore 获取历史失败: {e}") + # 优先使用 Redis(与 AIChat 保持一致) try: from utils.redis_cache import get_cache @@ -548,7 +545,8 @@ class AutoReply(PluginBase): # 降级到文件存储 if hasattr(aichat_plugin, 'history_dir') and aichat_plugin.history_dir: - history_file = aichat_plugin.history_dir / f"{chat_id}.json" + safe_id = (chat_id or "").replace("@", "_").replace(":", "_") + history_file = aichat_plugin.history_dir / f"{safe_id}.json" if history_file.exists(): with open(history_file, "r", encoding="utf-8") as f: return json.load(f) @@ -558,10 +556,10 @@ class AutoReply(PluginBase): return [] - async def _get_recent_messages(self, chat_id: str) -> str: - """获取最近消息历史""" + async def _get_recent_messages(self, group_id: str) -> str: + """获取最近消息历史(群聊)""" try: - history = await self._get_history(chat_id) + history = await self._get_history(group_id) if not history: return "暂无对话历史" @@ -572,6 +570,12 @@ class AutoReply(PluginBase): for record in recent: nickname = record.get('nickname', '未知') content = record.get('content', '') + if isinstance(content, list): + text_parts = [] + for item in content: + if item.get("type") == "text": + text_parts.append(item.get("text", "")) + content = "".join(text_parts).strip() or "[图片]" # 限制单条消息长度 if len(content) > 100: content = content[:100] + "..." @@ -584,17 +588,23 @@ class AutoReply(PluginBase): return "暂无对话历史" - async def _get_last_bot_reply(self, chat_id: str) -> Optional[str]: - """获取上次机器人回复""" + async def _get_last_bot_reply(self, group_id: str) -> Optional[str]: + """获取上次机器人回复(群聊)""" try: - history = await self._get_history(chat_id) + history = await self._get_history(group_id) if not history: return None # 从后往前查找机器人回复 for record in reversed(history): - if record.get('nickname') == self.bot_nickname: + if record.get('role') == 'assistant' or record.get('nickname') == self.bot_nickname: content = record.get('content', '') + if isinstance(content, list): + text_parts = [] + for item in content: + if item.get("type") == "text": + text_parts.append(item.get("text", "")) + content = "".join(text_parts).strip() or "[图片]" if len(content) > 100: content = content[:100] + "..." return content diff --git a/plugins/SignInPlugin/main.py b/plugins/SignInPlugin/main.py index 6010334..187601d 100644 --- a/plugins/SignInPlugin/main.py +++ b/plugins/SignInPlugin/main.py @@ -1606,9 +1606,8 @@ class SignInPlugin(PluginBase): return {"success": True, "message": f"城市注册请求已处理: {city}"} else: - return {"success": False, "message": "未知的工具名称"} + return None except Exception as e: logger.error(f"LLM工具执行失败: {e}") return {"success": False, "message": f"执行失败: {str(e)}"} - diff --git a/plugins/Weather/main.py b/plugins/Weather/main.py index e79c905..53d87ce 100644 --- a/plugins/Weather/main.py +++ b/plugins/Weather/main.py @@ -322,7 +322,7 @@ class WeatherPlugin(PluginBase): """执行LLM工具调用,供AIChat插件调用""" try: if tool_name != "query_weather": - return {"success": False, "message": "未知的工具名称"} + return None # 从 arguments 中获取用户信息 user_wxid = arguments.get("user_wxid", from_wxid) diff --git a/plugins/ZImageTurbo/main.py b/plugins/ZImageTurbo/main.py index a367202..41e17e5 100644 --- a/plugins/ZImageTurbo/main.py +++ b/plugins/ZImageTurbo/main.py @@ -345,7 +345,7 @@ class ZImageTurbo(PluginBase): async def execute_llm_tool(self, tool_name: str, arguments: dict, bot: WechatHookClient, from_wxid: str) -> dict: """执行LLM工具调用,供AIChat插件调用""" if tool_name != "generate_image": - return {"success": False, "message": "未知的工具名称"} + return None try: prompt = arguments.get("prompt", "") diff --git a/utils/context_store.py b/utils/context_store.py new file mode 100644 index 0000000..f00e484 --- /dev/null +++ b/utils/context_store.py @@ -0,0 +1,470 @@ +""" +上下文/存储统一封装 + +提供统一的会话上下文读写接口: +- 私聊/单人会话 memory(优先 Redis,降级内存) +- 群聊 history(优先 Redis,降级文件) +- 持久记忆 sqlite + +AIChat 只需要通过本模块读写消息,不再关心介质细节。 +""" + +from __future__ import annotations + +import asyncio +import json +import sqlite3 +from dataclasses import dataclass +from datetime import datetime +from pathlib import Path +from typing import Any, Dict, List, Optional + +from loguru import logger + +from utils.redis_cache import get_cache + + +def _safe_chat_id(chat_id: str) -> str: + return (chat_id or "").replace("@", "_").replace(":", "_") + + +def _extract_text_from_multimodal(content: Any) -> str: + if isinstance(content, str): + return content + if isinstance(content, list): + parts = [] + for item in content: + if isinstance(item, dict) and item.get("type") == "text": + parts.append(item.get("text", "")) + return "".join(parts).strip() + return str(content) + + +@dataclass +class HistoryRecord: + role: str = "user" + nickname: str = "" + content: Any = "" + timestamp: Any = None + wxid: Optional[str] = None + id: Optional[str] = None + + @classmethod + def from_raw(cls, raw: Dict[str, Any]) -> "HistoryRecord": + role = raw.get("role") or "user" + nickname = raw.get("nickname") or raw.get("SenderNickname") or "" + content = raw.get("content") if "content" in raw else raw.get("Content", "") + ts = raw.get("timestamp") or raw.get("time") or raw.get("CreateTime") + wxid = raw.get("wxid") or raw.get("SenderWxid") + rid = raw.get("id") or raw.get("msgid") + return cls(role=role, nickname=nickname, content=content, timestamp=ts, wxid=wxid, id=rid) + + def to_dict(self) -> Dict[str, Any]: + d = { + "role": self.role or "user", + "nickname": self.nickname, + "content": self.content, + } + if self.timestamp is not None: + d["timestamp"] = self.timestamp + if self.wxid: + d["wxid"] = self.wxid + if self.id: + d["id"] = self.id + return d + + +class ContextStore: + """ + 统一上下文存储。 + + Args: + config: AIChat 配置 dict + history_dir: 历史文件目录(群聊降级) + memory_fallback: AIChat 内存 dict(私聊降级) + history_locks: AIChat locks dict(文件写入) + persistent_db_path: sqlite 文件路径 + """ + + def __init__( + self, + config: Dict[str, Any], + history_dir: Optional[Path], + memory_fallback: Dict[str, List[Dict[str, Any]]], + history_locks: Dict[str, asyncio.Lock], + persistent_db_path: Optional[Path], + ): + self.config = config or {} + self.history_dir = history_dir + self.memory_fallback = memory_fallback + self.history_locks = history_locks + self.persistent_db_path = persistent_db_path + + # ------------------ 私聊 memory ------------------ + + def _use_redis_for_memory(self) -> bool: + redis_config = self.config.get("redis", {}) + if not redis_config.get("use_redis_history", True): + return False + redis_cache = get_cache() + return bool(redis_cache and redis_cache.enabled) + + def add_private_message( + self, + chat_id: str, + role: str, + content: Any, + *, + image_base64: str = None, + nickname: str = "", + sender_wxid: str = None, + ) -> None: + if not self.config.get("memory", {}).get("enabled", False): + return + + if image_base64: + message_content = [ + {"type": "text", "text": _extract_text_from_multimodal(content)}, + {"type": "image_url", "image_url": {"url": image_base64}}, + ] + else: + message_content = content + + redis_config = self.config.get("redis", {}) + if self._use_redis_for_memory(): + redis_cache = get_cache() + ttl = redis_config.get("chat_history_ttl", 86400) + try: + redis_cache.add_chat_message( + chat_id, + role, + message_content, + nickname=nickname, + sender_wxid=sender_wxid, + ttl=ttl, + ) + max_messages = self.config.get("memory", {}).get("max_messages", 20) + redis_cache.trim_chat_history(chat_id, max_messages) + return + except Exception as e: + logger.debug(f"[ContextStore] Redis private history 写入失败: {e}") + + if chat_id not in self.memory_fallback: + self.memory_fallback[chat_id] = [] + self.memory_fallback[chat_id].append({"role": role, "content": message_content}) + max_messages = self.config.get("memory", {}).get("max_messages", 20) + if len(self.memory_fallback[chat_id]) > max_messages: + self.memory_fallback[chat_id] = self.memory_fallback[chat_id][-max_messages:] + + def get_private_messages(self, chat_id: str) -> List[Dict[str, Any]]: + if not self.config.get("memory", {}).get("enabled", False): + return [] + + if self._use_redis_for_memory(): + redis_cache = get_cache() + max_messages = self.config.get("memory", {}).get("max_messages", 20) + try: + history = redis_cache.get_chat_history(chat_id, max_messages) + return [HistoryRecord.from_raw(h).to_dict() for h in history] + except Exception as e: + logger.debug(f"[ContextStore] Redis private history 读取失败: {e}") + + return self.memory_fallback.get(chat_id, []) + + def clear_private_messages(self, chat_id: str) -> None: + if self._use_redis_for_memory(): + redis_cache = get_cache() + try: + redis_cache.clear_chat_history(chat_id) + except Exception: + pass + self.memory_fallback.pop(chat_id, None) + + # ------------------ 群聊 history ------------------ + + def _use_redis_for_group_history(self) -> bool: + redis_config = self.config.get("redis", {}) + if not redis_config.get("use_redis_history", True): + return False + redis_cache = get_cache() + return bool(redis_cache and redis_cache.enabled) + + def _get_history_file(self, chat_id: str) -> Optional[Path]: + if not self.history_dir: + return None + return self.history_dir / f"{_safe_chat_id(chat_id)}.json" + + def _get_history_lock(self, chat_id: str) -> asyncio.Lock: + lock = self.history_locks.get(chat_id) + if lock is None: + lock = asyncio.Lock() + self.history_locks[chat_id] = lock + return lock + + def _read_history_file(self, history_file: Path) -> List[Dict[str, Any]]: + try: + with open(history_file, "r", encoding="utf-8") as f: + return json.load(f) + except FileNotFoundError: + return [] + except Exception as e: + logger.error(f"读取历史记录失败: {history_file}, {e}") + return [] + + def _write_history_file(self, history_file: Path, history: List[Dict[str, Any]]) -> None: + history_file.parent.mkdir(parents=True, exist_ok=True) + temp_file = Path(str(history_file) + ".tmp") + with open(temp_file, "w", encoding="utf-8") as f: + json.dump(history, f, ensure_ascii=False, indent=2) + temp_file.replace(history_file) + + async def load_group_history(self, chat_id: str) -> List[Dict[str, Any]]: + if not self.config.get("history", {}).get("enabled", True): + return [] + + if self._use_redis_for_group_history(): + redis_cache = get_cache() + max_history = self.config.get("history", {}).get("max_history", 100) + try: + history = redis_cache.get_group_history(chat_id, max_history) + return [HistoryRecord.from_raw(h).to_dict() for h in history] + except Exception as e: + logger.debug(f"[ContextStore] Redis group history 读取失败: {e}") + + history_file = self._get_history_file(chat_id) + if not history_file: + return [] + + lock = self._get_history_lock(chat_id) + async with lock: + raw_history = self._read_history_file(history_file) + return [HistoryRecord.from_raw(h).to_dict() for h in raw_history] + + async def add_group_message( + self, + chat_id: str, + nickname: str, + content: Any, + *, + record_id: str = None, + image_base64: str = None, + role: str = "user", + sender_wxid: str = None, + ) -> None: + if not self.config.get("history", {}).get("enabled", True): + return + + if image_base64: + message_content = [ + {"type": "text", "text": _extract_text_from_multimodal(content)}, + {"type": "image_url", "image_url": {"url": image_base64}}, + ] + else: + message_content = content + + if self._use_redis_for_group_history(): + redis_cache = get_cache() + redis_config = self.config.get("redis", {}) + ttl = redis_config.get("group_history_ttl", 172800) + try: + redis_cache.add_group_message( + chat_id, + nickname, + message_content, + record_id=record_id, + role=role, + sender_wxid=sender_wxid, + ttl=ttl, + ) + max_history = self.config.get("history", {}).get("max_history", 100) + redis_cache.trim_group_history(chat_id, max_history) + return + except Exception as e: + logger.debug(f"[ContextStore] Redis group history 写入失败: {e}") + + history_file = self._get_history_file(chat_id) + if not history_file: + return + + lock = self._get_history_lock(chat_id) + async with lock: + history = self._read_history_file(history_file) + record = HistoryRecord( + role=role or "user", + nickname=nickname, + content=message_content, + timestamp=datetime.now().isoformat(), + wxid=sender_wxid, + id=record_id, + ) + history.append(record.to_dict()) + max_history = self.config.get("history", {}).get("max_history", 100) + if len(history) > max_history: + history = history[-max_history:] + self._write_history_file(history_file, history) + + async def update_group_message_by_id(self, chat_id: str, record_id: str, new_content: Any) -> None: + if not self.config.get("history", {}).get("enabled", True): + return + + if self._use_redis_for_group_history(): + redis_cache = get_cache() + try: + redis_cache.update_group_message_by_id(chat_id, record_id, new_content) + return + except Exception as e: + logger.debug(f"[ContextStore] Redis group history 更新失败: {e}") + + history_file = self._get_history_file(chat_id) + if not history_file: + return + + lock = self._get_history_lock(chat_id) + async with lock: + history = self._read_history_file(history_file) + for rec in history: + if rec.get("id") == record_id: + rec["content"] = new_content + break + max_history = self.config.get("history", {}).get("max_history", 100) + if len(history) > max_history: + history = history[-max_history:] + self._write_history_file(history_file, history) + + # ------------------ 持久记忆 sqlite ------------------ + + def init_persistent_memory_db(self) -> Optional[Path]: + if not self.persistent_db_path: + return None + + self.persistent_db_path.parent.mkdir(exist_ok=True, parents=True) + conn = sqlite3.connect(self.persistent_db_path) + cursor = conn.cursor() + cursor.execute( + """ + CREATE TABLE IF NOT EXISTS memories ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + chat_id TEXT NOT NULL, + chat_type TEXT NOT NULL, + user_wxid TEXT NOT NULL, + user_nickname TEXT, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + cursor.execute("CREATE INDEX IF NOT EXISTS idx_chat_id ON memories(chat_id)") + conn.commit() + conn.close() + logger.info(f"持久记忆数据库已初始化: {self.persistent_db_path}") + return self.persistent_db_path + + def add_persistent_memory( + self, + chat_id: str, + chat_type: str, + user_wxid: str, + user_nickname: str, + content: str, + ) -> int: + if not self.persistent_db_path: + return -1 + + conn = sqlite3.connect(self.persistent_db_path) + cursor = conn.cursor() + cursor.execute( + """ + INSERT INTO memories (chat_id, chat_type, user_wxid, user_nickname, content) + VALUES (?, ?, ?, ?, ?) + """, + (chat_id, chat_type, user_wxid, user_nickname, content), + ) + memory_id = cursor.lastrowid + conn.commit() + conn.close() + return memory_id + + def get_persistent_memories(self, chat_id: str) -> List[Dict[str, Any]]: + if not self.persistent_db_path: + return [] + + conn = sqlite3.connect(self.persistent_db_path) + cursor = conn.cursor() + cursor.execute( + """ + SELECT id, user_nickname, content, created_at + FROM memories + WHERE chat_id = ? + ORDER BY created_at ASC + """, + (chat_id,), + ) + rows = cursor.fetchall() + conn.close() + return [ + {"id": r[0], "nickname": r[1], "content": r[2], "time": r[3]} + for r in rows + ] + + def delete_persistent_memory(self, chat_id: str, memory_id: int) -> bool: + if not self.persistent_db_path: + return False + + conn = sqlite3.connect(self.persistent_db_path) + cursor = conn.cursor() + cursor.execute( + "DELETE FROM memories WHERE id = ? AND chat_id = ?", + (memory_id, chat_id), + ) + deleted = cursor.rowcount > 0 + conn.commit() + conn.close() + return deleted + + def clear_persistent_memories(self, chat_id: str) -> int: + if not self.persistent_db_path: + return 0 + + conn = sqlite3.connect(self.persistent_db_path) + cursor = conn.cursor() + cursor.execute("DELETE FROM memories WHERE chat_id = ?", (chat_id,)) + deleted_count = cursor.rowcount + conn.commit() + conn.close() + return deleted_count + + # ------------------ 旧数据扫描/清理 ------------------ + + def find_legacy_group_history_keys(self) -> List[str]: + """ + 发现旧版本使用 safe_id 写入的 group_history key。 + + Returns: + legacy_keys 列表(不删除) + """ + redis_cache = get_cache() + if not redis_cache or not redis_cache.enabled: + return [] + + try: + keys = redis_cache.client.keys("group_history:*") + legacy = [] + for k in keys or []: + # 新 key 一般包含 @chatroom;旧 safe_id 不包含 @ + if "@chatroom" not in k and "_" in k: + legacy.append(k) + return legacy + except Exception as e: + logger.debug(f"[ContextStore] 扫描 legacy group_history keys 失败: {e}") + return [] + + def delete_legacy_group_history_keys(self, legacy_keys: List[str]) -> int: + """删除给定 legacy key 列表""" + redis_cache = get_cache() + if not redis_cache or not redis_cache.enabled or not legacy_keys: + return 0 + try: + return redis_cache.client.delete(*legacy_keys) + except Exception as e: + logger.debug(f"[ContextStore] 删除 legacy group_history keys 失败: {e}") + return 0 diff --git a/utils/llm_tooling.py b/utils/llm_tooling.py new file mode 100644 index 0000000..a8d845c --- /dev/null +++ b/utils/llm_tooling.py @@ -0,0 +1,183 @@ +""" +LLM 工具体系公共模块 + +统一工具收集、参数校验与执行结果结构,供 AIChat 等插件使用。 +""" + +from __future__ import annotations + +import json +from dataclasses import dataclass +from typing import Any, Dict, List, Optional, Tuple + +from loguru import logger + + +@dataclass +class ToolResult: + """统一的工具执行结果结构""" + + success: bool = True + message: str = "" + need_ai_reply: bool = False + already_sent: bool = False + send_result_text: bool = False + no_reply: bool = False + save_to_memory: bool = False + + @classmethod + def from_raw(cls, raw: Any) -> Optional["ToolResult"]: + if raw is None: + return None + + if not isinstance(raw, dict): + return cls(success=True, message=str(raw)) + + msg = raw.get("message", "") + if not isinstance(msg, str): + try: + msg = json.dumps(msg, ensure_ascii=False) + except Exception: + msg = str(msg) + + return cls( + success=bool(raw.get("success", True)), + message=msg, + need_ai_reply=bool(raw.get("need_ai_reply", False)), + already_sent=bool(raw.get("already_sent", False)), + send_result_text=bool(raw.get("send_result_text", False)), + no_reply=bool(raw.get("no_reply", False)), + save_to_memory=bool(raw.get("save_to_memory", False)), + ) + + +def collect_tools_with_plugins( + tools_config: Dict[str, Any], + plugins: Dict[str, Any], +) -> Dict[str, Tuple[str, Dict[str, Any]]]: + """ + 收集所有插件的 LLM 工具,并保留来源插件名。 + + Args: + tools_config: AIChat 配置中的 [tools] 节 + plugins: PluginManager().plugins 映射 + + Returns: + {tool_name: (plugin_name, tool_dict)} + """ + tools_by_name: Dict[str, Tuple[str, Dict[str, Any]]] = {} + + mode = tools_config.get("mode", "all") + whitelist = set(tools_config.get("whitelist", [])) + blacklist = set(tools_config.get("blacklist", [])) + + for plugin_name, plugin in plugins.items(): + if not hasattr(plugin, "get_llm_tools"): + continue + + plugin_tools = plugin.get_llm_tools() or [] + for tool in plugin_tools: + tool_name = tool.get("function", {}).get("name", "") + if not tool_name: + continue + + if mode == "whitelist" and tool_name not in whitelist: + continue + if mode == "blacklist" and tool_name in blacklist: + logger.debug(f"[黑名单] 禁用工具: {tool_name}") + continue + + if tool_name in tools_by_name: + logger.warning(f"重复工具名 {tool_name} 来自 {plugin_name},已忽略") + continue + + tools_by_name[tool_name] = (plugin_name, tool) + if mode == "whitelist": + logger.debug(f"[白名单] 启用工具: {tool_name}") + + return tools_by_name + + +def collect_tools( + tools_config: Dict[str, Any], + plugins: Dict[str, Any], +) -> List[Dict[str, Any]]: + """仅返回工具定义列表""" + return [item[1] for item in collect_tools_with_plugins(tools_config, plugins).values()] + + +def get_tool_schema_map( + tools_map: Dict[str, Tuple[str, Dict[str, Any]]], +) -> Dict[str, Dict[str, Any]]: + """构建工具名到参数 schema 的映射""" + schema_map: Dict[str, Dict[str, Any]] = {} + for name, (_plugin_name, tool) in tools_map.items(): + fn = tool.get("function", {}) + schema_map[name] = fn.get("parameters", {}) or {} + return schema_map + + +def validate_tool_arguments( + tool_name: str, + arguments: Dict[str, Any], + schema: Optional[Dict[str, Any]], +) -> Tuple[bool, str, Dict[str, Any]]: + """ + 轻量校验并补全默认参数。 + + Returns: + (ok, error_message, new_arguments) + """ + if not schema: + return True, "", arguments + + props = schema.get("properties", {}) or {} + required = schema.get("required", []) or [] + + # 应用默认值 + for key, prop in props.items(): + if key not in arguments and isinstance(prop, dict) and "default" in prop: + arguments[key] = prop["default"] + + missing = [] + for key in required: + if key not in arguments or arguments[key] in (None, "", []): + missing.append(key) + + if missing: + return False, f"缺少参数: {', '.join(missing)}", arguments + + # 枚举与基础类型校验 + for key, prop in props.items(): + if key not in arguments or not isinstance(prop, dict): + continue + + value = arguments[key] + + if "enum" in prop and value not in prop["enum"]: + return False, f"参数 {key} 必须是 {prop['enum']}", arguments + + expected_type = prop.get("type") + if expected_type == "integer": + try: + arguments[key] = int(value) + except Exception: + return False, f"参数 {key} 应为整数", arguments + elif expected_type == "number": + try: + arguments[key] = float(value) + except Exception: + return False, f"参数 {key} 应为数字", arguments + elif expected_type == "boolean": + if isinstance(value, bool): + continue + if isinstance(value, str) and value.lower() in ("true", "false", "1", "0"): + arguments[key] = value.lower() in ("true", "1") + else: + return False, f"参数 {key} 应为布尔值", arguments + elif expected_type == "string": + if not isinstance(value, str): + arguments[key] = str(value) + + return True, "", arguments + diff --git a/utils/redis_cache.py b/utils/redis_cache.py index 2615eb5..04a849b 100644 --- a/utils/redis_cache.py +++ b/utils/redis_cache.py @@ -322,7 +322,18 @@ class RedisCache: logger.error(f"获取对话历史失败: {chat_id}, {e}") return [] - def add_chat_message(self, chat_id: str, role: str, content, ttl: int = 86400) -> bool: + def add_chat_message( + self, + chat_id: str, + role: str, + content, + ttl: int = 86400, + *, + nickname: str = None, + sender_wxid: str = None, + record_id: str = None, + timestamp: float = None, + ) -> bool: """ 添加消息到对话历史 @@ -331,6 +342,10 @@ class RedisCache: role: 角色 (user/assistant) content: 消息内容(字符串或列表) ttl: 过期时间(秒),默认24小时 + nickname: 可选昵称(用于统一 schema) + sender_wxid: 可选发送者 wxid + record_id: 可选记录 ID + timestamp: 可选时间戳 Returns: 是否添加成功 @@ -340,7 +355,18 @@ class RedisCache: try: key = self._make_key("chat_history", chat_id) - message = {"role": role, "content": content} + import time as _time + message = { + "role": role or "user", + "content": content, + } + if nickname: + message["nickname"] = nickname + if sender_wxid: + message["wxid"] = sender_wxid + if record_id: + message["id"] = record_id + message["timestamp"] = timestamp or _time.time() self.client.rpush(key, json.dumps(message, ensure_ascii=False)) self.client.expire(key, ttl) return True @@ -416,8 +442,17 @@ class RedisCache: logger.error(f"获取群聊历史失败: {group_id}, {e}") return [] - def add_group_message(self, group_id: str, nickname: str, content, - record_id: str = None, ttl: int = 86400) -> bool: + def add_group_message( + self, + group_id: str, + nickname: str, + content, + record_id: str = None, + *, + role: str = "user", + sender_wxid: str = None, + ttl: int = 86400, + ) -> bool: """ 添加消息到群聊历史 @@ -426,6 +461,8 @@ class RedisCache: nickname: 发送者昵称 content: 消息内容 record_id: 可选的记录ID,用于后续更新 + role: 角色 (user/assistant),默认 user + sender_wxid: 可选的发送者 wxid ttl: 过期时间(秒),默认24小时 Returns: @@ -438,10 +475,13 @@ class RedisCache: import time key = self._make_key("group_history", group_id) message = { + "role": role or "user", "nickname": nickname, "content": content, "timestamp": time.time() } + if sender_wxid: + message["wxid"] = sender_wxid if record_id: message["id"] = record_id