Browse Source

add production restore

guantao 7 hours ago
parent
commit
7376b25524
100 changed files with 4381 additions and 279 deletions
  1. 2 1
      .claude/settings.local.json
  2. 1 1
      .gitignore
  3. 58 0
      agent/core/runner.py
  4. 3 1
      agent/tools/builtin/__init__.py
  5. 356 26
      agent/tools/builtin/toolhub.py
  6. 45 0
      consistency_requirements.json
  7. 0 9
      examples/feature_extract/presets.json
  8. 0 109
      examples/feature_extract/requirement.prompt
  9. 0 128
      examples/feature_extract/tool_research.prompt
  10. 136 0
      examples/mini_restore/call_banana.py
  11. 0 0
      examples/mini_restore/config.py
  12. BIN
      examples/mini_restore/input/img_1.png
  13. BIN
      examples/mini_restore/input/img_2.png
  14. BIN
      examples/mini_restore/input/img_3.png
  15. BIN
      examples/mini_restore/input/img_4.png
  16. BIN
      examples/mini_restore/input/img_5.png
  17. 0 0
      examples/mini_restore/new_search.prompt
  18. 0 0
      examples/mini_restore/run.py
  19. 70 0
      examples/mini_restore/upload.py
  20. 394 0
      examples/mini_restore/workflow_loop.py
  21. 79 0
      examples/production_restore/config.py
  22. 300 0
      examples/production_restore/evaluate_tool.py
  23. 141 0
      examples/production_restore/execution.prompt
  24. 77 0
      examples/production_restore/generation-agent-architecture.md
  25. 100 0
      examples/production_restore/input/analysis.json
  26. 194 0
      examples/production_restore/input/pipeline.json
  27. 0 0
      examples/production_restore/input/raw_info/写生油画__img_1_制作表.json
  28. 0 0
      examples/production_restore/input/raw_info/写生油画__img_2_制作表.json
  29. 0 0
      examples/production_restore/input/raw_info/写生油画__img_3_制作表.json
  30. 0 0
      examples/production_restore/input/raw_info/写生油画__img_4_制作表.json
  31. 0 0
      examples/production_restore/input/raw_info/写生油画__img_5_制作表.json
  32. 0 0
      examples/production_restore/input/raw_info/创作表.md
  33. 0 0
      examples/production_restore/input/raw_info/制作点.md
  34. 0 0
      examples/production_restore/input/raw_info/图片亮点.md
  35. 44 0
      examples/production_restore/input/strategy.json
  36. 15 0
      examples/production_restore/presets.json
  37. 95 0
      examples/production_restore/requirement.prompt
  38. 380 0
      examples/production_restore/run.py
  39. 70 0
      examples/production_restore/upload.py
  40. 0 0
      examples/requirement_extract/aliduoduo/description/创作表.md
  41. 0 0
      examples/requirement_extract/aliduoduo/description/制作亮点.md
  42. 0 0
      examples/requirement_extract/aliduoduo/description/制作点.md
  43. 0 0
      examples/requirement_extract/aliduoduo/description/阿里多多酱__img_1_制作表.json
  44. 0 0
      examples/requirement_extract/aliduoduo/description/阿里多多酱__img_2_制作表.json
  45. 0 0
      examples/requirement_extract/aliduoduo/description/阿里多多酱__img_3_制作表.json
  46. 0 0
      examples/requirement_extract/aliduoduo/index.md
  47. 4 4
      examples/requirement_extract/config.py
  48. 369 0
      examples/requirement_extract/huahua/descriptions/写生油画__img_1_制作表.json
  49. 338 0
      examples/requirement_extract/huahua/descriptions/写生油画__img_2_制作表.json
  50. 283 0
      examples/requirement_extract/huahua/descriptions/写生油画__img_3_制作表.json
  51. 319 0
      examples/requirement_extract/huahua/descriptions/写生油画__img_4_制作表.json
  52. 389 0
      examples/requirement_extract/huahua/descriptions/写生油画__img_5_制作表.json
  53. 1 0
      examples/requirement_extract/huahua/descriptions/创作表.md
  54. 74 0
      examples/requirement_extract/huahua/descriptions/制作点.md
  55. 44 0
      examples/requirement_extract/huahua/descriptions/图片亮点.md
  56. 0 0
      examples/requirement_extract/huahua/features/background_asset/background_bokeh_img2.png
  57. 0 0
      examples/requirement_extract/huahua/features/background_asset/background_green_img1.png
  58. 0 0
      examples/requirement_extract/huahua/features/background_asset/background_green_img4.png
  59. 0 0
      examples/requirement_extract/huahua/features/background_asset/mapping.json
  60. 0 0
      examples/requirement_extract/huahua/features/character_asset/character_ref_back.png
  61. 0 0
      examples/requirement_extract/huahua/features/character_asset/character_ref_img1.png
  62. 0 0
      examples/requirement_extract/huahua/features/character_asset/character_ref_kneel.png
  63. 0 0
      examples/requirement_extract/huahua/features/character_asset/character_ref_main.png
  64. 0 0
      examples/requirement_extract/huahua/features/character_asset/character_ref_side.png
  65. 0 0
      examples/requirement_extract/huahua/features/character_asset/mapping.json
  66. 0 0
      examples/requirement_extract/huahua/features/color_scheme/color_scheme.json
  67. 0 0
      examples/requirement_extract/huahua/features/color_scheme/color_scheme_complete.json
  68. 0 0
      examples/requirement_extract/huahua/features/color_scheme/color_scheme_visual.png
  69. 0 0
      examples/requirement_extract/huahua/features/color_scheme/color_swatch.png
  70. 0 0
      examples/requirement_extract/huahua/features/color_scheme/img_1_colors.json
  71. 0 0
      examples/requirement_extract/huahua/features/color_scheme/img_1_palette.png
  72. 0 0
      examples/requirement_extract/huahua/features/color_scheme/img_2_colors.json
  73. 0 0
      examples/requirement_extract/huahua/features/color_scheme/img_2_palette.png
  74. 0 0
      examples/requirement_extract/huahua/features/color_scheme/img_3_colors.json
  75. 0 0
      examples/requirement_extract/huahua/features/color_scheme/img_3_palette.png
  76. 0 0
      examples/requirement_extract/huahua/features/color_scheme/img_4_colors.json
  77. 0 0
      examples/requirement_extract/huahua/features/color_scheme/img_4_palette.png
  78. 0 0
      examples/requirement_extract/huahua/features/color_scheme/img_5_colors.json
  79. 0 0
      examples/requirement_extract/huahua/features/color_scheme/img_5_palette.png
  80. 0 0
      examples/requirement_extract/huahua/features/color_scheme/mapping.json
  81. 0 0
      examples/requirement_extract/huahua/features/depth_map/depth_img_1.png
  82. 0 0
      examples/requirement_extract/huahua/features/depth_map/depth_img_2.png
  83. 0 0
      examples/requirement_extract/huahua/features/depth_map/depth_img_3.png
  84. 0 0
      examples/requirement_extract/huahua/features/depth_map/depth_img_4.png
  85. 0 0
      examples/requirement_extract/huahua/features/depth_map/depth_img_5.png
  86. 0 0
      examples/requirement_extract/huahua/features/depth_map/mapping.json
  87. 0 0
      examples/requirement_extract/huahua/features/easel_asset/easel_blank_canvas_img4.png
  88. 0 0
      examples/requirement_extract/huahua/features/easel_asset/mapping.json
  89. 0 0
      examples/requirement_extract/huahua/features/edge_map/img_1_canny.png
  90. 0 0
      examples/requirement_extract/huahua/features/edge_map/img_2_canny.png
  91. 0 0
      examples/requirement_extract/huahua/features/edge_map/img_3_canny.png
  92. 0 0
      examples/requirement_extract/huahua/features/edge_map/img_4_canny.png
  93. 0 0
      examples/requirement_extract/huahua/features/edge_map/img_5_canny.png
  94. 0 0
      examples/requirement_extract/huahua/features/edge_map/mapping.json
  95. 0 0
      examples/requirement_extract/huahua/features/lighting_bokeh/lighting_analysis.json
  96. 0 0
      examples/requirement_extract/huahua/features/lighting_bokeh/lighting_img_2.json
  97. 0 0
      examples/requirement_extract/huahua/features/lighting_bokeh/lighting_img_3.json
  98. 0 0
      examples/requirement_extract/huahua/features/lighting_bokeh/lighting_img_5.json
  99. 0 0
      examples/requirement_extract/huahua/features/lighting_bokeh/lighting_visual.png
  100. 0 0
      examples/requirement_extract/huahua/features/lighting_bokeh/mapping.json

+ 2 - 1
.claude/settings.local.json

@@ -16,7 +16,8 @@
       "Bash(npm run:*)",
       "Bash(npm run:*)",
       "Bash(sed:*)",
       "Bash(sed:*)",
       "Bash(PYTHONIOENCODING=utf-8 python:*)",
       "Bash(PYTHONIOENCODING=utf-8 python:*)",
-      "mcp__ide__getDiagnostics"
+      "mcp__ide__getDiagnostics",
+      "Bash(TOOL_AGENT_ROUTER_URL=\"http://43.106.118.91:8001\" python:*)"
     ],
     ],
     "deny": [],
     "deny": [],
     "ask": []
     "ask": []

+ 1 - 1
.gitignore

@@ -74,7 +74,7 @@ knowhub/knowhub.db-wal
 examples/archive/*
 examples/archive/*
 examples/research/
 examples/research/
 examples/downloader/
 examples/downloader/
-
+examples/production_restore/features/
 # Milvus data
 # Milvus data
 knowhub/milvus_data/
 knowhub/milvus_data/
 
 

+ 58 - 0
agent/core/runner.py

@@ -1635,6 +1635,12 @@ class AgentRunner:
                                 tool_args = self._try_fix_json(tool_args)
                                 tool_args = self._try_fix_json(tool_args)
                                 if tool_args is None:
                                 if tool_args is None:
                                     self.log.warning(f"[Tool Call] JSON 解析失败,跳过工具调用 {tool_name}: {tc['function']['arguments'][:200]}")
                                     self.log.warning(f"[Tool Call] JSON 解析失败,跳过工具调用 {tool_name}: {tc['function']['arguments'][:200]}")
+                                    # 修复 history 中 assistant message 里的残缺 JSON,
+                                    # 避免 Qwen API 拒绝 "function.arguments must be in JSON format"
+                                    tc["function"]["arguments"] = json.dumps(
+                                        {"_error": "JSON parse failed", "_raw": tc["function"]["arguments"][:200]},
+                                        ensure_ascii=False,
+                                    )
                                     history.append({
                                     history.append({
                                         "role": "tool",
                                         "role": "tool",
                                         "tool_call_id": tc["id"],
                                         "tool_call_id": tc["id"],
@@ -1658,6 +1664,14 @@ class AgentRunner:
                         if current_trace:
                         if current_trace:
                             trigger_event_for_tool = current_trace.context.get("active_side_branch", {}).get("trigger_event", "unknown")
                             trigger_event_for_tool = current_trace.context.get("active_side_branch", {}).get("trigger_event", "unknown")
 
 
+                    # 设置 trace_id 上下文供 toolhub 使用(图片保存到 outputs/{trace_id}/)
+                    if tool_name in ("toolhub_call", "toolhub_search", "toolhub_health"):
+                        try:
+                            from agent.tools.builtin.toolhub import set_trace_context
+                            set_trace_context(trace_id)
+                        except ImportError:
+                            pass
+
                     tool_result = await self.tools.execute(
                     tool_result = await self.tools.execute(
                         tool_name,
                         tool_name,
                         tool_args,
                         tool_args,
@@ -2282,8 +2296,52 @@ class AgentRunner:
         import copy
         import copy
         import hashlib
         import hashlib
         import asyncio
         import asyncio
+        import base64 as b64mod
+        import httpx
+        import mimetypes
         messages = copy.deepcopy(messages)
         messages = copy.deepcopy(messages)
 
 
+        # 预处理:将所有 HTTP(S) URL 图片下载并转为 base64 data URL
+        # Qwen API 无法访问外部签名 URL(如 BFL、火山引擎 TOS),必须在本地转换
+        url_download_jobs = []  # [(msg_idx, block_idx, url)]
+        for i, msg in enumerate(messages):
+            if msg.get("role") != "tool":
+                continue
+            content = msg.get("content")
+            if not isinstance(content, list):
+                continue
+            for block_idx, block in enumerate(content):
+                if isinstance(block, dict) and block.get("type") == "image_url":
+                    url = block.get("image_url", {}).get("url", "")
+                    if url.startswith(("http://", "https://")):
+                        url_download_jobs.append((i, block_idx, url))
+
+        if url_download_jobs:
+            async def _download_image_to_data_url(url: str) -> str | None:
+                try:
+                    async with httpx.AsyncClient(timeout=60, trust_env=False) as client:
+                        resp = await client.get(url)
+                        resp.raise_for_status()
+                        ct = resp.headers.get("content-type", "").split(";")[0].strip()
+                        if not ct.startswith("image/"):
+                            ct = mimetypes.guess_type(url.split("?")[0])[0] or "image/png"
+                        b64 = b64mod.b64encode(resp.content).decode()
+                        return f"data:{ct};base64,{b64}"
+                except Exception:
+                    return None
+
+            results = await asyncio.gather(
+                *[_download_image_to_data_url(url) for _, _, url in url_download_jobs],
+                return_exceptions=True
+            )
+            converted = 0
+            for (msg_idx, block_idx, original_url), result in zip(url_download_jobs, results):
+                if isinstance(result, str) and result.startswith("data:"):
+                    messages[msg_idx]["content"][block_idx]["image_url"]["url"] = result
+                    converted += 1
+            if converted:
+                self.log.info(f"[Image Optimization] URL→base64 预转换: {converted}/{len(url_download_jobs)} 张")
+
         # 统计优化情况
         # 统计优化情况
         stats = {"kept": 0, "downscaled": 0, "described": 0, "cache_hit": 0}
         stats = {"kept": 0, "downscaled": 0, "described": 0, "cache_hit": 0}
 
 

+ 3 - 1
agent/tools/builtin/__init__.py

@@ -21,7 +21,7 @@ from agent.tools.builtin.sandbox import (sandbox_create_environment, sandbox_run
 from agent.tools.builtin.knowledge import(knowledge_search,knowledge_save,knowledge_list,knowledge_update,knowledge_batch_update,knowledge_slim)
 from agent.tools.builtin.knowledge import(knowledge_search,knowledge_save,knowledge_list,knowledge_update,knowledge_batch_update,knowledge_slim)
 from agent.tools.builtin.knowledge_manager import ask_knowledge, upload_knowledge
 from agent.tools.builtin.knowledge_manager import ask_knowledge, upload_knowledge
 from agent.tools.builtin.context import get_current_context
 from agent.tools.builtin.context import get_current_context
-from agent.tools.builtin.toolhub import toolhub_health, toolhub_search, toolhub_call
+from agent.tools.builtin.toolhub import toolhub_health, toolhub_search, toolhub_call, image_uploader, image_downloader
 from agent.tools.builtin.resource import resource_list_tools, resource_get_tool
 from agent.tools.builtin.resource import resource_list_tools, resource_get_tool
 from agent.tools.builtin.crawler import youtube_search, youtube_detail, x_search, import_content, extract_video_clip
 from agent.tools.builtin.crawler import youtube_search, youtube_detail, x_search, import_content, extract_video_clip
 from agent.trace.goal_tool import goal
 from agent.trace.goal_tool import goal
@@ -67,6 +67,8 @@ __all__ = [
     "toolhub_health",
     "toolhub_health",
     "toolhub_search",
     "toolhub_search",
     "toolhub_call",
     "toolhub_call",
+    "image_uploader",
+    "image_downloader",
     # 资源查询
     # 资源查询
     "resource_list_tools",
     "resource_list_tools",
     "resource_get_tool",
     "resource_get_tool",

+ 356 - 26
agent/tools/builtin/toolhub.py

@@ -14,13 +14,20 @@ ToolHub - 远程工具库集成模块
   POST /chat        → 对话接口(不在此封装)
   POST /chat        → 对话接口(不在此封装)
 """
 """
 
 
+import base64
+import contextvars
 import json
 import json
-from typing import Any, Dict, Optional
+import logging
+import mimetypes
+import time
+from pathlib import Path
+from typing import Any, Dict, List, Optional
 
 
 import httpx
 import httpx
 
 
 from agent.tools import tool, ToolResult
 from agent.tools import tool, ToolResult
 
 
+logger = logging.getLogger(__name__)
 
 
 # ── 配置 ─────────────────────────────────────────────
 # ── 配置 ─────────────────────────────────────────────
 
 
@@ -28,6 +35,189 @@ TOOLHUB_BASE_URL = "http://43.106.118.91:8001"
 DEFAULT_TIMEOUT = 30.0
 DEFAULT_TIMEOUT = 30.0
 CALL_TIMEOUT = 600.0   # 图像生成类工具耗时较长,云端机器启动可能需要数分钟
 CALL_TIMEOUT = 600.0   # 图像生成类工具耗时较长,云端机器启动可能需要数分钟
 
 
+# OSS 上传配置
+OSS_BUCKET_NAME = "aigc-admin"
+OSS_BUCKET_PATH = "toolhub_images"
+
+# 输出目录(相对于项目根目录)
+OUTPUT_BASE_DIR = Path("outputs")
+
+# trace_id 上下文变量,由 runner 在执行工具前设置
+_trace_id_var: contextvars.ContextVar[str] = contextvars.ContextVar("toolhub_trace_id", default="")
+
+
+def set_trace_context(trace_id: str):
+    """由 runner 调用,设置当前 trace_id 供图片保存使用"""
+    _trace_id_var.set(trace_id)
+
+
+def _get_output_dir(tool_id: str) -> Path:
+    """获取图片输出目录:outputs/{trace_id}/,无 trace_id 时用时间戳"""
+    trace_id = _trace_id_var.get("")
+    if trace_id:
+        # trace_id 可能含 @ 等特殊字符,取前段作为目录名
+        safe_id = trace_id.split("@")[0][:12] if "@" in trace_id else trace_id[:12]
+        out_dir = OUTPUT_BASE_DIR / safe_id
+    else:
+        out_dir = OUTPUT_BASE_DIR / f"no_trace_{int(time.time())}"
+    out_dir.mkdir(parents=True, exist_ok=True)
+    return out_dir
+
+
+# ── 图片处理辅助 ─────────────────────────────────────
+
+async def _upload_to_oss(local_path: str) -> Optional[str]:
+    """上传本地文件到 OSS,返回 CDN URL"""
+    try:
+        from cyber_sdk.ali_oss import upload_localfile
+        import os
+        safe_path = os.path.abspath(local_path).replace("\\", "/")
+        result = await upload_localfile(
+            file_path=safe_path,
+            bucket_path=OSS_BUCKET_PATH,
+            bucket_name=OSS_BUCKET_NAME,
+        )
+        oss_key = result.get("oss_object_key")
+        if oss_key:
+            cdn_url = f"https://res.cybertogether.net/{oss_key}"
+            logger.info(f"[ToolHub] 图片已上传 OSS: {cdn_url}")
+            return cdn_url
+    except Exception as e:
+        logger.warning(f"[ToolHub] OSS 上传失败: {e}")
+    return None
+
+
+async def _process_images(raw_images: List[str], tool_id: str) -> tuple:
+    """
+    统一处理工具返回的图片列表。
+
+    对每张图片:下载(如需) → 保存本地 → 上传 OSS → 拿到 CDN URL
+
+    Returns:
+        (images_for_llm, cdn_urls, saved_paths)
+        - images_for_llm: 给 runner 的图片列表(base64 格式,用于 LLM 多模态查看)
+        - cdn_urls: 永久 CDN URL 列表
+        - saved_paths: 本地文件路径列表
+    """
+    images_for_llm = []
+    cdn_urls = []
+    saved_paths = []
+    original_urls = []
+
+    out_dir = _get_output_dir(tool_id)
+
+    for idx, img in enumerate(raw_images):
+        if not isinstance(img, str) or len(img) <= 100:
+            continue
+
+        img_bytes = None
+        media_type = "image/png"
+
+        if img.startswith(("http://", "https://")):
+            original_urls.append(img)
+            try:
+                async with httpx.AsyncClient(timeout=60, trust_env=False) as dl:
+                    img_resp = await dl.get(img)
+                    img_resp.raise_for_status()
+                    ct = img_resp.headers.get("content-type", "image/png").split(";")[0].strip()
+                    if not ct.startswith("image/"):
+                        ct = mimetypes.guess_type(img.split("?")[0])[0] or "image/png"
+                    media_type = ct
+                    img_bytes = img_resp.content
+            except Exception as e:
+                logger.warning(f"[ToolHub] 图片下载失败: {e}")
+                continue
+
+        elif img.startswith("data:"):
+            header, b64 = img.split(",", 1)
+            media_type = header.split(";")[0].replace("data:", "")
+            img_bytes = base64.b64decode(b64)
+
+        else:
+            # raw base64
+            img_bytes = base64.b64decode(img)
+
+        if not img_bytes:
+            continue
+
+        # 1. 保存本地(用时间戳区分多次调用)
+        ts = int(time.time() * 1000)
+        ext = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}.get(media_type, ".png")
+        save_path = out_dir / f"{tool_id}_{ts}_{idx}{ext}"
+        save_path.write_bytes(img_bytes)
+        saved_paths.append(str(save_path))
+
+        # 2. 上传 OSS 拿 CDN URL
+        cdn_url = await _upload_to_oss(str(save_path))
+        if cdn_url:
+            cdn_urls.append(cdn_url)
+
+        # 3. base64 给 LLM 多模态查看
+        b64_data = base64.b64encode(img_bytes).decode()
+        images_for_llm.append({"type": "base64", "media_type": media_type, "data": b64_data})
+
+    return images_for_llm, cdn_urls, saved_paths
+
+
+async def _preprocess_params(params: Dict[str, Any]) -> Dict[str, Any]:
+    """
+    预处理工具参数:检测本地文件路径,自动上传到 OSS 并替换为 CDN URL。
+
+    支持的参数名:image, image_url, mask_image, pose_image, images (数组)
+    """
+    if not params:
+        return params
+
+    processed = params.copy()
+
+    # 单个图片参数
+    for key in ("image", "image_url", "mask_image", "pose_image"):
+        if key in processed and isinstance(processed[key], str):
+            val = processed[key]
+            # 检测是否为本地路径(不是 http/https/data: 开头)
+            if not val.startswith(("http://", "https://", "data:")):
+                # 尝试读取本地文件
+                try:
+                    from pathlib import Path
+                    p = Path(val)
+                    if p.exists() and p.is_file():
+                        logger.info(f"[ToolHub] 检测到本地文件 {key}={val},上传到 OSS...")
+                        cdn_url = await _upload_to_oss(str(p.resolve()))
+                        if cdn_url:
+                            processed[key] = cdn_url
+                            logger.info(f"[ToolHub] {key} 已替换为 CDN URL: {cdn_url}")
+                        else:
+                            logger.warning(f"[ToolHub] {key} 上传失败,保持原路径")
+                except Exception as e:
+                    logger.warning(f"[ToolHub] {key} 路径处理失败: {e}")
+
+    # images 数组参数
+    if "images" in processed and isinstance(processed["images"], list):
+        new_images = []
+        for idx, img in enumerate(processed["images"]):
+            if isinstance(img, str) and not img.startswith(("http://", "https://", "data:")):
+                try:
+                    from pathlib import Path
+                    p = Path(img)
+                    if p.exists() and p.is_file():
+                        logger.info(f"[ToolHub] 检测到本地文件 images[{idx}]={img},上传到 OSS...")
+                        cdn_url = await _upload_to_oss(str(p.resolve()))
+                        if cdn_url:
+                            new_images.append(cdn_url)
+                            logger.info(f"[ToolHub] images[{idx}] 已替换为 CDN URL: {cdn_url}")
+                        else:
+                            new_images.append(img)
+                    else:
+                        new_images.append(img)
+                except Exception as e:
+                    logger.warning(f"[ToolHub] images[{idx}] 路径处理失败: {e}")
+                    new_images.append(img)
+            else:
+                new_images.append(img)
+        processed["images"] = new_images
+
+    return processed
+
 
 
 # ── 工具实现 ──────────────────────────────────────────
 # ── 工具实现 ──────────────────────────────────────────
 
 
@@ -215,9 +405,12 @@ async def toolhub_call(
         ToolResult 包含工具执行结果
         ToolResult 包含工具执行结果
     """
     """
     try:
     try:
+        # 预处理参数:本地文件路径自动上传成 CDN URL
+        params = await _preprocess_params(params or {})
+
         payload = {
         payload = {
             "tool_id": tool_id,
             "tool_id": tool_id,
-            "params": params or {},
+            "params": params,
         }
         }
         async with httpx.AsyncClient(timeout=CALL_TIMEOUT, trust_env=False) as client:
         async with httpx.AsyncClient(timeout=CALL_TIMEOUT, trust_env=False) as client:
             resp = await client.post(
             resp = await client.post(
@@ -231,33 +424,36 @@ async def toolhub_call(
             result = data.get("result", {})
             result = data.get("result", {})
             result_str = json.dumps(result, ensure_ascii=False, indent=2)
             result_str = json.dumps(result, ensure_ascii=False, indent=2)
 
 
-            # 提取 base64 图片附件(单张 image 字段
+            # 提取图片并统一处理(下载 → 保存本地 → 上传 OSS → CDN URL
             images = []
             images = []
             if isinstance(result, dict):
             if isinstance(result, dict):
-                for img_key in ("image",):
-                    if result.get(img_key):
-                        images.append({
-                            "type": "base64",
-                            "media_type": "image/png",
-                            "data": result[img_key],
-                        })
-                        result_display = {k: v for k, v in result.items() if k != img_key}
-                        result_display[img_key] = f"<base64 image, {len(result[img_key])} chars>"
-                        result_str = json.dumps(result_display, ensure_ascii=False, indent=2)
-
-                # images 列表字段(如 nano_banana 返回 images 数组)
+                # 收集所有图片(单张 image 字段 + images 列表字段)
+                raw_images = []
+                has_single_image = False
+                has_images_list = False
+
+                if result.get("image") and isinstance(result["image"], str):
+                    raw_images.append(result["image"])
+                    has_single_image = True
+
                 if result.get("images") and isinstance(result["images"], list):
                 if result.get("images") and isinstance(result["images"], list):
-                    for img in result["images"]:
-                        if isinstance(img, str) and len(img) > 100:
-                            # data URL 或 base64
-                            if img.startswith("data:"):
-                                header, b64 = img.split(",", 1)
-                                mime = header.split(";")[0].replace("data:", "")
-                                images.append({"type": "base64", "media_type": mime, "data": b64})
-                            else:
-                                images.append({"type": "base64", "media_type": "image/png", "data": img})
-                    result_display = {k: v for k, v in result.items() if k != "images"}
-                    result_display["images"] = f"<{len(result['images'])} images>"
+                    raw_images.extend(result["images"])
+                    has_images_list = True
+
+                if raw_images:
+                    images, cdn_urls, saved_paths = await _process_images(raw_images, tool_id)
+
+                    # 构建文本输出(去掉原始图片数据)
+                    result_display = {k: v for k, v in result.items() if k not in ("image", "images")}
+                    if cdn_urls:
+                        result_display["cdn_urls"] = cdn_urls
+                        result_display["_note"] = (
+                            "图片已上传至 CDN(永久链接),可通过 cdn_urls 访问、传给其他工具或下载保存。"
+                            "同时也作为附件附加在本条消息中可直接查看。"
+                        )
+                    if saved_paths:
+                        result_display["saved_files"] = saved_paths
+                    result_display["image_count"] = len(images)
                     result_str = json.dumps(result_display, ensure_ascii=False, indent=2)
                     result_str = json.dumps(result_display, ensure_ascii=False, indent=2)
 
 
             return ToolResult(
             return ToolResult(
@@ -285,3 +481,137 @@ async def toolhub_call(
             output="",
             output="",
             error=str(e),
             error=str(e),
         )
         )
+
+
+@tool(
+    display={
+        "zh": {"name": "上传本地图片", "params": {"local_path": "本地文件路径"}},
+        "en": {"name": "Upload Local Image", "params": {"local_path": "Local file path"}},
+    }
+)
+async def image_uploader(local_path: str) -> ToolResult:
+    """将本地图片上传到 OSS,返回可用的 CDN URL(image_url)
+
+    当你需要获取一张本地图片的 HTTP 链接时使用此工具。
+    传入本地文件路径,自动上传到 OSS 并返回永久 CDN URL。
+
+    注意:在调用 toolhub_call 时,image/image_url 等参数可以直接传本地路径,
+    系统会自动上传。此工具适用于你需要单独获取图片 URL 的场景。
+
+    Args:
+        local_path: 本地图片文件路径(相对路径或绝对路径均可)
+
+    Returns:
+        ToolResult 包含上传后的 CDN URL
+    """
+    import os
+    from pathlib import Path
+
+    p = Path(local_path)
+    if not p.exists():
+        return ToolResult(
+            title="图片上传失败",
+            output="",
+            error=f"文件不存在: {local_path}",
+        )
+    if not p.is_file():
+        return ToolResult(
+            title="图片上传失败",
+            output="",
+            error=f"路径不是文件: {local_path}",
+        )
+
+    cdn_url = await _upload_to_oss(str(p.resolve()))
+    if cdn_url:
+        result = {
+            "local_path": str(p.resolve()),
+            "cdn_url": cdn_url,
+            "file_size": os.path.getsize(p),
+        }
+        return ToolResult(
+            title="图片上传成功",
+            output=json.dumps(result, ensure_ascii=False, indent=2),
+            long_term_memory=f"Uploaded {local_path} → {cdn_url}",
+        )
+    else:
+        return ToolResult(
+            title="图片上传失败",
+            output="",
+            error=f"OSS 上传失败,请检查文件路径和网络连接: {local_path}",
+        )
+
+
+@tool(
+    display={
+        "zh": {"name": "下载图片到本地", "params": {"url": "图片URL", "save_path": "保存路径"}},
+        "en": {"name": "Download Image", "params": {"url": "Image URL", "save_path": "Save path"}},
+    }
+)
+async def image_downloader(url: str, save_path: str = "") -> ToolResult:
+    """下载网络图片到本地文件
+
+    从 HTTP/HTTPS 链接下载图片并保存到本地。
+    适用于需要将 CDN 图片、生成结果等保存到本地目录的场景。
+
+    Args:
+        url: 图片的 HTTP/HTTPS 链接
+        save_path: 本地保存路径(相对或绝对路径均可)。
+                   如不指定,自动保存到当前输出目录,文件名从 URL 提取。
+
+    Returns:
+        ToolResult 包含下载后的本地文件路径和文件大小
+    """
+    import os
+    from pathlib import Path
+    from urllib.parse import urlparse, unquote
+
+    if not url.startswith(("http://", "https://")):
+        return ToolResult(
+            title="图片下载失败",
+            output="",
+            error=f"无效的 URL(必须以 http:// 或 https:// 开头): {url}",
+        )
+
+    # 自动生成保存路径
+    if not save_path:
+        out_dir = _get_output_dir("download")
+        # 从 URL 提取文件名
+        url_path = urlparse(url).path
+        filename = Path(unquote(url_path)).name if url_path else ""
+        if not filename or not any(filename.lower().endswith(ext) for ext in (".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp")):
+            filename = f"download_{int(time.time())}.png"
+        save_path = str(out_dir / filename)
+
+    # 确保目录存在
+    p = Path(save_path)
+    p.parent.mkdir(parents=True, exist_ok=True)
+
+    try:
+        async with httpx.AsyncClient(timeout=60.0, follow_redirects=True, trust_env=False) as client:
+            resp = await client.get(url)
+            resp.raise_for_status()
+            p.write_bytes(resp.content)
+
+        file_size = os.path.getsize(p)
+        result = {
+            "save_path": str(p.resolve()),
+            "file_size": file_size,
+            "source_url": url,
+        }
+        return ToolResult(
+            title="图片下载成功",
+            output=json.dumps(result, ensure_ascii=False, indent=2),
+            long_term_memory=f"Downloaded {url} → {save_path}",
+        )
+    except httpx.HTTPStatusError as e:
+        return ToolResult(
+            title="图片下载失败",
+            output="",
+            error=f"HTTP 错误 {e.response.status_code}: {url}",
+        )
+    except Exception as e:
+        return ToolResult(
+            title="图片下载失败",
+            output="",
+            error=f"下载失败: {e}",
+        )

+ 45 - 0
consistency_requirements.json

@@ -0,0 +1,45 @@
+{
+  "consistency_check_standards": {
+    "1_character_consistency": {
+      "description": "角色一致性",
+      "criteria": [
+        "面部特征一致",
+        "发型:棕色长发",
+        "肤色一致"
+      ]
+    },
+    "2_costume_consistency": {
+      "description": "服装一致性",
+      "criteria": [
+        "白色长裙",
+        "V 字露背设计",
+        "腰部系带",
+        "轻薄棉麻质地"
+      ]
+    },
+    "3_color_scheme": {
+      "description": "配色方案",
+      "criteria": [
+        "白绿配色",
+        "绿色饱和度中等偏高",
+        "纯白服装"
+      ]
+    },
+    "4_lighting_consistency": {
+      "description": "光影一致性",
+      "criteria": [
+        "逆光/轮廓光从左上方照射",
+        "光晕效果统一"
+      ]
+    },
+    "5_style_consistency": {
+      "description": "风格一致性",
+      "criteria": [
+        "真实摄影风格",
+        "85mm 镜头效果",
+        "f/1.8 浅景深"
+      ]
+    }
+  },
+  "evaluation_instruction": "请逐项检查每张图像在 5 个维度上的表现,识别不符合标准的图像,并详细说明问题所在。"
+}

+ 0 - 9
examples/feature_extract/presets.json

@@ -1,9 +0,0 @@
-{
-  "tool_research": {
-    "system_prompt_file": "tool_research.prompt",
-    "max_iterations": 50,
-    "temperature": 0.3,
-    "skills": ["planning", "research", "browser"],
-    "description": "工具调研 Agent - 为制作需求系统性搜索方法和工具"
-  }
-}

+ 0 - 109
examples/feature_extract/requirement.prompt

@@ -1,109 +0,0 @@
----
-model: qwen3.5-plus
-temperature: 0.3
----
-
-$system$
-
-## 角色
-你是社媒内容制作专家,擅长规划内容制作流程和计划。你的计划会尽可能使用AI工具或获取网络资源来完成内容制作,尽量减少实景拍摄等需要人类参与的制作。
-你的工作流程是:理解内容制作需求 → 搜索获取制作策略 → 形成初步的制作工序 → 拆解需要进一步调研的具体问题。
-你不需要关心具体实现细节(工具参数、模型权重等),只需确定整体制作工序。
-
-
-## 工作流程
-
-### 第一步:制作需求分析
-
-读取核心文件,理解制作需求:
-- `%input_dir%/index.md`(导航概览)
-- `%input_dir%/descriptions/制作亮点.md`
-- `%input_dir%/descriptions/制作点.md`(核心制作元素及权重)
-- `%input_dir%/descriptions/创作表.md`(创作视角描述,如存在)
-
-目标:明确哪些点要精准完成制作,哪些点容易出错。
-
-**输出** `%output_dir%/analysis.json`,schema 如下:
-
-```jsonschema
-{
-  "category": {
-    "name": "string — 内容品类名称",
-    "traits": ["string — 品类典型特征"],
-    "ai_challenges": ["string — 该品类 AI 还原的共性挑战"],
-    "reasoning": "string — 判断依据"
-  },
-  "highlight": [
-    {
-      "name": "string — 亮点名称(站在制作规划角度,从制作亮点归纳)",
-      "description": "string — 必须高度还原的内容特征",
-      "reasoning": "string — 为什么是上限点"
-    }
-  ],
-  "baseline": [
-    {
-      "name": "string — 基础下限点名称(自行总结)",
-      "description": "string — 做不好会导致'一眼假'的特征",
-      "why_critical": "string — 为什么重要,做不好会怎样",
-      "reasoning": "string — 判断依据"
-    }
-  ],
-  "requirement_summary": ["string — 整合品类特征、亮点、下限点的制作需求清单"]
-}
-```
-
-每条结论必须附带推理过程。
-
-
-### 第二步:搜索制作策略(如何基于制作需求完成内容生成的方法论)
-
-**前置**:基于 analysis.json 确认需求。
-**方法**:你要调用 `strategy_search` agent来完成具体的搜索工作,并根据以下标准进行评估;如果评估不通过,要指示该agent继续搜索。
-**标准**:
-核心问题:哪些制作策略能同时支持好亮点和下限点?
-策略评估维度:与当前需求场景的匹配度、亮点/下限点覆盖程度、依赖工具能力(是否可用)、优点、局限性、风险。
-**输出** 将最终确认的可参考的制作策略输出到`%output_dir%/strategy.json`,schema 如下:
-
-```jsonschema
-{
-  "strategies": [
-    {
-      "name": "string — 策略名称",
-      "source": "string — 来源(knowledge_id / URL / 帖子链接)",
-      "core_idea": "string — 核心思路",
-      "tool_dependencies": ["string — 依赖的工具能力"],
-      "highlight_coverage": ["string — 能覆盖的亮点"],
-      "baseline_coverage": ["string — 能覆盖的基础下限点"],
-      "pros": ["string"],
-      "cons": ["string"],
-      "risks": ["string"],
-      "feasibility": "high | medium | low",
-      "reasoning": "string — 选择理由"
-    }
-  ]
-}
-```
-
-### 第三步:精细读取具体素材,制定本次制作的具体制作工序
-
-**前置**:基于 analysis.json 和 strategy.json。
-**输出**:`%output_dir%/plan.md`,需要包含:主要步骤和各步骤的:理由、输入、输出、关联需求、风险(若有)、其他(若有)
-**要求**:
-- 阶段粒度:可独立描述目标和产物的流程单元,不过细也不过粗
-- 规格完整性:每个步骤必须写明输入和输出;步骤之间的输出和输入良好衔接
-- 需求全覆盖:analysis.json 每个上限点和下限点至少出现在一个阶段的 关联需求 中
-- 素材利用:已有素材在输入中标注路径
-**方法**:
-如果对使用什么方法、什么工具或者具体工具能力边界有疑虑,可以使用 `tool_research` agent进行调研。
-
-
-工序制定的参考策略:双向收敛构建法
-**自顶向下(需求拆解)**:从目标特征规格出发,拆解子特征和组成部分。
-**自底向上(能力推导)**:从已有素材和工具能力出发,推导可稳定产出的特征集合。
-**中间对齐(规格匹配)**:
-- 供给节点产出特征覆盖需求节点特征约束 → 路径可行
-- 无法覆盖 → 需更换工具/素材、调整路径、或降低制作标准
-
-$user$
-分析以下的制作需求,完成制作工序设计:
-%input_dir%

+ 0 - 128
examples/feature_extract/tool_research.prompt

@@ -1,128 +0,0 @@
----
-model: sonnet-4.6
-temperature: 0.3
----
-
-$system$
-## 角色
-你是社媒内容制作专家,擅长调研和分析内容制作的方法和工具。你尤其关注使用AI工具或获取网络资源来完成内容制作,尽量减少实景拍摄等需要人类参与的制作。
-
-## 任务
-你的任务是为指定的制作需求系统性搜索方法和工具。
-
-## 核心原则
-
-### 需求驱动,而非工具驱动
-调研的目标是完成需求,不是为了找工具而找工具。
-- query必须支撑需求或与需求对应
-- 工具不对可以换:如果调研中发现某工具无法满足需求,立即换方向,不要死磕
-
-### 信息评估
-1. 相关性:该工具/情报是否精准契合当前要解决的需求?
-2. 可用性:过滤后续无法被 agent 使用的工具
-  2.1. 过滤纯手机 app
-  2.2. 过滤本地桌面应用,如 PS
-3. 热度过滤:发布一定时间,但没有任何点赞、互动、阅读等数据的信息。
-
-### 工具评估
-1. 内在维度(工具自带的属性)
-- 时效性:越新越好
-- 智能化:越智能的越好
-- 通用性:越通用的越好
-
-1. 外部置信度(外界的反馈与背书)
-- 交叉验证(曝光率):在不同平台,不同内容提及次数越多的越好
-- 专家/平台背书(权威性):
-  - 赛道内头部 KOL 的推荐
-  - 一些专业平台的榜单(如Hugging Face榜单、liblib 热门榜单)
-- 帖子本身热度高、评论正面反馈多
-- 有实际效果案例展示
-
-### AI工具时效性硬约束
-当前时间:%current_time%。所有评估必须以此为基准。
-- 最近更新在 6 个月内:活跃
-- 6-12 个月:老化,需额外验证是否仍可用
-- 超过 12 个月:视为过时,除非有明确证据表明仍是主流方案
-每条评估必须标注信息的时间戳,并说明与当前时间的差距。
-稳定的基本工具不受此限制;但是AI工具迭代很快,需要考虑当前是否依然是优越工具。
-
-### 工具知识定义
-调研中发现的每个工具,必须按以下结构记录:
-1. **工具名称**:全称 + 常用简称
-2. **优势与劣势**:基于调研的客观评价
-3. **输入与输出格式**:该工具接受什么输入、产出什么输出(文件格式、数据结构)
-4. **时间线记录**:
-   - 工具时间:发布日期或最近一次重大更新时间
-   - 情报时间:发现该工具的帖子/文章/教程的发布时间(用于判断信息新旧)
-5. **使用案例**:真实跑通的场景描述和来源
-6. **工序定位**(如有):该工具在整个生产环节中处于哪一步?和哪些工具配合度高?
-调研中积累的工具知识用 save_knowledge 存储时,也遵循此结构。搜索策略可根据需求,在知识库中按此结构检索已有工具评估。
-
-### 迭代调研
-调研不是一轮结束的。每次评估后,如果发现:
-- 某个维度的信息不足(缺专家评价、缺消费者反馈等)
-- 评估结论不够确定(confidence < 8)
-- 多渠道评价不一致
-则必须继续调研,补充缺失维度,直到评估结论可信。
-
-## 参考策略
-
-**双向推演法**:
-- **工具找用例(正向)**:适合需求冷门时
-  - 流程:发现新工具 → 搜索工具名称 → 从用例中判断质量
-  - 优势:更容易找到工具
-  - 劣势:需大量搜索才能判断信息质量,缺少案例时难以评估
-- **用例找工具(反向)**:适合需求常见时
-  - 流程:找到匹配的案例分享 → 提取背后的工具组合
-  - 优势:搜索效率高,一旦命中即可直接匹配需求
-  - 劣势:依赖创作者是否开源分享,冷门工具或保密工作流时容易碰壁
-
-**粗到细策略**:先调研该类型下有哪些工具,再筛选出与需求相关的工具进行深入调研。
-
-
-## 输出:筛选给出最佳搜索结果
-
-**标准**:
-1. 综合可信度(overall_confidence)≥ 8 优先
-2. 与阶段 required_spec 的匹配度
-3. 工具可用性和稳定性
-4. 外部反馈的一致性
-
-**路径** `%output_dir%/tool_<requirement>_<round>.json`:
-
-```jsonschema
-{
-  "trace": "string — 如何完成的搜索,比如尝试了哪些query、根据阶段性结果如何调整后序搜索等等"
-  "selected_approach": {
-    "tool": "string — 选定工具",
-    "version": "string",
-    "use_case_refs": ["string — 参考用例来源"],
-    "implementation_outline": "string — 实现思路概要",
-    "confidence": "number — 1-10",
-    "reasoning": "string — 为什么选这个方案"
-  },
-  "alternatives": [
-    {
-      "tool": "string",
-      "why_not": "string",
-      "could_switch_if": "string"
-    }
-  ],
-  "risks": [
-    {
-      "risk": "string",
-      "severity": "high | medium | low",
-      "mitigation": "string"
-    }
-  ],
-  "unresolved": ["string — 未能验证的问题,需人工确认"]
-}
-```
-
-## 注意事项
-
-- 优先使用 search_knowledge
-- 调研中发现的知识用 save_knowledge 存储
-- 登陆时,或不确定时联系关涛(feishu)
-- search_posts 不好用时改用 browser-use
-

+ 136 - 0
examples/mini_restore/call_banana.py

@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+🍌 Nano Banana (千层套路·多模态生成) 外部调用脚本
+
+【工具定位】:它使用的是 Google 最新的多模态 Imagen 3 引擎(gemini-3.1-flash-image-preview)。
+【独门绝技 - 跨模态多图推理】:
+与传统的 SD / Flux (图生图) 的本质区别在于,你可以给它丢【任意张完全不同的图片】,
+然后在 prompt 里随心所欲地让它“融梗”交汇。
+比如传入图片 [猫.jpg] 和 [未来城.jpg],让它“把这只猫生成在这座未来城里”。
+
+【参数模式支持】:
+1. 纯文生图:只传 prompt
+2. 单图生图/重绘:传 1 张图 + prompt
+3. 多路意象融合:传 N 张图 + prompt
+
+【本地文件自动上传机制】:
+大模型 API 往往更喜欢吃稳定的 CDN 外链。
+此脚本在启动前会自动检测你传入的图片:如果是本地硬盘文件(比如 examples/cat.png),
+脚本会自动静默调用内部的 OSS 工具,把它秒传为 https://res.cybertogether.net/.. 干净外链,然后投喂给大脑!
+"""
+
+import asyncio
+import os
+import argparse
+import sys
+import httpx
+import json
+
+# 动态引入我们系统现成的 CDN 上传脚本
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'production_restore'))
+try:
+    from upload import upload_image
+except ImportError:
+    print("错误: 找不到 upload.py。请确保在 tests 目录下运行此脚本。")
+    sys.exit(1)
+
+ROUTER_URL = "http://43.106.118.91:8001/run_tool"
+
+async def process_images(images_list: list[str]) -> list[str]:
+    """处理图片数组,外链直接过,本地文件传 OSS"""
+    final_urls = []
+    for item in images_list:
+        if item.startswith("http://") or item.startswith("https://"):
+            print(f"✅ 检测到公网外链,无需转存: {item}")
+            final_urls.append(item)
+        elif os.path.exists(item):
+            print(f"📦 正在极速倒卖本地图片到 CDN: {item}")
+            try:
+                uploaded_url = await upload_image(item, 'aigc-admin', 'crawler/image')
+                if uploaded_url:
+                    print(f"🚀 上传成功: {uploaded_url}")
+                    final_urls.append(uploaded_url)
+                else:
+                    print(f"❌ 上传失败: {item}")
+            except Exception as e:
+                print(f"❌ 上传报错: {e}")
+        else:
+            print(f"⚠️ 跳过找不到的本地文件或无法识别的格式: {item}")
+    return final_urls
+
+async def run_nano_banana(prompt: str, images: list[str] = None, model: str = None):
+    print(f"\n=======================")
+    print(f"🍌 Nano Banana 启动中...")
+    print(f"=======================")
+
+    # 1. 整理图片
+    final_image_urls = []
+    if images and len(images) > 0:
+        print(f"🔍 检查到传了 {len(images)} 张神秘原图,准备过安检...")
+        final_image_urls = await process_images(images)
+    
+    # 2. 组装发给大模型中枢 Router 的参数
+    params = {
+        "prompt": prompt,
+        "image_urls": final_image_urls if final_image_urls else None
+    }
+    
+    if model:
+        params["model"] = model
+
+    payload = {
+        "tool_id": "nano_banana",
+        "params": params
+    }
+
+    # 3. 轰入 API
+    print("\n⚡ 正在呼叫总后台路由节点打怪...")
+    try:
+        async with httpx.AsyncClient(timeout=300.0) as client:
+            resp = await client.post(ROUTER_URL, json=payload)
+            resp.raise_for_status()
+            
+            result = resp.json()
+            if result.get("status") == "success":
+                gen_data = result.get("result", {})
+                
+                print("\n🎉 === 生成成功! ===")
+                # 打印文本回复
+                if gen_data.get("text"):
+                    print(f"\n💬 模型回话:\n{gen_data.get('text')}")
+                
+                # 打印图片输出
+                images_out = gen_data.get("images", [])
+                if images_out:
+                    print("\n🖼️ 吐出的神图 (Base64 数据流已转为你本地文件):")
+                    for idx, img_b64 in enumerate(images_out):
+                        # 处理前缀 data:image/jpeg;base64,
+                        if img_b64.startswith("data:"):
+                            mime_split = img_b64.split(";base64,")
+                            if len(mime_split) == 2:
+                                ext = mime_split[0].split("/")[-1]
+                                raw_data = mime_split[1]
+                                
+                                import base64
+                                save_path = f"banana_output_{idx}.{ext}"
+                                with open(save_path, "wb") as f:
+                                    f.write(base64.b64decode(raw_data))
+                                print(f" 💾 已保存到本地 -> {save_path}")
+            else:
+                print(f"❌ 大模型傲娇了: {result.get('error')}")
+                
+    except Exception as e:
+        print(f"💥 网络大爆炸报错: {e}")
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="调用 Nano Banana 进行任意风格的多段融合魔法")
+    parser.add_argument("-p", "--prompt", type=str, required=True, help="你想对 AI 喊瞎什么 (比如:用图1的赛博风画一只图2里的猫)")
+    parser.add_argument("-i", "--images", type=str, nargs="+", help="无限追加的垫图清单(可以是现成的 http 链接,也可以是你电脑里的硬盘文件如 example.png)")
+    parser.add_argument("-m", "--model", type=str, default=None, help="覆盖模型 (默认后台会走 gemini-3.1-flash-image-preview)")
+    
+    args = parser.parse_args()
+    
+    asyncio.run(run_nano_banana(prompt=args.prompt, images=args.images, model=args.model))

+ 0 - 0
examples/new_search/config.py → examples/mini_restore/config.py


BIN
examples/mini_restore/input/img_1.png


BIN
examples/mini_restore/input/img_2.png


BIN
examples/mini_restore/input/img_3.png


BIN
examples/mini_restore/input/img_4.png


BIN
examples/mini_restore/input/img_5.png


+ 0 - 0
examples/new_search/new_search.prompt → examples/mini_restore/new_search.prompt


+ 0 - 0
examples/new_search/run.py → examples/mini_restore/run.py


+ 70 - 0
examples/mini_restore/upload.py

@@ -0,0 +1,70 @@
+import asyncio
+import os
+import sys
+import httpx
+from cyber_sdk.ali_oss import upload_localfile
+
+async def upload_image(local_file_path: str, bucket_name: str = 'aigc-admin', bucket_path: str = 'template') -> str:
+    """Uploads a local image to OSS and returns the CDN URL."""
+    print(f"Uploading {local_file_path} to {bucket_name}/{bucket_path}...")
+    # Use forward slashes so cyber_sdk's .split('/') can correctly extract filename on Windows
+    safe_path = os.path.abspath(local_file_path).replace("\\", "/")
+    result = await upload_localfile(
+        file_path=safe_path, 
+        bucket_path=bucket_path, 
+        bucket_name=bucket_name
+    )
+    print("Upload SDK Response:", result)
+    
+    oss_object_key = result.get('oss_object_key')
+    if oss_object_key:
+        cdn_url = f"https://res.cybertogether.net/{oss_object_key}"
+        return cdn_url
+    return None
+
+async def download_image(url: str, save_path: str):
+    """Downloads an image from an HTTP link and saves it locally."""
+    print(f"Downloading from {url} to {save_path}...")
+    async with httpx.AsyncClient(timeout=30.0) as client:
+        resp = await client.get(url)
+        resp.raise_for_status()
+        with open(save_path, 'wb') as f:
+            f.write(resp.content)
+    print(f"Download completed: {save_path}")
+
+async def main():
+    if len(sys.argv) < 2:
+        print("Usage:")
+        print("  Upload: python upload.py <file_path>")
+        print("  Download: python upload.py download <url> <save_path>")
+        
+        # Self-test block if no param is given
+        print("\n--- Running Self Test ---")
+        test_file = 'img_1_gen.png'
+        if not os.path.exists(test_file):
+            print(f"Creating a dummy 1x1 PNG at {test_file} for testing.")
+            with open(test_file, 'wb') as f:
+                f.write(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01\r\n-\xb4\x00\x00\x00\x00IEND\xaeB`\x82')
+        
+        url = await upload_image(test_file, 'aigc-admin', 'crawler/image')
+        print(f"\nExtracted URL: {url}")
+        
+        if url:
+            # Download it back
+            download_path = "downloaded_dummy.png"
+            await download_image(url, download_path)
+            
+    elif sys.argv[1] == 'download':
+        if len(sys.argv) >= 4:
+            await download_image(sys.argv[2], sys.argv[3])
+        else:
+            print("Error: Missing parameters for download.")
+            print("Usage: python upload.py download <url> <save_path>")
+    else:
+        # Upload context
+        file_path = sys.argv[1]
+        url = await upload_image(file_path, 'aigc-admin', 'crawler/image')
+        print(f"\nFinal CDN URL: {url}")
+
+if __name__ == '__main__':
+    asyncio.run(main())

+ 394 - 0
examples/mini_restore/workflow_loop.py

@@ -0,0 +1,394 @@
+import sys
+import os
+import asyncio
+import json
+import base64
+import re
+
+# 将项目根目录加入,方便导入内部包
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')))
+
+from agent.llm.qwen import qwen_llm_call
+from agent.tools.builtin.search import search_posts
+
+# -----------------
+# Tools definitions
+# -----------------
+async def call_banana_tool(prompt: str) -> str:
+    """包装 call_banana.py 工具的调用方法,抓取它保存本地的文件路径"""
+    print(f"\n[Tool] ✨ 正在调用 call_banana 生成图片, Prompt: {prompt[:50]}...")
+    script_path = os.path.join(os.path.dirname(__file__), "call_banana.py")
+    
+    # 设置环境变量走兼容模式,同时强制指定 UTF-8 编码避免 Windows 下输出由于表情符号崩溃
+    env = os.environ.copy()
+    env["PYTHONIOENCODING"] = "utf-8"
+    process = await asyncio.create_subprocess_exec(
+        sys.executable, script_path, "-p", prompt,
+        stdout=asyncio.subprocess.PIPE,
+        stderr=asyncio.subprocess.PIPE,
+        env=env
+    )
+    stdout, stderr = await process.communicate()
+    output = stdout.decode('utf-8', errors='replace')
+    err_output = stderr.decode('utf-8', errors='replace')
+    if err_output:
+        output += "\n" + err_output
+    
+    # 解析输出:"💾 已保存到本地 -> banana_output_0.jpeg"
+    match = re.search(r"已保存到本地 -> (.+)", output)
+    if match:
+        path = match.group(1).strip()
+        print(f"[Tool] ✅ call_banana 返回图片路径: {path}")
+        return path
+    else:
+        print(f"[Tool] ❌ call_banana 似乎未成功生成文件, 控制台输出:\n{output}")
+        return f"Tool Execution Failed. output:\n{output}"
+
+async def search_tool(keyword: str) -> str:
+    print(f"\n[Tool] 🔍 启动小红书调研, 关键词: {keyword}")
+    try:
+        result = await search_posts(keyword=keyword, channel="xhs", max_count=3)
+        return result.output
+    except Exception as e:
+        return f"查询失败: {e}"
+
+def get_agent_tools():
+    return [
+        {
+            "type": "function",
+            "function": {
+                "name": "search_tool",
+                "description": "如果需要了解某个风格如何写 Prompt(例如“写实风格提示词”),调用此工具进行小红书全网搜索,返回总结经验以更新你的参数。",
+                "parameters": {
+                    "type": "object",
+                    "properties": {
+                        "keyword": {
+                            "type": "string",
+                            "description": "搜索关键词"
+                        }
+                    },
+                    "required": ["keyword"]
+                }
+            }
+        },
+        {
+            "type": "function",
+            "function": {
+                "name": "call_banana_tool",
+                "description": "使用此工具通过给定的详细提示词生成图片。工具将返回生成图片的本地保存路径。",
+                "parameters": {
+                    "type": "object",
+                    "properties": {
+                        "prompt": {
+                            "type": "string",
+                            "description": "英语或中文详细的生图提示词"
+                        }
+                    },
+                    "required": ["prompt"]
+                }
+            }
+        }
+    ]
+
+# -----------------
+# Agent 2: Image Evaluator (Qwen-VL-Max)
+# -----------------
+async def evaluate_images(target_image_path: str, generated_image_path: str, previous_feedback: str = None) -> str:
+    print(f"\n[Agent 2] 👁️ Qwen-VL 开始视觉评估...")
+    print(f"         - 目标图: {target_image_path}")
+    print(f"         - 生成图: {generated_image_path}")
+    
+    def encode_image(image_path):
+        with open(image_path, "rb") as image_file:
+            return base64.b64encode(image_file.read()).decode('utf-8')
+            
+    try:
+        target_b64 = encode_image(target_image_path)
+        gen_b64 = encode_image(generated_image_path)
+        
+        target_ext = target_image_path.split('.')[-1].lower()
+        if target_ext == 'jpg': target_ext = 'jpeg'
+        gen_ext = generated_image_path.split('.')[-1].lower()
+        if gen_ext == 'jpg': gen_ext = 'jpeg'
+    except Exception as e:
+        return f"无法读取图片以进行评估: {e}"
+
+    system_content = "你是专业的AI生图评审师。你的工作是对比【目标参考图】和当前【生成图】,找出具体的差异,并给出针对性的修改意见给生图Prompt工程师。"
+    if previous_feedback:
+        system_content += "\n你还会收到你【上一轮的评估反馈】。请结合你的旧反馈,检查这轮新图片是否修正了你上次提出的问题,避免重复说一样的话,而是要有动态进展意识!"
+
+    text_prompt = "请做详细的差异点分析:从构图、色彩、人物或物体细节、整体质感等方面指出当前生成图与目标图的差距。"
+    if previous_feedback:
+        text_prompt += f"\n\n你对上一版旧图的评估反馈曾经是:\n{previous_feedback}\n\n请比对这张【新生成图】,告诉我:上一版的问题被解决了吗?画面的进步点和退步点在哪里?请给出更新的针对性修改意见!"
+    else:
+        text_prompt += "结束时,请给出具体的 Prompt 修改建议。"
+
+    messages = [
+        {
+            "role": "system",
+            "content": system_content
+        },
+        {
+            "role": "user",
+            "content": [
+                {"type": "text", "text": "【目标参考图(理想状态)】:"},
+                {"type": "image_url", "image_url": {"url": f"data:image/{target_ext};base64,{target_b64}"}},
+                {"type": "text", "text": "【本次生成的图片】:"},
+                {"type": "image_url", "image_url": {"url": f"data:image/{gen_ext};base64,{gen_b64}"}},
+                {"type": "text", "text": text_prompt}
+            ]
+        }
+    ]
+    
+    try:
+        response = await qwen_llm_call(
+            messages=messages,
+            model="qwen3.5-plus" 
+        )
+        analysis = response["content"]
+        print(f"\n[Agent 2] 📃 评估反馈:\n{analysis}\n")
+        return analysis
+    except Exception as e:
+        print(f"\n[Agent 2] ⚠️ 评估发生错误: {e}")
+        return f"VL模型调用失败: {e}"
+
+# -----------------
+# Main Workflow Loop
+# -----------------
+
+def get_base64_url(image_path: str) -> str:
+    with open(image_path, "rb") as image_file:
+        b64_data = base64.b64encode(image_file.read()).decode('utf-8')
+    ext = image_path.split('.')[-1].lower()
+    if ext == 'jpg': ext = 'jpeg'
+    return f"data:image/{ext};base64,{b64_data}"
+
+async def main():
+    import argparse
+    import os
+    
+    default_target = os.path.join(os.path.dirname(os.path.abspath(__file__)), "input", "img_1.png")
+    parser = argparse.ArgumentParser(description="多智能体画图自动优化 Workflow")
+    parser.add_argument("-t", "--target", default=default_target, help="你想逼近的目标参考图本地路径")
+    parser.add_argument("-m", "--max_loops", type=int, default=10, help="优化的最大迭代论调")
+    args = parser.parse_args()
+    
+    target_image = args.target
+
+    print("\n" + "="*50)
+    print("🤖 启动双 Agent 生图闭环工作流 (纯 Vision-Language 架构)")
+    print("="*50)
+    
+    if not os.path.exists(target_image):
+        print(f"⚠️ 找不到目标图片: {target_image}")
+        print("提示: 系统依然会运行寻找文件,但 Agent 2 将无法给出评估。可随便放一个图片来模拟。")
+    
+    system_msg = {
+        "role": "system",
+        "content": "你是一个超级提示词工程师(Prompt Engineer)。目标:生成一张无限接近【目标参考图】的图片。\n作为多模态大模型,每一轮我都会给你看你上次生成的图片结果和评估专家的犀利分析反馈。你需要利用这些反馈进行修改。\n流程要求:\n1. (可选)如果你对风格不确定,可以请求 search_tool 调研别人怎么写相关提示词。\n2. 使用 call_banana_tool 来实际提交你的提示词并生成图片。\n3. 调用生成工具后,你本轮的工作就结束了,系统会把成果拿去评估并在下一轮找你。"
+    }
+
+    max_loops = args.max_loops
+    current_generation_loop_count = 0
+    last_gen_info = None
+    prompt_history = [] # 记录完整的历史 Prompt 轨迹,防止反复抽卡
+
+    while current_generation_loop_count < max_loops:
+        print(f"\n" + "="*40)
+        print(f"🔄 优化循环: 第 {current_generation_loop_count + 1}/{max_loops} 轮")
+        print("="*40)
+        
+        # 每轮重置上下文,只保留 system message 和含有"上次结果"的 initial user message
+        messages = [system_msg]
+        
+        if last_gen_info is None:
+            try:
+                target_b64_url = get_base64_url(target_image)
+                messages.append({
+                    "role": "user",
+                    "content": [
+                        {"type": "text", "text": "这是你需要逼近的【目标参考图】。现在请你仔细观察它,并提炼出一份详尽的初步生图 Prompt。你可以酌情使用 search_tool 调研,最后必须使用 call_banana_tool 提交你的 Prompt 生成最初的原型。"},
+                        {"type": "image_url", "image_url": {"url": target_b64_url}}
+                    ]
+                })
+            except Exception as e:
+                messages.append({
+                    "role": "user",
+                    "content": f"目标图片凭据读取失败({e}),请盲猜一个初始 Prompt 并使用 call_banana_tool 生成。"
+                })
+        else:
+            try:
+                gen_image_url = get_base64_url(last_gen_info["image_path"])
+                
+                # 构建历史记录描述,让它知道自己之前走过哪些弯路避免抽卡
+                history_text = "【你的历史迭代轨迹 (包含往期Prompt与评估专家对其的批评,用于防复读和总结改进)】:\n"
+                for i, record in enumerate(prompt_history):
+                    history_text += f"==== 第 {i+1} 轮 ====\n"
+                    history_text += f"[使用的 Prompt]:\n{record['prompt']}\n"
+                    history_text += f"[收到的反馈批评]:\n{record['feedback']}\n\n"
+                
+                messages.append({
+                    "role": "user",
+                    "content": [
+                        {"type": "text", "text": f"{history_text}\n这可以帮你回顾你之前走过的路径。现在聚焦到上一轮:\n\n你上一轮({len(prompt_history)})使用的生图Prompt为:\n{last_gen_info['prompt']}\n\n这里是你上一轮生成的图片结果,请仔细查看对比:"},
+                        {"type": "image_url", "image_url": {"url": gen_image_url}},
+                        {"type": "text", "text": f"【视觉评估专家的分析反馈】:\n{last_gen_info['feedback']}\n\n请针对上述反馈,思考到底哪里不像,参考上述的历史轨迹避免重蹈覆辙,进行新的调研修正(如果需要),或者直接使用 call_banana_tool 生成优化后的版本。"}
+                    ]
+                })
+            except Exception as e:
+                messages.append({
+                    "role": "user",
+                    "content": f"上一轮信息读取失败 ({e})。请重新尝试凭感觉用 call_banana_tool 再次生成。"
+                })
+
+        # Agent 1 内部工具调研微循环 (Agent 1 minor logic loop)
+        agent1_finished_generation = False
+        consecutive_empty = 0
+        
+        while not agent1_finished_generation:
+            print(f"---\n💬 正在请求 Agent 1 (Prompt 师)...")
+            # 这里 Agent 1 也换成 qwen-vl-max,这样它才能看到传给它的上一轮图片
+            response = await qwen_llm_call(
+                messages=messages,
+                model="qwen3.5-plus",
+                tools=get_agent_tools()
+            )
+            
+            content = response.get("content", "")
+            tool_calls = response.get("tool_calls")
+            
+            if content:
+                print(f"\n[Agent 1 思考]:\n{content}")
+                
+            if not tool_calls and not content:
+                consecutive_empty += 1
+                if consecutive_empty >= 3:
+                    print("Agent 连续多次无有意义输出,强制跳出本轮。")
+                    break
+            else:
+                consecutive_empty = 0
+
+            # 保持上下文
+            assistant_reply = {"role": "assistant"}
+            if content: assistant_reply["content"] = content
+            if tool_calls: assistant_reply["tool_calls"] = tool_calls
+            messages.append(assistant_reply)
+
+            if tool_calls:
+                for tc in tool_calls:
+                    func_name = tc["function"]["name"]
+                    args_dict = json.loads(tc["function"]["arguments"])
+                    tc_id = tc["id"]
+                    
+                    if func_name == "search_tool":
+                        res = await search_tool(**args_dict)
+                        messages.append({
+                            "role": "tool",
+                            "tool_call_id": tc_id,
+                            "content": str(res)
+                        })
+                    
+                    elif func_name == "call_banana_tool":
+                        print(f"\n⚙️ Agent 1 决定提交生图请求!")
+                        gen_path = await call_banana_tool(**args_dict)
+                        
+                        # ⚠️ 把生成的图片按轮次重命名防覆盖,保存中间过程
+                        if os.path.exists(gen_path):
+                            ext = gen_path.split('.')[-1]
+                            new_gen_path = f"gen_loop_{current_generation_loop_count + 1}.{ext}"
+                            import shutil
+                            shutil.move(gen_path, new_gen_path)
+                            gen_path = new_gen_path
+                            print(f"[文件管理] 中间图片已重命名并保存为: {new_gen_path}")
+                        
+                        prompt_used = args_dict.get("prompt", "")
+                        
+                        # 把消息补齐,虽然这一轮马上就要重置销毁了
+                        messages.append({
+                            "role": "tool",
+                            "tool_call_id": tc_id,
+                            "content": f"已生成,路径: {gen_path}"
+                        })
+                        
+                        agent1_finished_generation = True
+                        current_generation_loop_count += 1
+                        
+                        # 进行评估并记录,传递给下一大轮
+                        if os.path.exists(gen_path) and os.path.exists(target_image):
+                            prev_feedback = last_gen_info["feedback"] if last_gen_info else None
+                            evaluation_feedback = await evaluate_images(target_image, gen_path, prev_feedback)
+                            last_gen_info = {
+                                "prompt": prompt_used,
+                                "image_path": gen_path,
+                                "feedback": evaluation_feedback
+                            }
+                        else:
+                            last_gen_info = {
+                                "prompt": prompt_used,
+                                "image_path": gen_path,
+                                "feedback": f"系统提示:由于目标图 {target_image} 或生成图 {gen_path} 不存在,评估被跳过。"
+                            }
+                        
+                        # 记录到全局大历史中,供它长线参考防重踩坑
+                        prompt_history.append(last_gen_info)
+                        break # 跳出 tool_calls for loop
+            else:
+                # 没调工具
+                print("\n[控制中心] Agent 1 没有继续使用任何工具。结束其周期。")
+                agent1_finished_generation = True
+                break
+                
+    print("\n🎉 工作流闭环成功完成或达到了最大迭代次数。")
+    
+    # 最后由评估专家出具一份最完善的多维度最终报告
+    if len(prompt_history) > 0 and os.path.exists(target_image):
+        print("\n" + "="*50)
+        print("🏆 正在生成【专家最终多维度反馈报告】...")
+        print("="*50)
+        
+        first_gen = prompt_history[0]["image_path"]
+        last_gen = prompt_history[-1]["image_path"]
+        
+        if os.path.exists(first_gen) and os.path.exists(last_gen):
+            try:
+                target_b64 = encode_image(target_image)
+                first_b64 = encode_image(first_gen)
+                last_b64 = encode_image(last_gen)
+                target_ext = target_image.split('.')[-1].lower()
+                first_ext = first_gen.split('.')[-1].lower()
+                last_ext = last_gen.split('.')[-1].lower()
+                
+                # 构建供最终分析的文字轨迹
+                full_history_text = "【历次 Prompt 与专家反馈的演进轨迹】\n"
+                for i, record in enumerate(prompt_history):
+                    full_history_text += f"-- 第 {i+1} 轮 --\n[Prompt]: {record['prompt']}\n[反馈]: {record['feedback']}\n\n"
+
+                final_messages = [
+                    {
+                        "role": "system",
+                        "content": "你是首席AI打样架构师。目前的生图迭代优化工作流已拉下帷幕。你不需要拘泥于打分,而是要通过回顾整个演进历程,总结出‘最好用的 Prompt 模板’和‘最精准的评估反馈维度模板’。"
+                    },
+                    {
+                        "role": "user",
+                        "content": [
+                            {"type": "text", "text": "【目标参考图(原图)】:"},
+                            {"type": "image_url", "image_url": {"url": f"data:image/{target_ext if target_ext != 'jpg' else 'jpeg'};base64,{target_b64}"}},
+                            {"type": "text", "text": "这是最初第1轮盲试的生成图:"},
+                            {"type": "image_url", "image_url": {"url": f"data:image/{first_ext if first_ext != 'jpg' else 'jpeg'};base64,{first_b64}"}},
+                            {"type": "text", "text": f"这是经过迭代后的【最终生成图】:"},
+                            {"type": "image_url", "image_url": {"url": f"data:image/{last_ext if last_ext != 'jpg' else 'jpeg'};base64,{last_b64}"}},
+                            {"type": "text", "text": f"下面是 {len(prompt_history)} 轮迭代中,Prompt 和专家反馈的完整变迁记录:\n\n{full_history_text}\n\n请结合首尾图片的巨大差异以及中间的踩坑过程,深度复盘:\n1. 在构建生图 Prompt 时,哪些描述方式、句型或结构最能有效命中模型?请提炼出一个【最终版高转化率 Prompt 语法模板】。\n2. 在进行视觉反馈时,哪些维度的批评和建议对 Prompt 师是最具指导意义的?请提炼出一个【最终版高维度视觉评估反馈模板】。\n这两个模版需要具备极强的通用性和实战复用价值!"}
+                        ]
+                    }
+                ]
+                
+                response = await qwen_llm_call(
+                    messages=final_messages,
+                    model="qwen3.5-plus"
+                )
+                print(f"\n[Agent 2] 📋 【最终多维度评估报告】:\n{response['content']}\n")
+            except Exception as e:
+                print(f"最终报告生成失败: {e}")
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 79 - 0
examples/production_restore/config.py

@@ -0,0 +1,79 @@
+"""
+项目配置
+
+定义项目的运行配置。
+"""
+
+from agent.core.runner import KnowledgeConfig, RunConfig
+
+
+# ===== Agent 运行配置 =====
+
+RUN_CONFIG = RunConfig(
+    # 模型配置
+    model="qwen3.5-plus",
+    temperature=0.3,
+    max_iterations=1000,
+
+    # 启用 thinking 模式
+    extra_llm_params={"extra_body": {"enable_thinking": True}},
+
+    # Agent 预设(对应 presets.json 中的 "main")
+    agent_type="main",
+
+    # 任务名称
+    name="图像还原执行",
+
+    # 知识管理配置
+    knowledge=KnowledgeConfig(
+        # 压缩时提取
+        enable_extraction=False,
+        reflect_prompt="",
+
+        # agent 运行完成后提取
+        enable_completion_extraction=True,
+        completion_reflect_prompt="",
+
+        # 知识注入
+        enable_injection=False,
+
+        # 默认字段
+        owner="sunlit.howard@gmail.com",
+        default_tags={"project": "production_restore", "domain": "ai_image"},
+        default_scopes=["org:cybertogether"],
+        default_search_types=["strategy"],
+        default_search_owner="sunlit.howard@gmail.com"
+    )
+)
+
+
+# ===== 任务配置 =====
+
+INPUT_DIR = "examples/production_restore/input"       # 输入目录(pipeline.json、analysis.json、research.json)
+OUTPUT_DIR = "examples/production_restore/output_feature"     # 输出目录
+
+
+# ===== 基础设施配置 =====
+
+SKILLS_DIR = "./skills"
+TRACE_STORE_PATH = ".trace"
+DEBUG = True
+LOG_LEVEL = "INFO"
+LOG_FILE = None  # 设置为文件路径可以同时输出到文件
+
+# ===== 浏览器配置 =====
+# 可选值: "cloud" (云浏览器) 或 "local" (本地浏览器) 或 "container" (容器浏览器,支持预配置账户)
+BROWSER_TYPE = "local"
+HEADLESS = False
+
+# ===== IM 配置 =====
+IM_ENABLED = False                          # 是否启动 IM Client
+IM_CONTACT_ID = "agent_restore"             # Agent 在 IM 系统中的身份 ID
+IM_SERVER_URL = "ws://43.106.118.91:8105"   # IM Server WebSocket 地址
+IM_WINDOW_MODE = True                       # 窗口模式(True=每次运行消息隔离,推荐)
+IM_NOTIFY_INTERVAL = 10.0                   # 新消息检查间隔(秒)
+
+# ===== Knowledge Manager 配置 =====
+KNOWLEDGE_MANAGER_ENABLED = False           # 是否启动 Knowledge Manager(作为后台 IM Client)
+KNOWLEDGE_MANAGER_CONTACT_ID = "knowledge_manager"  # Knowledge Manager 的 IM 身份 ID
+KNOWLEDGE_MANAGER_ENABLE_DB_COMMIT = False  # 是否允许入库(False=只缓存,True=可入库)

+ 300 - 0
examples/production_restore/evaluate_tool.py

@@ -0,0 +1,300 @@
+"""
+图像质量评估工具
+
+输入:需求文档路径 + 图片路径(单图或多图)+ 质量标准(可选)
+输出:评分 + 详细反馈
+
+通过多模态 VL 大模型对生成图像进行质量评估:
+- 单图模式:对照需求文档检查是否满足要求
+- 多图模式:检查跨图一致性(角色、服装、色调等)
+"""
+
+import json
+from pathlib import Path
+from typing import Dict, Any, Optional, List, Union
+
+from agent.tools import tool, ToolResult
+from agent.llm import create_qwen_llm_call
+
+
+@tool(
+    display={
+        "zh": {"name": "图像质量评估", "params": {
+            "requirement_path": "需求文档路径",
+            "image_paths": "图片路径(单个字符串或列表)",
+            "quality_criteria": "质量标准(可选)"
+        }},
+        "en": {"name": "Image Quality Evaluation", "params": {
+            "requirement_path": "Requirement document path",
+            "image_paths": "Image path(s) (string or list)",
+            "quality_criteria": "Quality criteria (optional)"
+        }},
+    }
+)
+async def evaluate_image(
+    requirement_path: str,
+    image_paths: Union[str, List[str]],
+    quality_criteria: Optional[str] = None
+) -> ToolResult:
+    """评估生成图像是否满足需求文档的要求
+
+    使用多模态 VL 大模型对生成图像进行质量评估:
+
+    **单图模式**(传入单个路径字符串):
+    - 姿态、服装、光影、背景等是否符合规格
+    - 材质、细节的真实感
+    - 整体构图和色调
+
+    **多图模式**(传入路径列表):
+    - 检查跨图一致性:角色外观、服装款式、色调风格是否统一
+    - 识别不一致的图片并给出修复建议
+
+    Args:
+        requirement_path: 需求文档路径(JSON 或文本文件)
+        image_paths: 待评估的图片路径(单个字符串或路径列表)
+        quality_criteria: 额外的质量标准描述(可选)
+
+    Returns:
+        ToolResult 包含评分(0-10)和详细反馈
+    """
+    # 统一处理为列表
+    if isinstance(image_paths, str):
+        paths_list = [image_paths]
+        is_multi_image = False
+    else:
+        paths_list = image_paths
+        is_multi_image = len(paths_list) > 1
+    # 1. 读取需求文档
+    req_path = Path(requirement_path)
+    if not req_path.exists():
+        return ToolResult(
+            title="评估失败",
+            output="",
+            error=f"需求文档不存在: {requirement_path}",
+        )
+
+    requirement_text = req_path.read_text(encoding="utf-8")
+
+    # 如果是 JSON,尝试智能提取内容
+    requirement_summary = requirement_text
+    if requirement_path.endswith(".json"):
+        try:
+            req_data = json.loads(requirement_text)
+            parts = []
+            # 单图需求
+            if "required_spec" in req_data:
+                parts.append("## 单图需求\n" + json.dumps(req_data["required_spec"], ensure_ascii=False, indent=2))
+            if "prompt" in req_data:
+                parts.append(f"Prompt: {req_data['prompt']}")
+            # 多图一致性需求
+            if "consistency_checks" in req_data:
+                parts.append("## 一致性检查标准\n" + json.dumps(req_data["consistency_checks"], ensure_ascii=False, indent=2))
+            # 多图各自的需求(pipeline.json 整体)
+            if "images" in req_data:
+                img_specs = {}
+                for img_id, img_data in req_data["images"].items():
+                    if "required_spec" in img_data:
+                        img_specs[img_id] = img_data["required_spec"]
+                if img_specs:
+                    parts.append("## 各图需求规格\n" + json.dumps(img_specs, ensure_ascii=False, indent=2))
+            if parts:
+                requirement_summary = "\n\n".join(parts)
+        except:
+            pass
+
+    # 2. 检查所有图片文件
+    import base64
+    image_contents = []
+    missing_files = []
+    for p_str in paths_list:
+        p = Path(p_str)
+        if not p.exists():
+            missing_files.append(p_str)
+            continue
+        img_bytes = p.read_bytes()
+        img_b64 = base64.b64encode(img_bytes).decode("utf-8")
+        mime_type = "image/png"
+        if p.suffix.lower() in (".jpg", ".jpeg"):
+            mime_type = "image/jpeg"
+        elif p.suffix.lower() == ".webp":
+            mime_type = "image/webp"
+        image_contents.append({
+            "path": p_str,
+            "b64": img_b64,
+            "mime": mime_type,
+        })
+
+    if missing_files:
+        return ToolResult(
+            title="评估失败",
+            output="",
+            error=f"以下图片文件不存在: {', '.join(missing_files)}",
+        )
+    if not image_contents:
+        return ToolResult(
+            title="评估失败",
+            output="",
+            error="没有可评估的图片",
+        )
+
+    # 3. 构建评估 prompt(根据模式不同)
+    if is_multi_image:
+        image_labels = "\n".join([f"- 图片 {i+1}: {ic['path']}" for i, ic in enumerate(image_contents)])
+        eval_prompt = f"""你是一个专业的图像质量评估专家。请对以下 {len(image_contents)} 张生成图像进行评估,重点检查**跨图一致性**。
+
+## 需求文档
+{requirement_summary}
+
+## 图片列表
+{image_labels}
+
+## 质量标准
+{quality_criteria if quality_criteria else "按照需求文档中的一致性检查标准进行评估"}
+
+## 评估维度
+
+### A. 跨图一致性(每项 0-10 分)
+1. **角色一致性**:所有图中的人物面部特征、发型、肤色是否保持一致
+2. **服装一致性**:白色长裙的款式、材质、颜色是否 100% 统一
+3. **色调一致性**:白绿配色方案、色彩饱和度是否贯穿所有图像
+4. **光影一致性**:逆光/轮廓光方向、光晕效果是否统一
+5. **风格一致性**:摄影风格、镜头参数感(85mm、f/1.8 景深)是否统一
+
+### B. 单图质量(每张图 0-10 分)
+对每张图分别给出质量评分。
+
+## 输出格式
+请严格按照以下 JSON 格式输出:
+
+```json
+{{
+  "overall_score": <0-10 的总分>,
+  "consistency_scores": {{
+    "character": <0-10>,
+    "clothing": <0-10>,
+    "color_scheme": <0-10>,
+    "lighting": <0-10>,
+    "style": <0-10>
+  }},
+  "per_image_scores": {{
+    "图片1": <0-10>,
+    "图片2": <0-10>
+  }},
+  "inconsistent_images": ["<列出不一致的图片编号及问题>"],
+  "feedback": "<详细的文字反馈,指出一致性的优点和不足>",
+  "suggestions": "<改进建议,哪些图需要重新生成、怎么调整>"
+}}
+```
+
+请仔细对比所有图像,给出客观、专业的评估。"""
+
+    else:
+        eval_prompt = f"""你是一个专业的图像质量评估专家。请根据以下需求文档,对生成的图像进行详细评估。
+
+## 需求文档
+{requirement_summary}
+
+## 质量标准
+{quality_criteria if quality_criteria else "按照需求文档中的 required_spec 和 prompt 描述进行评估"}
+
+## 评估维度
+请从以下维度评估图像质量(每项 0-10 分):
+
+1. **姿态准确性**:人物姿态是否符合需求描述
+2. **服装还原度**:服装款式、材质、细节是否符合要求
+3. **光影效果**:光线方向、强度、轮廓光等是否符合描述
+4. **背景一致性**:背景元素、虚化效果是否符合要求
+5. **材质真实感**:服装、道具的材质是否真实自然
+6. **整体构图**:构图、色调、氛围是否符合预期
+
+## 输出格式
+请严格按照以下 JSON 格式输出评估结果:
+
+```json
+{{
+  "overall_score": <0-10 的总分>,
+  "dimension_scores": {{
+    "pose": <0-10>,
+    "clothing": <0-10>,
+    "lighting": <0-10>,
+    "background": <0-10>,
+    "material": <0-10>,
+    "composition": <0-10>
+  }},
+  "feedback": "<详细的文字反馈,指出优点和不足>",
+  "suggestions": "<改进建议,如需调整哪些参数或换用哪些工具>"
+}}
+```
+
+请仔细观察图像,给出客观、专业的评估。"""
+
+    # 4. 构建多模态消息
+    content_parts = [{"type": "text", "text": eval_prompt}]
+    for ic in image_contents:
+        content_parts.append({
+            "type": "image_url",
+            "image_url": {
+                "url": f"data:{ic['mime']};base64,{ic['b64']}"
+            }
+        })
+
+    messages = [{"role": "user", "content": content_parts}]
+
+    # 5. 调用 VL 模型
+    try:
+        llm_call = create_qwen_llm_call(model="qwen-vl-max")
+        response = await llm_call(messages, model="qwen-vl-max", temperature=0.3)
+
+        # 6. 解析评估结果
+        response_text = response["content"].strip()
+
+        # 提取 JSON
+        if "```json" in response_text:
+            json_start = response_text.find("```json") + 7
+            json_end = response_text.find("```", json_start)
+            json_str = response_text[json_start:json_end].strip()
+        elif "```" in response_text:
+            json_start = response_text.find("```") + 3
+            json_end = response_text.find("```", json_start)
+            json_str = response_text[json_start:json_end].strip()
+        else:
+            json_str = response_text
+
+        eval_result = json.loads(json_str)
+
+        # 7. 格式化输出
+        output = {
+            "mode": "multi_image_consistency" if is_multi_image else "single_image",
+            "requirement_path": requirement_path,
+            "image_paths": paths_list,
+            "evaluation": eval_result,
+        }
+
+        overall_score = eval_result.get("overall_score", 0)
+        image_count = len(paths_list)
+
+        if is_multi_image:
+            title = f"多图一致性评估完成({image_count} 张,总分: {overall_score}/10)"
+            memory = f"Consistency evaluation of {image_count} images: score={overall_score}/10"
+        else:
+            title = f"图像评估完成(总分: {overall_score}/10)"
+            memory = f"Evaluated {paths_list[0]}: score={overall_score}/10"
+
+        return ToolResult(
+            title=title,
+            output=json.dumps(output, ensure_ascii=False, indent=2),
+            long_term_memory=memory,
+        )
+
+    except json.JSONDecodeError as e:
+        return ToolResult(
+            title="评估完成(JSON 解析失败,返回原始文本)",
+            output=f"LLM 返回内容:\n{response_text}",
+            error=f"无法解析 LLM 返回的 JSON: {e}",
+        )
+    except Exception as e:
+        return ToolResult(
+            title="评估失败",
+            output="",
+            error=f"评估过程出错: {e}",
+        )

+ 141 - 0
examples/production_restore/execution.prompt

@@ -0,0 +1,141 @@
+---
+model: qwen3.5-plus
+temperature: 0.3
+---
+
+$system$
+## 角色
+你是一个 AI 图像生成执行专家,负责根据指令通过 ToolHub 工具库调用图像生成工具,**严格按照规格执行生成任务并如实汇报结果**。
+
+**你的边界**:只负责执行生成和验证,不负责修改还原策略。如遇到无法解决的技术阻塞,立即上报而不是自行改变方案。
+
+## 执行流程
+
+### 第一步:理解任务规格
+仔细阅读任务描述,提取:
+- 目标图(img_X)
+- 参考图路径(人物、背景、道具)
+- Prompt 文本
+- 图生图配置(img2img_config)
+
+**参考源信息(raw_info)**:
+在输入目录中有一个 `raw_info` 文件,包含原始图片的源信息(如图片描述、风格标签、技术参数、色彩分析等)。在构建或优化 prompt 时,**应先读取 raw_info 作为参考**,从中提取有价值的细节描述融入 prompt,以更精准地还原目标效果。raw_info 是辅助参考,最终 prompt 仍以任务指派中的要求为准。
+
+### 第二步:验证素材可用性
+在执行生成前,先验证所需文件存在且可读:
+- 使用 `read_file` 工具检查各参考素材文件(**`read_file` 支持读取图片,会自动转为 base64 供你查看,无需打开浏览器**)
+- 读取 `raw_info` 获取图片源信息,提取可用于 prompt 的细节
+- 如有素材缺失,立即在结果中标注(不要自行跳过)
+
+### 第三步:通过 ToolHub 工具库执行生成
+在执行任何生成操作之前,**必须先通过 ToolHub 查找并使用合适的工具**。
+
+1. 调用 `toolhub_health` 检查服务是否可用
+2. 若可用,调用 `toolhub_search` 查看工具列表,根据当前任务需求选择合适的工具
+3. 仔细阅读目标工具的参数说明,按要求调用 `toolhub_call` 执行任务
+4. 注意工具库中部分工具有**生命周期依赖**(如启动→执行→停止),需按分组顺序依次调用
+
+**工具选择优先级**:
+- **首选 `nano_banana`**:无论文生图还是图生图,优先使用 `nano_banana`,它速度快、效果好
+- 若 `nano_banana` 不可用或效果不满足 → 使用 `flux_generate`(传 image_url 参数图生图)或 `seedream_generate`(传 image_url 参数图生图)
+- 仅当以上工具效果均不足、需要精细姿态/边缘/深度控制时 → 再考虑使用 `liblibai_controlnet`
+
+**图片传递方式(极其重要,必须严格遵守)**:
+
+⚠️ **只允许传本地文件路径,严禁自行构造任何 HTTP/HTTPS 链接** ⚠️
+
+toolhub.py 内置了 `_preprocess_params` 函数,会自动将本地路径上传到 OSS 并替换为 CDN URL。你只需要传本地路径,系统会自动处理。
+
+✅ **正确做法**:
+```json
+{
+  "image_url": "examples/production_restore/features/character_asset/character_ref_back.png"
+}
+```
+
+❌ **以下行为全部禁止**:
+- 禁止自行拼接 `https://raw.githubusercontent.com/...` 等 GitHub 链接
+- 禁止自行拼接 `https://res.cybertogether.net/...` 等 CDN 链接
+- 禁止自行构造任何 `http://` 或 `https://` 开头的图片 URL
+- 禁止将图片读取后转为 base64 编码传入
+- 禁止调用 upload 相关函数手动上传
+
+支持自动上传的参数名:`image`、`image_url`、`mask_image`、`pose_image`、`images`(数组)
+
+- 如果你需要单独获取某张本地图片的 CDN URL,可以调用 `image_uploader` 工具,传入本地路径即可返回永久 CDN 链接
+- `toolhub_call` 返回的图片会自动上传到 CDN,结果中的 `cdn_urls` 为永久链接
+- 串联工作流时,可使用上一步返回的 `cdn_urls`,或继续使用本地路径
+- **不要**使用 `original_urls_expired` 中的链接,它们是临时签名链接,可能已过期
+
+**工具调用失败时的处理原则**:
+- 若某个工具调用失败(报错、超时、404 等),**不要自行实现替代方案**
+- 立即用 `toolhub_search` 在工具库中查找其他可用工具继续尝试
+- 若工具库中所有相关工具均已尝试且失败,在阻塞报告中列出每个工具的失败原因,等待上级指示
+
+### 第四步:验证生成结果
+使用 `evaluate_image` 工具对生成图像进行自动化评估:
+
+**单图评估**:传入需求文档路径和单张图片路径
+```json
+{"requirement_path": "examples/production_restore/input/pipeline.json", "image_paths": "输出图片路径"}
+```
+
+**多图一致性评估**:传入需求文档路径和多张图片路径列表,自动检查跨图一致性
+```json
+{"requirement_path": "examples/production_restore/input/pipeline.json", "image_paths": ["img_1路径", "img_2路径", "img_3路径"]}
+```
+
+- 单图模式:从姿态、服装、光影、背景、材质、构图 6 个维度打分(0-10)
+- 多图模式:从角色、服装、色调、光影、风格 5 个一致性维度打分,并逐图给分,指出不一致的图片
+- 根据评估结果判断是否需要重新生成或调整参数
+
+同时人工对照 output_spec 逐项检查:
+- 姿态是否符合需求描述(手臂位置、站/跪姿态)
+- 景深/散景效果是否达到要求
+- 人物参考是否注入(发型、服装款式)
+- 道具(画架、调色板)是否完整
+- 如有手部,手指数量是否正确
+
+### 第五步:输出结果报告
+
+**输出格式**:
+
+```
+## 任务:[img_X 还原]
+
+### 素材验证
+- 人物参考: [文件路径] — [存在/缺失]
+- 背景参考: [文件路径] — [存在/缺失]
+- 前序结果(chain_from): [文件路径] — [存在/缺失/不适用]
+
+### 生成参数
+- 使用工具: [toolhub_call tool_id=XX]
+- Prompt: [完整 prompt]
+- Negative Prompt: [完整 negative prompt]
+- 关键参数: [控制权重、参考图、分辨率、steps、CFG 等,按实际工具填写]
+- 分辨率: [宽]x[高]
+
+### 生成结果
+- 输出路径: [路径]
+- 生成耗时: [X]s
+
+### 验证结果
+- 姿态符合度: [通过/不通过] — [说明]
+- 景深效果: [通过/不通过] — [说明]
+- 人物一致性: [通过/不通过] — [说明]
+- 道具完整性: [通过/不通过] — [说明]
+- 手部细节: [通过/不通过] — [说明]
+- 整体评估: [通过/需调整]
+
+### 阻塞/风险(如有)
+- [说明遇到的问题]
+```
+
+## 注意事项
+- **查看图片用 `read_file`,不要用浏览器**:`read_file` 工具支持读取本地图片文件(PNG/JPG/WebP),会自动转为 base64 供你直接查看。**严禁使用浏览器工具来查看、预览或打开图片**
+- **ToolHub 优先**:每次任务开始必须先检查 ToolHub,有合适工具就用工具库,不要直接跳过去用其他方式
+- **nano_banana 优先**:优先使用 `nano_banana` 工具进行生成。若 `nano_banana` 不可用或效果不满足,再依次尝试 `flux_generate`、`seedream_generate` 等端到端工具。只有当所有端到端工具效果不足、需要精细的姿态/边缘/深度控制时,才考虑使用 `liblibai_controlnet`
+- **如实汇报**:不要美化结果,生成效果不好要明确说明
+- **优先使用已有素材**:严格使用任务指定的素材文件,不要自行替换
+- **记录完整参数**:每次生成的所有参数都要记录,便于主 agent 调整
+- **遇到阻塞立即上报**:如 ToolHub 不可用、工具调用失败、文件缺失等,立即说明,不要自行绕过

+ 77 - 0
examples/production_restore/generation-agent-architecture.md

@@ -0,0 +1,77 @@
+# 生成任务 Agent 架构
+
+## 设计原则
+
+按 context 内聚划分 agent:把解决特定子任务必须的信息交给一个 agent,每个 agent 的 context 保持小且聚焦。
+
+## 角色
+
+### Business Agent(决策循环)
+
+薄编排器。驱动 goal → evaluate → decide → dispatch 循环。
+
+- context:目标需求 + 当前结果 + 当前评估(不累积历史)
+- 做:提出目标、评估结果(通过 evaluate 工具)、决定迭代或接受、派发任务
+- 调度:Librarian(问策)、Craftsman(执行)、Researcher(调研)
+
+### Librarian(内部知识顾问)
+
+基于 KnowHub 已有知识给出方案建议。被动响应,不主动执行。
+
+- context:KnowHub 知识(需求、能力、工具、历史经验)
+- 做:接收当前状态 + 评估反馈 → 建议下一步行动
+- 不做:不执行工具、不做外部调研、不输出固定步骤序列
+- 回写:方案选择经验("这类需求用 X 方案效果最好")
+
+### Researcher(外部知识获取)
+
+当已有知识不足时,去外部获取新信息。
+
+- context:待调研的具体问题 + 外部信息源(网页、目录、试跑)
+- 做:搜索、对比、试验 → 返回调研结果 → 存入 KnowHub
+- 触发:Business Agent 根据 Librarian 的"知识不足"反馈决定派发
+
+### Craftsman(单步执行)
+
+接收一个具体的执行任务(非整个方案),完成并返回结果。
+
+- context:当前步骤的需求 + 候选工具详情(由 Librarian 提供)
+- 做:选具体工具/模型、配参、调用工具库执行、返回结果
+- 回写:参数和工具组合经验("这组参数出图最稳")
+- provider 选择在此层处理,不暴露给业务
+
+### evaluate 工具(质量评估)
+
+不是 agent,是 Business Agent 调用的工具。隔离 context 保证评估干净。
+
+- 输入:原始需求 + 输出结果 + 质量标准
+- 输出:评分 + 反馈(如"姿态准确但风格偏写实")
+- 内部可调用多模态 LLM 看图评估
+
+## 协作流程
+
+```
+Business: "动漫风姿态控制图"
+  → Librarian: "推荐 ControlNet + 动漫模型,但缺模型对比数据"
+  → Business 决定调研
+  → Researcher: 对比动漫模型 → 结果存入 KnowHub
+  → Librarian(有知识了): "推荐 AnimagineXL,先提取姿态再合成"
+  → Craftsman(提取姿态) → 姿态图
+  → Craftsman(合成生图) → 结果图
+  → evaluate(需求, 结果图) → "7/10,风格偏写实"
+  → Librarian: "换 Counterfeit 模型或加风格 LoRA"
+  → Craftsman(重新合成) → 新结果
+  → evaluate → "9/10,符合要求"
+  → Business: 接受
+```
+
+## 知识回流
+
+每个角色从自己的视角向 KnowHub 贡献知识:
+
+| 角色 | 回写的知识类型 |
+|------|--------------|
+| Librarian | 方案策略经验(哪种方案适合哪类需求) |
+| Craftsman | 工具参数经验(什么配置效果最好) |
+| Researcher | 调研发现(工具对比、新工具信息) |
+| evaluate | 质量评估记录(方案 A vs B 的效果对比) |

+ 100 - 0
examples/production_restore/input/analysis.json

@@ -0,0 +1,100 @@
+{
+  "category": {
+    "name": "户外白裙写生少女",
+    "traits": [
+      "穿着白色连衣裙的女性在户外草地进行油画写生",
+      "白绿配色为主,清新自然的森系视觉基调",
+      "包含画架、画布、调色板、画笔等写生道具",
+      "逆光/轮廓光与浅景深虚化营造梦幻氛围",
+      "画布内容与现实场景形成画中画呼应结构"
+    ],
+    "quality_challenges": [
+      "人物姿态自然度:需要呈现专注绘画的动态姿态,避免僵硬",
+      "白裙质感表现:轻薄面料的垂坠感、褶皱细节和透光效果",
+      "调色板颜料质感:厚重油画的 Impasto 堆叠感和色彩混合细节",
+      "光影层次:逆光下的轮廓光晕与主体细节的平衡",
+      "景深控制:前景清晰、背景虚化的自然过渡",
+      "画中画一致性:画布上的油画内容需与现实场景形成视觉呼应",
+      "发丝细节:逆光下头发边缘的光晕效果和发丝清晰度"
+    ],
+    "reasoning": "基于图片亮点.md 的 6 个聚类主题(优雅白裙少女、斑斓颜料、写生道具、白绿配色、光影景深、画中画结构)和制作点.md 的 6 大元素权重(女性 84.5、绘画工具 78.5、自然背景 74.0、画架画布 67.67、画架油画 60.97、女性衣物 44.7),以及 5 张制作表中对姿态、服装、光影、清晰度的详细规格描述综合判断"
+  },
+  "highlight": [
+    {
+      "name": "优雅的白裙写生少女",
+      "description": "女性主体需呈现文艺气质与娴静美,包括专注的绘画姿态(身体前倾、手臂抬起握笔)、自然垂坠的白色长裙(V 字露背或圆领、腰部系带收腰、裙摆飘逸)、棕色长发(自然披散、逆光下边缘光晕),整体塑造写生缪斯形象",
+      "reasoning": "图片亮点.md 中该聚类覆盖全部 5 张图片,制作点.md 中'女性'元素综合权重 84.5 为最高,5 张制作表中人物段落评分均在 0.745-0.858 之间,是画面核心主体",
+      "references": ["descriptions/图片亮点.md", "descriptions/制作点.md", "descriptions/写生油画__img_1_制作表.json#段落 1.1", "descriptions/写生油画__img_2_制作表.json#段落 2.1", "descriptions/写生油画__img_3_制作表.json#段落 3.1", "descriptions/写生油画__img_4_制作表.json#段落 4.1", "descriptions/写生油画__img_5_制作表.json#段落 5.1"]
+    },
+    {
+      "name": "斑斓厚重的油画颜料",
+      "description": "木质调色板上堆积的厚重油画颜料(Impasto),呈不规则块状分布,色彩鲜艳杂乱(绿色、蓝色、红色、黄色、白色、紫色等至少 10 种颜色),颜料具有膏状质感和明显堆叠厚度,与周围大面积纯白衣物形成强烈视觉反差",
+      "reasoning": "图片亮点.md 专门聚焦该实质物体,制作点.md 中'绘画工具'权重 78.5 排第二,img_1 和 img_5 制作表中调色板颜料细节描述详尽(颜色种类、分布模式、质地),img_5 中颜料段落评分达 0.754",
+      "references": ["descriptions/图片亮点.md", "descriptions/制作点.md", "descriptions/写生油画__img_1_制作表.json#段落 1.1.2.3", "descriptions/写生油画__img_5_制作表.json#段落 5.1.3.1"]
+    },
+    {
+      "name": "清新雅致的白绿配色",
+      "description": "大面积高饱和度自然草木绿背景(鲜绿色草地、深浅不一的绿色树木)与人物纯白服装形成鲜明对比,确立清新、自然、治愈的森系视觉基调,色彩饱和度中等偏高但不刺眼",
+      "reasoning": "图片亮点.md 中该形式类聚类强调色彩构成的形式美感,覆盖 img_1 和 img_4,5 张制作表的背景段落均描述'以绿色为主',色彩形式评分普遍较高",
+      "references": ["descriptions/图片亮点.md", "descriptions/写生油画__img_1_制作表.json#段落 1.3", "descriptions/写生油画__img_4_制作表.json#段落 4.3"]
+    },
+    {
+      "name": "唯美梦幻的光影与景深",
+      "description": "温暖的逆光/轮廓光从画面左上方或右上方照射,人物和物体边缘形成明显光晕;大光圈浅景深虚化(Bokeh)使背景呈现柔和散景,前景主体清晰锐利(头发丝、服装纹理、手部细节可见),营造脱离现实的梦幻浪漫氛围",
+      "reasoning": "图片亮点.md 中该形式类聚类覆盖 img_2、img_3、img_5,5 张制作表的形式段落均描述'逆光'或'自然光从左上方/右上方照射',清晰度描述均为'前景清晰、背景虚化',img_2 特别强调'强烈光斑和光晕效果'",
+      "references": ["descriptions/图片亮点.md", "descriptions/写生油画__img_2_制作表.json#段落 2 形式", "descriptions/写生油画__img_3_制作表.json#段落 3 形式", "descriptions/写生油画__img_5_制作表.json#段落 5 形式"]
+    },
+    {
+      "name": "虚实呼应的画中画结构",
+      "description": "画架上的画布内容与现实场景形成'镜像'或'互文'关系——画布上描绘身穿白色裙子的女性在户外草地/花丛中的背影,与现实中的画家形成视觉呼应,构建增加叙事深度的画中画结构,画布采用印象派风格(笔触粗犷、色彩鲜明)",
+      "reasoning": "图片亮点.md 中该形式类聚类覆盖 img_1 和 img_2,img_1、img_2、img_3 制作表中画布段落均详细描述'描绘了一名背对镜头的女性',内容主题与现实场景呼应,画布段落评分高达 0.891-0.958",
+      "references": ["descriptions/图片亮点.md", "descriptions/写生油画__img_1_制作表.json#段落 1.2.1", "descriptions/写生油画__img_2_制作表.json#段落 2.2.1", "descriptions/写生油画__img_3_制作表.json#段落 3.2.1"]
+    }
+  ],
+  "baseline": [
+    {
+      "name": "人物姿态自然度",
+      "description": "女性需呈现专注绘画的动态姿态(身体略微前倾、右臂抬起握笔、左臂弯曲持调色板、头部转向画布),避免僵硬呆板的摆拍感",
+      "why_critical": "制作表中 5 张图的人物姿态描述均强调'专注绘画的动态姿态',姿态不自然会导致人物像假人模特,破坏'优雅写生少女'的核心亮点",
+      "reasoning": "img_1 制作表#段落 1.1.2 描述'身体略微前倾,右臂抬起握持画笔,左臂弯曲握持调色板',img_2、img_3、img_4 均有类似姿态描述,姿态形式评分在 0.455-0.765 之间",
+      "references": ["descriptions/写生油画__img_1_制作表.json#段落 1.1.2", "descriptions/写生油画__img_2_制作表.json#段落 2.1.2", "descriptions/写生油画__img_3_制作表.json#段落 3.1.2", "descriptions/写生油画__img_4_制作表.json#段落 4.1.2"]
+    },
+    {
+      "name": "白裙材质与褶皱真实感",
+      "description": "白色长裙需呈现轻薄棉麻或丝绸质地的垂坠感和透气性,有自然形成的垂坠褶皱(裙摆、腰部、背部 V 领处),服装颜色为纯白色无图案",
+      "why_critical": "制作点.md 中'女性衣物'权重 44.7,5 张制作表的服装段落评分均高达 0.806-0.847,材质和褶皱描述详尽,做不好会导致服装像塑料或纸片,一眼假",
+      "reasoning": "img_1 制作表#段落 1.1.2.1 描述'轻薄的棉麻或丝绸质地,具有良好的垂坠感',img_2、img_3、img_4 均有'丝绸或棉麻混纺材质,表面光滑,有轻微光泽感'等描述",
+      "references": ["descriptions/制作点.md", "descriptions/写生油画__img_1_制作表.json#段落 1.1.2.1", "descriptions/写生油画__img_2_制作表.json#段落 2.1.2.1", "descriptions/写生油画__img_3_制作表.json#段落 3.1.2.1", "descriptions/写生油画__img_4_制作表.json#段落 4.1.2.1"]
+    },
+    {
+      "name": "调色板颜料质感",
+      "description": "调色板上的颜料需呈现膏状堆积的厚度和立体感,表面有光泽显示湿润质地,颜色种类丰富(至少 10 种),呈不规则块状和条状分布,部分颜色相互混合形成过渡色",
+      "why_critical": "图片亮点.md 专门将'斑斓厚重的油画颜料'列为实质类亮点,img_5 制作表中颜料段落评分 0.754,质感描述详尽,做不好会像平面贴图而非真实颜料",
+      "reasoning": "img_5 制作表#段落 5.1.3.1 详细描述'颜料呈膏状,堆积在调色板表面,具有一定的厚度和立体感,表面有光泽,显示出湿润的质地',img_1 也有'颜料堆叠感明显'描述",
+      "references": ["descriptions/图片亮点.md", "descriptions/写生油画__img_1_制作表.json#段落 1.1.2.3", "descriptions/写生油画__img_5_制作表.json#段落 5.1.3.1"]
+    },
+    {
+      "name": "前景主体清晰度",
+      "description": "人物主体(头发丝、服装纹理、手部细节)、调色板(木质纹理、颜料堆叠)、画架需清晰锐利,细节可见,与虚化背景形成对比",
+      "why_critical": "5 张制作表的形式段落均强调'前景人物和画架清晰锐利',清晰度形式评分普遍较高,做不好会导致画面整体模糊,失去专业摄影质感",
+      "reasoning": "img_1 制作表#段落 1 形式描述'画面中心区域清晰度高,细节锐利可见',img_2、img_3、img_4、img_5 均有类似描述,清晰度是所有制作表的必选形式参数",
+      "references": ["descriptions/写生油画__img_1_制作表.json#段落 1 形式", "descriptions/写生油画__img_2_制作表.json#段落 2 形式", "descriptions/写生油画__img_3_制作表.json#段落 3 形式", "descriptions/写生油画__img_4_制作表.json#段落 4 形式", "descriptions/写生油画__img_5_制作表.json#段落 5 形式"]
+    },
+    {
+      "name": "背景虚化自然度",
+      "description": "背景树木和草地需呈现柔和的虚化效果(浅景深),与前景清晰主体形成自然过渡,虚化区域有光斑效果(尤其是逆光场景)",
+      "why_critical": "图片亮点.md 将'唯美梦幻的光影与景深'列为形式类亮点,5 张制作表均描述'景深较浅,背景虚化',做不好会导致画面缺乏层次感和专业感",
+      "reasoning": "img_2 制作表#段落 2.3 描述'背景整体呈现柔和的虚化效果,景深较浅,树木轮廓模糊,光斑明显',所有制作表的背景段落均有景深描述",
+      "references": ["descriptions/图片亮点.md", "descriptions/写生油画__img_2_制作表.json#段落 2.3", "descriptions/写生油画__img_3_制作表.json#段落 3.3", "descriptions/写生油画__img_5_制作表.json#段落 5.3"]
+    }
+  ],
+  "requirement_summary": [
+    "高质量人物生成:白裙女性、自然绘画姿态、棕色长发、逆光轮廓光",
+    "服装材质还原:轻薄棉麻/丝绸质地、垂坠褶皱、纯白色",
+    "调色板与颜料:木质调色板、厚重油画颜料 Impasto 质感、多色混合",
+    "光影效果:逆光/轮廓光、温暖光晕、前景清晰背景虚化",
+    "画中画结构:画布上油画内容需与现实场景呼应、印象派风格",
+    "自然背景:高饱和度绿色草地和树木、清新森系色调",
+    "道具细节:画架结构、画笔、白玫瑰点缀"
+  ]
+}

+ 194 - 0
examples/production_restore/input/pipeline.json

@@ -0,0 +1,194 @@
+{
+  "project": "户外白裙写生少女图像还原",
+  "category": "户外白裙写生少女",
+  "total_images": 5,
+  "stages": {
+    "stage_1": {
+      "name": "链式图生图生成",
+      "status": "pending",
+      "description": "以 features 目录中的参考素材为底图,按顺序逐张生成。从 img_1 开始,每张图生成后,将结果作为下一张图的额外参考(chain_from),确保角色、服装、色调的跨图一致性。必须严格按 img_1 → img_2 → img_3 → img_4 → img_5 的顺序串行执行,不可并行",
+      "images": ["img_1", "img_2", "img_3", "img_4", "img_5"],
+      "execution_order": "sequential_chain",
+      "tool_priority": ["nano_banana", "flux_generate", "seedream_generate"],
+      "fallback": "仅当端到端工具效果不足时,才考虑使用 liblibai_controlnet"
+    },
+    "stage_2": {
+      "name": "迭代优化",
+      "status": "pending",
+      "description": "以 Stage 1 生成的图像作为底图,再次图生图迭代优化,增强材质、光影、细节的真实感"
+    },
+    "stage_3": {
+      "name": "跨图一致性检查",
+      "status": "pending",
+      "description": "对比 5 张生成图,检查角色、服装、色彩、光影的一致性。对不一致的图以当前结果为底图重新图生图优化"
+    },
+    "stage_4": {
+      "name": "细节修复与输出",
+      "status": "pending",
+      "description": "逐项检查并修复细节问题,以当前结果为底图进行定向图生图修复,输出最终成品"
+    }
+  },
+  "images": {
+    "img_1": {
+      "required_spec": {
+        "pose": "侧身背对,站姿,身体前倾,右臂抬起握笔,左臂持调色板",
+        "服装": "白色长裙,V 字露背,腰部系带,轻薄棉麻质地,垂坠褶皱",
+        "光影": "逆光从左上方照射,轮廓光晕,发丝金边",
+        "背景": "高饱和度绿色草地和树木,浅景深虚化",
+        "道具": "木质画架,白色画布(有画中画内容),木质调色板(多色颜料)",
+        "画中画": "画布上描绘背对镜头的女性 + 草地 + 蓝花,印象派风格"
+      },
+      "input_from": {
+        "character_ref": "character_asset/character_ref_img1.png",
+        "background_ref": "background_asset/background_green_img1.png",
+        "palette_ref": "palette_asset/palette_impasto_img1_v2.png"
+      },
+      "img2img_config": {
+        "base_image": "character_ref",
+        "reference_images": ["background_ref", "palette_ref"],
+        "chain_from": null,
+        "note": "第 1 张图,无前序结果。以人物参考图为底图做图生图,prompt 中融合背景、道具、光影描述。此图确立角色基准外观"
+      },
+      "prompt": "一位穿着纯白色长裙的女性在户外草地写生,身体略微前倾专注绘画,右手持画笔左手持调色板,棕色长发自然披散在背部,轻薄棉麻质地的白色长裙,V 字露背设计,腰部系带收腰,裙摆自然垂坠有飘逸感,温暖的逆光从左上方照射,人物边缘形成金色轮廓光,发丝呈现明亮光晕,全画幅 85mm 人像定焦镜头,光圈 f/1.8 大光圈,浅景深效果,前景人物清晰锐利,背景草地和树木柔和虚化,高饱和度自然草木绿背景,纯白服装与绿色形成鲜明对比,清新森系色调,真实摄影风格",
+      "negative_prompt": "AI 假人感,塑料质感,过度修饰,模糊,低质量,变形,cgi,3d 渲染,平滑无纹理",
+      "output_spec": {
+        "file": "img_1_restored.png",
+        "resolution": "1024x1365",
+        "critical_checks": ["人物姿态自然度", "白裙材质与褶皱真实感", "前景主体清晰度"],
+        "high_checks": ["逆光轮廓光效果", "背景虚化自然度", "调色板颜料质感"]
+      }
+    },
+    "img_2": {
+      "required_spec": {
+        "pose": "背对,逆光,半剪影效果,身体略微右倾",
+        "服装": "白色长裙,轻薄材质,逆光下透光效果",
+        "光影": "强烈逆光,明显光斑和光晕效果,发丝金边",
+        "背景": "绿色树木,柔和虚化,圆形光斑 (bokeh)",
+        "道具": "木质画架,白色画布(有画中画内容),调色板",
+        "画中画": "画布上描绘背对女性 + 植物 + 花朵 + 光斑,印象派风格"
+      },
+      "input_from": {
+        "character_ref": "character_asset/character_ref_back.png",
+        "background_ref": "background_asset/background_bokeh_img2.png"
+      },
+      "img2img_config": {
+        "base_image": "character_ref",
+        "reference_images": ["background_ref"],
+        "chain_from": "img_1",
+        "note": "以背影参考图为底图,同时将 img_1 的生成结果作为参考图传入,保持角色一致性。prompt 中强化逆光和散景效果"
+      },
+      "prompt": "一位穿着纯白色长裙的女性在户外草地写生,背对镜头,身体略微右倾,强烈逆光效果,人物呈半剪影,发丝边缘明亮金边,轻薄白裙在逆光下呈现透光效果,木质画架和画布,调色板,背景绿色树木柔和虚化,圆形光斑效果,梦幻浪漫氛围,85mm 镜头,f/1.8 大光圈,真实摄影风格",
+      "negative_prompt": "AI 假人感,塑料质感,过度修饰,模糊,低质量,变形,cgi,3d 渲染,光线平淡",
+      "output_spec": {
+        "file": "img_2_restored.png",
+        "resolution": "1024x1365",
+        "critical_checks": ["逆光轮廓光效果", "人物姿态自然度", "前景主体清晰度"],
+        "high_checks": ["背景光斑效果", "白裙透光感", "发丝金边细节"]
+      }
+    },
+    "img_3": {
+      "required_spec": {
+        "pose": "跪坐姿态,身体前倾,左臂靠近地面调色板",
+        "服装": "白色长裙,跪坐时裙摆自然铺展",
+        "光影": "自然光从左上方照射,柔和轮廓光",
+        "背景": "绿色草地,蓝紫色花丛,浅景深",
+        "道具": "木质画架,白色画布(有画中画内容),地面调色板",
+        "画中画": "画布上描绘白裙女性 + 花丛 + 遮阳伞,印象派风格"
+      },
+      "input_from": {
+        "character_ref": "character_asset/character_ref_kneel.png",
+        "background_ref": "background_asset/background_green_img1.png"
+      },
+      "img2img_config": {
+        "base_image": "character_ref",
+        "reference_images": ["background_ref"],
+        "chain_from": "img_2",
+        "note": "以跪姿参考图为底图,将 img_2 的结果作为参考保持角色一致性"
+      },
+      "prompt": "一位穿着纯白色长裙的女性跪坐在户外草地写生,身体前倾专注绘画,左臂靠近地面调色板,棕色长发自然披散,轻薄棉麻质地白裙,裙摆自然铺展在草地上,自然光从左上方照射,柔和轮廓光,背景绿色草地和蓝紫色花丛,浅景深虚化,85mm 镜头,f/1.8 光圈,清新森系色调,真实摄影风格",
+      "negative_prompt": "AI 假人感,塑料质感,过度修饰,模糊,低质量,变形,cgi,3d 渲染,姿态僵硬",
+      "output_spec": {
+        "file": "img_3_restored.png",
+        "resolution": "1024x1365",
+        "critical_checks": ["跪坐姿态自然度", "白裙材质与褶皱真实感", "前景主体清晰度"],
+        "high_checks": ["花丛背景虚化", "裙摆铺展效果", "光影柔和度"]
+      }
+    },
+    "img_4": {
+      "required_spec": {
+        "pose": "侧身面对镜头,仰望,右臂抬起握笔",
+        "服装": "白色长裙,侧身视角可见服装轮廓",
+        "光影": "自然光照射,柔和光影",
+        "背景": "高饱和度绿色草地和树木,浅景深",
+        "道具": "木质画架,空白白色画布(无内容),调色板",
+        "画中画": "空白画布,纯白色表面,无颜料痕迹"
+      },
+      "input_from": {
+        "character_ref": "character_asset/character_ref_side.png",
+        "background_ref": "background_asset/background_green_img4.png",
+        "easel_ref": "easel_asset/easel_blank_canvas_img4.png"
+      },
+      "img2img_config": {
+        "base_image": "character_ref",
+        "reference_images": ["background_ref", "easel_ref"],
+        "chain_from": "img_3",
+        "note": "以侧面参考图为底图,将 img_3 的结果作为参考保持角色一致性。prompt 中强化'空白画布无内容'"
+      },
+      "prompt": "一位穿着纯白色长裙的女性侧身站立面对镜头,头部仰望向左上方,右臂抬起握持画笔,左臂持调色板,轻薄棉麻质地白裙,自然光照射,柔和光影,背景高饱和度绿色草地和树木,浅景深虚化,木质画架上有空白白色画布,纯白色表面无颜料痕迹,85mm 镜头,f/1.8 光圈,清新森系色调,真实摄影风格",
+      "negative_prompt": "AI 假人感,塑料质感,过度修饰,模糊,低质量,变形,cgi,3d 渲染,画布上有内容,颜料痕迹",
+      "output_spec": {
+        "file": "img_4_restored.png",
+        "resolution": "1024x1365",
+        "critical_checks": ["人物姿态自然度", "白裙材质真实感", "空白画布纯白无内容"],
+        "high_checks": ["仰望表情自然", "侧身轮廓清晰", "背景虚化"]
+      }
+    },
+    "img_5": {
+      "required_spec": {
+        "pose": "特写镜头,聚焦手部和调色板,上半身可见",
+        "服装": "白色长裙,特写可见材质细节",
+        "光影": "温暖逆光,调色板颜料光泽",
+        "背景": "绿色背景,强烈虚化",
+        "道具": "木质调色板(多色厚重颜料 Impasto 质感),画笔",
+        "画中画": "画布部分可见于背景,绿蓝色调,笔触粗犷"
+      },
+      "input_from": {
+        "character_ref": "character_asset/character_ref_main.png",
+        "palette_ref": "palette_asset/palette_impasto_img1_v2.png"
+      },
+      "img2img_config": {
+        "base_image": "character_ref",
+        "reference_images": ["palette_ref"],
+        "chain_from": "img_4",
+        "note": "以人物正面参考图为底图,将 img_4 的结果作为参考保持角色一致性。prompt 中强化特写镜头和 Impasto 颜料质感"
+      },
+      "prompt": "特写镜头,聚焦女性双手握持木质调色板和画笔,调色板上有厚重油画颜料 Impasto 质感,膏状堆积,多色混合(绿色、蓝色、红色、黄色、白色、紫色),颜料表面湿润光泽,立体厚度可见,女性穿着纯白色长裙,上半身可见,温暖逆光照射,颜料光泽反射,背景绿色强烈虚化,85mm 微距镜头,f/1.8 光圈,真实摄影风格",
+      "negative_prompt": "AI 假人感,塑料质感,平滑无纹理,模糊,低质量,变形,cgi,3d 渲染,颜料扁平无厚度",
+      "output_spec": {
+        "file": "img_5_restored.png",
+        "resolution": "1024x1365",
+        "critical_checks": ["调色板颜料 Impasto 质感", "颜料湿润光泽", "特写清晰度"],
+        "high_checks": ["多色颜料混合", "手部细节", "背景虚化"]
+      }
+    }
+  },
+  "consistency_checks": {
+    "stage_3": {
+      "character_consistency": "5 张图中女性角色面部特征、发型、肤色应保持一致",
+      "costume_consistency": "白色长裙的材质、款式、颜色应 100% 一致(V 字露背、腰部系带、轻薄棉麻)",
+      "color_scheme": "白绿配色方案贯穿所有图像,绿色饱和度中等偏高,纯白服装",
+      "lighting_consistency": "逆光/轮廓光方向一致(左上方),光晕效果统一",
+      "style_consistency": "真实摄影风格,85mm 镜头,f/1.8 景深效果统一"
+    }
+  },
+  "repair_items": {
+    "stage_4": [
+      {"item": "人物面部细节", "check": "肌肤纹理清晰,无 AI 假人感", "repair_tool": "图生图局部重绘"},
+      {"item": "服装褶皱", "check": "白裙垂坠褶皱自然,材质真实", "repair_tool": "图生图局部重绘"},
+      {"item": "调色板颜料", "check": "Impasto 质感明显,膏状堆积,湿润光泽", "repair_tool": "图生图局部重绘"},
+      {"item": "画布内容", "check": "画中画结构清晰,印象派风格", "repair_tool": "图生图局部重绘"},
+      {"item": "光影效果", "check": "逆光轮廓光自然,发丝金边清晰", "repair_tool": "图生图局部重绘"},
+      {"item": "背景虚化", "check": "浅景深效果自然,光斑柔和", "repair_tool": "图生图局部重绘"}
+    ]
+  }
+}

+ 0 - 0
examples/feature_extract/huahua/descriptions/写生油画__img_1_制作表.json → examples/production_restore/input/raw_info/写生油画__img_1_制作表.json


+ 0 - 0
examples/feature_extract/huahua/descriptions/写生油画__img_2_制作表.json → examples/production_restore/input/raw_info/写生油画__img_2_制作表.json


+ 0 - 0
examples/feature_extract/huahua/descriptions/写生油画__img_3_制作表.json → examples/production_restore/input/raw_info/写生油画__img_3_制作表.json


+ 0 - 0
examples/feature_extract/huahua/descriptions/写生油画__img_4_制作表.json → examples/production_restore/input/raw_info/写生油画__img_4_制作表.json


+ 0 - 0
examples/feature_extract/huahua/descriptions/写生油画__img_5_制作表.json → examples/production_restore/input/raw_info/写生油画__img_5_制作表.json


+ 0 - 0
examples/feature_extract/huahua/descriptions/创作表.md → examples/production_restore/input/raw_info/创作表.md


+ 0 - 0
examples/feature_extract/huahua/descriptions/制作点.md → examples/production_restore/input/raw_info/制作点.md


+ 0 - 0
examples/feature_extract/huahua/descriptions/图片亮点.md → examples/production_restore/input/raw_info/图片亮点.md


+ 44 - 0
examples/production_restore/input/strategy.json

@@ -0,0 +1,44 @@
+{
+  "selected_strategy": {
+    "name": "素材驱动端到端工作流:参考素材图生图 → 迭代优化 → 一致性检查 → 细节修复",
+    "workflow_outline": [
+      "阶段 1 - 素材驱动图生图:以 features 中的参考素材(人物参考图)为底图传入 image_url,配合详细 prompt 描述服装材质、光影效果、相机参数,通过 nano_banana / flux_generate / seedream_generate 从头生成图像",
+      "阶段 2 - 迭代优化:以阶段 1 生成结果为底图再次图生图,增强材质、光影、细节真实感",
+      "阶段 3 - 跨图一致性检查:对比 5 张生成图的角色、服装、色彩、光影一致性,对不一致的图重新图生图优化",
+      "阶段 4 - 细节修复与输出:以当前结果为底图,针对局部细节(面部、褶皱、颜料质感等)进行定向图生图修复"
+    ],
+    "tool_priority": [
+      "nano_banana — 首选,速度快、效果好",
+      "flux_generate — 备选,支持 image_url 图生图",
+      "seedream_generate — 备选,支持 image_url 图生图",
+      "liblibai_controlnet — 最后回退,仅当需要精细姿态/边缘/深度控制且端到端工具效果不足时使用"
+    ],
+    "highlight_coverage": [
+      "优雅的白裙写生少女:端到端图生图保留原型构图 + 增强材质真实感",
+      "斑斓厚重的油画颜料:通过 prompt 强化 Impasto 质感描述 + palette_ref 素材辅助",
+      "清新雅致的白绿配色:prompt 中控制色彩 + background_ref 素材引导",
+      "唯美梦幻的光影与景深:prompt 中描述逆光轮廓光 + 相机参数(85mm、f/1.8)",
+      "虚实呼应的画中画结构:prompt 中同时描述现实场景和画布内容的镜像关系"
+    ],
+    "baseline_coverage": [
+      "人物姿态自然度:以原型图为底图保留姿态,端到端工具增强真实感",
+      "白裙材质与褶皱真实感:prompt 中详细描述布料材质(棉麻、垂坠、透光)",
+      "调色板颜料质感:prompt 强化 Impasto 描述 + palette_ref 素材参考",
+      "前景主体清晰度:prompt 中描述相机参数确保主体锐利",
+      "背景虚化自然度:prompt 中描述 f/1.8 大光圈、浅景深效果"
+    ],
+    "reasoning": "该策略的核心优势:1)端到端图生图工具(nano_banana/flux/seedream)通过 ToolHub 直接可用,无需额外配置;2)以原型图为底图可保留已验证的构图和姿态,只增强真实感;3)参考素材(人物、背景、道具)提供风格引导;4)全流程使用 ToolHub 工具,链路简短、执行效率高;5)仅在端到端工具效果不足时才回退到 ControlNet,避免不必要的复杂度"
+  },
+  "vs_alternatives": [
+    {
+      "alternative": "ControlNet 精确控制方案",
+      "why_not": "需要先提取控制信号(骨骼图、深度图、边缘图),当前素材中不包含这些文件;链路更长、配置更复杂;对于本项目已有原型图+参考素材的情况,端到端图生图更直接高效",
+      "could_switch_if": "端到端工具无法精确控制姿态或构图细节;需要逐像素级别的结构控制"
+    },
+    {
+      "alternative": "纯文生图方案(不使用底图)",
+      "why_not": "放弃已有原型图的构图和姿态信息,每次从零生成;一致性难以保证;浪费已有资源",
+      "could_switch_if": "原型图质量太差无法作为底图使用"
+    }
+  ]
+}

+ 15 - 0
examples/production_restore/presets.json

@@ -0,0 +1,15 @@
+{
+  "main": {
+    "system_prompt_file": "requirement.prompt",
+    "max_iterations": 1000,
+    "skills": ["planning"],
+    "description": "主 Agent - 还原流程管理与协调,按照 pipeline.json 逐阶段指派执行"
+  },
+  "executor": {
+    "system_prompt_file": "execution.prompt",
+    "max_iterations": 200,
+    "temperature": 0.3,
+    "skills": ["planning"],
+    "description": "执行 Agent - 根据指令执行图像生成、素材验证、细节修复等具体操作"
+  }
+}

+ 95 - 0
examples/production_restore/requirement.prompt

@@ -0,0 +1,95 @@
+---
+model: qwen3.5-plus
+temperature: 0.3
+---
+
+$system$
+
+## 角色
+你是一个专注执行的 AI 图像还原专家。你已拿到完整的还原方案(pipeline.json),任务是严格按照方案,逐阶段指派执行 agent 完成图像生成,并在每阶段验证结果后推进下一阶段。
+
+## 路径约定
+- 输入目录(JSON 方案文件):`%input_dir%`
+- 素材目录(参考图等素材):`%input_dir%/../features`
+- 输出目录:`%output_dir%`
+
+pipeline.json 中 `input_from` 字段的路径均相对于素材目录(`features/`)。**指派任务时必须将其展开为完整路径**,传给 executor 的路径必须以 `examples/production_restore/features/` 开头。
+
+### 素材目录结构
+```
+features/
+├── character_asset/   — 人物参考图(character_ref_*.png,含正面/侧面/背面/跪姿等多角度)
+├── background_asset/  — 背景参考图(绿色草地、逆光散景等)
+├── easel_asset/       — 画架道具素材(空白画布等)
+├── palette_asset/     — 调色板道具素材(Impasto 厚涂颜料)
+```
+
+## 指派子 agent 时的要求
+
+指派 executor 执行每张图的生成任务时,**必须在任务描述中明确包含以下信息**:
+
+1. **底图路径**:pipeline.json 中 `img2img_config.base_image` 指定了哪张素材作为底图(如 `character_ref`),将对应的 `input_from` 路径展开为完整路径,例如:
+   - 底图: `examples/production_restore/features/character_asset/character_ref_img1.png`
+
+2. **参考素材的完整路径**:`img2img_config.reference_images` 中列出的参考素材,展开为完整路径,例如:
+   - 背景参考: `examples/production_restore/features/background_asset/background_green_img1.png`
+   - 调色板参考: `examples/production_restore/features/palette_asset/palette_impasto_img1_v2.png`
+
+3. **素材使用方式**:告知 executor 如何使用这些素材:
+   - **底图** → 作为端到端工具(nano_banana / flux_generate / seedream_generate)的 `image_url` 参数传入,进行图生图生成
+   - **参考素材** → 辅助 prompt 描述,帮助 executor 理解目标效果;如工具支持多图输入,可作为参考图传入
+   - executor 必须先读取素材文件,确认存在后再调用工具
+   - **必须使用底图做图生图**,不能跳过底图用纯文生图替代
+   - 如果底图或关键素材文件缺失必须上报
+
+4. **Prompt 和生成配置**:从 pipeline.json 中提取的完整 prompt、negative prompt、img2img_config 等
+
+5. **源信息参考**:告知 executor 读取 `%input_dir%/raw_info` 文件获取图片源信息,从中提取有价值的细节融入 prompt
+
+6. **输出路径**:指定输出保存到 `%output_dir%/`
+
+## 工作流程
+
+### 第一步:读取还原方案
+读取 `%input_dir%/pipeline.json`,提取每张图的 `required_spec`、`input_from`、`img2img_config` 和 prompt,直接进入生成阶段。
+
+### 第二步:链式图生图生成(Stage 1)
+按照 pipeline.json 的 stage_1,**严格按 img_1 → img_2 → img_3 → img_4 → img_5 的顺序串行生成**,不可并行:
+
+1. **img_1(基准图)**:以 `img2img_config.base_image` 指定的参考素材为底图生成。此图确立角色基准外观(面部、发型、肤色、服装),后续所有图都以此为参考锚点
+2. **img_2 ~ img_5(链式传递)**:每张图的 `img2img_config.chain_from` 字段指向前一张图(如 img_2 的 chain_from = img_1)。生成时,**必须将 chain_from 指向的前一张图的生成结果(CDN URL 或本地路径)作为额外的参考图传给 executor**,确保角色一致性
+
+每张图的指派流程:
+- 以 `img2img_config.base_image` 指定的参考素材为底图,传入工具的 `image_url` 参数
+- 如有 `chain_from`,将前一张图的生成结果路径也传给 executor,说明"此为前序图的生成结果,请保持角色、服装、色调一致"
+- 将该图的 `required_spec`、`input_from`(转换为完整路径后)、prompt、`img2img_config` 一并传给 executor
+- **必须在任务描述中列出底图、前序结果图和所有参考素材的完整路径**
+- 指定输出路径到 `%output_dir%`
+- 根据该图的 `output_spec` 评估返回结果
+- 不满足 → 用 `continue_from` 追问同一 executor,说明问题并建议调整方向
+- 满足 → 记录生成结果路径(供下一张图的 chain_from 使用),将生成参数追加记录到 `%output_dir%/stage1_generation_log.md`,进入下一张图
+
+### 第三步:迭代优化(Stage 2)
+Stage 1 全部完成后,对每张图评估是否需要进一步优化:
+- 以 Stage 1 的生成结果作为新的底图,再次图生图迭代,增强材质、光影、细节
+- 记录到 `%output_dir%/stage2_optimization_log.md`
+
+### 第四步:跨图一致性检查(Stage 3)
+- 指派 executor 对比 5 张生成图,检查 pipeline.json stage_3 中所列的一致性要求
+- 对有问题的图,以当前结果为底图重新图生图优化
+- 记录检查报告(`%output_dir%/stage3_consistency_report.md`)
+
+### 第五步:细节修复与输出(Stage 4)
+按照 pipeline.json 的 stage_4,逐项检查并修复。以当前结果为底图,结合定向 prompt 进行图生图修复。最终成品保存至 `%output_dir%/`。记录修复报告(`%output_dir%/stage4_repair_report.md`)。
+
+### 评估原则
+在评估每个 executor 的返回结果时,重点关注 analysis.json 中的优先级:
+1. **Critical 下限点**:不满足必须重做
+2. **High 下限点**:不满足尽量修复
+3. **上限点**:尽力还原,但不影响下限点完成的情况下接受次优
+
+$user$
+请读取输入目录中的还原方案,开始执行完整的图像还原流程:
+%input_dir%
+
+输出目录:%output_dir%

+ 380 - 0
examples/production_restore/run.py

@@ -0,0 +1,380 @@
+"""
+图像还原流程 - 使用框架交互功能
+
+使用 Agent 模式 + Skills + 框架交互控制器
+读取 input/ 中的 pipeline.json,逐阶段执行图像还原。
+
+功能:
+1. 使用框架提供的 InteractiveController
+2. 使用配置文件管理运行参数
+3. 支持命令行随时打断(输入 'p' 暂停,'q' 退出)
+4. 暂停后可插入干预消息
+5. 支持触发经验总结
+6. 查看当前 GoalTree
+7. 支持通过 --trace <ID> 恢复已有 Trace 继续执行
+"""
+
+import argparse
+import os
+import sys
+import asyncio
+from pathlib import Path
+
+# Clash Verge TUN 模式兼容:禁止 httpx/urllib 自动检测系统 HTTP 代理
+os.environ.setdefault("no_proxy", "*")
+
+# 添加项目根目录到 Python 路径
+sys.path.insert(0, str(Path(__file__).parent.parent.parent))
+
+from dotenv import load_dotenv
+load_dotenv()
+
+from agent.llm.prompts import SimplePrompt
+from agent.core.runner import AgentRunner, RunConfig
+from agent.trace import (
+    FileSystemTraceStore,
+    Trace,
+    Message,
+)
+from agent.llm import create_qwen_llm_call
+from agent.cli import InteractiveController
+from agent.utils import setup_logging
+from agent.tools.builtin.browser.baseClass import init_browser_session, kill_browser_session
+
+# 导入自定义工具(触发 @tool 注册)
+from agent.tools.builtin.toolhub import toolhub_health, toolhub_search, toolhub_call, image_uploader, image_downloader  # noqa: F401
+from evaluate_tool import evaluate_image  # noqa: F401
+
+# 导入项目配置
+from config import RUN_CONFIG, SKILLS_DIR, TRACE_STORE_PATH, DEBUG, LOG_LEVEL, LOG_FILE, BROWSER_TYPE, HEADLESS, INPUT_DIR, OUTPUT_DIR
+
+
+async def main():
+    # 解析命令行参数
+    parser = argparse.ArgumentParser(description="图像还原 (Agent 模式 + 交互增强)")
+    parser.add_argument(
+        "--trace", type=str, default=None,
+        help="已有的 Trace ID,用于恢复继续执行(不指定则新建)",
+    )
+    args = parser.parse_args()
+
+    # 路径配置
+    base_dir = Path(__file__).parent
+    project_root = base_dir.parent.parent
+    prompt_path = base_dir / "requirement.prompt"
+    output_dir = project_root / OUTPUT_DIR
+    output_dir.mkdir(parents=True, exist_ok=True)
+
+    # 1. 配置日志
+    setup_logging(level=LOG_LEVEL, file=LOG_FILE)
+
+    # 2. 加载项目级 presets
+    print("2. 加载 presets...")
+    presets_path = base_dir / "presets.json"
+    if presets_path.exists():
+        from agent.core.presets import load_presets_from_json
+        load_presets_from_json(str(presets_path))
+        print(f"   - 已加载项目 presets")
+    else:
+        print(f"   - 未找到 presets.json,跳过")
+
+    # 3. 加载 prompt
+    print("3. 加载 prompt...")
+    prompt = SimplePrompt(prompt_path)
+
+    # 4. 构建任务消息
+    print("4. 构建任务消息...")
+    print(f"   - 输入目录: {INPUT_DIR}")
+    print(f"   - 输出目录: {OUTPUT_DIR}")
+    messages = prompt.build_messages(input_dir=INPUT_DIR, output_dir=OUTPUT_DIR)
+
+    # 5. 初始化浏览器
+    browser_mode_names = {"cloud": "云浏览器", "local": "本地浏览器", "container": "容器浏览器"}
+    browser_mode_name = browser_mode_names.get(BROWSER_TYPE, BROWSER_TYPE)
+    print(f"5. 正在初始化{browser_mode_name}...")
+    await init_browser_session(
+        browser_type=BROWSER_TYPE,
+        headless=HEADLESS,
+        url="https://www.google.com/",
+        profile_name=""
+    )
+    print(f"   ✅ {browser_mode_name}初始化完成\n")
+
+    # 6. 创建 Agent Runner
+    print("6. 创建 Agent Runner...")
+    print(f"   - Skills 目录: {SKILLS_DIR}")
+
+    # 从 prompt 的 frontmatter 中提取模型配置(优先于 config.py)
+    prompt_model = prompt.config.get("model", None)
+    if prompt_model:
+        model_for_llm = prompt_model
+        print(f"   - 模型 (from prompt): {model_for_llm}")
+    else:
+        model_for_llm = RUN_CONFIG.model
+        print(f"   - 模型 (from config): {model_for_llm}")
+
+    store = FileSystemTraceStore(base_path=TRACE_STORE_PATH)
+    runner = AgentRunner(
+        trace_store=store,
+        llm_call=create_qwen_llm_call(model=model_for_llm),
+        skills_dir=SKILLS_DIR,
+        debug=DEBUG
+    )
+
+    # 7. 创建交互控制器
+    interactive = InteractiveController(
+        runner=runner,
+        store=store,
+        enable_stdin_check=True
+    )
+    # 将 stdin 检查回调注入 runner,供子 agent 执行期间使用
+    runner.stdin_check = interactive.check_stdin
+
+    # 8. 任务信息
+    task_name = RUN_CONFIG.name or base_dir.name
+    print("=" * 60)
+    print(f"{task_name}")
+    print("=" * 60)
+    print("💡 交互提示:")
+    print("   - 执行过程中输入 'p' 或 'pause' 暂停并进入交互模式")
+    print("   - 执行过程中输入 'q' 或 'quit' 停止执行")
+    print("=" * 60)
+    print()
+
+    # 9. 判断是新建还是恢复
+    resume_trace_id = args.trace
+    if resume_trace_id:
+        existing_trace = await store.get_trace(resume_trace_id)
+        if not existing_trace:
+            print(f"\n错误: Trace 不存在: {resume_trace_id}")
+            sys.exit(1)
+        print(f"恢复已有 Trace: {resume_trace_id[:8]}...")
+        print(f"   - 状态: {existing_trace.status}")
+        print(f"   - 消息数: {existing_trace.total_messages}")
+        print(f"\n💡 提示:恢复 Trace 时会先进入交互菜单,您可以选择从指定消息续跑")
+    else:
+        print(f"启动新 Agent...")
+
+    print()
+
+    final_response = ""
+    current_trace_id = resume_trace_id
+    current_sequence = 0
+    should_exit = False
+
+    try:
+        # 配置
+        run_config = RUN_CONFIG
+        if resume_trace_id:
+            initial_messages = None
+            run_config.trace_id = resume_trace_id
+        else:
+            initial_messages = messages
+            run_config.name = f"{task_name}:还原任务"
+
+        while not should_exit:
+            if current_trace_id:
+                run_config.trace_id = current_trace_id
+
+            final_response = ""
+
+            # 如果是恢复 trace 或 trace 已完成/失败且没有新消息,进入交互菜单
+            if current_trace_id and initial_messages is None:
+                check_trace = await store.get_trace(current_trace_id)
+                if check_trace:
+                    # 显示 trace 状态
+                    if check_trace.status == "completed":
+                        print(f"\n[Trace] ✅ 已完成")
+                        print(f"  - Total messages: {check_trace.total_messages}")
+                        print(f"  - Total cost: ${check_trace.total_cost:.4f}")
+                    elif check_trace.status == "failed":
+                        print(f"\n[Trace] ❌ 已失败: {check_trace.error_message}")
+                    elif check_trace.status == "stopped":
+                        print(f"\n[Trace] ⏸️ 已停止")
+                        print(f"  - Total messages: {check_trace.total_messages}")
+                    else:
+                        print(f"\n[Trace] 📊 状态: {check_trace.status}")
+                        print(f"  - Total messages: {check_trace.total_messages}")
+
+                    current_sequence = check_trace.head_sequence
+
+                    menu_result = await interactive.show_menu(current_trace_id, current_sequence)
+
+                    if menu_result["action"] == "stop":
+                        break
+                    elif menu_result["action"] == "continue":
+                        new_messages = menu_result.get("messages", [])
+                        if new_messages:
+                            initial_messages = new_messages
+                            run_config.after_sequence = menu_result.get("after_sequence")
+                        else:
+                            initial_messages = []
+                            run_config.after_sequence = None
+                        continue
+                    break
+
+            # 如果没有进入菜单(新建 trace),设置初始消息
+            if initial_messages is None:
+                initial_messages = []
+
+            print(f"{'▶️ 开始执行...' if not current_trace_id else '▶️ 继续执行...'}")
+
+            # 执行 Agent
+            paused = False
+            try:
+                async for item in runner.run(messages=initial_messages, config=run_config):
+                    # 检查用户中断
+                    cmd = interactive.check_stdin()
+                    if cmd == 'pause':
+                        print("\n⏸️ 正在暂停执行...")
+                        if current_trace_id:
+                            await runner.stop(current_trace_id)
+                        await asyncio.sleep(0.5)
+
+                        menu_result = await interactive.show_menu(current_trace_id, current_sequence)
+
+                        if menu_result["action"] == "stop":
+                            should_exit = True
+                            paused = True
+                            break
+                        elif menu_result["action"] == "continue":
+                            new_messages = menu_result.get("messages", [])
+                            if new_messages:
+                                initial_messages = new_messages
+                                after_seq = menu_result.get("after_sequence")
+                                if after_seq is not None:
+                                    run_config.after_sequence = after_seq
+                                paused = True
+                                break
+                            else:
+                                initial_messages = []
+                                run_config.after_sequence = None
+                                paused = True
+                                break
+
+                    elif cmd == 'quit':
+                        print("\n🛑 用户请求停止...")
+                        if current_trace_id:
+                            await runner.stop(current_trace_id)
+                        should_exit = True
+                        break
+
+                    # 处理 Trace 对象
+                    if isinstance(item, Trace):
+                        current_trace_id = item.trace_id
+                        if item.status == "running":
+                            print(f"[Trace] 开始: {item.trace_id[:8]}...")
+                        elif item.status == "completed":
+                            print(f"\n[Trace] ✅ 完成")
+                            print(f"  - Total messages: {item.total_messages}")
+                            print(f"  - Total cost: ${item.total_cost:.4f}")
+                        elif item.status == "failed":
+                            print(f"\n[Trace] ❌ 失败: {item.error_message}")
+                        elif item.status == "stopped":
+                            print(f"\n[Trace] ⏸️ 已停止")
+
+                    # 处理 Message 对象
+                    elif isinstance(item, Message):
+                        current_sequence = item.sequence
+
+                        if item.role == "assistant":
+                            content = item.content
+                            if isinstance(content, dict):
+                                text = content.get("text", "")
+                                tool_calls = content.get("tool_calls")
+
+                                if text and not tool_calls:
+                                    final_response = text
+                                    print(f"\n[Response] Agent 回复:")
+                                    print(text)
+                                elif text:
+                                    preview = text[:150] + "..." if len(text) > 150 else text
+                                    print(f"[Assistant] {preview}")
+
+                        elif item.role == "tool":
+                            content = item.content
+                            tool_name = "unknown"
+                            if isinstance(content, dict):
+                                tool_name = content.get("tool_name", "unknown")
+
+                            if item.description and item.description != tool_name:
+                                desc = item.description[:80] if len(item.description) > 80 else item.description
+                                print(f"[Tool Result] ✅ {tool_name}: {desc}...")
+                            else:
+                                print(f"[Tool Result] ✅ {tool_name}")
+
+            except Exception as e:
+                print(f"\n执行出错: {e}")
+                import traceback
+                traceback.print_exc()
+
+            if paused:
+                if should_exit:
+                    break
+                continue
+
+            if should_exit:
+                break
+
+            # Runner 退出后显示交互菜单
+            if current_trace_id:
+                menu_result = await interactive.show_menu(current_trace_id, current_sequence)
+
+                if menu_result["action"] == "stop":
+                    break
+                elif menu_result["action"] == "continue":
+                    new_messages = menu_result.get("messages", [])
+                    if new_messages:
+                        initial_messages = new_messages
+                        run_config.after_sequence = menu_result.get("after_sequence")
+                    else:
+                        initial_messages = []
+                        run_config.after_sequence = None
+                    continue
+            break
+
+    except KeyboardInterrupt:
+        print("\n\n用户中断 (Ctrl+C)")
+        if current_trace_id:
+            await runner.stop(current_trace_id)
+    finally:
+        # 清理浏览器会话
+        try:
+            await kill_browser_session()
+        except Exception:
+            pass
+
+    # 输出结果
+    if final_response:
+        print()
+        print("=" * 60)
+        print("Agent 响应:")
+        print("=" * 60)
+        print(final_response)
+        print("=" * 60)
+        print()
+
+        output_file = output_dir / "result.txt"
+        with open(output_file, 'w', encoding='utf-8') as f:
+            f.write(final_response)
+
+        print(f"✓ 结果已保存到: {output_file}")
+        print()
+
+    # 可视化提示
+    if current_trace_id:
+        print("=" * 60)
+        print("可视化 Step Tree:")
+        print("=" * 60)
+        print("1. 启动 API Server:")
+        print("   python3 api_server.py")
+        print()
+        print("2. 浏览器访问:")
+        print("   http://localhost:8000/api/traces")
+        print()
+        print(f"3. Trace ID: {current_trace_id}")
+        print("=" * 60)
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 70 - 0
examples/production_restore/upload.py

@@ -0,0 +1,70 @@
+import asyncio
+import os
+import sys
+import httpx
+from cyber_sdk.ali_oss import upload_localfile
+
+async def upload_image(local_file_path: str, bucket_name: str = 'aigc-admin', bucket_path: str = 'template') -> str:
+    """Uploads a local image to OSS and returns the CDN URL."""
+    print(f"Uploading {local_file_path} to {bucket_name}/{bucket_path}...")
+    # Use forward slashes so cyber_sdk's .split('/') can correctly extract filename on Windows
+    safe_path = os.path.abspath(local_file_path).replace("\\", "/")
+    result = await upload_localfile(
+        file_path=safe_path, 
+        bucket_path=bucket_path, 
+        bucket_name=bucket_name
+    )
+    print("Upload SDK Response:", result)
+    
+    oss_object_key = result.get('oss_object_key')
+    if oss_object_key:
+        cdn_url = f"https://res.cybertogether.net/{oss_object_key}"
+        return cdn_url
+    return None
+
+async def download_image(url: str, save_path: str):
+    """Downloads an image from an HTTP link and saves it locally."""
+    print(f"Downloading from {url} to {save_path}...")
+    async with httpx.AsyncClient(timeout=30.0) as client:
+        resp = await client.get(url)
+        resp.raise_for_status()
+        with open(save_path, 'wb') as f:
+            f.write(resp.content)
+    print(f"Download completed: {save_path}")
+
+async def main():
+    if len(sys.argv) < 2:
+        print("Usage:")
+        print("  Upload: python upload.py <file_path>")
+        print("  Download: python upload.py download <url> <save_path>")
+        
+        # Self-test block if no param is given
+        print("\n--- Running Self Test ---")
+        test_file = 'img_1_gen.png'
+        if not os.path.exists(test_file):
+            print(f"Creating a dummy 1x1 PNG at {test_file} for testing.")
+            with open(test_file, 'wb') as f:
+                f.write(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01\r\n-\xb4\x00\x00\x00\x00IEND\xaeB`\x82')
+        
+        url = await upload_image(test_file, 'aigc-admin', 'crawler/image')
+        print(f"\nExtracted URL: {url}")
+        
+        if url:
+            # Download it back
+            download_path = "downloaded_dummy.png"
+            await download_image(url, download_path)
+            
+    elif sys.argv[1] == 'download':
+        if len(sys.argv) >= 4:
+            await download_image(sys.argv[2], sys.argv[3])
+        else:
+            print("Error: Missing parameters for download.")
+            print("Usage: python upload.py download <url> <save_path>")
+    else:
+        # Upload context
+        file_path = sys.argv[1]
+        url = await upload_image(file_path, 'aigc-admin', 'crawler/image')
+        print(f"\nFinal CDN URL: {url}")
+
+if __name__ == '__main__':
+    asyncio.run(main())

+ 0 - 0
examples/feature_extract/aliduoduo/description/创作表.md → examples/requirement_extract/aliduoduo/description/创作表.md


+ 0 - 0
examples/feature_extract/aliduoduo/description/制作亮点.md → examples/requirement_extract/aliduoduo/description/制作亮点.md


+ 0 - 0
examples/feature_extract/aliduoduo/description/制作点.md → examples/requirement_extract/aliduoduo/description/制作点.md


+ 0 - 0
examples/feature_extract/aliduoduo/description/阿里多多酱__img_1_制作表.json → examples/requirement_extract/aliduoduo/description/阿里多多酱__img_1_制作表.json


+ 0 - 0
examples/feature_extract/aliduoduo/description/阿里多多酱__img_2_制作表.json → examples/requirement_extract/aliduoduo/description/阿里多多酱__img_2_制作表.json


+ 0 - 0
examples/feature_extract/aliduoduo/description/阿里多多酱__img_3_制作表.json → examples/requirement_extract/aliduoduo/description/阿里多多酱__img_3_制作表.json


+ 0 - 0
examples/feature_extract/aliduoduo/index.md → examples/requirement_extract/aliduoduo/index.md


+ 4 - 4
examples/feature_extract/config.py → examples/requirement_extract/config.py

@@ -16,7 +16,7 @@ RUN_CONFIG = RunConfig(
     max_iterations=1000,
     max_iterations=1000,
 
 
     # 任务名称
     # 任务名称
-    name="内容解构",
+    name="需求提取",
 
 
     # 知识管理配置
     # 知识管理配置
     knowledge=KnowledgeConfig(
     knowledge=KnowledgeConfig(
@@ -43,8 +43,8 @@ RUN_CONFIG = RunConfig(
 
 
 # ===== 任务配置 =====
 # ===== 任务配置 =====
 
 
-INPUT_DIR = "examples/feature_extract/huahua"        # 输入素材目录
-OUTPUT_DIR = "examples/feature_extract/huahua/output"  # 输出目录,直接保存在输入目录内
+INPUT_DIR = "examples/requirement_extract"        # 输入目录(包含多个内容文件夹)
+OUTPUT_DIR = "examples/requirement_extract/output"  # 输出目录(每个内容的结果存到子目录下)
 
 
 
 
 # ===== 基础设施配置 =====
 # ===== 基础设施配置 =====
@@ -57,5 +57,5 @@ LOG_FILE = None  # 设置为文件路径可以同时输出到文件
 
 
 # ===== 浏览器配置 =====
 # ===== 浏览器配置 =====
 # 可选值: "cloud" (云浏览器) 或 "local" (本地浏览器) 或 "container" (容器浏览器,支持预配置账户)
 # 可选值: "cloud" (云浏览器) 或 "local" (本地浏览器) 或 "container" (容器浏览器,支持预配置账户)
-BROWSER_TYPE = "container"
+BROWSER_TYPE = "local"
 HEADLESS = True
 HEADLESS = True

+ 369 - 0
examples/requirement_extract/huahua/descriptions/写生油画__img_1_制作表.json

@@ -0,0 +1,369 @@
+[
+  {
+    "名称": "户外绘画场景",
+    "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地。",
+    "段落ID": "段落1",
+    "形式": {
+      "拍摄角度": {
+        "名称": "拍摄角度",
+        "描述": "相机位于人物右后方,略低于人物视线,以平视偏低的视角拍摄,使得人物和画架占据画面右侧和中央,背景的草地和树木在左侧和上方。",
+        "类型": "视角",
+        "形式ID": "形式5"
+      },
+      "景别": {
+        "名称": "景别",
+        "描述": "中景,画面中人物从腰部以上到头部完整呈现,画架大部分可见,背景的树木和草地也占据了较大比例,强调了人物与环境的互动。",
+        "类型": "构图",
+        "形式ID": "形式7"
+      },
+      "光照": {
+        "名称": "光照",
+        "描述": "自然光,光线柔和,从画面左上方照射,在人物的右侧和画架的左侧形成轻微阴影,整体画面亮度适中,无明显过曝或欠曝区域。",
+        "类型": "光影",
+        "形式ID": "形式2"
+      },
+      "色彩饱和度": {
+        "名称": "色彩饱和度",
+        "描述": "整体色彩饱和度中等偏高,绿色草地和树木的颜色鲜明,人物白色服装和调色板上的颜料色彩也较为突出,画面整体呈现出清新自然的色调。",
+        "类型": "色彩",
+        "形式ID": "形式11"
+      },
+      "清晰度": {
+        "名称": "清晰度",
+        "描述": "画面中心区域(人物、画架、画布)清晰度高,细节锐利可见。背景的树木和远处的草地有轻微虚化,呈现出景深效果。",
+        "类型": "清晰度",
+        "形式ID": "形式1"
+      },
+      "构图": {
+        "名称": "构图",
+        "描述": "采用开放式构图,人物和画架位于画面右侧偏中,占据了画面约60%的区域,背景的草地和树木占据左侧和上方约40%的区域。人物的视线和绘画动作引导观众看向画布,形成视觉焦点。画面整体平衡,右侧主体突出,左侧背景延伸。",
+        "类型": "构图",
+        "形式ID": "形式4"
+      },
+      "评分详情": {
+        "combined_score": 0.846
+      }
+    },
+    "子段落": [
+      {
+        "名称": "人物",
+        "描述": "一名女性,侧身背对镜头,正在进行绘画。",
+        "段落ID": "段落1.1",
+        "形式": {
+          "拍摄角度": {
+            "名称": "拍摄角度",
+            "描述": "人物侧身背对镜头,头部略微向左转,使得右耳和部分右脸颊可见。身体朝向画布,呈现出专注绘画的姿态。",
+            "类型": "视角",
+            "形式ID": "形式5"
+          },
+          "景别": {
+            "名称": "景别",
+            "描述": "人物从头部到脚部完整呈现,占据画面右侧约60%的区域,属于全身景别。",
+            "类型": "构图",
+            "形式ID": "形式7"
+          },
+          "光照": {
+            "名称": "光照",
+            "描述": "人物右侧受光,左侧(背对镜头一侧)有轻微阴影,光线柔和,使得服装的褶皱和头发的细节清晰可见。",
+            "类型": "光影",
+            "形式ID": "形式2"
+          },
+          "色彩饱和度": {
+            "名称": "色彩饱和度",
+            "描述": "人物的头发、肤色和白色服装的色彩饱和度适中,与背景的绿色形成对比,突出人物主体。",
+            "类型": "色彩",
+            "形式ID": "形式11"
+          },
+          "清晰度": {
+            "名称": "清晰度",
+            "描述": "人物主体清晰度高,头发丝、服装纹理、手部细节均清晰可见。",
+            "类型": "清晰度",
+            "形式ID": "形式1"
+          },
+          "构图": {
+            "名称": "构图",
+            "描述": "人物位于画面右侧,从画面顶部延伸至底部,占据了画面约60%的垂直空间,形成视觉上的引导线,将观众的注意力引向画布。",
+            "类型": "构图",
+            "形式ID": "形式4"
+          },
+          "评分详情": {
+            "combined_score": 0.745
+          }
+        },
+        "子段落": [
+          {
+            "名称": "头发",
+            "描述": "棕色长发,部分散落在肩上。",
+            "段落ID": "段落1.1.1",
+            "形式": {
+              "发色": {
+                "名称": "发色",
+                "描述": "棕色,呈现出自然光泽的深棕色,在光照下略显浅棕。",
+                "类型": "色彩",
+                "形式ID": "形式23"
+              },
+              "发型": {
+                "名称": "发型",
+                "描述": "长发,直发,部分散落在右肩和背部,发尾略有卷曲,发际线清晰可见。",
+                "类型": "形态",
+                "形式ID": "形式21"
+              },
+              "评分详情": {
+                "combined_score": 0.504
+              }
+            },
+            "评分详情": {
+              "combined_score": 0.525
+            }
+          },
+          {
+            "名称": "身体",
+            "描述": "女性的躯干和手臂。",
+            "段落ID": "段落1.1.2",
+            "形式": {
+              "姿态": {
+                "名称": "姿态",
+                "描述": "女性身体略微前倾,右臂抬起握持画笔,左臂弯曲握持调色板,头部转向画布,呈现出专注绘画的动态姿态。",
+                "类型": "动作",
+                "形式ID": "形式8"
+              },
+              "清晰度": {
+                "名称": "清晰度",
+                "描述": "身体部分(手臂、手、颈部)清晰度高,皮肤纹理和服装褶皱细节锐利。",
+                "类型": "清晰度",
+                "形式ID": "形式1"
+              },
+              "光照": {
+                "名称": "光照",
+                "描述": "身体右侧受光,左侧有轻微阴影,光线均匀柔和,突出了身体的轮廓和服装的立体感。",
+                "类型": "光影",
+                "形式ID": "形式2"
+              },
+              "评分详情": {
+                "combined_score": 0.588
+              }
+            },
+            "子段落": [
+              {
+                "名称": "服装",
+                "描述": "白色长裙,袖子宽松。",
+                "段落ID": "段落1.1.2.1",
+                "形式": {
+                  "服装颜色": {
+                    "名称": "服装颜色",
+                    "描述": "纯白色,无其他图案或颜色。",
+                    "类型": "色彩",
+                    "形式ID": "形式16"
+                  },
+                  "服装款式": {
+                    "名称": "服装款式",
+                    "描述": "长袖连衣裙,袖子宽松,裙摆飘逸,腰部有收紧设计,领口为V字形,背部有系带细节。",
+                    "类型": "形态",
+                    "形式ID": "形式14"
+                  },
+                  "材质": {
+                    "名称": "材质",
+                    "描述": "轻薄的棉麻或丝绸质地,具有良好的垂坠感和透气性。",
+                    "类型": "质感",
+                    "形式ID": "形式10"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.835
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.847
+                }
+              },
+              {
+                "名称": "画笔",
+                "描述": "女性右手握持的细长画笔。",
+                "段落ID": "段落1.1.2.2",
+                "形式": {
+                  "形状": {
+                    "名称": "形状",
+                    "描述": "细长杆状,笔尖为锥形,笔杆中部略粗。",
+                    "类型": "形状",
+                    "形式ID": "形式17"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.42
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.56
+                }
+              },
+              {
+                "名称": "调色板",
+                "描述": "女性左手握持的椭圆形调色板,上面有多种颜料。",
+                "段落ID": "段落1.1.2.3",
+                "形式": {
+                  "形状": {
+                    "名称": "形状",
+                    "描述": "不规则椭圆形,边缘圆润,中间有一个拇指孔。",
+                    "类型": "形状",
+                    "形式ID": "形式17"
+                  },
+                  "颜色": {
+                    "名称": "颜色",
+                    "描述": "调色板底色为深棕色,表面沾有多种颜料,包括绿色、蓝色、白色、粉色、黄色、红色等,其中绿色颜料面积最大。",
+                    "类型": "色彩",
+                    "形式ID": "形式3"
+                  },
+                  "颜料分布": {
+                    "名称": "颜料分布",
+                    "描述": "颜料呈不规则块状分布在调色板表面,绿色颜料集中在中央区域,其他颜色颜料散布在边缘。",
+                    "类型": "布局",
+                    "形式ID": "形式25"
+                  },
+                  "清晰度": {
+                    "名称": "清晰度",
+                    "描述": "调色板的木质纹理和颜料的堆叠感清晰可见。",
+                    "类型": "清晰度",
+                    "形式ID": "形式1"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.644
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.675
+                }
+              }
+            ],
+            "评分详情": {
+              "combined_score": 0.595
+            }
+          }
+        ],
+        "评分详情": {
+          "combined_score": 0.815
+        }
+      },
+      {
+        "名称": "画架",
+        "描述": "木质三脚画架,支撑着画布。",
+        "段落ID": "段落1.2",
+        "形式": {
+          "评分详情": {
+            "combined_score": 0.442
+          }
+        },
+        "子段落": [
+          {
+            "名称": "画布",
+            "描述": "画架上的一幅未完成的画作,描绘了一名背对镜头的女性。",
+            "段落ID": "段落1.2.1",
+            "形式": {
+              "绘画风格": {
+                "名称": "绘画风格",
+                "描述": "印象派风格,笔触粗犷,色彩鲜明,注重光影和氛围的表达,而非精确的细节描绘。",
+                "类型": "风格",
+                "形式ID": "形式27"
+              },
+              "色彩": {
+                "名称": "色彩",
+                "描述": "画面以绿色和蓝色为主色调,描绘了草地和花丛,人物服装为白色,色彩对比鲜明。",
+                "类型": "色彩",
+                "形式ID": "形式28"
+              },
+              "构图": {
+                "名称": "构图",
+                "描述": "画布中央偏下位置描绘了一名背对镜头的女性,周围是绿色的草地和蓝色的花朵,形成了一个景深感较强的画面。",
+                "类型": "构图",
+                "形式ID": "形式4"
+              },
+              "清晰度": {
+                "名称": "清晰度",
+                "描述": "画布上的画作清晰可见,但由于绘画风格,细节并非写实般锐利,而是呈现出笔触的模糊感。",
+                "类型": "清晰度",
+                "形式ID": "形式1"
+              },
+              "笔触": {
+                "名称": "笔触",
+                "描述": "笔触粗犷有力,颜料堆叠感明显,尤其是绿色和蓝色区域,呈现出明显的纹理。",
+                "类型": "笔触",
+                "形式ID": "形式29"
+              },
+              "内容主题": {
+                "名称": "内容主题",
+                "描述": "描绘了一名身穿白色裙子的女性在户外草地或花丛中行走的背影,与现实场景中的画家形成一种“画中画”的呼应。",
+                "类型": "内容",
+                "形式ID": "形式26"
+              },
+              "评分详情": {
+                "combined_score": 0.946
+              }
+            },
+            "评分详情": {
+              "combined_score": 0.958
+            }
+          },
+          {
+            "名称": "玫瑰花",
+            "描述": "画架下方放置的一朵白色玫瑰花。",
+            "段落ID": "段落1.2.2",
+            "形式": {
+              "清晰度": {
+                "名称": "清晰度",
+                "描述": "玫瑰花的花瓣纹理和叶片细节清晰可见,边缘锐利。",
+                "类型": "清晰度",
+                "形式ID": "形式1"
+              },
+              "评分详情": {
+                "combined_score": 0.318
+              }
+            },
+            "评分详情": {
+              "combined_score": 0.342
+            }
+          }
+        ],
+        "评分详情": {
+          "combined_score": 0.588
+        }
+      },
+      {
+        "名称": "背景",
+        "描述": "远处的绿色树木和近处的草地。",
+        "段落ID": "段落1.3",
+        "形式": {
+          "颜色": {
+            "名称": "颜色",
+            "描述": "背景以绿色为主,包括深绿色(树木)和浅绿色(草地),色彩鲜明且富有层次感。",
+            "类型": "色彩",
+            "形式ID": "形式3"
+          },
+          "清晰度": {
+            "名称": "清晰度",
+            "描述": "近处草地清晰度较高,远处树木和更远的背景有明显虚化,呈现出景深效果。",
+            "类型": "清晰度",
+            "形式ID": "形式1"
+          },
+          "光照": {
+            "名称": "光照",
+            "描述": "背景整体受光均匀,树木和草地有自然的光影变化,无明显过曝或欠曝区域。",
+            "类型": "光影",
+            "形式ID": "形式2"
+          },
+          "景深": {
+            "名称": "景深",
+            "描述": "景深较浅,前景(人物和画架)清晰,中景(近处草地)清晰,远景(树木)虚化,营造出空间层次感。",
+            "类型": "空间",
+            "形式ID": "形式9"
+          },
+          "评分详情": {
+            "combined_score": 0.595
+          }
+        },
+        "评分详情": {
+          "combined_score": 0.703
+        }
+      }
+    ],
+    "评分详情": {
+      "combined_score": 0.78
+    }
+  }
+]

+ 338 - 0
examples/requirement_extract/huahua/descriptions/写生油画__img_2_制作表.json

@@ -0,0 +1,338 @@
+[
+  {
+    "名称": "户外绘画场景",
+    "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和阳光。",
+    "段落ID": "段落2",
+    "形式": {
+      "拍摄角度": {
+        "名称": "拍摄角度",
+        "描述": "从人物背后的中低角度拍摄,视线略微向上倾斜,将人物、画架和背景的树木都纳入画面,人物的头部位于画面顶部偏左,画架位于画面右侧,背景的树木和天空占据画面上半部分。",
+        "类型": "视角",
+        "形式ID": "形式5"
+      },
+      "景别": {
+        "名称": "景别",
+        "描述": "中景,画面中人物从腰部以上到头顶,以及画架和部分背景草地和树木清晰可见,人物占据画面约70%的高度,画架占据画面约80%的高度。",
+        "类型": "构图",
+        "形式ID": "形式7"
+      },
+      "光照": {
+        "名称": "光照",
+        "描述": "逆光,阳光从画面左上方透过树叶照射过来,形成强烈的光斑和光晕效果,人物和画架处于半剪影状态,但细节仍可见,草地受光均匀,整体画面明亮。",
+        "类型": "光影",
+        "形式ID": "形式2"
+      },
+      "色彩饱和度": {
+        "名称": "色彩饱和度",
+        "描述": "中等偏高,绿色草地和树木的色彩鲜艳,女性白色长裙和画架的木色饱和度适中,画面整体色彩明快。",
+        "类型": "色彩",
+        "形式ID": "形式11"
+      },
+      "清晰度": {
+        "名称": "清晰度",
+        "描述": "前景人物和画架清晰锐利,背景的树木和阳光呈现柔和的虚化效果,景深较浅。",
+        "类型": "清晰度",
+        "形式ID": "形式1"
+      },
+      "构图": {
+        "名称": "构图",
+        "描述": "采用三分法构图,人物主体位于画面左侧三分之一处,画架位于画面右侧三分之一处,形成平衡的视觉效果。人物的头部位于画面上方三分之一处,地平线位于画面下方三分之一处。",
+        "类型": "构图",
+        "形式ID": "形式4"
+      },
+      "评分详情": {
+        "combined_score": 0.811
+      }
+    },
+    "子段落": [
+      {
+        "名称": "人物",
+        "描述": "一名女性,背对镜头,正在进行绘画。",
+        "段落ID": "段落2.1",
+        "形式": {
+          "拍摄角度": {
+            "名称": "拍摄角度",
+            "描述": "从人物背后的中低角度拍摄,视线略微向上倾斜,人物背对镜头,头部略微偏向右侧,身体朝向画架。",
+            "类型": "视角",
+            "形式ID": "形式5"
+          },
+          "景别": {
+            "名称": "景别",
+            "描述": "中景,人物从腰部以上到头顶清晰可见,占据画面约70%的高度,其身体大部分位于画面左侧。",
+            "类型": "构图",
+            "形式ID": "形式7"
+          },
+          "光照": {
+            "名称": "光照",
+            "描述": "逆光,阳光从人物左后方照射,导致人物背部和头发边缘有明显的光晕,身体正面受光较少,但仍能看清细节。",
+            "类型": "光影",
+            "形式ID": "形式2"
+          },
+          "色彩饱和度": {
+            "名称": "色彩饱和度",
+            "描述": "中等偏高,头发的棕色和服装的白色饱和度适中,与背景的绿色形成对比。",
+            "类型": "色彩",
+            "形式ID": "形式11"
+          },
+          "清晰度": {
+            "名称": "清晰度",
+            "描述": "人物主体清晰锐利,头发丝和服装褶皱细节可见。",
+            "类型": "清晰度",
+            "形式ID": "形式1"
+          },
+          "构图": {
+            "名称": "构图",
+            "描述": "人物主体位于画面左侧三分之一处,头部位于画面上方三分之一处,形成视觉引导。",
+            "类型": "构图",
+            "形式ID": "形式4"
+          },
+          "评分详情": {
+            "combined_score": 0.849
+          }
+        },
+        "子段落": [
+          {
+            "名称": "头发",
+            "描述": "棕色长发,披散在背部。",
+            "段落ID": "段落2.1.1",
+            "形式": {
+              "发色": {
+                "名称": "发色",
+                "描述": "深棕色,在阳光下呈现出暖棕色调,发梢颜色略浅。",
+                "类型": "色彩",
+                "形式ID": "形式23"
+              },
+              "发型": {
+                "名称": "发型",
+                "描述": "长直发,自然披散在背部,发梢略带自然卷曲,长度及腰。",
+                "类型": "形态",
+                "形式ID": "形式21"
+              },
+              "发量": {
+                "名称": "发量",
+                "描述": "发量浓密,覆盖了大部分背部,从头顶到发梢呈现出厚重感。",
+                "类型": "量感",
+                "形式ID": "形式24"
+              },
+              "光泽度": {
+                "名称": "光泽度",
+                "描述": "头发表面有明显的光泽,尤其是在阳光照射下,发丝边缘呈现出明亮的光晕。",
+                "类型": "质感",
+                "形式ID": "形式22"
+              },
+              "评分详情": {
+                "combined_score": 0.532
+              }
+            },
+            "评分详情": {
+              "combined_score": 0.546
+            }
+          },
+          {
+            "名称": "身体",
+            "描述": "女性的躯干和手臂。",
+            "段落ID": "段落2.1.2",
+            "形式": {
+              "姿态": {
+                "名称": "姿态",
+                "描述": "女性身体略微向右倾斜,背部微弓,头部略微前倾,右手持画笔,左手持调色板,呈现出专注绘画的姿态。",
+                "类型": "动作",
+                "形式ID": "形式8"
+              },
+              "光照": {
+                "名称": "光照",
+                "描述": "逆光,身体背部和手臂边缘有明显的光晕,身体正面受光较少,形成一定的阴影。",
+                "类型": "光影",
+                "形式ID": "形式2"
+              },
+              "评分详情": {
+                "combined_score": 0.455
+              }
+            },
+            "子段落": [
+              {
+                "名称": "服装",
+                "描述": "白色长裙,露背设计。",
+                "段落ID": "段落2.1.2.1",
+                "形式": {
+                  "服装颜色": {
+                    "名称": "服装颜色",
+                    "描述": "纯白色,在阳光下略带米色调。",
+                    "类型": "色彩",
+                    "形式ID": "形式16"
+                  },
+                  "服装款式": {
+                    "名称": "服装款式",
+                    "描述": "长袖连衣裙,V字露背设计,腰部有系带收腰,裙摆为宽松的A字形长裙,长度及脚踝。",
+                    "类型": "形态",
+                    "形式ID": "形式14"
+                  },
+                  "材质": {
+                    "名称": "材质",
+                    "描述": "丝绸或棉麻混纺材质,表面光滑,有轻微的光泽感,质地轻薄。",
+                    "类型": "质感",
+                    "形式ID": "形式10"
+                  },
+                  "褶皱": {
+                    "名称": "褶皱",
+                    "描述": "裙摆和腰部有自然形成的垂坠褶皱,背部V领处也有轻微褶皱。",
+                    "类型": "形态",
+                    "形式ID": "形式20"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.828
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.834
+                }
+              },
+              {
+                "名称": "画笔",
+                "描述": "女性右手握持的细长画笔。",
+                "段落ID": "段落2.1.2.2",
+                "形式": {
+                  "材质": {
+                    "名称": "材质",
+                    "描述": "笔杆为木质或塑料,刷毛为动物毛或合成纤维。",
+                    "类型": "质感",
+                    "形式ID": "形式10"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.294
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.455
+                }
+              },
+              {
+                "名称": "调色板",
+                "描述": "女性左手握持的椭圆形调色板,上面有多种颜料。",
+                "段落ID": "段落2.1.2.3",
+                "形式": {
+                  "材质": {
+                    "名称": "材质",
+                    "描述": "木质或塑料材质,表面光滑。",
+                    "类型": "质感",
+                    "形式ID": "形式10"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.455
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.48
+                }
+              }
+            ],
+            "评分详情": {
+              "combined_score": 0.476
+            }
+          }
+        ],
+        "评分详情": {
+          "combined_score": 0.825
+        }
+      },
+      {
+        "名称": "画架",
+        "描述": "木质三脚画架,支撑着画布。",
+        "段落ID": "段落2.2",
+        "形式": {
+          "评分详情": {
+            "combined_score": 0.427
+          }
+        },
+        "子段落": [
+          {
+            "名称": "画布",
+            "描述": "画架上的一幅未完成的画作,描绘了一名背对镜头的女性。",
+            "段落ID": "段落2.2.1",
+            "形式": {
+              "绘画风格": {
+                "名称": "绘画风格",
+                "描述": "印象派或写意风格,笔触粗犷,色彩鲜明,注重光影和氛围的表达,而非精确的细节描绘。",
+                "类型": "风格",
+                "形式ID": "形式27"
+              },
+              "色彩": {
+                "名称": "色彩",
+                "描述": "以绿色、蓝色、紫色为主,辅以白色和黄色,色彩明亮且饱和度较高。",
+                "类型": "色彩",
+                "形式ID": "形式28"
+              },
+              "构图": {
+                "名称": "构图",
+                "描述": "画面中央描绘了一名背对镜头的女性形象,周围是模糊的绿色植物和花朵,背景有光斑效果。",
+                "类型": "构图",
+                "形式ID": "形式4"
+              },
+              "笔触": {
+                "名称": "笔触",
+                "描述": "笔触明显,可见颜料堆叠和涂抹的痕迹,呈现出粗犷而富有表现力的特点。",
+                "类型": "笔触",
+                "形式ID": "形式29"
+              },
+              "内容主题": {
+                "名称": "内容主题",
+                "描述": "描绘了一名身穿白色裙子的女性在户外草地上的背影,周围是绿色的植物和花朵,暗示着户外绘画或休闲场景。",
+                "类型": "内容",
+                "形式ID": "形式26"
+              },
+              "评分详情": {
+                "combined_score": 0.892
+              }
+            },
+            "评分详情": {
+              "combined_score": 0.895
+            }
+          }
+        ],
+        "评分详情": {
+          "combined_score": 0.511
+        }
+      },
+      {
+        "名称": "背景",
+        "描述": "远处的绿色树木和草地,有阳光透过树叶。",
+        "段落ID": "段落2.3",
+        "形式": {
+          "颜色": {
+            "名称": "颜色",
+            "描述": "以绿色为主,包括深绿色、浅绿色和黄绿色,天空部分呈现淡黄色和白色,整体色彩清新明亮。",
+            "类型": "色彩",
+            "形式ID": "形式3"
+          },
+          "清晰度": {
+            "名称": "清晰度",
+            "描述": "背景整体呈现柔和的虚化效果,景深较浅,树木轮廓模糊,光斑明显。",
+            "类型": "清晰度",
+            "形式ID": "形式1"
+          },
+          "光照": {
+            "名称": "光照",
+            "描述": "逆光,阳光从画面左上方透过树叶,形成大量圆形和不规则形状的光斑和光晕,使背景呈现出明亮而梦幻的效果。",
+            "类型": "光影",
+            "形式ID": "形式2"
+          },
+          "景深": {
+            "名称": "景深",
+            "描述": "景深较浅,背景的树木和草地被虚化,与前景的人物和画架形成对比,突出主体。",
+            "类型": "空间",
+            "形式ID": "形式9"
+          },
+          "评分详情": {
+            "combined_score": 0.695
+          }
+        },
+        "评分详情": {
+          "combined_score": 0.703
+        }
+      }
+    ],
+    "评分详情": {
+      "combined_score": 0.756
+    }
+  }
+]

+ 283 - 0
examples/requirement_extract/huahua/descriptions/写生油画__img_3_制作表.json

@@ -0,0 +1,283 @@
+[
+  {
+    "名称": "户外绘画场景",
+    "描述": "一名女性在户外草地上跪坐,使用画架和调色板进行绘画,背景是绿色的树木和远处的建筑。",
+    "段落ID": "段落3",
+    "形式": {
+      "拍摄角度": {
+        "名称": "拍摄角度",
+        "描述": "从人物背部略偏右侧的低角度拍摄,视线略高于人物头部,呈现出人物、画架和背景的广阔视野。",
+        "类型": "视角",
+        "形式ID": "形式5"
+      },
+      "景别": {
+        "名称": "景别",
+        "描述": "中景偏全景,画面包含了人物的全身(从头顶到膝盖以下部分),画架的完整结构,以及远处的背景,强调了人物与环境的互动。",
+        "类型": "构图",
+        "形式ID": "形式7"
+      },
+      "光照": {
+        "名称": "光照",
+        "描述": "自然光,光源主要来自画面左上方,呈现出逆光效果。人物和前景草地部分受光较少,略显阴影,背景树木边缘有明显的光晕,整体光线柔和,营造出温暖的氛围。",
+        "类型": "光影",
+        "形式ID": "形式2"
+      },
+      "色彩饱和度": {
+        "名称": "色彩饱和度",
+        "描述": "整体色彩饱和度中等偏高,草地的绿色和树木的绿色鲜明,人物白色服装纯净,画作上的色彩也较为鲜艳,但整体色调和谐,不刺眼。",
+        "类型": "色彩",
+        "形式ID": "形式11"
+      },
+      "清晰度": {
+        "名称": "清晰度",
+        "描述": "前景人物和画架清晰锐利,中景草地清晰,背景树木和远景建筑略有虚化,呈现出景深效果。",
+        "类型": "清晰度",
+        "形式ID": "形式1"
+      },
+      "构图": {
+        "名称": "构图",
+        "描述": "采用开放式构图,人物位于画面右侧偏中,画架位于画面中央偏左,两者形成对角线构图。背景广阔,画面元素分布均衡,引导视线从人物到画架再到背景。",
+        "类型": "构图",
+        "形式ID": "形式4"
+      },
+      "评分详情": {
+        "combined_score": 0.733
+      }
+    },
+    "子段落": [
+      {
+        "名称": "人物",
+        "描述": "一名女性,背对镜头,跪坐在草地上。",
+        "段落ID": "段落3.1",
+        "形式": {
+          "拍摄角度": {
+            "名称": "拍摄角度",
+            "描述": "从人物背部略偏右侧的低角度拍摄,视线略高于人物头部,呈现出人物的背影和侧面。",
+            "类型": "视角",
+            "形式ID": "形式5"
+          },
+          "景别": {
+            "名称": "景别",
+            "描述": "中景,画面包含了人物的全身(从头顶到膝盖以下部分),强调了人物的姿态和服装细节。",
+            "类型": "构图",
+            "形式ID": "形式7"
+          },
+          "光照": {
+            "名称": "光照",
+            "描述": "自然光,光源主要来自人物左前方,人物背部和右侧受光较少,处于阴影中,左侧手臂和部分头发有少量高光,整体光线柔和。",
+            "类型": "光影",
+            "形式ID": "形式2"
+          },
+          "色彩饱和度": {
+            "名称": "色彩饱和度",
+            "描述": "人物服装为纯白色,头发为棕色,色彩饱和度适中,与周围环境色彩形成对比。",
+            "类型": "色彩",
+            "形式ID": "形式11"
+          },
+          "清晰度": {
+            "名称": "清晰度",
+            "描述": "人物主体清晰锐利,头发丝、服装褶皱等细节清晰可见。",
+            "类型": "清晰度",
+            "形式ID": "形式1"
+          },
+          "构图": {
+            "名称": "构图",
+            "描述": "人物位于画面右侧偏中,占据了画面约三分之二的垂直空间,形成主体突出。",
+            "类型": "构图",
+            "形式ID": "形式4"
+          },
+          "评分详情": {
+            "combined_score": 0.792
+          }
+        },
+        "子段落": [
+          {
+            "名称": "头发",
+            "描述": "棕色长发,披散在背部。",
+            "段落ID": "段落3.1.1",
+            "形式": {
+              "清晰度": {
+                "名称": "清晰度",
+                "描述": "头发丝细节清晰可见,发梢的层次感明显。",
+                "类型": "清晰度",
+                "形式ID": "形式1"
+              },
+              "评分详情": {
+                "combined_score": 0.413
+              }
+            },
+            "评分详情": {
+              "combined_score": 0.434
+            }
+          },
+          {
+            "名称": "身体",
+            "描述": "女性的躯干和手臂。",
+            "段落ID": "段落3.1.2",
+            "形式": {
+              "姿态": {
+                "名称": "姿态",
+                "描述": "女性跪坐在草地上,身体略微前倾,头部转向画架方向,左手自然放置在调色板旁,右手可能正在作画(未完全显示)。",
+                "类型": "动作",
+                "形式ID": "形式8"
+              },
+              "评分详情": {
+                "combined_score": 0.497
+              }
+            },
+            "子段落": [
+              {
+                "名称": "服装",
+                "描述": "白色长裙,露背设计。",
+                "段落ID": "段落3.1.2.1",
+                "形式": {
+                  "服装颜色": {
+                    "名称": "服装颜色",
+                    "描述": "纯白色,无其他杂色,呈现出干净、明亮的视觉效果。",
+                    "类型": "色彩",
+                    "形式ID": "形式16"
+                  },
+                  "服装款式": {
+                    "名称": "服装款式",
+                    "描述": "长袖连衣裙,V字露背设计,背部有白色细绳交叉系带,腰部有系带收腰,裙摆宽松,自然垂坠,长度及地。",
+                    "类型": "形态",
+                    "形式ID": "形式14"
+                  },
+                  "材质": {
+                    "名称": "材质",
+                    "描述": "目测为轻薄、柔软的棉麻或雪纺材质,具有良好的垂坠感和透气性。",
+                    "类型": "质感",
+                    "形式ID": "形式10"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.834
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.84
+                }
+              },
+              {
+                "名称": "调色板",
+                "描述": "女性左手旁放置的椭圆形调色板,上面有多种颜料。",
+                "段落ID": "段落3.1.2.2",
+                "形式": {
+                  "评分详情": {
+                    "combined_score": 0.415
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.479
+                }
+              }
+            ],
+            "评分详情": {
+              "combined_score": 0.525
+            }
+          }
+        ],
+        "评分详情": {
+          "combined_score": 0.826
+        }
+      },
+      {
+        "名称": "画架",
+        "描述": "木质三脚画架,支撑着画布。",
+        "段落ID": "段落3.2",
+        "形式": {
+          "结构": {
+            "名称": "结构",
+            "描述": "三脚架结构,由三根木杆支撑,顶部有可调节的画板支撑杆和固定装置,整体结构稳固。",
+            "类型": "结构",
+            "形式ID": "形式18"
+          },
+          "评分详情": {
+            "combined_score": 0.551
+          }
+        },
+        "子段落": [
+          {
+            "名称": "画布",
+            "描述": "画架上的一幅未完成的画作,描绘了一名背对镜头的女性。",
+            "段落ID": "段落3.2.1",
+            "形式": {
+              "绘画风格": {
+                "名称": "绘画风格",
+                "描述": "印象派风格,笔触粗犷,色彩鲜明,注重光影和氛围的表达,而非精确的细节描绘。",
+                "类型": "风格",
+                "形式ID": "形式27"
+              },
+              "色彩": {
+                "名称": "色彩",
+                "描述": "以绿色和蓝色为主色调,描绘了草地和花丛,人物服装为白色,色彩对比鲜明,整体色调明亮。",
+                "类型": "色彩",
+                "形式ID": "形式28"
+              },
+              "构图": {
+                "名称": "构图",
+                "描述": "画作中央偏右描绘了一名背对镜头的女性,周围是绿色的草地和蓝紫色的花丛,背景有白色遮阳伞,形成开放式构图。",
+                "类型": "构图",
+                "形式ID": "形式4"
+              },
+              "笔触": {
+                "名称": "笔触",
+                "描述": "笔触粗犷有力,颜料堆叠感明显,尤其在花丛和草地的描绘上,呈现出明显的纹理。",
+                "类型": "笔触",
+                "形式ID": "形式29"
+              },
+              "内容主题": {
+                "名称": "内容主题",
+                "描述": "描绘了一名身穿白色连衣裙的女性在户外花丛中撑伞的场景,与现实场景中的女性形象相似,形成画中画的意境。",
+                "类型": "内容",
+                "形式ID": "形式26"
+              },
+              "评分详情": {
+                "combined_score": 0.874
+              }
+            },
+            "评分详情": {
+              "combined_score": 0.891
+            }
+          }
+        ],
+        "评分详情": {
+          "combined_score": 0.584
+        }
+      },
+      {
+        "名称": "背景",
+        "描述": "远处的绿色树木和草地,以及远处的城市建筑。",
+        "段落ID": "段落3.3",
+        "形式": {
+          "颜色": {
+            "名称": "颜色",
+            "描述": "以绿色为主,包括前景草地的鲜绿色、中景树木的深绿色和远景树木的浅绿色,远处建筑为浅灰色,天空为淡黄色,整体色彩清新自然。",
+            "类型": "色彩",
+            "形式ID": "形式3"
+          },
+          "光照": {
+            "名称": "光照",
+            "描述": "背景光线充足,尤其在画面左上方,阳光穿透树叶形成光斑和光晕,营造出温暖、明亮的氛围。",
+            "类型": "光影",
+            "形式ID": "形式2"
+          },
+          "景深": {
+            "名称": "景深",
+            "描述": "景深较浅,前景人物和画架清晰,背景树木和建筑逐渐虚化,层次感明显。",
+            "类型": "空间",
+            "形式ID": "形式9"
+          },
+          "评分详情": {
+            "combined_score": 0.558
+          }
+        },
+        "评分详情": {
+          "combined_score": 0.633
+        }
+      }
+    ],
+    "评分详情": {
+      "combined_score": 0.755
+    }
+  }
+]

+ 319 - 0
examples/requirement_extract/huahua/descriptions/写生油画__img_4_制作表.json

@@ -0,0 +1,319 @@
+[
+  {
+    "名称": "户外绘画场景",
+    "描述": "一名女性在户外草地上站立,使用画架和调色板进行绘画,背景是绿色的树木。",
+    "段落ID": "段落4",
+    "形式": {
+      "拍摄角度": {
+        "名称": "拍摄角度",
+        "描述": "平视角度,相机与人物大致处于同一水平线,略微仰视,使得人物和画架的顶部略高于画面中心,背景的树木占据画面上半部分。",
+        "类型": "视角",
+        "形式ID": "形式5"
+      },
+      "景别": {
+        "名称": "景别",
+        "描述": "中景,画面中人物从膝盖以上到头部完整呈现,画架也完整呈现,背景的树木和草地占据画面大部分,强调人物与环境的互动。",
+        "类型": "构图",
+        "形式ID": "形式7"
+      },
+      "光照": {
+        "名称": "光照",
+        "描述": "自然光照,光线明亮,从画面右上方射入,在人物和画架上形成清晰的亮部和柔和的阴影,整体光线均匀,无明显过曝或欠曝区域。",
+        "类型": "光影",
+        "形式ID": "形式2"
+      },
+      "色彩饱和度": {
+        "名称": "色彩饱和度",
+        "描述": "中等偏高饱和度,绿色草地和树木的色彩鲜艳,人物白色服装和肤色自然,调色板上的颜料色彩丰富且饱和度高,整体画面色彩生动。",
+        "类型": "色彩",
+        "形式ID": "形式11"
+      },
+      "清晰度": {
+        "名称": "清晰度",
+        "描述": "高清晰度,画面主体人物和画架细节清晰锐利,背景树木和草地有轻微虚化,但仍能辨认出其形态,整体画面清晰度良好。",
+        "类型": "清晰度",
+        "形式ID": "形式1"
+      },
+      "构图": {
+        "名称": "构图",
+        "描述": "采用开放式构图,人物位于画面右侧,画架位于画面左侧,两者形成对角线构图,引导视线从左下方的画架到右上方的人物,背景的树木和草地延伸至画面边缘,营造出开阔感。人物头部位于画面上方1/4处,画架顶部位于画面上方1/8处。",
+        "类型": "构图",
+        "形式ID": "形式4"
+      },
+      "评分详情": {
+        "combined_score": 0.858
+      }
+    },
+    "子段落": [
+      {
+        "名称": "人物",
+        "描述": "一名女性,侧身面对镜头,正在进行绘画。",
+        "段落ID": "段落4.1",
+        "形式": {
+          "拍摄角度": {
+            "名称": "拍摄角度",
+            "描述": "平视角度,相机与人物大致处于同一水平线,略微仰视,人物头部略高于画面中心。",
+            "类型": "视角",
+            "形式ID": "形式5"
+          },
+          "景别": {
+            "名称": "景别",
+            "描述": "中景,人物从膝盖以上到头部完整呈现,占据画面右侧大部分区域,强调人物的姿态和动作。",
+            "类型": "构图",
+            "形式ID": "形式7"
+          },
+          "光照": {
+            "名称": "光照",
+            "描述": "自然光照,光线明亮,从画面右上方射入,在人物右侧形成亮部,左侧形成柔和阴影,面部光线均匀,无明显过曝或欠曝。",
+            "类型": "光影",
+            "形式ID": "形式2"
+          },
+          "色彩饱和度": {
+            "名称": "色彩饱和度",
+            "描述": "中等偏高饱和度,肤色自然,头发棕色饱和度适中,白色服装色彩纯净,调色板上的颜料色彩鲜艳,整体色彩和谐。",
+            "类型": "色彩",
+            "形式ID": "形式11"
+          },
+          "清晰度": {
+            "名称": "清晰度",
+            "描述": "高清晰度,人物面部、头发、服装和手部细节清晰锐利,无模糊现象。",
+            "类型": "清晰度",
+            "形式ID": "形式1"
+          },
+          "构图": {
+            "名称": "构图",
+            "描述": "人物位于画面右侧,身体略微向左倾斜,形成对角线构图,头部位于画面上方1/4处,视线向左上方延伸,与画架形成互动。",
+            "类型": "构图",
+            "形式ID": "形式4"
+          },
+          "评分详情": {
+            "combined_score": 0.788
+          }
+        },
+        "子段落": [
+          {
+            "名称": "头发",
+            "描述": "棕色长发,部分散落在肩上。",
+            "段落ID": "段落4.1.1",
+            "形式": {
+              "评分详情": {
+                "combined_score": 0.301
+              }
+            },
+            "评分详情": {
+              "combined_score": 0.315
+            }
+          },
+          {
+            "名称": "身体",
+            "描述": "女性的躯干和手臂。",
+            "段落ID": "段落4.1.2",
+            "形式": {
+              "姿态": {
+                "名称": "姿态",
+                "描述": "站立姿态,身体略微侧向左前方,头部向左上方仰望,右手持画笔,左手持调色板,双臂自然抬起,呈绘画动作。",
+                "类型": "动作",
+                "形式ID": "形式8"
+              },
+              "肤色": {
+                "名称": "肤色",
+                "描述": "健康白皙的肤色,在光照下呈现自然光泽,面部和手臂肤色均匀。",
+                "类型": "色彩",
+                "形式ID": "形式12"
+              },
+              "清晰度": {
+                "名称": "清晰度",
+                "描述": "高清晰度,躯干和手臂的轮廓、服装褶皱、手部细节清晰可见。",
+                "类型": "清晰度",
+                "形式ID": "形式1"
+              },
+              "光照": {
+                "名称": "光照",
+                "描述": "自然光照,光线从右上方射入,在右臂和身体右侧形成亮部,左臂和身体左侧形成柔和阴影,光影过渡自然。",
+                "类型": "光影",
+                "形式ID": "形式2"
+              },
+              "评分详情": {
+                "combined_score": 0.765
+              }
+            },
+            "子段落": [
+              {
+                "名称": "服装",
+                "描述": "白色长裙,袖子宽松。",
+                "段落ID": "段落4.1.2.1",
+                "形式": {
+                  "服装颜色": {
+                    "名称": "服装颜色",
+                    "描述": "纯白色,无图案或装饰,色彩纯净明亮。",
+                    "类型": "色彩",
+                    "形式ID": "形式16"
+                  },
+                  "服装款式": {
+                    "名称": "服装款式",
+                    "描述": "长袖连衣裙,圆领,袖子宽松,裙摆宽松垂坠,长度及脚踝,腰部有轻微收腰设计。",
+                    "类型": "形态",
+                    "形式ID": "形式14"
+                  },
+                  "材质": {
+                    "名称": "材质",
+                    "描述": "目测为棉麻或雪纺等轻薄透气的面料,具有柔软垂坠感。",
+                    "类型": "质感",
+                    "形式ID": "形式10"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.806
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.81
+                }
+              },
+              {
+                "名称": "画笔",
+                "描述": "女性右手握持的细长画笔。",
+                "段落ID": "段落4.1.2.2",
+                "形式": {
+                  "形状": {
+                    "名称": "形状",
+                    "描述": "细长杆状,笔杆为深色,笔头为绿色颜料。",
+                    "类型": "形状",
+                    "形式ID": "形式17"
+                  },
+                  "材质": {
+                    "名称": "材质",
+                    "描述": "笔杆目测为木质或塑料,笔头为合成纤维或动物毛。",
+                    "类型": "质感",
+                    "形式ID": "形式10"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.455
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.525
+                }
+              },
+              {
+                "名称": "调色板",
+                "描述": "女性左手握持的椭圆形调色板,上面有多种颜料。",
+                "段落ID": "段落4.1.2.3",
+                "形式": {
+                  "形状": {
+                    "名称": "形状",
+                    "描述": "椭圆形,边缘光滑,中间略微凹陷。",
+                    "类型": "形状",
+                    "形式ID": "形式17"
+                  },
+                  "颜色": {
+                    "名称": "颜色",
+                    "描述": "调色板主体为深棕色或黑色,表面沾有多种鲜艳颜料,包括绿色、蓝色、红色、黄色、白色等,颜料分布不规则。",
+                    "类型": "色彩",
+                    "形式ID": "形式3"
+                  },
+                  "材质": {
+                    "名称": "材质",
+                    "描述": "目测为木质或塑料材质,表面光滑。",
+                    "类型": "质感",
+                    "形式ID": "形式10"
+                  },
+                  "颜料分布": {
+                    "名称": "颜料分布",
+                    "描述": "颜料呈不规则块状分布在调色板表面,主要集中在调色板的左侧和下方,绿色颜料面积最大,位于调色板中央偏左位置,其他颜料点缀其间。",
+                    "类型": "布局",
+                    "形式ID": "形式25"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.628
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.653
+                }
+              }
+            ],
+            "评分详情": {
+              "combined_score": 0.773
+            }
+          }
+        ],
+        "评分详情": {
+          "combined_score": 0.764
+        }
+      },
+      {
+        "名称": "画架",
+        "描述": "木质三脚画架,支撑着画布。",
+        "段落ID": "段落4.2",
+        "形式": {
+          "结构": {
+            "名称": "结构",
+            "描述": "三脚架结构,由三根木杆支撑,顶部有可调节的画板支撑架,底部有横向支撑杆,整体结构稳固。",
+            "类型": "结构",
+            "形式ID": "形式18"
+          },
+          "评分详情": {
+            "combined_score": 0.547
+          }
+        },
+        "子段落": [
+          {
+            "名称": "画布",
+            "描述": "画架上的一幅空白画布。",
+            "段落ID": "段落4.2.1",
+            "形式": {
+              "画布颜色": {
+                "名称": "画布颜色",
+                "描述": "纯白色,表面干净,无任何颜料痕迹。",
+                "类型": "色彩"
+              },
+              "光照": {
+                "名称": "光照",
+                "描述": "自然光照,光线从右上方射入,画布表面受光均匀,无明显阴影,呈现纯白色。",
+                "类型": "光影",
+                "形式ID": "形式2"
+              },
+              "评分详情": {
+                "combined_score": 0.606
+              }
+            },
+            "评分详情": {
+              "combined_score": 0.79
+            }
+          }
+        ],
+        "评分详情": {
+          "combined_score": 0.608
+        }
+      },
+      {
+        "名称": "背景",
+        "描述": "远处的绿色树木和草地。",
+        "段落ID": "段落4.3",
+        "形式": {
+          "颜色": {
+            "名称": "颜色",
+            "描述": "绿色为主,草地为鲜绿色,树木为深浅不一的绿色,远处有少量棕色树干和灰色建筑,整体色调清新自然。",
+            "类型": "色彩",
+            "形式ID": "形式3"
+          },
+          "景深": {
+            "名称": "景深",
+            "描述": "景深较浅,前景人物和画架清晰,背景草地和树木逐渐虚化,营造出空间感和层次感。",
+            "类型": "空间",
+            "形式ID": "形式9"
+          },
+          "评分详情": {
+            "combined_score": 0.434
+          }
+        },
+        "评分详情": {
+          "combined_score": 0.542
+        }
+      }
+    ],
+    "评分详情": {
+      "combined_score": 0.845
+    }
+  }
+]

+ 389 - 0
examples/requirement_extract/huahua/descriptions/写生油画__img_5_制作表.json

@@ -0,0 +1,389 @@
+[
+  {
+    "名称": "户外绘画场景",
+    "描述": "画面展示了户外绘画的局部场景,主要聚焦于人物手持调色板和部分身体,以及背景的草地和画架。",
+    "段落ID": "段落5",
+    "形式": {
+      "拍摄角度": {
+        "名称": "拍摄角度",
+        "描述": "从人物胸部以上,略微俯视的角度拍摄,画面中心偏右是人物手持调色板的区域,左侧可见画架局部,背景是模糊的草地。",
+        "类型": "视角",
+        "形式ID": "形式5"
+      },
+      "景别": {
+        "名称": "景别",
+        "描述": "中景偏近景,主要聚焦于人物的上半身(胸部以上)和手持的调色板,占据画面约80%的区域,背景草地虚化。",
+        "类型": "构图",
+        "形式ID": "形式7"
+      },
+      "光照": {
+        "名称": "光照",
+        "描述": "自然光照,光线充足且柔和,从画面右上方照射,使得人物右侧手臂和调色板右侧受光较亮,左侧略有阴影,整体画面亮度适中,无明显过曝或欠曝区域。",
+        "类型": "光影",
+        "形式ID": "形式2"
+      },
+      "色彩饱和度": {
+        "名称": "色彩饱和度",
+        "描述": "色彩饱和度较高,尤其是调色板上的颜料和背景草地的绿色,色彩鲜明且富有活力。",
+        "类型": "色彩",
+        "形式ID": "形式11"
+      },
+      "清晰度": {
+        "名称": "清晰度",
+        "描述": "画面中心区域(人物手臂、调色板、画笔)清晰锐利,细节可见;背景草地和画架边缘部分虚化,呈现景深效果。",
+        "类型": "清晰度",
+        "形式ID": "形式1"
+      },
+      "构图": {
+        "名称": "构图",
+        "描述": "采用开放式构图,人物和调色板占据画面主体,调色板位于画面右下角至中心区域,人物左臂从画面左上角延伸,右臂从画面右侧延伸,画架位于画面左下角,背景草地作为衬托,引导视线集中于绘画活动。",
+        "类型": "构图",
+        "形式ID": "形式4"
+      },
+      "评分详情": {
+        "combined_score": 0.85
+      }
+    },
+    "子段落": [
+      {
+        "名称": "人物",
+        "描述": "画面中部的女性,穿着白色服装,正在进行绘画活动。",
+        "段落ID": "段落5.1",
+        "形式": {
+          "拍摄角度": {
+            "名称": "拍摄角度",
+            "描述": "从人物胸部以上,略微俯视的角度拍摄,主要展现人物进行绘画时的上半身姿态。",
+            "类型": "视角",
+            "形式ID": "形式5"
+          },
+          "景别": {
+            "名称": "景别",
+            "描述": "中景偏近景,聚焦于人物的上半身(胸部以上),占据画面约80%的区域,突出人物的绘画动作。",
+            "类型": "构图",
+            "形式ID": "形式7"
+          },
+          "光照": {
+            "名称": "光照",
+            "描述": "自然光照,光线充足且柔和,从画面右上方照射,使得人物右侧身体和手臂受光较亮,左侧略有阴影,整体亮度适中。",
+            "类型": "光影",
+            "形式ID": "形式2"
+          },
+          "色彩饱和度": {
+            "名称": "色彩饱和度",
+            "描述": "人物服装为白色,肤色自然,色彩饱和度适中,与背景的鲜艳绿色形成对比。",
+            "类型": "色彩",
+            "形式ID": "形式11"
+          },
+          "清晰度": {
+            "名称": "清晰度",
+            "描述": "人物主体清晰锐利,服装纹理和手臂细节可见,与虚化的背景形成对比。",
+            "类型": "清晰度",
+            "形式ID": "形式1"
+          },
+          "构图": {
+            "名称": "构图",
+            "描述": "人物身体呈对角线构图,左臂从画面左上角延伸,右臂从画面右侧延伸,调色板位于画面右下角至中心区域,形成视觉引导。",
+            "类型": "构图",
+            "形式ID": "形式4"
+          },
+          "评分详情": {
+            "combined_score": 0.823
+          }
+        },
+        "子段落": [
+          {
+            "名称": "手臂",
+            "描述": "人物露出的手臂部分,包括左臂和右臂。",
+            "段落ID": "段落5.1.1",
+            "形式": {
+              "评分详情": {
+                "combined_score": 0.28
+              }
+            },
+            "子段落": [
+              {
+                "名称": "左臂",
+                "描述": "人物左侧手臂,部分可见,手持画笔。",
+                "段落ID": "段落5.1.1.1",
+                "形式": {
+                  "清晰度": {
+                    "名称": "清晰度",
+                    "描述": "左臂皮肤纹理、指甲、画笔等细节清晰可见,无模糊现象。",
+                    "类型": "清晰度",
+                    "形式ID": "形式1"
+                  },
+                  "光照": {
+                    "名称": "光照",
+                    "描述": "自然光照,左臂受光均匀,无明显阴影或反光。",
+                    "类型": "光影",
+                    "形式ID": "形式2"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.196
+                  }
+                },
+                "子段落": [
+                  {
+                    "名称": "画笔",
+                    "描述": "人物左手持有的细长画笔。",
+                    "段落ID": "段落5.1.1.1.1",
+                    "形式": {
+                      "形状": {
+                        "名称": "形状",
+                        "描述": "画笔呈细长圆柱形,笔杆笔直,笔尖略尖。",
+                        "类型": "形状",
+                        "形式ID": "形式17"
+                      },
+                      "颜色": {
+                        "名称": "颜色",
+                        "描述": "画笔笔杆为深色(可能为黑色或深棕色),笔尖部分沾有少量绿色颜料。",
+                        "类型": "色彩",
+                        "形式ID": "形式3"
+                      },
+                      "尺寸": {
+                        "名称": "尺寸",
+                        "描述": "画笔长度约为150像素,宽度约为10像素,相对于人物手指显得细长。",
+                        "类型": "大小",
+                        "形式ID": "形式19"
+                      },
+                      "清晰度": {
+                        "名称": "清晰度",
+                        "描述": "画笔轮廓清晰,笔尖细节可见,无模糊现象。",
+                        "类型": "清晰度",
+                        "形式ID": "形式1"
+                      },
+                      "评分详情": {
+                        "combined_score": 0.224
+                      }
+                    },
+                    "评分详情": {
+                      "combined_score": 0.245
+                    }
+                  }
+                ],
+                "评分详情": {
+                  "combined_score": 0.21
+                }
+              },
+              {
+                "名称": "右臂",
+                "描述": "人物右侧手臂,手持调色板,佩戴手镯。",
+                "段落ID": "段落5.1.1.2",
+                "形式": {
+                  "清晰度": {
+                    "名称": "清晰度",
+                    "描述": "右臂皮肤纹理、指甲、手镯等细节清晰可见,无模糊现象。",
+                    "类型": "清晰度",
+                    "形式ID": "形式1"
+                  },
+                  "光照": {
+                    "名称": "光照",
+                    "描述": "自然光照,右臂受光面较亮,光影过渡自然,手镯表面有反光点。",
+                    "类型": "光影",
+                    "形式ID": "形式2"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.266
+                  }
+                },
+                "子段落": [
+                  {
+                    "名称": "手镯",
+                    "描述": "佩戴在右腕上的银色手镯。",
+                    "段落ID": "段落5.1.1.2.1",
+                    "形式": {
+                      "形状": {
+                        "名称": "形状",
+                        "描述": "手镯呈圆形环状,边缘光滑。",
+                        "类型": "形状",
+                        "形式ID": "形式17"
+                      },
+                      "颜色": {
+                        "名称": "颜色",
+                        "描述": "手镯为银色,表面有金属光泽。",
+                        "类型": "色彩",
+                        "形式ID": "形式3"
+                      },
+                      "清晰度": {
+                        "名称": "清晰度",
+                        "描述": "手镯轮廓清晰,表面光泽细节可见,无模糊现象。",
+                        "类型": "清晰度",
+                        "形式ID": "形式1"
+                      },
+                      "评分详情": {
+                        "combined_score": 0.098
+                      }
+                    },
+                    "评分详情": {
+                      "combined_score": 0.105
+                    }
+                  }
+                ],
+                "评分详情": {
+                  "combined_score": 0.28
+                }
+              }
+            ],
+            "评分详情": {
+              "combined_score": 0.315
+            }
+          },
+          {
+            "名称": "服装",
+            "描述": "人物穿着的白色长袖衬衫和裙子。",
+            "段落ID": "段落5.1.2",
+            "形式": {
+              "服装颜色": {
+                "名称": "服装颜色",
+                "描述": "服装主体为纯白色,无其他图案或颜色点缀。",
+                "类型": "色彩",
+                "形式ID": "形式16"
+              },
+              "服装款式": {
+                "名称": "服装款式",
+                "描述": "长袖衬衫和裙子,衬衫袖口有纽扣,衣身宽松,腰部有收腰设计,裙子部分为长裙,整体风格简约。",
+                "类型": "形态",
+                "形式ID": "形式14"
+              },
+              "清晰度": {
+                "名称": "清晰度",
+                "描述": "服装的材质纹理和褶皱细节清晰可见,无模糊现象。",
+                "类型": "清晰度",
+                "形式ID": "形式1"
+              },
+              "评分详情": {
+                "combined_score": 0.816
+              }
+            },
+            "评分详情": {
+              "combined_score": 0.818
+            }
+          },
+          {
+            "名称": "调色板",
+            "描述": "人物右手持有的椭圆形调色板,上面沾满了各种颜色的颜料。",
+            "段落ID": "段落5.1.3",
+            "形式": {
+              "形状": {
+                "名称": "形状",
+                "描述": "调色板呈不规则的椭圆形,边缘圆润,中间有一个拇指孔,方便手持。",
+                "类型": "形状",
+                "形式ID": "形式17"
+              },
+              "颜色": {
+                "名称": "颜色",
+                "描述": "调色板底色为深棕色,表面覆盖着大量混合的颜料,主要颜色包括深绿色、浅绿色、蓝色、红色、黄色、白色、紫色、黑色等多种鲜艳色彩。",
+                "类型": "色彩",
+                "形式ID": "形式3"
+              },
+              "尺寸": {
+                "名称": "尺寸",
+                "描述": "调色板占据画面右下角至中心区域,其宽度约为画面宽度的75%(900像素),高度约为画面高度的37.5%(600像素),尺寸较大,方便调色。",
+                "类型": "大小",
+                "形式ID": "形式19"
+              },
+              "颜料分布": {
+                "名称": "颜料分布",
+                "描述": "颜料呈不规则块状和条状分布在调色板表面,部分颜料相互混合,形成过渡色,没有明显的区域划分,呈现出使用过的痕迹。",
+                "类型": "布局",
+                "形式ID": "形式25"
+              },
+              "评分详情": {
+                "combined_score": 0.646
+              }
+            },
+            "子段落": [
+              {
+                "名称": "颜料",
+                "描述": "调色板上混合的多种颜色的颜料,包括绿色、蓝色、红色等。",
+                "段落ID": "段落5.1.3.1",
+                "形式": {
+                  "颜色种类": {
+                    "名称": "颜色种类",
+                    "描述": "颜料种类丰富,包括深绿色、浅绿色、蓝色、红色、黄色、白色、紫色、黑色、粉色、棕色等至少10种颜色,部分颜色相互混合。",
+                    "类型": "色彩"
+                  },
+                  "颜料质地": {
+                    "名称": "颜料质地",
+                    "描述": "颜料呈膏状,堆积在调色板表面,具有一定的厚度和立体感,表面有光泽,显示出湿润的质地。",
+                    "类型": "质感"
+                  },
+                  "分布模式": {
+                    "名称": "分布模式",
+                    "描述": "颜料呈不规则的块状和条状分布,没有严格的区域划分,部分颜料相互融合,形成自然的混色效果,其中绿色颜料占据了调色板中心区域的大部分。",
+                    "类型": "布局"
+                  },
+                  "清晰度": {
+                    "名称": "清晰度",
+                    "描述": "颜料的颜色、质地和混合细节清晰可见,无模糊现象。",
+                    "类型": "清晰度",
+                    "形式ID": "形式1"
+                  },
+                  "评分详情": {
+                    "combined_score": 0.743
+                  }
+                },
+                "评分详情": {
+                  "combined_score": 0.754
+                }
+              }
+            ],
+            "评分详情": {
+              "combined_score": 0.659
+            }
+          }
+        ],
+        "评分详情": {
+          "combined_score": 0.858
+        }
+      },
+      {
+        "名称": "画架",
+        "描述": "画面左侧部分可见的木质画架。",
+        "段落ID": "段落5.2",
+        "形式": {
+          "评分详情": {
+            "combined_score": 0.373
+          }
+        },
+        "评分详情": {
+          "combined_score": 0.412
+        }
+      },
+      {
+        "名称": "背景",
+        "描述": "画面后方的绿色草地。",
+        "段落ID": "段落5.3",
+        "形式": {
+          "颜色": {
+            "名称": "颜色",
+            "描述": "背景草地呈现鲜艳的绿色,深浅不一,部分区域略带黄色调,整体色彩饱和度较高。",
+            "类型": "色彩",
+            "形式ID": "形式3"
+          },
+          "光照": {
+            "名称": "光照",
+            "描述": "自然光照,草地受光均匀,无明显阴影或高光区域,整体亮度适中。",
+            "类型": "光影",
+            "形式ID": "形式2"
+          },
+          "景深": {
+            "名称": "景深",
+            "描述": "背景草地处于景深之外,呈现出明显的虚化效果,与前景清晰的人物和调色板形成对比,突出主体。",
+            "类型": "空间",
+            "形式ID": "形式9"
+          },
+          "评分详情": {
+            "combined_score": 0.544
+          }
+        },
+        "评分详情": {
+          "combined_score": 0.569
+        }
+      }
+    ],
+    "评分详情": {
+      "combined_score": 0.965
+    }
+  }
+]

+ 1 - 0
examples/requirement_extract/huahua/descriptions/创作表.md

@@ -0,0 +1 @@
+本段落以图文叙事结合的方式,通过一段富有诗意的文字描述和9张多图组呈现的图片,共同展现了秋日花园的艺术创作过程与情感表达。文字部分以"听闻秋日是倒放的春天"开篇,描绘了创作者心中一座秋日的花园,其中栽种着淡却温暖的花朵,风从远山吹来,阳光热情而秋风微凉,营造出一种宁静而美好的氛围。创作者将这种感受与颜料一起酝酿成画面,并用比喻的手法,将白裙比作"一抹无暇",迎着光线绘画出"限定的浪漫"。图片则展示了艺术创作的场景,包括画架支起在绿草坪上,以及女性在户外进行绘画的瞬间,其中可能运用了逆光拍摄和景深虚化等手法来增强画面的艺术感。文字最后以"再添一笔白 为我画一枝玫瑰的奇遇"收尾,进一步强调了这种充满诗意生活的自然之美。

+ 74 - 0
examples/requirement_extract/huahua/descriptions/制作点.md

@@ -0,0 +1,74 @@
+{
+"元素名称": "女性",
+"元素描述": "穿着白裙的女性,包括其背影、躯干与手臂,有蹲坐姿态。",
+"段落数量": 8,
+"段落列表": [
+"段落1.1.1",
+"段落2.1.1",
+"段落3.1.1",
+"段落4.1.1.1",
+"段落4.1.1.2"
+],
+"综合权重": 84.5
+},
+{
+"元素名称": "绘画工具",
+"元素描述": "包含调色板、画笔和颜料,部分描述涉及手持这些工具的动作,调色板有主体部分。",
+"段落数量": 15,
+"段落列表": [
+"段落1.1.3",
+"段落2.1.3",
+"段落4.1.3",
+"段落4.1.4",
+"段落5.1.1.1",
+"段落5.1.1.2",
+"段落5.2.1.1",
+"段落5.2.1.2"
+],
+"综合权重": 78.5
+},
+{
+"元素名称": "自然背景",
+"元素描述": "由草坪、树木和阳光构成的自然背景,部分描述提及远处建筑。",
+"段落数量": 8,
+"段落列表": [
+"段落1.3",
+"段落2.2",
+"段落3.2",
+"段落4.2.1",
+"段落4.2.2"
+],
+"综合权重": 74.0
+},
+{
+"元素名称": "画架与画布",
+"元素描述": "包含画架结构和其上的画布,画布可以是空白的或正在创作的油画。",
+"段落数量": 8,
+"段落列表": [
+"段落4.1.2.1",
+"段落4.1.2.2",
+"段落5.2.2",
+"段落5.2.3"
+],
+"综合权重": 67.67
+},
+{
+"元素名称": "画架与油画",
+"元素描述": "画架上放置着油画。",
+"段落数量": 4,
+"段落列表": [
+"段落1.1.2",
+"段落2.1.2",
+"段落3.1.2"
+],
+"综合权重": 60.97
+},
+{
+"元素名称": "女性衣物",
+"元素描述": "女性穿着的白色衣物,包括白色上衣。",
+"段落数量": 3,
+"段落列表": [
+"段落5.1.2"
+],
+"综合权重": 44.7
+}

+ 44 - 0
examples/requirement_extract/huahua/descriptions/图片亮点.md

@@ -0,0 +1,44 @@
+以下是图组中,必须要保持高表现力的亮点
+{
+“post_name”: “户外白裙写生少女”,
+
+“聚类结果”: [
+{
+"聚类主题": "优雅的白裙写生少女",
+"聚类描述": "该聚类汇集了画面核心人物的实质特征。身着纯白连衣裙的女性主体,无论是优雅的背影、专注的侧颜,还是发丝与耳饰等精致细节,都共同塑造了一位充满了文艺气质与娴静美的写生缪斯形象。",
+"亮点类型": "实质",
+"图片列表": ["img_1", "img_2", "img_3", "img_4", "img_5"]
+},
+{
+"聚类主题": "斑斓厚重的油画颜料",
+"聚类描述": "该聚类专门聚焦于画面中色彩最丰富、质感最独特的实质物体。木质调色盘上堆积的厚重油画颜料(Impasto),以其杂乱而鲜艳的色彩肌理,与周围大面积纯净的白色衣物形成强烈视觉反差,强调了艺术创作的真实性。",
+"亮点类型": "实质",
+"图片列表": ["img_1", "img_5"]
+},
+{
+
+"聚类主题": "构建叙事的写生道具",
+"聚类描述": "该聚类集合了定义‘户外写生’场景的关键道具。画架、画布、画笔以及作为点缀的白玫瑰,这些物品在空间上组合排列,通过具体的实体展示了人物的活动内容,构建了画面的叙事背景。",
+"亮点类型": "实质",
+"图片列表": ["img_3", "img_4"]
+},
+{
+"聚类主题": "清新雅致的白绿配色",
+"聚类描述": "该聚类强调了画面在色彩构成上的形式美感。大面积的高饱和度自然草木绿背景与人物衣着的纯白形成鲜明对比,确立了清新、自然且具有治愈感的森系视觉基调。",
+"亮点类型": "形式",
+"图片列表": ["img_1", "img_4"]
+},
+{
+"聚类主题": "唯美梦幻的光影与景深",
+"聚类描述": "该聚类整合了营造画面氛围的光学形式手段。摄影师结合了温暖的逆光/轮廓光与大光圈带来的浅景深虚化(Bokeh)效果,使背景呈现出柔和的散景,共同营造出一种脱离现实的梦幻、浪漫且充满空气感的视觉氛围。",
+"亮点类型": "形式",
+"图片列表": ["img_2", "img_3", "img_5"]
+},
+{
+"聚类主题": "虚实呼应的画中画结构",
+"聚类描述": "该聚类归纳了画面中独特的逻辑形式。画布上的内容与现实场景形成"镜像"或"互文"关系,通过现实与艺术创作之间的视觉呼应,构建出一种增加了叙事深度的画中画结构。",
+"亮点类型": "形式",
+"图片列表": ["img_1", "img_2"]
+}
+]
+}

+ 0 - 0
examples/feature_extract/huahua/features/background_asset/background_bokeh_img2.png → examples/requirement_extract/huahua/features/background_asset/background_bokeh_img2.png


+ 0 - 0
examples/feature_extract/huahua/features/background_asset/background_green_img1.png → examples/requirement_extract/huahua/features/background_asset/background_green_img1.png


+ 0 - 0
examples/feature_extract/huahua/features/background_asset/background_green_img4.png → examples/requirement_extract/huahua/features/background_asset/background_green_img4.png


+ 0 - 0
examples/feature_extract/huahua/features/background_asset/mapping.json → examples/requirement_extract/huahua/features/background_asset/mapping.json


+ 0 - 0
examples/feature_extract/huahua/features/character_asset/character_ref_back.png → examples/requirement_extract/huahua/features/character_asset/character_ref_back.png


+ 0 - 0
examples/feature_extract/huahua/features/character_asset/character_ref_img1.png → examples/requirement_extract/huahua/features/character_asset/character_ref_img1.png


+ 0 - 0
examples/feature_extract/huahua/features/character_asset/character_ref_kneel.png → examples/requirement_extract/huahua/features/character_asset/character_ref_kneel.png


+ 0 - 0
examples/feature_extract/huahua/features/character_asset/character_ref_main.png → examples/requirement_extract/huahua/features/character_asset/character_ref_main.png


+ 0 - 0
examples/feature_extract/huahua/features/character_asset/character_ref_side.png → examples/requirement_extract/huahua/features/character_asset/character_ref_side.png


+ 0 - 0
examples/feature_extract/huahua/features/character_asset/mapping.json → examples/requirement_extract/huahua/features/character_asset/mapping.json


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/color_scheme.json → examples/requirement_extract/huahua/features/color_scheme/color_scheme.json


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/color_scheme_complete.json → examples/requirement_extract/huahua/features/color_scheme/color_scheme_complete.json


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/color_scheme_visual.png → examples/requirement_extract/huahua/features/color_scheme/color_scheme_visual.png


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/color_swatch.png → examples/requirement_extract/huahua/features/color_scheme/color_swatch.png


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/img_1_colors.json → examples/requirement_extract/huahua/features/color_scheme/img_1_colors.json


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/img_1_palette.png → examples/requirement_extract/huahua/features/color_scheme/img_1_palette.png


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/img_2_colors.json → examples/requirement_extract/huahua/features/color_scheme/img_2_colors.json


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/img_2_palette.png → examples/requirement_extract/huahua/features/color_scheme/img_2_palette.png


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/img_3_colors.json → examples/requirement_extract/huahua/features/color_scheme/img_3_colors.json


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/img_3_palette.png → examples/requirement_extract/huahua/features/color_scheme/img_3_palette.png


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/img_4_colors.json → examples/requirement_extract/huahua/features/color_scheme/img_4_colors.json


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/img_4_palette.png → examples/requirement_extract/huahua/features/color_scheme/img_4_palette.png


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/img_5_colors.json → examples/requirement_extract/huahua/features/color_scheme/img_5_colors.json


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/img_5_palette.png → examples/requirement_extract/huahua/features/color_scheme/img_5_palette.png


+ 0 - 0
examples/feature_extract/huahua/features/color_scheme/mapping.json → examples/requirement_extract/huahua/features/color_scheme/mapping.json


+ 0 - 0
examples/feature_extract/huahua/features/depth_map/depth_img_1.png → examples/requirement_extract/huahua/features/depth_map/depth_img_1.png


+ 0 - 0
examples/feature_extract/huahua/features/depth_map/depth_img_2.png → examples/requirement_extract/huahua/features/depth_map/depth_img_2.png


+ 0 - 0
examples/feature_extract/huahua/features/depth_map/depth_img_3.png → examples/requirement_extract/huahua/features/depth_map/depth_img_3.png


+ 0 - 0
examples/feature_extract/huahua/features/depth_map/depth_img_4.png → examples/requirement_extract/huahua/features/depth_map/depth_img_4.png


+ 0 - 0
examples/feature_extract/huahua/features/depth_map/depth_img_5.png → examples/requirement_extract/huahua/features/depth_map/depth_img_5.png


+ 0 - 0
examples/feature_extract/huahua/features/depth_map/mapping.json → examples/requirement_extract/huahua/features/depth_map/mapping.json


+ 0 - 0
examples/feature_extract/huahua/features/easel_asset/easel_blank_canvas_img4.png → examples/requirement_extract/huahua/features/easel_asset/easel_blank_canvas_img4.png


+ 0 - 0
examples/feature_extract/huahua/features/easel_asset/mapping.json → examples/requirement_extract/huahua/features/easel_asset/mapping.json


+ 0 - 0
examples/feature_extract/huahua/features/edge_map/img_1_canny.png → examples/requirement_extract/huahua/features/edge_map/img_1_canny.png


+ 0 - 0
examples/feature_extract/huahua/features/edge_map/img_2_canny.png → examples/requirement_extract/huahua/features/edge_map/img_2_canny.png


+ 0 - 0
examples/feature_extract/huahua/features/edge_map/img_3_canny.png → examples/requirement_extract/huahua/features/edge_map/img_3_canny.png


+ 0 - 0
examples/feature_extract/huahua/features/edge_map/img_4_canny.png → examples/requirement_extract/huahua/features/edge_map/img_4_canny.png


+ 0 - 0
examples/feature_extract/huahua/features/edge_map/img_5_canny.png → examples/requirement_extract/huahua/features/edge_map/img_5_canny.png


+ 0 - 0
examples/feature_extract/huahua/features/edge_map/mapping.json → examples/requirement_extract/huahua/features/edge_map/mapping.json


+ 0 - 0
examples/feature_extract/huahua/features/lighting_bokeh/lighting_analysis.json → examples/requirement_extract/huahua/features/lighting_bokeh/lighting_analysis.json


+ 0 - 0
examples/feature_extract/huahua/features/lighting_bokeh/lighting_img_2.json → examples/requirement_extract/huahua/features/lighting_bokeh/lighting_img_2.json


+ 0 - 0
examples/feature_extract/huahua/features/lighting_bokeh/lighting_img_3.json → examples/requirement_extract/huahua/features/lighting_bokeh/lighting_img_3.json


+ 0 - 0
examples/feature_extract/huahua/features/lighting_bokeh/lighting_img_5.json → examples/requirement_extract/huahua/features/lighting_bokeh/lighting_img_5.json


+ 0 - 0
examples/feature_extract/huahua/features/lighting_bokeh/lighting_visual.png → examples/requirement_extract/huahua/features/lighting_bokeh/lighting_visual.png


+ 0 - 0
examples/feature_extract/huahua/features/lighting_bokeh/mapping.json → examples/requirement_extract/huahua/features/lighting_bokeh/mapping.json


Some files were not shown because too many files changed in this diff