2026-03-20 16:13:19 +08:00
|
|
|
|
from typing import Optional
|
|
|
|
|
|
from pymongo import MongoClient
|
|
|
|
|
|
from pymongo.collection import Collection
|
|
|
|
|
|
from pymongo.errors import PyMongoError
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
|
|
from src.core.config import MONGO_URI
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ThreadImageMinIOStore:
|
|
|
|
|
|
"""
|
|
|
|
|
|
根據 thread_id 存取/更新 current_image 的 MinIO 物件路徑(不存 binary)
|
|
|
|
|
|
|
|
|
|
|
|
儲存格式範例:
|
|
|
|
|
|
{
|
|
|
|
|
|
"thread_id": "thread_abc123",
|
|
|
|
|
|
"current_image_path": "images/2025/03/thread_abc123_latest.png",
|
|
|
|
|
|
"updated_at": ISODate,
|
|
|
|
|
|
"metadata": {"format": "png", "desc": "生成的貓圖", "size_bytes": 512345}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
使用方式:
|
|
|
|
|
|
store = ThreadImageMinIOStore("mongodb://localhost:27017/", "deepagents_db")
|
|
|
|
|
|
store.save_image_path("thread_abc123", "images/cat/001.png", "https://minio.example.com/bucket/images/cat/001.png")
|
|
|
|
|
|
path_info = store.get_image_path("thread_abc123")
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
mongo_uri: str,
|
|
|
|
|
|
db_name: str = "deepagents_db",
|
|
|
|
|
|
collection_name: str = "agent_image_paths",
|
|
|
|
|
|
connect_timeout_ms: int = 5000,
|
|
|
|
|
|
server_selection_timeout_ms: int = 5000,
|
|
|
|
|
|
):
|
|
|
|
|
|
self.client = MongoClient(
|
|
|
|
|
|
mongo_uri,
|
|
|
|
|
|
connectTimeoutMS=connect_timeout_ms,
|
|
|
|
|
|
serverSelectionTimeoutMS=server_selection_timeout_ms,
|
|
|
|
|
|
retryWrites=True,
|
|
|
|
|
|
retryReads=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
self.db = self.client[db_name]
|
|
|
|
|
|
self.collection: Collection = self.db[collection_name]
|
|
|
|
|
|
|
|
|
|
|
|
# 建立唯一索引
|
|
|
|
|
|
self.collection.create_index("thread_id", unique=True)
|
|
|
|
|
|
|
|
|
|
|
|
def save_image_path(
|
|
|
|
|
|
self,
|
|
|
|
|
|
thread_id: str,
|
2026-03-30 19:37:10 +08:00
|
|
|
|
object_path: list, # MinIO 中的相對路徑,例如 "test/123.png" 或 "images/20250320/abc.png"
|
2026-03-20 16:13:19 +08:00
|
|
|
|
metadata: Optional[dict] = None
|
|
|
|
|
|
) -> bool:
|
|
|
|
|
|
"""
|
|
|
|
|
|
保存或更新某個 thread 的 current_image MinIO 路徑
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
thread_id: 對話執行緒 ID
|
|
|
|
|
|
object_path: MinIO bucket 內的物件路徑 (不含 bucket 名稱)
|
|
|
|
|
|
metadata: 額外資訊,例如 {"prompt": "...", "format": "png", "width": 1024}
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
bool: 是否成功
|
|
|
|
|
|
"""
|
|
|
|
|
|
document = {
|
|
|
|
|
|
"thread_id": thread_id,
|
|
|
|
|
|
"current_image_path": object_path,
|
|
|
|
|
|
"updated_at": datetime.now(),
|
|
|
|
|
|
"metadata": metadata or {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
result = self.collection.update_one(
|
|
|
|
|
|
{"thread_id": thread_id},
|
|
|
|
|
|
{"$set": document},
|
|
|
|
|
|
upsert=True
|
|
|
|
|
|
)
|
|
|
|
|
|
action = "updated" if result.modified_count > 0 else "inserted"
|
|
|
|
|
|
logger.info(f"Image path for thread {thread_id} {action}: {object_path}")
|
|
|
|
|
|
return True
|
|
|
|
|
|
except PyMongoError as e:
|
|
|
|
|
|
logger.error(f"Failed to save image path for thread {thread_id}: {e}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def get_image_path(self, thread_id: str) -> Optional[dict]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
取得某 thread 的 current_image MinIO 資訊
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
{
|
|
|
|
|
|
"current_image_path": str,
|
|
|
|
|
|
"updated_at": datetime,
|
|
|
|
|
|
"metadata": dict
|
|
|
|
|
|
} 或 None
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
doc = self.collection.find_one({"thread_id": thread_id})
|
|
|
|
|
|
if not doc:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"current_image_path": doc.get("current_image_path"),
|
|
|
|
|
|
"updated_at": doc.get("updated_at"),
|
|
|
|
|
|
"metadata": doc.get("metadata", {})
|
|
|
|
|
|
}
|
|
|
|
|
|
except PyMongoError as e:
|
|
|
|
|
|
logger.error(f"Failed to get image path for thread {thread_id}: {e}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_object_path_only(self, thread_id: str) -> Optional[str]:
|
|
|
|
|
|
"""只取 MinIO 相對路徑,方便直接給 MinIO client 使用"""
|
|
|
|
|
|
info = self.get_image_path(thread_id)
|
|
|
|
|
|
return info["current_image_path"] if info else None
|
|
|
|
|
|
|
|
|
|
|
|
def delete_image_path(self, thread_id: str) -> bool:
|
|
|
|
|
|
"""刪除某 thread 的記錄(不影響 MinIO 實際檔案)"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
result = self.collection.delete_one({"thread_id": thread_id})
|
|
|
|
|
|
if result.deleted_count > 0:
|
|
|
|
|
|
logger.info(f"Image path record for thread {thread_id} deleted")
|
|
|
|
|
|
return True
|
|
|
|
|
|
return False
|
|
|
|
|
|
except PyMongoError as e:
|
|
|
|
|
|
logger.error(f"Failed to delete image path for thread {thread_id}: {e}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
|
|
self.client.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
image_store = ThreadImageMinIOStore(MONGO_URI, "agent_tool_generate_db")
|
|
|
|
|
|
success = image_store.save_image_path(
|
|
|
|
|
|
thread_id="121233",
|
2026-03-30 19:37:10 +08:00
|
|
|
|
object_path=["test/123.png"],
|
2026-03-20 16:13:19 +08:00
|
|
|
|
metadata={"prompt": "prompt", "generated_at": str(datetime.now())})
|
|
|
|
|
|
print(success)
|
|
|
|
|
|
info = image_store.get_image_path("121233")
|
|
|
|
|
|
print(info)
|