From 98d6775e6291e9c1d6cef6396fbfa09850f6d44f Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 26 Nov 2025 13:11:45 +0800 Subject: [PATCH] =?UTF-8?q?ref=EF=BC=9A=E8=A7=A3=E8=80=A6=E8=A1=A8?= =?UTF-8?q?=E6=83=85=E5=8C=85=E8=AF=86=E5=88=AB=E5=92=8C=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/planner_actions/planner.py | 52 ++++++++++++ src/chat/utils/utils_image.py | 115 ++++++++++++++++---------- src/common/database/database_model.py | 15 ++++ 3 files changed, 139 insertions(+), 43 deletions(-) diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 5771feed..5d10903c 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -745,6 +745,58 @@ class ActionPlanner: logger.warning(f"解析JSON块失败: {e}, 块内容: {match[:100]}...") continue + # 如果没有找到完整的```json```块,尝试查找不完整的代码块(缺少结尾```) + if not json_objects: + json_start_pos = content.find("```json") + if json_start_pos != -1: + # 找到```json之后的内容 + json_content_start = json_start_pos + 7 # ```json的长度 + # 提取从```json之后到内容结尾的所有内容 + incomplete_json_str = content[json_content_start:].strip() + + # 提取JSON之前的内容作为推理文本 + if json_start_pos > 0: + reasoning_content = content[:json_start_pos].strip() + reasoning_content = re.sub(r"^//\s*", "", reasoning_content, flags=re.MULTILINE) + reasoning_content = reasoning_content.strip() + + if incomplete_json_str: + try: + # 清理可能的注释和格式问题 + json_str = re.sub(r"//.*?\n", "\n", incomplete_json_str) + json_str = re.sub(r"/\*.*?\*/", "", json_str, flags=re.DOTALL) + json_str = json_str.strip() + + if json_str: + # 尝试按行分割,每行可能是一个JSON对象 + lines = [line.strip() for line in json_str.split("\n") if line.strip()] + for line in lines: + try: + json_obj = json.loads(repair_json(line)) + if isinstance(json_obj, dict): + json_objects.append(json_obj) + elif isinstance(json_obj, list): + for item in json_obj: + if isinstance(item, dict): + json_objects.append(item) + except json.JSONDecodeError: + pass + + # 如果按行解析没有成功,尝试将整个块作为一个JSON对象或数组 + if not json_objects: + try: + json_obj = json.loads(repair_json(json_str)) + if isinstance(json_obj, dict): + json_objects.append(json_obj) + elif isinstance(json_obj, list): + for item in json_obj: + if isinstance(item, dict): + json_objects.append(item) + except Exception as e: + logger.debug(f"尝试解析不完整的JSON代码块失败: {e}") + except Exception as e: + logger.debug(f"处理不完整的JSON代码块时出错: {e}") + return json_objects, reasoning_content diff --git a/src/chat/utils/utils_image.py b/src/chat/utils/utils_image.py index f6012f09..99b00204 100644 --- a/src/chat/utils/utils_image.py +++ b/src/chat/utils/utils_image.py @@ -12,7 +12,7 @@ from rich.traceback import install from src.common.logger import get_logger from src.common.database.database import db -from src.common.database.database_model import Images, ImageDescriptions +from src.common.database.database_model import Images, ImageDescriptions, EmojiDescriptionCache from src.config.config import global_config, model_config from src.llm_models.utils_model import LLMRequest @@ -40,7 +40,7 @@ class ImageManager: try: db.connect(reuse_if_open=True) - db.create_tables([Images, ImageDescriptions], safe=True) + db.create_tables([Images, ImageDescriptions, EmojiDescriptionCache], safe=True) except Exception as e: logger.error(f"数据库连接或表创建失败: {e}") @@ -49,6 +49,11 @@ class ImageManager: except Exception as e: logger.warning(f"数据库清理失败: {e}") + try: + self._cleanup_emoji_from_image_descriptions() + except Exception as e: + logger.warning(f"清理ImageDescriptions中的emoji记录失败: {e}") + self._initialized = True def _ensure_image_dir(self): @@ -119,6 +124,31 @@ class ImageManager: else: logger.info("[清理完成] 未发现无效描述记录") + @staticmethod + def _cleanup_emoji_from_image_descriptions(): + """清理Images和ImageDescriptions表中type为emoji的记录(已迁移到EmojiDescriptionCache)""" + try: + # 清理Images表中type为emoji的记录 + deleted_images = Images.delete().where(Images.type == "emoji").execute() + + # 清理ImageDescriptions表中type为emoji的记录 + deleted_descriptions = ( + ImageDescriptions.delete().where(ImageDescriptions.type == "emoji").execute() + ) + + total_deleted = deleted_images + deleted_descriptions + if total_deleted > 0: + logger.info( + f"[清理完成] 从Images表中删除 {deleted_images} 条emoji类型记录, " + f"从ImageDescriptions表中删除 {deleted_descriptions} 条emoji类型记录, " + f"共删除 {total_deleted} 条记录" + ) + else: + logger.info("[清理完成] Images和ImageDescriptions表中未发现emoji类型记录") + except Exception as e: + logger.error(f"清理Images和ImageDescriptions中的emoji记录时出错: {str(e)}") + raise + async def get_emoji_tag(self, image_base64: str) -> str: from src.chat.emoji_system.emoji_manager import get_emoji_manager @@ -135,7 +165,7 @@ class ImageManager: return f"[表情包:{tag_str}]" async def get_emoji_description(self, image_base64: str) -> str: - """获取表情包描述,优先使用Emoji表中的缓存数据""" + """获取表情包描述,优先使用EmojiDescriptionCache表中的缓存数据""" try: # 计算图片哈希 # 确保base64字符串只包含ASCII字符 @@ -158,10 +188,19 @@ class ImageManager: except Exception as e: logger.debug(f"查询EmojiManager时出错: {e}") - # 查询ImageDescriptions表的缓存描述 - if cached_description := self._get_description_from_db(image_hash, "emoji"): - logger.info(f"[缓存命中] 使用ImageDescriptions表中的描述: {cached_description[:50]}...") - return f"[表情包:{cached_description}]" + # 查询EmojiDescriptionCache表的缓存(包含描述和情感标签) + try: + cache_record = EmojiDescriptionCache.get_or_none(EmojiDescriptionCache.emoji_hash == image_hash) + if cache_record: + # 优先使用情感标签,如果没有则使用详细描述 + if cache_record.emotion_tags: + logger.info(f"[缓存命中] 使用EmojiDescriptionCache表中的情感标签: {cache_record.emotion_tags[:50]}...") + return f"[表情包:{cache_record.emotion_tags}]" + elif cache_record.description: + logger.info(f"[缓存命中] 使用EmojiDescriptionCache表中的描述: {cache_record.description[:50]}...") + return f"[表情包:{cache_record.description}]" + except Exception as e: + logger.debug(f"查询EmojiDescriptionCache时出错: {e}") # === 二步走识别流程 === @@ -221,45 +260,35 @@ class ImageManager: logger.debug(f"[emoji识别] 详细描述: {detailed_description[:50]}... -> 情感标签: {final_emotion}") - if cached_description := self._get_description_from_db(image_hash, "emoji"): - logger.warning(f"虽然生成了描述,但是找到缓存表情包描述: {cached_description}") - return f"[表情包:{cached_description}]" - - # 保存表情包文件和元数据(用于可能的后续分析) - logger.debug(f"保存表情包: {image_hash}") - current_timestamp = time.time() - filename = f"{int(current_timestamp)}_{image_hash[:8]}.{image_format}" - emoji_dir = os.path.join(self.IMAGE_DIR, "emoji") - os.makedirs(emoji_dir, exist_ok=True) - file_path = os.path.join(emoji_dir, filename) - + # 再次检查缓存(防止并发情况下其他线程已经保存) try: - # 保存文件 - with open(file_path, "wb") as f: - f.write(image_bytes) - - # 保存到数据库 (Images表) - 包含详细描述用于可能的注册流程 - try: - img_obj = Images.get((Images.emoji_hash == image_hash) & (Images.type == "emoji")) - img_obj.path = file_path - img_obj.description = detailed_description # 保存详细描述 - img_obj.timestamp = current_timestamp - img_obj.save() - except Images.DoesNotExist: # type: ignore - Images.create( - image_id=str(uuid.uuid4()), - emoji_hash=image_hash, - path=file_path, - type="emoji", - description=detailed_description, # 保存详细描述 - timestamp=current_timestamp, - vlm_processed=True, - ) + cache_record = EmojiDescriptionCache.get_or_none(EmojiDescriptionCache.emoji_hash == image_hash) + if cache_record and cache_record.emotion_tags: + logger.warning(f"虽然生成了描述,但是找到缓存表情包情感标签: {cache_record.emotion_tags}") + return f"[表情包:{cache_record.emotion_tags}]" except Exception as e: - logger.error(f"保存表情包文件或元数据失败: {str(e)}") + logger.debug(f"再次查询EmojiDescriptionCache时出错: {e}") - # 保存最终的情感标签到缓存 (ImageDescriptions表) - self._save_description_to_db(image_hash, final_emotion, "emoji") + # 保存识别出的详细描述和情感标签到 emoji_description_cache + try: + current_timestamp = time.time() + cache_record, created = EmojiDescriptionCache.get_or_create( + emoji_hash=image_hash, + defaults={ + "description": detailed_description, + "emotion_tags": final_emotion, + "timestamp": current_timestamp, + }, + ) + if not created: + # 更新已有记录 + cache_record.description = detailed_description + cache_record.emotion_tags = final_emotion + cache_record.timestamp = current_timestamp + cache_record.save() + logger.info(f"[缓存保存] 表情包描述和情感标签已保存到EmojiDescriptionCache: {image_hash[:8]}...") + except Exception as e: + logger.error(f"保存表情包描述和情感标签缓存失败: {str(e)}") return f"[表情包:{final_emotion}]" diff --git a/src/common/database/database_model.py b/src/common/database/database_model.py index ba2f774c..40d542eb 100644 --- a/src/common/database/database_model.py +++ b/src/common/database/database_model.py @@ -239,6 +239,20 @@ class ImageDescriptions(BaseModel): table_name = "image_descriptions" +class EmojiDescriptionCache(BaseModel): + """ + 存储表情包的详细描述和情感标签缓存 + """ + + emoji_hash = TextField(unique=True, index=True) + description = TextField() # 详细描述 + emotion_tags = TextField(null=True) # 情感标签,逗号分隔 + timestamp = FloatField() + + class Meta: + table_name = "emoji_description_cache" + + class OnlineTime(BaseModel): """ 用于存储在线时长记录的模型。 @@ -389,6 +403,7 @@ MODELS = [ Messages, Images, ImageDescriptions, + EmojiDescriptionCache, OnlineTime, PersonInfo, Expression,