feat: Enhance emoji and image management with asynchronous background processing

- Added support for scheduling background tasks to build emoji and image descriptions when not found in cache.
- Improved error handling and logging for emoji and image processing.
- Updated `SessionMessage` processing to allow for optional heavy media analysis and voice transcription.
- Refactored logging messages for better clarity and consistency across various modules.
- Introduced a new function to build outbound log previews for messages, enhancing logging capabilities.
This commit is contained in:
DrSmoothl
2026-03-26 23:03:47 +08:00
parent 777d4cb0d2
commit 0a08973c41
17 changed files with 488 additions and 167 deletions

View File

@@ -1,9 +1,11 @@
from datetime import datetime
from pathlib import Path
from typing import Dict, Optional
from rich.traceback import install
from sqlmodel import select
from typing import Optional
import asyncio
import base64
import hashlib
@@ -24,7 +26,8 @@ IMAGE_DIR = DATA_DIR / "images"
logger = get_logger("image")
def _ensure_image_dir_exists():
def _ensure_image_dir_exists() -> None:
"""确保图片缓存目录存在。"""
IMAGE_DIR.mkdir(parents=True, exist_ok=True)
@@ -32,13 +35,21 @@ vlm = LLMServiceClient(task_name="vlm", request_type="image")
class ImageManager:
def __init__(self):
"""图片描述管理器。"""
def __init__(self) -> None:
"""初始化图片管理器。"""
_ensure_image_dir_exists()
self._pending_description_tasks: Dict[str, asyncio.Task[None]] = {}
logger.info("图片管理器初始化完成")
async def get_image_description(
self, *, image_hash: Optional[str] = None, image_bytes: Optional[bytes] = None
self,
*,
image_hash: Optional[str] = None,
image_bytes: Optional[bytes] = None,
wait_for_build: bool = True,
) -> str:
"""
获取图片描述的封装方法
@@ -50,6 +61,7 @@ class ImageManager:
Args:
image_hash (Optional[str]): 图片的哈希值,如果提供则优先使用该
image_bytes (Optional[bytes]): 图片的字节数据,如果提供则在数据库中找不到哈希值时使用该数据生成描述
wait_for_build (bool): 未命中缓存时是否同步等待描述构建完成
Returns:
return (str): 图片描述,如果发生错误或无法生成描述则返回空字符串
Raises:
@@ -74,6 +86,9 @@ class ImageManager:
if not image_bytes:
logger.warning("图片哈希值未找到,且未提供图片字节数据,返回无描述")
return ""
if not wait_for_build:
self._schedule_description_build(hash_str, image_bytes)
return ""
logger.info(f"图片描述未找到,哈希值: {hash_str},准备生成新描述")
try:
image = await self.save_image_and_process(image_bytes)
@@ -82,6 +97,47 @@ class ImageManager:
logger.error(f"生成图片描述时发生错误: {e}")
return ""
def _schedule_description_build(self, image_hash: str, image_bytes: bytes) -> None:
"""调度图片描述后台构建任务。
Args:
image_hash: 图片哈希值。
image_bytes: 图片字节数据。
"""
if image_hash in self._pending_description_tasks:
return
task = asyncio.create_task(self._build_description_in_background(image_hash, image_bytes))
self._pending_description_tasks[image_hash] = task
task.add_done_callback(lambda finished_task: self._finalize_description_build(image_hash, finished_task))
async def _build_description_in_background(self, image_hash: str, image_bytes: bytes) -> None:
"""在后台构建并缓存图片描述。
Args:
image_hash: 图片哈希值。
image_bytes: 图片字节数据。
"""
try:
logger.info(f"图片描述后台构建已开始,哈希值: {image_hash}")
await self.save_image_and_process(image_bytes)
logger.info(f"图片描述后台构建完成,哈希值: {image_hash}")
except Exception as exc:
logger.warning(f"图片描述后台构建失败,哈希值: {image_hash},错误: {exc}")
def _finalize_description_build(self, image_hash: str, task: asyncio.Task[None]) -> None:
"""回收图片描述后台构建任务。
Args:
image_hash: 图片哈希值。
task: 已完成的后台任务。
"""
self._pending_description_tasks.pop(image_hash, None)
try:
task.result()
except Exception as exc:
logger.debug(f"图片描述后台任务结束时捕获异常,哈希值: {image_hash},错误: {exc}")
def get_image_from_db(self, image_hash: str) -> Optional[MaiImage]:
"""
从数据库中根据图片哈希值获取图片记录