diff --git a/src/server/deep_agent/init_prompt.py b/src/server/deep_agent/init_prompt.py index 26deeef..2450dc7 100755 --- a/src/server/deep_agent/init_prompt.py +++ b/src/server/deep_agent/init_prompt.py @@ -24,7 +24,7 @@ SYSTEM_RULES_PROMPT = """ - 默认风格为:干净的黑白线稿、手绘草图风格、概念设计草图(concept sketch)、技术线稿(technical line drawing)。 - 优先使用线稿专用提示词,避免出现 realistic、photorealistic、photo、render、highly detailed rendering 等词。 - 只能**一次性**调用图片相关工具(edit_quote_upload_furniture、edit_furniture、generate_furniture 等),不要多次调用。 -- 生成类工具最多只能生成 4 张图片。 +- 生成类工具每次最多只能生成 12 张图片。 - 如果用户消息中提到“上传的图片”“我提供的图片”“这张图”或出现 MinIO 路径 → 优先使用 `edit_quote_upload_furniture`。 - 如果是本对话中刚刚生成的图片 → 使用 `edit_furniture`。 @@ -44,8 +44,8 @@ SYSTEM_RULES_PROMPT = """ 【2】调用图片工具的正确方式 - 必须一次性调用工具,不要拆分成多次调用。 -- 在给图片工具的指令中,明确说明生成或修改的数量(但上限为4)。 -- 示例:用户说“生成10张” → 只调用一次工具并限制为4张,然后正常回复。 +- 在给图片工具的指令中,明确说明生成或修改的数量(但上限为12)。 +- 示例:用户说“生成100张” → 只调用一次工具并限制为12张,然后正常回复。 **禁止行为**: - ❌ 不要在任何回复中输出图片路径或文件路径。 @@ -85,94 +85,6 @@ user_profile_subagent """ -def build_system_prompt(): - system_prompt = f""" - 你是主调度 Agent(Supervisor),负责理解用户意图并选择合适的子Agent。 - 系统中存在两个相关子Agent: - 1. user_profile_subagent - 负责收集和维护用户画像信息,包括但不限于: - - style(风格) - - room_type(房间类型) - - budget(预算) - - 其他报告生成所需信息 - - 2. research-subagent - 负责生成完整报告、调研、总结、分析。 - 如果没有找到这个agent,引导用户开启trending report按钮即载入生成报告能力. - - ======================== - 核心执行规则(必须严格遵守) - ======================== - - 【1】图像生成与编辑任务处理(最高优先级) - 当用户请求生成或修改家具图片时(包含“生成”“画”“创建”“设计”“修改”“帮我改”等关键词): - - - 你生成的所有家具图片**必须是设计线稿(furniture sketch / line drawing)**,而不是真实照片、渲染图或彩色效果图。 - - 默认风格为:干净的黑白线稿、手绘草图风格、概念设计草图(concept sketch)、技术线稿(technical line drawing)。 - - 优先使用线稿专用提示词,避免出现 realistic、photorealistic、photo、render、highly detailed rendering 等词。 - - 只能**一次性**调用图片相关工具(edit_quote_upload_furniture、edit_furniture、generate_furniture 等),不要多次调用。 - - 生成类工具最多只能生成 4 张图片。 - - 如果用户消息中提到“上传的图片”“我提供的图片”“这张图”或出现 MinIO 路径 → 优先使用 `edit_quote_upload_furniture`。 - - 如果是本对话中刚刚生成的图片 → 使用 `edit_furniture`。 - - **关键参数规则(必须严格遵守)**: - - 调用 `generate_furniture` 或 `edit_quote_upload_furniture` 时,`prompts` 参数**必须是 list[str]**,即使只有一条提示词,也要写成列表形式。 - 正确示例: - prompts = ["Generate a traditional Chinese style rattan chair with intricate woven patterns..."] - 错误示例:prompts = "Generate a traditional Chinese style..." (这是字符串,会导致错误!) - - - `image_paths`(如果需要)也必须是 list[str]。 - - **重要输出规则**: - - 你**绝对不能**在回复中输出任何文件路径、MinIO 路径、图片 URL 或类似 "uploads/"、"furniture/sketches/" 的内容。 - - 所有图片都会通过系统其他方式展示给用户,你不需要也不允许展示路径。 - - 工具调用成功后:可以回复“已为你生成/修改图片,请查看” 或 直接不回复(让系统展示图片)。 - - 工具调用失败时:可以礼貌告知用户“图片生成失败,请稍后重试”或简要说明问题(但不要包含任何路径)。 - - 【2】调用图片工具的正确方式 - - 必须一次性调用工具,不要拆分成多次调用。 - - 在给图片工具的指令中,明确说明生成或修改的数量(但上限为4)。 - - 示例:用户说“生成10张” → 只调用一次工具并限制为4张,然后正常回复。 - - **禁止行为**: - - ❌ 不要在任何回复中输出图片路径或文件路径。 - - ❌ 不要多次调用生成工具来凑数量。 - - ❌ 不要把路径告诉用户。 - - ❌ 工具成功后不要描述“生成了哪些路径的图片”。 - - 【3】当用户请求报告 / 调研 / 分析 / 总结时: - 先判断是否已经具备足够的用户画像信息。 - 如果用户需求信息不足(例如缺少风格、房间类型、预算、主题、范围等): - → 调用 user_profile_subagent 收集信息 - 不要直接生成报告。 - 如果用户画像信息已经完整: - → 调用 research-subagent 生成报告。 - ------------------------ - 【5】用户画像优先级规则 - 只要用户输入包含以下情况: - - 表达设计需求 - - 提供偏好信息(例如风格、预算、房间类型) - - 修改之前的偏好 - - 补充报告信息 - 都应该优先调用: - user_profile_subagent - 用于更新或收集用户画像。 - ------------------------ - 【6】调度原则 - - user_profile_subagent 只负责 **信息收集** - - research-subagent 只负责 **报告生成** - 不要混用职责。 - ======================== - 重要提醒(最高优先级): - 在整个对话过程中,你**绝对禁止**输出任何包含以下内容的文字: - - 以 "uploads/"、"furniture/"、"projects/"、"sketches/" 开头的路径 - - 任何 .png、.jpg 结尾的路径l - - 任何 http 开头的图片链接(除非系统明确要求) - 所有图片展示均由系统统一处理,你只需负责正确调用工具。 - """ - return system_prompt - - def build_painter_prompt(): prompt = """ 你是 painter_subagent,专门负责「生成」或「编辑」 sketch 图像的工具调度助手。 diff --git a/src/server/deep_agent/tools/generate_furniture_sketch.py b/src/server/deep_agent/tools/generate_furniture_sketch.py index 4c69941..ba171f5 100755 --- a/src/server/deep_agent/tools/generate_furniture_sketch.py +++ b/src/server/deep_agent/tools/generate_furniture_sketch.py @@ -1,73 +1,103 @@ -import uuid -from typing import Optional - import httpx +import uuid 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 +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) -# image_store = ThreadImageMinIOStore(MONGO_URI, "agent_tool_generate_db") + +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] = 1): +async def generate_furniture(runtime: ToolRuntime, prompts: List[str] = None, num_images: Optional[int] = 12, ): """ - 使用图像生成模型根据用户提供的详细英文提示词,从零生成一张全新的家具设计草图。 + 生成家具设计线稿草图(sketch / line drawing)。 功能说明: - - 输入一段详细的英文描述,即可生成一张高品质的家具设计图片(可用于草图、效果图、渲染图等)。 - - 生成后的图片会以 image_url 形式返回,自动加入对话上下文,后续 Agent 可以直接“看到”生成的家具图片并继续操作(描述、编辑、迭代等)。 + - 默认生成 12 张家具设计线稿。 + - 智能处理 prompts 数量与生成数量不一致的情况: + - 如果只有一个 prompt → 用该 prompt 生成全部 12 张(不同随机变体)。 + - 如果有多个 prompt → 自动均匀分配生成数量(尽量让每个 prompt 生成相同数量)。 + - 生成过程会一张一张进行,适合用户实时查看。 参数说明: - - 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。 + - prompts (list[str]): + 必须是列表,即使只有一个提示词也要用 ["你的提示词"] 格式。 + 提供详细的英文提示词,描述越详细越好。 + - num_images (int, 可选): 要生成的图片总数量,默认 12 张,最大限制为 12 张。 返回值: - 返回新生成家具图片的 image_url,后续对话中 Agent 可直接引用该图片进行描述、进一步编辑或分析。 - - 使用场景: - - 从零创建新的家具设计方案 - - 快速生成多种风格的家具概念图 - - 室内设计初期灵感生成 - - 家具产品可视化展示 - - 注意: - - 生成的图片会自动携带到整个对话上下文中,支持后续使用 edit_furniture 等工具进行迭代修改。 - - 如果需要生成多个方案,可以多次调用本工具或在 prompt 中明确要求生成不同变体。 + 返回 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 > 4: - num_images = 4 - # current_checkpoint_id = runtime.store.get(namespace=("image_history",), key="checkpoint_id", ).value.get("current_checkpoint_id") + elif num_images > 12: + num_images = 12 - logger.info(f"\n[系统日志] 正在调用 generate_furniture ") + 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" - object_name = f"furniture/sketches/{uuid.uuid4()}" + base_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())}) + 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 - # else: - # return "Image generation failed." + except Exception as e: - logger.warning(f"绘图流程异常:{e}") - return "generate furniture error" + logger.error(f"generate_furniture 执行异常: {e}", exc_info=True) + return f"generate furniture error: {str(e)}" @tool @@ -112,26 +142,7 @@ async def edit_furniture(runtime: ToolRuntime, config: RunnableConfig, input_ima "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)): @@ -139,17 +150,7 @@ async def edit_furniture(runtime: ToolRuntime, config: RunnableConfig, input_ima 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"