新增图片上下文存储

This commit is contained in:
zcr
2026-03-30 15:12:56 +08:00
parent 1579c8d0f5
commit e3cf22edae
4 changed files with 121 additions and 248 deletions

View File

@@ -2,48 +2,20 @@ import uuid
import httpx
import logging
from langchain_core.messages import ToolMessage
from langchain_core.runnables import RunnableConfig
from langchain_core.stores import BaseStore
from minio import Minio
from google import genai
from pathlib import Path
from datetime import datetime
from langchain_core.tools import tool
from google.oauth2 import service_account
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.utils.new_oss_client import get_presigned_url, check_and_extract_minio_image
# 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
# 初始化全局凭证和客户端
# 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
# )
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")
def is_image_path_exist(image_path):
try:
return Path(image_path).exists()
except:
return False
@tool
async def generate_furniture(prompt: str, runtime: ToolRuntime):
"""
@@ -70,41 +42,16 @@ async def generate_furniture(prompt: str, runtime: ToolRuntime):
- 生成的图片会自动携带到整个对话上下文中,支持后续使用 edit_furniture 等工具进行迭代修改。
- 如果需要生成多个方案,可以多次调用本工具或在 prompt 中明确要求生成不同变体。
"""
logger.info(f"\n[系统日志] 正在调用 generate_furniture ...")
# thread_id = runtime.config.get("configurable").get("thread_id")
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:
# 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)
image_url = await generate_or_edit_image(prompt=prompt)
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}"
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."
except Exception as e:
@@ -113,141 +60,88 @@ async def generate_furniture(prompt: str, runtime: ToolRuntime):
@tool
async def edit_furniture(image_url: str, prompt: str, runtime: ToolRuntime, config: RunnableConfig):
async def edit_furniture(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")
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
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']
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]
object_name = f"furniture/sketches/{uuid.uuid4()}.png"
bucket_name = "fida-public-bucket" # 替换为你的 bucket 名称
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)
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=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}"
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 extract_result['message']
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"
# 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
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