2026-03-11 21:45:46 +08:00
|
|
|
|
import uuid
|
2026-03-19 17:55:39 +08:00
|
|
|
|
import httpx
|
2026-03-20 16:13:19 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
from minio import Minio
|
2026-03-11 21:45:46 +08:00
|
|
|
|
from google import genai
|
2026-03-20 16:13:19 +08:00
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from langchain_core.tools import tool
|
|
|
|
|
|
from google.oauth2 import service_account
|
2026-03-19 17:55:39 +08:00
|
|
|
|
from langgraph.prebuilt import ToolRuntime
|
2026-03-11 21:45:46 +08:00
|
|
|
|
|
2026-03-20 16:13:19 +08:00
|
|
|
|
from src.core.config import settings, MONGO_URI
|
|
|
|
|
|
from src.server.deep_agent.utils.mongodb_util import ThreadImageMinIOStore
|
2026-03-11 21:45:46 +08:00
|
|
|
|
|
2026-03-20 16:13:19 +08:00
|
|
|
|
# from google.genai.types import GenerateContentConfig, Modality
|
|
|
|
|
|
# from langgraph.config import get_stream_writer
|
|
|
|
|
|
# from src.server.utils.new_oss_client import oss_upload_image, oss_get_image, is_minio_file_exist, oss_upload_image_file
|
2026-03-11 21:45:46 +08:00
|
|
|
|
|
|
|
|
|
|
# 初始化全局凭证和客户端
|
2026-03-20 16:13:19 +08:00
|
|
|
|
# creds = service_account.Credentials.from_service_account_file(
|
|
|
|
|
|
# settings.GOOGLE_GENAI_USE_VERTEXAI,
|
|
|
|
|
|
# scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
|
|
|
|
|
# )
|
|
|
|
|
|
# client = genai.Client(
|
|
|
|
|
|
# credentials=creds,
|
|
|
|
|
|
# project=settings.GOOGLE_CLOUD_PROJECT,
|
|
|
|
|
|
# location=settings.GOOGLE_CLOUD_LOCATION,
|
|
|
|
|
|
# vertexai=True
|
|
|
|
|
|
# )
|
2026-03-11 21:45:46 +08:00
|
|
|
|
|
2026-03-20 16:13:19 +08:00
|
|
|
|
logger = logging.getLogger(__name__)
|
2026-03-11 21:45:46 +08:00
|
|
|
|
minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE)
|
2026-03-20 16:13:19 +08:00
|
|
|
|
image_store = ThreadImageMinIOStore(MONGO_URI, "agent_tool_generate_db")
|
2026-03-11 21:45:46 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-19 17:55:39 +08:00
|
|
|
|
def is_image_path_exist(image_path):
|
2026-03-11 21:45:46 +08:00
|
|
|
|
try:
|
2026-03-19 17:55:39 +08:00
|
|
|
|
return Path(image_path).exists()
|
|
|
|
|
|
except:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_generate_furniture_tool(workspace_dir, width: int = 1024, height: int = 1024):
|
|
|
|
|
|
@tool
|
|
|
|
|
|
async def generate_furniture(prompt: str, runtime: ToolRuntime) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
使用 Gemini 图像生成模型根据详细的英文提示词生成家具设计草图。
|
|
|
|
|
|
"""
|
|
|
|
|
|
logger.info(f"\n[系统日志] 正在调用 generate_furniture ...")
|
2026-03-20 16:13:19 +08:00
|
|
|
|
thread_id = runtime.config.get("configurable").get("thread_id")
|
2026-03-19 17:55:39 +08:00
|
|
|
|
try:
|
|
|
|
|
|
# 1. 生成图像 - local flux2-klein
|
|
|
|
|
|
object_name = f"furniture/sketches/{uuid.uuid4()}.png"
|
|
|
|
|
|
bucket_name = "fida-test" # 替换为你的 bucket 名称
|
|
|
|
|
|
request_data = {
|
|
|
|
|
|
"prompt": prompt,
|
|
|
|
|
|
"bucket_name": bucket_name,
|
|
|
|
|
|
"object_name": object_name,
|
|
|
|
|
|
"width": width,
|
|
|
|
|
|
"height": height
|
|
|
|
|
|
}
|
|
|
|
|
|
async with httpx.AsyncClient(timeout=120) as client:
|
|
|
|
|
|
resp = await client.post(
|
|
|
|
|
|
f"http://{settings.FLUX2_GEN_IMG_MODEL_URL}/predict",
|
|
|
|
|
|
json=request_data,
|
|
|
|
|
|
)
|
|
|
|
|
|
result = resp.json()
|
|
|
|
|
|
image_url = result.get("output_path", None)
|
|
|
|
|
|
|
|
|
|
|
|
if image_url:
|
2026-03-20 16:13:19 +08:00
|
|
|
|
image_store.save_image_path(thread_id=thread_id, object_path=image_url, metadata={"prompt": prompt, "generated_at": str(datetime.now())})
|
2026-03-19 17:55:39 +08:00
|
|
|
|
return image_url
|
|
|
|
|
|
else:
|
|
|
|
|
|
return f"Image generation failed."
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"绘图流程异常:{e}")
|
|
|
|
|
|
return "绘图流程异常"
|
|
|
|
|
|
|
|
|
|
|
|
return generate_furniture
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_edit_furniture_tool(workspace_dir, width: int = 1024, height: int = 1024):
|
|
|
|
|
|
@tool
|
2026-03-20 16:13:19 +08:00
|
|
|
|
async def edit_furniture(prompt: str, runtime: ToolRuntime) -> str:
|
2026-03-19 17:55:39 +08:00
|
|
|
|
"""
|
|
|
|
|
|
使用图像生成模型根据详细的英文提示词编辑家具设计草图。
|
|
|
|
|
|
"""
|
|
|
|
|
|
logger.info(f"\n[系统日志] 正在调用 edit_furniture ...")
|
2026-03-20 16:13:19 +08:00
|
|
|
|
thread_id = runtime.config.get("configurable").get("thread_id")
|
2026-03-19 17:55:39 +08:00
|
|
|
|
try:
|
2026-03-20 17:22:22 +08:00
|
|
|
|
current_image_path = None
|
|
|
|
|
|
if image_store.get_image_path(thread_id):
|
|
|
|
|
|
current_image_path = image_store.get_image_path(thread_id).get("current_image_path", False)
|
|
|
|
|
|
user_input_image_paths = runtime.state.get("files").get("input_image", [])
|
|
|
|
|
|
user_quote_image_path = runtime.state.get("files").get("quote_image", "")
|
|
|
|
|
|
input_path = []
|
|
|
|
|
|
if len(user_input_image_paths) or current_image_path:
|
|
|
|
|
|
if len(user_input_image_paths):
|
|
|
|
|
|
for path in user_input_image_paths:
|
|
|
|
|
|
input_path.append(path)
|
|
|
|
|
|
if user_quote_image_path:
|
|
|
|
|
|
input_path.append(user_quote_image_path)
|
|
|
|
|
|
if not len(user_input_image_paths) and not user_quote_image_path:
|
|
|
|
|
|
input_path = [current_image_path]
|
2026-03-20 16:13:19 +08:00
|
|
|
|
object_name = f"furniture/sketches/{uuid.uuid4()}.png"
|
|
|
|
|
|
bucket_name = "fida-test" # 替换为你的 bucket 名称
|
|
|
|
|
|
request_data = {
|
|
|
|
|
|
"input_image_paths": input_path,
|
|
|
|
|
|
"prompt": prompt,
|
|
|
|
|
|
"bucket_name": bucket_name,
|
|
|
|
|
|
"object_name": object_name,
|
|
|
|
|
|
"width": width,
|
|
|
|
|
|
"height": height
|
|
|
|
|
|
}
|
|
|
|
|
|
async with httpx.AsyncClient(timeout=120) as client:
|
|
|
|
|
|
resp = await client.post(
|
|
|
|
|
|
f"http://{settings.FLUX2_GEN_IMG_MODEL_URL}/predict",
|
|
|
|
|
|
json=request_data,
|
|
|
|
|
|
)
|
|
|
|
|
|
result = resp.json()
|
|
|
|
|
|
image_url = result.get("output_path", None)
|
|
|
|
|
|
|
|
|
|
|
|
if image_url:
|
|
|
|
|
|
image_store.save_image_path(thread_id=thread_id, object_path=image_url, metadata={"prompt": prompt, "generated_at": str(datetime.now())})
|
|
|
|
|
|
return image_url
|
|
|
|
|
|
else:
|
|
|
|
|
|
return f"Image generation failed."
|
2026-03-19 17:55:39 +08:00
|
|
|
|
else:
|
2026-03-20 16:13:19 +08:00
|
|
|
|
return f"The picture to be edited does not exist."
|
2026-03-19 17:55:39 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"edit_furniture error :{e}")
|
|
|
|
|
|
return "edit_furniture error"
|
|
|
|
|
|
|
|
|
|
|
|
return edit_furniture
|
|
|
|
|
|
|
|
|
|
|
|
# def create_generate_furniture_tool(workspace_dir):
|
|
|
|
|
|
# @tool
|
|
|
|
|
|
# async def generate_furniture(prompt: str) -> str:
|
|
|
|
|
|
# """
|
|
|
|
|
|
# 使用 Gemini 图像生成模型根据详细的英文提示词生成家具设计草图。
|
|
|
|
|
|
# """
|
|
|
|
|
|
# print(f"\n[系统日志] 正在调用 Nano Banana (Gemini Image Gen) ...")
|
|
|
|
|
|
#
|
|
|
|
|
|
# try:
|
|
|
|
|
|
# response = client.models.generate_content(
|
|
|
|
|
|
# model="gemini-2.5-flash-image",
|
|
|
|
|
|
# contents=(f"Generate a professional furniture design sketch: {prompt}"),
|
|
|
|
|
|
# config=GenerateContentConfig(
|
|
|
|
|
|
# response_modalities=[Modality.TEXT, Modality.IMAGE],
|
|
|
|
|
|
# ),
|
|
|
|
|
|
# )
|
|
|
|
|
|
#
|
|
|
|
|
|
# image_bytes = None
|
|
|
|
|
|
# for part in response.candidates[0].content.parts:
|
|
|
|
|
|
# if part.inline_data:
|
|
|
|
|
|
# image_bytes = part.inline_data.data
|
|
|
|
|
|
# break
|
|
|
|
|
|
#
|
|
|
|
|
|
# if not image_bytes:
|
|
|
|
|
|
# return "未能生成图像数据。"
|
|
|
|
|
|
# # 1. 定义OSS存储路径和本地保存路径
|
|
|
|
|
|
# object_name = f"furniture/sketches/{uuid.uuid4()}.png"
|
|
|
|
|
|
# bucket = "fida-test" # 替换为你的 bucket 名称
|
|
|
|
|
|
# filename = os.path.join(workspace_dir, f"{bucket}/{object_name}")
|
|
|
|
|
|
#
|
|
|
|
|
|
# # 2. 创建本地目录(确保目录存在)
|
|
|
|
|
|
# local_dir = os.path.dirname(filename)
|
|
|
|
|
|
# if not os.path.exists(local_dir):
|
|
|
|
|
|
# os.makedirs(local_dir, exist_ok=True)
|
|
|
|
|
|
#
|
|
|
|
|
|
# # 3. 保存图片到本地文件(新增核心逻辑)
|
|
|
|
|
|
# try:
|
|
|
|
|
|
# with open(filename, "wb") as f:
|
|
|
|
|
|
# f.write(image_bytes)
|
|
|
|
|
|
# print(f"[系统日志] 图片已保存到本地:{filename}")
|
|
|
|
|
|
# except Exception as save_e:
|
|
|
|
|
|
# logger.warning(f"保存图片到本地失败:{save_e}")
|
|
|
|
|
|
# # 本地保存失败不中断上传流程,仅记录日志
|
|
|
|
|
|
#
|
|
|
|
|
|
# # 4. 上传图片到OSS(原有逻辑)
|
|
|
|
|
|
# upload_res = oss_upload_image(
|
|
|
|
|
|
# oss_client=minio_client,
|
|
|
|
|
|
# bucket=bucket,
|
|
|
|
|
|
# object_name=object_name,
|
|
|
|
|
|
# image_bytes=image_bytes
|
|
|
|
|
|
# )
|
|
|
|
|
|
#
|
|
|
|
|
|
# if upload_res:
|
|
|
|
|
|
# image_url = f"{bucket}/{object_name}"
|
|
|
|
|
|
# return image_url
|
|
|
|
|
|
# else:
|
|
|
|
|
|
# return f"图片生成成功(本地路径:{filename}),但上传至存储服务器失败。"
|
|
|
|
|
|
#
|
|
|
|
|
|
# except Exception as e:
|
|
|
|
|
|
# logger.warning(f"绘图流程异常:{e}")
|
|
|
|
|
|
# return "绘图流程异常"
|
|
|
|
|
|
#
|
|
|
|
|
|
# return generate_furniture
|