修复因新建分支导致上下文中的current img混乱,采用tool直接返回图片公开url地址(原因 1.保证图片时效性,2.保证图片能够进入上下文-deep agents的main-agent与sub-agent有上下文隔离机制,且task消息类型只支持str)

This commit is contained in:
zcr
2026-03-26 17:16:58 +08:00
parent bac64f0ef1
commit 48ef18295f
7 changed files with 542 additions and 98 deletions

View File

@@ -2,7 +2,7 @@ from langchain.agents.middleware import wrap_tool_call
from src.server.deep_agent.init_llm import llm
from src.server.deep_agent.init_prompt import build_painter_prompt
from src.server.deep_agent.tools.generate_furniture_sketch import create_generate_furniture_tool, create_edit_furniture_tool
from src.server.deep_agent.tools.generate_furniture_sketch import generate_furniture, edit_furniture
@wrap_tool_call
@@ -13,9 +13,6 @@ async def log_tool_calls(request, handler):
def build_painter_subagent(workspace_dir):
generate_furniture = create_generate_furniture_tool(workspace_dir)
edit_furniture = create_edit_furniture_tool(workspace_dir)
painter_subagent = {
"name": "painter_subagent",
"description": "理解用户意图,利用prompt编辑或生成家具sketch图像",

View File

@@ -1,4 +1,3 @@
from src.server.deep_agent.init_llm import llm
from src.server.deep_agent.init_prompt import build_researcher_prompt
from src.server.deep_agent.tools.crawl_tool import create_crawl4ai_batch_tool
from src.server.deep_agent.tools.report_generator_tool import create_report_generator_tool
@@ -21,7 +20,6 @@ def build_researcher_subagent(workspace_dir):
crawl4ai_batch,
structured_retrieval,
report_generator
],
"model": llm
]
}
return research_subagent

View File

@@ -1,4 +1,3 @@
from src.server.deep_agent.init_llm import llm
from src.server.deep_agent.init_prompt import build_user_persona_prompt
from src.server.deep_agent.tools.user_persona_tool import query_report_profile, update_report_profile, check_profile_complete
@@ -6,7 +5,6 @@ user_profile_subagent = {
"name": "user_profile_subagent",
"description": "收集用户报告画像并存储到MongoDB",
"system_prompt": build_user_persona_prompt(),
"model": llm,
"tools": [
query_report_profile,
update_report_profile,

View File

@@ -1,8 +1,8 @@
def build_system_prompt(use_report):
system_prompt = f"""
你是主调度 AgentSupervisor负责理解用户意图并选择合适的子Agent。
当前参数:
use_report = {use_report}
当前参数: use_report = {use_report}
系统中存在两个相关子Agent
1. user_profile_subagent
负责收集和维护用户画像信息,包括但不限于:
@@ -10,33 +10,36 @@ def build_system_prompt(use_report):
- room_type房间类型
- budget预算
- 其他报告生成所需信息
2. research-subagent
负责生成完整报告、调研、总结、分析。
3. painter_subagent
负责根据用户描述,构造适用于 生成家具sketch的prompt或编辑家具sketch的prompt
1.用prompt用工具生成图片.
2.用prompt和图片路径用工具编辑图片.
1.用prompt用工具生成图片.
2.用prompt和图片url用工具编辑图片.
========================
执行规则
========================
【1】当用户请求报告 / 调研 / 分析 / 总结时:
【1】当 use_report = False 时:
- 严禁调用 research-subagent
- 如果用户明确请求报告、调研、总结、分析:
请礼貌回复:
"报告功能当前未开启,你可以打开 Trending report 后我来帮你生成报告。"
- 其他普通问题可以正常回答或调用其他子Agent
------------------------
【2】当用户请求报告 / 调研 / 分析 / 总结时:
先判断是否已经具备足够的用户画像信息。
如果用户需求信息不足(例如缺少风格、房间类型、预算、主题、范围等):
→ 调用 user_profile_subagent 收集信息
→ 调用 user_profile_subagent 收集信息
不要直接生成报告。
如果用户画像信息已经完整:
→ 调用 research-subagent 生成报告。
------------------------
【2】当 use_report = False 时:
- 严禁调用 research-subagent
- 如果用户明确请求报告、调研、总结、分析:
请礼貌回复:
"报告功能当前未开启,你可以打开 use_report=True 后我来帮你生成报告。"
- 其他普通问题可以正常回答或调用其他子Agent。
------------------------
【3】用户画像优先级规则
只要用户输入包含以下情况:
@@ -52,13 +55,31 @@ def build_system_prompt(use_report):
- user_profile_subagent 只负责 **信息收集**
- research-subagent 只负责 **报告生成**
不要混用职责。
========================
!禁止输出:
- 路径
- 图片url地址
- 工具参数
- 解释过程
========================
!输出规则:
- 当 painter_subagent 返回图片地址image_url
* 不要直接输出原始 URL 给用户。
* 请用 Markdown 格式回复,例如:
"已为你生成/编辑家具 sketch"
<image-card alt="家具设计" src="image_url" ></image-card>
* 或者仅回复:"图片已生成,请查看。"(如果你想在前端单独显示图片)
- **禁止** 把工具返回的原始 image_url 直接暴露给用户。
- 你的输出必须**简短**。
========================
"""
return system_prompt
def build_painter_prompt():
prompt = """
"""
你是 painter_subagent专门负责「生成」或「编辑」 sketch 图像的工具调度助手。
你的唯一任务是根据用户意图严格选择正确的工具generate_furniture 或 edit_furniture并构造对应参数。
--------------------------------
@@ -83,7 +104,7 @@ def build_painter_prompt():
---
### ❗默认规则(非常重要)
如果用户输入不明确(例如:“改成绿色”):
👉 一律视为【编辑类】
👉 一律视为【编辑类】
👉 使用 edit_furniture
--------------------------------
【二、关于图片来源(关键规则)】
@@ -96,7 +117,8 @@ def build_painter_prompt():
调用 edit_furniture 时:
- 只需要提供:
{
"prompt": "<英文图像编辑描述>"
"prompt": "<英文图像编辑描述>",
"image_url": "<用户指定的图片url>"
}
- prompt 要求:
- 清晰描述修改内容
@@ -111,18 +133,33 @@ def build_painter_prompt():
- ❌ 忽略“修改类”意图
- ❌ 因为信息少就拒绝调用工具
--------------------------------
【五、用户回复规则(必须遵守)】
你对用户的最终回复只能是以下格式之一:
- "图片已成功生成!"
- "已按你的要求完成修改,图片已更新!"
❗禁止输出:
- 路径
- URL
- 工具参数
- 解释过程
--------------------------------
现在开始工作。
"""
prompt = """
你是 painter_subagent唯一任务是根据用户意图选择工具并构造参数。
【工具选择规则】
- 包含“修改 / 改成 / 调整 / 优化 / 换成 / 基于已有图片”等语义 → 必须用 edit_furniture。
- 明确“生成 / 创建 / 设计 / 画一个”等 → 用 generate_furniture。
- 不明确时默认视为编辑类,用 edit_furniture。
【图片规则】
- edit_furniture 自动使用上下文当前图片。
- 严禁自行编造 image_url。
【输出要求】
- 仅输出正确的工具调用。
- **禁止任何解释、过程、额外文字**。
- edit_furniture 只需提供 prompt英文修改描述和 image_url如果用户指定
现在严格执行。
"""
return prompt

View File

@@ -2,6 +2,9 @@ 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
@@ -9,9 +12,9 @@ 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
@@ -41,78 +44,127 @@ def is_image_path_exist(image_path):
return False
def create_generate_furniture_tool(workspace_dir, width: int = 1024, height: int = 1024):
@tool
async def generate_furniture(prompt: str, runtime: ToolRuntime) -> str:
"""
使用 Gemini 图像生成模型根据详细的英文提示词生成家具设计草图。
"""
logger.info(f"\n[系统日志] 正在调用 generate_furniture ...")
thread_id = runtime.config.get("configurable").get("thread_id")
try:
# 1. 生成图像 - local flux2-klein
object_name = f"furniture/sketches/{uuid.uuid4()}.png"
bucket_name = "fida-test" # 替换为你的 bucket 名称
request_data = {
"prompt": prompt,
"bucket_name": bucket_name,
"object_name": object_name,
"width": width,
"height": height
}
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)
@tool
async def generate_furniture(prompt: str, runtime: ToolRuntime):
"""
使用图像生成模型根据用户提供的详细英文提示词,从零生成一张全新的家具设计草图。
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 image_url
else:
return f"Image generation failed."
功能说明:
- 输入一段详细的英文描述,即可生成一张高品质的家具设计图片(可用于草图、效果图、渲染图等)。
- 生成后的图片会以 image_url 形式返回,自动加入对话上下文,后续 Agent 可以直接“看到”生成的家具图片并继续操作(描述、编辑、迭代等)。
except Exception as e:
logger.warning(f"绘图流程异常:{e}")
return "绘图流程异常"
参数说明:
- 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."
return generate_furniture
返回值:
返回新生成家具图片的 image_url后续对话中 Agent 可直接引用该图片进行描述、进一步编辑或分析。
使用场景:
- 从零创建新的家具设计方案
- 快速生成多种风格的家具概念图
- 室内设计初期灵感生成
- 家具产品可视化展示
注意:
- 生成的图片会自动携带到整个对话上下文中,支持后续使用 edit_furniture 等工具进行迭代修改。
- 如果需要生成多个方案,可以多次调用本工具或在 prompt 中明确要求生成不同变体。
"""
logger.info(f"\n[系统日志] 正在调用 generate_furniture ...")
# thread_id = runtime.config.get("configurable").get("thread_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)
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}"
else:
return "Image generation failed."
except Exception as e:
logger.warning(f"绘图流程异常:{e}")
return "generate furniture error"
def create_edit_furniture_tool(workspace_dir, width: int = 1024, height: int = 1024):
@tool
async def edit_furniture(prompt: str, runtime: ToolRuntime) -> str:
"""
使用图像生成模型根据详细的英文提示词编辑家具设计草图。
"""
logger.info(f"\n[系统日志] 正在调用 edit_furniture ...")
thread_id = runtime.config.get("configurable").get("thread_id")
try:
current_image_path = None
if image_store.get_image_path(thread_id):
current_image_path = image_store.get_image_path(thread_id).get("current_image_path", False)
user_input_image_paths = runtime.state.get("files").get("input_image", [])
user_quote_image_path = runtime.state.get("files").get("quote_image", "")
input_path = []
@tool
async def edit_furniture(image_url: str, 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")
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", "")
extract_result = check_and_extract_minio_image(url=image_url)
if extract_result['state']:
current_image_path = extract_result['data']
if len(user_input_image_paths) or current_image_path:
if len(user_input_image_paths):
for path in user_input_image_paths:
input_path.append(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-test" # 替换为你的 bucket 名称
bucket_name = "fida-public-bucket" # 替换为你的 bucket 名称
request_data = {
"input_image_paths": input_path,
"prompt": prompt,
"bucket_name": bucket_name,
"object_name": object_name,
"width": width,
"height": height
"width": 1024,
"height": 1024
}
async with httpx.AsyncClient(timeout=120) as client:
resp = await client.post(
@@ -123,17 +175,18 @@ def create_edit_furniture_tool(workspace_dir, width: int = 1024, height: int = 1
image_url = result.get("output_path", None)
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 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}"
else:
return f"Image generation failed."
return "Image generation failed."
else:
return f"The picture to be edited does not exist."
except Exception as e:
logger.warning(f"edit_furniture error {e}")
return "edit_furniture error"
return "The picture to be edited does not exist."
else:
return extract_result['message']
return edit_furniture
except Exception as e:
logger.warning(f"edit_furniture error {e}")
return "edit_furniture error"
# def create_generate_furniture_tool(workspace_dir):
# @tool

View File

@@ -113,6 +113,74 @@ def load_minio_file_to_state(oss_client, bucket: str, object_name: str, display_
raise ValueError(f"MinIO 下載失敗: {err}")
from urllib.parse import urlparse
def check_and_extract_minio_image(url: str) -> dict[str, str]:
"""
校验URL + 提取MinIO图片路径支持预签名地址
返回格式: {"state": bool, "message": str, "data": str}
"""
# 1. 空值判断
if not url or not isinstance(url, str):
return {
"state": False,
"message": "URL cannot be empty or invalid format",
"data": ""
}
# 2. 解析URL
try:
parsed = urlparse(url)
if not (parsed.scheme and parsed.netloc):
return {
"state": False,
"message": "Invalid URL format",
"data": ""
}
except Exception:
return {
"state": False,
"message": "Failed to parse URL",
"data": ""
}
# 3. 域名判断
allowed_domains = {"www.minio-api.aida.com.hk", "minio-api.aida.com.hk"}
if parsed.netloc not in allowed_domains:
return {
"state": False,
"message": f"Invalid domain: {parsed.netloc}",
"data": ""
}
# 4. Get file path (ignore query parameters for presigned URL)
file_path = parsed.path.strip()
if not file_path:
return {
"state": False,
"message": "No file path found in URL",
"data": ""
}
# 5. Check if it's an image
image_exts = (".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".tiff")
if not file_path.lower().endswith(image_exts):
return {
"state": False,
"message": "Not a valid image file",
"data": ""
}
# 6. Extract final path
result_path = file_path.lstrip("/")
return {
"state": True,
"message": "Success, path extracted",
"data": result_path
}
if __name__ == '__main__':
url = 'fida-test/furniture/sketches/1b82b2db-8019-4796-b2cc-11fb24c7799d.png'
read_type = "2"