将生成图片或图片路径写入上下文中 放弃自控图片上下文
This commit is contained in:
@@ -1,23 +1,25 @@
|
||||
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 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
|
||||
# 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")
|
||||
# image_store = ThreadImageMinIOStore(MONGO_URI, "agent_tool_generate_db")
|
||||
|
||||
|
||||
@tool
|
||||
async def generate_furniture(prompt: str, runtime: ToolRuntime):
|
||||
async def generate_furniture(runtime: ToolRuntime, prompts: list[str] = None, num_images: Optional[int] = 1):
|
||||
"""
|
||||
使用图像生成模型根据用户提供的详细英文提示词,从零生成一张全新的家具设计草图。
|
||||
|
||||
@@ -28,6 +30,7 @@ async def generate_furniture(prompt: str, runtime: ToolRuntime):
|
||||
参数说明:
|
||||
- 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 可直接引用该图片进行描述、进一步编辑或分析。
|
||||
@@ -42,85 +45,232 @@ async def generate_furniture(prompt: str, runtime: ToolRuntime):
|
||||
- 生成的图片会自动携带到整个对话上下文中,支持后续使用 edit_furniture 等工具进行迭代修改。
|
||||
- 如果需要生成多个方案,可以多次调用本工具或在 prompt 中明确要求生成不同变体。
|
||||
"""
|
||||
current_checkpoint_id = runtime.store.get(namespace=("image_history",), key="checkpoint_id", ).value.get("current_checkpoint_id")
|
||||
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 ...当前checkpoint_id={current_checkpoint_id}")
|
||||
|
||||
try:
|
||||
image_url = await generate_or_edit_image(prompt=prompt)
|
||||
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_url:
|
||||
image_store.save_image_path(thread_id=current_checkpoint_id, object_path=image_url, metadata={"prompt": prompt, "generated_at": str(datetime.now())})
|
||||
return image_url
|
||||
else:
|
||||
return "Image generation failed."
|
||||
# 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(prompt: str, runtime: ToolRuntime, config: RunnableConfig):
|
||||
async def edit_furniture(runtime: ToolRuntime, config: RunnableConfig, input_image_paths: list[str] = None, prompts: list[str] = None, ):
|
||||
"""
|
||||
使用先进的图像编辑模型(image editing model)对家具设计草图进行精准修改。
|
||||
使用先进的图像编辑模型对家具设计草图进行精准修改。
|
||||
|
||||
功能说明:
|
||||
- 根据用户提供的**详细英文提示词**,生成修改后的新家具图片。
|
||||
- 支持批量处理多张家具图片,根据对应的提示词生成修改后的新图片。
|
||||
- input_image_paths 和 prompts 必须一一对应,数量完全一致。
|
||||
- 最多支持同时处理 4 对图片和提示词(即最多 4 张图片)。
|
||||
|
||||
参数说明:
|
||||
- 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."
|
||||
|
||||
- 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}")
|
||||
|
||||
input_path = []
|
||||
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)
|
||||
else:
|
||||
current_image_path = None
|
||||
# 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", "")
|
||||
|
||||
if current_image_path:
|
||||
if len(user_input_image_paths) or current_image_path:
|
||||
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]
|
||||
|
||||
# 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_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 image_url
|
||||
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"
|
||||
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 "
|
||||
|
||||
Reference in New Issue
Block a user