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
2026-03-26 17:16:58 +08:00
from langchain_core . messages import ToolMessage
from langchain_core . runnables import RunnableConfig
from langchain_core . stores import BaseStore
2026-03-20 16:13:19 +08:00
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-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-26 17:16:58 +08:00
from src . server . utils . new_oss_client import get_presigned_url , check_and_extract_minio_image
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
2026-03-26 17:16:58 +08:00
@tool
async def generate_furniture ( prompt : str , runtime : ToolRuntime ) :
"""
使用图像生成模型根据用户提供的详细英文提示词 , 从零生成一张全新的家具设计草图 。
功能说明 :
- 输入一段详细的英文描述 , 即可生成一张高品质的家具设计图片 ( 可用于草图 、 效果图 、 渲染图等 ) 。
- 生成后的图片会以 image_url 形式返回 , 自动加入对话上下文 , 后续 Agent 可以直接 “ 看到 ” 生成的家具图片并继续操作 ( 描述 、 编辑 、 迭代等 ) 。
参数说明 :
- prompt ( str ) : * * 必须是详细的英文提示词 * * , 越详细越好 , 包含家具类型 、 风格 、 颜色 、 材质 、 尺寸比例 、 背景 、 视角 、 光影等具体要求 。
示例 : " Generate a modern minimalist dining chair made of light oak wood and white leather, with slim metal legs, photographed in a bright Scandinavian living room with natural sunlight, high detail, 8k resolution. "
返回值 :
返回新生成家具图片的 image_url , 后续对话中 Agent 可直接引用该图片进行描述 、 进一步编辑或分析 。
使用场景 :
- 从零创建新的家具设计方案
- 快速生成多种风格的家具概念图
- 室内设计初期灵感生成
- 家具产品可视化展示
注意 :
- 生成的图片会自动携带到整个对话上下文中 , 支持后续使用 edit_furniture 等工具进行迭代修改 。
- 如果需要生成多个方案 , 可以多次调用本工具或在 prompt 中明确要求生成不同变体 。
"""
logger . info ( f " \n [系统日志] 正在调用 generate_furniture ... " )
# thread_id = runtime.config.get("configurable").get("thread_id")
try :
# 1. 生成图像 - local flux2-klein
object_name = f " furniture/sketches/ { uuid . uuid4 ( ) } .png "
bucket_name = " fida-public-bucket " # 替换为你的 bucket 名称
request_data = {
" prompt " : prompt ,
" bucket_name " : bucket_name ,
" object_name " : object_name ,
" width " : 1024 ,
" height " : 1024
}
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 )
# image_presigned_url = get_presigned_url(oss_client=minio_client, bucket=bucket_name, object_name=object_name)
if image_url :
# image_store.save_image_path(thread_id=thread_id, object_path=image_url, metadata={"prompt": prompt, "generated_at": str(datetime.now())})
# image_id = str(uuid.uuid4())
# runtime.store.put(
# namespace=("images", thread_id), # e.g. ("images", thread_id)
# key=image_id,
# value={
# "url": image_url,
# "prompt": prompt,
# "timestamp": str(uuid.uuid4()), # 或用 datetime
# "version": "v1"
# }
# )
return f " Image has been generated: https://minio-api.aida.com.hk/ { image_url } "
else :
return " Image generation failed. "
except Exception as e :
logger . warning ( f " 绘图流程异常: { e } " )
return " generate furniture error "
@tool
async def edit_furniture ( image_url : str , prompt : str , runtime : ToolRuntime , config : RunnableConfig ) :
"""
使用先进的图像编辑模型 ( image editing model ) 对家具设计草图进行精准修改 。
功能说明 :
- 输入一张家具图片 ( 草图 / 效果图 ) , 根据用户提供的 * * 详细英文提示词 * * , 生成修改后的新家具图片 。
- 修改后的图片会以 image_url 形式返回 , 自动加入对话上下文 , 后续 Agent 可以直接 “ 看到 ” 编辑结果并继续操作 。
参数说明 :
- image_url ( str ) : 原始家具图片的 URL ( 支持公开 http / https 链接或 data : image / . . . ; base64 格式 ) 。
- prompt ( str ) : * * 必须是详细的英文提示词 * * , 描述想要的具体修改 ( 风格 、 颜色 、 材质 、 形状 、 添加 / 删除元素 、 比例等 ) 。
示例 : " Change the sofa to a modern minimalist style with dark gray fabric and metal legs, add a matching coffee table, make the overall lighting warmer and more luxurious. "
返回值 :
返回新生成家具图片的 image_url , 后续对话中 Agent 可直接引用该图片进行描述 、 进一步编辑或分析 。
使用场景 :
- 家具设计迭代
- 室内设计方案修改
- 风格转换 ( 现代 / 北欧 / 工业风等 )
- 材质 / 颜色调整
注意 : 如果需要多次迭代编辑 , 直接在下一次调用时传入上一次返回的新 image_url 即可 。
"""
logger . info ( f " \n [系统日志] 正在调用 edit_furniture ... " )
thread_id = runtime . config . get ( " configurable " ) . get ( " thread_id " )
input_path = [ ]
try :
user_input_image_paths = runtime . state . get ( " files " ) . get ( " input_image " , [ ] )
user_quote_image_path = runtime . state . get ( " files " ) . get ( " quote_image " , " " )
extract_result = check_and_extract_minio_image ( url = image_url )
if extract_result [ ' state ' ] :
current_image_path = extract_result [ ' data ' ]
2026-03-20 17:22:22 +08:00
if len ( user_input_image_paths ) or current_image_path :
2026-03-26 17:16:58 +08:00
for path in user_input_image_paths :
input_path . append ( path )
2026-03-20 17:22:22 +08:00
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 "
2026-03-26 17:16:58 +08:00
bucket_name = " fida-public-bucket " # 替换为你的 bucket 名称
2026-03-20 16:13:19 +08:00
request_data = {
" input_image_paths " : input_path ,
" prompt " : prompt ,
" bucket_name " : bucket_name ,
" object_name " : object_name ,
2026-03-26 17:16:58 +08:00
" width " : 1024 ,
" height " : 1024
2026-03-20 16:13:19 +08:00
}
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-26 17:16:58 +08:00
# image_store.save_image_path(thread_id=thread_id, object_path=image_url, metadata={"prompt": prompt, "generated_at": str(datetime.now())})
return f " Image has been generated: https://minio-api.aida.com.hk/ { image_url } "
2026-03-20 16:13:19 +08:00
else :
2026-03-26 17:16:58 +08:00
return " Image generation failed. "
2026-03-19 17:55:39 +08:00
else :
2026-03-26 17:16:58 +08:00
return " The picture to be edited does not exist. "
else :
return extract_result [ ' message ' ]
2026-03-19 17:55:39 +08:00
2026-03-26 17:16:58 +08:00
except Exception as e :
logger . warning ( f " edit_furniture error : { e } " )
return " edit_furniture error "
2026-03-19 17:55:39 +08:00
# 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