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_one:1张图片 + 多个提示词(同一张图生成多个不同变体,例如不同颜色) - 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 的长度必须完全相同 示例: 示例1:many_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" 示例2:many_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