Files
FiDA_Python/src/server/deep_agent/tools/generate_furniture_sketch.py

299 lines
14 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import uuid
from typing import Optional
import httpx
import logging
from langchain_core.runnables import RunnableConfig
from minio import Minio
# from pathlib import Path
# from datetime import datetime
from langchain_core.tools import tool
from langgraph.prebuilt import ToolRuntime
from src.core.config import settings, MONGO_URI
# from src.server.deep_agent.utils.mongodb_util import ThreadImageMinIOStore
logger = logging.getLogger(__name__)
minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE)
# image_store = ThreadImageMinIOStore(MONGO_URI, "agent_tool_generate_db")
@tool
async def generate_furniture(runtime: ToolRuntime, prompts: list[str] = None, num_images: Optional[int] = 1):
"""
使用图像生成模型根据用户提供的详细英文提示词,从零生成一张全新的家具设计草图。
功能说明:
- 输入一段详细的英文描述,即可生成一张高品质的家具设计图片(可用于草图、效果图、渲染图等)。
- 生成后的图片会以 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."
- num_images (int, 可选): 要生成的图片数量,默认 1 张。最大只能是 4 张。如果输入超过 4会自动限制为 4。
返回值:
返回新生成家具图片的 image_url后续对话中 Agent 可直接引用该图片进行描述、进一步编辑或分析。
使用场景:
- 从零创建新的家具设计方案
- 快速生成多种风格的家具概念图
- 室内设计初期灵感生成
- 家具产品可视化展示
注意:
- 生成的图片会自动携带到整个对话上下文中,支持后续使用 edit_furniture 等工具进行迭代修改。
- 如果需要生成多个方案,可以多次调用本工具或在 prompt 中明确要求生成不同变体。
"""
if num_images is None or num_images < 1:
num_images = 1
elif num_images > 4:
num_images = 4
# current_checkpoint_id = runtime.store.get(namespace=("image_history",), key="checkpoint_id", ).value.get("current_checkpoint_id")
logger.info(f"\n[系统日志] 正在调用 generate_furniture ")
try:
bucket_name = "fida-public-bucket"
object_name = f"furniture/sketches/{uuid.uuid4()}"
image_urls = []
for i in range(num_images):
image_urls.append(await generate_or_edit_image(prompt=prompts[i], bucket_name=bucket_name, object_name=f"{object_name}-{i}.png"))
# if image_urls:
# image_store.save_image_path(thread_id=current_checkpoint_id, object_path=image_urls, metadata={"prompt": prompt, "generated_at": str(datetime.now())})
return image_urls
# else:
# return "Image generation failed."
except Exception as e:
logger.warning(f"绘图流程异常:{e}")
return "generate furniture error"
@tool
async def edit_furniture(runtime: ToolRuntime, config: RunnableConfig, input_image_paths: list[str] = None, prompts: list[str] = None, ):
"""
使用先进的图像编辑模型对家具设计草图进行精准修改。
功能说明:
- 支持批量处理多张家具图片,根据对应的提示词生成修改后的新图片。
- input_image_paths 和 prompts 必须一一对应,数量完全一致。
- 最多支持同时处理 4 对图片和提示词(即最多 4 张图片)。
参数说明:
- input_image_paths (list[str]):
输入图片在 MinIO 中的存储路径列表。
示例:["furniture/designs/sofa_concept_v1.png", "projects/room_2026/chair_v2.jpg"]
注意:路径必须是有效的 MinIO 对象路径,工具会自动下载对应图片。
- prompts (list[str]):
必须是列表,即使只有一个提示词也要用 ["你的提示词"] 格式。
与图片一一对应的详细英文提示词列表。
每个提示词描述对对应图片的具体修改要求(风格、颜色、材质、形状、添加/删除元素等)。
示例:["Change the sofa to a modern minimalist style with dark gray fabric and metal legs, add a matching coffee table.",
"Convert the chair to Scandinavian Nordic style with light wood and soft beige upholstery."]
使用要求(重要):
- input_image_paths 和 prompts 的长度必须完全相同。
- 列表长度必须在 1 到 4 之间(最多 4 对)。
- input_image_paths[0] 对应 prompts[0],以此类推,一一对应进行编辑。
使用场景:
- 家具设计方案迭代
- 室内设计多方案对比修改
- 批量风格转换(现代/北欧/工业/奢华风等)
- 材质、颜色、细节批量调整
示例调用:
input_image_paths = ["designs/sofa1.png", "designs/chair1.png"]
prompts = [
"Make the sofa more luxurious with velvet fabric and gold accents.",
"Change the chair to a sleek modern design with black leather and chrome legs."
]
"""
# image_history = runtime.store.get(namespace=("image_history",), key="checkpoint_id", )
# last_checkpoint_id = image_history.value.get("last_checkpoint_id")
# current_checkpoint_id = image_history.value.get("current_checkpoint_id")
#
# logger.info(f"\n[系统日志] 正在调用 edit_furniture ...current_checkpoint_id={current_checkpoint_id} --- last_checkpoint_id={last_checkpoint_id}")
#
# if image_store.get_image_path(last_checkpoint_id):
# current_image_path = image_store.get_image_path(last_checkpoint_id).get("current_image_path", False)
# if current_image_path:
# if isinstance(current_image_path, list):
# # 只取最后一张
# current_image_path = current_image_path[-1]
# else:
# current_image_path = None
# 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", "")
result = []
if len(input_image_paths):
for i in range(len(input_image_paths)):
bucket_name = "fida-public-bucket"
object_name = f"furniture/sketches/{uuid.uuid4()}.png"
image_url = await generate_or_edit_image(input_path=[input_image_paths[i]], prompt=prompts[i], bucket_name=bucket_name, object_name=f"{object_name}-{i}.png")
result.append(image_url)
# image_url = await generate_or_edit_image(input_path=input_path, prompt=prompt, bucket_name=bucket_name, object_name=object_name)
# if image_url:
# image_store.save_image_path(thread_id=current_checkpoint_id, object_path=[image_url], metadata={"prompt": prompt, "generated_at": str(datetime.now())})
return result
# else:
# return "Image generation failed."
# else:
# return "The picture to be edited does not exist."
# else:
# return "No recent image found, please upload or cite it"
except Exception as e:
logger.warning(f"edit_furniture error {e}")
return "edit_furniture error"
@tool
async def edit_quote_upload_furniture(image_paths: list[str] = None, mode: str = "auto", prompts: list[str] = None, ):
"""
使用先进的图像编辑模型对家具图片进行精准批量修改。
支持四种模式:
- one_to_one最常用多张图片 + 多个提示词,一一对应编辑
- one_to_many多张图片 + 1个提示词所有图片统一修改
- many_to_one1张图片 + 多个提示词(同一张图生成多个不同变体,例如不同颜色)
- many_to_many新增多张图片 + 多个提示词,一一对应(多对多交叉编辑)
参数说明:
- image_paths (list[str]): MinIO 图片路径列表,长度建议 1~4
- prompts (list[str]): 详细英文提示词列表
- mode (str): "one_to_one", "one_to_many", "many_to_one", "many_to_many", "auto"(默认自动判断)
使用要求:
- image_paths 长度必须在 1~4 之间
- mode="auto" 时会根据长度智能判断
- many_to_many 模式下image_paths 和 prompts 的长度必须完全相同
示例:
示例1many_to_many多对多一一对应
image_paths = ["sofa1.png", "chair1.png", "table1.png"]
prompts = [
"Change to bright yellow modern style.",
"Change to deep green luxury style.",
"Change to soft beige Scandinavian style."
]
mode = "many_to_many"
示例2many_to_one同一张图多个颜色版本
image_paths = ["sofa_original.png"]
prompts = ["yellow version", "green version", "blue version", "black version"]
mode = "many_to_one"
"""
try:
# ====================== 参数校验(直接返回错误信息) ======================
if not image_paths or len(image_paths) < 1 or len(image_paths) > 4:
return f"参数错误image_paths 必须提供,且长度需要在 1 到 4 张之间。目前收到 {len(image_paths) if image_paths else 0} 张。"
if not prompts:
return "参数错误prompts 不能为空,请至少提供一个修改提示词。"
if mode not in ["one_to_one", "one_to_many", "many_to_one", "many_to_many", "auto"]:
return f"参数错误mode 参数无效。可用值one_to_one, one_to_many, many_to_one, many_to_many, auto。当前收到{mode}"
# Auto 模式智能判断
if mode == "auto":
if len(image_paths) == 1 and len(prompts) > 1:
mode = "many_to_one"
elif len(prompts) == 1:
mode = "one_to_many"
elif len(image_paths) == len(prompts):
mode = "many_to_many" # 新增:数量相等时优先 many_to_many
else:
mode = "one_to_one"
# 各模式严格校验
if mode == "many_to_one":
if len(image_paths) != 1:
return f"参数错误many_to_one 模式只能传入 1 张图片,当前传入了 {len(image_paths)} 张。"
if len(prompts) < 1:
return "参数错误many_to_one 模式下 prompts 至少需要 1 个。"
elif mode == "one_to_many":
if len(prompts) != 1:
return f"参数错误one_to_many 模式下 prompts 必须只有 1 个,当前有 {len(prompts)} 个。"
elif mode in ["one_to_one", "many_to_many"]:
if len(prompts) != len(image_paths):
return (f"参数错误:{mode} 模式下 image_paths 和 prompts 数量必须完全一致。\n"
f"当前 image_paths 有 {len(image_paths)}prompts 有 {len(prompts)} 个。")
# ====================== 执行编辑 ======================
result = []
bucket_name = "fida-public-bucket"
if mode == "many_to_one":
# 同一张图片 + 多个 prompt
base_image = image_paths[0]
for i, prompt in enumerate(prompts):
object_name = f"furniture/sketches/{uuid.uuid4()}.png"
image_url = await generate_or_edit_image(
input_path=[base_image],
prompt=prompt,
bucket_name=bucket_name,
object_name=f"{object_name}-var{i}.png"
)
result.append(image_url)
else:
# one_to_one、many_to_many、one_to_many 统一处理
for i in range(len(image_paths)):
# 根据模式决定当前使用的 prompt
if mode == "one_to_many":
current_prompt = prompts[0]
else:
current_prompt = prompts[i] # one_to_one 和 many_to_many 都用对应位置的 prompt
object_name = f"furniture/sketches/{uuid.uuid4()}.png"
image_url = await generate_or_edit_image(
input_path=[image_paths[i]],
prompt=current_prompt,
bucket_name=bucket_name,
object_name=f"{object_name}-{i}.png"
)
result.append(image_url)
return result
except Exception as e:
logger.error(f"edit_quote_upload_furniture 执行异常: {e}", exc_info=True)
return f"工具执行失败:{str(e)},请检查参数后重试。"
async def generate_or_edit_image(input_path=None, bucket_name="fida-public-bucket",
object_name=f"furniture/sketches/{uuid.uuid4()}.png",
prompt="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."):
if input_path is None:
input_path = []
request_data = {
"input_image_paths": input_path,
"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)
return image_url