Files
FiDA_Python/src/server/deep_agent/tools/generate_furniture_sketch.py
2026-04-14 14:42:27 +08:00

384 lines
16 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 httpx
import uuid
import logging
from langchain_core.runnables import RunnableConfig
from minio import Minio
from langgraph.prebuilt import ToolRuntime
from src.core.config import settings
logger = logging.getLogger(__name__)
minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE)
from typing import List, Optional
from langchain_core.tools import tool
logger = logging.getLogger(__name__)
@tool
async def generate_furniture(runtime: ToolRuntime, prompts: List[str] = None, num_images: Optional[int] = 12, ):
"""
生成家具设计线稿草图sketch / line drawing
功能说明:
- 默认生成 12 张家具设计线稿。
- 智能处理 prompts 数量与生成数量不一致的情况:
- 如果只有一个 prompt → 用该 prompt 生成全部 12 张(不同随机变体)。
- 如果有多个 prompt → 自动均匀分配生成数量(尽量让每个 prompt 生成相同数量)。
- 生成过程会一张一张进行,适合用户实时查看。
参数说明:
- prompts (list[str]):
必须是列表,即使只有一个提示词也要用 ["你的提示词"] 格式。
提供详细的英文提示词,描述越详细越好。
- num_images (int, 可选): 要生成的图片总数量,默认 12 张,最大限制为 12 张。
返回值:
返回 image_urls 列表,系统会自动依次展示生成的图片。
"""
# ====================== 参数安全处理 ======================
if prompts is None or len(prompts) == 0:
return "Error: prompts 参数不能为空。请至少提供一个详细的英文提示词。"
if not isinstance(prompts, list):
prompts = [str(prompts)]
# 数量限制
if num_images is None or num_images < 1:
num_images = 1
elif num_images > 12:
num_images = 12
n_prompts = len(prompts)
logger.info(f"[generate_furniture] 开始生成 | prompts数量={n_prompts} | num_images={num_images}默认12")
# ====================== 均匀分配 prompts核心逻辑 ======================
if n_prompts == 0:
return "Error: prompts 列表为空"
# 计算每个 prompt 应该生成的张数
base_count = num_images // n_prompts
remainder = num_images % n_prompts
images_per_prompt = [base_count] * n_prompts
for i in range(remainder):
images_per_prompt[i] += 1
# 构建实际使用的 prompt 列表
expanded_prompts: List[str] = []
for i, count in enumerate(images_per_prompt):
expanded_prompts.extend([prompts[i]] * count)
logger.info(f"[generate_furniture] 分配完成: {images_per_prompt} 每个prompt生成张数")
# ====================== 生成图片 ======================
try:
bucket_name = "fida-public-bucket"
base_object_name = f"furniture/sketches/{uuid.uuid4()}"
image_urls = []
for i in range(num_images):
prompt = expanded_prompts[i]
object_name = f"{base_object_name}-{i:02d}.png"
image_url = await generate_or_edit_image(
prompt=prompt,
bucket_name=bucket_name,
object_name=object_name
)
image_urls.append(image_url)
logger.info(f"[generate_furniture] 已生成第 {i + 1}/{num_images}")
logger.info(f"[generate_furniture] 成功生成 {len(image_urls)} 张图片")
return image_urls
except Exception as e:
logger.error(f"generate_furniture 执行异常: {e}", exc_info=True)
return f"generate furniture error: {str(e)}"
@tool
async def edit_furniture(
runtime: ToolRuntime,
config: RunnableConfig,
input_image_paths: list[str] = None,
prompts: list[str] = None,
mode: str = "auto",
):
"""
使用先进的图像编辑模型对家具设计草图进行精准修改。
支持三种灵活模式(与 edit_quote_upload_furniture 保持一致):
- one_to_one默认最常用多张图片 + 多个提示词,一一对应编辑
- one_to_many1 张图片 + 多个提示词(同一张图片生成多个不同变体,例如不同风格/颜色)
- many_to_one多张图片 + 1 个提示词(所有图片应用相同的修改)
参数说明:
- input_image_paths (list[str]): 输入图片的 MinIO 路径列表,长度建议 1~4
- prompts (list[str]): 修改提示词列表(必须是英文提示词)
- mode (str): "one_to_one", "one_to_many", "many_to_one", "auto"(默认自动判断)
使用要求(必须严格遵守):
- input_image_paths 和 prompts 不能为空,长度必须在 1~4 之间。
- mode="auto" 时会根据列表长度智能判断模式:
- 1 张图片 + 多个 prompt → one_to_many
- 多个图片 + 1 个 prompt → many_to_one
- 图片数量 == prompt 数量 → one_to_one
- 编辑对象默认使用最近生成的图片(由 Supervisor 传入最新路径)。
示例调用:
1. one_to_one一一对应最常用
input_image_paths = ["furniture/sketches/sofa_v1.png", "furniture/sketches/chair_v1.png"]
prompts = [
"Change the sofa to modern minimalist style with dark gray fabric.",
"Make the chair more Scandinavian with light wood and beige upholstery."
]
mode = "one_to_one"
2. one_to_many同一张图片多个版本
input_image_paths = ["furniture/sketches/sofa_latest.png"]
prompts = [
"Change to luxurious velvet with gold accents.",
"Change to industrial style with metal frame.",
"Change to soft pastel Nordic style."
]
mode = "one_to_many"
3. many_to_one多张图片统一修改
input_image_paths = ["furniture/sketches/sofa1.png", "furniture/sketches/chair1.png", "furniture/sketches/table1.png"]
prompts = ["Make all furniture more luxurious with velvet fabric and gold accents."]
mode = "many_to_one"
"""
try:
# ====================== 参数校验 ======================
if not input_image_paths or len(input_image_paths) < 1 or len(input_image_paths) > 4:
return f"参数错误input_image_paths 必须提供,且长度需在 1 到 4 张之间。目前收到 {len(input_image_paths) if input_image_paths else 0} 张。"
if not prompts:
return "参数错误prompts 不能为空,请至少提供一个修改提示词。"
if mode not in ["one_to_one", "one_to_many", "many_to_one", "auto"]:
return f"参数错误mode 参数无效。可用值one_to_one, one_to_many, many_to_one, auto。当前收到{mode}"
# Auto 模式智能判断
if mode == "auto":
if len(input_image_paths) == 1 and len(prompts) > 1:
mode = "one_to_many"
elif len(prompts) == 1:
mode = "many_to_one"
elif len(input_image_paths) == len(prompts):
mode = "one_to_one"
else:
mode = "one_to_one" # 兜底
# 各模式严格校验
if mode == "one_to_many":
if len(input_image_paths) != 1:
return f"参数错误one_to_many 模式只能传入 1 张图片,当前传入了 {len(input_image_paths)} 张。"
if len(prompts) < 1:
return "参数错误one_to_many 模式下 prompts 至少需要 1 个。"
elif mode == "many_to_one":
if len(prompts) != 1:
return f"参数错误many_to_one 模式下 prompts 必须只有 1 个,当前有 {len(prompts)} 个。"
elif mode == "one_to_one":
if len(prompts) != len(input_image_paths):
return (f"参数错误one_to_one 模式下 input_image_paths 和 prompts 数量必须完全一致。\n"
f"当前图片 {len(input_image_paths)}prompts {len(prompts)} 个。")
# ====================== 执行编辑 ======================
result = []
bucket_name = "fida-public-bucket"
if mode == "one_to_many":
# 同一张图片 + 多个 prompt
base_image = input_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)
elif mode == "many_to_one":
# 多张图片 + 1 个 prompt
current_prompt = prompts[0]
for i, image_path in enumerate(input_image_paths):
object_name = f"furniture/sketches/{uuid.uuid4()}.png"
image_url = await generate_or_edit_image(
input_path=[image_path],
prompt=current_prompt,
bucket_name=bucket_name,
object_name=f"{object_name}-{i}.png"
)
result.append(image_url)
else:
# one_to_one一一对应
for i in range(len(input_image_paths)):
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)
return result
except Exception as e:
logger.error(f"edit_furniture 执行异常: {e}", exc_info=True)
return f"工具执行失败:{str(e)},请检查参数后重试。"
@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