elksmmx 8 ساعت پیش
والد
کامیت
a065e3b294
59فایلهای تغییر یافته به همراه7543 افزوده شده و 117 حذف شده
  1. BIN
      examples/find knowledge/input/img_1.png
  2. BIN
      examples/find knowledge/input/img_2.png
  3. BIN
      examples/find knowledge/input/img_3.png
  4. BIN
      examples/find knowledge/input/img_4.png
  5. BIN
      examples/find knowledge/input/img_5.png
  6. BIN
      examples/find knowledge/input/三视图示例.png
  7. 369 0
      examples/find knowledge/input/写生油画__img_1_制作表.json
  8. 338 0
      examples/find knowledge/input/写生油画__img_2_制作表.json
  9. 283 0
      examples/find knowledge/input/写生油画__img_3_制作表.json
  10. 319 0
      examples/find knowledge/input/写生油画__img_4_制作表.json
  11. 389 0
      examples/find knowledge/input/写生油画__img_5_制作表.json
  12. 118 0
      examples/find knowledge/input/写生油画提取需求.md
  13. 388 0
      examples/find knowledge/skills/dimension_research.md
  14. 360 0
      examples/find knowledge/skills/tool_research.md
  15. 659 116
      examples/find knowledge/test.prompt
  16. 753 0
      examples/find knowledge/test.prompt.backup
  17. 2 1
      examples/find knowledge/tool/__init__.py
  18. 141 0
      examples/find knowledge/tool/teacher.py
  19. 70 0
      examples/find knowledge_test/README.md
  20. 0 0
      examples/find knowledge_test/input/img_1.jpg
  21. 0 0
      examples/find knowledge_test/input/img_2.jpg
  22. 0 0
      examples/find knowledge_test/input/img_3.jpg
  23. 0 0
      examples/find knowledge_test/input/img_4.jpg
  24. 0 0
      examples/find knowledge_test/input/img_5.jpg
  25. 0 0
      examples/find knowledge_test/input/img_6.jpg
  26. 0 0
      examples/find knowledge_test/input/img_7.jpg
  27. 0 0
      examples/find knowledge_test/input/img_8.jpg
  28. 0 0
      examples/find knowledge_test/input/img_9.jpg
  29. 0 0
      examples/find knowledge_test/input/写生油画__img_1_合并评分.json
  30. 0 0
      examples/find knowledge_test/input/写生油画__img_2_合并评分.json
  31. 0 0
      examples/find knowledge_test/input/写生油画__img_3_合并评分.json
  32. 0 0
      examples/find knowledge_test/input/写生油画__img_4_合并评分.json
  33. 0 0
      examples/find knowledge_test/input/写生油画__img_5_合并评分.json
  34. 0 0
      examples/find knowledge_test/input/写生油画__img_6_合并评分.json
  35. 0 0
      examples/find knowledge_test/input/写生油画__img_7_合并评分.json
  36. 0 0
      examples/find knowledge_test/input/写生油画__img_8_合并评分.json
  37. 0 0
      examples/find knowledge_test/input/写生油画__img_9_合并评分.json
  38. 0 0
      examples/find knowledge_test/input/写生油画__post_highlight_简化版.json
  39. 0 0
      examples/find knowledge_test/input/写生油画_图片制作点实质结果.json
  40. 422 0
      examples/find knowledge_test/run.py
  41. 264 0
      examples/find knowledge_test/skills/dimension_research.md
  42. 201 0
      examples/find knowledge_test/skills/tool_research.md
  43. 276 0
      examples/find knowledge_test/test.prompt
  44. 7 0
      examples/find knowledge_test/tool/__init__.py
  45. 487 0
      examples/find knowledge_test/tool/nanobanana.py
  46. 30 0
      examples/test_browser/README.md
  47. 85 0
      examples/test_browser/SOLUTION.md
  48. 136 0
      examples/test_browser/diagnose_cdp.py
  49. 52 0
      examples/test_browser/simple_cdp_test.sh
  50. 98 0
      examples/test_browser/test_browser.py
  51. 80 0
      examples/test_browser/test_browser_fixed.py
  52. 158 0
      examples/test_browser/test_browser_v2.py
  53. 143 0
      examples/test_browser/test_httpx_vs_curl.py
  54. 66 0
      examples/test_browser/test_search_posts.py
  55. 82 0
      examples/test_nanobanana/README.md
  56. 263 0
      examples/test_nanobanana/run.py
  57. 9 0
      examples/test_nanobanana/test.prompt
  58. 7 0
      examples/test_nanobanana/tool/__init__.py
  59. 488 0
      examples/test_nanobanana/tool/nanobanana.py

BIN
examples/find knowledge/input/img_1.png


BIN
examples/find knowledge/input/img_2.png


BIN
examples/find knowledge/input/img_3.png


BIN
examples/find knowledge/input/img_4.png


BIN
examples/find knowledge/input/img_5.png


BIN
examples/find knowledge/input/三视图示例.png


+ 369 - 0
examples/find knowledge/input/写生油画__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/find knowledge/input/写生油画__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/find knowledge/input/写生油画__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/find knowledge/input/写生油画__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/find knowledge/input/写生油画__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
+    }
+  }
+]

+ 118 - 0
examples/find knowledge/input/写生油画提取需求.md

@@ -0,0 +1,118 @@
+以下是图组中,必须要保持高表现力的亮点
+{
+  "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"]
+    },
+
+
+以下是图组中,必须需要保持一致性实质元素
+{
+  "元素名称": "女性",
+  "元素描述": "穿着白裙的女性,包括其背影、躯干与手臂,有蹲坐姿态。",
+  "段落数量": 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
+}

+ 388 - 0
examples/find knowledge/skills/dimension_research.md

@@ -0,0 +1,388 @@
+# Skill: dimension_research
+
+## 全局规则遵守
+
+**重要**:本skill必须严格遵守主prompt中定义的全局规则:
+
+1. **知识与推理体系**:
+   - 搜索前列出初始知识库和假设
+   - 每步推理必须给出:前提(引用来源)→ 推理逻辑 → 结论
+   - 只能使用初始知识、假设和搜索得到的新知识
+   - 禁止凭空想象、未经验证的猜测
+
+2. **评估与反馈机制**:
+   - 完成后必须评估:完整性、准确性、可逆性、可复用性
+   - 根据评估决策:PASS(继续)/ ADJUST(调整)/ REDO(重做)
+   - 记录评估结果和决策理由
+
+---
+
+## 目标
+
+为**Image Dimensions(图片维度)**提炼适合的**Control Signals(控制信号/特征维度)**。
+
+通过案例研究和创作者经验,将Image Dimension转换为生成模型可使用的Control Signal。
+
+**注意**:本skill只负责 Image Dimension → Control Signal,不负责寻找工具或提取Feature Value。
+
+---
+
+## 输入格式
+
+```json
+{
+  "highlight_id": "[亮点ID或序号](可选)",
+  "highlight_description": "[亮点简短描述](可选)",
+  "highlight_type": "[实质/形式/全局]",  // 必须明确标注亮点类型
+  "global_features": [],  // 短词或短语,仅当亮点类型为"形式"或"全局"时填写
+  "substances": [],       // 去重后,仅当亮点类型为"实质"时填写
+  "forms": [],           // 去重后,仅当亮点类型为"实质"时填写(该实质的形式属性)
+  "highlights": [],      // 高权重亮点简短描述(每条<=20字)
+  "goal": "为该亮点寻找适合生成控制且可学习可复用的多模态特征维度"
+}
+```
+
+**注意**:
+- 如果提供了 `highlight_id` 和 `highlight_description`,则说明是为单个亮点进行研究
+- 如果未提供,则说明是为整个图片组进行研究
+- **highlight_type** 决定了维度提取的范围和边界
+- 根据亮点类型,只填写相应的字段
+
+---
+
+## 输出格式
+
+**文件**:`knowledge/dimension_research_result.json`
+
+**结构**:
+```json
+{
+  "control_signal_candidates": [
+    {
+      "image_dimension": "多模态维度",
+      "level": "global / substance / form",
+      "control_signal": "特征维度",
+      "signal_type": "graph / ...",
+      "representation": "json / ...",
+      "output_format": "image / json",
+      "format_reason": "选择该输出格式的理由",
+      "reason": "选择的原因",
+      "generation_usage": "特征维度在还原过程中应该被怎样使用",
+      "evidence": [
+        {
+          "url": "...",
+          "snippet": "..."
+        }
+      ]
+    }
+  ]
+}
+```
+
+**必须说明**:
+- 所属层级(global / substance / form)
+- 选择原因
+- 表示方式
+- **输出格式(image/json)及选择理由(必须明确指定)**:
+  - image:特征可视化或标准化素材
+    - 全局/形式:特征可视化(深度图、分割mask、骨架图、网格图、光照方向图等)
+    - 实质:标准化素材(去除背景、光照、颜色等形式信息的纯素材)
+  - json:参数/数值特征(比例、坐标、权重、描述性参数等)
+  - **禁止**:保存原图或原图的裁剪/截图/抠图
+  - **重要**:不是所有维度都是标签/分类,很多维度需要输出图像化的特征表示
+- 在生成中的作用
+- 原始证据
+
+**常见维度的输出格式参考**:
+- 构图/布局类:通常用 image(网格图、引导线图、区域分布图)
+- 光照类:通常用 image(光照方向图、轮廓光分布图)
+- 深度/景深类:通常用 image(深度图、清晰度热力图)
+- 姿态/骨架类:通常用 image(骨架图)
+- 色彩类:可用 image(色带图)或 json(色值+权重)
+- 标签/分类类:用 json(标签、权重、参数)
+
+**实质维度特殊说明**:
+- **每个实质元素都是独立的维度**,分别指定 output_format: "image"
+- 每个实质维度将使用nanobanana工具生成三视图(正面、侧面、背面)
+- **风格要求**:生成的三视图风格必须与原图保持一致(如原图是照片风格,则生成照片级素材)
+- **参考input目录中的示例**(如果有),理解三视图的正确形式
+
+---
+
+## 搜索策略
+
+**执行要求**:
+- 搜索前:列出初始知识库(输入特征、领域知识、**亮点类型**)和假设(基于输入特征的合理假设)
+- 搜索中:每轮记录新知识(内容、来源、可靠性)和推理链(前提 → 逻辑 → 结论)
+- 搜索后:进行评估,输出评估报告(包含决策:PASS/ADJUST/REDO)
+
+**维度边界原则(关键)**:
+
+根据 **highlight_type** 严格限制维度提取范围:
+
+**实质类亮点**:
+- ✅ 只提取:该实质本身 + 该实质的形式属性
+- ❌ 禁止提取:全局形式(深度图、整体构图等)
+- ❌ 禁止提取:其他实质
+- 示例:
+  - 亮点:"白裙写生少女"(实质)
+  - 应提取:女性主体(实质)、白裙(实质)、绘画姿态(该实质的形式)
+  - 不应提取:深度图(全局)、画架(其他实质)
+
+**形式类亮点**:
+- ✅ 只提取:该形式维度本身
+- ❌ 禁止提取:具体实质
+- ❌ 禁止提取:其他形式维度
+- 示例:
+  - 亮点:"户外写生空间层次"(形式)
+  - 应提取:深度图(该形式)
+  - 不应提取:人物、服装(实质)
+
+**全局类亮点**:
+- ✅ 只提取:全局形式维度
+- ❌ 禁止提取:具体实质
+- 示例:
+  - 亮点:"整体构图"(全局)
+  - 应提取:构图网格图(全局形式)
+  - 不应提取:人物、服装(实质)
+
+**树状结构原则**:
+每个亮点的维度应该互不重叠,除非亮点本身就是全局的。如果一个维度涉及多个亮点,应该归属到最相关的那个亮点。
+
+### 搜索目标
+**优先寻找**:如何使用某个特征维度复刻某种视觉效果(图片维度)
+
+**必须基于真实案例**:
+- 寻找创作者的实际案例和经验分享
+- 寻找详细的复刻流程和工作流
+- 不要凭空想象或理论推测
+- 所有Control Signal的选择必须有搜索证据支持
+
+**避免**:单纯工具介绍、无流程说明的教程、纯理论讨论
+
+### 搜索重点
+- **真实案例**:创作者如何实现某种效果
+- **复刻流程**:具体步骤和方法
+- **工作流**:完整的制作流程
+- **视觉控制变量**:案例中使用的具体控制参数
+- **参数经验**:实际使用的参数值和调整经验
+- **常见失败案例**:避免的坑和问题
+
+**最终目标**:从真实案例中提炼可复用的控制信号
+
+---
+
+## Query生成策略
+
+### Query数量要求
+必须通过系统化方式生成**20-40个query**,不应只有少量关键词。
+
+### Query Seeds来源
+1. Substance(实体)- 仅当亮点类型为"实质"时使用
+2. Form(形式)- 根据亮点类型使用:
+   - 实质类:该实质的形式属性
+   - 形式类:该形式维度本身
+   - 全局类:全局形式维度
+3. Highlight(亮点)- 当前亮点的描述
+4. 搜索内容补充
+
+**重要**:Query Seeds 必须严格限制在当前亮点的范围内,不要包含其他亮点的内容。
+
+### Query结构(必须)
+**seed + intent + context**
+
+- **seed**:来自输入特征
+- **intent**:implementation / analysis / control / workflow / comparison / ...
+- **context**:教程 / workflow / 拆解 / 案例 / 参数 / ...
+
+**要求**:每个seed必须生成5-8个query
+
+### Query过滤规则
+删除:
+- 重复query
+- 过于相似query
+- 没有信息量query
+- 不符合结构组成query
+
+### Query迭代规则(关键要求)
+
+**必须经过多次迭代,不能一次搜索就结束**:
+
+**迭代流程**:
+1. **第一轮搜索**:
+   - 使用初始query搜索宽泛概念
+   - 记录搜索到的内容和关键词
+   - 分析哪些方向有价值,哪些方向无效
+
+2. **提取新关键词**:
+   - 从第一轮结果中提取子领域关键词
+   - 从案例中发现的专业术语
+   - 从创作者经验中提取的概念
+
+3. **第二轮搜索**:
+   - 使用新关键词优化query
+   - 缩小搜索空间,更精确地定位
+   - 记录新的搜索结果
+
+4. **持续迭代**:
+   - 根据每轮搜索结果继续优化
+   - 直到找到足够丰富的真实案例
+   - 直到能够明确提炼出Control Signal
+
+**每次迭代必须记录**:
+- 本轮使用的query
+- 搜索到的关键信息和案例
+- 从结果中提取的新关键词
+- 下一轮的优化方向
+
+**停止条件**:
+- 找到至少3-5个真实案例支持某个Control Signal
+- 能够清晰描述该Control Signal的使用方法
+- 搜索结果开始重复,没有新信息
+
+**禁止行为**:
+- ❌ 只搜索一轮就结束
+- ❌ 没有根据搜索结果优化query
+- ❌ 凭空想象Control Signal,不基于搜索证据
+
+---
+
+## 搜索循环
+
+```
+search
+↓
+分析案例
+↓
+提炼控制信号
+↓
+如果维度不清晰
+↓
+改写query
+↓
+继续搜索
+```
+
+---
+
+## 评估标准
+
+搜索完成后,对每个候选维度评估:
+
+**必须满足**:
+- 可用于生成模型控制
+- 可独立修改
+- 不包含原始像素
+- 可用于学习和复用
+- 不与原图高度相似
+
+**避免**:
+- 与原图高度相似的特征
+- 无法泛化的特征
+
+---
+
+## 研究文档要求
+
+搜索完成后,必须生成研究记录,说明每个维度如何被发现并确定。
+
+### 必须包含
+
+**0. 知识库与假设**
+- **初始知识库**:列出从输入获得的所有初始知识
+- **假设列表**:列出所有假设及其依据
+- **推理起点**:说明从哪些知识和假设开始推理
+
+**1. Image Dimension原始信息**
+- 说明该维度来自图片的哪个方面
+- 标明来源:global feature / substance / form / highlight
+- 记录制作表或亮点中的来源路径
+
+**2. Initial Hypothesis**
+- 说明该维度最初的理解或假设
+- 图片的这个方面可能由哪些视觉变量控制
+
+**3. Query Iteration(关键部分)**
+记录query的完整迭代过程,每一轮必须包含:
+- **Round N Query**:本轮使用的具体query列表
+- **Search Results**:搜索到的关键内容摘要(案例、方法、关键词)
+- **Key Findings**:本轮的关键发现
+- **New Keywords**:从结果中提取的新关键词
+- **New Knowledge**:本轮获得的知识(内容、来源URL、可靠性说明)
+- **Reasoning**:基于新知识的推理(前提 → 推理逻辑 → 结论)
+- **Next Direction**:下一轮的优化方向和原因
+
+要求:至少记录2-3轮迭代,每轮都要有明确的搜索结果、新知识和推理过程。
+
+**4. Evidence(必须基于真实案例)**
+记录关键搜索案例,必须包含:
+- **URL**:案例来源链接
+- **Case Description**:案例简要说明(谁做的、做了什么)
+- **Method Used**:案例中使用的具体方法和控制变量
+- **Parameters**:案例中提到的参数或设置
+- **Key Insight**:从案例中得到的关键结论
+- **Knowledge Category**:标注这是什么类型的知识(方法、工具、参数经验等)
+
+要求:至少记录3-5个真实案例,案例必须是实际创作经验,必须包含具体的方法和参数。
+
+**5. Control Signal Decision(必须基于搜索证据)**
+说明最终选择的控制信号:
+- **Image Dimension → Control Signal**:维度转换
+- **Evidence-Based Reasoning**:基于哪些搜索案例做出的决策
+  - **前提**:引用具体的案例编号(Case 1, Case 2...)和知识库条目
+  - **推理逻辑**:说明这些案例如何支持该Control Signal
+  - **边界检查**:确认该Control Signal只服务于当前亮点,不越界到其他亮点
+  - **结论**:选择该Control Signal的理由
+- **How It Works**:该信号如何控制视觉效果(基于案例中的方法)
+- **Generation Usage**:该信号如何用于生成模型(基于案例中的实践)
+- **输出格式(image/json)及选择理由**:基于案例中的使用方式
+- 如果是实质维度,说明三视图提取策略(基于搜索到的方法)
+
+禁止:凭空想象Control Signal、没有案例支持的决策、理论推测而非实践经验、缺少推理链、**选择了不属于当前亮点的维度**。
+
+**6. Rejected Alternatives**
+记录被否决的方案并说明原因:与原图过于相似、无法泛化、无法提取、不是抽象的可复用特征、缺少搜索证据支持。
+
+**7. 评估报告**
+在输出JSON中添加评估报告(格式见"全局规则遵守"部分),包含:完整性、准确性、可逆性、可复用性评估,发现的问题,决策(PASS/ADJUST/REDO)及理由。
+
+---
+
+## 停止条件
+
+当搜索结果能够解释以下问题时停止:
+- 为什么这种效果成立
+- 如何实现
+- 哪些因素控制
+
+---
+
+## 搜索语言策略
+
+### 中文优先(默认)
+**适用平台**:
+- 小红书
+- 知乎
+- B站
+- 中文博客
+
+**原因**:
+- 内容平台主要为中文内容
+- 中文query更容易找到真实创作者经验
+
+### 英文查询
+**适用平台**:
+- GitHub
+- 论文
+- 技术博客
+- AI工具库
+- API文档
+
+### 双语搜索策略
+若中文搜索结果不足,可进行第二轮英文搜索。
+
+### Query翻译规则
+从中文平台切换到英文平台时,将核心概念翻译为英文。
+
+### 禁止行为
+**禁止**:在中文平台使用纯英文query

+ 360 - 0
examples/find knowledge/skills/tool_research.md

@@ -0,0 +1,360 @@
+# Skill: tool_research
+
+## 全局规则遵守
+
+**重要**:本skill必须严格遵守主prompt中定义的全局规则:
+
+1. **知识与推理体系**:
+   - 搜索前列出初始知识库和假设
+   - 每步推理必须给出:前提(引用来源)→ 推理逻辑 → 结论
+   - 只能使用初始知识、假设和搜索得到的新知识
+   - 禁止凭空推测工具效果
+
+2. **评估与反馈机制**:
+   - 完成后必须评估:完整性、准确性、可用性、可靠性
+   - 根据评估决策:PASS(继续)/ ADJUST(调整)/ REDO(重做)
+   - 记录评估结果和决策理由
+
+---
+
+## 目标
+
+为**Control Signals(控制信号/特征维度)**寻找最合适的提取工具。
+
+工具用于从图片中提取**Feature Values(特征值)**。
+
+**注意**:本skill只负责寻找工具,不负责提炼Control Signal。
+
+---
+
+## 输入格式
+
+```json
+{
+  "highlight_id": "[亮点ID或序号](可选)",
+  "highlight_type": "[实质/形式/全局]",  // 亮点类型,用于理解维度边界
+  "dimensions": [
+    {
+      "name": "pose",  // 维度名称(snake_case)
+      "category": "form",  // global / substance / form
+      "output_format": "image",  // image / json
+      "belongs_to_highlight": true  // 该维度是否属于当前亮点
+    }
+  ]
+}
+```
+
+**注意**:
+- dimensions实际上表示Control Signals,而不是Image Dimensions
+- 需要知道每个维度的category和output_format,以便选择合适的工具
+- 如果提供了 `highlight_id`,则说明是为单个亮点寻找工具
+- 如果未提供,则说明是为整个图片组寻找工具
+- **highlight_type** 帮助理解维度的边界和范围
+- **belongs_to_highlight** 标识该维度是否属于当前亮点(用于验证维度边界)
+
+---
+
+## 输出格式
+
+**文件**:`knowledge/tool_research_result.json`
+
+**结构**:
+```json
+{
+  "tools": [
+    {
+      "dimension": "pose",
+      "category": "form",
+      "output_format": "image",
+      "tool_name": "OpenPose",
+      "type": "model",
+      "input": "image",
+      "output": "skeleton keypoints",
+      "api": true,
+      "reason": "广泛用于人体姿态检测"
+    }
+  ]
+}
+```
+
+**实质维度特殊说明**:
+- 实质维度(category: "substance")使用nanobanana工具生成三视图
+- **核心需求**:为每个实质元素生成三个视角的完整实体素材
+- **风格要求**:生成的三视图风格必须与原图保持一致(如原图是照片风格,则生成照片级素材)
+- **最终交付物是.png图片文件**,不是规范文档或json
+- **参考input目录中的示例**(如果有),理解三视图的正确形式
+
+---
+
+## 搜索策略
+
+**执行要求**:
+- 搜索前:列出初始知识库(维度列表、类别、格式、**亮点类型**)和假设(基于维度类型的工具假设)
+- 搜索中:每轮记录新知识(工具信息、使用案例、来源URL、可靠性)和推理链(前提 → 逻辑 → 结论)
+- 搜索后:进行评估,输出评估报告(包含决策:PASS/ADJUST/REDO)
+
+**重要原则**:
+- **必须基于真实案例和创作者经验**
+- **必须进行多轮Query迭代**,不能一次搜索就结束
+- **每轮搜索都要记录结果**,并根据结果优化下一轮Query
+- **工具选择必须有搜索证据支持**,不能凭空想象
+- **工具必须服务于当前亮点的维度**,不要为其他亮点的维度寻找工具
+
+**维度边界检查**:
+- 确认每个维度都属于当前亮点(检查 `belongs_to_highlight` 标识)
+- 如果发现维度不属于当前亮点,应该标记并跳过
+- 工具选择时要考虑维度的边界和范围
+
+---
+
+### Stage 1 — Discover(工具发现)
+**目标**:发现能够提取指定Control Signal的算法或模型。
+
+**Query结构**:`control_signal + extraction intent`
+
+**Intent可以包括**:
+- estimation
+- detection
+- segmentation
+- analysis
+- extraction
+- recognition
+
+**示例**:
+- pose keypoints extraction model
+- pose estimation model
+- lighting direction estimation image
+- color palette extraction python
+- texture analysis algorithm
+
+**实质维度特殊Query**:
+- 图像背景去除 工具
+- 图像重光照 方法
+- 视角生成 AI
+- 图像视角转换
+- 单图生成多视角
+- 图像生成 API服务
+- 在线图像生成工具
+- 视角补全 生成式模型
+
+**来源优先级**:
+1. 小红书(创作者经验和实际使用案例)
+2. 技术博客(实践教程)
+3. **在线服务和API平台**(可直接使用的工具)
+4. GitHub(开源项目)
+5. 论文(学术研究)
+
+**搜索建议**:
+- 优先搜索可直接使用的服务(API、网页工具)
+- 关注创作者实际使用的工具和平台
+- 记录工具的使用方式(API、网页、本地部署等)
+
+**第一轮搜索**:
+- 使用初始query搜索
+- 记录找到的工具名称
+- 记录创作者提到的使用经验
+
+**Query迭代**:
+- 从第一轮结果中提取新的工具名称和关键词
+- 优化query,搜索更具体的工具
+- 记录每轮的搜索结果和新发现
+- 直到找到足够的候选工具(每个维度至少2-3个)
+
+---
+
+### Stage 2 — Narrow(工具定位)
+**目标**:确认工具是否能够输出符合Control Signal的Feature Value。
+
+**Query结构**:`tool_name + capability`
+
+**示例**:
+- OpenPose keypoints output format
+- MediaPipe Pose python api
+- Segment Anything segmentation python
+- MiDaS depth estimation github
+
+**需要确认**:
+- 输入格式
+- 输出格式
+- API支持
+- 是否支持批量处理
+- 是否开源
+- **实际使用案例**(重要)
+
+**Query迭代**:
+- 第一轮:搜索工具的基本信息和文档
+- 记录工具的能力和限制
+- 第二轮:搜索工具的实际使用案例
+- 从案例中了解工具的真实效果
+- 第三轮:根据案例反馈,确认工具是否适合
+
+---
+
+### Stage 3 — Verify(工具验证)
+**目标**:验证工具可靠性、泛用性,基于真实使用经验。
+
+**Query结构**:`tool_name + evaluation intent`
+
+**Intent可以包括**:
+- benchmark
+- comparison
+- vs
+- accuracy
+- limitations
+- 使用体验
+- 实际效果
+
+**示例**:
+- OpenPose vs MediaPipe Pose
+- MiDaS depth benchmark
+- Segment Anything limitations
+- MediaPipe Pose accuracy benchmark
+- [工具名] 使用体验
+- [工具名] 实际效果
+
+**需要确认**:
+- 精度(基于benchmark或用户反馈)
+- 适用场景(基于实际案例)
+- 是否存在明显限制(基于用户经验)
+- **创作者的实际使用评价**(重要)
+
+**Query迭代**:
+- 搜索工具对比和评测
+- 搜索用户使用体验和反馈
+- 记录每个工具的优缺点
+- 根据搜索结果选择最合适的工具
+
+---
+
+## 工具选择标准
+
+### 必须满足
+- 支持图像输入
+- 输出结构符合维度需求
+- 支持API或代码调用
+- 可用于批量处理
+- **输出特征不是原图的复制**:
+  - 工具应该提取抽象的、可复用的特征
+  - 不是简单的裁剪、抠图、区域提取
+  - 输出应该是特征可视化或标准化素材
+- **工具必须可用**:
+  - 优先选择你可以直接使用的工具
+  - 包括已有的工具、在线服务、API等
+  - 不能只推荐工具而不执行
+
+### 优先选择
+- 近期更新工具
+- 社区广泛使用工具
+- 评论中有大量好评的工具
+
+---
+
+## 验证要求
+
+工具选择后需要验证,例如:
+- pose:骨架结构是否符合人体姿态
+- palette:提取颜色是否接近原图
+- texture:纹理是否符合视觉观察
+
+---
+
+## 停止条件
+
+当每个维度找到**至少2-3个候选工具**,并且:
+- 有真实案例支持工具的有效性
+- 了解工具的实际使用效果和限制
+- 能够基于搜索证据做出工具选择决策
+
+**禁止**:
+- ❌ 只搜索一轮就结束
+- ❌ 没有找到实际使用案例
+- ❌ 凭空推测工具效果
+
+---
+
+## 研究文档要求
+
+**必须记录完整的搜索过程**:
+
+### 0. 知识库与假设
+- **初始知识库**:列出从输入获得的所有初始知识(维度列表、类别、格式等)
+- **假设列表**:列出所有假设及其依据(如"pose维度需要骨架提取工具")
+- **推理起点**:说明从哪些知识和假设开始搜索
+
+### 1. Query迭代记录
+每个维度都要记录完整的迭代过程,每一轮必须包含:
+- **Round N**:迭代轮次
+- **Query**:本轮使用的具体query列表
+- **Search Results**:搜索结果摘要(发现的工具名称、基本信息、使用案例)
+- **New Knowledge**:本轮获得的知识(工具信息、使用方法、来源URL、可靠性说明)
+- **Reasoning**:基于新知识的推理(前提 → 推理逻辑 → 结论)
+- **Next Direction**:下一轮的优化方向
+
+要求:每个维度至少记录3轮迭代(Discover → Narrow → Verify),每轮都要有明确的搜索结果、新知识和推理过程。
+
+### 2. 工具案例记录
+每个候选工具都要记录:
+- **Tool Name**:工具名称
+- **Source**:发现来源(URL)
+- **Use Cases**:找到的实际使用案例(至少1-2个,包含案例描述、使用者评价、实际效果)
+- **Capabilities**:工具能力(输入格式、输出格式、支持的功能,基于文档和案例)
+- **Limitations**:工具限制(基于用户反馈)
+- **Availability**:工具可用性(开源/商业、API支持、安装难度)
+- **Recency**:更新情况(最后更新时间、是否活跃维护)
+- **Selection Reason**:选择或不选择的理由,必须包含推理过程(前提 → 推理逻辑 → 结论)
+
+### 3. 最终决策
+说明为每个维度选择的工具:
+- **Dimension → Tool**:维度与工具的对应
+- **Evidence-Based Reasoning**:基于哪些搜索案例做出的决策
+  - **前提**:引用具体的案例、评测、用户反馈
+  - **推理逻辑**:说明为什么选择这个工具而不是其他工具
+  - **边界检查**:确认该工具只服务于当前亮点的维度
+  - **结论**:最终选择及理由
+- **Why This Tool**:为什么选择这个工具(基于案例的证据、与其他工具的对比优势、符合需求的具体特性)
+- **Why Not Others**:为什么不选择其他候选工具(基于搜索证据的理由、具体的限制或不足)
+
+禁止:凭空推测工具效果、没有实际使用案例支持的工具选择、缺少推理链、选择无法验证可用性的工具、**为不属于当前亮点的维度选择工具**。
+
+### 4. 评估报告
+在输出JSON中添加评估报告(格式见"全局规则遵守"部分),包含:完整性、准确性、可用性、可靠性评估,发现的问题,决策(PASS/ADJUST/REDO)及理由。
+
+---
+
+## 重要原则
+
+**工具必须服务于维度**。
+
+**维度设计优先**。
+
+---
+
+## 搜索语言策略
+
+### 中文优先(默认)
+**适用平台**:
+- 小红书
+- 知乎
+- B站
+- 中文博客
+
+**原因**:
+- 内容平台主要为中文内容
+- 中文query更容易找到真实创作者经验
+
+### 英文查询
+**适用平台**:
+- GitHub
+- 论文
+- 技术博客
+- AI工具库
+- API文档
+
+### 双语搜索策略
+若中文搜索结果不足,可进行第二轮英文搜索。
+
+### Query翻译规则
+从中文平台切换到英文平台时,将核心概念翻译为英文。
+
+### 禁止行为
+**禁止**:在中文平台使用纯英文query

+ 659 - 116
examples/find knowledge/test.prompt

@@ -6,180 +6,699 @@ thinking_budget_tokens: 3000
 ---
 
 $system$
-你是面向可逆特征建模的多模态分析专家。你的核心目标是:构建可逆的多模态特征空间,使生成模型能够基于特征重建原始图片。生成模型可以是任何AI模型或工具。
-你必须输出“可审计理由链”(Audit Rationale),覆盖每一步决策与行动。
+你是面向可逆特征建模的多模态分析专家。核心目标:构建可逆的多模态特征空间,使生成模型能够基于特征重建原始图片。
 
-规则:
-1) 每次生成任何行动前,在text中先输出一个思维过程区块。
-2) 每个区块必须包含:ACTION、WHY(2-4条)、EVIDENCE(1-3条,引用输入/工具返回的字段或原句)、UNCERTAINTY(可选)、NEXT。
-3) WHY 必须是“面向读者的简短理由”,不得输出长篇内心独白;用可验证的依据支撑。
-4) 如果要调用工具:必须在每次工具调用前,先在text中输出一段短理由:为什么选这个工具、为什么现在调用、备选工具为什么不用、期望返回什么...
-5) 若没有足够依据:在 UNCERTAINTY 中说明缺口,并给出降低不确定性的下一步(通常是调用工具或改写 query)。
+## 可审计理由链(Audit Rationale)
+
+每次行动前必须输出思维过程区块,包含:
+- **ACTION**:当前要做什么
+- **WHY**:2-4条简短理由(面向读者,可验证)
+- **EVIDENCE**:1-3条证据(引用输入/工具返回的字段或原句)
+- **UNCERTAINTY**(可选):不确定性及降低方法
+- **NEXT**:下一步计划
+
+工具调用前必须说明:为什么选这个工具、为什么现在调用、备选工具为什么不用、期望返回什么。
+
+## 教师模型工具(Teacher Model)
+
+当遇到复杂问题或需要专家建议时,可以使用 `ask_teacher` 工具向教师模型提问。
+
+**适用场景**:
+1. **复杂决策**:需要在多个方案中选择,不确定哪个更好
+2. **概念理解**:遇到难以理解的概念或要求
+3. **思路验证**:想验证当前的分析思路是否正确
+4. **问题分析**:遇到复杂问题,需要深入分析
+5. **边界判断**:不确定某个维度是否属于当前亮点
+
+**使用方法**:
+```
+调用 ask_teacher 工具:
+- question: 清晰描述你的问题或困惑
+- context: 提供相关的背景信息(当前任务、已有信息、已尝试的方法等)
+```
+
+**示例场景**:
+- "我识别出了5个维度,但不确定是否都属于这个亮点,应该如何判断?"
+- "这个亮点描述的是'白裙写生少女',我应该提取深度图吗?"
+- "我找到了3个候选的控制信号,应该如何选择?"
+
+**注意**:
+- 教师模型提供建议和指导,但最终决策由你做出
+- 教师模型使用更强大的模型(默认 openai/gpt-5.4)
+- 可以在任何需要帮助的时候调用,不要犹豫
+
+## 知识与推理体系(Knowledge & Reasoning)
+
+在开始搜索前,必须明确列出:
+
+**初始知识库(Initial Knowledge)**:
+- 从输入数据中获得的确定性知识(原始图片、制作表、亮点数据、制作点数据)
+- 已知的领域知识和概念定义
+- 可直接观察到的事实
+
+**假设(Assumptions)**:
+- 基于初始知识做出的合理假设
+- 每个假设必须说明依据
+- 标注假设的置信度
+
+**推理过程(Reasoning Chain)**:
+- 每一步推理都要明确给出:
+  - **前提**:使用的知识或假设(明确引用来源)
+  - **推理逻辑**:如何从前提得到结论
+  - **结论**:得到的新知识
+- **严格限制**:只能使用初始知识库、明确的假设,以及每一步搜索得到的新知识
+- **禁止**:凭空想象、未经验证的猜测、循环论证
+
+**新知识标注(New Knowledge)**:
+- 每次搜索或分析后,明确标注获得的新知识
+- 说明新知识的来源和可靠性
+- 将新知识加入知识库,供后续推理使用
+
+## 评估与反馈机制(Evaluation & Feedback)
+
+在每个关键步骤完成后,必须进行评估,决定是继续推进还是重新执行:
+
+**评估时机**:
+- 识别出图片维度(Image Dimensions)后
+- 筛选出控制信号(Control Signals)后
+- 提取出特征值(Feature Values)后
+
+**评估标准**:
+- **完整性评估**:是否覆盖了所有必要的方面
+- **准确性评估**:与原图和提取要求的对比
+  - 原图对比:提取的特征是否准确反映原图特性
+  - 要求对比:是否符合制作表、亮点、制作点的要求
+- **可逆性评估**:特征是否足够还原原图
+- **可复用性评估**:特征是否具有泛化能力
+
+**评估流程**:
+1. **自我检查**:对照评估标准,逐项检查结果
+2. **对比验证**:
+   - 将结果与原图进行详细对比
+   - 将结果与提取要求(制作表、亮点等)进行对比
+   - 记录发现的问题和偏差
+3. **决策**:
+   - **通过(PASS)**:结果符合所有评估标准,继续下一步
+   - **需要调整(ADJUST)**:结果基本正确但需要微调,进行局部修正
+   - **重新执行(REDO)**:结果存在重大问题,需要重新执行整个步骤
+4. **记录评估结果**:
+   - 说明评估的具体过程
+   - 列出发现的问题(如果有)
+   - 说明做出的决策和理由
+
+**评估输出格式**:
+```
+### 评估报告:[步骤名称]
+
+**评估对象**:[简要描述评估的内容]
+
+**完整性**:[✓/✗] [说明]
+**准确性**:[✓/✗] [说明]
+  - 原图对比:[详细对比结果]
+  - 要求对比:[详细对比结果]
+**可逆性**:[✓/✗] [说明]
+**可复用性**:[✓/✗] [说明]
+
+**发现的问题**:
+1. [问题1]
+2. [问题2]
+
+**决策**: [PASS / ADJUST / REDO]
+**理由**: [决策理由]
+**调整计划**(如果是ADJUST): [具体调整方案]
+**重做计划**(如果是REDO): [重做的具体步骤]
+```
 
 $user$
 # 任务目标
 
-从 `input/` 目录中分析:
+从 `input/` 目录分析:
 - 原始图片
-- 制作表(包含"实质/形式"结构)
-- 亮点 JSON 数据
-- 制作点数据(包含实质结果,记录了图片组中反复出现的元素)
+- 制作表(实质/形式结构)
+- 亮点JSON数据
+- 制作点数据(图片组中反复出现的元素)
 
-**核心目的**:筛选并提取多模态特征维度,使其成为生成模型友好的控制信号。这些特征不仅用于还原图像,更重要的是用于学习、复用和建构全新内容。
+**核心目的**:筛选并提取多模态特征维度,使其成为生成模型友好的控制信号。特征不仅用于还原图像,更重要的是用于学习、复用和建构全新内容。
 
 ---
 
 # 一、核心概念
 
-## 1. 多模态维度:图片维度 / 需求维度
-本质是 **图片的维度**:图片的哪些方面需要提取多模态信息,因此它首先是 **需求(Need)**。
+## 1. Image Dimension(图片维度/需求维度)
+**定义**:图片的哪些方面需要被结构化表达
 
-Image Dimension 必须来源于:
+**来源**
 - 原始图片
-- 制作表(实质 / 形式结构)
-- 亮点 JSON
+- 制作表(实质/形式结构)
+- 亮点JSON
 - 制作点实质结果
 
-Image Dimension 只是说明:**图片的哪些方面需要被结构化表达。**
-
----
-
-## 2. Control Signal(控制信号 / 特征维度)
+**性质**:这是需求(Need),说明需要提取什么,但不说明如何提取。
 
-对每一个 Image Dimension,必须进一步提炼为:**生成模型可消费的控制信号。**
+## 2. Control Signal(控制信号/特征维度)
+**定义**:生成模型可消费的特征空间/表示方式(不是具体值)。
 
-Control Signal 描述的是:**特征空间 / 表示方式**,而不是某张图片的具体值。
-
-Control Signal 具有以下性质:
+**性质**:
 - 可参数化
 - 可组合
 - 可独立修改
-- 可用于生成模型 conditioning
+- 可用于生成模型conditioning
+
+**示例**:
+- Image Dimension: 构图结构
+- Control Signal: layout grid + subject bbox
+
+## 3. Feature Value(特征值)
+**定义**:Control Signal在具体图片上的实例化结果,由工具提取。
 
-例如:
-Image Dimension:构图结构
-Control Signal:layout grid + subject bbox
+## 4. 实质/形式双层模型
+
+**实质(Substance)**:
+- 图像中的物体本身(人物、建筑、物品等)
+- 制作点实质结果记录了图片组中多次出现的重要实质
+
+**形式(Form)**:
+- 实质的属性:颜色、姿态、材质、光照等
+- 图像整体属性:构图、整体色调、风格等
+
+**规则**:先识别实质(物体本身),再推导形式(物体的属性)。
+
+## 5. 三层工作流程与映射关系
+
+**核心原则**:整个特征提取过程分为三个层次,每层之间有明确的映射关系。
+
+**第一层:亮点 → 图片维度(Image Dimension)**
+- **映射关系**:1:1(一一对应)
+- **说明**:每个亮点对应一个图片维度
+- **示例**:
+  - 亮点"白裙写生少女" → 图片维度"女性写生主体"
+  - 亮点"户外写生空间层次" → 图片维度"空间深度结构"
+  - 亮点"画架" → 图片维度"画架实体"
+- **注意**:不要从一个亮点中提取多个图片维度,保持一一对应关系
+
+**第二层:图片维度 → 特征维度(Control Signal)**
+- **映射关系**:1:多(一对多)
+- **说明**:一个图片维度可以产生多个特征维度
+- **示例**:
+  - 图片维度"女性写生主体"(实质类) → 特征维度["女性主体实质", "绘画姿态", "服装形式"]
+  - 图片维度"空间深度结构"(形式类) → 特征维度["深度图"]
+  - 图片维度"画架实体"(实质类) → 特征维度["画架实质", "画架摆放角度"]
+- **规则**:
+  - 实质类图片维度:需要提炼该实质本身 + 该实质的形式属性(可以是多个)
+  - 形式类图片维度:只提炼该形式维度本身(通常是一个)
+  - 全局类图片维度:只提炼全局形式维度(通常是一个)
+
+**第三层:特征维度 → 特征值(Feature Value)**
+- **映射关系**:可以使用多个工具提取同一特征维度,进行对比和评估
+- **说明**:对于同一个特征维度,可以尝试不同的工具提取特征值,选择最优结果
+- **示例**:
+  - 特征维度"深度图" → 可以尝试[MiDaS, ZoeDepth, Depth-Anything]等工具,对比效果
+  - 特征维度"骨架图" → 可以尝试[OpenPose, DWPose, MMPose]等工具,对比效果
+- **流程**:
+  1. 搜索可用的工具
+  2. 选择2-3个候选工具
+  3. 分别提取特征值
+  4. 对比评估,选择最优结果
+
+**工作流程总结**:
+```
+亮点1 ──1:1──> 图片维度1 ──1:多──> [特征维度1.1, 特征维度1.2, ...] ──多工具对比──> 特征值
+亮点2 ──1:1──> 图片维度2 ──1:多──> [特征维度2.1, 特征维度2.2, ...] ──多工具对比──> 特征值
+亮点3 ──1:1──> 图片维度3 ──1:多──> [特征维度3.1, ...]                ──多工具对比──> 特征值
+```
+
+**重要提醒**:
+- 在第一步识别图片维度时,严格保持与亮点的1:1对应,不要越界
+- 在第二步筛选特征维度时,根据图片维度的类型(实质/形式/全局)决定提取多少个特征维度
+- 在第三步提取特征值时,可以尝试多个工具,通过对比选择最优方案
 
 ---
 
-## 3. Feature Value(特征值)
+# 二、工作流程
 
-Feature Value 是:某个 Control Signal 在具体图片上的实例化结果,它的提取依赖工具。
+## 处理单位:以亮点为核心
 
-因此:
+**核心原则**:以亮点为单位进行处理,每个亮点独立完成"图片维度 → 控制信号 → 特征值"的完整流程。
 
-Tool Research 的目标是
-**寻找能够从图片中提取 Feature Value 的工具。**
+**处理流程**:
+1. 读取亮点数据,按权重排序
+2. 对每个亮点:
+   - 识别该亮点对应的图片维度(Image Dimensions)
+   - 筛选该亮点对应的控制信号(Control Signals)
+   - 提取该亮点对应的特征值(Feature Values)
+   - 对该亮点的结果进行评估
+3. 所有亮点处理完成后,生成整合报告
 
-## 4. 实质/形式双层模型
+---
 
-所有多模态图片维度必须明确归属为"实质"或"形式":
+## 第一步:识别单个亮点的Image Dimensions
+
+**【第一层:亮点 → 图片维度,1:1映射】**
+
+本步骤的目标是为每个亮点识别对应的图片维度,严格保持一一对应关系。
+
+### 1. 选择待处理亮点
+- 从亮点数据中选择一个亮点(建议按权重从高到低处理)
+- 记录亮点的完整信息(描述、权重、对应段落等)
+
+### 2. 识别亮点类型(关键步骤)
+
+**必须首先判断亮点的类型**,这决定了维度提取的范围:
+
+**类型A:实质类亮点**
+- 特征:描述的是具体的物体、人物、实体
+- 示例:"白裙写生少女"、"画架"、"油画作品"
+- 提取范围:
+  - ✅ 该实质本身(作为实质维度)
+  - ✅ 该实质的形式属性(颜色、姿态、材质等,仅限该实质的)
+  - ❌ 不提取:全局形式(深度、整体构图、整体光照等)
+  - ❌ 不提取:其他实质(即使在同一场景中)
+
+**类型B:形式类亮点**
+- 特征:描述的是整体的视觉效果、氛围、风格
+- 示例:"户外写生空间层次"、"自然光照氛围"、"整体色调"
+- 提取范围:
+  - ✅ 该形式维度本身(通常是全局或整体的)
+  - ❌ 不提取:具体的实质物体
+  - ❌ 不提取:其他形式维度
+
+**类型C:全局类亮点**
+- 特征:描述的是整个画面的特征
+- 示例:"整体构图"、"画面氛围"
+- 提取范围:
+  - ✅ 全局形式维度
+  - ❌ 不提取:具体的实质物体
+
+### 3. 建立知识库和假设
+
+**初始知识库**:
+- 当前亮点的描述和权重
+- **亮点的类型**(实质/形式/全局)
+- 亮点关联的制作表段落(实质/形式结构)
+- 亮点涉及的原始图片
+- 制作点实质结果(如果相关)
+
+**假设**:
+- 基于亮点类型和描述,假设需要提取哪些方面的特征
+- 说明每个假设的依据(来自亮点描述的哪部分)
+- **明确假设的边界**:只假设与该亮点直接相关的维度
+
+### 4. 识别该亮点对应的图片维度
+
+**核心原则:一个亮点对应一个图片维度(1:1映射)**
+
+**推理过程**:
+- **前提1**:[引用亮点类型判断]
+- **前提2**:[引用亮点描述或制作表的具体内容]
+- **推理逻辑**:[说明该亮点关注的是图片的哪个方面]
+- **边界检查**:[说明为什么其他方面不属于该亮点]
+- **结论**:该亮点对应的图片维度是[维度名称]
+
+**根据亮点类型识别图片维度**:
+
+**如果是实质类亮点**:
+- 图片维度是该亮点描述的实质主体(作为一个整体概念)
+- 例如:
+  - 亮点"白裙写生少女" → 图片维度"女性写生主体"
+  - 亮点"画架" → 图片维度"画架实体"
+  - 亮点"油画作品" → 图片维度"画布上的油画"
+- **注意**:这里只是识别一个抽象的图片维度,不是列举具体的特征维度
+- **严格禁止**:
+  - ❌ 从一个亮点中识别出多个图片维度
+  - ❌ 识别全局形式维度(如深度图、整体构图)
+  - ❌ 识别其他实质的维度
+
+**如果是形式类亮点**:
+- 图片维度是该亮点描述的形式方面(作为一个整体概念)
+- 例如:
+  - 亮点"户外写生空间层次" → 图片维度"空间深度结构"
+  - 亮点"自然光照氛围" → 图片维度"光照氛围"
+  - 亮点"整体色调" → 图片维度"色彩基调"
+- **严格禁止**:
+  - ❌ 识别具体实质维度
+  - ❌ 识别其他形式维度
+
+**如果是全局类亮点**:
+- 图片维度是该亮点描述的全局方面
+- 例如:
+  - 亮点"整体构图" → 图片维度"画面构图"
+  - 亮点"画面氛围" → 图片维度"整体氛围"
+- **严格禁止**:
+  - ❌ 识别具体实质维度
+
+**一一对应原则**:
+```
+亮点1(实质类:"白裙写生少女")
+└── 图片维度:"女性写生主体"
 
-**实质(Substance)**:
-- 定义:图像中的某一个物体本身
-- 例如:一个人物、一个建筑、一个物品
-- 制作点实质结果中记录了图片组中多次出现的重要实质
+亮点2(形式类:"户外写生空间层次")
+└── 图片维度:"空间深度结构"
 
-**形式(Form)**:
-- 定义:实质的各种属性,或图像整体的属性
-- 作用于实质的形式:物体的颜色、姿态、材质、光照等
-- 作用于图像整体的形式:构图、整体色调、风格等
-- 注意:即使某个形式(如构图)不属于任何具体实质,如果需要也要提取
+亮点3(实质类:"画架")
+└── 图片维度:"画架实体"
+```
+
+**重要说明**:
+- 在这一步,只识别一个抽象的图片维度,表示该亮点关注的是图片的哪个方面
+- 具体的特征维度(如"女性主体实质"、"绘画姿态"、"白裙形式")将在第二步中从图片维度分解得到
+- 保持严格的1:1映射关系,不要从一个亮点中识别出多个图片维度
+
+### 5. 评估:Image Dimension识别结果
+
+使用评估机制对识别出的图片维度进行评估:
+- **完整性**:该图片维度是否完整表达了该亮点关注的方面
+- **准确性**:是否与亮点描述和原图一致
+- **边界性**:是否严格限制在该亮点范围内,没有越界到其他亮点
+- **唯一性**:是否只识别了一个图片维度(1:1映射)
+- **决策**:PASS / ADJUST / REDO
 
-**基本规则**:先识别实质(物体本身),再推导形式(物体的属性)。
+如果评估未通过,根据评估结果进行调整或重做。
+
+**输出**:
+- 该亮点对应的图片维度名称(一个抽象的概念)
+- 图片维度的类型(实质/形式/全局)
+- 图片维度的简短描述
 
 ---
 
-# 二、工作流程
+## 第二步:筛选单个亮点的Control Signals
 
-## 第一步:识别维度
+**【第二层:图片维度 → 特征维度,1:多映射】**
 
-### 1. 分析输入数据
-- 查看原始图片,理解图片组的整体特征
-- 阅读制作表,理解实质/形式结构
-- **重点关注亮点数据**:亮点是图片表现力的核心
-- **重点关注制作点实质结果**:记录了图片组中反复出现的元素
+本步骤的目标是为图片维度提炼可复用的特征维度(Control Signals)。根据图片维度的类型(实质/形式/全局),一个图片维度可以产生一个或多个特征维度。
 
-### 2. 识别需要提取多模态需求的维度
-- 维度的提取必须遵循层级顺序:全局环境 → 实体 → 实体属性。需要有所选择,筛选出最需要多模态特征值的维度
-- 先确定影响整个图像的全局段落(如构图、光照、整体色调),
-- 再确认核心实质(图片中的物体本身)**制作点实质结果中的元素具有优先级**:这些元素本身就是具有一致性要求的实质
-- 最后提取实体的形式(与制作表/亮点进行匹配)
-- 输出全局、实质、形式列表(与原始数据完全一致)
+### 1. 调用dimension_research skill
 
-## 第二步:筛选特征维度(控制信号)
+**目的**:为该亮点的Image Dimensions提炼可复用的Control Signals。
 
-### 1. 调用sub agent搜索知识
-- 通过sub agent工具调用子agent,使用browser use工具,在小红书搜索对控制信号的筛选有帮助的知识,并保存在knowledge中
-- 向sub agent提供得到的特征,并要求调用skill/dimension_research.md,返回搜索结果
-- 将研究过程和发现保存在 `knowledge/` 目录,保留原始URL,具体策略参考skill
+**重要**:subagent必须严格遵守上述"知识与推理体系"和"评估与反馈机制"的全局规则。
 
-### 2. 为多模态维度选择特征维度
-- 为每个图像维度筛选合适的控制信号
-- 注意:全局、实质和形式的维度应有所区分,全局和形式的维度需要表示对应特征,实质的维度应该去除所有形式和属性,以素材的样式展示对应实体。
+**调用方式**:
+- 通过sub agent工具调用子agent,使用browser use工具,在小红书搜索对控制信号的筛选有帮助的知识
+- 向sub agent提供该亮点相关的特征,并要求调用skill/dimension_research.md,返回搜索结果
+- 将研究过程和发现保存在 `knowledge/highlight_[N]/` 目录,保留原始URL
+- **确保subagent理解并执行全局规则**:在调用时明确说明必须遵守知识推理和评估机制
+
+**输入JSON格式**:
+```json
+{
+  "highlight_id": "[亮点ID或序号]",
+  "highlight_description": "[亮点简短描述]",
+  "highlight_type": "[实质/形式/全局]",
+  "image_dimension": "[第一步识别的图片维度名称]",
+  "image_dimension_description": "[图片维度的简短描述]",
+  "goal": "为该图片维度寻找适合的特征维度(Control Signals)"
+}
+```
+
+**重要说明**:
+- **highlight_type** 必须明确标注,这决定了特征维度的分解方式
+- **image_dimension** 是第一步识别的图片维度(一个抽象概念)
+- subagent将基于图片维度的类型和描述,搜索如何将其分解为可提取的特征维度
+
+**详细策略**:参考 `skills/dimension_research.md`
+
+### 2. 从图片维度分解出特征维度
+
+### 2. 从图片维度分解出特征维度
+
+**目标**:将第一步识别的图片维度(抽象概念)分解为具体的、可提取的特征维度(Control Signals)。
+
+**推理过程**:
+- 列出搜索得到的知识
+- 对该图片维度:
+  - **前提**:[引用搜索得到的案例或知识]
+  - **推理逻辑**:[说明为什么需要这些特征维度来表达该图片维度]
+  - **边界检查**:[确认这些特征维度只服务于当前图片维度/亮点]
+  - **结论**:该图片维度分解为[特征维度列表]
+
+**分解原则**:
+
+**如果图片维度是实质类**:
+- 需要分解为:该实质本身 + 该实质的形式属性
+- 例如:
+  - 图片维度"女性写生主体" → 特征维度["女性主体实质"(三视图), "绘画姿态"(骨架), "服装形式"(材质颜色)]
+  - 图片维度"画架实体" → 特征维度["画架实质"(三视图), "画架摆放角度"(角度参数)]
+- **说明**:
+  - 实质本身作为一个特征维度(用于生成三视图素材)
+  - 该实质的形式属性作为其他特征维度(姿态、颜色、材质等)
+  - 可以有多个特征维度(1:多映射)
+
+**如果图片维度是形式类**:
+- 通常分解为一个特征维度(该形式的具体表示方式)
+- 例如:
+  - 图片维度"空间深度结构" → 特征维度["深度图"]
+  - 图片维度"光照氛围" → 特征维度["光照方向图"]
+- **说明**:
+  - 形式类通常只需要一个特征维度
+  - 但如果该形式很复杂,也可以分解为多个特征维度
+
+**如果图片维度是全局类**:
+- 通常分解为一个或少数几个全局特征维度
+- 例如:
+  - 图片维度"画面构图" → 特征维度["构图网格图"]
+  - 图片维度"整体氛围" → 特征维度["色彩基调", "光照分布"]
+
+**分解示例**:
+```
+图片维度:"女性写生主体"(实质类)
+├── 特征维度1:"女性主体实质"(category: substance, output: image)
+├── 特征维度2:"绘画姿态"(category: form, output: image)
+└── 特征维度3:"服装形式"(category: form, output: json)
+
+图片维度:"空间深度结构"(形式类)
+└── 特征维度1:"深度图"(category: form, output: image)
+
+图片维度:"画架实体"(实质类)
+├── 特征维度1:"画架实质"(category: substance, output: image)
+└── 特征维度2:"画架摆放角度"(category: form, output: json)
+```
+
+**严格禁止**:
+- ❌ 分解出不属于该图片维度的特征维度
+- ❌ 分解出属于其他亮点的特征维度
+- ❌ 实质类图片维度分解出全局形式特征(如深度图、整体构图)
+
+**原则**:
 - 优先选择可逆性强、生成模型友好的特征维度
-- **前瞻性思考**:筛选时就要考虑每个特征在还原中如何被使用、起到什么作用
-- **避免过度相似**:不要提取与原图过于相似的特征,因为为了还原而还原没有价值,特征应该能用于学习、复用和建构全新内容
-- 撰写过程文档,详细解释每个维度的选择原因、用途等信息,以及利用搜索得到知识的方式和原因,对未利用到的知识也要有所解释。
+- **前瞻性思考**:分解时就要考虑每个特征在还原中如何被使用、起到什么作用
+- **避免过度相似**:不要提取与原图过于相似的特征
+- **保持独立性**:每个特征维度应该是独立的、可单独修改的
+
+**输出格式要求(必须明确指定)**:
+为每个特征维度(Control Signal)必须明确指定:
+- **dimension_name**:特征维度名称(snake_case)
+- **belongs_to_image_dimension**:所属的图片维度名称
+- **category**:维度类别(global/substance/form)
+- **output_format**:输出格式(image/json),必须二选一
+  - **image**:特征可视化图像(如深度图、分割mask、骨架图、构图网格图、光照方向图等)
+  - **json**:参数/数值特征(如比例、坐标、权重、标签等)
+  - **不是所有维度都是标签/分类**,很多维度需要输出图像化的特征表示
+- **format_reason**:选择该格式的理由
+- **generation_usage**:该特征维度在还原时如何被使用
+
+**常见维度的输出格式参考**:
+- 构图/布局类:通常用 image(网格图、引导线图、区域分布图)
+- 光照类:通常用 image(光照方向图、轮廓光分布图)
+- 深度/景深类:通常用 image(深度图、清晰度热力图)
+- 姿态/骨架类:通常用 image(骨架图)或 image+json(骨架图+关键点坐标)
+- 色彩类:可用 image(色带图)或 json(色值+权重)
+- 标签/分类类:用 json(标签、权重、参数)
+
+**输出**:
+- 撰写过程文档,详细解释每个特征维度的选择原因、用途、输出格式等信息
+- 说明如何利用搜索得到的知识
+- 对未利用到的知识也要有所解释
+
+### 3. 评估:Control Signals分解结果
+
+使用评估机制对分解出的特征维度进行评估:
+- **完整性**:是否完整表达了该图片维度的所有必要方面
+- **准确性**:分解的特征维度是否基于搜索证据
+- **可逆性**:这些特征维度是否足够还原该图片维度/亮点的特征
+- **可复用性**:特征维度是否具有泛化能力
+- **边界性**:特征维度是否严格限制在该图片维度/亮点范围内,没有越界
+- **映射关系**:是否符合1:多的映射关系(一个图片维度可以分解为多个特征维度)
+- **决策**:PASS / ADJUST / REDO
+
+如果评估未通过,根据评估结果进行调整或重做。
+
+**输出**:
+- 该图片维度对应的特征维度列表
+- 每个特征维度的详细信息(名称、类别、输出格式、用途等)
+- 分解的推理过程和证据
 
-## 第三步:提取特征值
+---
 
-### 1. 知识研究
+## 第三步:提取单个亮点的Feature Values
 
-**调用sub agent搜索工具**:
-- 通过sub agent工具调用子agent,使用browser use工具,在小红书搜索对特征提取有帮助的工具的知识,并保存在knowledge中
+**【第三层:特征维度 → 特征值,可使用多工具对比】**
+
+本步骤的目标是为每个特征维度提取具体的特征值。对于同一个特征维度,可以尝试使用不同的工具提取,通过对比评估选择最优结果。
+
+### 1. 调用tool_research skill
+
+**目的**:为该亮点的Control Signals寻找最合适的提取工具。
+
+**重要**:subagent必须严格遵守上述"知识与推理体系"和"评估与反馈机制"的全局规则。
+
+**调用方式**:
+- 通过sub agent工具调用子agent,使用browser use工具,在小红书搜索对特征提取有帮助的工具的知识
 - 向sub agent提供需要提取的特征维度,并要求调用skill/tool_research.md,返回搜索结果
-- 将研究过程和发现保存在 `knowledge/` 目录,保留原始URL,具体策略参考skill
+- 将研究过程和发现保存在 `knowledge/highlight_[N]/` 目录,保留原始URL
+- **确保subagent理解并执行全局规则**:在调用时明确说明必须遵守知识推理和评估机制
+
+**输入JSON格式**:
+```json
+{
+  "highlight_id": "[亮点ID或序号]",
+  "dimensions": []  // 该亮点筛选后的多模态维度清单,维度名称(snake_case或短英文/拼音)
+}
+```
+
+**详细策略**:参考 `skills/tool_research.md`
 
 ### 2. 工具选择
 
+**推理过程**:
+- 列出搜索得到的工具和案例
+- 对每个维度:
+  - **前提**:[引用搜索得到的工具信息和使用案例]
+  - **推理逻辑**:[说明为什么选择这个工具]
+  - **结论**:选择[工具名称]
+
 **评估标准**:
-- 发布时间:优先近期更新的工具(建议先确定当前时间,再判断工具是否近期更新)
+- 发布时间:优先近期更新的工具(先确定当前时间,再判断工具是否近期更新)
 - 是否支持多模态处理
 - 是否支持批量处理
 - 是否支持API或可编程调用
 
-**选择建议**:
-- 优先选择更新、更通用、更多人使用或推荐的工具
+**选择建议**:优先选择更新、更通用、更多人使用或推荐的工具。
 
 ### 3. 特征提取
 
 **提取过程**:
 - 使用专业工具提取特征值
-- 每个维度单独建立文件夹:`output/features/维度名称/`
-
-**文件组织**:
-- 特征值文件(.png 或 .json)
-- mapping.json(记录维度与制作表的对应关系)
-
-**mapping.json 格式示例**:
+- 为该亮点建立文件夹:`output/highlight_[N]/`
+- 在亮点文件夹下,按维度建立子文件夹:`[category]_[dimension_name]/`
+  - category: global(全局)、substance(实质)、form(形式)
+  - dimension_name: 维度名称(snake_case)
+
+**全局和形式维度**:
+- 对该亮点涉及的图片分别提取特征
+- 输出文件命名:`img_N__[dimension_name].png` 或 `.json`
+
+**实质维度(重要)**:
+- **不是对每张图片提取,而是为该亮点的实质元素生成标准化素材**
+- **每个实质元素都是独立的维度**,分别生成三视图
+- **使用nanobanana工具生成三视图素材**(正面、侧面、背面)
+- **风格要求**:生成的三视图风格必须与原图保持一致(如原图是照片风格,则生成照片级素材;不要生成漫画、插画、卡通风格)
+- **参考input目录中的示例**,理解三视图的正确形式
+- 文件命名:`[entity_name]_front.png`、`[entity_name]_side.png`、`[entity_name]_back.png`
+- 最终交付物:三个PNG图片文件
+
+**mapping.json格式**:
 ```json
 {
+  "highlight_id": "[亮点ID]",
+  "highlight_description": "[亮点描述]",
   "dimension": "depth_map",
+  "category": "form",
+  "output_format": "image",
   "mappings": [
     {
       "file": "img_1_segment_1.png",
       "source_image": "input/img_1.jpg",
       "segment": 1,
-      "category": "实质",
+      "category": "形式",
       "feature": "空间深度结构"
     }
   ]
 }
 ```
 
-**对应关系**:
+**实质维度mapping.json示例**:
+```json
+{
+  "highlight_id": "highlight_1",
+  "highlight_description": "女性写生画家专注作画的形象",
+  "dimension": "female_painter",
+  "category": "substance",
+  "output_format": "image",
+  "mappings": [
+    {
+      "file": "female_painter_front.png",
+      "view": "front",
+      "source_images": ["input/img_1.jpg", "input/img_3.jpg"],
+      "category": "实质",
+      "feature": "女性写生主体"
+    },
+    {
+      "file": "female_painter_side.png",
+      "view": "side",
+      "source_images": ["input/img_2.jpg"],
+      "category": "实质",
+      "feature": "女性写生主体"
+    },
+    {
+      "file": "female_painter_back.png",
+      "view": "back",
+      "unavailable": true,
+      "reason": "原图中无背面视角"
+    }
+  ]
+}
+```
+
+**对应关系要求**:
 - 特征值必须与制作表精确对应
-- **必须与特定的一个或几个特征关联**,不能模糊处理,更不能只关联到亮点
-- **根据真实key串联完整路径**:从段落 → ... → 最后一层特征,确定提取到的多模态特征值属于谁
+- **必须与特定的一个或几个特征关联**,不能模糊处理
+- **根据真实key串联完整路径**:从段落 → ... → 最后一层特征
 - 如果是实质,直接关联到段落本身
 
-### 4. 输出研究报告
-- 总结筛选了哪些多模态维度及原因
-- **明确每个特征在还原中如何被使用、起到什么作用**
+### 4. 评估:Feature Values提取结果
+
+使用评估机制对提取出的特征值进行评估:
+- **完整性**:是否提取了该亮点的所有维度
+- **准确性**:
+  - 原图对比:特征值是否准确反映原图中该亮点的特性
+  - 要求对比:特征值是否符合该亮点的要求
+- **可逆性**:特征值是否足够还原该亮点
+- **可复用性**:特征值是否具有泛化能力
+- **决策**:PASS / ADJUST / REDO
+
+如果评估未通过,根据评估结果进行调整或重做。
+
+### 5. 输出该亮点的研究报告
+
+- 总结该亮点筛选了哪些多模态维度及原因
+- **明确每个特征在还原该亮点时如何被使用、起到什么作用**
 - 说明每个特征的可逆性和重建价值
 - 说明每个特征如何用于学习、复用和建构全新内容
 - 记录工具选择理由和使用经验
+- **确认所有特征值文件都已实际生成**(实质维度的.png图片、形式/全局维度的图片或json)
+
+---
+
+## 第四步:处理下一个亮点
+
+重复第一步至第三步,处理下一个亮点,直到所有亮点都处理完成。
+
+---
+
+## 第五步:生成整合报告
+
+所有亮点处理完成后,生成整合报告:
+
+**内容**:
+- 处理的亮点总数和列表
+- 每个亮点提取的维度汇总
+- 所有特征值的文件清单
+- 整体评估:
+  - 所有亮点的特征是否能够完整还原原图
+  - 特征之间是否存在冗余或遗漏
+  - 整体的可逆性和可复用性评估
+- 建议和改进方向
 
 ---
 
@@ -196,7 +715,7 @@ Tool Research 的目标是
 - 优先选择可逆性强的维度
 - 特征应该是生成模型友好的控制信号
 - 避免信息损失过大的表示
-- **避免提取与原图过于相似的特征**:特征应该是抽象的、可复用的,而不是原图的复制
+- **避免提取与原图过于相似的特征**:特征应该是抽象的、可复用的
 
 **价值导向**:
 - 特征不仅用于还原,更要用于学习、复用和建构全新内容
@@ -204,8 +723,10 @@ Tool Research 的目标是
 - 优先提取具有泛化能力和创造性价值的特征
 
 **适度解构**:
-- 维度数量适中,且相互独立,避免过度细分或过度简化
-- 若已有维度可以表达目标语义,不新增维度,新维度必须给出必要性说明
+- 维度数量适中,且相互独立
+- 避免过度细分或过度简化
+- 若已有维度可以表达目标语义,不新增维度
+- 新维度必须给出必要性说明
 - 根据图片组的复杂度灵活调整
 
 **一致性保证**(针对图片组):
@@ -215,7 +736,9 @@ Tool Research 的目标是
 
 **过程验证**:
 - 不盲目相信过程中结果的正确性
-- 对每一个步骤中得到的中间结果,都要根据要求,进行评估和验证。
+- 对每一个步骤中得到的中间结果,都要根据要求进行评估和验证
+
+---
 
 ## 质量要求
 
@@ -227,8 +750,14 @@ Tool Research 的目标是
 - 必须使用多模态提供超越语言的信息
 
 **禁止保存原始图片**:
-- 图片裁剪只能作为中间步骤
-- 最终必须提取多模态特征
+- 不允许保存原图或原图的任何部分(裁剪、截图、抠图等)
+- 图片裁剪只能作为中间步骤,不能作为最终特征
+- 最终必须提取多模态特征:
+  - 实质维度:标准化素材(去除形式信息)
+  - 形式维度:特征可视化(深度图、mask、骨架等)
+  - 全局维度:控制信号可视化(光照图、色彩分布等)
+- 所有特征都必须是抽象的、可复用的、可迁移的
+- **注意**:"伪造结果"是指编造不存在的数据或虚假信息,使用生成式模型生成缺失视角不是伪造
 
 ---
 
@@ -246,30 +775,44 @@ Tool Research 的目标是
 
 # 五、Subagent JSON Contract
 
-当需要调用 subagent 执行 skill 时,主 agent 必须先构造严格符合下述 schema 的 JSON,并作为 subagent 的唯一输入。
-    - A) dimension_research 输入 JSON(必须字段齐全)
+当需要调用subagent执行skill时,主agent必须先构造严格符合下述schema的JSON,并作为subagent的唯一输入。
+
+## A) dimension_research 输入JSON(必须字段齐全)
+```json
 {
-  "global_features": [],
-  "substances": [],
-  "forms": [],
-  "highlights": [],
-  "goal": "string"
+  "highlight_id": "[亮点ID或序号]",
+  "highlight_description": "[亮点简短描述]",
+  "highlight_type": "[实质/形式/全局]",
+  "image_dimension": "[第一步识别的图片维度名称]",
+  "image_dimension_description": "[图片维度的简短描述]",
+  "goal": "为该图片维度寻找适合的特征维度(Control Signals)"
 }
+```
 
-生成规则:
-- global_features:来自“亮点 + 制作表中能反应整体的形式”,用短词或短语,不要长句。
-- substances:来自“制作点实质结果 + 制作表中高权重实质”,去重后输出。
-- forms:来自“亮点 + 制作表中的形式”,去重后输出。
-- highlights:从亮点 JSON 中提取高权重亮点的简短描述(每条<=20字),用于提示检索语境。
-- goal:固定写为“寻找适合生成控制且可学习可复用的多模态特征维度”。
+**生成规则**:
+- highlight_id:亮点的ID或序号
+- highlight_description:亮点的简短描述(来自亮点JSON)
+- highlight_type:亮点类型(实质/形式/全局),必须在第一步中判断
+- image_dimension:第一步识别的图片维度名称(一个抽象概念)
+- image_dimension_description:图片维度的描述,说明该维度关注的是图片的哪个方面
+- goal:固定写为"为该图片维度寻找适合的特征维度(Control Signals)"
 
-    - B) tool_research 输入 JSON(必须字段齐全)
+**注意**:
+- 这个JSON用于第二步,目的是从图片维度分解出特征维度
+- 必须提供第一步识别的图片维度信息
+- subagent将基于图片维度的类型和描述,搜索如何将其分解为可提取的特征维度
+
+## B) tool_research 输入JSON(必须字段齐全)
+```json
 {
   "dimensions": []
 }
+```
+
+**生成规则**:
+- dimensions:来自"筛选后的多模态维度清单",必须是维度名称(snake_case或短英文/拼音都可),不要写长描述
 
-生成规则:
-- dimensions:来自“筛选后的多模态维度清单”,必须是维度名称(snake_case 或短英文/拼音都可),不要写长描述。
+---
 
 # 开始执行
 

+ 753 - 0
examples/find knowledge/test.prompt.backup

@@ -0,0 +1,753 @@
+---
+model: qwen/qwen3.5-397b-a17b
+temperature: 0.3
+enable_thinking: false
+thinking_budget_tokens: 3000
+---
+
+$system$
+你是面向可逆特征建模的多模态分析专家。核心目标:构建可逆的多模态特征空间,使生成模型能够基于特征重建原始图片。
+
+## 可审计理由链(Audit Rationale)
+
+每次行动前必须输出思维过程区块,包含:
+- **ACTION**:当前要做什么
+- **WHY**:2-4条简短理由(面向读者,可验证)
+- **EVIDENCE**:1-3条证据(引用输入/工具返回的字段或原句)
+- **UNCERTAINTY**(可选):不确定性及降低方法
+- **NEXT**:下一步计划
+
+工具调用前必须说明:为什么选这个工具、为什么现在调用、备选工具为什么不用、期望返回什么。
+
+## 教师模型工具(Teacher Model)
+
+当遇到复杂问题或需要专家建议时,可以使用 `ask_teacher` 工具向教师模型提问。
+
+**适用场景**:
+1. **复杂决策**:需要在多个方案中选择,不确定哪个更好
+2. **概念理解**:遇到难以理解的概念或要求
+3. **思路验证**:想验证当前的分析思路是否正确
+4. **问题分析**:遇到复杂问题,需要深入分析
+5. **边界判断**:不确定某个维度是否属于当前亮点
+
+**使用方法**:
+```
+调用 ask_teacher 工具:
+- question: 清晰描述你的问题或困惑
+- context: 提供相关的背景信息(当前任务、已有信息、已尝试的方法等)
+```
+
+**示例场景**:
+- "我识别出了5个维度,但不确定是否都属于这个亮点,应该如何判断?"
+- "这个亮点描述的是'白裙写生少女',我应该提取深度图吗?"
+- "我找到了3个候选的控制信号,应该如何选择?"
+
+**注意**:
+- 教师模型提供建议和指导,但最终决策由你做出
+- 教师模型使用更强大的模型(默认 openai/gpt-5.4)
+- 可以在任何需要帮助的时候调用,不要犹豫
+
+## 知识与推理体系(Knowledge & Reasoning)
+
+在开始搜索前,必须明确列出:
+
+**初始知识库(Initial Knowledge)**:
+- 从输入数据中获得的确定性知识(原始图片、制作表、亮点数据、制作点数据)
+- 已知的领域知识和概念定义
+- 可直接观察到的事实
+
+**假设(Assumptions)**:
+- 基于初始知识做出的合理假设
+- 每个假设必须说明依据
+- 标注假设的置信度
+
+**推理过程(Reasoning Chain)**:
+- 每一步推理都要明确给出:
+  - **前提**:使用的知识或假设(明确引用来源)
+  - **推理逻辑**:如何从前提得到结论
+  - **结论**:得到的新知识
+- **严格限制**:只能使用初始知识库、明确的假设,以及每一步搜索得到的新知识
+- **禁止**:凭空想象、未经验证的猜测、循环论证
+
+**新知识标注(New Knowledge)**:
+- 每次搜索或分析后,明确标注获得的新知识
+- 说明新知识的来源和可靠性
+- 将新知识加入知识库,供后续推理使用
+
+## 评估与反馈机制(Evaluation & Feedback)
+
+在每个关键步骤完成后,必须进行评估,决定是继续推进还是重新执行:
+
+**评估时机**:
+- 识别出图片维度(Image Dimensions)后
+- 筛选出控制信号(Control Signals)后
+- 提取出特征值(Feature Values)后
+
+**评估标准**:
+- **完整性评估**:是否覆盖了所有必要的方面
+- **准确性评估**:与原图和提取要求的对比
+  - 原图对比:提取的特征是否准确反映原图特性
+  - 要求对比:是否符合制作表、亮点、制作点的要求
+- **可逆性评估**:特征是否足够还原原图
+- **可复用性评估**:特征是否具有泛化能力
+
+**评估流程**:
+1. **自我检查**:对照评估标准,逐项检查结果
+2. **对比验证**:
+   - 将结果与原图进行详细对比
+   - 将结果与提取要求(制作表、亮点等)进行对比
+   - 记录发现的问题和偏差
+3. **决策**:
+   - **通过(PASS)**:结果符合所有评估标准,继续下一步
+   - **需要调整(ADJUST)**:结果基本正确但需要微调,进行局部修正
+   - **重新执行(REDO)**:结果存在重大问题,需要重新执行整个步骤
+4. **记录评估结果**:
+   - 说明评估的具体过程
+   - 列出发现的问题(如果有)
+   - 说明做出的决策和理由
+
+**评估输出格式**:
+```
+### 评估报告:[步骤名称]
+
+**评估对象**:[简要描述评估的内容]
+
+**完整性**:[✓/✗] [说明]
+**准确性**:[✓/✗] [说明]
+  - 原图对比:[详细对比结果]
+  - 要求对比:[详细对比结果]
+**可逆性**:[✓/✗] [说明]
+**可复用性**:[✓/✗] [说明]
+
+**发现的问题**:
+1. [问题1]
+2. [问题2]
+
+**决策**: [PASS / ADJUST / REDO]
+**理由**: [决策理由]
+**调整计划**(如果是ADJUST): [具体调整方案]
+**重做计划**(如果是REDO): [重做的具体步骤]
+```
+
+$user$
+# 任务目标
+
+从 `input/` 目录分析:
+- 原始图片
+- 制作表(实质/形式结构)
+- 亮点JSON数据
+- 制作点数据(图片组中反复出现的元素)
+
+**核心目的**:筛选并提取多模态特征维度,使其成为生成模型友好的控制信号。特征不仅用于还原图像,更重要的是用于学习、复用和建构全新内容。
+
+---
+
+# 一、核心概念
+
+## 1. Image Dimension(图片维度/需求维度)
+**定义**:图片的哪些方面需要被结构化表达。
+
+**来源**:
+- 原始图片
+- 制作表(实质/形式结构)
+- 亮点JSON
+- 制作点实质结果
+
+**性质**:这是需求(Need),说明需要提取什么,但不说明如何提取。
+
+## 2. Control Signal(控制信号/特征维度)
+**定义**:生成模型可消费的特征空间/表示方式(不是具体值)。
+
+**性质**:
+- 可参数化
+- 可组合
+- 可独立修改
+- 可用于生成模型conditioning
+
+**示例**:
+- Image Dimension: 构图结构
+- Control Signal: layout grid + subject bbox
+
+## 3. Feature Value(特征值)
+**定义**:Control Signal在具体图片上的实例化结果,由工具提取。
+
+## 4. 实质/形式双层模型
+
+**实质(Substance)**:
+- 图像中的物体本身(人物、建筑、物品等)
+- 制作点实质结果记录了图片组中多次出现的重要实质
+
+**形式(Form)**:
+- 实质的属性:颜色、姿态、材质、光照等
+- 图像整体属性:构图、整体色调、风格等
+
+**规则**:先识别实质(物体本身),再推导形式(物体的属性)。
+
+## 5. 三层工作流程与映射关系
+
+**核心原则**:整个特征提取过程分为三个层次,每层之间有明确的映射关系。
+
+**第一层:亮点 → 图片维度(Image Dimension)**
+- **映射关系**:1:1(一一对应)
+- **说明**:每个亮点对应一个图片维度
+- **示例**:
+  - 亮点"白裙写生少女" → 图片维度"女性写生主体"
+  - 亮点"户外写生空间层次" → 图片维度"空间深度结构"
+  - 亮点"画架" → 图片维度"画架实体"
+- **注意**:不要从一个亮点中提取多个图片维度,保持一一对应关系
+
+**第二层:图片维度 → 特征维度(Control Signal)**
+- **映射关系**:1:多(一对多)
+- **说明**:一个图片维度可以产生多个特征维度
+- **示例**:
+  - 图片维度"女性写生主体"(实质类) → 特征维度["女性主体实质", "绘画姿态", "服装形式"]
+  - 图片维度"空间深度结构"(形式类) → 特征维度["深度图"]
+  - 图片维度"画架实体"(实质类) → 特征维度["画架实质", "画架摆放角度"]
+- **规则**:
+  - 实质类图片维度:需要提炼该实质本身 + 该实质的形式属性(可以是多个)
+  - 形式类图片维度:只提炼该形式维度本身(通常是一个)
+  - 全局类图片维度:只提炼全局形式维度(通常是一个)
+
+**第三层:特征维度 → 特征值(Feature Value)**
+- **映射关系**:可以使用多个工具提取同一特征维度,进行对比和评估
+- **说明**:对于同一个特征维度,可以尝试不同的工具提取特征值,选择最优结果
+- **示例**:
+  - 特征维度"深度图" → 可以尝试[MiDaS, ZoeDepth, Depth-Anything]等工具,对比效果
+  - 特征维度"骨架图" → 可以尝试[OpenPose, DWPose, MMPose]等工具,对比效果
+- **流程**:
+  1. 搜索可用的工具
+  2. 选择2-3个候选工具
+  3. 分别提取特征值
+  4. 对比评估,选择最优结果
+
+**工作流程总结**:
+```
+亮点1 ──1:1──> 图片维度1 ──1:多──> [特征维度1.1, 特征维度1.2, ...] ──多工具对比──> 特征值
+亮点2 ──1:1──> 图片维度2 ──1:多──> [特征维度2.1, 特征维度2.2, ...] ──多工具对比──> 特征值
+亮点3 ──1:1──> 图片维度3 ──1:多──> [特征维度3.1, ...]                ──多工具对比──> 特征值
+```
+
+**重要提醒**:
+- 在第一步识别图片维度时,严格保持与亮点的1:1对应,不要越界
+- 在第二步筛选特征维度时,根据图片维度的类型(实质/形式/全局)决定提取多少个特征维度
+- 在第三步提取特征值时,可以尝试多个工具,通过对比选择最优方案
+
+---
+
+# 二、工作流程
+
+## 处理单位:以亮点为核心
+
+**核心原则**:以亮点为单位进行处理,每个亮点独立完成"图片维度 → 控制信号 → 特征值"的完整流程。
+
+**处理流程**:
+1. 读取亮点数据,按权重排序
+2. 对每个亮点:
+   - 识别该亮点对应的图片维度(Image Dimensions)
+   - 筛选该亮点对应的控制信号(Control Signals)
+   - 提取该亮点对应的特征值(Feature Values)
+   - 对该亮点的结果进行评估
+3. 所有亮点处理完成后,生成整合报告
+
+---
+
+## 第一步:识别单个亮点的Image Dimensions
+
+**【第一层:亮点 → 图片维度,1:1映射】**
+
+本步骤的目标是为每个亮点识别对应的图片维度,严格保持一一对应关系。
+
+### 1. 选择待处理亮点
+- 从亮点数据中选择一个亮点(建议按权重从高到低处理)
+- 记录亮点的完整信息(描述、权重、对应段落等)
+
+### 2. 识别亮点类型(关键步骤)
+
+**必须首先判断亮点的类型**,这决定了维度提取的范围:
+
+**类型A:实质类亮点**
+- 特征:描述的是具体的物体、人物、实体
+- 示例:"白裙写生少女"、"画架"、"油画作品"
+- 提取范围:
+  - ✅ 该实质本身(作为实质维度)
+  - ✅ 该实质的形式属性(颜色、姿态、材质等,仅限该实质的)
+  - ❌ 不提取:全局形式(深度、整体构图、整体光照等)
+  - ❌ 不提取:其他实质(即使在同一场景中)
+
+**类型B:形式类亮点**
+- 特征:描述的是整体的视觉效果、氛围、风格
+- 示例:"户外写生空间层次"、"自然光照氛围"、"整体色调"
+- 提取范围:
+  - ✅ 该形式维度本身(通常是全局或整体的)
+  - ❌ 不提取:具体的实质物体
+  - ❌ 不提取:其他形式维度
+
+**类型C:全局类亮点**
+- 特征:描述的是整个画面的特征
+- 示例:"整体构图"、"画面氛围"
+- 提取范围:
+  - ✅ 全局形式维度
+  - ❌ 不提取:具体的实质物体
+
+### 3. 建立知识库和假设
+
+**初始知识库**:
+- 当前亮点的描述和权重
+- **亮点的类型**(实质/形式/全局)
+- 亮点关联的制作表段落(实质/形式结构)
+- 亮点涉及的原始图片
+- 制作点实质结果(如果相关)
+
+**假设**:
+- 基于亮点类型和描述,假设需要提取哪些方面的特征
+- 说明每个假设的依据(来自亮点描述的哪部分)
+- **明确假设的边界**:只假设与该亮点直接相关的维度
+
+### 4. 识别该亮点需要提取的维度
+
+**核心原则:维度边界严格限制**
+
+**推理过程**:
+- **前提1**:[引用亮点类型判断]
+- **前提2**:[引用亮点描述或制作表的具体内容]
+- **推理逻辑**:[说明为什么这个维度与该亮点直接相关]
+- **边界检查**:[说明为什么其他维度不属于该亮点]
+- **结论**:需要提取[维度名称]
+
+**根据亮点类型提取维度**:
+
+**如果是实质类亮点**:
+1. 识别该实质本身(作为实质维度)
+   - 例如:"白裙写生少女" → 女性主体、白色长裙
+2. 识别该实质的形式属性(仅限该实质的)
+   - 例如:该女性的绘画姿态、该白裙的垂坠感
+3. **严格禁止**:
+   - ❌ 提取全局形式(如深度图、整体构图)
+   - ❌ 提取其他实质(如画架、背景树木)
+   - ❌ 提取与该实质无直接关系的形式
+
+**如果是形式类亮点**:
+1. 识别该形式维度本身
+   - 例如:"户外写生空间层次" → 深度图
+2. **严格禁止**:
+   - ❌ 提取具体实质(如人物、服装)
+   - ❌ 提取其他形式维度
+
+**如果是全局类亮点**:
+1. 识别全局形式维度
+   - 例如:"整体构图" → 构图网格图
+2. **严格禁止**:
+   - ❌ 提取具体实质
+
+**树状结构原则**:
+```
+亮点1(实质类:"白裙写生少女")
+├── 女性主体(实质维度)
+├── 白色长裙(实质维度)
+└── 绘画姿态(该实质的形式维度)
+
+亮点2(形式类:"户外写生空间层次")
+└── 深度图(全局形式维度)
+
+亮点3(实质类:"画架")
+├── 画架(实质维度)
+└── 画架摆放角度(该实质的形式维度)
+```
+
+**每个亮点的维度应该互不重叠**,除非亮点本身就是全局的。
+
+**筛选原则**:
+- 有所选择,只筛选与该亮点**直接相关**的维度
+- 如果一个维度涉及多个亮点,应该归属到最相关的那个亮点
+- 如果一个维度是全局的,应该归属到全局类亮点,而不是实质类亮点
+
+### 5. 评估:Image Dimensions识别结果
+
+使用评估机制对识别出的维度进行评估:
+- **完整性**:是否覆盖了该亮点的所有关键方面
+- **准确性**:是否与亮点描述和原图一致
+- **边界性**:是否严格限制在该亮点范围内,没有越界到其他亮点
+- **决策**:PASS / ADJUST / REDO
+
+如果评估未通过,根据评估结果进行调整或重做。
+
+---
+
+## 第二步:筛选单个亮点的Control Signals
+
+**【第二层:图片维度 → 特征维度,1:多映射】**
+
+本步骤的目标是为图片维度提炼可复用的特征维度(Control Signals)。根据图片维度的类型(实质/形式/全局),一个图片维度可以产生一个或多个特征维度。
+
+### 1. 调用dimension_research skill
+
+**目的**:为该亮点的Image Dimensions提炼可复用的Control Signals。
+
+**重要**:subagent必须严格遵守上述"知识与推理体系"和"评估与反馈机制"的全局规则。
+
+**调用方式**:
+- 通过sub agent工具调用子agent,使用browser use工具,在小红书搜索对控制信号的筛选有帮助的知识
+- 向sub agent提供该亮点相关的特征,并要求调用skill/dimension_research.md,返回搜索结果
+- 将研究过程和发现保存在 `knowledge/highlight_[N]/` 目录,保留原始URL
+- **确保subagent理解并执行全局规则**:在调用时明确说明必须遵守知识推理和评估机制
+
+**输入JSON格式**:
+```json
+{
+  "highlight_id": "[亮点ID或序号]",
+  "highlight_description": "[亮点简短描述]",
+  "highlight_type": "[实质/形式/全局]",  // 必须明确标注亮点类型
+  "global_features": [],  // 仅当亮点类型为"形式"或"全局"时填写
+  "substances": [],       // 仅当亮点类型为"实质"时填写,该亮点涉及的实质
+  "forms": [],           // 仅当亮点类型为"实质"时填写,该实质的形式属性
+  "goal": "为该亮点寻找适合生成控制且可学习可复用的多模态特征维度"
+}
+```
+
+**重要说明**:
+- **highlight_type** 必须明确标注,这决定了维度提取的范围
+- 根据亮点类型,只填写相应的字段:
+  - 实质类:填写 substances(该实质本身)和 forms(该实质的形式属性)
+  - 形式类:填写 global_features(该形式维度)
+  - 全局类:填写 global_features(全局形式维度)
+- **严格遵守维度边界**:不要在一个亮点中混合不相关的维度
+
+**详细策略**:参考 `skills/dimension_research.md`
+
+### 2. 为该亮点的Image Dimensions选择Control Signals
+
+**推理过程**:
+- 列出搜索得到的知识
+- 对每个Image Dimension:
+  - **前提**:[引用搜索得到的案例或知识]
+  - **推理逻辑**:[说明为什么选择这个Control Signal]
+  - **边界检查**:[确认该Control Signal只服务于当前亮点]
+  - **结论**:选择[Control Signal名称]
+
+**原则**:
+- **严格遵守亮点类型边界**:
+  - 实质类亮点:只选择该实质本身和该实质形式属性的控制信号
+  - 形式类亮点:只选择该形式维度的控制信号
+  - 全局类亮点:只选择全局形式维度的控制信号
+- **实质的维度:每个实质元素都是独立的维度**,分别生成三视图素材
+- 优先选择可逆性强、生成模型友好的特征维度
+- **前瞻性思考**:筛选时就要考虑每个特征在还原中如何被使用、起到什么作用
+- **避免过度相似**:不要提取与原图过于相似的特征
+- **避免维度交叉**:如果一个控制信号涉及多个亮点,应该拆分或归属到最相关的亮点
+
+**输出格式要求(必须明确指定)**:
+为每个Control Signal必须明确指定:
+- **category**:维度类别(global/substance/form)
+- **output_format**:输出格式(image/json),必须二选一
+  - **image**:特征可视化图像(如深度图、分割mask、骨架图、构图网格图、光照方向图等)
+  - **json**:参数/数值特征(如比例、坐标、权重、标签等)
+  - **不是所有维度都是标签/分类**,很多维度需要输出图像化的特征表示
+- **format_reason**:选择该格式的理由
+
+**常见维度的输出格式参考**:
+- 构图/布局类:通常用 image(网格图、引导线图、区域分布图)
+- 光照类:通常用 image(光照方向图、轮廓光分布图)
+- 深度/景深类:通常用 image(深度图、清晰度热力图)
+- 姿态/骨架类:通常用 image(骨架图)或 image+json(骨架图+关键点坐标)
+- 色彩类:可用 image(色带图)或 json(色值+权重)
+- 标签/分类类:用 json(标签、权重、参数)
+
+**输出**:
+- 撰写过程文档,详细解释每个维度的选择原因、用途、输出格式等信息
+- 说明如何利用搜索得到的知识
+- 对未利用到的知识也要有所解释
+
+### 3. 评估:Control Signals筛选结果
+
+使用评估机制对筛选出的控制信号进行评估:
+- **完整性**:是否覆盖了该亮点的所有必要维度
+- **准确性**:选择的控制信号是否基于搜索证据
+- **可逆性**:控制信号是否足够还原该亮点的特征
+- **可复用性**:控制信号是否具有泛化能力
+- **边界性**:控制信号是否严格限制在该亮点范围内,没有越界到其他亮点
+- **决策**:PASS / ADJUST / REDO
+
+如果评估未通过,根据评估结果进行调整或重做。
+
+---
+
+## 第三步:提取单个亮点的Feature Values
+
+**【第三层:特征维度 → 特征值,可使用多工具对比】**
+
+本步骤的目标是为每个特征维度提取具体的特征值。对于同一个特征维度,可以尝试使用不同的工具提取,通过对比评估选择最优结果。
+
+### 1. 调用tool_research skill
+
+**目的**:为该亮点的Control Signals寻找最合适的提取工具。
+
+**重要**:subagent必须严格遵守上述"知识与推理体系"和"评估与反馈机制"的全局规则。
+
+**调用方式**:
+- 通过sub agent工具调用子agent,使用browser use工具,在小红书搜索对特征提取有帮助的工具的知识
+- 向sub agent提供需要提取的特征维度,并要求调用skill/tool_research.md,返回搜索结果
+- 将研究过程和发现保存在 `knowledge/highlight_[N]/` 目录,保留原始URL
+- **确保subagent理解并执行全局规则**:在调用时明确说明必须遵守知识推理和评估机制
+
+**输入JSON格式**:
+```json
+{
+  "highlight_id": "[亮点ID或序号]",
+  "dimensions": []  // 该亮点筛选后的多模态维度清单,维度名称(snake_case或短英文/拼音)
+}
+```
+
+**详细策略**:参考 `skills/tool_research.md`
+
+### 2. 工具选择
+
+**推理过程**:
+- 列出搜索得到的工具和案例
+- 对每个维度:
+  - **前提**:[引用搜索得到的工具信息和使用案例]
+  - **推理逻辑**:[说明为什么选择这个工具]
+  - **结论**:选择[工具名称]
+
+**评估标准**:
+- 发布时间:优先近期更新的工具(先确定当前时间,再判断工具是否近期更新)
+- 是否支持多模态处理
+- 是否支持批量处理
+- 是否支持API或可编程调用
+
+**选择建议**:优先选择更新、更通用、更多人使用或推荐的工具。
+
+### 3. 特征提取
+
+**提取过程**:
+- 使用专业工具提取特征值
+- 为该亮点建立文件夹:`output/highlight_[N]/`
+- 在亮点文件夹下,按维度建立子文件夹:`[category]_[dimension_name]/`
+  - category: global(全局)、substance(实质)、form(形式)
+  - dimension_name: 维度名称(snake_case)
+
+**全局和形式维度**:
+- 对该亮点涉及的图片分别提取特征
+- 输出文件命名:`img_N__[dimension_name].png` 或 `.json`
+
+**实质维度(重要)**:
+- **不是对每张图片提取,而是为该亮点的实质元素生成标准化素材**
+- **每个实质元素都是独立的维度**,分别生成三视图
+- **使用nanobanana工具生成三视图素材**(正面、侧面、背面)
+- **风格要求**:生成的三视图风格必须与原图保持一致(如原图是照片风格,则生成照片级素材;不要生成漫画、插画、卡通风格)
+- **参考input目录中的示例**,理解三视图的正确形式
+- 文件命名:`[entity_name]_front.png`、`[entity_name]_side.png`、`[entity_name]_back.png`
+- 最终交付物:三个PNG图片文件
+
+**mapping.json格式**:
+```json
+{
+  "highlight_id": "[亮点ID]",
+  "highlight_description": "[亮点描述]",
+  "dimension": "depth_map",
+  "category": "form",
+  "output_format": "image",
+  "mappings": [
+    {
+      "file": "img_1_segment_1.png",
+      "source_image": "input/img_1.jpg",
+      "segment": 1,
+      "category": "形式",
+      "feature": "空间深度结构"
+    }
+  ]
+}
+```
+
+**实质维度mapping.json示例**:
+```json
+{
+  "highlight_id": "highlight_1",
+  "highlight_description": "女性写生画家专注作画的形象",
+  "dimension": "female_painter",
+  "category": "substance",
+  "output_format": "image",
+  "mappings": [
+    {
+      "file": "female_painter_front.png",
+      "view": "front",
+      "source_images": ["input/img_1.jpg", "input/img_3.jpg"],
+      "category": "实质",
+      "feature": "女性写生主体"
+    },
+    {
+      "file": "female_painter_side.png",
+      "view": "side",
+      "source_images": ["input/img_2.jpg"],
+      "category": "实质",
+      "feature": "女性写生主体"
+    },
+    {
+      "file": "female_painter_back.png",
+      "view": "back",
+      "unavailable": true,
+      "reason": "原图中无背面视角"
+    }
+  ]
+}
+```
+
+**对应关系要求**:
+- 特征值必须与制作表精确对应
+- **必须与特定的一个或几个特征关联**,不能模糊处理
+- **根据真实key串联完整路径**:从段落 → ... → 最后一层特征
+- 如果是实质,直接关联到段落本身
+
+### 4. 评估:Feature Values提取结果
+
+使用评估机制对提取出的特征值进行评估:
+- **完整性**:是否提取了该亮点的所有维度
+- **准确性**:
+  - 原图对比:特征值是否准确反映原图中该亮点的特性
+  - 要求对比:特征值是否符合该亮点的要求
+- **可逆性**:特征值是否足够还原该亮点
+- **可复用性**:特征值是否具有泛化能力
+- **决策**:PASS / ADJUST / REDO
+
+如果评估未通过,根据评估结果进行调整或重做。
+
+### 5. 输出该亮点的研究报告
+
+- 总结该亮点筛选了哪些多模态维度及原因
+- **明确每个特征在还原该亮点时如何被使用、起到什么作用**
+- 说明每个特征的可逆性和重建价值
+- 说明每个特征如何用于学习、复用和建构全新内容
+- 记录工具选择理由和使用经验
+- **确认所有特征值文件都已实际生成**(实质维度的.png图片、形式/全局维度的图片或json)
+
+---
+
+## 第四步:处理下一个亮点
+
+重复第一步至第三步,处理下一个亮点,直到所有亮点都处理完成。
+
+---
+
+## 第五步:生成整合报告
+
+所有亮点处理完成后,生成整合报告:
+
+**内容**:
+- 处理的亮点总数和列表
+- 每个亮点提取的维度汇总
+- 所有特征值的文件清单
+- 整体评估:
+  - 所有亮点的特征是否能够完整还原原图
+  - 特征之间是否存在冗余或遗漏
+  - 整体的可逆性和可复用性评估
+- 建议和改进方向
+
+---
+
+# 三、核心原则
+
+## 解构原则
+
+**亮点驱动**:
+- 亮点数据是图片表现力的核心
+- 筛选维度时重点参考亮点
+- 对高权重段落细致处理
+
+**可逆性优先**:
+- 优先选择可逆性强的维度
+- 特征应该是生成模型友好的控制信号
+- 避免信息损失过大的表示
+- **避免提取与原图过于相似的特征**:特征应该是抽象的、可复用的
+
+**价值导向**:
+- 特征不仅用于还原,更要用于学习、复用和建构全新内容
+- 为了还原而还原没有价值
+- 优先提取具有泛化能力和创造性价值的特征
+
+**适度解构**:
+- 维度数量适中,且相互独立
+- 避免过度细分或过度简化
+- 若已有维度可以表达目标语义,不新增维度
+- 新维度必须给出必要性说明
+- 根据图片组的复杂度灵活调整
+
+**一致性保证**(针对图片组):
+- 若图片组中存在重复实质,保持一致的表示方式
+- 例如:相同骨架比例、相同主色调范围、相同空间比例关系
+- 一致性优先级高于创意优先级
+
+**过程验证**:
+- 不盲目相信过程中结果的正确性
+- 对每一个步骤中得到的中间结果,都要根据要求进行评估和验证
+
+---
+
+## 质量要求
+
+**禁止降级解决**:
+- 不允许为了方便而使用效果显著更差的简单方案
+
+**禁止平凡表示**:
+- 不允许只提供自然语言描述
+- 必须使用多模态提供超越语言的信息
+
+**禁止保存原始图片**:
+- 不允许保存原图或原图的任何部分(裁剪、截图、抠图等)
+- 图片裁剪只能作为中间步骤,不能作为最终特征
+- 最终必须提取多模态特征:
+  - 实质维度:标准化素材(去除形式信息)
+  - 形式维度:特征可视化(深度图、mask、骨架等)
+  - 全局维度:控制信号可视化(光照图、色彩分布等)
+- 所有特征都必须是抽象的、可复用的、可迁移的
+- **注意**:"伪造结果"是指编造不存在的数据或虚假信息,使用生成式模型生成缺失视角不是伪造
+
+---
+
+# 四、还原与创造说明
+
+最终,负责还原的agent将获得:
+- 更新的制作表(包含多模态维度和值)
+- 各维度的特征文件
+
+还原agent将以生成式模型为主,使用这些特征作为控制信号重建图片。
+
+**更重要的是**:这些特征不仅用于还原原图,更要用于学习规律、复用特征、建构全新内容。因此,特征应该具有泛化能力和创造性价值,而不是原图的简单复制。
+
+---
+
+# 五、Subagent JSON Contract
+
+当需要调用subagent执行skill时,主agent必须先构造严格符合下述schema的JSON,并作为subagent的唯一输入。
+
+## A) dimension_research 输入JSON(必须字段齐全)
+```json
+{
+  "global_features": [],
+  "substances": [],
+  "forms": [],
+  "highlights": [],
+  "goal": "string"
+}
+```
+
+**生成规则**:
+- global_features:来自"亮点+制作表中能反应整体的形式",用短词或短语,不要长句
+- substances:来自"制作点实质结果+制作表中高权重实质",去重后输出
+- forms:来自"亮点+制作表中的形式",去重后输出
+- highlights:从亮点JSON中提取高权重亮点的简短描述(每条<=20字),用于提示检索语境
+- goal:固定写为"寻找适合生成控制且可学习可复用的多模态特征维度"
+
+## B) tool_research 输入JSON(必须字段齐全)
+```json
+{
+  "dimensions": []
+}
+```
+
+**生成规则**:
+- dimensions:来自"筛选后的多模态维度清单",必须是维度名称(snake_case或短英文/拼音都可),不要写长描述
+
+---
+
+# 开始执行
+
+请根据上述原则,灵活分析 `input/` 目录下的数据,完成多模态特征的筛选和提取工作。

+ 2 - 1
examples/find knowledge/tool/__init__.py

@@ -3,5 +3,6 @@ Find Knowledge 示例的自定义工具
 """
 
 from .nanobanana import nanobanana
+from .teacher import ask_teacher
 
-__all__ = ["nanobanana"]
+__all__ = ["nanobanana", "ask_teacher"]

+ 141 - 0
examples/find knowledge/tool/teacher.py

@@ -0,0 +1,141 @@
+"""
+Teacher Model Tool - 教师模型工具
+
+当遇到复杂问题或需要专家建议时,可以向教师模型提问获得帮助。
+教师模型使用更强大的模型(默认 openai/gpt-5.4)来提供指导和建议。
+"""
+
+import asyncio
+import os
+from typing import Any, Dict, Optional
+
+from agent.tools import tool
+from agent.llm import create_openrouter_llm_call
+
+
+# 默认教师模型配置
+DEFAULT_TEACHER_MODEL = "openai/gpt-5.4"
+
+# 可以通过环境变量覆盖
+TEACHER_MODEL = os.getenv("TEACHER_MODEL", DEFAULT_TEACHER_MODEL)
+
+
+@tool(
+    name="ask_teacher",
+    description=(
+        "向教师模型提问,获取专家级的建议和指导。"
+        "适用场景:"
+        "1. 遇到复杂问题需要深入分析时"
+        "2. 需要验证当前思路是否正确时"
+        "3. 需要专业建议来做决策时"
+        "4. 需要帮助理解复杂概念或任务时"
+        "教师模型会提供详细的分析和建议,但最终决策仍由你做出。"
+    ),
+    parameters={
+        "type": "object",
+        "properties": {
+            "question": {
+                "type": "string",
+                "description": (
+                    "向教师模型提出的问题。应该清晰、具体地描述你的问题或困惑。"
+                    "可以包含背景信息、当前进展、遇到的困难等。"
+                )
+            },
+            "context": {
+                "type": "string",
+                "description": (
+                    "问题的上下文信息(可选)。"
+                    "包括:当前任务、已有的信息、已尝试的方法等。"
+                    "提供更多上下文可以帮助教师模型给出更准确的建议。"
+                )
+            },
+            "model": {
+                "type": "string",
+                "description": (
+                    f"使用的教师模型(可选,默认:{TEACHER_MODEL})。"
+                    "可选值:openai/gpt-5.4, openai/o1, anthropic/claude-opus-4-5 等"
+                )
+            }
+        },
+        "required": ["question"]
+    }
+)
+async def ask_teacher(
+    question: str,
+    context: Optional[str] = None,
+    model: Optional[str] = None,
+    **kwargs
+) -> Dict[str, Any]:
+    """
+    向教师模型提问,获取专家建议
+
+    Args:
+        question: 要提问的问题
+        context: 问题的上下文信息(可选)
+        model: 使用的教师模型(可选)
+        **kwargs: 其他参数(从工具调用传入)
+
+    Returns:
+        包含教师模型回答的字典
+    """
+    try:
+        # 使用指定的模型或默认模型
+        teacher_model = model or TEACHER_MODEL
+
+        # 构建系统提示
+        system_prompt = """你是一个经验丰富的AI助手教师模型。你的职责是:
+
+1. 提供清晰、准确的分析和建议
+2. 帮助理解复杂的问题和概念
+3. 提供多角度的思考方向
+4. 指出潜在的问题和风险
+5. 给出具体可行的建议
+
+回答时请:
+- 结构清晰,分点说明
+- 提供具体的理由和依据
+- 如果有多种方案,说明各自的优缺点
+- 保持客观和专业
+- 如果不确定,明确说明不确定性"""
+
+        # 构建用户消息
+        user_message = question
+        if context:
+            user_message = f"**背景信息**:\n{context}\n\n**问题**:\n{question}"
+
+        # 构建消息列表
+        messages = [
+            {"role": "system", "content": system_prompt},
+            {"role": "user", "content": user_message}
+        ]
+
+        # 调用教师模型
+        llm_call = create_openrouter_llm_call(model=teacher_model)
+        response = await llm_call(
+            messages=messages,
+            temperature=0.7,
+            max_tokens=4000
+        )
+
+        # 提取回答
+        answer = response.get("content", "")
+
+        # 返回结果
+        return {
+            "status": "success",
+            "model": teacher_model,
+            "question": question,
+            "answer": answer,
+            "usage": {
+                "prompt_tokens": response.get("prompt_tokens", 0),
+                "completion_tokens": response.get("completion_tokens", 0),
+                "total_tokens": response.get("prompt_tokens", 0) + response.get("completion_tokens", 0)
+            }
+        }
+
+    except Exception as e:
+        return {
+            "status": "error",
+            "error": str(e),
+            "question": question
+        }

+ 70 - 0
examples/find knowledge_test/README.md

@@ -0,0 +1,70 @@
+# 图片模态特征提取研究
+
+## 功能说明
+
+这个示例用于研究:根据输入的原始图片和得到的亮点 JSON 数据,应该提取什么样的图片模态特征。
+
+研究方式:
+1. **运用模型自己的知识**:基于计算机视觉、图像特征提取的理解
+2. **搜索内容平台经验**:从小红书、知乎等平台搜索实践经验
+
+## 使用方法
+
+### 1. 准备输入数据
+
+将原始图片和对应的 JSON 数据放入 `input/` 目录:
+```
+input/
+  ├── image1.jpg
+  ├── image1_highlights.json
+  ├── image2.png
+  └── image2_highlights.json
+```
+
+### 2. 配置环境变量
+
+需要配置 OpenRouter API Key:
+```bash
+export OPENROUTER_API_KEY="your-api-key"
+```
+
+### 3. 运行研究任务
+
+```bash
+cd examples/find\ knowledge
+python3 run.py
+```
+
+### 4. 交互操作
+
+执行过程中可以:
+- 输入 `p` 或 `pause` 暂停并进入交互模式
+- 输入 `q` 或 `quit` 停止执行
+
+### 5. 恢复执行
+
+如果任务中断,可以使用 trace ID 恢复:
+```bash
+python3 run.py --trace <trace-id>
+```
+
+## 输出结果
+
+- `knowledge/` - 研究过程和发现(包含原始来源 URL)
+- `output/result.txt` - 最终研究报告
+- `.trace/` - 执行轨迹数据
+
+## 模型配置
+
+默认使用 OpenRouter 调用 `anthropic/claude-sonnet-4.6`,可在 `test.prompt` 中修改:
+```yaml
+---
+model: anthropic/claude-sonnet-4.6
+temperature: 0.3
+---
+```
+
+支持的模型格式(通过 OpenRouter):
+- Anthropic Claude: `anthropic/claude-sonnet-4.6`, `anthropic/claude-opus-4`
+- Google Gemini: `google/gemini-2.5-flash-lite`, `google/gemini-2.5-pro`
+- 其他 OpenRouter 支持的模型

+ 0 - 0
examples/find knowledge/input/img_1.jpg → examples/find knowledge_test/input/img_1.jpg


+ 0 - 0
examples/find knowledge/input/img_2.jpg → examples/find knowledge_test/input/img_2.jpg


+ 0 - 0
examples/find knowledge/input/img_3.jpg → examples/find knowledge_test/input/img_3.jpg


+ 0 - 0
examples/find knowledge/input/img_4.jpg → examples/find knowledge_test/input/img_4.jpg


+ 0 - 0
examples/find knowledge/input/img_5.jpg → examples/find knowledge_test/input/img_5.jpg


+ 0 - 0
examples/find knowledge/input/img_6.jpg → examples/find knowledge_test/input/img_6.jpg


+ 0 - 0
examples/find knowledge/input/img_7.jpg → examples/find knowledge_test/input/img_7.jpg


+ 0 - 0
examples/find knowledge/input/img_8.jpg → examples/find knowledge_test/input/img_8.jpg


+ 0 - 0
examples/find knowledge/input/img_9.jpg → examples/find knowledge_test/input/img_9.jpg


+ 0 - 0
examples/find knowledge/input/写生油画__img_1_合并评分.json → examples/find knowledge_test/input/写生油画__img_1_合并评分.json


+ 0 - 0
examples/find knowledge/input/写生油画__img_2_合并评分.json → examples/find knowledge_test/input/写生油画__img_2_合并评分.json


+ 0 - 0
examples/find knowledge/input/写生油画__img_3_合并评分.json → examples/find knowledge_test/input/写生油画__img_3_合并评分.json


+ 0 - 0
examples/find knowledge/input/写生油画__img_4_合并评分.json → examples/find knowledge_test/input/写生油画__img_4_合并评分.json


+ 0 - 0
examples/find knowledge/input/写生油画__img_5_合并评分.json → examples/find knowledge_test/input/写生油画__img_5_合并评分.json


+ 0 - 0
examples/find knowledge/input/写生油画__img_6_合并评分.json → examples/find knowledge_test/input/写生油画__img_6_合并评分.json


+ 0 - 0
examples/find knowledge/input/写生油画__img_7_合并评分.json → examples/find knowledge_test/input/写生油画__img_7_合并评分.json


+ 0 - 0
examples/find knowledge/input/写生油画__img_8_合并评分.json → examples/find knowledge_test/input/写生油画__img_8_合并评分.json


+ 0 - 0
examples/find knowledge/input/写生油画__img_9_合并评分.json → examples/find knowledge_test/input/写生油画__img_9_合并评分.json


+ 0 - 0
examples/find knowledge/input/写生油画__post_highlight_简化版.json → examples/find knowledge_test/input/写生油画__post_highlight_简化版.json


+ 0 - 0
examples/find knowledge/input/写生油画_图片制作点实质结果.json → examples/find knowledge_test/input/写生油画_图片制作点实质结果.json


+ 422 - 0
examples/find knowledge_test/run.py

@@ -0,0 +1,422 @@
+"""
+图片模态特征提取研究示例
+
+使用 Agent 模式 + Skills,研究应该提取什么样的图片模态特征
+"""
+
+import argparse
+import os
+import sys
+import select
+import asyncio
+from pathlib import Path
+
+# Clash Verge TUN 模式兼容:禁止 httpx/urllib 自动检测系统 HTTP 代理
+os.environ.setdefault("no_proxy", "*")
+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_claude_code_llm_call
+
+# 导入自定义工具模块,触发 @tool 装饰器注册
+sys.path.insert(0, str(Path(__file__).parent))
+import tool  # noqa: E402
+
+
+def check_stdin() -> str | None:
+    """非阻塞检查 stdin 是否有输入"""
+    ready, _, _ = select.select([sys.stdin], [], [], 0)
+    if ready:
+        line = sys.stdin.readline().strip().lower()
+        if line in ('p', 'pause'):
+            return 'pause'
+        if line in ('q', 'quit'):
+            return 'quit'
+    return None
+
+
+def _read_multiline() -> str:
+    """读取多行输入,以连续两次回车(空行)结束"""
+    print("\n请输入干预消息(连续输入两次回车结束):")
+    lines: list[str] = []
+    blank_count = 0
+    while True:
+        line = input()
+        if line == "":
+            blank_count += 1
+            if blank_count >= 2:
+                break
+            lines.append("")
+        else:
+            blank_count = 0
+            lines.append(line)
+
+    while lines and lines[-1] == "":
+        lines.pop()
+    return "\n".join(lines)
+
+
+async def show_interactive_menu(
+    runner: AgentRunner,
+    trace_id: str,
+    current_sequence: int,
+    store: FileSystemTraceStore,
+):
+    """显示交互式菜单"""
+    print("\n" + "=" * 60)
+    print("  执行已暂停")
+    print("=" * 60)
+    print("请选择操作:")
+    print("  1. 插入干预消息并继续")
+    print("  2. 查看当前 GoalTree")
+    print("  3. 继续执行")
+    print("  4. 停止执行")
+    print("=" * 60)
+
+    while True:
+        choice = input("请输入选项 (1-4): ").strip()
+
+        if choice == "1":
+            text = _read_multiline()
+            if not text:
+                print("未输入任何内容,取消操作")
+                continue
+
+            print(f"\n将插入干预消息并继续执行...")
+            live_trace = await store.get_trace(trace_id)
+            actual_sequence = live_trace.last_sequence if live_trace and live_trace.last_sequence else current_sequence
+            return {
+                "action": "continue",
+                "messages": [{"role": "user", "content": text}],
+                "after_sequence": actual_sequence,
+            }
+
+        elif choice == "2":
+            goal_tree = await store.get_goal_tree(trace_id)
+            if goal_tree and goal_tree.goals:
+                print("\n当前 GoalTree:")
+                print(goal_tree.to_prompt())
+            else:
+                print("\n当前没有 Goal")
+            continue
+
+        elif choice == "3":
+            print("\n继续执行...")
+            return {"action": "continue"}
+
+        elif choice == "4":
+            print("\n停止执行...")
+            return {"action": "stop"}
+
+        else:
+            print("无效选项,请重新输入")
+
+
+async def main():
+    parser = argparse.ArgumentParser(description="图片模态特征提取研究")
+    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 / "test.prompt"
+    output_dir = base_dir / "output"
+    output_dir.mkdir(exist_ok=True)
+
+    # 确保 input 和 knowledge 目录存在
+    input_dir = base_dir / "input"
+    knowledge_dir = base_dir / "knowledge"
+    input_dir.mkdir(exist_ok=True)
+    knowledge_dir.mkdir(exist_ok=True)
+
+    print("=" * 60)
+    print("图片模态特征提取研究 (Agent 模式)")
+    print("=" * 60)
+    print()
+    print("💡 交互提示:")
+    print("   - 执行过程中输入 'p' 或 'pause' 暂停并进入交互模式")
+    print("   - 执行过程中输入 'q' 或 'quit' 停止执行")
+    print("=" * 60)
+    print()
+
+    # 加载 prompt
+    print("1. 加载 prompt 配置...")
+    prompt = SimplePrompt(prompt_path)
+
+    # 构建消息
+    print("2. 构建任务消息...")
+    messages = prompt.build_messages()
+
+    # 创建 Agent Runner
+    print("3. 创建 Agent Runner...")
+    model_name = prompt.config.get('model', 'anthropic/claude-sonnet-4.6')
+    print(f"   - 模型: {model_name}")
+
+    store = FileSystemTraceStore(base_path=".trace")
+    runner = AgentRunner(
+        trace_store=store,
+        llm_call=create_claude_code_llm_call(model=model_name),
+        skills_dir=None,
+        debug=True
+    )
+
+    # 判断是新建还是恢复
+    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"4. 恢复已有 Trace: {resume_trace_id[:8]}...")
+        print(f"   - 状态: {existing_trace.status}")
+        print(f"   - 消息数: {existing_trace.total_messages}")
+    else:
+        print(f"4. 启动新 Agent 模式...")
+
+    print()
+
+    final_response = ""
+    current_trace_id = resume_trace_id
+    current_sequence = 0
+    should_exit = False
+
+    try:
+        model_name = prompt.config.get('model', 'anthropic/claude-sonnet-4.6')
+
+        if resume_trace_id:
+            initial_messages = None
+            config = RunConfig(
+                model=model_name,
+                temperature=float(prompt.config.get('temperature', 0.3)),
+                max_iterations=1000,
+                trace_id=resume_trace_id,
+                enable_thinking=prompt.config.get('enable_thinking', False),
+                thinking_budget_tokens=prompt.config.get('thinking_budget_tokens', 10000),
+            )
+        else:
+            initial_messages = messages
+            config = RunConfig(
+                model=model_name,
+                temperature=float(prompt.config.get('temperature', 0.3)),
+                max_iterations=1000,
+                name="图片模态特征提取研究",
+                enable_thinking=prompt.config.get('enable_thinking', False),
+                thinking_budget_tokens=prompt.config.get('thinking_budget_tokens', 10000),
+            )
+
+        while not should_exit:
+            if current_trace_id:
+                config.trace_id = current_trace_id
+
+            final_response = ""
+
+            # 检查 trace 状态
+            if current_trace_id and initial_messages is None:
+                check_trace = await store.get_trace(current_trace_id)
+                if check_trace and check_trace.status in ("completed", "failed"):
+                    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}")
+                    else:
+                        print(f"\n[Trace] ❌ 已失败: {check_trace.error_message}")
+                    current_sequence = check_trace.head_sequence
+
+                    menu_result = await show_interactive_menu(
+                        runner, current_trace_id, current_sequence, store
+                    )
+
+                    if menu_result["action"] == "stop":
+                        break
+                    elif menu_result["action"] == "continue":
+                        new_messages = menu_result.get("messages", [])
+                        if new_messages:
+                            initial_messages = new_messages
+                            config.after_sequence = menu_result.get("after_sequence")
+                        else:
+                            initial_messages = []
+                            config.after_sequence = None
+                        continue
+                    break
+
+                initial_messages = []
+
+            print(f"{'▶️ 开始执行...' if not current_trace_id else '▶️ 继续执行...'}")
+
+            # 执行 Agent
+            paused = False
+            try:
+                async for item in runner.run(messages=initial_messages, config=config):
+                    # 检查用户中断
+                    cmd = 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 show_interactive_menu(
+                            runner, current_trace_id, current_sequence, store
+                        )
+
+                        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:
+                                    config.after_sequence = after_seq
+                                paused = True
+                                break
+                            else:
+                                initial_messages = []
+                                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 tokens: {item.total_tokens}")
+                            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}")
+
+                                if tool_calls:
+                                    for tc in tool_calls:
+                                        tool_name = tc.get("function", {}).get("name", "unknown")
+                                        print(f"[Tool Call] 🛠️  {tool_name}")
+
+                        elif item.role == "tool":
+                            content = item.content
+                            if isinstance(content, dict):
+                                tool_name = content.get("tool_name", "unknown")
+                                print(f"[Tool Result] ✅ {tool_name}")
+                            if item.description:
+                                desc = item.description[:80] if len(item.description) > 80 else item.description
+                                print(f"  {desc}...")
+
+            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 show_interactive_menu(
+                    runner, current_trace_id, current_sequence, store
+                )
+
+                if menu_result["action"] == "stop":
+                    break
+                elif menu_result["action"] == "continue":
+                    new_messages = menu_result.get("messages", [])
+                    if new_messages:
+                        initial_messages = new_messages
+                        config.after_sequence = menu_result.get("after_sequence")
+                    else:
+                        initial_messages = []
+                        config.after_sequence = None
+                    continue
+            break
+
+    except KeyboardInterrupt:
+        print("\n\n用户中断 (Ctrl+C)")
+        if current_trace_id:
+            await runner.stop(current_trace_id)
+
+    # 输出结果
+    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())

+ 264 - 0
examples/find knowledge_test/skills/dimension_research.md

@@ -0,0 +1,264 @@
+# Skill: dimension_research
+
+## Purpose
+
+该 skill 用于在内容平台(主要是小红书)研究: 哪些 **Control Signals(控制信号 / 特征维度)** 适合表达输入的 **Image Dimensions(图片维度 / 需求)**。
+
+输入来自主 agent:
+- global_features
+- substances
+- forms
+- highlights
+这些输入代表不同类的 **Image Dimensions**。
+
+该 skill 的任务是:通过案例研究和创作者经验,为每个 Image Dimension 提炼:**可复用的 Control Signals。**
+
+注意:dimension_research 不负责寻找工具。,只负责:Image Dimension → Control Signal。
+
+---
+## Definitions
+
+Image Dimension(图片维度 / 需求)指图片中需要被结构化表达的方面。
+
+Control Signal(控制信号 / 特征维度)可以将 Image Dimension 转换到生成模型可使用的信号空间。
+
+Control Signal 不是某张图片的具体值, 是特征的表示方式。
+
+Feature Value(特征值)是 Control Signal 在具体图片上的实例化结果,由工具提取。
+dimension_research 不负责提取 Feature Value。
+
+# Input
+subagent 将收到来自主 agent 的 JSON:
+{
+  "global_features": [],
+  "substances": [],
+  "forms": [],
+  "highlights": [],
+  "goal": "寻找适合生成控制的多模态维度"
+}
+
+字段说明:
+global_features:图像整体特征
+substances:图像中的实体
+forms:实体的形式
+highlights:亮点信息
+---
+
+# Output
+输出文件: knowledge/dimension_research_result.json
+结构:
+{
+  "control_signal_candidates": [
+    {
+      "image_dimension": "多模态维度",
+      "level": "global / substance / form",
+      "control_signal": "特征维度",
+      "signal_type": "graph/...",
+      "representation": "json/...",
+      "reason": "选择的原因",
+      "generation_usage": "特征维度在还原过程中应该被怎样使用",
+      "evidence": [
+        {
+          "url": "...",
+          "snippet": "..."
+        }
+      ]
+    }
+  ]
+}
+
+每个特征维度必须说明:
+    - 所属层级(global / substance / form)
+    - 选择原因
+    - 表示方式
+    - 在生成中的作用
+    - 原始证据
+---
+# Search Strategy
+## 搜索目标
+
+优先寻找: 如何使用某个特征维度复刻某种视觉效果,也就是图片维度
+而不是: 某个工具如何使用
+
+---
+## 搜索重点
+
+优先寻找:
+- 复刻流程
+- 工作流
+- 案例中使用的视觉控制变量
+- 参数经验
+- 常见失败案例
+
+避免:
+- 单纯工具介绍
+- 无流程说明的教程
+- 纯理论讨论
+
+最终从案例中提炼可复用的控制信号
+
+---
+
+## 搜索循环
+执行循环:
+search  
+↓  
+分析案例  
+↓  
+提炼控制信号  
+↓  
+如果维度不清晰  
+↓  
+改写 query  
+↓  
+继续搜索  
+
+---
+# Query 扩展
+dimension_research 的 query 不应只有少量关键词。 必须通过系统化方式生成 20–40 个 query。
+
+获得的特征
+↓
+生成 Query Seeds
+↓
+扩展 Query
+↓
+执行搜索
+
+---
+## Query Seeds
+
+Query Seeds 来源:
+1. Substance(实体)
+2. Form(形式)
+3. Highlight(亮点)
+4. 搜索内容补充
+
+---
+## Query 模板
+Query **必须**由以下结构组成: seed + intent + context
+
+seed:来自输入特征
+intent:implementation / analysis / control / workflow / comparison /...
+context:教程 / workflow / 拆解 / 案例 / 参数 /...
+
+每个 seed 必须生成 5–8 个 query。
+总 query 数量建议 20–40。
+
+---
+## Query 过滤规则
+删除: 重复 query、过于相似 query、没有信息量 query、不符合结构组成 query
+
+---
+## Query 迭代规则
+**重要** :对每一个维度的搜索,都必须经过多次迭代。每一次搜索完成后,根据搜索的效果以及得到的知识,改进query的seed、intent和context,使用优化后的query继续搜索,直到得到的知识足够丰富,且都可以通过评估。
+**策略** :每一轮迭代,除了是对query的优化,也应该缩小搜索空间。比如:第一轮搜索宽泛的概念,从搜索结果中得到一些子领域的关键词,并运用到第二轮搜索。最终通过迭代,缩小范围,锁定一个或几个特征维度。
+
+---
+# Evaluation
+搜索完成后,对每个候选维度进行评估:
+是否:
+- 可用于生成模型控制
+- 可独立修改
+- 不包含原始像素
+- 可用于学习和复用
+- 不与原图高度相似
+
+避免:
+- 与原图高度相似的特征
+- 无法泛化的特征
+---
+# Dimension Research Documentation
+
+搜索完成后,必须生成一份研究记录,用于说明每一个维度是如何被发现并确定的。
+
+研究记录必须包含以下信息:
+
+1. Image Dimension 原始信息
+说明该维度来自图片的哪个方面。
+必须标明来源:
+- global feature
+- substance
+- form
+- highlight
+并记录制作表或亮点中的来源路径。
+
+2. Initial Hypothesis
+说明该维度最初的理解或假设:
+图片的这个方面可能由哪些视觉变量控制。
+
+3. Query Iteration
+记录 query 的修改过程。
+至少包含:
+- 使用的 seed
+- 每一轮 query
+- 搜索结果观察
+- 为什么需要修改 query
+- 新发现的关键词或概念
+
+4. Evidence
+记录关键搜索案例,包括:
+- URL
+- 简要说明
+- 从案例中得到的结论
+
+5. Control Signal Decision
+说明最终选择的控制信号:
+Image Dimension → Control Signal
+并解释:
+- 为什么选择该信号
+- 它如何控制视觉效果
+- 该信号如何用于生成模型
+
+6. Rejected Alternatives
+记录被否决的方案,并说明原因,例如:
+- 与原图过于相似
+- 无法泛化
+- 无法提取
+
+---
+# Stop Condition
+
+当搜索结果能够解释以下问题:
+- 为什么这种效果成立
+- 如何实现
+- 哪些因素控制
+即可停止搜索。
+
+# Search Language Strategy
+查询语言必须根据平台自动选择。
+
+---
+## 中文优先
+默认情况下: 优先使用中文查询。
+原因:
+- 内容平台主要为中文内容
+- 中文 query 更容易找到真实创作者经验
+
+适用平台:
+- 小红书
+- 知乎
+- B站
+- 中文博客
+
+---
+## 英文查询
+当搜索来源为:
+- GitHub
+- 论文
+- 技术博客
+- AI工具库
+- API文档
+才使用英文 query。
+
+---
+## 双语搜索策略
+若中文搜索结果不足: 可以进行第二轮搜索:
+
+---
+## Query 翻译规则
+从中文平台切换到英文平台时,将核心概念翻译为英文。
+
+---
+## 禁止行为
+禁止: 在中文平台使用纯英文 query。

+ 201 - 0
examples/find knowledge_test/skills/tool_research.md

@@ -0,0 +1,201 @@
+# Skill: tool_research
+## Purpose
+
+该 skill 用于为 **Control Signals(控制信号 / 特征维度)** 寻找最合适的提取方法。Control Signal 表示生成模型可消费的特征空间,
+
+tool_research 的任务是: 寻找能够从图片中提取这些特征维度的**Feature Values(特征值)** 的工具或方法。
+
+工具可以包括:
+
+- AI 模型
+- 图像算法
+- 开源工具
+- API
+- Python 图像处理库
+
+## Definitions
+
+Control Signal 是生成模型可消费的特征维度,描述了一个可逆的特征空间,而不是某张图片的具体值。
+
+Feature Value 是 Control Signal 在具体图片上的实例化结果,也是特征维度的具体维度值。Feature Value 由工具从图片中提取。
+
+---
+# Input
+来自主 agent 的 JSON:
+
+{
+  "dimensions": [
+    "pose",
+    "lighting_direction",
+    "color_palette",
+    "texture_pattern"
+  ]
+}
+
+注意: 这里的 dimensions 实际上表示 **Control Signals**,而不是 Image Dimensions。
+
+
+---
+
+# Output
+
+输出文件:
+
+knowledge/tool_research_result.json
+
+结构:
+
+{
+  "tools": [
+    {
+      "dimension": "pose",
+      "tool_name": "OpenPose",
+      "type": "model",
+      "input": "image",
+      "output": "skeleton keypoints",
+      "api": true,
+      "reason": "广泛用于人体姿态检测"
+    }
+  ]
+}
+
+
+---
+# Search Strategy
+
+## Stage 1 — Discover(工具发现)
+目标: 发现能够提取指定 Control Signal 的算法或模型。
+Query 结构: control_signal + extraction intent
+
+intent 可以包括:
+- estimation
+- detection
+- segmentation
+- analysis
+- extraction
+- recognition
+
+示例:
+pose keypoints extraction model  
+pose estimation model  
+lighting direction estimation image  
+color palette extraction python  
+texture analysis algorithm
+
+来源优先级:
+1. 小红书(创作者经验)
+2. 技术博客
+3. GitHub
+4. 论文
+该阶段目标: 获得候选工具名称。
+
+---
+## Stage 2 — Narrow(工具定位)
+目标:确认工具是否能够输出符合 Control Signal 的 Feature Value。
+Query 结构: tool_name + capability
+
+示例:
+- OpenPose keypoints output format
+- MediaPipe Pose python api
+- Segment Anything segmentation python
+- MiDaS depth estimation github
+
+需要确认:
+- 输入格式
+- 输出格式
+- API 支持
+- 是否支持批量处理
+- 是否开源
+
+---
+## Stage 3 — Verify(工具验证)
+目标: 验证工具可靠性、泛用性。可以参考评论反馈,或者搜索失败案例
+Query 结构: tool_name + evaluation intent
+
+intent 可以包括:
+- benchmark
+- comparison
+- vs
+- accuracy
+- limitations
+- latest release
+
+示例:
+OpenPose vs MediaPipe Pose
+MiDaS depth benchmark
+Segment Anything limitations
+MediaPipe Pose accuracy benchmark
+
+需要确认:
+- 精度
+- 适用场景
+- 是否存在明显限制
+
+---
+# Tool Selection Criteria
+工具必须满足:
+- 支持图像输入
+- 输出结构符合维度需求
+- 支持 API 或代码调用
+- 可用于批量处理
+
+优先选择:
+- 近期更新工具
+- 社区广泛使用工具
+- 评论中有大量好评的工具
+
+---
+# Verification
+
+工具选择后需要验证:
+例如:
+- pose: 骨架结构是否符合人体姿态
+- palette: 提取颜色是否接近原图
+- texture: 纹理是否符合视觉观察
+
+---
+# Stop Condition
+当每个维度找到,至少 2 个候选工具,即可停止搜索。
+
+---
+# Important Principle
+工具必须服务于维度。
+维度设计优先。
+
+# Search Language Strategy
+查询语言必须根据平台自动选择。
+
+---
+## 中文优先
+默认情况下: 优先使用中文查询。
+原因:
+- 内容平台主要为中文内容
+- 中文 query 更容易找到真实创作者经验
+
+适用平台:
+- 小红书
+- 知乎
+- B站
+- 中文博客
+
+---
+## 英文查询
+当搜索来源为:
+- GitHub
+- 论文
+- 技术博客
+- AI工具库
+- API文档
+才使用英文 query。
+
+---
+## 双语搜索策略
+若中文搜索结果不足: 可以进行第二轮搜索:
+
+---
+## Query 翻译规则
+从中文平台切换到英文平台时,将核心概念翻译为英文。
+
+---
+## 禁止行为
+禁止: 在中文平台使用纯英文 query。

+ 276 - 0
examples/find knowledge_test/test.prompt

@@ -0,0 +1,276 @@
+---
+model: claude-sonnet-4-6
+temperature: 0.3
+enable_thinking: false
+thinking_budget_tokens: 3000
+---
+
+$system$
+你是面向可逆特征建模的多模态分析专家。你的核心目标是:构建可逆的多模态特征空间,使生成模型能够基于特征重建原始图片。生成模型可以是任何AI模型或工具。
+你必须输出“可审计理由链”(Audit Rationale),覆盖每一步决策与行动。
+
+规则:
+1) 每次生成任何行动前,在text中先输出一个思维过程区块。
+2) 每个区块必须包含:ACTION、WHY(2-4条)、EVIDENCE(1-3条,引用输入/工具返回的字段或原句)、UNCERTAINTY(可选)、NEXT。
+3) WHY 必须是“面向读者的简短理由”,不得输出长篇内心独白;用可验证的依据支撑。
+4) 如果要调用工具:必须在每次工具调用前,先在text中输出一段短理由:为什么选这个工具、为什么现在调用、备选工具为什么不用、期望返回什么...
+5) 若没有足够依据:在 UNCERTAINTY 中说明缺口,并给出降低不确定性的下一步(通常是调用工具或改写 query)。
+
+$user$
+# 任务目标
+
+从 `input/` 目录中分析:
+- 原始图片
+- 制作表(包含"实质/形式"结构)
+- 亮点 JSON 数据
+- 制作点数据(包含实质结果,记录了图片组中反复出现的元素)
+
+**核心目的**:筛选并提取多模态特征维度,使其成为生成模型友好的控制信号。这些特征不仅用于还原图像,更重要的是用于学习、复用和建构全新内容。
+
+---
+
+# 一、核心概念
+
+## 1. 多模态维度:图片维度 / 需求维度
+本质是 **图片的维度**:图片的哪些方面需要提取多模态信息,因此它首先是 **需求(Need)**。
+
+Image Dimension 必须来源于:
+- 原始图片
+- 制作表(实质 / 形式结构)
+- 亮点 JSON
+- 制作点实质结果
+
+Image Dimension 只是说明:**图片的哪些方面需要被结构化表达。**
+
+---
+
+## 2. Control Signal(控制信号 / 特征维度)
+
+对每一个 Image Dimension,必须进一步提炼为:**生成模型可消费的控制信号。**
+
+Control Signal 描述的是:**特征空间 / 表示方式**,而不是某张图片的具体值。
+
+Control Signal 具有以下性质:
+- 可参数化
+- 可组合
+- 可独立修改
+- 可用于生成模型 conditioning
+
+例如:
+Image Dimension:构图结构
+Control Signal:layout grid + subject bbox
+
+---
+
+## 3. Feature Value(特征值)
+
+Feature Value 是:某个 Control Signal 在具体图片上的实例化结果,它的提取依赖工具。
+
+因此:
+
+Tool Research 的目标是
+**寻找能够从图片中提取 Feature Value 的工具。**
+
+## 4. 实质/形式双层模型
+
+所有多模态图片维度必须明确归属为"实质"或"形式":
+
+**实质(Substance)**:
+- 定义:图像中的某一个物体本身
+- 例如:一个人物、一个建筑、一个物品
+- 制作点实质结果中记录了图片组中多次出现的重要实质
+
+**形式(Form)**:
+- 定义:实质的各种属性,或图像整体的属性
+- 作用于实质的形式:物体的颜色、姿态、材质、光照等
+- 作用于图像整体的形式:构图、整体色调、风格等
+- 注意:即使某个形式(如构图)不属于任何具体实质,如果需要也要提取
+
+**基本规则**:先识别实质(物体本身),再推导形式(物体的属性)。
+
+---
+
+# 二、工作流程
+
+## 第一步:识别维度
+
+### 1. 分析输入数据
+- 查看原始图片,理解图片组的整体特征
+- 阅读制作表,理解实质/形式结构
+- **重点关注亮点数据**:亮点是图片表现力的核心
+- **重点关注制作点实质结果**:记录了图片组中反复出现的元素
+
+### 2. 识别需要提取多模态需求的维度
+- 维度的提取必须遵循层级顺序:全局环境 → 实体 → 实体属性。需要有所选择,筛选出最需要多模态特征值的维度
+- 先确定影响整个图像的全局段落(如构图、光照、整体色调),
+- 再确认核心实质(图片中的物体本身)**制作点实质结果中的元素具有优先级**:这些元素本身就是具有一致性要求的实质
+- 最后提取实体的形式(与制作表/亮点进行匹配)
+- 输出全局、实质、形式列表(与原始数据完全一致)
+
+## 第二步:筛选特征维度(控制信号)
+
+### 1. 调用sub agent搜索知识
+- 通过sub agent工具调用子agent,使用browser use工具,在小红书搜索对控制信号的筛选有帮助的知识,并保存在knowledge中
+- 向sub agent提供得到的特征,并要求调用skill/dimension_research.md,返回搜索结果
+- 将研究过程和发现保存在 `knowledge/` 目录,保留原始URL,具体策略参考skill
+
+### 2. 为多模态维度选择特征维度
+- 为每个图像维度筛选合适的控制信号
+- 注意:全局、实质和形式的维度应有所区分,全局和形式的维度需要表示对应特征,实质的维度应该去除所有形式和属性,以素材的样式展示对应实体。
+- 优先选择可逆性强、生成模型友好的特征维度
+- **前瞻性思考**:筛选时就要考虑每个特征在还原中如何被使用、起到什么作用
+- **避免过度相似**:不要提取与原图过于相似的特征,因为为了还原而还原没有价值,特征应该能用于学习、复用和建构全新内容
+- 撰写过程文档,详细解释每个维度的选择原因、用途等信息,以及利用搜索得到知识的方式和原因,对未利用到的知识也要有所解释。
+
+## 第三步:提取特征值
+
+### 1. 知识研究
+
+**调用sub agent搜索工具**:
+- 通过sub agent工具调用子agent,使用browser use工具,在小红书搜索对特征提取有帮助的工具的知识,并保存在knowledge中
+- 向sub agent提供需要提取的特征维度,并要求调用skill/tool_research.md,返回搜索结果
+- 将研究过程和发现保存在 `knowledge/` 目录,保留原始URL,具体策略参考skill
+
+### 2. 工具选择
+
+**评估标准**:
+- 发布时间:优先近期更新的工具(建议先确定当前时间,再判断工具是否近期更新)
+- 是否支持多模态处理
+- 是否支持批量处理
+- 是否支持API或可编程调用
+
+**选择建议**:
+- 优先选择更新、更通用、更多人使用或推荐的工具
+
+### 3. 特征提取
+
+**提取过程**:
+- 使用专业工具提取特征值
+- 每个维度单独建立文件夹:`output/features/维度名称/`
+
+**文件组织**:
+- 特征值文件(.png 或 .json)
+- mapping.json(记录维度与制作表的对应关系)
+
+**mapping.json 格式示例**:
+```json
+{
+  "dimension": "depth_map",
+  "mappings": [
+    {
+      "file": "img_1_segment_1.png",
+      "source_image": "input/img_1.jpg",
+      "segment": 1,
+      "category": "实质",
+      "feature": "空间深度结构"
+    }
+  ]
+}
+```
+
+**对应关系**:
+- 特征值必须与制作表精确对应
+- **必须与特定的一个或几个特征关联**,不能模糊处理,更不能只关联到亮点
+- **根据真实key串联完整路径**:从段落 → ... → 最后一层特征,确定提取到的多模态特征值属于谁
+- 如果是实质,直接关联到段落本身
+
+### 4. 输出研究报告
+- 总结筛选了哪些多模态维度及原因
+- **明确每个特征在还原中如何被使用、起到什么作用**
+- 说明每个特征的可逆性和重建价值
+- 说明每个特征如何用于学习、复用和建构全新内容
+- 记录工具选择理由和使用经验
+
+---
+
+# 三、核心原则
+
+## 解构原则
+
+**亮点驱动**:
+- 亮点数据是图片表现力的核心
+- 筛选维度时重点参考亮点
+- 对高权重段落细致处理
+
+**可逆性优先**:
+- 优先选择可逆性强的维度
+- 特征应该是生成模型友好的控制信号
+- 避免信息损失过大的表示
+- **避免提取与原图过于相似的特征**:特征应该是抽象的、可复用的,而不是原图的复制
+
+**价值导向**:
+- 特征不仅用于还原,更要用于学习、复用和建构全新内容
+- 为了还原而还原没有价值
+- 优先提取具有泛化能力和创造性价值的特征
+
+**适度解构**:
+- 维度数量适中,且相互独立,避免过度细分或过度简化
+- 若已有维度可以表达目标语义,不新增维度,新维度必须给出必要性说明
+- 根据图片组的复杂度灵活调整
+
+**一致性保证**(针对图片组):
+- 若图片组中存在重复实质,保持一致的表示方式
+- 例如:相同骨架比例、相同主色调范围、相同空间比例关系
+- 一致性优先级高于创意优先级
+
+**过程验证**:
+- 不盲目相信过程中结果的正确性
+- 对每一个步骤中得到的中间结果,都要根据要求,进行评估和验证。
+
+## 质量要求
+
+**禁止降级解决**:
+- 不允许为了方便而使用效果显著更差的简单方案
+
+**禁止平凡表示**:
+- 不允许只提供自然语言描述
+- 必须使用多模态提供超越语言的信息
+
+**禁止保存原始图片**:
+- 图片裁剪只能作为中间步骤
+- 最终必须提取多模态特征
+
+---
+
+# 四、还原与创造说明
+
+最终,负责还原的agent将获得:
+- 更新的制作表(包含多模态维度和值)
+- 各维度的特征文件
+
+还原agent将以生成式模型为主,使用这些特征作为控制信号重建图片。
+
+**更重要的是**:这些特征不仅用于还原原图,更要用于学习规律、复用特征、建构全新内容。因此,特征应该具有泛化能力和创造性价值,而不是原图的简单复制。
+
+---
+
+# 五、Subagent JSON Contract
+
+当需要调用 subagent 执行 skill 时,主 agent 必须先构造严格符合下述 schema 的 JSON,并作为 subagent 的唯一输入。
+    - A) dimension_research 输入 JSON(必须字段齐全)
+{
+  "global_features": [],
+  "substances": [],
+  "forms": [],
+  "highlights": [],
+  "goal": "string"
+}
+
+生成规则:
+- global_features:来自“亮点 + 制作表中能反应整体的形式”,用短词或短语,不要长句。
+- substances:来自“制作点实质结果 + 制作表中高权重实质”,去重后输出。
+- forms:来自“亮点 + 制作表中的形式”,去重后输出。
+- highlights:从亮点 JSON 中提取高权重亮点的简短描述(每条<=20字),用于提示检索语境。
+- goal:固定写为“寻找适合生成控制且可学习可复用的多模态特征维度”。
+
+    - B) tool_research 输入 JSON(必须字段齐全)
+{
+  "dimensions": []
+}
+
+生成规则:
+- dimensions:来自“筛选后的多模态维度清单”,必须是维度名称(snake_case 或短英文/拼音都可),不要写长描述。
+
+# 开始执行
+
+请根据上述原则,灵活分析 `input/` 目录下的数据,完成多模态特征的筛选和提取工作。

+ 7 - 0
examples/find knowledge_test/tool/__init__.py

@@ -0,0 +1,7 @@
+"""
+Find Knowledge 示例的自定义工具
+"""
+
+from .nanobanana import nanobanana
+
+__all__ = ["nanobanana"]

+ 487 - 0
examples/find knowledge_test/tool/nanobanana.py

@@ -0,0 +1,487 @@
+"""
+NanoBanana Tool - 图像生成
+
+通用图像生成工具,可以接受自然语言描述和/或图像输入,生成新的图像。
+支持通过 OpenRouter 调用 Gemini 2.5 Flash Image 模型。
+"""
+
+import base64
+import json
+import mimetypes
+import os
+import re
+from pathlib import Path
+from typing import Optional, Dict, Any, List, Tuple
+
+import httpx
+from dotenv import load_dotenv
+
+from agent.tools import tool, ToolResult
+
+OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
+DEFAULT_TIMEOUT = 120.0
+
+DEFAULT_IMAGE_PROMPT = "根据输入生成图像。"
+
+DEFAULT_IMAGE_MODEL_CANDIDATES = [
+    # "google/gemini-2.5-flash-image",
+    "google/gemini-3.1-flash-image-preview"
+]
+
+
+def _resolve_api_key() -> Optional[str]:
+    """优先读取环境变量,缺失时尝试从 .env 加载。"""
+    api_key = os.getenv("OPENROUTER_API_KEY") or os.getenv("OPEN_ROUTER_API_KEY")
+    if api_key:
+        return api_key
+
+    load_dotenv()
+    return os.getenv("OPENROUTER_API_KEY") or os.getenv("OPEN_ROUTER_API_KEY")
+
+
+def _image_to_data_url(image_path: Path) -> str:
+    """将图片文件编码为 data URL。"""
+    mime_type = mimetypes.guess_type(str(image_path))[0] or "application/octet-stream"
+    raw = image_path.read_bytes()
+    b64 = base64.b64encode(raw).decode("utf-8")
+    return f"data:{mime_type};base64,{b64}"
+
+
+def _safe_json_parse(content: str) -> Dict[str, Any]:
+    """尽量从模型文本中提取 JSON。"""
+    try:
+        return json.loads(content)
+    except json.JSONDecodeError:
+        start = content.find("{")
+        end = content.rfind("}")
+        if start != -1 and end != -1 and end > start:
+            candidate = content[start:end + 1]
+            return json.loads(candidate)
+        raise
+
+
+def _extract_data_url_images(message: Dict[str, Any]) -> List[Tuple[str, str]]:
+    """
+    从 OpenRouter 响应消息中提取 data URL 图片。
+
+    Returns:
+        List[(mime_type, base64_data)]
+    """
+    extracted: List[Tuple[str, str]] = []
+
+    # 官方文档中的主要位置:message.images[]
+    for img in message.get("images", []) or []:
+        if not isinstance(img, dict):
+            continue
+        if img.get("type") != "image_url":
+            continue
+        data_url = ((img.get("image_url") or {}).get("url") or "").strip()
+        if not data_url.startswith("data:"):
+            continue
+        m = re.match(r"^data:([^;]+);base64,(.+)$", data_url, flags=re.DOTALL)
+        if not m:
+            continue
+        extracted.append((m.group(1), m.group(2)))
+
+    # 兼容某些模型可能把 image_url 放在 content 数组中
+    content = message.get("content")
+    if isinstance(content, list):
+        for part in content:
+            if not isinstance(part, dict):
+                continue
+            if part.get("type") != "image_url":
+                continue
+            data_url = ((part.get("image_url") or {}).get("url") or "").strip()
+            if not data_url.startswith("data:"):
+                continue
+            m = re.match(r"^data:([^;]+);base64,(.+)$", data_url, flags=re.DOTALL)
+            if not m:
+                continue
+            extracted.append((m.group(1), m.group(2)))
+
+    return extracted
+
+
+def _extract_image_refs(choice: Dict[str, Any], message: Dict[str, Any]) -> List[Dict[str, str]]:
+    """
+    尝试从不同响应格式中提取图片引用。
+
+    返回格式:
+    - {"kind": "data_url", "value": "data:image/png;base64,..."}
+    - {"kind": "base64", "value": "...", "mime_type": "image/png"}
+    - {"kind": "url", "value": "https://..."}
+    """
+    refs: List[Dict[str, str]] = []
+
+    # 1) 标准 message.images
+    for img in message.get("images", []) or []:
+        if not isinstance(img, dict):
+            continue
+        # image_url 结构
+        data_url = ((img.get("image_url") or {}).get("url") or "").strip()
+        if data_url.startswith("data:"):
+            refs.append({"kind": "data_url", "value": data_url})
+            continue
+        if data_url.startswith("http"):
+            refs.append({"kind": "url", "value": data_url})
+            continue
+
+        # 兼容 base64 字段
+        b64 = (img.get("b64_json") or img.get("base64") or "").strip()
+        if b64:
+            refs.append({"kind": "base64", "value": b64, "mime_type": img.get("mime_type", "image/png")})
+
+    # 2) 某些格式可能在 choice.images
+    for img in choice.get("images", []) or []:
+        if not isinstance(img, dict):
+            continue
+        data_url = ((img.get("image_url") or {}).get("url") or "").strip()
+        if data_url.startswith("data:"):
+            refs.append({"kind": "data_url", "value": data_url})
+            continue
+        if data_url.startswith("http"):
+            refs.append({"kind": "url", "value": data_url})
+            continue
+        b64 = (img.get("b64_json") or img.get("base64") or "").strip()
+        if b64:
+            refs.append({"kind": "base64", "value": b64, "mime_type": img.get("mime_type", "image/png")})
+
+    # 3) content 数组里的 image_url
+    content = message.get("content")
+    if isinstance(content, list):
+        for part in content:
+            if not isinstance(part, dict):
+                continue
+            if part.get("type") != "image_url":
+                continue
+            url = ((part.get("image_url") or {}).get("url") or "").strip()
+            if url.startswith("data:"):
+                refs.append({"kind": "data_url", "value": url})
+            elif url.startswith("http"):
+                refs.append({"kind": "url", "value": url})
+
+    # 4) 极端兼容:文本中可能出现 data:image 或 http 图片 URL
+    if isinstance(content, str):
+        # data URL
+        for m in re.finditer(r"(data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/=]+)", content):
+            refs.append({"kind": "data_url", "value": m.group(1)})
+        # http(s) 图片链接
+        for m in re.finditer(r"(https?://\S+\.(?:png|jpg|jpeg|webp))", content, flags=re.IGNORECASE):
+            refs.append({"kind": "url", "value": m.group(1)})
+
+    return refs
+
+
+def _mime_to_ext(mime_type: str) -> str:
+    """MIME 类型映射到扩展名。"""
+    mapping = {
+        "image/png": ".png",
+        "image/jpeg": ".jpg",
+        "image/webp": ".webp",
+    }
+    return mapping.get(mime_type.lower(), ".png")
+
+
+def _normalize_model_id(model_id: str) -> str:
+    """
+    规范化常见误写模型 ID,减少无效重试。
+    """
+    if not model_id:
+        return model_id
+    m = model_id.strip()
+    # 常见误写:gemini/gemini-xxx -> google/gemini-xxx
+    if m.startswith("gemini/"):
+        m = "google/" + m.split("/", 1)[1]
+    # 常见顺序误写:preview-image -> image
+    if "gemini-2.5-flash-preview-image" in m:
+        m = m.replace("gemini-2.5-flash-preview-image", "gemini-2.5-flash-image")
+    # 兼容旧 ID 到当前可用 ID
+    if "gemini-2.5-flash-image-preview" in m:
+        m = m.replace("gemini-2.5-flash-image-preview", "gemini-2.5-flash-image")
+    return m
+
+
+@tool(description="通用的图像生成工具,根据文本描述和/或参考图像生成图片。输出格式只有图片,不能输出文字。")
+async def nanobanana(
+    image_path: str = "",
+    image_paths: Optional[List[str]] = None,
+    prompt: Optional[str] = None,
+    model: Optional[str] = None,
+    max_tokens: int = 1200,
+    image_output_path: Optional[str] = None,
+) -> ToolResult:
+    """
+    通用图像生成工具,可以接受自然语言描述和/或图像输入,生成新的图像。
+
+    Args:
+        image_path: 输入图片路径(单图模式,可选)
+        image_paths: 输入图片路径列表(多图模式,可选)
+        prompt: 自定义生成描述(可选,默认使用通用prompt)
+        model: OpenRouter 模型名(可选,默认使用 gemini-2.5-flash-image)
+        max_tokens: 最大输出 token
+        image_output_path: 生成图片保存路径(可选)
+
+    Returns:
+        ToolResult: 包含生成的图片路径
+    """
+    raw_paths: List[str] = []
+    if image_paths:
+        raw_paths.extend(image_paths)
+    if image_path:
+        raw_paths.append(image_path)
+
+    # 图像输入是可选的,但如果提供了就需要验证
+    input_paths: List[Path] = []
+    if raw_paths:
+        # 去重并检查路径
+        unique_raw: List[str] = []
+        seen = set()
+        for p in raw_paths:
+            if p and p not in seen:
+                unique_raw.append(p)
+                seen.add(p)
+
+        input_paths = [Path(p) for p in unique_raw]
+        invalid = [str(p) for p in input_paths if (not p.exists() or not p.is_file())]
+        if invalid:
+            return ToolResult(
+                title="NanoBanana 生成失败",
+                output="",
+                error=f"以下图片不存在或不可读: {invalid}",
+            )
+
+    api_key = _resolve_api_key()
+    if not api_key:
+        return ToolResult(
+            title="NanoBanana 生成失败",
+            output="",
+            error="未找到 OpenRouter API Key,请设置 OPENROUTER_API_KEY 或 OPEN_ROUTER_API_KEY",
+        )
+
+    user_prompt = prompt or DEFAULT_IMAGE_PROMPT
+
+    # 编码图像(如果有)
+    image_data_urls = []
+    if input_paths:
+        try:
+            image_data_urls = [_image_to_data_url(p) for p in input_paths]
+        except Exception as e:
+            return ToolResult(
+                title="NanoBanana 生成失败",
+                output="",
+                error=f"图片编码失败: {e}",
+            )
+
+    user_content: List[Dict[str, Any]] = [{"type": "text", "text": user_prompt}]
+    for u in image_data_urls:
+        user_content.append({"type": "image_url", "image_url": {"url": u}})
+
+    payload: Dict[str, Any] = {
+        "messages": [
+            {
+                "role": "system",
+                "content": "你是图像生成助手。请根据用户的描述和/或输入图像生成新的图像。",
+            },
+            {
+                "role": "user",
+                "content": user_content,
+            },
+        ],
+        "temperature": 0.2,
+        "max_tokens": max_tokens,
+        "modalities": ["image", "text"],
+    }
+
+    headers = {
+        "Authorization": f"Bearer {api_key}",
+        "Content-Type": "application/json",
+        "HTTP-Referer": "https://local-agent",
+        "X-Title": "Agent NanoBanana Tool",
+    }
+
+    endpoint = f"{OPENROUTER_BASE_URL}/chat/completions"
+
+    # 自动尝试多个可用模型,减少 404/invalid model 影响
+    candidates: List[str] = []
+    if model:
+        candidates.append(_normalize_model_id(model))
+    if env_model := os.getenv("NANOBANANA_IMAGE_MODEL"):
+        candidates.append(_normalize_model_id(env_model))
+    candidates.extend([_normalize_model_id(x) for x in DEFAULT_IMAGE_MODEL_CANDIDATES])
+    # 去重并保持顺序
+    dedup: List[str] = []
+    seen = set()
+    for m in candidates:
+        if m and m not in seen:
+            dedup.append(m)
+            seen.add(m)
+    candidates = dedup
+
+    data: Optional[Dict[str, Any]] = None
+    used_model: Optional[str] = None
+    errors: List[Dict[str, Any]] = []
+
+    for cand in candidates:
+        modality_attempts: List[Optional[List[str]]] = [["image", "text"], ["image"], None]
+
+        for mods in modality_attempts:
+            trial_payload = dict(payload)
+            trial_payload["model"] = cand
+
+            if mods is None:
+                trial_payload.pop("modalities", None)
+            else:
+                trial_payload["modalities"] = mods
+
+            try:
+                async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
+                    resp = await client.post(endpoint, json=trial_payload, headers=headers)
+                    resp.raise_for_status()
+                    data = resp.json()
+                    used_model = cand
+                    break
+            except httpx.HTTPStatusError as e:
+                errors.append({
+                    "model": cand,
+                    "modalities": mods,
+                    "status_code": e.response.status_code,
+                    "body": e.response.text[:600],
+                })
+                continue
+            except Exception as e:
+                errors.append({
+                    "model": cand,
+                    "modalities": mods,
+                    "status_code": None,
+                    "body": str(e)[:600],
+                })
+                continue
+
+        if data is not None:
+            break
+
+    if data is None:
+        return ToolResult(
+            title="NanoBanana 生成失败",
+            output=json.dumps({"attempted_models": candidates, "errors": errors}, ensure_ascii=False, indent=2),
+            long_term_memory="All candidate models failed for this request",
+            metadata={"attempted_models": candidates, "errors": errors},
+        )
+
+    chosen_model = used_model or candidates[0]
+
+    choices = data.get("choices") or []
+    message = choices[0].get("message", {}) if choices else {}
+
+    # 提取生成的图像
+    refs = _extract_image_refs(choices[0] if choices else {}, message)
+    if not refs:
+        content = message.get("content")
+        preview = ""
+        if isinstance(content, str):
+            preview = content[:500]
+        elif isinstance(content, list):
+            preview = json.dumps(content[:3], ensure_ascii=False)[:500]
+
+        return ToolResult(
+            title="NanoBanana 生成失败",
+            output=json.dumps(data, ensure_ascii=False, indent=2),
+            error="模型未返回可解析图片(未在 message.images/choice.images/content 中发现图片)",
+            metadata={
+                "model": chosen_model,
+                "choice_keys": list((choices[0] if choices else {}).keys()),
+                "message_keys": list(message.keys()) if isinstance(message, dict) else [],
+                "content_preview": preview,
+            },
+        )
+
+    output_paths: List[str] = []
+    if image_output_path:
+        base_path = Path(image_output_path)
+    else:
+        if len(input_paths) > 1:
+            base_path = input_paths[0].parent / "set_generated.png"
+        else:
+            base_path = input_paths[0].parent / f"{input_paths[0].stem}_generated.png"
+    base_path.parent.mkdir(parents=True, exist_ok=True)
+
+    for idx, ref in enumerate(refs):
+        kind = ref.get("kind", "")
+        mime_type = "image/png"
+        raw_bytes: Optional[bytes] = None
+
+        if kind == "data_url":
+            m = re.match(r"^data:([^;]+);base64,(.+)$", ref.get("value", ""), flags=re.DOTALL)
+            if not m:
+                continue
+            mime_type = m.group(1)
+            raw_bytes = base64.b64decode(m.group(2))
+        elif kind == "base64":
+            mime_type = ref.get("mime_type", "image/png")
+            raw_bytes = base64.b64decode(ref.get("value", ""))
+        elif kind == "url":
+            url = ref.get("value", "")
+            try:
+                with httpx.Client(timeout=DEFAULT_TIMEOUT) as client:
+                    r = client.get(url)
+                    r.raise_for_status()
+                    raw_bytes = r.content
+                    mime_type = r.headers.get("content-type", "image/png").split(";")[0]
+            except Exception:
+                continue
+        else:
+            continue
+
+        if not raw_bytes:
+            continue
+
+        ext = _mime_to_ext(mime_type)
+        if len(refs) == 1:
+            target = base_path
+            if target.suffix.lower() not in [".png", ".jpg", ".jpeg", ".webp"]:
+                target = target.with_suffix(ext)
+        else:
+            stem = base_path.stem
+            target = base_path.with_name(f"{stem}_{idx+1}{ext}")
+        try:
+            target.write_bytes(raw_bytes)
+            output_paths.append(str(target))
+        except Exception as e:
+            return ToolResult(
+                title="NanoBanana 生成失败",
+                output="",
+                error=f"写入生成图片失败: {e}",
+                metadata={"model": chosen_model},
+            )
+
+    if not output_paths:
+        return ToolResult(
+            title="NanoBanana 生成失败",
+            output=json.dumps(data, ensure_ascii=False, indent=2),
+            error="检测到图片引用但写入失败(可能是无效 base64 或 URL 不可访问)",
+            metadata={"model": chosen_model, "ref_count": len(refs)},
+        )
+
+    usage = data.get("usage", {})
+    prompt_tokens = usage.get("prompt_tokens") or usage.get("input_tokens", 0)
+    completion_tokens = usage.get("completion_tokens") or usage.get("output_tokens", 0)
+    summary = {
+        "model": chosen_model,
+        "input_images": [str(p) for p in input_paths],
+        "input_count": len(input_paths),
+        "generated_images": output_paths,
+        "prompt_tokens": prompt_tokens,
+        "completion_tokens": completion_tokens,
+    }
+    return ToolResult(
+        title="NanoBanana 图片生成完成",
+        output=json.dumps({"summary": summary}, ensure_ascii=False, indent=2),
+        long_term_memory=f"Generated {len(output_paths)} image(s) from {len(input_paths)} input image(s) using {chosen_model}",
+        attachments=output_paths,
+        metadata=summary,
+        tool_usage={
+            "model": chosen_model,
+            "prompt_tokens": prompt_tokens,
+            "completion_tokens": completion_tokens,
+        }
+    )

+ 30 - 0
examples/test_browser/README.md

@@ -0,0 +1,30 @@
+# 浏览器工具测试
+
+这个文件夹用于测试和诊断浏览器工具的问题。
+
+## 使用方法
+
+```bash
+cd /Users/elksmmx/Desktop/agent\ 2.10/Agent/examples/test_browser
+python3 test_browser.py
+```
+
+## 测试内容
+
+1. **headless=True 模式** - 无头浏览器模式,不显示窗口
+2. **headless=False 模式** - 有头浏览器模式,显示窗口
+
+## 预期结果
+
+- 如果两个测试都通过,说明浏览器工具配置正常
+- 如果只有 headless=True 通过,说明需要使用无头模式
+- 如果都失败,需要检查 Chrome 安装和权限
+
+## 常见问题
+
+### JSONDecodeError: Expecting value
+
+这通常是 CDP 连接时序问题,解决方案:
+1. 使用 headless=True 模式
+2. 增加初始化延迟
+3. 确保没有其他 Chrome 进程占用资源

+ 85 - 0
examples/test_browser/SOLUTION.md

@@ -0,0 +1,85 @@
+# 浏览器工具问题解决方案
+
+## 问题描述
+
+browser-use 启动时报错:
+```
+JSONDecodeError: Expecting value: line 1 column 1 (char 0)
+CDP connected but failed to re-create CDP session
+```
+
+## 根本原因
+
+**环境变量中的代理设置**导致 httpx 库无法访问 localhost 的 CDP 端点。
+
+即使关闭了 VPN,系统环境变量中可能仍然保留了代理配置,导致:
+- httpx 尝试通过代理访问 `http://localhost:9222`
+- 代理无法处理 localhost 请求
+- 请求超时或返回空响应
+- 导致 JSONDecodeError
+
+## 解决方案
+
+### 方案1:设置 NO_PROXY 环境变量(推荐)
+
+在你的 Python 脚本开头添加:
+
+```python
+import os
+
+# 禁止对所有地址使用代理
+os.environ['NO_PROXY'] = '*'
+os.environ['no_proxy'] = '*'
+```
+
+### 方案2:在 run.py 中启用
+
+在 `examples/find knowledge/run.py` 第15行,取消注释:
+
+```python
+# 原来(第15行):
+# os.environ.setdefault("no_proxy", "*")
+
+# 改为:
+os.environ.setdefault("no_proxy", "*")
+```
+
+### 方案3:永久设置(可选)
+
+在 `~/.zshrc` 或 `~/.bash_profile` 中添加:
+
+```bash
+export NO_PROXY="*"
+export no_proxy="*"
+```
+
+然后重启终端。
+
+## 验证
+
+运行测试脚本验证修复:
+
+```bash
+cd examples/test_browser
+python3 test_browser_fixed.py
+```
+
+应该看到:
+```
+✅ 浏览器会话启动成功
+✅ 导航成功
+```
+
+## 为什么同事可以运行
+
+可能的原因:
+1. 同事没有安装 VPN 或代理软件
+2. 同事的环境变量中没有代理配置
+3. 同事的代码已经包含了 NO_PROXY 设置
+
+## 技术细节
+
+- httpx 默认会读取环境变量 `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`
+- 当设置了代理但没有设置 NO_PROXY 时,localhost 请求也会被代理
+- 大多数代理不支持 localhost,导致请求失败
+- 设置 `NO_PROXY=*` 告诉 httpx 不要对任何地址使用代理

+ 136 - 0
examples/test_browser/diagnose_cdp.py

@@ -0,0 +1,136 @@
+"""
+底层 CDP 诊断脚本
+直接测试 Chrome 的 CDP 端点响应
+"""
+import subprocess
+import time
+import httpx
+import asyncio
+
+
+def start_chrome_with_cdp(port=9222, headless=True):
+    """启动 Chrome 并返回进程"""
+    cmd = [
+        '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
+        f'--remote-debugging-port={port}',
+        '--user-data-dir=/tmp/test-cdp-diagnosis',
+        '--no-first-run',
+        '--no-default-browser-check',
+    ]
+
+    if headless:
+        cmd.append('--headless=new')
+
+    cmd.append('about:blank')
+
+    print(f"启动命令: {' '.join(cmd)}")
+
+    proc = subprocess.Popen(
+        cmd,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE
+    )
+
+    return proc
+
+
+async def test_cdp_endpoint(port=9222, max_retries=10):
+    """测试 CDP 端点是否响应"""
+    url = f'http://localhost:{port}/json/version'
+
+    print(f"\n测试 CDP 端点: {url}")
+    print("="*60)
+
+    async with httpx.AsyncClient(timeout=10.0) as client:
+        for i in range(max_retries):
+            try:
+                print(f"尝试 {i+1}/{max_retries}...", end=' ')
+                resp = await client.get(url)
+
+                print(f"✅ 状态码: {resp.status_code}")
+                print(f"内容长度: {len(resp.content)} 字节")
+                print(f"内容: {resp.text[:200]}")
+
+                if resp.status_code == 200 and len(resp.content) > 0:
+                    data = resp.json()
+                    print(f"\n✅ CDP 端点正常响应")
+                    print(f"Browser: {data.get('Browser')}")
+                    print(f"WebSocket URL: {data.get('webSocketDebuggerUrl')}")
+                    return True
+
+            except httpx.TimeoutException:
+                print("❌ 超时")
+            except httpx.ConnectError:
+                print("❌ 连接失败")
+            except Exception as e:
+                print(f"❌ 错误: {e}")
+
+            await asyncio.sleep(1)
+
+    print("\n❌ CDP 端点无法响应")
+    return False
+
+
+async def main():
+    print("Chrome CDP 底层诊断")
+    print("="*60)
+
+    # 测试 1: headless=True
+    print("\n【测试 1】headless=True")
+    proc1 = start_chrome_with_cdp(port=9222, headless=True)
+    print("等待 Chrome 启动...")
+    time.sleep(3)
+
+    result1 = await test_cdp_endpoint(port=9222)
+
+    print("\n终止 Chrome...")
+    proc1.terminate()
+    proc1.wait(timeout=5)
+
+    # 清理
+    subprocess.run(['rm', '-rf', '/tmp/test-cdp-diagnosis'],
+                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+    await asyncio.sleep(2)
+
+    # 测试 2: headless=False
+    print("\n" + "="*60)
+    print("【测试 2】headless=False")
+    proc2 = start_chrome_with_cdp(port=9223, headless=False)
+    print("等待 Chrome 启动...")
+    time.sleep(3)
+
+    result2 = await test_cdp_endpoint(port=9223)
+
+    print("\n终止 Chrome...")
+    proc2.terminate()
+    proc2.wait(timeout=5)
+
+    # 清理
+    subprocess.run(['rm', '-rf', '/tmp/test-cdp-diagnosis'],
+                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+    # 总结
+    print("\n" + "="*60)
+    print("诊断总结")
+    print("="*60)
+    print(f"headless=True:  {'✅ CDP 正常' if result1 else '❌ CDP 失败'}")
+    print(f"headless=False: {'✅ CDP 正常' if result2 else '❌ CDP 失败'}")
+    print("="*60)
+
+    if not result1 and not result2:
+        print("\n⚠️  问题分析:")
+        print("Chrome 的 CDP 端点无法响应 HTTP 请求")
+        print("\n可能原因:")
+        print("1. Chrome 被防火墙或安全软件阻止")
+        print("2. macOS 权限问题")
+        print("3. Chrome 版本与 CDP 协议不兼容")
+        print("4. 系统资源不足导致 Chrome 启动缓慢")
+        print("\n建议:")
+        print("1. 检查系统偏好设置 > 安全性与隐私 > 防火墙")
+        print("2. 尝试重新安装 Chrome")
+        print("3. 检查是否有杀毒软件拦截")
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 52 - 0
examples/test_browser/simple_cdp_test.sh

@@ -0,0 +1,52 @@
+#!/bin/bash
+
+echo "简单 CDP 测试"
+echo "=============================="
+
+# 启动 Chrome
+echo "启动 Chrome..."
+"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
+  --headless=new \
+  --remote-debugging-port=9222 \
+  --user-data-dir=/tmp/simple-cdp-test \
+  about:blank > /tmp/chrome-output.log 2>&1 &
+
+CHROME_PID=$!
+echo "Chrome PID: $CHROME_PID"
+
+# 等待启动
+echo "等待 5 秒..."
+sleep 5
+
+# 测试 CDP 端点
+echo ""
+echo "测试 CDP 端点..."
+echo "=============================="
+
+for i in {1..5}; do
+    echo "尝试 $i/5..."
+    curl -s --max-time 2 http://localhost:9222/json/version
+
+    if [ $? -eq 0 ]; then
+        echo ""
+        echo "✅ CDP 端点响应成功"
+        break
+    else
+        echo "❌ 失败"
+        sleep 1
+    fi
+done
+
+echo ""
+echo "Chrome 输出日志:"
+echo "=============================="
+cat /tmp/chrome-output.log
+
+# 清理
+echo ""
+echo "清理..."
+kill $CHROME_PID 2>/dev/null
+rm -rf /tmp/simple-cdp-test
+rm -f /tmp/chrome-output.log
+
+echo "完成"

+ 98 - 0
examples/test_browser/test_browser.py

@@ -0,0 +1,98 @@
+"""
+浏览器工具测试脚本
+用于诊断 browser-use 在不同配置下的行为
+"""
+import asyncio
+import sys
+from pathlib import Path
+
+# 添加项目根目录到路径
+sys.path.insert(0, str(Path(__file__).parent.parent.parent))
+
+from agent.tools.builtin.browser.baseClass import (
+    init_browser_session,
+    cleanup_browser_session,
+    browser_navigate_to_url
+)
+
+
+async def test_headless_true():
+    """测试 headless=True 模式"""
+    print("\n" + "="*60)
+    print("测试 1: headless=True 模式")
+    print("="*60)
+
+    try:
+        session, tools = await init_browser_session(
+            browser_type="local",
+            headless=True
+        )
+        print("✅ 浏览器会话初始化成功 (headless=True)")
+
+        # 测试导航
+        result = await browser_navigate_to_url("https://www.example.com")
+        print(f"✅ 导航成功: {result}")
+
+        await cleanup_browser_session()
+        print("✅ 清理成功")
+        return True
+
+    except Exception as e:
+        print(f"❌ 失败: {e}")
+        import traceback
+        traceback.print_exc()
+        return False
+
+
+async def test_headless_false():
+    """测试 headless=False 模式"""
+    print("\n" + "="*60)
+    print("测试 2: headless=False 模式")
+    print("="*60)
+
+    try:
+        session, tools = await init_browser_session(
+            browser_type="local",
+            headless=False
+        )
+        print("✅ 浏览器会话初始化成功 (headless=False)")
+
+        # 测试导航
+        result = await browser_navigate_to_url("https://www.example.com")
+        print(f"✅ 导航成功: {result}")
+
+        await cleanup_browser_session()
+        print("✅ 清理成功")
+        return True
+
+    except Exception as e:
+        print(f"❌ 失败: {e}")
+        import traceback
+        traceback.print_exc()
+        return False
+
+
+async def main():
+    print("浏览器工具诊断测试")
+    print("="*60)
+
+    # 测试 headless=True
+    result1 = await test_headless_true()
+
+    # 等待一下,确保资源释放
+    await asyncio.sleep(2)
+
+    # 测试 headless=False
+    result2 = await test_headless_false()
+
+    # 总结
+    print("\n" + "="*60)
+    print("测试总结")
+    print("="*60)
+    print(f"headless=True:  {'✅ 通过' if result1 else '❌ 失败'}")
+    print(f"headless=False: {'✅ 通过' if result2 else '❌ 失败'}")
+    print("="*60)
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 80 - 0
examples/test_browser/test_browser_fixed.py

@@ -0,0 +1,80 @@
+"""
+浏览器工具测试 - 修复版
+使用 trust_env=False 解决代理环境变量问题
+"""
+import asyncio
+import sys
+import os
+from pathlib import Path
+
+# 添加项目根目录到路径
+sys.path.insert(0, str(Path(__file__).parent.parent.parent))
+
+# 关键修复:设置环境变量,让 httpx 忽略代理设置
+os.environ['NO_PROXY'] = '*'
+os.environ['no_proxy'] = '*'
+
+from browser_use import BrowserSession
+from browser_use.tools.service import Tools
+
+
+async def test_with_fix():
+    """测试:使用修复后的配置"""
+    print("\n" + "="*60)
+    print("测试: 修复代理问题后的浏览器启动")
+    print("="*60)
+
+    try:
+        session = BrowserSession(
+            headless=True,
+            is_local=True,
+            executable_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
+        )
+
+        print("等待 Chrome 启动...")
+        await asyncio.sleep(3)
+
+        print("启动会话...")
+        await session.start()
+
+        print("✅ 浏览器会话启动成功")
+
+        # 测试导航
+        tools = Tools()
+        print("测试导航到 example.com...")
+        await tools.navigate(url="https://www.example.com", browser_session=session)
+        print("✅ 导航成功")
+
+        await session.close()
+        return True
+
+    except Exception as e:
+        print(f"❌ 失败: {e}")
+        import traceback
+        traceback.print_exc()
+        return False
+
+
+async def main():
+    print("浏览器工具测试 - 修复版")
+    print("="*60)
+    print("修复方法: 设置 NO_PROXY 环境变量")
+    print("="*60)
+
+    result = await test_with_fix()
+
+    print("\n" + "="*60)
+    print("测试结果")
+    print("="*60)
+    print(f"状态: {'✅ 成功' if result else '❌ 失败'}")
+    print("="*60)
+
+    if result:
+        print("\n💡 解决方案确认:")
+        print("在代码开头添加以下环境变量设置即可解决问题:")
+        print("  os.environ['NO_PROXY'] = '*'")
+        print("  os.environ['no_proxy'] = '*'")
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 158 - 0
examples/test_browser/test_browser_v2.py

@@ -0,0 +1,158 @@
+"""
+浏览器工具测试脚本 V2
+测试不同的配置和workaround
+"""
+import asyncio
+import sys
+from pathlib import Path
+
+# 添加项目根目录到路径
+sys.path.insert(0, str(Path(__file__).parent.parent.parent))
+
+from browser_use import BrowserSession
+import httpx
+
+
+async def test_direct_browser_use():
+    """直接使用 browser-use,不通过框架封装"""
+    print("\n" + "="*60)
+    print("测试: 直接使用 browser-use (headless=True)")
+    print("="*60)
+
+    try:
+        # 方案1: 增加启动延迟
+        session = BrowserSession(
+            headless=True,
+            is_local=True,
+            executable_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
+        )
+
+        print("等待 Chrome 启动...")
+        await asyncio.sleep(3)  # 增加等待时间
+
+        print("启动会话...")
+        await session.start()
+
+        print("✅ 成功启动")
+
+        # 测试导航
+        from browser_use.tools.service import Tools
+        tools = Tools()
+        await tools.navigate(url="https://www.example.com", browser_session=session)
+        print("✅ 导航成功")
+
+        await session.close()
+        return True
+
+    except Exception as e:
+        print(f"❌ 失败: {e}")
+        import traceback
+        traceback.print_exc()
+        return False
+
+
+async def test_with_custom_timeout():
+    """测试:使用自定义超时设置"""
+    print("\n" + "="*60)
+    print("测试: 自定义 HTTP 超时 (headless=True)")
+    print("="*60)
+
+    try:
+        # 创建自定义 HTTP 客户端
+        http_client = httpx.AsyncClient(timeout=30.0)
+
+        session = BrowserSession(
+            headless=True,
+            is_local=True,
+            executable_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
+        )
+
+        # 尝试替换内部 HTTP 客户端
+        if hasattr(session, '_http_client'):
+            session._http_client = http_client
+
+        print("等待 Chrome 启动...")
+        await asyncio.sleep(3)
+
+        print("启动会话...")
+        await session.start()
+
+        print("✅ 成功启动")
+
+        await session.close()
+        await http_client.aclose()
+        return True
+
+    except Exception as e:
+        print(f"❌ 失败: {e}")
+        import traceback
+        traceback.print_exc()
+        return False
+
+
+async def test_headless_false_simple():
+    """测试:headless=False 简单模式"""
+    print("\n" + "="*60)
+    print("测试: headless=False (简单模式)")
+    print("="*60)
+
+    try:
+        session = BrowserSession(
+            headless=False,
+            is_local=True,
+            executable_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
+        )
+
+        print("等待 Chrome 启动...")
+        await asyncio.sleep(3)
+
+        print("启动会话...")
+        await session.start()
+
+        print("✅ 成功启动")
+
+        # 测试导航
+        from browser_use.tools.service import Tools
+        tools = Tools()
+        await tools.navigate(url="https://www.example.com", browser_session=session)
+        print("✅ 导航成功")
+
+        await session.close()
+        return True
+
+    except Exception as e:
+        print(f"❌ 失败: {e}")
+        import traceback
+        traceback.print_exc()
+        return False
+
+
+async def main():
+    print("浏览器工具诊断测试 V2")
+    print("="*60)
+
+    results = {}
+
+    # 测试1: 直接使用 browser-use
+    results['direct'] = await test_direct_browser_use()
+    await asyncio.sleep(2)
+
+    # 测试2: 自定义超时
+    results['custom_timeout'] = await test_with_custom_timeout()
+    await asyncio.sleep(2)
+
+    # 测试3: headless=False
+    results['headless_false'] = await test_headless_false_simple()
+
+    # 总结
+    print("\n" + "="*60)
+    print("测试总结")
+    print("="*60)
+    for name, result in results.items():
+        status = '✅ 通过' if result else '❌ 失败'
+        print(f"{name:20s}: {status}")
+    print("="*60)
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 143 - 0
examples/test_browser/test_httpx_vs_curl.py

@@ -0,0 +1,143 @@
+"""
+对比 curl 和 httpx 访问 CDP 端点
+找出为什么 httpx 会失败
+"""
+import subprocess
+import time
+import httpx
+import asyncio
+
+
+def start_chrome():
+    """启动 Chrome"""
+    proc = subprocess.Popen([
+        '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
+        '--headless=new',
+        '--remote-debugging-port=9222',
+        '--user-data-dir=/tmp/httpx-test-cdp',
+        'about:blank'
+    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+    print("等待 Chrome 启动...")
+    time.sleep(3)
+    return proc
+
+
+async def test_httpx_default():
+    """测试 httpx 默认配置"""
+    print("\n【测试 1】httpx 默认配置")
+    print("="*60)
+
+    try:
+        async with httpx.AsyncClient() as client:
+            print("发送请求...")
+            resp = await client.get('http://localhost:9222/json/version')
+            print(f"✅ 成功: {resp.status_code}")
+            print(f"内容: {resp.text[:100]}")
+            return True
+    except Exception as e:
+        print(f"❌ 失败: {e}")
+        return False
+
+
+async def test_httpx_long_timeout():
+    """测试 httpx 长超时"""
+    print("\n【测试 2】httpx 长超时 (30秒)")
+    print("="*60)
+
+    try:
+        async with httpx.AsyncClient(timeout=30.0) as client:
+            print("发送请求...")
+            resp = await client.get('http://localhost:9222/json/version')
+            print(f"✅ 成功: {resp.status_code}")
+            print(f"内容: {resp.text[:100]}")
+            return True
+    except Exception as e:
+        print(f"❌ 失败: {e}")
+        return False
+
+
+async def test_httpx_no_proxy():
+    """测试 httpx 禁用代理"""
+    print("\n【测试 3】httpx 禁用代理")
+    print("="*60)
+
+    try:
+        # 明确禁用代理
+        async with httpx.AsyncClient(
+            timeout=30.0,
+            proxies={}  # 空字典表示不使用代理
+        ) as client:
+            print("发送请求...")
+            resp = await client.get('http://localhost:9222/json/version')
+            print(f"✅ 成功: {resp.status_code}")
+            print(f"内容: {resp.text[:100]}")
+            return True
+    except Exception as e:
+        print(f"❌ 失败: {e}")
+        return False
+
+
+async def test_httpx_trust_env_false():
+    """测试 httpx trust_env=False"""
+    print("\n【测试 4】httpx trust_env=False (忽略环境变量)")
+    print("="*60)
+
+    try:
+        # trust_env=False 会忽略环境变量中的代理设置
+        async with httpx.AsyncClient(
+            timeout=30.0,
+            trust_env=False
+        ) as client:
+            print("发送请求...")
+            resp = await client.get('http://localhost:9222/json/version')
+            print(f"✅ 成功: {resp.status_code}")
+            print(f"内容: {resp.text[:100]}")
+            return True
+    except Exception as e:
+        print(f"❌ 失败: {e}")
+        return False
+
+
+async def main():
+    print("httpx vs curl 对比测试")
+    print("="*60)
+
+    # 启动 Chrome
+    proc = start_chrome()
+
+    # 运行测试
+    results = {
+        'default': await test_httpx_default(),
+        'long_timeout': await test_httpx_long_timeout(),
+        'no_proxy': await test_httpx_no_proxy(),
+        'trust_env_false': await test_httpx_trust_env_false(),
+    }
+
+    # 清理
+    print("\n清理...")
+    proc.terminate()
+    proc.wait(timeout=5)
+    subprocess.run(['rm', '-rf', '/tmp/httpx-test-cdp'],
+                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+    # 总结
+    print("\n" + "="*60)
+    print("测试总结")
+    print("="*60)
+    for name, result in results.items():
+        status = '✅ 成功' if result else '❌ 失败'
+        print(f"{name:20s}: {status}")
+    print("="*60)
+
+    # 给出建议
+    if results['trust_env_false'] and not results['default']:
+        print("\n💡 发现问题:")
+        print("httpx 默认会读取环境变量中的代理设置")
+        print("即使关闭了 VPN,环境变量可能仍然存在")
+        print("\n解决方案:")
+        print("在 browser-use 初始化时设置 trust_env=False")
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 66 - 0
examples/test_browser/test_search_posts.py

@@ -0,0 +1,66 @@
+"""
+测试 search_posts 工具
+"""
+import asyncio
+import sys
+import os
+from pathlib import Path
+
+# 添加项目根目录到路径
+sys.path.insert(0, str(Path(__file__).parent.parent.parent))
+
+# 测试1: 不设置NO_PROXY
+print("="*60)
+print("测试 1: 不设置 NO_PROXY (应该失败)")
+print("="*60)
+
+from agent.tools.builtin.search import search_posts
+
+async def test_without_fix():
+    result = await search_posts(keyword="多模态特征", channel="xhs", max_count=3)
+    print(f"标题: {result.title}")
+    print(f"输出长度: {len(result.output)}")
+    if result.error:
+        print(f"错误: {result.error}")
+    return result.error is None
+
+result1 = asyncio.run(test_without_fix())
+print(f"结果: {'✅ 成功' if result1 else '❌ 失败'}")
+
+# 测试2: 设置NO_PROXY
+print("\n" + "="*60)
+print("测试 2: 设置 NO_PROXY (应该成功)")
+print("="*60)
+
+os.environ['NO_PROXY'] = '*'
+os.environ['no_proxy'] = '*'
+
+# 重新导入以应用环境变量
+import importlib
+import agent.tools.builtin.search
+importlib.reload(agent.tools.builtin.search)
+from agent.tools.builtin.search import search_posts as search_posts_fixed
+
+async def test_with_fix():
+    result = await search_posts_fixed(keyword="多模态特征", channel="xhs", max_count=3)
+    print(f"标题: {result.title}")
+    print(f"输出长度: {len(result.output)}")
+    if result.error:
+        print(f"错误: {result.error}")
+    else:
+        print(f"前100字符: {result.output[:100]}")
+    return result.error is None
+
+result2 = asyncio.run(test_with_fix())
+print(f"结果: {'✅ 成功' if result2 else '❌ 失败'}")
+
+# 总结
+print("\n" + "="*60)
+print("总结")
+print("="*60)
+print(f"不设置 NO_PROXY: {'✅ 成功' if result1 else '❌ 失败'}")
+print(f"设置 NO_PROXY:   {'✅ 成功' if result2 else '❌ 失败'}")
+
+if not result1 and result2:
+    print("\n💡 确认:search_posts 工具也受代理环境变量影响")
+    print("解决方案:在 run.py 开头设置 NO_PROXY 环境变量")

+ 82 - 0
examples/test_nanobanana/README.md

@@ -0,0 +1,82 @@
+# NanoBanana 工具测试
+
+测试 nanobanana 图像生成工具的功能。
+
+## 工具描述
+
+**nanobanana**: 通用的图像生成工具,根据文本描述和/或参考图像生成图片。输出格式只有图片,不能输出文字。
+
+## 测试场景
+
+### 测试1: 纯文本生成图像
+- 输入:文本描述
+- 输出:生成的图像
+- 示例:生成一只可爱的橙色小猫
+
+### 测试2: 基于参考图像生成
+- 输入:参考图像 + 文本描述
+- 输出:转换后的图像
+- 示例:将图片转换为油画风格
+
+## 使用方法
+
+### 1. 准备环境
+
+确保已设置 OpenRouter API Key:
+```bash
+export OPENROUTER_API_KEY="your-api-key"
+# 或
+export OPEN_ROUTER_API_KEY="your-api-key"
+```
+
+### 2. 准备测试图片(可选)
+
+如果要测试图像转换功能,请在 `input/` 目录中放置测试图片:
+```bash
+cp your-test-image.png examples/test_nanobanana/input/
+```
+
+### 3. 运行测试
+
+```bash
+cd /Users/elksmmx/Desktop/agent\ 2.10/Agent
+python examples/test_nanobanana/run.py
+```
+
+## 输出
+
+生成的图片将保存在 `output/` 目录中:
+- `output/cat.png` - 测试1生成的图片
+- `output/transformed.png` - 测试2生成的图片
+
+## 目录结构
+
+```
+test_nanobanana/
+├── README.md           # 本文件
+├── run.py             # 测试脚本
+├── tool/              # 自定义工具
+│   ├── __init__.py
+│   └── nanobanana.py  # nanobanana工具实现
+├── input/             # 测试图片输入目录
+├── output/            # 生成图片输出目录
+└── .trace/            # 执行轨迹(自动生成)
+```
+
+## 注意事项
+
+1. **API Key**: 必须配置 OpenRouter API Key
+2. **模型**: 默认使用 `google/gemini-2.5-flash-image` 模型
+3. **费用**: 图像生成会消耗 API 额度
+4. **输出格式**: 工具只输出图片,不输出文字描述
+
+## 常见问题
+
+### Q: 提示 "未找到 OpenRouter API Key"
+A: 请设置环境变量 `OPENROUTER_API_KEY` 或 `OPEN_ROUTER_API_KEY`
+
+### Q: 生成失败,提示模型不可用
+A: 检查 OpenRouter 账户是否有足够额度,或尝试其他模型
+
+### Q: 想要测试其他模型
+A: 修改 `run.py` 中的模型参数,或在调用时指定 `model` 参数

+ 263 - 0
examples/test_nanobanana/run.py

@@ -0,0 +1,263 @@
+"""
+NanoBanana 工具测试脚本
+
+测试场景:
+1. 纯文本生成图像
+2. 基于第一个测试生成的图像,转换为不同风格
+"""
+
+import asyncio
+import sys
+from pathlib import Path
+from dotenv import load_dotenv
+
+# 添加项目根目录到 Python 路径
+project_root = Path(__file__).parent.parent.parent
+sys.path.insert(0, str(project_root))
+
+# 从 Agent 根目录加载 .env 文件
+env_path = project_root / ".env"
+if env_path.exists():
+    load_dotenv(env_path)
+    print(f"✅ 已加载环境变量: {env_path}")
+else:
+    print(f"⚠️  未找到 .env 文件: {env_path}")
+
+from agent.core.runner import AgentRunner, RunConfig
+from agent.trace.store import FileSystemTraceStore
+from agent.trace.models import Trace, Message
+from agent.llm.openrouter import create_openrouter_llm_call
+
+
+async def test_text_to_image(output_dir: Path) -> Path:
+    """测试1: 纯文本生成图像
+
+    Returns:
+        生成的图片路径
+    """
+    print("\n" + "="*60)
+    print("测试1: 纯文本生成图像")
+    print("="*60)
+
+    # 导入自定义工具
+    import examples.test_nanobanana.tool
+
+    store = FileSystemTraceStore(base_path=".trace")
+    llm_call = create_openrouter_llm_call(model="anthropic/claude-sonnet-4.5")
+
+    runner = AgentRunner(
+        trace_store=store,
+        llm_call=llm_call,
+    )
+
+    config = RunConfig(
+        model="anthropic/claude-sonnet-4.5",
+        temperature=0.3,
+        max_iterations=10,
+    )
+
+    # 使用绝对路径
+    output_path = output_dir / "cat.png"
+
+    task = f"""
+请使用 nanobanana 工具生成一张图片:
+- 内容:一只可爱的橙色小猫,坐在窗台上看着外面的雨
+- 风格:水彩画风格
+- 保存到:{output_path}
+
+注意:路径必须使用绝对路径。
+"""
+
+    print(f"\n任务: 生成小猫图片")
+    print(f"输出路径: {output_path}\n")
+
+    # 将任务转换为消息格式
+    messages = [{"role": "user", "content": task}]
+
+    generated_image = None
+    try:
+        async for item in runner.run(messages=messages, config=config):
+            # 处理 Trace 对象(整体状态变化)
+            if isinstance(item, Trace):
+                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 tokens: {item.total_tokens}")
+                    print(f"  - Total cost: ${item.total_cost:.4f}")
+                    if output_path.exists():
+                        generated_image = output_path
+                        print(f"  - 生成的图片: {generated_image}")
+                elif item.status == "failed":
+                    print(f"\n[Trace] ❌ 失败: {item.error_message}")
+                elif item.status == "stopped":
+                    print(f"\n[Trace] ⏸️ 已停止")
+
+            # 处理 Message 对象(执行过程)
+            elif isinstance(item, Message):
+                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:
+                            # 纯文本回复(最终响应)
+                            print(f"\n[Response] {text}")
+                        elif tool_calls:
+                            # 工具调用
+                            tool_names = [tc.get("function", {}).get("name") for tc in tool_calls]
+                            print(f"[Tool Call] {', '.join(tool_names)}")
+
+                elif item.role == "tool":
+                    # 工具结果
+                    if isinstance(item.content, dict):
+                        tool_name = item.content.get("tool_name", "unknown")
+                        print(f"[Tool Result] {tool_name}")
+    except Exception as e:
+        print(f"\n❌ 测试失败: {e}")
+        import traceback
+        traceback.print_exc()
+
+    return generated_image
+
+
+async def test_image_to_image(input_image: Path, output_dir: Path):
+    """测试2: 基于参考图像生成新图像
+
+    Args:
+        input_image: 参考图像路径(来自测试1)
+        output_dir: 输出目录
+    """
+    print("\n" + "="*60)
+    print("测试2: 基于参考图像生成新图像")
+    print("="*60)
+
+    if not input_image or not input_image.exists():
+        print("⚠️  跳过测试2: 测试1没有成功生成图片")
+        return
+
+    print(f"参考图像: {input_image}")
+
+    # 导入自定义工具
+    import examples.test_nanobanana.tool
+
+    store = FileSystemTraceStore(base_path=".trace")
+    llm_call = create_openrouter_llm_call(model="anthropic/claude-sonnet-4.5")
+
+    runner = AgentRunner(
+        trace_store=store,
+        llm_call=llm_call,
+    )
+
+    config = RunConfig(
+        model="anthropic/claude-sonnet-4.5",
+        temperature=0.3,
+        max_iterations=10,
+    )
+
+    # 使用绝对路径
+    output_path = output_dir / "cat_oil_painting.png"
+
+    task = f"""
+请使用 nanobanana 工具,基于参考图像生成一张新图片:
+- 参考图像:{input_image}
+- 要求:保持小猫和窗台的元素,但改变风格为油画风格
+- 保存到:{output_path}
+
+注意:路径必须使用绝对路径。
+"""
+
+    print(f"\n任务: 将小猫图片转换为油画风格")
+    print(f"输出路径: {output_path}\n")
+
+    # 将任务转换为消息格式
+    messages = [{"role": "user", "content": task}]
+
+    try:
+        async for item in runner.run(messages=messages, config=config):
+            # 处理 Trace 对象(整体状态变化)
+            if isinstance(item, Trace):
+                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 tokens: {item.total_tokens}")
+                    print(f"  - Total cost: ${item.total_cost:.4f}")
+                    if output_path.exists():
+                        print(f"  - 生成的图片: {output_path}")
+                elif item.status == "failed":
+                    print(f"\n[Trace] ❌ 失败: {item.error_message}")
+                elif item.status == "stopped":
+                    print(f"\n[Trace] ⏸️ 已停止")
+
+            # 处理 Message 对象(执行过程)
+            elif isinstance(item, Message):
+                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:
+                            # 纯文本回复(最终响应)
+                            print(f"\n[Response] {text}")
+                        elif tool_calls:
+                            # 工具调用
+                            tool_names = [tc.get("function", {}).get("name") for tc in tool_calls]
+                            print(f"[Tool Call] {', '.join(tool_names)}")
+
+                elif item.role == "tool":
+                    # 工具结果
+                    if isinstance(item.content, dict):
+                        tool_name = item.content.get("tool_name", "unknown")
+                        print(f"[Tool Result] {tool_name}")
+    except Exception as e:
+        print(f"\n❌ 测试失败: {e}")
+        import traceback
+        traceback.print_exc()
+
+
+async def main():
+    """运行所有测试"""
+    print("\n" + "="*60)
+    print("NanoBanana 工具测试")
+    print("="*60)
+
+    # 创建输出目录(使用绝对路径)
+    output_dir = Path(__file__).parent / "output"
+    output_dir.mkdir(exist_ok=True)
+    print(f"输出目录: {output_dir.absolute()}\n")
+
+    try:
+        # 测试1: 纯文本生成
+        generated_image = await test_text_to_image(output_dir)
+
+        # 测试2: 图像转换(基于测试1的结果)
+        if generated_image:
+            await test_image_to_image(generated_image, output_dir)
+        else:
+            print("\n⚠️  跳过测试2: 测试1未成功生成图片")
+
+        print("\n" + "="*60)
+        print("测试完成!")
+        print("="*60)
+        print(f"\n查看生成的图片:")
+        if generated_image and generated_image.exists():
+            print(f"  1. {generated_image}")
+        oil_painting = output_dir / "cat_oil_painting.png"
+        if oil_painting.exists():
+            print(f"  2. {oil_painting}")
+
+    except KeyboardInterrupt:
+        print("\n\n⚠️  测试被用户中断")
+    except Exception as e:
+        print(f"\n\n❌ 测试失败: {e}")
+        import traceback
+        traceback.print_exc()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 9 - 0
examples/test_nanobanana/test.prompt

@@ -0,0 +1,9 @@
+# NanoBanana 工具测试 Prompt
+
+请使用 nanobanana 工具测试图像生成功能。
+
+## 测试任务
+
+1. 生成一张图片:一只可爱的橙色小猫坐在窗台上看雨,水彩画风格
+2. 将生成的图片保存到 examples/test_nanobanana/output/test_cat.png
+

+ 7 - 0
examples/test_nanobanana/tool/__init__.py

@@ -0,0 +1,7 @@
+"""
+Test NanoBanana 示例的自定义工具
+"""
+
+from .nanobanana import nanobanana
+
+__all__ = ["nanobanana"]

+ 488 - 0
examples/test_nanobanana/tool/nanobanana.py

@@ -0,0 +1,488 @@
+"""
+NanoBanana Tool - 图像生成
+
+通用图像生成工具,可以接受自然语言描述和/或图像输入,生成新的图像。
+支持通过 OpenRouter 调用 Gemini 2.5 Flash Image 模型。
+"""
+
+import base64
+import json
+import mimetypes
+import os
+import re
+from pathlib import Path
+from typing import Optional, Dict, Any, List, Tuple
+
+import httpx
+from dotenv import load_dotenv
+
+from agent.tools import tool, ToolResult
+
+OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
+DEFAULT_TIMEOUT = 120.0
+
+DEFAULT_IMAGE_PROMPT = "根据输入生成图像。"
+
+DEFAULT_IMAGE_MODEL_CANDIDATES = [
+    # "google/gemini-2.5-flash-image",
+    "google/gemini-3-pro-image-preview"
+    # "google/gemini-3.1-flash-image-preview"
+]
+
+
+def _resolve_api_key() -> Optional[str]:
+    """优先读取环境变量,缺失时尝试从 .env 加载。"""
+    api_key = os.getenv("OPENROUTER_API_KEY") or os.getenv("OPEN_ROUTER_API_KEY")
+    if api_key:
+        return api_key
+
+    load_dotenv()
+    return os.getenv("OPENROUTER_API_KEY") or os.getenv("OPEN_ROUTER_API_KEY")
+
+
+def _image_to_data_url(image_path: Path) -> str:
+    """将图片文件编码为 data URL。"""
+    mime_type = mimetypes.guess_type(str(image_path))[0] or "application/octet-stream"
+    raw = image_path.read_bytes()
+    b64 = base64.b64encode(raw).decode("utf-8")
+    return f"data:{mime_type};base64,{b64}"
+
+
+def _safe_json_parse(content: str) -> Dict[str, Any]:
+    """尽量从模型文本中提取 JSON。"""
+    try:
+        return json.loads(content)
+    except json.JSONDecodeError:
+        start = content.find("{")
+        end = content.rfind("}")
+        if start != -1 and end != -1 and end > start:
+            candidate = content[start:end + 1]
+            return json.loads(candidate)
+        raise
+
+
+def _extract_data_url_images(message: Dict[str, Any]) -> List[Tuple[str, str]]:
+    """
+    从 OpenRouter 响应消息中提取 data URL 图片。
+
+    Returns:
+        List[(mime_type, base64_data)]
+    """
+    extracted: List[Tuple[str, str]] = []
+
+    # 官方文档中的主要位置:message.images[]
+    for img in message.get("images", []) or []:
+        if not isinstance(img, dict):
+            continue
+        if img.get("type") != "image_url":
+            continue
+        data_url = ((img.get("image_url") or {}).get("url") or "").strip()
+        if not data_url.startswith("data:"):
+            continue
+        m = re.match(r"^data:([^;]+);base64,(.+)$", data_url, flags=re.DOTALL)
+        if not m:
+            continue
+        extracted.append((m.group(1), m.group(2)))
+
+    # 兼容某些模型可能把 image_url 放在 content 数组中
+    content = message.get("content")
+    if isinstance(content, list):
+        for part in content:
+            if not isinstance(part, dict):
+                continue
+            if part.get("type") != "image_url":
+                continue
+            data_url = ((part.get("image_url") or {}).get("url") or "").strip()
+            if not data_url.startswith("data:"):
+                continue
+            m = re.match(r"^data:([^;]+);base64,(.+)$", data_url, flags=re.DOTALL)
+            if not m:
+                continue
+            extracted.append((m.group(1), m.group(2)))
+
+    return extracted
+
+
+def _extract_image_refs(choice: Dict[str, Any], message: Dict[str, Any]) -> List[Dict[str, str]]:
+    """
+    尝试从不同响应格式中提取图片引用。
+
+    返回格式:
+    - {"kind": "data_url", "value": "data:image/png;base64,..."}
+    - {"kind": "base64", "value": "...", "mime_type": "image/png"}
+    - {"kind": "url", "value": "https://..."}
+    """
+    refs: List[Dict[str, str]] = []
+
+    # 1) 标准 message.images
+    for img in message.get("images", []) or []:
+        if not isinstance(img, dict):
+            continue
+        # image_url 结构
+        data_url = ((img.get("image_url") or {}).get("url") or "").strip()
+        if data_url.startswith("data:"):
+            refs.append({"kind": "data_url", "value": data_url})
+            continue
+        if data_url.startswith("http"):
+            refs.append({"kind": "url", "value": data_url})
+            continue
+
+        # 兼容 base64 字段
+        b64 = (img.get("b64_json") or img.get("base64") or "").strip()
+        if b64:
+            refs.append({"kind": "base64", "value": b64, "mime_type": img.get("mime_type", "image/png")})
+
+    # 2) 某些格式可能在 choice.images
+    for img in choice.get("images", []) or []:
+        if not isinstance(img, dict):
+            continue
+        data_url = ((img.get("image_url") or {}).get("url") or "").strip()
+        if data_url.startswith("data:"):
+            refs.append({"kind": "data_url", "value": data_url})
+            continue
+        if data_url.startswith("http"):
+            refs.append({"kind": "url", "value": data_url})
+            continue
+        b64 = (img.get("b64_json") or img.get("base64") or "").strip()
+        if b64:
+            refs.append({"kind": "base64", "value": b64, "mime_type": img.get("mime_type", "image/png")})
+
+    # 3) content 数组里的 image_url
+    content = message.get("content")
+    if isinstance(content, list):
+        for part in content:
+            if not isinstance(part, dict):
+                continue
+            if part.get("type") != "image_url":
+                continue
+            url = ((part.get("image_url") or {}).get("url") or "").strip()
+            if url.startswith("data:"):
+                refs.append({"kind": "data_url", "value": url})
+            elif url.startswith("http"):
+                refs.append({"kind": "url", "value": url})
+
+    # 4) 极端兼容:文本中可能出现 data:image 或 http 图片 URL
+    if isinstance(content, str):
+        # data URL
+        for m in re.finditer(r"(data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/=]+)", content):
+            refs.append({"kind": "data_url", "value": m.group(1)})
+        # http(s) 图片链接
+        for m in re.finditer(r"(https?://\S+\.(?:png|jpg|jpeg|webp))", content, flags=re.IGNORECASE):
+            refs.append({"kind": "url", "value": m.group(1)})
+
+    return refs
+
+
+def _mime_to_ext(mime_type: str) -> str:
+    """MIME 类型映射到扩展名。"""
+    mapping = {
+        "image/png": ".png",
+        "image/jpeg": ".jpg",
+        "image/webp": ".webp",
+    }
+    return mapping.get(mime_type.lower(), ".png")
+
+
+def _normalize_model_id(model_id: str) -> str:
+    """
+    规范化常见误写模型 ID,减少无效重试。
+    """
+    if not model_id:
+        return model_id
+    m = model_id.strip()
+    # 常见误写:gemini/gemini-xxx -> google/gemini-xxx
+    if m.startswith("gemini/"):
+        m = "google/" + m.split("/", 1)[1]
+    # 常见顺序误写:preview-image -> image
+    if "gemini-2.5-flash-preview-image" in m:
+        m = m.replace("gemini-2.5-flash-preview-image", "gemini-2.5-flash-image")
+    # 兼容旧 ID 到当前可用 ID
+    if "gemini-2.5-flash-image-preview" in m:
+        m = m.replace("gemini-2.5-flash-image-preview", "gemini-2.5-flash-image")
+    return m
+
+
+@tool(description="通用的图像生成工具,根据文本描述和/或参考图像生成图片。输出格式只有图片,不能输出文字。")
+async def nanobanana(
+    image_path: str = "",
+    image_paths: Optional[List[str]] = None,
+    prompt: Optional[str] = None,
+    model: Optional[str] = None,
+    max_tokens: int = 1200,
+    image_output_path: Optional[str] = None,
+) -> ToolResult:
+    """
+    通用图像生成工具,可以接受自然语言描述和/或图像输入,生成新的图像。
+
+    Args:
+        image_path: 输入图片路径(单图模式,可选)
+        image_paths: 输入图片路径列表(多图模式,可选)
+        prompt: 自定义生成描述(可选,默认使用通用prompt)
+        model: OpenRouter 模型名(可选,默认使用 gemini-2.5-flash-image)
+        max_tokens: 最大输出 token
+        image_output_path: 生成图片保存路径(可选)
+
+    Returns:
+        ToolResult: 包含生成的图片路径
+    """
+    raw_paths: List[str] = []
+    if image_paths:
+        raw_paths.extend(image_paths)
+    if image_path:
+        raw_paths.append(image_path)
+
+    # 图像输入是可选的,但如果提供了就需要验证
+    input_paths: List[Path] = []
+    if raw_paths:
+        # 去重并检查路径
+        unique_raw: List[str] = []
+        seen = set()
+        for p in raw_paths:
+            if p and p not in seen:
+                unique_raw.append(p)
+                seen.add(p)
+
+        input_paths = [Path(p) for p in unique_raw]
+        invalid = [str(p) for p in input_paths if (not p.exists() or not p.is_file())]
+        if invalid:
+            return ToolResult(
+                title="NanoBanana 生成失败",
+                output="",
+                error=f"以下图片不存在或不可读: {invalid}",
+            )
+
+    api_key = _resolve_api_key()
+    if not api_key:
+        return ToolResult(
+            title="NanoBanana 生成失败",
+            output="",
+            error="未找到 OpenRouter API Key,请设置 OPENROUTER_API_KEY 或 OPEN_ROUTER_API_KEY",
+        )
+
+    user_prompt = prompt or DEFAULT_IMAGE_PROMPT
+
+    # 编码图像(如果有)
+    image_data_urls = []
+    if input_paths:
+        try:
+            image_data_urls = [_image_to_data_url(p) for p in input_paths]
+        except Exception as e:
+            return ToolResult(
+                title="NanoBanana 生成失败",
+                output="",
+                error=f"图片编码失败: {e}",
+            )
+
+    user_content: List[Dict[str, Any]] = [{"type": "text", "text": user_prompt}]
+    for u in image_data_urls:
+        user_content.append({"type": "image_url", "image_url": {"url": u}})
+
+    payload: Dict[str, Any] = {
+        "messages": [
+            {
+                "role": "system",
+                "content": "你是图像生成助手。请根据用户的描述和/或输入图像生成新的图像。",
+            },
+            {
+                "role": "user",
+                "content": user_content,
+            },
+        ],
+        "temperature": 0.2,
+        "max_tokens": max_tokens,
+        "modalities": ["image", "text"],
+    }
+
+    headers = {
+        "Authorization": f"Bearer {api_key}",
+        "Content-Type": "application/json",
+        "HTTP-Referer": "https://local-agent",
+        "X-Title": "Agent NanoBanana Tool",
+    }
+
+    endpoint = f"{OPENROUTER_BASE_URL}/chat/completions"
+
+    # 自动尝试多个可用模型,减少 404/invalid model 影响
+    candidates: List[str] = []
+    if model:
+        candidates.append(_normalize_model_id(model))
+    if env_model := os.getenv("NANOBANANA_IMAGE_MODEL"):
+        candidates.append(_normalize_model_id(env_model))
+    candidates.extend([_normalize_model_id(x) for x in DEFAULT_IMAGE_MODEL_CANDIDATES])
+    # 去重并保持顺序
+    dedup: List[str] = []
+    seen = set()
+    for m in candidates:
+        if m and m not in seen:
+            dedup.append(m)
+            seen.add(m)
+    candidates = dedup
+
+    data: Optional[Dict[str, Any]] = None
+    used_model: Optional[str] = None
+    errors: List[Dict[str, Any]] = []
+
+    for cand in candidates:
+        modality_attempts: List[Optional[List[str]]] = [["image", "text"], ["image"], None]
+
+        for mods in modality_attempts:
+            trial_payload = dict(payload)
+            trial_payload["model"] = cand
+
+            if mods is None:
+                trial_payload.pop("modalities", None)
+            else:
+                trial_payload["modalities"] = mods
+
+            try:
+                async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
+                    resp = await client.post(endpoint, json=trial_payload, headers=headers)
+                    resp.raise_for_status()
+                    data = resp.json()
+                    used_model = cand
+                    break
+            except httpx.HTTPStatusError as e:
+                errors.append({
+                    "model": cand,
+                    "modalities": mods,
+                    "status_code": e.response.status_code,
+                    "body": e.response.text[:600],
+                })
+                continue
+            except Exception as e:
+                errors.append({
+                    "model": cand,
+                    "modalities": mods,
+                    "status_code": None,
+                    "body": str(e)[:600],
+                })
+                continue
+
+        if data is not None:
+            break
+
+    if data is None:
+        return ToolResult(
+            title="NanoBanana 生成失败",
+            output=json.dumps({"attempted_models": candidates, "errors": errors}, ensure_ascii=False, indent=2),
+            long_term_memory="All candidate models failed for this request",
+            metadata={"attempted_models": candidates, "errors": errors},
+        )
+
+    chosen_model = used_model or candidates[0]
+
+    choices = data.get("choices") or []
+    message = choices[0].get("message", {}) if choices else {}
+
+    # 提取生成的图像
+    refs = _extract_image_refs(choices[0] if choices else {}, message)
+    if not refs:
+        content = message.get("content")
+        preview = ""
+        if isinstance(content, str):
+            preview = content[:500]
+        elif isinstance(content, list):
+            preview = json.dumps(content[:3], ensure_ascii=False)[:500]
+
+        return ToolResult(
+            title="NanoBanana 生成失败",
+            output=json.dumps(data, ensure_ascii=False, indent=2),
+            error="模型未返回可解析图片(未在 message.images/choice.images/content 中发现图片)",
+            metadata={
+                "model": chosen_model,
+                "choice_keys": list((choices[0] if choices else {}).keys()),
+                "message_keys": list(message.keys()) if isinstance(message, dict) else [],
+                "content_preview": preview,
+            },
+        )
+
+    output_paths: List[str] = []
+    if image_output_path:
+        base_path = Path(image_output_path)
+    else:
+        if len(input_paths) > 1:
+            base_path = input_paths[0].parent / "set_generated.png"
+        else:
+            base_path = input_paths[0].parent / f"{input_paths[0].stem}_generated.png"
+    base_path.parent.mkdir(parents=True, exist_ok=True)
+
+    for idx, ref in enumerate(refs):
+        kind = ref.get("kind", "")
+        mime_type = "image/png"
+        raw_bytes: Optional[bytes] = None
+
+        if kind == "data_url":
+            m = re.match(r"^data:([^;]+);base64,(.+)$", ref.get("value", ""), flags=re.DOTALL)
+            if not m:
+                continue
+            mime_type = m.group(1)
+            raw_bytes = base64.b64decode(m.group(2))
+        elif kind == "base64":
+            mime_type = ref.get("mime_type", "image/png")
+            raw_bytes = base64.b64decode(ref.get("value", ""))
+        elif kind == "url":
+            url = ref.get("value", "")
+            try:
+                with httpx.Client(timeout=DEFAULT_TIMEOUT) as client:
+                    r = client.get(url)
+                    r.raise_for_status()
+                    raw_bytes = r.content
+                    mime_type = r.headers.get("content-type", "image/png").split(";")[0]
+            except Exception:
+                continue
+        else:
+            continue
+
+        if not raw_bytes:
+            continue
+
+        ext = _mime_to_ext(mime_type)
+        if len(refs) == 1:
+            target = base_path
+            if target.suffix.lower() not in [".png", ".jpg", ".jpeg", ".webp"]:
+                target = target.with_suffix(ext)
+        else:
+            stem = base_path.stem
+            target = base_path.with_name(f"{stem}_{idx+1}{ext}")
+        try:
+            target.write_bytes(raw_bytes)
+            output_paths.append(str(target))
+        except Exception as e:
+            return ToolResult(
+                title="NanoBanana 生成失败",
+                output="",
+                error=f"写入生成图片失败: {e}",
+                metadata={"model": chosen_model},
+            )
+
+    if not output_paths:
+        return ToolResult(
+            title="NanoBanana 生成失败",
+            output=json.dumps(data, ensure_ascii=False, indent=2),
+            error="检测到图片引用但写入失败(可能是无效 base64 或 URL 不可访问)",
+            metadata={"model": chosen_model, "ref_count": len(refs)},
+        )
+
+    usage = data.get("usage", {})
+    prompt_tokens = usage.get("prompt_tokens") or usage.get("input_tokens", 0)
+    completion_tokens = usage.get("completion_tokens") or usage.get("output_tokens", 0)
+    summary = {
+        "model": chosen_model,
+        "input_images": [str(p) for p in input_paths],
+        "input_count": len(input_paths),
+        "generated_images": output_paths,
+        "prompt_tokens": prompt_tokens,
+        "completion_tokens": completion_tokens,
+    }
+    return ToolResult(
+        title="NanoBanana 图片生成完成",
+        output=json.dumps({"summary": summary}, ensure_ascii=False, indent=2),
+        long_term_memory=f"Generated {len(output_paths)} image(s) from {len(input_paths)} input image(s) using {chosen_model}",
+        attachments=output_paths,
+        metadata=summary,
+        tool_usage={
+            "model": chosen_model,
+            "prompt_tokens": prompt_tokens,
+            "completion_tokens": completion_tokens,
+        }
+    )