Просмотр исходного кода

Merge remote-tracking branch 'refs/remotes/origin/main'

Talegorithm 4 дней назад
Родитель
Сommit
42362b2d93
100 измененных файлов с 15524 добавлено и 435 удалено
  1. 3 1
      .claude/settings.local.json
  2. 540 47
      agent/core/runner.py
  3. 408 3
      agent/memory/skills/research.md
  4. 8 1
      agent/tools/builtin/__init__.py
  5. 161 26
      agent/tools/builtin/bash.py
  6. 94 6
      agent/tools/builtin/browser/baseClass.py
  7. 1 1
      agent/tools/builtin/experience.py
  8. 1183 0
      agent/tools/builtin/knowledge.py
  9. 11 0
      agent/tools/builtin/subagent.py
  10. 3 0
      agent/tools/models.py
  11. 13 3
      agent/tools/registry.py
  12. 6 5
      agent/trace/compaction.py
  13. 144 0
      agent/trace/examples_api.py
  14. 14 0
      agent/trace/goal_models.py
  15. 102 0
      agent/trace/logs_websocket.py
  16. 119 10
      agent/trace/store.py
  17. 11 0
      api_server.py
  18. BIN
      examples/deep_research/input/1.jpeg
  19. 55 0
      examples/deep_research/input/1_invariant_features.json
  20. BIN
      examples/deep_research/input/3.jpeg
  21. 55 0
      examples/deep_research/input/3_invariant_features.json
  22. BIN
      examples/deep_research/input/7.jpeg
  23. 59 0
      examples/deep_research/input/7_invariant_features.json
  24. 9 0
      examples/deep_research/input/《秋日际遇》写生油画.json
  25. 23 0
      examples/deep_research/production.prompt
  26. 581 0
      examples/deep_research/run.py
  27. 31 50
      examples/how/input/set_invariant_features.json
  28. 83 39
      examples/how/run.py
  29. 147 233
      examples/how/tool/nanobanana.py
  30. 106 0
      examples/research/README.md
  31. 191 0
      examples/research/TROUBLESHOOTING.md
  32. 31 9
      examples/research/run.py
  33. 2 1
      examples/research/test.prompt
  34. BIN
      examples/restore/input/images/img_1.jpg
  35. 32 0
      examples/restore/input/images/img_1_invariant_features.json
  36. BIN
      examples/restore/input/images/img_2.jpg
  37. BIN
      examples/restore/input/images/img_3.jpg
  38. BIN
      examples/restore/input/images/img_5.jpg
  39. BIN
      examples/restore/input/images/img_6.jpg
  40. BIN
      examples/restore/input/images/img_7.jpg
  41. 80 0
      examples/restore/input/images/img_7_invariant_features.json
  42. BIN
      examples/restore/input/images/img_8.jpg
  43. BIN
      examples/restore/input/images/img_9.jpg
  44. 18 0
      examples/restore/input/images/set_invariant_features.json
  45. 29 0
      examples/restore/input/paragraphs/00_基础信息.json
  46. 215 0
      examples/restore/input/paragraphs/01_图片分段_01_g1_户外绘画场景.json
  47. 186 0
      examples/restore/input/paragraphs/01_图片分段_02_g1_户外绘画场景.json
  48. 215 0
      examples/restore/input/paragraphs/01_图片分段_03_g1_户外绘画场景.json
  49. 185 0
      examples/restore/input/paragraphs/01_图片分段_04_g1_户外绘画场景.json
  50. 213 0
      examples/restore/input/paragraphs/01_图片分段_05_g2_户外绘画场景.json
  51. 213 0
      examples/restore/input/paragraphs/01_图片分段_06_g2_户外绘画场景.json
  52. 334 0
      examples/restore/input/paragraphs/01_图片分段_07_g3_人物与玫瑰花.json
  53. 214 0
      examples/restore/input/paragraphs/01_图片分段_08_g1_户外绘画场景.json
  54. 186 0
      examples/restore/input/paragraphs/01_图片分段_09_g1_户外绘画场景.json
  55. 249 0
      examples/restore/input/paragraphs/02_图片形式_01.json
  56. 231 0
      examples/restore/input/paragraphs/02_图片形式_02.json
  57. 265 0
      examples/restore/input/paragraphs/02_图片形式_03.json
  58. 203 0
      examples/restore/input/paragraphs/02_图片形式_04.json
  59. 255 0
      examples/restore/input/paragraphs/02_图片形式_05.json
  60. 267 0
      examples/restore/input/paragraphs/02_图片形式_06.json
  61. 353 0
      examples/restore/input/paragraphs/02_图片形式_07.json
  62. 228 0
      examples/restore/input/paragraphs/02_图片形式_08.json
  63. 236 0
      examples/restore/input/paragraphs/02_图片形式_09.json
  64. 137 0
      examples/restore/input/paragraphs/03_图片制作点实质结果.json
  65. 738 0
      examples/restore/input/paragraphs/04_图片制作点形式结果.json
  66. 616 0
      examples/restore/input/paragraphs_overview.json
  67. BIN
      examples/restore/input/person.jpg
  68. 84 0
      examples/restore/input/readme.md
  69. 24 0
      examples/restore/production.prompt
  70. 276 0
      examples/restore/prompt_generator.py
  71. 623 0
      examples/restore/run.py
  72. BIN
      examples/restore_old/input/images/img_1.jpg
  73. BIN
      examples/restore_old/input/images/img_2.jpg
  74. BIN
      examples/restore_old/input/images/img_3.jpg
  75. BIN
      examples/restore_old/input/images/img_5.jpg
  76. BIN
      examples/restore_old/input/images/img_6.jpg
  77. BIN
      examples/restore_old/input/images/img_7.jpg
  78. BIN
      examples/restore_old/input/images/img_8.jpg
  79. BIN
      examples/restore_old/input/images/img_9.jpg
  80. 11 0
      examples/restore_old/input/images/set_invariant_features.json
  81. 29 0
      examples/restore_old/input/paragraphs/00_基础信息.json
  82. 215 0
      examples/restore_old/input/paragraphs/01_图片分段_01_g1_户外绘画场景.json
  83. 186 0
      examples/restore_old/input/paragraphs/01_图片分段_02_g1_户外绘画场景.json
  84. 215 0
      examples/restore_old/input/paragraphs/01_图片分段_03_g1_户外绘画场景.json
  85. 185 0
      examples/restore_old/input/paragraphs/01_图片分段_04_g1_户外绘画场景.json
  86. 213 0
      examples/restore_old/input/paragraphs/01_图片分段_05_g2_户外绘画场景.json
  87. 213 0
      examples/restore_old/input/paragraphs/01_图片分段_06_g2_户外绘画场景.json
  88. 334 0
      examples/restore_old/input/paragraphs/01_图片分段_07_g3_人物与玫瑰花.json
  89. 214 0
      examples/restore_old/input/paragraphs/01_图片分段_08_g1_户外绘画场景.json
  90. 186 0
      examples/restore_old/input/paragraphs/01_图片分段_09_g1_户外绘画场景.json
  91. 249 0
      examples/restore_old/input/paragraphs/02_图片形式_01.json
  92. 231 0
      examples/restore_old/input/paragraphs/02_图片形式_02.json
  93. 265 0
      examples/restore_old/input/paragraphs/02_图片形式_03.json
  94. 203 0
      examples/restore_old/input/paragraphs/02_图片形式_04.json
  95. 255 0
      examples/restore_old/input/paragraphs/02_图片形式_05.json
  96. 267 0
      examples/restore_old/input/paragraphs/02_图片形式_06.json
  97. 353 0
      examples/restore_old/input/paragraphs/02_图片形式_07.json
  98. 228 0
      examples/restore_old/input/paragraphs/02_图片形式_08.json
  99. 236 0
      examples/restore_old/input/paragraphs/02_图片形式_09.json
  100. 137 0
      examples/restore_old/input/paragraphs/03_图片制作点实质结果.json

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

@@ -11,7 +11,9 @@
       "Bash(timeout 60 python:*)",
       "Bash(timeout 240 python:*)",
       "Bash(curl:*)",
-      "Bash(tree:*)"
+      "Bash(tree:*)",
+      "Bash(xargs grep:*)",
+      "Bash(npm run:*)"
     ],
     "deny": [],
     "ask": []

+ 540 - 47
agent/core/runner.py

@@ -26,7 +26,7 @@ from typing import AsyncIterator, Optional, Dict, Any, List, Callable, Literal,
 from agent.trace.models import Trace, Message
 from agent.trace.protocols import TraceStore
 from agent.trace.goal_models import GoalTree
-from agent.tools.builtin.experience import _get_structured_experiences, _batch_update_experiences
+from agent.tools.builtin.knowledge import _get_structured_knowledge, _batch_update_knowledge, save_knowledge
 from agent.trace.compaction import (
     CompressionConfig,
     filter_by_goal_status,
@@ -79,8 +79,11 @@ class RunConfig:
     # --- 额外 LLM 参数(传给 llm_call 的 **kwargs)---
     extra_llm_params: Dict[str, Any] = field(default_factory=dict)
 
+    # --- 研究流程控制 ---
+    enable_research_flow: bool = True  # 是否启用自动研究流程(知识检索→经验检索→调研→计划)
 
-# 内置工具列表(始终自动加载)
+
+    # 内置工具列表(始终自动加载)
 BUILTIN_TOOLS = [
     # 文件操作工具
     "read_file",
@@ -104,11 +107,18 @@ BUILTIN_TOOLS = [
     "get_experience",
     "get_search_suggestions",
 
+    # 知识管理工具
+    "search_knowledge",
+    "save_knowledge",
+    "update_knowledge",
+    "list_knowledge",
+    
+
     # 沙箱工具
-    "sandbox_create_environment",
-    "sandbox_run_shell",
-    "sandbox_rebuild_with_ports",
-    "sandbox_destroy_environment",
+    # "sandbox_create_environment",
+    # "sandbox_run_shell",
+    # "sandbox_rebuild_with_ports",
+    # "sandbox_destroy_environment",
 
     # 浏览器工具
     "browser_navigate_to_url",
@@ -218,13 +228,19 @@ class AgentRunner:
         self.utility_llm_call = utility_llm_call
         self.config = config or AgentConfig()
         self.skills_dir = skills_dir
-        # 确保 experiences_path 不为 None
-        self.experiences_path = experiences_path
+        # 保留 experiences_path 参数以向后兼容,但不再使用(经验已迁移到知识系统)
+        self.experiences_path = experiences_path or "./.cache/experiences.md"
         self.goal_tree = goal_tree
         self.debug = debug
         self._cancel_events: Dict[str, asyncio.Event] = {}  # trace_id → cancel event
         self.used_ex_ids: List[str] = []  # 当前运行中使用过的经验 ID
 
+        # 研究流程状态管理(每个 trace 独立)
+        self._research_states: Dict[str, Dict[str, Any]] = {}  # trace_id → research_state
+
+        # 知识保存跟踪(每个 trace 独立)
+        self._saved_knowledge_ids: Dict[str, List[str]] = {}  # trace_id → [knowledge_ids]
+
     # ===== 核心公开方法 =====
 
     async def run(
@@ -338,11 +354,16 @@ class AgentRunner:
             status = "failed"
             error = error or "Agent 没有产生 assistant 文本结果"
 
+        # 获取保存的知识 ID
+        trace_id = final_trace.trace_id if final_trace else config.trace_id
+        saved_knowledge_ids = self._saved_knowledge_ids.get(trace_id, [])
+
         return {
             "status": status,
             "summary": summary,
-            "trace_id": final_trace.trace_id if final_trace else config.trace_id,
+            "trace_id": trace_id,
             "error": error,
+            "saved_knowledge_ids": saved_knowledge_ids,  # 新增:返回保存的知识 ID
             "stats": {
                 "total_messages": final_trace.total_messages if final_trace else 0,
                 "total_tokens": final_trace.total_tokens if final_trace else 0,
@@ -636,8 +657,235 @@ class AgentRunner:
         if self.trace_store and head_seq is not None:
             await self.trace_store.update_trace(trace_id, head_sequence=head_seq)
 
+        # 6. 初始化研究流程(仅在新建 trace 且启用研究流程时)
+        if config.enable_research_flow and not config.trace_id:
+            await self._init_research_flow(trace_id, new_messages, goal_tree, config)
+
         return history, sequence, created_messages, head_seq or 0
 
+    async def _init_research_flow(
+        self,
+        trace_id: str,
+        messages: List[Dict],
+        goal_tree: Optional[GoalTree],
+        config: RunConfig,
+    ):
+        """
+        初始化研究流程状态
+
+        研究流程阶段(已简化):
+        1. research_decision: 决定是否需要调研(知识和经验已自动注入到 GoalTree)
+        2. research: 执行调研(如果需要)
+        3. planning: 制定计划
+        4. execution: 正常执行
+        """
+        # 提取任务描述
+        task_desc = self._extract_task_description(messages)
+
+        # 初始化研究状态(直接从 research_decision 开始,因为知识已自动注入)
+        self._research_states[trace_id] = {
+            "stage": "research_decision",  # 直接进入决策阶段
+            "task_desc": task_desc,
+            "knowledge_found": False,
+            "experience_found": False,
+            "need_research": False,
+            "research_completed": False,
+            "planning_completed": False,
+            "knowledge_results": [],
+            "experience_results": [],
+            "decision_guide_injected": False,  # 防止重复注入决策引导
+        }
+
+        logger.info(f"[Research Flow] 初始化研究流程(知识已自动注入): {task_desc[:50]}...")
+
+    def _extract_task_description(self, messages: List[Dict]) -> str:
+        """从消息中提取任务描述"""
+        for msg in messages:
+            if msg.get("role") == "user":
+                content = msg.get("content", "")
+                if isinstance(content, str):
+                    return content
+                elif isinstance(content, list):
+                    for part in content:
+                        if isinstance(part, dict) and part.get("type") == "text":
+                            return part.get("text", "")
+        return "未知任务"
+
+    def _get_research_state(self, trace_id: str) -> Optional[Dict[str, Any]]:
+        """获取研究流程状态"""
+        return self._research_states.get(trace_id)
+
+    def _update_research_stage(self, trace_id: str, stage: str, **kwargs):
+        """更新研究流程阶段"""
+        if trace_id in self._research_states:
+            self._research_states[trace_id]["stage"] = stage
+            self._research_states[trace_id].update(kwargs)
+            logger.info(f"[Research Flow] 阶段切换: {stage}")
+
+    def _build_research_guide(self, research_state: Dict[str, Any]) -> str:
+        """根据研究流程状态构建引导消息"""
+        stage = research_state["stage"]
+        task_desc = research_state["task_desc"]
+
+        if stage == "research":
+            # 读取 research.md 的内容
+            research_skill_content = ""
+            research_skill_path = os.path.join(
+                os.path.dirname(__file__),
+                "..", "memory", "skills", "research.md"
+            )
+            try:
+                with open(research_skill_path, "r", encoding="utf-8") as f:
+                    research_skill_content = f.read()
+            except Exception as e:
+                logger.warning(f"无法读取 research.md: {e}")
+                research_skill_content = "(无法加载 research.md 内容)"
+
+            return f"""
+## 📚 研究流程 - 执行调研
+
+现有信息不足,需要进行调研。
+
+{research_skill_content}
+
+**重要提示**:
+- 调研完成后,请使用 `save_knowledge` 工具保存调研结果
+- 系统会自动检测到 save_knowledge 调用,并进入下一阶段(计划)
+"""
+
+        elif stage == "planning":
+            return f"""
+## 📋 研究流程 - 制定计划
+
+调研已完成(或无需调研),现在请制定执行计划。
+
+**请立即执行以下操作**:
+1. 使用 `goal` 工具创建目标树
+2. 将任务分解为可执行的子目标
+3. 为每个子目标设置合理的优先级
+
+注意:这是强制步骤,必须创建 goal tree 才能进入执行阶段。
+"""
+
+        # research_decision 阶段的引导消息已移到 _build_research_decision_guide
+        return ""
+
+    def _build_research_decision_guide(self, research_state: Dict[str, Any]) -> str:
+        """构建调研决策阶段的引导消息(基于已自动注入的知识和经验)"""
+        experience_results = research_state.get("experience_results", [])
+        task_desc = research_state.get("task_desc", "")
+
+        # 构建经验摘要
+        experience_summary = ""
+        if experience_results:
+            experience_summary = f"✅ 已自动检索到 {len(experience_results)} 条相关经验(见上方 GoalTree 中的「📚 相关知识」)\n"
+        else:
+            experience_summary = "❌ 未找到相关经验\n"
+
+        return f"""
+---
+
+## 🤔 调研决策
+
+{experience_summary}
+
+### 决策指南
+
+**当前状态**:系统已自动检索知识库和经验库,相关内容已注入到上方的 GoalTree 中(查看 Current Goal 下的「📚 相关知识」部分)。
+
+**请根据已注入的知识和经验,选择下一步行动**:
+
+**选项 1: 知识充足,直接制定计划**
+- 如果上方显示的知识和经验已经足够完成任务
+- 直接使用 `goal` 工具制定执行计划
+
+**选项 2: 知识不足,需要调研** ⭐
+- 如果上方没有显示相关知识,或现有知识不足以完成任务
+- **立即调用 `agent` 工具启动调研子任务**:
+
+```python
+agent(
+    task=\"\"\"针对任务「{task_desc[:100]}」进行深入调研:
+
+1. 使用 web_search 工具搜索相关技术文档、教程、最佳实践
+2. 搜索关键词建议:
+   - 核心技术名称 + "教程"
+   - 核心技术名称 + "最佳实践"
+   - 核心技术名称 + "示例代码"
+3. 使用 read_file 工具查看项目中的相关文件
+4. 对每条有价值的信息,使用 save_knowledge 工具保存,标签类型选择:
+   - tool: 工具使用方法
+   - definition: 概念定义
+   - usercase: 使用案例
+   - strategy: 策略经验
+
+调研完成后,系统会自动进入计划阶段。
+\"\"\",
+    skills=["research"]  # 注入调研指南
+)
+```
+
+**重要提示**:
+- 如果 GoalTree 中没有显示「📚 相关知识」,说明知识库为空,必须先调研
+- 调研应该简洁高效,最多设立两个 goal
+"""
+
+    async def _handle_research_flow_transition(
+        self,
+        trace_id: str,
+        tool_name: str,
+        tool_result: Any,
+        goal_tree: Optional[GoalTree],
+    ):
+        """处理研究流程的状态转换"""
+        research_state = self._get_research_state(trace_id)
+        if not research_state:
+            return
+
+        stage = research_state["stage"]
+
+        # 阶段 1: 调研决策(通过 assistant 的文本回复或 agent 工具调用判断)
+        # 这个阶段的转换在 assistant 回复后处理,或检测到 agent 工具调用
+
+        # 阶段 2: 调研完成
+        # 情况 1: 检测到 save_knowledge 调用(直接调研)
+        # 情况 2: 检测到 agent 工具执行完成(子 agent 调研)
+        if stage == "research":
+            if tool_name == "save_knowledge":
+                # 直接调研:检测到 save_knowledge 调用
+                self._update_research_stage(
+                    trace_id,
+                    "planning",
+                    research_completed=True
+                )
+                logger.info(f"[Research Flow] 调研完成(直接调研),进入计划阶段")
+            elif tool_name == "agent":
+                # 子 agent 调研:agent 工具执行完成
+                self._update_research_stage(
+                    trace_id,
+                    "planning",
+                    research_completed=True
+                )
+                logger.info(f"[Research Flow] 调研完成(子 agent 调研),进入计划阶段")
+
+        # 阶段 3: 计划完成(检测到 goal 工具调用)
+        elif stage == "planning" and tool_name == "goal":
+            # 检查是否创建了 goal tree
+            if goal_tree and goal_tree.goals:
+                self._update_research_stage(
+                    trace_id,
+                    "execution",
+                    planning_completed=True
+                )
+                logger.info(f"[Research Flow] 计划完成,进入执行阶段")
+
+                # 打印 goal tree
+                print("\n" + "="*60)
+                print("📋 Goal Tree 已创建:")
+                print("="*60)
+                print(goal_tree.to_prompt())
+                print("="*60 + "\n")
+
     # ===== Phase 3: AGENT LOOP =====
 
     async def _agent_loop(
@@ -690,14 +938,26 @@ class AgentRunner:
                 for part in msg["content"]
                 if isinstance(part, dict) and part.get("type") in ("image", "image_url")
             )
-            print(f"\n[压缩评估] 消息数: {msg_count} | 图片数: {img_count} | Token: {token_count:,} / {max_tokens:,} ({progress_pct:.1f}%)")
 
-            if token_count > max_tokens:
-                print(f"[压缩评估] ⚠️  超过阈值,触发压缩流程")
+            # 检查是否需要压缩(token 或消息数量超限)
+            needs_compression_by_tokens = token_count > max_tokens
+            needs_compression_by_count = (
+                compression_config.max_messages > 0 and
+                msg_count > compression_config.max_messages
+            )
+            needs_compression = needs_compression_by_tokens or needs_compression_by_count
+
+            print(f"\n[压缩评估] 消息数: {msg_count} / {compression_config.max_messages} | 图片数: {img_count} | Token: {token_count:,} / {max_tokens:,} ({progress_pct:.1f}%)")
+
+            if needs_compression:
+                if needs_compression_by_count:
+                    print(f"[压缩评估] ⚠️  消息数超过阈值 ({msg_count} > {compression_config.max_messages}),触发压缩流程")
+                if needs_compression_by_tokens:
+                    print(f"[压缩评估] ⚠️  Token 数超过阈值,触发压缩流程")
             else:
                 print(f"[压缩评估] ✅ 未超阈值,无需压缩")
 
-            if token_count > max_tokens and self.trace_store and goal_tree:
+            if needs_compression and self.trace_store and goal_tree:
                 # 使用本地 head_seq(store 中的 head_sequence 在 loop 期间未更新,是过时的)
                 if head_seq > 0:
                     main_path_msgs = await self.trace_store.get_main_path_messages(
@@ -726,38 +986,64 @@ class AgentRunner:
                             sum(1 for g in goal_tree.goals
                                 if g.status in ("completed", "abandoned")),
                         )
-            elif token_count > max_tokens:
+            elif needs_compression:
                 print("[压缩评估] ⚠️  无法执行 Level 1 压缩(缺少 store 或 goal_tree)")
                 logger.warning(
-                    "消息 token 数 (%d) 超过阈值 (%d),但无法执行 Level 1 压缩(缺少 store 或 goal_tree)",
-                    token_count, max_tokens,
+                    "消息数 (%d) 或 token 数 (%d) 超过阈值 (max_messages=%d, max_tokens=%d),但无法执行 Level 1 压缩(缺少 store 或 goal_tree)",
+                    msg_count, token_count, compression_config.max_messages, max_tokens,
                 )
 
             # Level 2 压缩:LLM 总结(Level 1 后仍超阈值时触发)
             token_count_after = estimate_tokens(history)
-            if token_count_after > max_tokens:
+            msg_count_after = len(history)
+            needs_level2_by_tokens = token_count_after > max_tokens
+            needs_level2_by_count = (
+                compression_config.max_messages > 0 and
+                msg_count_after > compression_config.max_messages
+            )
+            needs_level2 = needs_level2_by_tokens or needs_level2_by_count
+
+            if needs_level2:
                 progress_pct_after = (token_count_after / max_tokens * 100) if max_tokens > 0 else 0
+                reason = []
+                if needs_level2_by_count:
+                    reason.append(f"消息数 {msg_count_after} > {compression_config.max_messages}")
+                if needs_level2_by_tokens:
+                    reason.append(f"Token {token_count_after:,} > {max_tokens:,} ({progress_pct_after:.1f}%)")
                 print(
-                    f"[Level 2 压缩] Level 1 后仍超阈值: {token_count_after:,} / {max_tokens:,} ({progress_pct_after:.1f}%) "
+                    f"[Level 2 压缩] Level 1 后仍超阈值: {' | '.join(reason)} "
                     f"→ 触发 LLM 总结"
                 )
                 logger.info(
-                    "Level 1 后 token 仍超阈值 (%d > %d),触发 Level 2 压缩",
-                    token_count_after, max_tokens,
+                    "Level 1 后仍超阈值 (消息数=%d/%d, token=%d/%d),触发 Level 2 压缩",
+                    msg_count_after, compression_config.max_messages, token_count_after, max_tokens,
                 )
                 history, head_seq, sequence = await self._compress_history(
                     trace_id, history, goal_tree, config, sequence, head_seq,
                 )
                 final_tokens = estimate_tokens(history)
                 print(f"[Level 2 压缩] 完成: Token {token_count_after:,} → {final_tokens:,}")
-            elif token_count > max_tokens:
+            elif needs_compression:
                 # Level 1 压缩成功,未触发 Level 2
-                print(f"[压缩评估] ✅ Level 1 压缩后达标: {token_count_after:,} / {max_tokens:,}")
+                print(f"[压缩评估] ✅ Level 1 压缩后达标: 消息数 {msg_count_after} | Token {token_count_after:,} / {max_tokens:,}")
             print()  # 空行分隔
 
             # 构建 LLM messages(注入上下文)
             llm_messages = list(history)
 
+            # 收集需要持久化的消息
+            user_messages_to_persist = []  # 研究流程引导和经验检索改为 user 消息
+            system_messages_to_persist = []  # 上下文注入保持为 system 消息
+
+            # 研究流程引导(仅在启用且处于研究阶段时)- 改为 user 消息
+            research_state = self._get_research_state(trace_id)
+            if research_state and research_state["stage"] != "execution":
+                research_guide = self._build_research_guide(research_state)
+                if research_guide:
+                    user_msg = {"role": "user", "content": research_guide}
+                    llm_messages.append(user_msg)
+                    user_messages_to_persist.append(("研究流程引导", user_msg))
+
             # 先对历史消息应用 Prompt Caching(在注入动态内容之前)
             # 这样可以确保历史消息的缓存点固定,不受动态注入影响
             llm_messages = self._add_cache_control(
@@ -771,7 +1057,9 @@ class AgentRunner:
             if iteration % CONTEXT_INJECTION_INTERVAL == 0:
                 context_injection = self._build_context_injection(trace, goal_tree)
                 if context_injection:
-                    llm_messages.append({"role": "system", "content": context_injection})
+                    system_msg = {"role": "system", "content": context_injection}
+                    llm_messages.append(system_msg)
+                    system_messages_to_persist.append(("上下文注入", system_msg))
 
             # 经验检索:goal 切换时重新检索,注入为 system message
             current_goal_id = goal_tree.current_id if goal_tree else None
@@ -780,12 +1068,18 @@ class AgentRunner:
                 current_goal = goal_tree.find(current_goal_id)
                 if current_goal:
                     try:
-                        relevant_exps = await _get_structured_experiences(
+                        relevant_exps = await _get_structured_knowledge(
                             query_text=current_goal.description,
                             top_k=3,
-                            context={"runner": self}
+                            context={"runner": self},
                         )
                         if relevant_exps:
+                            # 保存到 goal 对象
+                            current_goal.knowledge = relevant_exps
+                            logger.info(f"[Knowledge Injection] 已将 {len(relevant_exps)} 条知识注入到 goal {current_goal.id}: {current_goal.description[:40]}")
+                            logger.debug(f"[Knowledge Injection] 注入的知识 IDs: {[exp.get('id') for exp in relevant_exps]}")
+                            # 持久化保存 goal_tree
+                            await self.trace_store.update_goal_tree(trace_id, goal_tree)
                             self.used_ex_ids = [exp['id'] for exp in relevant_exps]
                             parts = [f"[{exp['id']}] {exp['content']}" for exp in relevant_exps]
                             _cached_exp_text = "## 参考历史经验\n" + "\n\n".join(parts)
@@ -796,14 +1090,83 @@ class AgentRunner:
                                 self.used_ex_ids,
                             )
                         else:
+                            current_goal.knowledge = []
+                            logger.info(f"[Knowledge Injection] goal {current_goal.id} 未找到相关知识")
+                            # 持久化保存 goal_tree
+                            await self.trace_store.update_goal_tree(trace_id, goal_tree)
                             _cached_exp_text = ""
+                            logger.info(
+                                "经验检索: goal='%s', 未找到相关经验",
+                                current_goal.description[:40],
+                            )
                     except Exception as e:
                         logger.warning("经验检索失败: %s", e)
+                        current_goal.knowledge = []
                         _cached_exp_text = ""
 
-            # 经验注入:goal切换时注入相关历史经验
-            if _cached_exp_text:
-                llm_messages.append({"role": "system", "content": _cached_exp_text})
+            # 经验注入:goal切换时注入相关历史经验 - 改为 user 消息
+            # 或者在 research_decision 阶段注入调研决策引导
+            if _cached_exp_text or (research_state and research_state["stage"] == "research_decision" and not research_state.get("decision_guide_injected", False)):
+                exp_content = _cached_exp_text if _cached_exp_text else ""
+
+                # 如果处于 research_decision 阶段,追加引导消息
+                if research_state and research_state["stage"] == "research_decision" and not research_state.get("decision_guide_injected", False):
+                    if exp_content:
+                        exp_content += "\n\n"
+                    exp_content += self._build_research_decision_guide(research_state)
+                    # 标记已注入,防止重复
+                    research_state["decision_guide_injected"] = True
+                    logger.info("[Research Flow] 已注入调研决策引导消息")
+
+                if exp_content:  # 确保有内容才注入
+                    user_msg = {"role": "user", "content": exp_content}
+                    llm_messages.append(user_msg)
+                    user_messages_to_persist.append(("经验检索", user_msg))
+
+            # 持久化 user 消息到 trace 和 history
+            for label, usr_msg in user_messages_to_persist:
+                # 添加到 history(这样会被包含在后续的对话中)
+                history.append(usr_msg)
+
+                # 保存到 trace store
+                if self.trace_store:
+                    # 在 content 前添加标签,这样会自动出现在 description 中
+                    labeled_content = f"[{label}]\n{usr_msg['content']}"
+                    user_message = Message.create(
+                        trace_id=trace_id,
+                        role="user",
+                        sequence=sequence,
+                        goal_id=current_goal_id,
+                        parent_sequence=head_seq if head_seq > 0 else None,
+                        content=labeled_content,
+                    )
+                    await self.trace_store.add_message(user_message)
+                    yield user_message
+                    head_seq = sequence
+                    sequence += 1
+
+            # 持久化 system 消息到 trace 和 history
+            for label, sys_msg in system_messages_to_persist:
+                # 添加到 history(这样会被包含在后续的对话中)
+                history.append(sys_msg)
+
+                # 保存到 trace store
+                if self.trace_store:
+                    # 在 content 前添加标签,这样会自动出现在 description 中
+                    labeled_content = f"[{label}]\n{sys_msg['content']}"
+                    system_message = Message.create(
+                        trace_id=trace_id,
+                        role="system",
+                        sequence=sequence,
+                        goal_id=current_goal_id,
+                        parent_sequence=head_seq if head_seq > 0 else None,
+                        content=labeled_content,
+                    )
+                    await self.trace_store.add_message(system_message)
+                    yield system_message
+                    head_seq = sequence
+                    sequence += 1
+
 
             # 调用 LLM
             result = await self.llm_call(
@@ -823,6 +1186,40 @@ class AgentRunner:
             cache_creation_tokens = result.get("cache_creation_tokens")
             cache_read_tokens = result.get("cache_read_tokens")
 
+            # 研究流程:处理 research_decision 阶段的转换
+            research_state = self._get_research_state(trace_id)
+            research_decision_handled = False
+            if research_state and research_state["stage"] == "research_decision":
+                # 检查是否调用了 agent 工具进行调研
+                if tool_calls:
+                    has_agent_call = any(
+                        tc.get("function", {}).get("name") == "agent"
+                        for tc in tool_calls
+                    )
+                    if has_agent_call:
+                        # LLM 决定使用子 agent 进行调研
+                        self._update_research_stage(trace_id, "research", need_research=True)
+                        logger.info(f"[Research Flow] LLM 决定使用子 agent 进行调研,进入调研阶段")
+                        research_decision_handled = True
+                        # 继续执行 agent 工具调用
+                    else:
+                        # 检查是否调用了 goal 工具(直接进入计划)
+                        has_goal_call = any(
+                            tc.get("function", {}).get("name") == "goal"
+                            for tc in tool_calls
+                        )
+                        if has_goal_call:
+                            self._update_research_stage(trace_id, "planning", need_research=False)
+                            logger.info(f"[Research Flow] LLM 决定无需调研,直接进入计划阶段")
+                            research_decision_handled = True
+                else:
+                    # 根据 assistant 的文本回复判断
+                    response_lower = response_content.lower()
+                    if "无需调研" in response_content or "不需要调研" in response_content or "信息充足" in response_content:
+                        self._update_research_stage(trace_id, "planning", need_research=False)
+                        logger.info(f"[Research Flow] LLM 决定无需调研,直接进入计划阶段")
+                        research_decision_handled = True
+
             # 按需自动创建 root goal
             if goal_tree and not goal_tree.goals and tool_calls:
                 has_goal_call = any(
@@ -864,6 +1261,16 @@ class AgentRunner:
 
             if self.trace_store:
                 await self.trace_store.add_message(assistant_msg)
+                # 记录模型使用
+                await self.trace_store.record_model_usage(
+                    trace_id=trace_id,
+                    sequence=sequence - 1,  # assistant_msg的sequence
+                    role="assistant",
+                    model=config.model,
+                    prompt_tokens=prompt_tokens,
+                    completion_tokens=completion_tokens,
+                    cache_read_tokens=cache_read_tokens or 0,
+                )
 
             yield assistant_msg
             head_seq = sequence
@@ -926,13 +1333,32 @@ class AgentRunner:
                         }
                     )
 
+                    # 跟踪保存的知识 ID
+                    if tool_name == "save_knowledge" and isinstance(tool_result, dict):
+                        metadata = tool_result.get("metadata", {})
+                        knowledge_id = metadata.get("knowledge_id")
+                        if knowledge_id:
+                            if trace_id not in self._saved_knowledge_ids:
+                                self._saved_knowledge_ids[trace_id] = []
+                            self._saved_knowledge_ids[trace_id].append(knowledge_id)
+                            logger.info(f"[Knowledge Tracking] 记录保存的知识 ID: {knowledge_id}")
+
                     # --- 支持多模态工具反馈 ---
-                    # execute() 返回 dict{"text","images"} 或 str
-                    if isinstance(tool_result, dict) and tool_result.get("images"):
-                        tool_result_text = tool_result["text"]
+                    # execute() 返回 dict{"text","images","tool_usage"} 或 str
+                    # 统一为dict格式
+                    if isinstance(tool_result, str):
+                        tool_result = {"text": tool_result}
+
+                    tool_text = tool_result.get("text", str(tool_result))
+                    tool_images = tool_result.get("images", [])
+                    tool_usage = tool_result.get("tool_usage")  # 新增:提取tool_usage
+
+                    # 处理多模态消息
+                    if tool_images:
+                        tool_result_text = tool_text
                         # 构建多模态消息格式
-                        tool_content_for_llm = [{"type": "text", "text": tool_result_text}]
-                        for img in tool_result["images"]:
+                        tool_content_for_llm = [{"type": "text", "text": tool_text}]
+                        for img in tool_images:
                             if img.get("type") == "base64" and img.get("data"):
                                 media_type = img.get("media_type", "image/png")
                                 tool_content_for_llm.append({
@@ -944,8 +1370,8 @@ class AgentRunner:
                         img_count = len(tool_content_for_llm) - 1  # 减去 text 块
                         print(f"[Runner] 多模态工具反馈: tool={tool_name}, images={img_count}, text_len={len(tool_result_text)}")
                     else:
-                        tool_result_text = str(tool_result)
-                        tool_content_for_llm = tool_result_text
+                        tool_result_text = tool_text
+                        tool_content_for_llm = tool_text
 
                     tool_msg = Message.create(
                         trace_id=trace_id,
@@ -960,10 +1386,22 @@ class AgentRunner:
 
                     if self.trace_store:
                         await self.trace_store.add_message(tool_msg)
+                        # 记录工具的模型使用
+                        if tool_usage:
+                            await self.trace_store.record_model_usage(
+                                trace_id=trace_id,
+                                sequence=sequence,
+                                role="tool",
+                                tool_name=tool_name,
+                                model=tool_usage.get("model"),
+                                prompt_tokens=tool_usage.get("prompt_tokens", 0),
+                                completion_tokens=tool_usage.get("completion_tokens", 0),
+                                cache_read_tokens=tool_usage.get("cache_read_tokens", 0),
+                            )
                         # 截图单独存为同名 PNG 文件
-                        if isinstance(tool_result, dict) and tool_result.get("images"):
+                        if tool_images:
                             import base64 as b64mod
-                            for img in tool_result["images"]:
+                            for img in tool_images:
                                 if img.get("data"):
                                     png_path = self.trace_store._get_messages_dir(trace_id) / f"{tool_msg.message_id}.png"
                                     png_path.write_bytes(b64mod.b64decode(img["data"]))
@@ -982,8 +1420,18 @@ class AgentRunner:
                     })
                     # ------------------------------------------
 
+                    # 研究流程状态转换
+                    await self._handle_research_flow_transition(
+                        trace_id, tool_name, tool_result, goal_tree
+                    )
+
                 continue  # 继续循环
 
+            # 研究流程决策阶段:即使没有工具调用也要继续
+            if research_decision_handled:
+                logger.info(f"[Research Flow] 研究决策完成,继续下一轮循环")
+                continue
+
             # 无工具调用,任务完成
             break
 
@@ -1105,10 +1553,54 @@ created_at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
                     structured_entries.append(entry)
 
                 if structured_entries:
-                    os.makedirs(os.path.dirname(self.experiences_path), exist_ok=True)
-                    with open(self.experiences_path, "a", encoding="utf-8") as f:
-                        f.write("\n\n" + "\n\n".join(structured_entries))
-                    logger.info(f"已提取并保存 {len(structured_entries)} 条结构化经验")
+                    # 保存经验为知识(strategy 标签)
+                    saved_count = 0
+                    for entry in structured_entries:
+                        try:
+                            # 从 entry 中提取信息
+                            lines = entry.split("\n")
+                            ex_id = ""
+                            intents = []
+                            states = []
+                            content = ""
+
+                            for line in lines:
+                                if line.startswith("id:"):
+                                    ex_id = line.split(":", 1)[1].strip()
+                                elif line.startswith("tags:"):
+                                    tags_match = _re2.search(r"intent:\s*\[(.*?)\].*state:\s*\[(.*?)\]", line)
+                                    if tags_match:
+                                        intents_str = tags_match.group(1).strip("'\"")
+                                        states_str = tags_match.group(2).strip("'\"")
+                                        intents = [i.strip().strip("'\"") for i in intents_str.split(",") if i.strip()]
+                                        states = [s.strip().strip("'\"") for s in states_str.split(",") if s.strip()]
+                                elif line.startswith("- ") and not line.startswith("- 经验ID:"):
+                                    content = line[2:].strip()
+
+                            # 构建 scenario(从 intent 和 state 生成)
+                            scenario_parts = []
+                            if intents:
+                                scenario_parts.append(f"意图: {', '.join(intents)}")
+                            if states:
+                                scenario_parts.append(f"状态: {', '.join(states)}")
+                            scenario = " | ".join(scenario_parts) if scenario_parts else "通用经验"
+
+                            # 调用 save_knowledge 保存为 strategy 标签的知识
+                            await save_knowledge(
+                                scenario=scenario,
+                                content=content,
+                                tags_type=["strategy"],
+                                urls=[],
+                                agent_id="runner",
+                                score=3,
+                                trace_id=trace_id
+                            )
+                            saved_count += 1
+                        except Exception as e:
+                            logger.warning(f"保存经验失败: {e}")
+                            continue
+
+                    logger.info(f"已提取并保存 {saved_count}/{len(structured_entries)} 条结构化经验到知识库")
                 else:
                     logger.warning("未能解析出符合格式的经验条目,请检查 REFLECT_PROMPT。")
                     logger.debug(f"LLM Raw Output:\n{reflection_text}")
@@ -1155,16 +1647,17 @@ created_at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
                     import re as _re
                     update_map = {}
                     for line in eval_block.splitlines():
-                        m = _re.search(r"ID:\s*(ex_\S+)\s*\|\s*Result:\s*(\w+)", line)
+                        # 匹配新的知识 ID 格式:knowledge-xxx 或 research-xxx
+                        m = _re.search(r"ID:\s*((?:knowledge|research)-\S+)\s*\|\s*Result:\s*(\w+)", line)
                         if m:
-                            ex_id, result = m.group(1), m.group(2).lower()
+                            knowledge_id, result = m.group(1), m.group(2).lower()
                             if result in ("helpful", "harmful"):
-                                update_map[ex_id] = {"action": result, "feedback": ""}
+                                update_map[knowledge_id] = {"action": result, "feedback": ""}
                             elif result == "mixed":
-                                update_map[ex_id] = {"action": "helpful", "feedback": ""}
+                                update_map[knowledge_id] = {"action": "helpful", "feedback": ""}
                     if update_map:
-                        count = await _batch_update_experiences(update_map, context={"runner": self})
-                        logger.info("经验评估完成,更新了 %s 条经验", count)
+                        count = await _batch_update_knowledge(update_map, context={"runner": self})
+                        logger.info("知识评估完成,更新了 %s 条知识", count)
             except Exception as e:
                 logger.warning("经验评估解析失败(不影响压缩): %s", e)
 

+ 408 - 3
agent/memory/skills/research.md

@@ -1,10 +1,415 @@
 ---
-name: research
-description: 信息调研,使用搜索工具和浏览器获取外部信息
+name: atomic_research
+description: 知识调研 - 根据目标和任务自动执行搜索,返回结构化知识列表
 ---
 
 ## 信息调研
 
 你可以通过联网搜索工具 `search_posts` 获取来自 Github、小红书、微信公众号、知乎等渠道的信息。对于需要深度交互的网页内容,使用浏览器工具进行操作。
 
-调研过程可能需要多次搜索,比如基于搜索结果中获得的启发或信息启动新的搜索,直到得到令人满意的答案。你可以使用 `goal` 工具管理搜索的过程,或者使用文档记录搜索的中间或最终结果。
+## 调研过程可能需要多次搜索,比如基于搜索结果中获得的启发或信息启动新的搜索,直到得到令人满意的答案。你可以使用 `goal` 工具管理搜索的过程,或者使用文档记录搜索的中间或最终结果。(可以着重参考browser的工具来辅助搜索)
+
+## 工作流程
+
+### 输入
+
+- **目标**:要达成的目标(如"找到 PDF 表格提取的最佳方案")
+- **任务**:具体的任务描述(如"从复杂 PDF 中提取表格数据")
+
+### 执行流程
+
+**Step 1: 拆解搜索维度**
+
+```
+goal(add="搜索工具, 搜索案例, 搜索方法论")
+```
+
+**Step 2: 多维度搜索**
+
+- 搜索工具:`search_posts(query="PDF table extraction tool")`
+- 搜索案例:`search_posts(query="PDF table extraction usercase site:github.com")`
+- 搜索定义:`search_posts(query="PDF table extraction definition")`
+- 搜索方法:`search_posts(query="PDF table extraction best practice")`
+
+**Step 3: 结构化记录**
+每发现一条有价值的信息,立即保存为结构化知识:
+
+```python
+save_knowledge(
+    tags_type=["tool"],  # tool/usercase/definition/plan
+    scenario="在什么情景下,要完成什么目标,得到能达成一个什么结果",
+    content="这条知识实际的核心内容",
+    urls=["参考的论文/github/博客等"],
+    agent_id="当前 agent ID",
+    score=5,
+    helpful_count=1,
+    harmful_count=0
+)
+```
+
+**Step 4: 输出知识列表**
+
+```
+goal(done="已完成调研,共记录 N 条知识")
+```
+
+### 输出
+
+- 保存到 `.cache/knowledge_atoms/` 目录,每条知识一个 JSON 文件
+- 文件名格式:`atom-YYYYMMDD-HHMMSS-XXXX.json`
+- 返回知识列表摘要
+
+---
+
+## 知识结构
+
+每条知识原子包含以下字段:
+
+### ① id(唯一标识)
+
+格式:`atom-YYYYMMDD-NNN`
+
+示例:`atom-20260302-001`
+
+### ② tags(知识类型)
+
+单条知识可以包含多个标签:
+
+| 类型           | 说明                                       | 示例                                |
+| -------------- | ------------------------------------------ | ----------------------------------- |
+| **tool**       | 工具相关信息,包含使用案例和使用方法       | pdfplumber 库的使用方法             |
+| **usercase**   | 针对任务的用户案例,某个用户完成任务的方法 | 某用户如何提取 PDF 表格的完整流程   |
+| **definition** | 内容的具体定义,或任务的问题定义           | 什么是 PDF 表格提取,有哪些技术挑战 |
+| **plan**       | 完成任务的通用计划、方法论或流程           | PDF 表格提取的标准流程和最佳实践    |
+
+### ③ summary(摘要)
+
+**格式**:在 [情景] 下,完成 [目标],得到 [预期结果]。
+
+**示例**:
+
+```
+在 Python 3.11 环境下,需要从结构复杂的 PDF(包含多列、嵌套表格)中提取表格数据,
+并保留单元格坐标信息,最终得到可用于数据分析的结构化数据。
+```
+
+### ④ content(核心内容)
+
+这条知识实际的核心内容,使用 Markdown 格式,根据类型不同包含不同信息:
+
+**tool 类型**:
+
+- 工具名称和简介
+- 核心 API 和使用方法
+- 适用场景
+- 优缺点对比
+- 代码示例
+
+**usercase 类型**:
+
+- 用户背景和需求
+- 采用的方案
+- 实现步骤
+- 遇到的问题和解决方法
+- 最终效果
+
+**definition 类型**:
+
+- 概念定义
+- 技术原理
+- 应用场景
+- 与相关概念的区别
+
+**plan 类型**:
+
+- 完整流程步骤
+- 关键决策点
+- 常见陷阱和避坑指南
+- 评估标准
+
+### ⑤ tips(避坑指南)
+
+⚠️ 具体的避坑指南或核心建议。
+
+**示例**:
+
+```
+⚠️ 如果 PDF 包含隐形表格线,务必开启 explicit_horizontal_lines 参数
+⚠️ 使用 page.crop() 先裁剪区域再提取,可提升 3-5 倍速度
+```
+
+### ⑥ trace(回溯)
+
+**包含**:
+
+- `urls`: 参考的论文、GitHub、博客等(URL 列表)
+- `agent_id`: 执行此调研的 agent ID
+- `timestamp`: 记录时间
+
+**示例**:
+
+```json
+{
+  "urls": ["https://github.com/jsvine/pdfplumber"],
+  "agent_id": "research_agent_001",
+  "timestamp": "2026-03-02T12:45:41Z"
+}
+```
+
+### ⑦ eval(评估反馈)
+
+**包含**:
+
+- `helpful`: 好用的使用次数(初始值为 1)
+- `harmful`: 不好用的使用次数(初始值为 0)
+
+**示例**:
+
+```json
+{
+  "helpful": 1,
+  "harmful": 0
+}
+```
+
+### ⑧ execute_history(执行历史)
+
+**包含**:
+
+- `helpful`: 好用的使用案例描述列表(字符串数组,初始为空)
+- `harmful`: 不好用的使用案例描述列表(字符串数组,初始为空)
+
+**示例**:
+
+```json
+{
+  "helpful": [],
+  "harmful": []
+}
+```
+
+---
+
+## 完整示例
+
+````json
+{
+  "research_report": {
+    "goal": "找到 PDF 表格提取的最佳方案",
+    "task": "从复杂 PDF 中提取表格数据",
+    "summary": "共记录 3 条核心知识原子,涵盖工具选型与实战 SOP",
+    "atoms": [
+      {
+        "id": "atom-20260302-001",
+        "tags": ["tool", "plan", "usercase"],
+        "summary": "在 Python 3.11 环境下,从结构复杂的 PDF(包含多列、嵌套表格)中提取表格数据,并保留单元格坐标信息,最终得到可用于数据分析的结构化数据。",
+        "content": "## 推荐工具\npdfplumber - 专注于 PDF 表格提取\n\n## 核心 API\n使用 extract_tables() 方法\n\n## 代码示例\n```python\nimport pdfplumber\nwith pdfplumber.open('file.pdf') as pdf:\n    tables = pdf.pages[0].extract_tables()\n```",
+        "tips": "⚠️ 如果 PDF 包含隐形表格线,务必开启 explicit_horizontal_lines 参数\n⚠️ 使用 page.crop() 先裁剪区域再提取,可提升 3-5 倍速度",
+        "trace": {
+          "urls": ["https://github.com/jsvine/pdfplumber"],
+          "agent_id": "research_agent_001",
+          "timestamp": "2026-03-02T12:45:41Z"
+        },
+        "eval": {
+          "helpful": 1,
+          "harmful": 0
+        },
+        "execute_history": {
+          "helpful": [],
+          "harmful": []
+        }
+      },
+      {
+        "id": "atom-20260302-002",
+        "tags": ["usercase"],
+        "summary": "针对 500MB 以上的大型扫描版 PDF 进行自动化处理。",
+        "content": "使用纯 Python 库(如 pdfplumber/PyMuPDF)性能极差且准确率低。",
+        "tips": "建议方案:先使用 PaddleOCR 进行版面分析,再提取坐标区域。",
+        "trace": {
+          "urls": ["https://reddit.com/r/python/comments/..."],
+          "agent_id": "research_agent_01",
+          "timestamp": "2026-03-02T13:00:00Z"
+        },
+        "eval": {
+          "helpful": 1,
+          "harmful": 0
+        },
+        "execute_history": {
+          "helpful": [],
+          "harmful": []
+        }
+      }
+    ]
+  }
+}
+````
+
+---
+
+## 适用场景
+
+- 需要从多个来源收集和整理知识
+- 需要持续积累和评估知识的有效性
+- 需要追溯知识来源和使用历史
+
+---
+
+## 使用工具
+
+### 保存知识
+
+````python
+save_knowledge(
+    tags=["tool", "plan"],
+    summary="在 Python 3.11 环境下,从复杂 PDF 中提取表格数据,并保留单元格坐标信息。",
+    content="""
+## 推荐工具
+pdfplumber - 专注于 PDF 表格提取
+
+## 核心 API
+使用 extract_tables() 方法
+
+## 代码示例
+```python
+import pdfplumber
+with pdfplumber.open('file.pdf') as pdf:
+    tables = pdf.pages[0].extract_tables()
+````
+
+""",
+tips="⚠️ 必须设置 explicit_horizontal_lines=True 以识别隐形表格线",
+urls=["https://github.com/jsvine/pdfplumber"],
+agent_id="research_agent_001"
+)
+
+````
+
+### 更新评估反馈
+
+```python
+update_knowledge(
+    knowledge_id="atom-20260302-001",
+    add_helpful_case="在解析 2025 年报 PDF 时,通过配置 explicit_lines 成功提取了 100+ 嵌套表格。"
+)
+````
+
+或添加失败案例:
+
+```python
+update_knowledge(
+    knowledge_id="atom-20260302-001",
+    add_harmful_case="在处理 300MB 的扫描版 PDF 时,该方案因缺乏 OCR 能力导致提取结果为空。"
+)
+```
+
+---
+
+## 调研策略
+
+### 1. 工具调研(tool)
+
+**搜索关键词**:
+
+- `[任务] tool python`
+- `[任务] library comparison`
+- `[任务] vs site:reddit.com`
+
+**记录重点**:
+
+- 工具名称和链接
+- 核心 API
+- 适用场景
+- 优缺点对比
+- 避坑指南
+
+### 2. 案例调研(usercase)
+
+**搜索关键词**:
+
+- `[任务] example site:github.com`
+- `[任务] tutorial`
+- `how to [任务]`
+
+**记录重点**:
+
+- 用户背景
+- 采用方案
+- 实现步骤
+- 遇到的问题
+- 最终效果
+
+### 3. 定义调研(definition)
+
+**搜索关键词**:
+
+- `what is [任务]`
+- `[任务] definition`
+- `[任务] explained`
+
+**记录重点**:
+
+- 概念定义
+- 技术原理
+- 应用场景
+- 相关概念区别
+
+### 4. 方法论调研(plan)
+
+**搜索关键词**:
+
+- `[任务] best practice`
+- `[任务] workflow`
+- `[任务] step by step`
+
+**记录重点**:
+
+- 完整流程
+- 关键决策点
+- 常见陷阱
+- 评估标准
+
+---
+
+## 输出格式
+
+调研完成后,输出知识列表摘要:
+
+```
+📚 调研完成报告
+
+目标:找到 PDF 表格提取的最佳方案
+任务:从复杂 PDF 中提取表格数据
+
+共记录 5 条知识:
+
+1. [tool] pdfplumber - PDF 表格提取工具
+   评分:⭐⭐⭐⭐⭐ (5/5)
+   反馈:helpful: 1, harmful: 0
+
+2. [usercase] 财报 PDF 表格提取案例
+   评分:⭐⭐⭐⭐ (4/5)
+   反馈:helpful: 1, harmful: 0
+
+3. [definition] PDF 表格提取技术原理
+   评分:⭐⭐⭐⭐ (4/5)
+   反馈:helpful: 1, harmful: 0
+
+4. [plan] PDF 表格提取标准流程
+   评分:⭐⭐⭐⭐⭐ (5/5)
+   反馈:helpful: 1, harmful: 0
+
+5. [tool] tabula-py - 替代方案
+   评分:⭐⭐⭐ (3/5)
+   反馈:helpful: 1, harmful: 0
+
+知识文件保存在:.cache/knowledge_atoms/
+```
+
+---
+
+## 记住
+
+- **边搜边记**:发现有价值的信息立即保存
+- **结构化**:严格按照 5 个维度记录
+- **可追溯**:记录所有参考来源
+- **可评估**:初始评分 + 持续反馈

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

@@ -19,7 +19,8 @@ from agent.tools.builtin.experience import get_experience
 from agent.tools.builtin.search import search_posts, get_search_suggestions
 from agent.tools.builtin.sandbox import (sandbox_create_environment, sandbox_run_shell,
                                          sandbox_rebuild_with_ports,sandbox_destroy_environment)
-
+from agent.tools.builtin.knowledge import(search_knowledge,save_knowledge,list_knowledge,update_knowledge)
+from agent.trace.goal_tool import goal
 # 导入浏览器工具以触发注册
 import agent.tools.builtin.browser  # noqa: F401
 
@@ -36,6 +37,10 @@ __all__ = [
     "bash_command",
     "skill",
     "get_experience",
+    "search_knowledge",
+    "save_knowledge",
+    "list_knowledge",
+    "update_knowledge",
     "list_skills",
     "agent",
     "evaluate",
@@ -45,4 +50,6 @@ __all__ = [
     "sandbox_run_shell",
     "sandbox_rebuild_with_ports",
     "sandbox_destroy_environment",
+    # Goal 管理
+    "goal",
 ]

+ 161 - 26
agent/tools/builtin/bash.py

@@ -1,27 +1,139 @@
 """
 Bash Tool - 命令执行工具
 
-参考 OpenCode bash.ts 完整实现。
-
 核心功能:
 - 执行 shell 命令
 - 超时控制
 - 工作目录设置
 - 环境变量传递
+- 虚拟环境隔离(Python 命令,强制执行,LLM 不可控)
+- 目录白名单保护
 """
 
 import os
 import signal
 import asyncio
+import logging
 from pathlib import Path
 from typing import Optional, Dict
 
 from agent.tools import tool, ToolResult, ToolContext
 
 # 常量
-DEFAULT_TIMEOUT = 120  # 2 分钟
-MAX_OUTPUT_LENGTH = 50000  # 最大输出长度
-GRACEFUL_KILL_WAIT = 3  # SIGTERM 后等几秒再 SIGKILL
+DEFAULT_TIMEOUT = 120
+MAX_OUTPUT_LENGTH = 50000
+GRACEFUL_KILL_WAIT = 3
+
+# ===== 安全配置(模块级,LLM 不可控)=====
+ENABLE_VENV = True                # 是否强制启用虚拟环境
+VENV_DIR = ".venv"                # 虚拟环境目录名(相对于项目根目录)
+
+ALLOWED_WORKDIR_PATTERNS = [
+    ".",
+    "examples",
+    "examples/**",
+    ".cache",
+    ".cache/**",
+    "tests",
+    "tests/**",
+    "output",
+    "output/**",
+]
+
+PYTHON_KEYWORDS = ["python", "python3", "pip", "pip3", "pytest", "poetry", "uv"]
+
+logger = logging.getLogger(__name__)
+
+
+def _get_project_root() -> Path:
+    """获取项目根目录(bash.py 在 agent/tools/builtin/ 下)"""
+    return Path(__file__).parent.parent.parent.parent
+
+
+def _is_safe_workdir(path: Path) -> bool:
+    """检查工作目录是否在白名单内"""
+    try:
+        project_root = _get_project_root()
+        resolved_path = path.resolve()
+        resolved_root = project_root.resolve()
+
+        if not resolved_path.is_relative_to(resolved_root):
+            return False
+
+        relative_path = resolved_path.relative_to(resolved_root)
+
+        for pattern in ALLOWED_WORKDIR_PATTERNS:
+            if pattern == ".":
+                if relative_path == Path("."):
+                    return True
+            elif pattern.endswith("/**"):
+                base = Path(pattern[:-3])
+                if relative_path == base or relative_path.is_relative_to(base):
+                    return True
+            else:
+                if relative_path == Path(pattern):
+                    return True
+
+        return False
+    except (ValueError, OSError) as e:
+        logger.warning(f"路径检查失败: {e}")
+        return False
+
+
+def _should_use_venv(command: str) -> bool:
+    """判断命令是否应该在虚拟环境中执行"""
+    command_lower = command.lower()
+    for keyword in PYTHON_KEYWORDS:
+        if keyword in command_lower:
+            return True
+    return False
+
+
+async def _ensure_venv(venv_path: Path) -> bool:
+    """确保虚拟环境存在,不存在则创建"""
+    if os.name == 'nt':
+        python_exe = venv_path / "Scripts" / "python.exe"
+    else:
+        python_exe = venv_path / "bin" / "python"
+
+    if venv_path.exists() and python_exe.exists():
+        return True
+
+    # 创建虚拟环境
+    print(f"[bash] 正在创建虚拟环境: {venv_path}")
+    logger.info(f"创建虚拟环境: {venv_path}")
+    try:
+        process = await asyncio.create_subprocess_shell(
+            f"python -m venv {venv_path}",
+            stdout=asyncio.subprocess.PIPE,
+            stderr=asyncio.subprocess.PIPE,
+        )
+        stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=60)
+
+        if process.returncode == 0:
+            print(f"[bash] ✅ 虚拟环境创建成功: {venv_path}")
+            logger.info(f"虚拟环境创建成功: {venv_path}")
+            return True
+        else:
+            err_text = stderr.decode('utf-8', errors='replace') if stderr else ""
+            print(f"[bash] ❌ 虚拟环境创建失败: {err_text[:200]}")
+            logger.error(f"虚拟环境创建失败: exit code {process.returncode}, {err_text[:200]}")
+            return False
+
+    except Exception as e:
+        print(f"[bash] ❌ 虚拟环境创建异常: {e}")
+        logger.error(f"虚拟环境创建异常: {e}")
+        return False
+
+
+def _wrap_command_with_venv(command: str, venv_path: Path) -> str:
+    """将命令包装为在虚拟环境中执行"""
+    if os.name == 'nt':
+        activate_script = venv_path / "Scripts" / "activate.bat"
+        return f'call "{activate_script}" && {command}'
+    else:
+        activate_script = venv_path / "bin" / "activate"
+        return f'source "{activate_script}" && {command}'
 
 
 def _kill_process_tree(pid: int) -> None:
@@ -33,20 +145,17 @@ def _kill_process_tree(pid: int) -> None:
     except ProcessLookupError:
         return
 
-    # 先优雅终止
     try:
         os.killpg(pgid, signal.SIGTERM)
     except ProcessLookupError:
         return
 
-    # 等一小段时间让进程自行退出
     time.sleep(GRACEFUL_KILL_WAIT)
 
-    # 强制杀
     try:
         os.killpg(pgid, signal.SIGKILL)
     except ProcessLookupError:
-        pass  # 已退出
+        pass
 
 
 @tool(description="执行 bash 命令")
@@ -72,7 +181,6 @@ async def bash_command(
     Returns:
         ToolResult: 命令输出
     """
-    # 参数验证
     if timeout is not None and timeout < 0:
         return ToolResult(
             title="参数错误",
@@ -91,6 +199,32 @@ async def bash_command(
             error="Directory not found"
         )
 
+    # 目录白名单检查
+    if not _is_safe_workdir(cwd):
+        project_root = _get_project_root()
+        return ToolResult(
+            title="目录不允许",
+            output=(
+                f"工作目录不在白名单内: {cwd}\n"
+                f"项目根目录: {project_root}\n"
+                f"允许的目录: {', '.join(ALLOWED_WORKDIR_PATTERNS)}"
+            ),
+            error="Directory not allowed"
+        )
+
+    # 虚拟环境处理(强制执行,LLM 不可绕过)
+    actual_command = command
+    if ENABLE_VENV and _should_use_venv(command):
+        venv_dir = _get_project_root() / VENV_DIR
+
+        venv_ok = await _ensure_venv(venv_dir)
+        if venv_ok:
+            actual_command = _wrap_command_with_venv(command, venv_dir)
+            print(f"[bash] 🐍 使用虚拟环境: {venv_dir}")
+            logger.info(f"[bash] 使用虚拟环境: {venv_dir}")
+        else:
+            logger.warning(f"[bash] 虚拟环境不可用,回退到系统环境: {venv_dir}")
+
     # 准备环境变量
     process_env = os.environ.copy()
     if env:
@@ -98,23 +232,30 @@ async def bash_command(
 
     # 执行命令
     try:
-        process = await asyncio.create_subprocess_shell(
-            command,
-            stdout=asyncio.subprocess.PIPE,
-            stderr=asyncio.subprocess.PIPE,
-            cwd=str(cwd),
-            env=process_env,
-            start_new_session=True,  # 新进程组,超时时可杀整棵进程树
-        )
+        if os.name == 'nt':
+            process = await asyncio.create_subprocess_shell(
+                actual_command,
+                stdout=asyncio.subprocess.PIPE,
+                stderr=asyncio.subprocess.PIPE,
+                cwd=str(cwd),
+                env=process_env,
+            )
+        else:
+            process = await asyncio.create_subprocess_shell(
+                actual_command,
+                stdout=asyncio.subprocess.PIPE,
+                stderr=asyncio.subprocess.PIPE,
+                cwd=str(cwd),
+                env=process_env,
+                start_new_session=True,
+            )
 
-        # 等待命令完成(带超时)
         try:
             stdout, stderr = await asyncio.wait_for(
                 process.communicate(),
                 timeout=timeout_sec
             )
         except asyncio.TimeoutError:
-            # 超时,杀整个进程组(shell + 所有子进程)
             _kill_process_tree(process.pid)
             try:
                 await asyncio.wait_for(process.wait(), timeout=GRACEFUL_KILL_WAIT + 2)
@@ -127,30 +268,24 @@ async def bash_command(
                 metadata={"command": command, "timeout": timeout_sec}
             )
 
-        # 解码输出
         stdout_text = stdout.decode('utf-8', errors='replace') if stdout else ""
         stderr_text = stderr.decode('utf-8', errors='replace') if stderr else ""
 
-        # 截断过长输出
         truncated = False
         if len(stdout_text) > MAX_OUTPUT_LENGTH:
             stdout_text = stdout_text[:MAX_OUTPUT_LENGTH] + f"\n\n(输出被截断,总长度: {len(stdout_text)} 字符)"
             truncated = True
 
-        # 组合输出
         output = ""
         if stdout_text:
             output += stdout_text
-
         if stderr_text:
             if output:
                 output += "\n\n--- stderr ---\n"
             output += stderr_text
-
         if not output:
             output = "(命令无输出)"
 
-        # 检查退出码
         exit_code = process.returncode
         success = exit_code == 0
 

+ 94 - 6
agent/tools/builtin/browser/baseClass.py

@@ -62,6 +62,9 @@ from ....llm.openrouter import openrouter_llm_call
 # 将项目根目录添加到 Python 路径
 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 
+# 配置日志
+logger = logging.getLogger(__name__)
+
 # 导入框架的工具装饰器和结果类
 from agent.tools import tool, ToolResult
 from agent.tools.builtin.browser.sync_mysql_help import mysql
@@ -223,7 +226,11 @@ async def create_container(url: str, account_name: str = "liuwenwu") -> Dict[str
 
 async def init_browser_session(
     browser_type: str = "local",
-    headless: bool = False,
+    # TEMPORARY FIX (2026-03-02): 改为 True 以解决 CDP 连接时序问题
+    # browser-use 在非 headless 模式下有时会在 Chrome 完全启动前尝试连接 CDP,
+    # 导致 "JSONDecodeError: Expecting value" 错误
+    # TODO: 之后改回 headless: bool = False,或在 browser-use 修复此问题后移除此注释
+    headless: bool = True,  # 原值: False
     url: Optional[str] = None,
     profile_name: str = "default",
     user_data_dir: Optional[str] = None,
@@ -287,6 +294,8 @@ async def init_browser_session(
 
     # 创建会话
     _browser_session = BrowserSession(**session_params)
+    # 添加短暂延迟,确保 Chrome CDP 端点完全就绪
+    await asyncio.sleep(1)
     await _browser_session.start()
 
     _browser_tools = Tools()
@@ -1997,14 +2006,17 @@ async def browser_export_cookies(name: str = "", account: str = "") -> ToolResul
 
 
 @tool()
-async def browser_load_cookies(url: str, name: str = "") -> ToolResult:
+async def browser_load_cookies(url: str, name: str = "", auto_navigate: bool = True) -> ToolResult:
     """
     根据目标 URL 自动查找本地 Cookie 文件,注入浏览器并导航到目标页面恢复登录态。
+    如果找不到 Cookie 文件,会根据 auto_navigate 参数决定是否直接导航到目标页面。
+
     重要:此工具会自动完成导航,调用前不需要先调用 browser_navigate_to_url。
 
     Args:
         url: 目标 URL(必须提供,同时用于自动匹配 Cookie 文件)
         name: Cookie 文件名(可选,不传则根据 URL 域名自动查找)
+        auto_navigate: 找不到 Cookie 时是否自动导航到目标页面(默认 True)
     """
     try:
         browser, tools = await get_browser_session()
@@ -2017,19 +2029,95 @@ async def browser_load_cookies(url: str, name: str = "") -> ToolResult:
             from urllib.parse import urlparse
             domain = urlparse(url).netloc.replace("www.", "")
             if _COOKIES_DIR.exists():
-                matches = list(_COOKIES_DIR.glob(f"{domain}*.json"))
+                # 尝试多种匹配模式
+                matches = []
+
+                # 1. 精确匹配完整域名(如 xiaohongshu.com.json)
+                exact_match = _COOKIES_DIR / f"{domain}.json"
+                if exact_match.exists():
+                    matches.append(exact_match)
+                    logger.info(f"Cookie 精确匹配成功: {exact_match.name}")
+
+                # 2. 匹配域名前缀(如 xiaohongshu.com*.json)
+                if not matches:
+                    prefix_matches = list(_COOKIES_DIR.glob(f"{domain}*.json"))
+                    if prefix_matches:
+                        matches = prefix_matches
+                        logger.info(f"Cookie 前缀匹配成功: {[m.name for m in matches]}")
+
+                # 3. 模糊匹配:提取主域名(如 xiaohongshu)
+                if not matches:
+                    main_domain = domain.split('.')[0]  # 提取第一部分
+                    fuzzy_matches = list(_COOKIES_DIR.glob(f"{main_domain}*.json"))
+                    if fuzzy_matches:
+                        matches = fuzzy_matches
+                        logger.info(f"Cookie 模糊匹配成功: {[m.name for m in matches]} (主域名: {main_domain})")
+
                 if matches:
                     cookie_file = matches[0]  # 取第一个匹配的
+                    logger.info(f"使用 Cookie 文件: {cookie_file.name}")
                 else:
                     available = [f.stem for f in _COOKIES_DIR.glob("*.json")]
-                    return ToolResult(title="未找到 Cookie", output=f"没有匹配 {domain} 的文件,可用: {available}", error=f"无 {domain} 的 Cookie 文件")
+                    logger.warning(f"未找到匹配的 Cookie 文件。域名: {domain}, 可用: {available}")
+                    hint = f"可用的 Cookie 文件: {available}" if available else "提示:首次使用需要先手动登录,然后使用 browser_export_cookies 保存 Cookie"
+
+                    # 如果启用自动导航,直接访问目标页面
+                    if auto_navigate:
+                        await tools.navigate(url=url, browser_session=browser)
+                        await tools.wait(seconds=2, browser_session=browser)
+                        return ToolResult(
+                            title="未找到 Cookie,已导航到目标页面",
+                            output=f"没有找到 {domain} 的 Cookie 文件,已自动导航到 {url}。\n\n{hint}\n\n建议:如需保持登录态,请手动登录后使用 browser_export_cookies 保存 Cookie。",
+                            error=None,
+                            long_term_memory=f"未找到 {domain} 的 Cookie,已导航到 {url}"
+                        )
+                    else:
+                        return ToolResult(
+                            title="未找到 Cookie",
+                            output=f"没有匹配 {domain} 的 Cookie 文件。{hint}\n\n建议:使用 browser_navigate_to_url 访问 {url} 并手动登录,或使用 browser_export_cookies 保存当前 Cookie。",
+                            error=None,
+                            long_term_memory=f"未找到 {domain} 的 Cookie 文件"
+                        )
             else:
-                return ToolResult(title="未找到 Cookie", output=".cookies 目录不存在", error="Cookie 目录不存在")
+                # Cookie 目录不存在
+                if auto_navigate:
+                    await tools.navigate(url=url, browser_session=browser)
+                    await tools.wait(seconds=2, browser_session=browser)
+                    return ToolResult(
+                        title="首次使用 Cookie 功能,已导航到目标页面",
+                        output=f"这是首次使用 Cookie 功能,已自动导航到 {url}。\n\n建议:手动完成登录后,使用 browser_export_cookies 保存 Cookie 供下次使用。",
+                        error=None,
+                        long_term_memory="首次使用 Cookie 功能,已导航到目标页面"
+                    )
+                else:
+                    return ToolResult(
+                        title="Cookie 目录不存在",
+                        output=f"这是首次使用 Cookie 功能。建议:\n1. 使用 browser_navigate_to_url 访问 {url}\n2. 手动完成登录\n3. 使用 browser_export_cookies 保存 Cookie 供下次使用",
+                        error=None,
+                        long_term_memory="Cookie 目录不存在,这是首次使用"
+                    )
         else:
             cookie_file = _COOKIES_DIR / f"{name}.json"
             if not cookie_file.exists():
                 available = [f.stem for f in _COOKIES_DIR.glob("*.json")] if _COOKIES_DIR.exists() else []
-                return ToolResult(title="文件不存在", output=f"可用: {available}", error=f"未找到 .cookies/{name}.json")
+                hint = f"可用的 Cookie 文件: {available}" if available else "提示:使用 browser_export_cookies 保存 Cookie"
+
+                if auto_navigate:
+                    await tools.navigate(url=url, browser_session=browser)
+                    await tools.wait(seconds=2, browser_session=browser)
+                    return ToolResult(
+                        title="Cookie 文件不存在,已导航到目标页面",
+                        output=f"未找到 .cookies/{name}.json,已自动导航到 {url}。\n\n{hint}",
+                        error=None,
+                        long_term_memory=f"未找到 {name}.json,已导航到目标页面"
+                    )
+                else:
+                    return ToolResult(
+                        title="Cookie 文件不存在",
+                        output=f"未找到 .cookies/{name}.json。{hint}",
+                        error=None,
+                        long_term_memory=f"未找到 {name}.json Cookie 文件"
+                    )
 
         cookies = json.loads(cookie_file.read_text(encoding="utf-8"))
 

+ 1 - 1
agent/tools/builtin/experience.py

@@ -11,7 +11,7 @@ from ...llm.openrouter import openrouter_llm_call
 logger = logging.getLogger(__name__)
 
 # 默认经验存储路径(当无法从 context 获取时使用)
-DEFAULT_EXPERIENCES_PATH = "./.cache/experiences_0.md"
+DEFAULT_EXPERIENCES_PATH = "./.cache/experiences_restore.md"
 
 def _get_experiences_path(context: Optional[Any] = None) -> str:
     """

+ 1183 - 0
agent/tools/builtin/knowledge.py

@@ -0,0 +1,1183 @@
+"""
+原子知识保存工具
+
+提供便捷的 API 让 Agent 快速保存结构化的原子知识
+"""
+
+import os
+import re
+import json
+import yaml
+import logging
+from datetime import datetime
+from pathlib import Path
+from typing import List, Dict, Optional, Any
+from agent.tools import tool, ToolResult, ToolContext
+from ...llm.openrouter import openrouter_llm_call
+
+logger = logging.getLogger(__name__)
+
+
+def _generate_knowledge_id() -> str:
+    """生成知识原子 ID(带微秒和随机后缀避免冲突)"""
+    import uuid
+    timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')
+    random_suffix = uuid.uuid4().hex[:4]
+    return f"knowledge-{timestamp}-{random_suffix}"
+
+
+def _format_yaml_list(items: List[str], indent: int = 2) -> str:
+    """格式化 YAML 列表"""
+    if not items:
+        return "[]"
+    indent_str = " " * indent
+    return "\n" + "\n".join(f"{indent_str}- {item}" for item in items)
+
+
+@tool()
+async def save_knowledge(
+    scenario: str,
+    content: str,
+    tags_type: List[str],
+    urls: List[str] = None,
+    agent_id: str = "research_agent",
+    score: int = 3,
+    trace_id: str = "",
+) -> ToolResult:
+    """
+    保存原子知识到本地文件(JSON 格式)
+
+    Args:
+        scenario: 任务描述(在什么情景下 + 要完成什么目标 + 得到能达成一个什么结果)
+        content: 核心内容
+        tags_type: 知识类型标签,可选:tool, usercase, definition, plan, strategy
+        urls: 参考来源链接列表(论文/GitHub/博客等)
+        agent_id: 执行此调研的 agent ID
+        score: 初始评分 1-5(默认 3)
+        trace_id: 当前 trace ID(可选)
+
+    Returns:
+        保存结果
+    """
+    try:
+        # 生成 ID
+        knowledge_id = _generate_knowledge_id()
+
+        # 准备目录
+        knowledge_dir = Path(".cache/knowledge_atoms")
+        knowledge_dir.mkdir(parents=True, exist_ok=True)
+
+        # 构建文件路径(使用 .json 扩展名)
+        file_path = knowledge_dir / f"{knowledge_id}.json"
+
+        # 构建 JSON 数据结构
+        knowledge_data = {
+            "id": knowledge_id,
+            "trace_id": trace_id or "N/A",
+            "tags": {
+                "type": tags_type
+            },
+            "scenario": scenario,
+            "content": content,
+            "trace": {
+                "urls": urls or [],
+                "agent_id": agent_id,
+                "timestamp": datetime.now().isoformat()
+            },
+            "eval": {
+                "score": score,
+                "helpful": 0,
+                "harmful": 0,
+                "helpful_history": [],
+                "harmful_history": []
+            },
+            "metrics": {
+                "helpful": 1,
+                "harmful": 0
+            },
+            "created_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+        }
+
+        # 保存为 JSON 文件
+        with open(file_path, "w", encoding="utf-8") as f:
+            json.dump(knowledge_data, f, ensure_ascii=False, indent=2)
+
+        return ToolResult(
+            title="✅ 原子知识已保存",
+            output=f"知识 ID: {knowledge_id}\n文件路径: {file_path}\n\n场景:\n{scenario[:100]}...",
+            long_term_memory=f"保存原子知识: {knowledge_id} - {scenario[:50]}",
+            metadata={"knowledge_id": knowledge_id, "file_path": str(file_path)}
+        )
+
+    except Exception as e:
+        return ToolResult(
+            title="❌ 保存失败",
+            output=f"错误: {str(e)}",
+            error=str(e)
+        )
+
+
+@tool()
+async def update_knowledge(
+    knowledge_id: str,
+    add_helpful_case: Optional[Dict[str, str]] = None,
+    add_harmful_case: Optional[Dict[str, str]] = None,
+    update_score: Optional[int] = None,
+    evolve_feedback: Optional[str] = None,
+) -> ToolResult:
+    """
+    更新已有的原子知识的评估反馈
+
+    Args:
+        knowledge_id: 知识 ID(如 research-20260302-001)
+        add_helpful_case: 添加好用的案例 {"case_id": "...", "scenario": "...", "result": "...", "timestamp": "..."}
+        add_harmful_case: 添加不好用的案例 {"case_id": "...", "scenario": "...", "result": "...", "timestamp": "..."}
+        update_score: 更新评分(1-5)
+        evolve_feedback: 经验进化反馈(当提供时,会使用 LLM 重写知识内容)
+
+    Returns:
+        更新结果
+    """
+    try:
+        # 查找文件(支持 JSON 和 MD 格式)
+        knowledge_dir = Path(".cache/knowledge_atoms")
+        json_path = knowledge_dir / f"{knowledge_id}.json"
+        md_path = knowledge_dir / f"{knowledge_id}.md"
+
+        file_path = None
+        if json_path.exists():
+            file_path = json_path
+            is_json = True
+        elif md_path.exists():
+            file_path = md_path
+            is_json = False
+        else:
+            return ToolResult(
+                title="❌ 文件不存在",
+                output=f"未找到知识文件: {knowledge_id}",
+                error="文件不存在"
+            )
+
+        # 读取现有内容
+        with open(file_path, "r", encoding="utf-8") as f:
+            content = f.read()
+
+        # 解析数据
+        if is_json:
+            data = json.loads(content)
+        else:
+            # 解析 YAML frontmatter
+            yaml_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
+            if not yaml_match:
+                return ToolResult(
+                    title="❌ 格式错误",
+                    output=f"无法解析知识文件格式: {file_path}",
+                    error="格式错误"
+                )
+            data = yaml.safe_load(yaml_match.group(1))
+
+        # 更新内容
+        updated = False
+        summary = []
+
+        if add_helpful_case:
+            data["eval"]["helpful"] += 1
+            data["eval"]["helpful_history"].append(add_helpful_case)
+            data["metrics"]["helpful"] += 1
+            summary.append(f"添加 helpful 案例: {add_helpful_case.get('case_id')}")
+            updated = True
+
+        if add_harmful_case:
+            data["eval"]["harmful"] += 1
+            data["eval"]["harmful_history"].append(add_harmful_case)
+            data["metrics"]["harmful"] += 1
+            summary.append(f"添加 harmful 案例: {add_harmful_case.get('case_id')}")
+            updated = True
+
+        if update_score is not None:
+            data["eval"]["score"] = update_score
+            summary.append(f"更新评分: {update_score}")
+            updated = True
+
+        # 经验进化机制
+        if evolve_feedback:
+            old_content = data.get("content", "")
+            evolved_content = await _evolve_knowledge_with_llm(old_content, evolve_feedback)
+            data["content"] = evolved_content
+            data["metrics"]["helpful"] += 1
+            summary.append(f"知识进化: 基于反馈重写内容")
+            updated = True
+
+        if not updated:
+            return ToolResult(
+                title="⚠️ 无更新",
+                output="未指定任何更新内容",
+                long_term_memory="尝试更新原子知识但未指定更新内容"
+            )
+
+        # 更新时间戳
+        data["updated_at"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+
+        # 保存更新
+        if is_json:
+            with open(file_path, "w", encoding="utf-8") as f:
+                json.dump(data, f, ensure_ascii=False, indent=2)
+        else:
+            # 重新生成 YAML frontmatter
+            meta_str = yaml.dump(data, allow_unicode=True).strip()
+            with open(file_path, "w", encoding="utf-8") as f:
+                f.write(f"---\n{meta_str}\n---\n")
+
+        return ToolResult(
+            title="✅ 原子知识已更新",
+            output=f"知识 ID: {knowledge_id}\n文件路径: {file_path}\n\n更新内容:\n" + "\n".join(f"- {s}" for s in summary),
+            long_term_memory=f"更新原子知识: {knowledge_id}"
+        )
+
+    except Exception as e:
+        return ToolResult(
+            title="❌ 更新失败",
+            output=f"错误: {str(e)}",
+            error=str(e)
+        )
+
+
+@tool()
+async def list_knowledge(
+    limit: int = 10,
+    tags_type: Optional[List[str]] = None,
+) -> ToolResult:
+    """
+    列出已保存的原子知识
+
+    Args:
+        limit: 返回数量限制(默认 10)
+        tags_type: 按类型过滤(可选)
+
+    Returns:
+        知识列表
+    """
+    try:
+        knowledge_dir = Path(".cache/knowledge_atoms")
+
+        if not knowledge_dir.exists():
+            return ToolResult(
+                title="📂 知识库为空",
+                output="还没有保存任何原子知识",
+                long_term_memory="知识库为空"
+            )
+
+        # 获取所有文件
+        files = sorted(knowledge_dir.glob("*.md"), key=lambda x: x.stat().st_mtime, reverse=True)
+
+        if not files:
+            return ToolResult(
+                title="📂 知识库为空",
+                output="还没有保存任何原子知识",
+                long_term_memory="知识库为空"
+            )
+
+        # 读取并过滤
+        results = []
+        for file_path in files[:limit]:
+            with open(file_path, "r", encoding="utf-8") as f:
+                content = f.read()
+
+            # 提取关键信息
+            import re
+            id_match = re.search(r"id: (.+)", content)
+            scenario_match = re.search(r"scenario: \|\n  (.+)", content)
+            score_match = re.search(r"score: (\d+)", content)
+
+            knowledge_id = id_match.group(1) if id_match else "unknown"
+            scenario = scenario_match.group(1) if scenario_match else "N/A"
+            score = score_match.group(1) if score_match else "N/A"
+
+            results.append(f"- [{knowledge_id}] (⭐{score}) {scenario[:60]}...")
+
+        output = f"共找到 {len(files)} 条原子知识,显示最近 {len(results)} 条:\n\n" + "\n".join(results)
+
+        return ToolResult(
+            title="📚 原子知识列表",
+            output=output,
+            long_term_memory=f"列出 {len(results)} 条原子知识"
+        )
+
+    except Exception as e:
+        return ToolResult(
+            title="❌ 列表失败",
+            output=f"错误: {str(e)}",
+            error=str(e)
+        )
+
+
+# ===== 语义检索功能 =====
+
+async def _route_knowledge_by_llm(query_text: str, metadata_list: List[Dict], k: int = 5) -> List[str]:
+    """
+    第一阶段:语义路由。
+    让 LLM 挑选出 2*k 个语义相关的 ID。
+    """
+    if not metadata_list:
+        return []
+
+    # 扩大筛选范围到 2*k
+    routing_k = k * 2
+
+    routing_data = [
+        {
+            "id": m["id"],
+            "tags": m["tags"],
+            "scenario": m["scenario"][:100]  # 只取前100字符
+        } for m in metadata_list
+    ]
+
+    prompt = f"""
+你是一个知识检索专家。根据用户的当前任务需求,从下列原子知识元数据中挑选出最相关的最多 {routing_k} 个知识 ID。
+任务需求:"{query_text}"
+
+可选知识列表:
+{json.dumps(routing_data, ensure_ascii=False, indent=1)}
+
+请直接输出 ID 列表,用逗号分隔(例如: knowledge-20260302-001, research-20260302-002)。若无相关项请输出 "None"。
+"""
+
+    try:
+        print(f"\n[Step 1: 知识语义路由] 任务: '{query_text}' | 候选总数: {len(metadata_list)} | 目标提取数: {routing_k}")
+
+        response = await openrouter_llm_call(
+            messages=[{"role": "user", "content": prompt}],
+            model="google/gemini-2.0-flash-001"
+        )
+
+        content = response.get("content", "").strip()
+        selected_ids = [idx.strip() for idx in re.split(r'[,\s]+', content) if idx.strip().startswith(("knowledge-", "research-"))]
+
+        print(f"[Step 1: 知识语义路由] LLM 初选 ID ({len(selected_ids)}个): {selected_ids}")
+        return selected_ids
+    except Exception as e:
+        logger.error(f"LLM 知识路由失败: {e}")
+        return []
+
+
+async def _evolve_knowledge_with_llm(old_content: str, feedback: str) -> str:
+    """
+    使用 LLM 进行知识进化重写(类似经验进化机制)
+    """
+    prompt = f"""你是一个 AI Agent 知识库管理员。请根据反馈建议,对现有的知识内容进行重写进化。
+
+【原知识内容】:
+{old_content}
+
+【实战反馈建议】:
+{feedback}
+
+【重写要求】:
+1. 融合知识:将反馈中的避坑指南、新参数或修正后的选择逻辑融入原知识,使其更具通用性和准确性。
+2. 保持结构:如果原内容有特定格式(如 Markdown、代码示例等),请保持该格式。
+3. 语言:简洁直接,使用中文。
+4. 禁止:严禁输出任何开场白、解释语或额外的 Markdown 标题,直接返回重写后的正文。
+"""
+    try:
+        response = await openrouter_llm_call(
+            messages=[{"role": "user", "content": prompt}],
+            model="google/gemini-2.0-flash-001"
+        )
+
+        evolved_content = response.get("content", "").strip()
+
+        # 简单安全校验:如果 LLM 返回太短或为空,回退到原内容+追加
+        if len(evolved_content) < 5:
+            raise ValueError("LLM output too short")
+
+        return evolved_content
+
+    except Exception as e:
+        logger.warning(f"知识进化失败,采用追加模式回退: {e}")
+        timestamp = datetime.now().strftime('%Y-%m-%d')
+        return f"{old_content}\n\n---\n[Update {timestamp}]: {feedback}"
+
+
+async def _route_knowledge_by_llm(query_text: str, metadata_list: List[Dict], k: int = 5) -> List[str]:
+    """
+    第一阶段:语义路由。
+    让 LLM 挑选出 2*k 个语义相关的 ID。
+    """
+    if not metadata_list:
+        return []
+
+    # 扩大筛选范围到 2*k
+    routing_k = k * 2
+
+    routing_data = [
+        {
+            "id": m["id"],
+            "tags": m["tags"],
+            "scenario": m["scenario"][:100]  # 只取前100字符
+        } for m in metadata_list
+    ]
+
+    prompt = f"""
+你是一个知识检索专家。根据用户的当前任务需求,从下列原子知识元数据中挑选出最相关的最多 {routing_k} 个知识 ID。
+任务需求:"{query_text}"
+
+可选知识列表:
+{json.dumps(routing_data, ensure_ascii=False, indent=1)}
+
+请直接输出 ID 列表,用逗号分隔(例如: knowledge-20260302-001, research-20260302-002)。若无相关项请输出 "None"。
+"""
+
+    try:
+        print(f"\n[Step 1: 知识语义路由] 任务: '{query_text}' | 候选总数: {len(metadata_list)} | 目标提取数: {routing_k}")
+
+        response = await openrouter_llm_call(
+            messages=[{"role": "user", "content": prompt}],
+            model="google/gemini-2.0-flash-001"
+        )
+
+        content = response.get("content", "").strip()
+        selected_ids = [idx.strip() for idx in re.split(r'[,\s]+', content) if idx.strip().startswith(("knowledge-", "research-"))]
+
+        print(f"[Step 1: 知识语义路由] LLM 初选 ID ({len(selected_ids)}个): {selected_ids}")
+        return selected_ids
+    except Exception as e:
+        logger.error(f"LLM 知识路由失败: {e}")
+        return []
+
+
+async def _get_structured_knowledge(
+    query_text: str,
+    top_k: int = 5,
+    min_score: int = 3,
+    context: Optional[Any] = None,
+    tags_filter: Optional[List[str]] = None
+) -> List[Dict]:
+    """
+    语义检索原子知识(包括经验)
+
+    1. 解析知识库文件(支持 JSON 和 YAML 格式)
+    2. 语义路由:提取 2*k 个 ID
+    3. 质量精排:基于评分筛选出最终的 k 个
+
+    Args:
+        query_text: 查询文本
+        top_k: 返回数量
+        min_score: 最低评分过滤
+        context: 上下文(兼容 experience 接口)
+        tags_filter: 标签过滤(如 ["strategy"] 只返回经验)
+    """
+    knowledge_dir = Path(".cache/knowledge_atoms")
+
+    if not knowledge_dir.exists():
+        print(f"[Knowledge System] 警告: 知识库目录不存在 ({knowledge_dir})")
+        return []
+
+    # 同时支持 .json 和 .md 文件
+    json_files = list(knowledge_dir.glob("*.json"))
+    md_files = list(knowledge_dir.glob("*.md"))
+    files = json_files + md_files
+
+    if not files:
+        print(f"[Knowledge System] 警告: 知识库为空")
+        return []
+
+    # --- 阶段 1: 解析所有知识文件 ---
+    content_map = {}
+    metadata_list = []
+
+    for file_path in files:
+        try:
+            with open(file_path, "r", encoding="utf-8") as f:
+                content = f.read()
+
+            # 根据文件扩展名选择解析方式
+            if file_path.suffix == ".json":
+                # 解析 JSON 格式
+                metadata = json.loads(content)
+            else:
+                # 解析 YAML frontmatter(兼容旧格式)
+                yaml_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
+                if not yaml_match:
+                    logger.warning(f"跳过无效文件: {file_path}")
+                    continue
+                metadata = yaml.safe_load(yaml_match.group(1))
+
+            if not isinstance(metadata, dict):
+                logger.warning(f"跳过损坏的知识文件: {file_path}")
+                continue
+
+            kid = metadata.get("id")
+            if not kid:
+                logger.warning(f"跳过缺少 id 的知识文件: {file_path}")
+                continue
+
+            # 提取 scenario 和 content
+            scenario = metadata.get("scenario", "").strip()
+            content_text = metadata.get("content", "").strip()
+
+            # 标签过滤
+            tags = metadata.get("tags", {})
+            if tags_filter:
+                # 检查 tags.type 是否包含任何过滤标签
+                tag_types = tags.get("type", [])
+                if isinstance(tag_types, str):
+                    tag_types = [tag_types]
+                if not any(tag in tag_types for tag in tags_filter):
+                    continue  # 跳过不匹配的标签
+
+            meta_item = {
+                "id": kid,
+                "tags": tags,
+                "scenario": scenario,
+                "score": metadata.get("eval", {}).get("score", 3),
+                "helpful": metadata.get("metrics", {}).get("helpful", 0),
+                "harmful": metadata.get("metrics", {}).get("harmful", 0),
+            }
+            metadata_list.append(meta_item)
+            content_map[kid] = {
+                "scenario": scenario,
+                "content": content_text,
+                "tags": tags,
+                "score": meta_item["score"],
+                "helpful": meta_item["helpful"],
+                "harmful": meta_item["harmful"],
+            }
+        except Exception as e:
+            logger.error(f"解析知识文件失败 {file_path}: {e}")
+            continue
+
+    if not metadata_list:
+        print(f"[Knowledge System] 警告: 没有有效的知识条目")
+        return []
+
+    # --- 阶段 2: 语义路由 (取 2*k) ---
+    candidate_ids = await _route_knowledge_by_llm(query_text, metadata_list, k=top_k)
+
+    # --- 阶段 3: 质量精排 (根据评分和反馈选出最终的 k) ---
+    print(f"[Step 2: 知识质量精排] 正在根据评分和反馈进行打分...")
+    scored_items = []
+
+    for kid in candidate_ids:
+        if kid in content_map:
+            item = content_map[kid]
+            score = item["score"]
+            helpful = item["helpful"]
+            harmful = item["harmful"]
+
+            # 计算综合分:基础分 + helpful - harmful*2
+            quality_score = score + helpful - (harmful * 2.0)
+
+            # 过滤门槛:评分低于 min_score 或质量分过低
+            if score < min_score or quality_score < 0:
+                print(f"  - 剔除低质量知识: {kid} (Score: {score}, Helpful: {helpful}, Harmful: {harmful})")
+                continue
+
+            scored_items.append({
+                "id": kid,
+                "scenario": item["scenario"],
+                "content": item["content"],
+                "tags": item["tags"],
+                "score": score,
+                "quality_score": quality_score,
+                "metrics": {
+                    "helpful": helpful,
+                    "harmful": harmful
+                }
+            })
+
+    # 按照质量分排序
+    final_sorted = sorted(scored_items, key=lambda x: x["quality_score"], reverse=True)
+
+    # 截取最终的 top_k
+    result = final_sorted[:top_k]
+
+    print(f"[Step 2: 知识质量精排] 最终选定知识: {[it['id'] for it in result]}")
+    print(f"[Knowledge System] 检索结束。\n")
+    return result
+
+
+@tool()
+async def search_knowledge(
+    query: str,
+    top_k: int = 5,
+    min_score: int = 3,
+    tags_type: Optional[List[str]] = None,
+    context: Optional[ToolContext] = None,
+) -> ToolResult:
+    """
+    语义检索原子知识库
+
+    Args:
+        query: 搜索查询(任务描述)
+        top_k: 返回数量(默认 5)
+        min_score: 最低评分过滤(默认 3)
+        tags_type: 按类型过滤(tool/usercase/definition/plan)
+        context: 工具上下文
+
+    Returns:
+        相关知识列表
+    """
+    try:
+        relevant_items = await _get_structured_knowledge(
+            query_text=query,
+            top_k=top_k,
+            min_score=min_score
+        )
+
+        if not relevant_items:
+            return ToolResult(
+                title="🔍 未找到相关知识",
+                output=f"查询: {query}\n\n知识库中暂无相关的高质量知识。建议进行调研。",
+                long_term_memory=f"知识检索: 未找到相关知识 - {query[:50]}"
+            )
+
+        # 格式化输出
+        output_lines = [f"查询: {query}\n", f"找到 {len(relevant_items)} 条相关知识:\n"]
+
+        for idx, item in enumerate(relevant_items, 1):
+            output_lines.append(f"\n### {idx}. [{item['id']}] (⭐ {item['score']})")
+            output_lines.append(f"**场景**: {item['scenario'][:150]}...")
+            output_lines.append(f"**内容**: {item['content'][:200]}...")
+
+        return ToolResult(
+            title="✅ 知识检索成功",
+            output="\n".join(output_lines),
+            long_term_memory=f"知识检索: 找到 {len(relevant_items)} 条相关知识 - {query[:50]}",
+            metadata={
+                "count": len(relevant_items),
+                "knowledge_ids": [item["id"] for item in relevant_items],
+                "items": relevant_items
+            }
+        )
+
+    except Exception as e:
+        logger.error(f"知识检索失败: {e}")
+        return ToolResult(
+            title="❌ 检索失败",
+            output=f"错误: {str(e)}",
+            error=str(e)
+        )
+
+
+@tool(description="通过两阶段检索获取最相关的历史经验(strategy 标签的知识)")
+async def get_experience(
+    query: str,
+    k: int = 3,
+    context: Optional[ToolContext] = None,
+) -> ToolResult:
+    """
+    检索历史经验(兼容旧接口,实际调用 search_knowledge 并过滤 strategy 标签)
+
+    Args:
+        query: 搜索查询(任务描述)
+        k: 返回数量(默认 3)
+        context: 工具上下文
+
+    Returns:
+        相关经验列表
+    """
+    try:
+        relevant_items = await _get_structured_knowledge(
+            query_text=query,
+            top_k=k,
+            min_score=1,  # 经验的评分门槛较低
+            context=context,
+            tags_filter=["strategy"]  # 只返回经验
+        )
+
+        if not relevant_items:
+            return ToolResult(
+                title="🔍 未找到相关经验",
+                output=f"查询: {query}\n\n经验库中暂无相关的经验。",
+                long_term_memory=f"经验检索: 未找到相关经验 - {query[:50]}",
+                metadata={"items": [], "count": 0}
+            )
+
+        # 格式化输出(兼容旧格式)
+        output_lines = [f"查询: {query}\n", f"找到 {len(relevant_items)} 条相关经验:\n"]
+
+        for idx, item in enumerate(relevant_items, 1):
+            output_lines.append(f"\n### {idx}. [{item['id']}]")
+            output_lines.append(f"{item['content'][:300]}...")
+
+        return ToolResult(
+            title="✅ 经验检索成功",
+            output="\n".join(output_lines),
+            long_term_memory=f"经验检索: 找到 {len(relevant_items)} 条相关经验 - {query[:50]}",
+            metadata={
+                "items": relevant_items,
+                "count": len(relevant_items)
+            }
+        )
+
+    except Exception as e:
+        logger.error(f"经验检索失败: {e}")
+        return ToolResult(
+            title="❌ 检索失败",
+            output=f"错误: {str(e)}",
+            error=str(e)
+        )
+
+
+# ===== 批量更新功能(类似经验机制)=====
+
+async def _batch_update_knowledge(
+    update_map: Dict[str, Dict[str, Any]],
+    context: Optional[Any] = None
+) -> int:
+    """
+    内部函数:批量更新知识(兼容 experience 接口)
+
+    Args:
+        update_map: 更新映射 {knowledge_id: {"action": "helpful/harmful/evolve", "feedback": "..."}}
+        context: 上下文(兼容 experience 接口)
+
+    Returns:
+        成功更新的数量
+    """
+    if not update_map:
+        return 0
+
+    knowledge_dir = Path(".cache/knowledge_atoms")
+    if not knowledge_dir.exists():
+        return 0
+
+    success_count = 0
+    evolution_tasks = []
+    evolution_registry = {}  # task_idx -> (file_path, data)
+
+    for knowledge_id, instr in update_map.items():
+        try:
+            # 查找文件
+            json_path = knowledge_dir / f"{knowledge_id}.json"
+            md_path = knowledge_dir / f"{knowledge_id}.md"
+
+            file_path = None
+            is_json = False
+            if json_path.exists():
+                file_path = json_path
+                is_json = True
+            elif md_path.exists():
+                file_path = md_path
+                is_json = False
+            else:
+                continue
+
+            # 读取并解析
+            with open(file_path, "r", encoding="utf-8") as f:
+                content = f.read()
+
+            if is_json:
+                data = json.loads(content)
+            else:
+                yaml_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
+                if not yaml_match:
+                    continue
+                data = yaml.safe_load(yaml_match.group(1))
+
+            # 更新 metrics
+            action = instr.get("action")
+            feedback = instr.get("feedback", "")
+
+            # 处理 mixed 中间态
+            if action == "mixed":
+                data["metrics"]["helpful"] = data.get("metrics", {}).get("helpful", 0) + 1
+                action = "evolve"
+
+            if action == "helpful":
+                data["metrics"]["helpful"] = data.get("metrics", {}).get("helpful", 0) + 1
+            elif action == "harmful":
+                data["metrics"]["harmful"] = data.get("metrics", {}).get("harmful", 0) + 1
+            elif action == "evolve" and feedback:
+                # 注册进化任务
+                old_content = data.get("content", "")
+                task = _evolve_knowledge_with_llm(old_content, feedback)
+                evolution_tasks.append(task)
+                evolution_registry[len(evolution_tasks) - 1] = (file_path, data, is_json)
+                data["metrics"]["helpful"] = data.get("metrics", {}).get("helpful", 0) + 1
+
+            data["updated_at"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+
+            # 如果不需要进化,直接保存
+            if action != "evolve" or not feedback:
+                if is_json:
+                    with open(file_path, "w", encoding="utf-8") as f:
+                        json.dump(data, f, ensure_ascii=False, indent=2)
+                else:
+                    meta_str = yaml.dump(data, allow_unicode=True).strip()
+                    with open(file_path, "w", encoding="utf-8") as f:
+                        f.write(f"---\n{meta_str}\n---\n")
+                success_count += 1
+
+        except Exception as e:
+            logger.error(f"更新知识失败 {knowledge_id}: {e}")
+            continue
+
+    # 并发进化
+    if evolution_tasks:
+        import asyncio
+        print(f"🧬 并发处理 {len(evolution_tasks)} 条知识进化...")
+        evolved_results = await asyncio.gather(*evolution_tasks)
+
+        # 回填进化结果
+        for task_idx, (file_path, data, is_json) in evolution_registry.items():
+            data["content"] = evolved_results[task_idx].strip()
+
+            if is_json:
+                with open(file_path, "w", encoding="utf-8") as f:
+                    json.dump(data, f, ensure_ascii=False, indent=2)
+            else:
+                meta_str = yaml.dump(data, allow_unicode=True).strip()
+                with open(file_path, "w", encoding="utf-8") as f:
+                    f.write(f"---\n{meta_str}\n---\n")
+            success_count += 1
+
+    return success_count
+
+
+@tool()
+async def batch_update_knowledge(
+    feedback_list: List[Dict[str, Any]],
+    context: Optional[ToolContext] = None,
+) -> ToolResult:
+    """
+    批量反馈知识的有效性(类似经验机制)
+
+    Args:
+        feedback_list: 评价列表,每个元素包含:
+            - knowledge_id: (str) 知识 ID
+            - is_effective: (bool) 是否有效
+            - feedback: (str, optional) 改进建议,若有效且有建议则触发知识进化
+
+    Returns:
+        批量更新结果
+    """
+    try:
+        if not feedback_list:
+            return ToolResult(
+                title="⚠️ 反馈列表为空",
+                output="未提供任何反馈",
+                long_term_memory="批量更新知识: 反馈列表为空"
+            )
+
+        knowledge_dir = Path(".cache/knowledge_atoms")
+        if not knowledge_dir.exists():
+            return ToolResult(
+                title="❌ 知识库不存在",
+                output="知识库目录不存在",
+                error="知识库不存在"
+            )
+
+        success_count = 0
+        failed_items = []
+
+        for item in feedback_list:
+            knowledge_id = item.get("knowledge_id")
+            is_effective = item.get("is_effective")
+            feedback = item.get("feedback", "")
+
+            if not knowledge_id:
+                failed_items.append({"id": "unknown", "reason": "缺少 knowledge_id"})
+                continue
+
+            try:
+                # 查找文件
+                json_path = knowledge_dir / f"{knowledge_id}.json"
+                md_path = knowledge_dir / f"{knowledge_id}.md"
+
+                file_path = None
+                is_json = False
+                if json_path.exists():
+                    file_path = json_path
+                    is_json = True
+                elif md_path.exists():
+                    file_path = md_path
+                    is_json = False
+                else:
+                    failed_items.append({"id": knowledge_id, "reason": "文件不存在"})
+                    continue
+
+                # 读取并解析
+                with open(file_path, "r", encoding="utf-8") as f:
+                    content = f.read()
+
+                if is_json:
+                    data = json.loads(content)
+                else:
+                    yaml_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
+                    if not yaml_match:
+                        failed_items.append({"id": knowledge_id, "reason": "格式错误"})
+                        continue
+                    data = yaml.safe_load(yaml_match.group(1))
+
+                # 更新 metrics
+                if is_effective:
+                    data["metrics"]["helpful"] = data.get("metrics", {}).get("helpful", 0) + 1
+                    # 如果有反馈建议,触发进化
+                    if feedback:
+                        old_content = data.get("content", "")
+                        evolved_content = await _evolve_knowledge_with_llm(old_content, feedback)
+                        data["content"] = evolved_content
+                else:
+                    data["metrics"]["harmful"] = data.get("metrics", {}).get("harmful", 0) + 1
+
+                data["updated_at"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+
+                # 保存
+                if is_json:
+                    with open(file_path, "w", encoding="utf-8") as f:
+                        json.dump(data, f, ensure_ascii=False, indent=2)
+                else:
+                    meta_str = yaml.dump(data, allow_unicode=True).strip()
+                    with open(file_path, "w", encoding="utf-8") as f:
+                        f.write(f"---\n{meta_str}\n---\n")
+
+                success_count += 1
+
+            except Exception as e:
+                failed_items.append({"id": knowledge_id, "reason": str(e)})
+                continue
+
+        output_lines = [f"成功更新 {success_count} 条知识"]
+        if failed_items:
+            output_lines.append(f"\n失败 {len(failed_items)} 条:")
+            for item in failed_items:
+                output_lines.append(f"  - {item['id']}: {item['reason']}")
+
+        return ToolResult(
+            title="✅ 批量更新完成",
+            output="\n".join(output_lines),
+            long_term_memory=f"批量更新知识: 成功 {success_count} 条,失败 {len(failed_items)} 条"
+        )
+
+    except Exception as e:
+        logger.error(f"批量更新知识失败: {e}")
+        return ToolResult(
+            title="❌ 批量更新失败",
+            output=f"错误: {str(e)}",
+            error=str(e)
+        )
+
+
+# ===== 知识库瘦身功能(类似经验机制)=====
+
+@tool()
+async def slim_knowledge(
+    model: str = "anthropic/claude-sonnet-4.5",
+    context: Optional[ToolContext] = None,
+) -> ToolResult:
+    """
+    知识库瘦身:调用顶级大模型,将知识库中语义相似的知识合并精简
+
+    Args:
+        model: 使用的模型(默认 claude-sonnet-4.5)
+        context: 工具上下文
+
+    Returns:
+        瘦身结果报告
+    """
+    try:
+        knowledge_dir = Path(".cache/knowledge_atoms")
+
+        if not knowledge_dir.exists():
+            return ToolResult(
+                title="📂 知识库不存在",
+                output="知识库目录不存在,无需瘦身",
+                long_term_memory="知识库瘦身: 目录不存在"
+            )
+
+        # 获取所有文件
+        json_files = list(knowledge_dir.glob("*.json"))
+        md_files = list(knowledge_dir.glob("*.md"))
+        files = json_files + md_files
+
+        if len(files) < 2:
+            return ToolResult(
+                title="📂 知识库过小",
+                output=f"知识库仅有 {len(files)} 条,无需瘦身",
+                long_term_memory=f"知识库瘦身: 仅有 {len(files)} 条"
+            )
+
+        # 解析所有知识
+        parsed = []
+        for file_path in files:
+            try:
+                with open(file_path, "r", encoding="utf-8") as f:
+                    content = f.read()
+
+                if file_path.suffix == ".json":
+                    data = json.loads(content)
+                else:
+                    yaml_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
+                    if not yaml_match:
+                        continue
+                    data = yaml.safe_load(yaml_match.group(1))
+
+                parsed.append({
+                    "file_path": file_path,
+                    "data": data,
+                    "is_json": file_path.suffix == ".json"
+                })
+            except Exception as e:
+                logger.error(f"解析文件失败 {file_path}: {e}")
+                continue
+
+        if len(parsed) < 2:
+            return ToolResult(
+                title="📂 有效知识过少",
+                output=f"有效知识仅有 {len(parsed)} 条,无需瘦身",
+                long_term_memory=f"知识库瘦身: 有效知识 {len(parsed)} 条"
+            )
+
+        # 构造发给大模型的内容
+        entries_text = ""
+        for p in parsed:
+            data = p["data"]
+            entries_text += f"[ID: {data.get('id')}] [Tags: {data.get('tags', {})}] "
+            entries_text += f"[Metrics: {data.get('metrics', {})}] [Score: {data.get('eval', {}).get('score', 3)}]\n"
+            entries_text += f"Scenario: {data.get('scenario', 'N/A')}\n"
+            entries_text += f"Content: {data.get('content', '')[:200]}...\n\n"
+
+        prompt = f"""你是一个 AI Agent 知识库管理员。以下是当前知识库的全部条目,请执行瘦身操作:
+
+【任务】:
+1. 识别语义高度相似或重复的知识,将它们合并为一条更精炼、更通用的知识。
+2. 合并时保留 helpful 最高的那条的 ID 和 metrics(metrics 中 helpful/harmful 取各条之和)。
+3. 对于独立的、无重复的知识,保持原样不动。
+4. 保持原有的知识结构和格式。
+
+【当前知识库】:
+{entries_text}
+
+【输出格式要求】:
+严格按以下格式输出每条知识,条目之间用 === 分隔:
+ID: <保留的id>
+TAGS: <yaml格式的tags>
+METRICS: <yaml格式的metrics>
+SCORE: <评分>
+SCENARIO: <场景描述>
+CONTENT: <合并后的知识内容>
+===
+
+最后一行输出合并报告,格式:
+REPORT: 原有 X 条,合并后 Y 条,精简了 Z 条。
+
+禁止输出任何开场白或解释。"""
+
+        print(f"\n[知识瘦身] 正在调用 {model} 分析 {len(parsed)} 条知识...")
+        response = await openrouter_llm_call(
+            messages=[{"role": "user", "content": prompt}],
+            model=model
+        )
+        content = response.get("content", "").strip()
+        if not content:
+            return ToolResult(
+                title="❌ 大模型返回为空",
+                output="大模型返回为空,瘦身失败",
+                error="大模型返回为空"
+            )
+
+        # 解析大模型输出
+        report_line = ""
+        new_entries = []
+        blocks = [b.strip() for b in content.split("===") if b.strip()]
+
+        for block in blocks:
+            if block.startswith("REPORT:"):
+                report_line = block
+                continue
+
+            lines = block.split("\n")
+            kid, tags, metrics, score, scenario, content_lines = None, {}, {}, 3, "", []
+            current_field = None
+
+            for line in lines:
+                if line.startswith("ID:"):
+                    kid = line[3:].strip()
+                    current_field = None
+                elif line.startswith("TAGS:"):
+                    try:
+                        tags = yaml.safe_load(line[5:].strip()) or {}
+                    except Exception:
+                        tags = {}
+                    current_field = None
+                elif line.startswith("METRICS:"):
+                    try:
+                        metrics = yaml.safe_load(line[8:].strip()) or {}
+                    except Exception:
+                        metrics = {"helpful": 0, "harmful": 0}
+                    current_field = None
+                elif line.startswith("SCORE:"):
+                    try:
+                        score = int(line[6:].strip())
+                    except Exception:
+                        score = 3
+                    current_field = None
+                elif line.startswith("SCENARIO:"):
+                    scenario = line[9:].strip()
+                    current_field = "scenario"
+                elif line.startswith("CONTENT:"):
+                    content_lines.append(line[8:].strip())
+                    current_field = "content"
+                elif current_field == "scenario":
+                    scenario += "\n" + line
+                elif current_field == "content":
+                    content_lines.append(line)
+
+            if kid and content_lines:
+                new_data = {
+                    "id": kid,
+                    "tags": tags,
+                    "scenario": scenario,
+                    "content": "\n".join(content_lines).strip(),
+                    "metrics": metrics,
+                    "eval": {
+                        "score": score,
+                        "helpful": 0,
+                        "harmful": 0,
+                        "helpful_history": [],
+                        "harmful_history": []
+                    },
+                    "updated_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+                }
+                new_entries.append(new_data)
+
+        if not new_entries:
+            return ToolResult(
+                title="❌ 解析失败",
+                output="解析大模型输出失败,知识库未修改",
+                error="解析失败"
+            )
+
+        # 删除旧文件
+        for p in parsed:
+            try:
+                p["file_path"].unlink()
+            except Exception as e:
+                logger.error(f"删除旧文件失败 {p['file_path']}: {e}")
+
+        # 写入新文件(统一使用 JSON 格式)
+        for data in new_entries:
+            file_path = knowledge_dir / f"{data['id']}.json"
+            with open(file_path, "w", encoding="utf-8") as f:
+                json.dump(data, f, ensure_ascii=False, indent=2)
+
+        result = f"瘦身完成:{len(parsed)} → {len(new_entries)} 条知识"
+        if report_line:
+            result += f"\n{report_line}"
+
+        print(f"[知识瘦身] {result}")
+        return ToolResult(
+            title="✅ 知识库瘦身完成",
+            output=result,
+            long_term_memory=f"知识库瘦身: {len(parsed)} → {len(new_entries)} 条"
+        )
+
+    except Exception as e:
+        logger.error(f"知识库瘦身失败: {e}")
+        return ToolResult(
+            title="❌ 瘦身失败",
+            output=f"错误: {str(e)}",
+            error=str(e)
+        )
+

+ 11 - 0
agent/tools/builtin/subagent.py

@@ -133,6 +133,16 @@ def _format_single_result(result: Dict[str, Any], sub_trace_id: str, continued:
     if summary:
         lines.append(summary)
         lines.append("")
+
+    # 添加保存的知识 ID
+    saved_knowledge_ids = result.get("saved_knowledge_ids", [])
+    if saved_knowledge_ids:
+        lines.append("---\n")
+        lines.append(f"**保存的知识** ({len(saved_knowledge_ids)} 条):")
+        for kid in saved_knowledge_ids:
+            lines.append(f"- {kid}")
+        lines.append("")
+
     lines.append("---\n")
     lines.append("**执行统计**:")
     stats = result.get("stats", {})
@@ -146,6 +156,7 @@ def _format_single_result(result: Dict[str, Any], sub_trace_id: str, continued:
         "mode": "delegate",
         "sub_trace_id": sub_trace_id,
         "continue_from": continued,
+        "saved_knowledge_ids": saved_knowledge_ids,  # 传递给父 agent
         **result,
         "summary": formatted_summary,
     }

+ 3 - 0
agent/tools/models.py

@@ -41,6 +41,9 @@ class ToolResult:
 	attachments: List[str] = field(default_factory=list)  # 文件路径列表
 	images: List[Dict[str, Any]] = field(default_factory=list)  # 图片列表
 
+	# Token追踪(用于工具内部LLM调用)
+	tool_usage: Optional[Dict[str, Any]] = None  # 格式:{"model": "...", "prompt_tokens": 100, "completion_tokens": 50, "cost": 0.0}
+
 	def to_llm_message(self, first_time: bool = True) -> str:
 		"""
 		转换为给 LLM 的消息

+ 13 - 3
agent/tools/registry.py

@@ -241,10 +241,20 @@ class ToolRegistry:
 			# 处理 ToolResult 对象
 			from agent.tools.models import ToolResult
 			if isinstance(result, ToolResult):
-				# 有图片时返回 dict 以便 runner 构建多模态消息
+				ret = {"text": result.to_llm_message()}
+
+				# 保留images
 				if result.images:
-					return {"text": result.to_llm_message(), "images": result.images}
-				return result.to_llm_message()
+					ret["images"] = result.images
+
+				# 保留tool_usage
+				if result.tool_usage:
+					ret["tool_usage"] = result.tool_usage
+
+				# 向后兼容:只有text时返回字符串
+				if len(ret) == 1:
+					return ret["text"]
+				return ret
 
 			return json.dumps(result, ensure_ascii=False, indent=2)
 

+ 6 - 5
agent/trace/compaction.py

@@ -84,6 +84,7 @@ class CompressionConfig:
     max_tokens: int = 0                # 最大 token 数(0 = 自动:context_window * 0.5)
     threshold_ratio: float = 0.5       # 触发压缩的阈值 = context_window 的比例
     keep_recent_messages: int = 10     # Level 1 中始终保留最近 N 条消息
+    max_messages: int = 50             # 最大消息数(超过此数量触发压缩,0 = 禁用)
 
     def get_max_tokens(self, model: str) -> int:
         """获取实际的 max_tokens(如果为 0 则自动计算)"""
@@ -297,13 +298,13 @@ def needs_level2_compression(
 
 # ===== Level 2: 压缩 Prompt =====
 
-COMPRESSION_EVAL_PROMPT = """请对以上对话历史进行压缩总结,并评价所引用的历史经验。
-### 任务 1:评价已用经验
-本次任务参考了以下经验内容:{ex_reference_list}
+COMPRESSION_EVAL_PROMPT = """请对以上对话历史进行压缩总结,并评价所引用的历史知识/经验。
+### 任务 1:评价已用知识
+本次任务参考了以下知识内容:{ex_reference_list}
 
-请对比“经验建议”与“实际执行轨迹”,给出三色打分:
+请对比”知识建议”与”实际执行轨迹”,给出三色打分:
 [[EVALUATION]]
-ID: ex_xxx | Result: helpful/harmful/mixed | Reason: [优点]... [局限/修正]...
+ID: knowledge-xxx 或 research-xxx | Result: helpful/harmful/mixed | Reason: [优点]... [局限/修正]...
 
 ### 任务 2:对话历史摘要
 要求:

+ 144 - 0
agent/trace/examples_api.py

@@ -0,0 +1,144 @@
+"""
+Examples API - 提供 examples 项目列表和 prompt 读取接口
+"""
+
+import os
+from typing import List, Optional
+from pathlib import Path
+from fastapi import APIRouter, HTTPException
+from pydantic import BaseModel
+
+
+router = APIRouter(prefix="/api/examples", tags=["examples"])
+
+
+class ExampleProject(BaseModel):
+    """Example 项目信息"""
+    name: str
+    path: str
+    has_prompt: bool
+
+
+class ExampleListResponse(BaseModel):
+    """Example 列表响应"""
+    projects: List[ExampleProject]
+
+
+class PromptResponse(BaseModel):
+    """Prompt 响应"""
+    system_prompt: str
+    user_prompt: str
+    model: Optional[str] = None
+    temperature: Optional[float] = None
+
+
+# 配置 examples 目录路径
+EXAMPLES_DIR = Path("examples")
+
+
+@router.get("", response_model=ExampleListResponse)
+async def list_examples():
+    """
+    列出所有 example 项目
+
+    扫描 examples 目录,返回所有子目录及其 prompt 文件状态
+    """
+    if not EXAMPLES_DIR.exists():
+        return ExampleListResponse(projects=[])
+
+    projects = []
+    for item in EXAMPLES_DIR.iterdir():
+        if item.is_dir():
+            # 检查是否有 prompt 文件
+            prompt_file = item / "production.prompt"
+            has_prompt = prompt_file.exists()
+
+            projects.append(ExampleProject(
+                name=item.name,
+                path=str(item),
+                has_prompt=has_prompt
+            ))
+
+    # 按名称排序
+    projects.sort(key=lambda x: x.name)
+
+    return ExampleListResponse(projects=projects)
+
+
+@router.get("/{project_name}/prompt", response_model=PromptResponse)
+async def get_example_prompt(project_name: str):
+    """
+    获取指定 example 项目的 prompt
+
+    读取 production.prompt 文件,解析 frontmatter 和内容
+    """
+    project_path = EXAMPLES_DIR / project_name
+    if not project_path.exists() or not project_path.is_dir():
+        raise HTTPException(status_code=404, detail=f"Project not found: {project_name}")
+
+    prompt_file = project_path / "production.prompt"
+    if not prompt_file.exists():
+        raise HTTPException(status_code=404, detail=f"Prompt file not found for project: {project_name}")
+
+    try:
+        content = prompt_file.read_text(encoding="utf-8")
+
+        # 解析 frontmatter 和内容
+        system_prompt = ""
+        user_prompt = ""
+        model = None
+        temperature = None
+
+        # 检查是否有 frontmatter
+        if content.startswith("---"):
+            parts = content.split("---", 2)
+            if len(parts) >= 3:
+                frontmatter = parts[1].strip()
+                body = parts[2].strip()
+
+                # 解析 frontmatter
+                for line in frontmatter.split("\n"):
+                    if ":" in line:
+                        key, value = line.split(":", 1)
+                        key = key.strip()
+                        value = value.strip()
+                        if key == "model":
+                            model = value
+                        elif key == "temperature":
+                            try:
+                                temperature = float(value)
+                            except ValueError:
+                                pass
+            else:
+                body = content
+        else:
+            body = content
+
+        # 解析 $system$ 和 $user$ 部分
+        if "$system$" in body:
+            parts = body.split("$system$", 1)
+            if len(parts) > 1:
+                rest = parts[1]
+                if "$user$" in rest:
+                    system_part, user_part = rest.split("$user$", 1)
+                    system_prompt = system_part.strip()
+                    user_prompt = user_part.strip()
+                else:
+                    system_prompt = rest.strip()
+        elif "$user$" in body:
+            parts = body.split("$user$", 1)
+            if len(parts) > 1:
+                user_prompt = parts[1].strip()
+        else:
+            # 没有标记,全部作为 user_prompt
+            user_prompt = body.strip()
+
+        return PromptResponse(
+            system_prompt=system_prompt,
+            user_prompt=user_prompt,
+            model=model,
+            temperature=temperature
+        )
+
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=f"Failed to read prompt file: {str(e)}")

+ 14 - 0
agent/trace/goal_models.py

@@ -70,6 +70,9 @@ class Goal:
     self_stats: GoalStats = field(default_factory=GoalStats)          # 自身统计(仅直接关联的 messages)
     cumulative_stats: GoalStats = field(default_factory=GoalStats)    # 累计统计(自身 + 所有后代)
 
+    # 相关知识(自动检索注入)
+    knowledge: Optional[List[Dict[str, Any]]] = None                  # 相关知识列表
+
     created_at: datetime = field(default_factory=datetime.now)
 
     def to_dict(self) -> Dict[str, Any]:
@@ -87,6 +90,7 @@ class Goal:
             "sub_trace_metadata": self.sub_trace_metadata,
             "self_stats": self.self_stats.to_dict(),
             "cumulative_stats": self.cumulative_stats.to_dict(),
+            "knowledge": self.knowledge,
             "created_at": self.created_at.isoformat() if self.created_at else None,
         }
 
@@ -118,6 +122,7 @@ class Goal:
             sub_trace_metadata=data.get("sub_trace_metadata"),
             self_stats=self_stats,
             cumulative_stats=cumulative_stats,
+            knowledge=data.get("knowledge"),
             created_at=created_at or datetime.now(),
         )
 
@@ -395,6 +400,15 @@ class GoalTree:
             if goal.summary and (include_summary or goal.id in current_path):
                 result.append(f"{prefix}    → {goal.summary}")
 
+            # 显示相关知识:仅在当前焦点 goal 显示
+            if goal.id == self.current_id and goal.knowledge:
+                result.append(f"{prefix}    📚 相关知识 ({len(goal.knowledge)} 条):")
+                for idx, k in enumerate(goal.knowledge[:3], 1):
+                    k_id = k.get('id', 'N/A')
+                    # 将多行内容压缩为单行摘要
+                    k_content = k.get('content', '').replace('\n', ' ').strip()[:80]
+                    result.append(f"{prefix}       {idx}. [{k_id}] {k_content}...")
+
             # 递归处理子目标
             children = self.get_children(goal.id)
 

+ 102 - 0
agent/trace/logs_websocket.py

@@ -0,0 +1,102 @@
+"""
+Logs WebSocket - 实时推送后端日志到前端
+"""
+
+import asyncio
+import logging
+from typing import Set
+from fastapi import APIRouter, WebSocket, WebSocketDisconnect
+from datetime import datetime
+
+
+router = APIRouter(prefix="/api/logs", tags=["logs"])
+
+
+# 存储所有连接的WebSocket客户端
+_clients: Set[WebSocket] = set()
+
+
+class WebSocketLogHandler(logging.Handler):
+    """自定义日志处理器,将日志推送到WebSocket客户端"""
+
+    def emit(self, record: logging.LogRecord):
+        """发送日志记录到所有连接的客户端"""
+        try:
+            log_entry = self.format(record)
+            # 构造日志消息
+            message = {
+                "timestamp": datetime.now().isoformat(),
+                "level": record.levelname,
+                "name": record.name,
+                "message": log_entry,
+            }
+            # 异步发送到所有客户端
+            asyncio.create_task(broadcast_log(message))
+        except Exception:
+            self.handleError(record)
+
+
+async def broadcast_log(message: dict):
+    """广播日志消息到所有连接的客户端"""
+    disconnected = set()
+    for client in _clients:
+        try:
+            await client.send_json(message)
+        except Exception:
+            disconnected.add(client)
+
+    # 移除断开连接的客户端
+    for client in disconnected:
+        _clients.discard(client)
+
+
+@router.websocket("/watch")
+async def logs_websocket(websocket: WebSocket):
+    """
+    日志WebSocket端点
+
+    客户端连接后,实时接收后端日志
+    """
+    await websocket.accept()
+    _clients.add(websocket)
+
+    try:
+        # 发送欢迎消息
+        await websocket.send_json({
+            "timestamp": datetime.now().isoformat(),
+            "level": "INFO",
+            "name": "logs_websocket",
+            "message": "Connected to logs stream",
+        })
+
+        # 保持连接,等待客户端断开
+        while True:
+            # 接收客户端消息(用于保持连接)
+            await websocket.receive_text()
+    except WebSocketDisconnect:
+        pass
+    finally:
+        _clients.discard(websocket)
+
+
+def setup_websocket_logging(level=logging.INFO):
+    """
+    设置WebSocket日志处理器
+
+    将根日志器的日志推送到WebSocket客户端
+    """
+    handler = WebSocketLogHandler()
+    handler.setLevel(level)
+
+    # 设置日志格式
+    formatter = logging.Formatter(
+        "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
+        datefmt="%Y-%m-%d %H:%M:%S"
+    )
+    handler.setFormatter(formatter)
+
+    # 添加到根日志器
+    root_logger = logging.getLogger()
+    root_logger.addHandler(handler)
+
+    return handler

+ 119 - 10
agent/trace/store.py

@@ -61,6 +61,10 @@ class FileSystemTraceStore:
         """获取 events.jsonl 文件路径"""
         return self._get_trace_dir(trace_id) / "events.jsonl"
 
+    def _get_model_usage_file(self, trace_id: str) -> Path:
+        """获取 model_usage.json 文件路径"""
+        return self._get_trace_dir(trace_id) / "model_usage.json"
+
     # ===== Trace 操作 =====
 
     async def create_trace(self, trace: Trace) -> str:
@@ -74,7 +78,7 @@ class FileSystemTraceStore:
 
         # 写入 meta.json
         meta_file = self._get_meta_file(trace.trace_id)
-        meta_file.write_text(json.dumps(trace.to_dict(), indent=2, ensure_ascii=False))
+        meta_file.write_text(json.dumps(trace.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8")
 
         # 创建空的 events.jsonl
         events_file = self._get_events_file(trace.trace_id)
@@ -88,7 +92,7 @@ class FileSystemTraceStore:
         if not meta_file.exists():
             return None
 
-        data = json.loads(meta_file.read_text())
+        data = json.loads(meta_file.read_text(encoding="utf-8"))
 
         # 解析 datetime 字段
         if data.get("created_at"):
@@ -111,7 +115,7 @@ class FileSystemTraceStore:
 
         # 写回文件
         meta_file = self._get_meta_file(trace_id)
-        meta_file.write_text(json.dumps(trace.to_dict(), indent=2, ensure_ascii=False))
+        meta_file.write_text(json.dumps(trace.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8")
 
     async def list_traces(
         self,
@@ -136,7 +140,7 @@ class FileSystemTraceStore:
                 continue
 
             try:
-                data = json.loads(meta_file.read_text())
+                data = json.loads(meta_file.read_text(encoding="utf-8"))
 
                 # 过滤
                 if mode and data.get("mode") != mode:
@@ -172,7 +176,7 @@ class FileSystemTraceStore:
             return None
 
         try:
-            data = json.loads(goal_file.read_text())
+            data = json.loads(goal_file.read_text(encoding="utf-8"))
             return GoalTree.from_dict(data)
         except Exception:
             return None
@@ -180,7 +184,7 @@ class FileSystemTraceStore:
     async def update_goal_tree(self, trace_id: str, tree: GoalTree) -> None:
         """更新完整 GoalTree"""
         goal_file = self._get_goal_file(trace_id)
-        goal_file.write_text(json.dumps(tree.to_dict(), indent=2, ensure_ascii=False))
+        goal_file.write_text(json.dumps(tree.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8")
 
     async def add_goal(self, trace_id: str, goal: Goal) -> None:
         """添加 Goal 到 GoalTree"""
@@ -197,7 +201,14 @@ class FileSystemTraceStore:
             "parent_id": goal.parent_id
         }
         await self.append_event(trace_id, "goal_added", event_data)
-        print(f"[DEBUG] Pushed goal_added event: goal_id={goal.id}, parent_id={goal.parent_id}")
+
+        # 打印详细的 goal 信息
+        desc_preview = goal.description[:80] + "..." if len(goal.description) > 80 else goal.description
+        print(f"[Goal Added] ID={goal.id}, Parent={goal.parent_id or 'root'}")
+        print(f"  📝 {desc_preview}")
+        if goal.reason:
+            reason_preview = goal.reason[:60] + "..." if len(goal.reason) > 60 else goal.reason
+            print(f"  💡 {reason_preview}")
 
     async def update_goal(self, trace_id: str, goal_id: str, **updates) -> None:
         """更新 Goal 字段"""
@@ -459,7 +470,7 @@ class FileSystemTraceStore:
             message_file = trace_dir / "messages" / f"{message_id}.json"
             if message_file.exists():
                 try:
-                    data = json.loads(message_file.read_text())
+                    data = json.loads(message_file.read_text(encoding="utf-8"))
                     return Message.from_dict(data)
                 except Exception:
                     pass
@@ -479,7 +490,7 @@ class FileSystemTraceStore:
         messages = []
         for message_file in messages_dir.glob("*.json"):
             try:
-                data = json.loads(message_file.read_text())
+                data = json.loads(message_file.read_text(encoding="utf-8"))
                 msg = Message.from_dict(data)
                 messages.append(msg)
             except Exception:
@@ -541,7 +552,7 @@ class FileSystemTraceStore:
         # 确定文件路径
         messages_dir = self._get_messages_dir(message.trace_id)
         message_file = messages_dir / f"{message_id}.json"
-        message_file.write_text(json.dumps(message.to_dict(), indent=2, ensure_ascii=False))
+        message_file.write_text(json.dumps(message.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8")
 
     async def abandon_messages_after(self, trace_id: str, cutoff_sequence: int) -> List[str]:
         """
@@ -566,6 +577,104 @@ class FileSystemTraceStore:
 
         return abandoned_ids
 
+    # ===== 模型使用追踪 =====
+
+    async def record_model_usage(
+        self,
+        trace_id: str,
+        sequence: int,
+        role: str,
+        model: str,
+        prompt_tokens: int,
+        completion_tokens: int,
+        cache_read_tokens: int = 0,
+        tool_name: Optional[str] = None,
+    ) -> None:
+        """
+        记录模型使用情况到 model_usage.json
+
+        Args:
+            trace_id: Trace ID
+            sequence: 消息序号
+            role: 角色(assistant/tool)
+            model: 模型名称
+            prompt_tokens: 输入tokens
+            completion_tokens: 输出tokens
+            cache_read_tokens: 缓存读取tokens
+            tool_name: 工具名称(role=tool时)
+        """
+        usage_file = self._get_model_usage_file(trace_id)
+
+        # 读取现有数据
+        if usage_file.exists():
+            data = json.loads(usage_file.read_text(encoding="utf-8"))
+        else:
+            data = {
+                "summary": {
+                    "total_models": 0,
+                    "total_tokens": 0,
+                    "total_cache_read_tokens": 0,
+                    "agent_tokens": 0,
+                    "tool_tokens": 0,
+                },
+                "models": [],
+                "timeline": [],
+            }
+
+        # 更新summary
+        total_tokens = prompt_tokens + completion_tokens
+        data["summary"]["total_tokens"] += total_tokens
+        data["summary"]["total_cache_read_tokens"] += cache_read_tokens
+
+        if role == "assistant":
+            data["summary"]["agent_tokens"] += total_tokens
+            source = "agent"
+        else:
+            data["summary"]["tool_tokens"] += total_tokens
+            source = f"tool:{tool_name}" if tool_name else "tool"
+
+        # 更新models列表
+        model_entry = None
+        for m in data["models"]:
+            if m["model"] == model and m["source"] == source:
+                model_entry = m
+                break
+
+        if model_entry:
+            model_entry["prompt_tokens"] += prompt_tokens
+            model_entry["completion_tokens"] += completion_tokens
+            model_entry["total_tokens"] += total_tokens
+            model_entry["cache_read_tokens"] += cache_read_tokens
+            model_entry["call_count"] += 1
+        else:
+            data["models"].append({
+                "model": model,
+                "source": source,
+                "prompt_tokens": prompt_tokens,
+                "completion_tokens": completion_tokens,
+                "total_tokens": total_tokens,
+                "cache_read_tokens": cache_read_tokens,
+                "call_count": 1,
+            })
+            data["summary"]["total_models"] = len(data["models"])
+
+        # 添加到timeline
+        timeline_entry = {
+            "sequence": sequence,
+            "role": role,
+            "model": model,
+            "prompt_tokens": prompt_tokens,
+            "completion_tokens": completion_tokens,
+        }
+        if cache_read_tokens > 0:
+            timeline_entry["cache_read_tokens"] = cache_read_tokens
+        if tool_name:
+            timeline_entry["tool_name"] = tool_name
+        data["timeline"].append(timeline_entry)
+
+        # 写回文件
+        usage_file.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
+
     # ===== 事件流操作(用于 WebSocket 断线续传)=====
 
     async def get_events(

+ 11 - 0
api_server.py

@@ -19,6 +19,8 @@ from agent.trace import FileSystemTraceStore
 from agent.trace.api import router as api_router, set_trace_store as set_api_trace_store
 from agent.trace.run_api import router as run_router, experiences_router, set_runner
 from agent.trace.websocket import router as ws_router, set_trace_store as set_ws_trace_store
+from agent.trace.examples_api import router as examples_router
+from agent.trace.logs_websocket import router as logs_router, setup_websocket_logging
 
 
 # ===== 日志配置 =====
@@ -29,6 +31,9 @@ logging.basicConfig(
 )
 logger = logging.getLogger(__name__)
 
+# 设置WebSocket日志推送
+setup_websocket_logging(level=logging.INFO)
+
 
 # ===== FastAPI 应用 =====
 
@@ -75,6 +80,9 @@ set_runner(runner)
 
 # ===== 注册路由 =====
 
+# Examples API(GET /api/examples)
+app.include_router(examples_router)
+
 # Trace 执行 API(POST + GET /running,需配置 Runner)
 # 注意:run_router 必须在 api_router 之前注册,否则 GET /running 会被 /{trace_id} 捕获
 app.include_router(run_router)
@@ -88,6 +96,9 @@ app.include_router(api_router)
 # Trace WebSocket(实时推送)
 app.include_router(ws_router)
 
+# Logs WebSocket(日志推送)
+app.include_router(logs_router)
+
 @app.websocket("/ws_ping")
 async def ws_ping(websocket: WebSocket):
     await websocket.accept()

BIN
examples/deep_research/input/1.jpeg


+ 55 - 0
examples/deep_research/input/1_invariant_features.json

@@ -0,0 +1,55 @@
+{
+  "主色调Hex序列": [
+    {
+      "hex": "#3D5C3D",
+      "占比": "25.3%"
+    },
+    {
+      "hex": "#6B8E6B",
+      "占比": "20.1%"
+    },
+    {
+      "hex": "#FFFFFF",
+      "占比": "15.7%"
+    },
+    {
+      "hex": "#A0C0A0",
+      "占比": "12.5%"
+    },
+    {
+      "hex": "#80A080",
+      "占比": "9.8%"
+    }
+  ],
+  "色温估算值": 6000,
+  "高光RGB偏移量": [
+    0,
+    0,
+    0
+  ],
+  "中间调RGB偏移量": [
+    0,
+    0,
+    0
+  ],
+  "阴影RGB偏移量": [
+    0,
+    0,
+    0
+  ],
+  "饱和度量化值": 150,
+  "对比度量化值": 130,
+  "亮度量化值": 180,
+  "暗角强度": 0.0,
+  "颗粒噪声强度": 0.0,
+  "shadows色相偏移角度": 0,
+  "midtones色相偏移角度": 0,
+  "highlights色相偏移角度": 0,
+  "锐化程度": 0.5,
+  "胶片感特征": {
+    "褪色": false,
+    "漏光": false,
+    "色差": false
+  },
+  "LUT风格分类": "自然光照"
+}

BIN
examples/deep_research/input/3.jpeg


+ 55 - 0
examples/deep_research/input/3_invariant_features.json

@@ -0,0 +1,55 @@
+{
+  "主色调Hex序列": [
+    {
+      "color": "#4A6B2F",
+      "占比": "25.3%"
+    },
+    {
+      "color": "#8B9E60",
+      "占比": "20.1%"
+    },
+    {
+      "color": "#FFFFFF",
+      "占比": "15.7%"
+    },
+    {
+      "color": "#D4D4D4",
+      "占比": "10.5%"
+    },
+    {
+      "color": "#A07E4C",
+      "占比": "8.2%"
+    }
+  ],
+  "色温估算值": 6500,
+  "高光RGB偏移量": [
+    0,
+    0,
+    0
+  ],
+  "中间调RGB偏移量": [
+    0,
+    0,
+    0
+  ],
+  "阴影RGB偏移量": [
+    0,
+    0,
+    0
+  ],
+  "饱和度量化值": 150,
+  "对比度量化值": 130,
+  "亮度量化值": 180,
+  "暗角强度": 0.1,
+  "颗粒噪声强度": 0.05,
+  "shadows色相偏移角度": 0,
+  "midtones色相偏移角度": 0,
+  "highlights色相偏移角度": 0,
+  "锐化程度": 0.3,
+  "胶片感特征": {
+    "褪色": false,
+    "漏光": false,
+    "色差": false
+  },
+  "LUT风格分类": "Natural"
+}

BIN
examples/deep_research/input/7.jpeg


+ 59 - 0
examples/deep_research/input/7_invariant_features.json

@@ -0,0 +1,59 @@
+{
+  "dominant_colors": [
+    {
+      "hex": "#3E6B3E",
+      "percentage": 25.3
+    },
+    {
+      "hex": "#FFFFFF",
+      "percentage": 20.1
+    },
+    {
+      "hex": "#8C8C8C",
+      "percentage": 15.7
+    },
+    {
+      "hex": "#D4D4D4",
+      "percentage": 12.5
+    },
+    {
+      "hex": "#A0A0A0",
+      "percentage": 9.8
+    }
+  ],
+  "color_temperature_kelvin": 6000,
+  "rgb_offset": {
+    "highlights": {
+      "R": 5,
+      "G": 0,
+      "B": -5
+    },
+    "midtones": {
+      "R": 0,
+      "G": 0,
+      "B": 0
+    },
+    "shadows": {
+      "R": -5,
+      "G": 0,
+      "B": 5
+    }
+  },
+  "saturation": 150,
+  "contrast": 130,
+  "brightness": 180,
+  "vignette_intensity": 0.05,
+  "grain_intensity": 0.0,
+  "hue_shift": {
+    "shadows": 0,
+    "midtones": 0,
+    "highlights": 0
+  },
+  "sharpening_degree": 0.6,
+  "film_characteristics": {
+    "fade": false,
+    "light_leak": false,
+    "chromatic_aberration": false
+  },
+  "lut_style_classification": "Natural_Soft"
+}

+ 9 - 0
examples/deep_research/input/《秋日际遇》写生油画.json

@@ -0,0 +1,9 @@
+{
+  "images": [
+    "examples/deep_research/input/1.jpeg",
+    "examples/deep_research/input/3.jpeg",
+    "examples/deep_research/input/7.jpeg"
+  ],
+  "body_text": "听闻秋日是倒放的春天\n于是我心中有一座秋日的花园\n栽种着一簇簇淡却温暖的花\n风沿着远边的山吹来\n热情的阳光里秋风微凉\n与颜料一起酝酿出的画面\n白裙是一抹无暇\n迎着光绘画出\n那片在我心上开满\n限定的浪漫\n被画架支起\n绿草坪还驻留了匆匆而过的热闹\n再添一笔白\n为我画一枝玫瑰的奇遇\n———@万淮 #草地拍照[话题]##画画[话题]#",
+  "title": "《秋日际遇》写生油画"
+}

+ 23 - 0
examples/deep_research/production.prompt

@@ -0,0 +1,23 @@
+---
+model: sonnet-4.6
+temperature: 0.3
+---
+
+$system$
+你是一个顶尖的多模态特征工程专家。你的核心任务是利用技术手段(而非自然语言)从原始素材中提取出可复用的“视觉/听觉灵魂”,并验证这些特征是否能指导生成模型还原出一致的内容。
+你的行动准则:
+拒绝平庸描述:严禁使用“构图精美”、“色彩柔和”、“动作自然”等文学化词汇。你必须提供硬核数据表示,如:主色调 Hex 序列、关键点坐标矩阵 $(x, y)$、边缘拓扑图、或者是显著性热力图。
+工具驱动思维:在接到任务后,首先调研并调用最适合的工具链(如:OpenCV 图像处理、MediaPipe 姿态检测、DINOv2 特征提取、Nano Banana 图像重组)。
+闭环还原验证:所有的特征提取必须通过“还原测试”。如果你提取的特征(如人物骨架)能让生成模型在不同背景下复刻出相同的动作,则视为成功。
+工程化交付:你不仅要给出结论,还要生成可执行的 Python 脚本(resource 目录下)和结构化的数据文件(feature.json)。
+$user$
+**任务目标**
+分析 examples/deep_research/input/ 中的原内容,针对特征维度 滤镜风格【在此处输入维度,例如:滤镜风格、人物骨架拓扑、画面几何构图】 进行深度提取与还原验证。
+**交付清单** (Output Requirement)
+在 examples/deep_research/output_1/ 目录下输出:
+    1. method.md:记录提取该维度所用的具体算法工具(如 OpenCV Canny/MediaPipe)及还原效果量化评估。
+    2. feature.json:存储提取出的结构化特征数据(如归一化的坐标、颜色值等),禁止自然语言。
+    3. reduction.jpg:采用三段式对比展示:[ 原图 | 提取的特征图(或点位可视化) | 还原后的生成图 ]。
+    4. ./resource/:保存实现上述提取过程的简单 Python 脚本或配置文件。
+禁止降级解决,请立即开始调研并调用工具执行。
+不要用sandbox执行,直接在本地目录执行(examples/deep_research)

+ 581 - 0
examples/deep_research/run.py

@@ -0,0 +1,581 @@
+"""
+示例(增强版)
+
+使用 Agent 模式 + Skills
+
+新增功能:
+1. 支持命令行随时打断(输入 'p' 暂停,'q' 退出)
+2. 暂停后可插入干预消息
+3. 支持触发经验总结
+4. 查看当前 GoalTree
+5. 框架层自动清理不完整的工具调用
+6. 支持通过 --trace <ID> 恢复已有 Trace 继续执行
+"""
+
+import argparse
+import os
+import sys
+import select
+import asyncio
+from pathlib import Path
+
+# Clash Verge TUN 模式兼容:禁止 httpx/urllib 自动检测系统 HTTP 代理
+# TUN 虚拟网卡已在网络层接管所有流量,不需要应用层再走 HTTP 代理,
+# 否则 httpx 检测到 macOS 系统代理 (127.0.0.1:7897) 会导致 ConnectError
+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.core.presets import AgentPreset, register_preset
+from agent.trace import (
+    FileSystemTraceStore,
+    Trace,
+    Message,
+)
+from agent.llm import create_openrouter_llm_call
+from agent.tools import get_tool_registry
+
+
+# ===== 非阻塞 stdin 检测 =====
+if sys.platform == 'win32':
+    import msvcrt
+
+def check_stdin() -> str | None:
+    """
+    跨平台非阻塞检查 stdin 输入。
+    Windows: 使用 msvcrt.kbhit()
+    macOS/Linux: 使用 select.select()
+    """
+    if sys.platform == 'win32':
+        # 检查是否有按键按下
+        if msvcrt.kbhit():
+            # 读取按下的字符(msvcrt.getwch 是非阻塞读取宽字符)
+            ch = msvcrt.getwch().lower()
+            if ch == 'p':
+                return 'pause'
+            if ch == 'q':
+                return 'quit'
+            # 如果是其他按键,可以选择消耗掉或者忽略
+        return None
+    else:
+        # Unix/Mac 逻辑
+        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,
+):
+    """
+    显示交互式菜单,让用户选择操作。
+
+    进入本函数前不再有后台线程占用 stdin,所以 input() 能正常工作。
+    """
+    print("\n" + "=" * 60)
+    print("  执行已暂停")
+    print("=" * 60)
+    print("请选择操作:")
+    print("  1. 插入干预消息并继续")
+    print("  2. 触发经验总结(reflect)")
+    print("  3. 查看当前 GoalTree")
+    print("  4. 手动压缩上下文(compact)")
+    print("  5. 继续执行")
+    print("  6. 停止执行")
+    print("=" * 60)
+
+    while True:
+        choice = input("请输入选项 (1-6): ").strip()
+
+        if choice == "1":
+            text = _read_multiline()
+            if not text:
+                print("未输入任何内容,取消操作")
+                continue
+
+            print(f"\n将插入干预消息并继续执行...")
+            # 从 store 读取实际的 last_sequence,避免本地 current_sequence 过时
+            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":
+            # 触发经验总结
+            print("\n触发经验总结...")
+            focus = input("请输入反思重点(可选,直接回车跳过): ").strip()
+
+            from agent.trace.compaction import build_reflect_prompt
+
+            # 保存当前 head_sequence
+            trace = await store.get_trace(trace_id)
+            saved_head = trace.head_sequence
+
+            prompt = build_reflect_prompt()
+            if focus:
+                prompt += f"\n\n请特别关注:{focus}"
+
+            print("正在生成反思...")
+            reflect_cfg = RunConfig(trace_id=trace_id, max_iterations=1, tools=[])
+
+            reflection_text = ""
+            try:
+                result = await runner.run_result(
+                    messages=[{"role": "user", "content": prompt}],
+                    config=reflect_cfg,
+                )
+                reflection_text = result.get("summary", "")
+            finally:
+                # 恢复 head_sequence(反思消息成为侧枝)
+                await store.update_trace(trace_id, head_sequence=saved_head)
+
+            # 追加到 experiences 文件
+            if reflection_text:
+                from datetime import datetime
+                experiences_path = runner.experiences_path or "./.cache/experiences_find.md"
+                os.makedirs(os.path.dirname(experiences_path), exist_ok=True)
+                header = f"\n\n---\n\n## {trace_id} ({datetime.now().strftime('%Y-%m-%d %H:%M')})\n\n"
+                with open(experiences_path, "a", encoding="utf-8") as f:
+                    f.write(header + reflection_text + "\n")
+                print(f"\n反思已保存到: {experiences_path}")
+                print("\n--- 反思内容 ---")
+                print(reflection_text)
+                print("--- 结束 ---\n")
+            else:
+                print("未生成反思内容")
+
+            continue
+
+        elif choice == "3":
+            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 == "4":
+            # 手动压缩上下文
+            print("\n正在执行上下文压缩(compact)...")
+            try:
+                goal_tree = await store.get_goal_tree(trace_id)
+                trace = await store.get_trace(trace_id)
+                if not trace:
+                    print("未找到 Trace,无法压缩")
+                    continue
+
+                # 重建当前 history
+                main_path = await store.get_main_path_messages(trace_id, trace.head_sequence)
+                history = [msg.to_llm_dict() for msg in main_path]
+                head_seq = main_path[-1].sequence if main_path else 0
+                next_seq = head_seq + 1
+
+                compact_config = RunConfig(trace_id=trace_id)
+                new_history, new_head, new_seq = await runner._compress_history(
+                    trace_id=trace_id,
+                    history=history,
+                    goal_tree=goal_tree,
+                    config=compact_config,
+                    sequence=next_seq,
+                    head_seq=head_seq,
+                )
+                print(f"\n✅ 压缩完成: {len(history)} 条消息 → {len(new_history)} 条")
+            except Exception as e:
+                print(f"\n❌ 压缩失败: {e}")
+            continue
+
+        elif choice == "5":
+            print("\n继续执行...")
+            return {"action": "continue"}
+
+        elif choice == "6":
+            print("\n停止执行...")
+            return {"action": "stop"}
+
+        else:
+            print("无效选项,请重新输入")
+
+
+async def main():
+    # 解析命令行参数
+    parser = argparse.ArgumentParser(description="任务 (Agent 模式 + 交互增强)")
+    parser.add_argument(
+        "--trace", type=str, default=None,
+        help="已有的 Trace ID,用于恢复继续执行(不指定则新建)",
+    )
+    args = parser.parse_args()
+
+    # 路径配置
+    base_dir = Path(__file__).parent
+    project_root = base_dir.parent.parent
+    prompt_path = base_dir / "production.prompt"
+
+    # 为每次运行创建独立的输出目录(基于时间戳或 trace_id)
+    if args.trace:
+        # 恢复模式:使用已有 trace_id 作为目录名
+        output_dir = base_dir / "outputs" / args.trace[:8]
+    else:
+        # 新建模式:使用时间戳
+        from datetime import datetime
+        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+        output_dir = base_dir / "outputs" / timestamp
+    output_dir.mkdir(parents=True, exist_ok=True)
+
+    print(f"📁 输出目录: {output_dir}")
+    print()
+
+    # 加载项目级 presets(examples/how/presets.json)
+    presets_path = base_dir / "presets.json"
+    if presets_path.exists():
+        import json
+        with open(presets_path, "r", encoding="utf-8") as f:
+            project_presets = json.load(f)
+        for name, cfg in project_presets.items():
+            register_preset(name, AgentPreset(**cfg))
+        print(f"   - 已加载项目 presets: {list(project_presets.keys())}")
+
+    # Skills 目录(可选:用户自定义 skills)
+    # 注意:内置 skills(agent/memory/skills/)会自动加载
+    skills_dir = str(base_dir / "skills")
+
+    print("=" * 60)
+    print("mcp/skills 发现、获取、评价 分析任务 (Agent 模式 + 交互增强)")
+    print("=" * 60)
+    print()
+    print("💡 交互提示:")
+    print("   - 执行过程中输入 'p' 或 'pause' 暂停并进入交互模式")
+    print("   - 执行过程中输入 'q' 或 'quit' 停止执行")
+    print("=" * 60)
+    print()
+
+    # 1. 加载 prompt
+    print("1. 加载 prompt 配置...")
+    prompt = SimplePrompt(prompt_path)
+
+    # 2. 构建消息(仅新建时使用,恢复时消息已在 trace 中)
+    print("2. 构建任务消息...")
+    # 注入输出目录到 prompt 变量
+    messages = prompt.build_messages(variables={
+        "output_dir": str(output_dir.relative_to(base_dir))
+    })
+
+    # 3. 创建 Agent Runner(配置 skills)
+    print("3. 创建 Agent Runner...")
+    print(f"   - Skills 目录: {skills_dir}")
+    print(f"   - 模型: {prompt.config.get('model', 'sonnet-4.5')}")
+
+    # 加载自定义工具
+    print("   - 加载自定义工具: nanobanana")
+    import examples.how.tool  # 导入自定义工具模块,触发 @tool 装饰器注册
+
+    store = FileSystemTraceStore(base_path=".trace")
+    runner = AgentRunner(
+        trace_store=store,
+        llm_call=create_openrouter_llm_call(model=f"anthropic/claude-{prompt.config.get('model', 'sonnet-4.5')}"),
+        skills_dir=skills_dir,
+        experiences_path="./.cache/experiences_find.md",
+        debug=True
+    )
+
+    # 4. 判断是新建还是恢复
+    resume_trace_id = args.trace
+    if resume_trace_id:
+        # 验证 trace 存在
+        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}")
+        print(f"   - 任务: {existing_trace.task}")
+    else:
+        print(f"4. 启动新 Agent 模式...")
+
+    print()
+
+    final_response = ""
+    current_trace_id = resume_trace_id
+    current_sequence = 0
+    should_exit = False
+
+    try:
+        # 恢复模式:不发送初始消息,只指定 trace_id 续跑
+        if resume_trace_id:
+            initial_messages = None  # None = 未设置,触发早期菜单检查
+            config = RunConfig(
+                model=f"claude-{prompt.config.get('model', 'sonnet-4.5')}",
+                temperature=float(prompt.config.get('temperature', 0.3)),
+                max_iterations=1000,
+                trace_id=resume_trace_id,
+            )
+        else:
+            initial_messages = messages
+            config = RunConfig(
+                model=f"claude-{prompt.config.get('model', 'sonnet-4.5')}",
+                temperature=float(prompt.config.get('temperature', 0.3)),
+                max_iterations=1000,
+                name="社交媒体内容解构、建构、评估任务",
+            )
+
+        while not should_exit:
+            # 如果是续跑,需要指定 trace_id
+            if current_trace_id:
+                config.trace_id = current_trace_id
+
+            # 清理上一轮的响应,避免失败后显示旧内容
+            final_response = ""
+
+            # 如果 trace 已完成/失败且没有新消息,直接进入交互菜单
+            # 注意:initial_messages 为 None 表示未设置(首次加载),[] 表示有意为空(用户选择"继续")
+            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:
+                            # 无新消息:对 failed trace 意味着重试,对 completed 意味着继续
+                            initial_messages = []
+                            config.after_sequence = None
+                        continue
+                    break
+
+                # 对 stopped/running 等非终态的 trace,直接续跑
+                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)
+
+                        # 等待一小段时间让 runner 处理 stop 信号
+                        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()
+
+            # paused → 菜单已在暂停时内联显示过
+            if paused:
+                if should_exit:
+                    break
+                continue
+
+            # quit → 直接退出
+            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)
+
+    # 6. 输出结果
+    if final_response:
+        print()
+        print("=" * 60)
+        print("Agent 响应:")
+        print("=" * 60)
+        print(final_response)
+        print("=" * 60)
+        print()
+
+        # 7. 保存结果
+        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())

+ 31 - 50
examples/how/input/set_invariant_features.json

@@ -1,54 +1,35 @@
 {
-  "整体视觉风格和色调": "这组图片呈现出清新、自然、艺术的风格。色调以绿色(草地、树木)和白色(人物服装、玫瑰花)为主,辅以画板上的丰富色彩,整体明亮柔和,营造出一种宁静而富有生机的氛围。",
-  "每张图的主要元素": {
-    "图1": {
-      "人物": "一位长发女性,身穿白色长裙,侧身面向画架,手持画笔和调色板,正在作画。",
-      "场景": "户外草地,背景有绿树。",
-      "道具": "木质画架、画布(上面有未完成的画作,画中人物与现实人物相似)、调色板、画笔、一朵白色玫瑰花(插在画架旁)。",
-      "文字": "无"
-    },
-    "图2": {
-      "人物": "同一位长发女性,身穿白色长裙,背对镜头,跪坐在草地上,面向画架。",
-      "场景": "户外草地,背景有绿树和远处模糊的建筑轮廓。",
-      "道具": "木质画架、画布(上面有未完成的画作)、一个白色桶状容器(可能装有画材)。",
-      "文字": "无"
+  "visual_content": {
+    "overall_description": "这组图片展示了一位年轻女性在户外草地上进行绘画创作的场景,以及她与一朵白玫瑰互动的特写。画面整体呈现出一种宁静、艺术且充满自然气息的氛围。",
+    "人物": [
+      {
+        "性别": "女性",
+        "年龄": "年轻",
+        "穿着": "白色长袖连衣裙,款式飘逸,第一张图中裙摆有随风飘动的效果,第二张图中连衣裙的露背设计和系带细节清晰可见。",
+        "发型": "深棕色长发,自然垂落,在第一张图中部分头发遮挡了脸部,第二张图中头发披散在背部,第三张图中头发侧分,露出耳朵。",
+        "姿态": "第一张图:侧身站立,面向画架,右手持画笔正在作画,左手持调色板。第二张图:背对镜头,跪坐在草地上,面向画架。第三张图:侧脸特写,闭眼轻嗅一朵白玫瑰,表情安详。",
+        "面部特征": "第三张图中可见精致的侧脸轮廓,长睫毛,涂有红色口红,耳朵上佩戴金色耳环。",
+        "手部特征": "第三张图中可见修长的手指,指甲涂有蓝色指甲油,轻柔地握着白玫瑰的茎部。"
+      }
+    ],
+    "场景": {
+      "环境": "户外草地,背景是茂密的绿色树木,远处可能还有建筑或城市景观(第二张图的背景光线和模糊的轮廓暗示)。",
+      "道具": [
+        "画架:木质三脚画架,支撑着一幅画布。",
+        "画布上的画作:画作内容是一位身穿白色连衣裙的女性背影,置身于一片蓝色和绿色调的花海或草地中,与现实中的画家本人形成一种“画中画”的呼应。",
+        "调色板:椭圆形,上面沾满了各种颜料,以绿色、蓝色、白色为主。",
+        "画笔:细长,用于作画。",
+        "白玫瑰:一朵盛开的白色玫瑰,带有绿叶,在第一张图中被放置在画架旁,第三张图中被人物手持。",
+        "水桶/工具箱:第二张图中人物右侧地面上有一个白色水桶或工具箱,里面可能放有绘画工具。"
+      ]
     },
-    "图3": {
-      "人物": "同一位长发女性的侧脸特写,闭着眼睛,面带微笑,正在闻一朵白色玫瑰花。",
-      "场景": "户外草地(背景模糊)。",
-      "道具": "一朵白色玫瑰花、耳环、项链。",
-      "文字": "无"
-    }
-  },
-  "构图方式和元素关系": {
-    "图1": "采用斜向构图,人物和画架形成一个对角线,引导视线从左下到右上。人物与画作形成“画中画”的趣味关系,现实中的人物正在描绘画中的自己,增加了艺术性和故事感。白色玫瑰花作为点缀,与人物的白色裙子相呼应。",
-    "图2": "采用中心构图,人物和画架位于画面中央,背景的树木形成自然的框架。人物背对镜头,强调了其专注于创作的状态,也留给观众想象空间。人物跪坐的姿态显得谦逊而投入。",
-    "图3": "采用特写构图,聚焦于人物的侧脸和手中的玫瑰花。人物的脸部占据画面大部分,背景虚化,突出主体。玫瑰花靠近人物的鼻子,强调了“闻花”的动作和感受,营造出一种宁静美好的氛围。"
-  },
-  "光线处理": {
-    "图1": "光线明亮柔和,似乎是自然光,没有明显的阴影,整体曝光均匀。光线从侧面或前方照射,使人物和画作细节清晰。",
-    "图2": "光线呈现出逆光或侧逆光的效果,背景的树木边缘有金色的光晕,草地也显得更加明亮。这种光线处理营造出温暖、梦幻的氛围,尤其是在日落时分或清晨的光线效果。",
-    "图3": "光线柔和,从侧面照射,使人物的脸部轮廓和玫瑰花的细节清晰可见。没有强烈的对比,整体光线均匀,突出了人物的柔美和宁静。"
-  },
-  "多图之间的固定要素和变化要素": {
-    "固定要素": [
-      "同一位长发女性(身穿白色长裙)",
-      "户外草地场景",
-      "木质画架和画布(画作内容相似)",
-      "白色玫瑰花(在不同图中出现或作为道具)",
-      "清新自然的整体风格"
+    "构图": [
+      "第一张图:采用侧面构图,人物位于画面右侧,画架和画作位于画面中央偏左,形成一个动态的三角形构图。人物的身体线条和裙摆的飘动增加了画面的动感。",
+      "第二张图:采用背面构图,人物跪坐在画面中央偏下,画架和画作位于画面中央,背景的树木和天空占据了上半部分。这种构图强调了人物与画作的专注关系,并展现了广阔的自然环境。",
+      "第三张图:采用特写构图,聚焦于人物的侧脸和手持的白玫瑰,背景虚化,突出了主体的情感表达和细节。"
     ],
-    "变化要素": [
-      "人物的姿态和动作(作画、跪坐、闻花)",
-      "拍摄角度和景别(侧身全身、背影全身、侧脸特写)",
-      "光线效果(均匀光、逆光/侧逆光)",
-      "画面焦点和强调的主题(作画过程、专注状态、享受自然)"
-    ]
-  },
-  "特殊的视觉处理手法": [
-    "\"画中画\"效果:图1中人物正在描绘画中的自己,这种艺术手法增加了画面的层次感和趣味性。",
-    "景深运用:所有图片都使用了浅景深,虚化背景,突出主体人物和道具,使画面更具电影感和艺术感。",
-    "色彩搭配:白色服装与绿色草地形成鲜明对比,同时白色玫瑰花与服装相呼应,整体色彩和谐统一。",
-    "叙事性:三张图片通过不同角度和动作,共同讲述了一个关于艺术创作、享受自然和自我沉浸的小故事,具有一定的连贯性和叙事性。"
-  ]
+    "色调": "整体色调清新、明亮。以绿色(草地、树木)、白色(连衣裙、白玫瑰、画布上的部分颜色)和蓝色(画布上的花朵)为主。阳光的运用使得画面呈现出温暖的光晕,尤其在第二张图中,逆光效果营造出柔和的金黄色调。",
+    "光线": "自然光。第一张图和第三张图光线较为均匀,第二张图有明显的逆光效果,阳光从人物背后洒下,为画面增添了梦幻感和层次感。",
+    "氛围": "宁静、艺术、优雅、自然、浪漫。人物专注于创作,与周围的自然环境融为一体,白玫瑰的出现增添了一丝诗意和柔美。整体给人一种放松、美好的感觉。"
+  }
 }

+ 83 - 39
examples/how/run.py

@@ -150,45 +150,8 @@ async def show_interactive_menu(
             print("\n触发经验总结...")
             focus = input("请输入反思重点(可选,直接回车跳过): ").strip()
 
-            from agent.trace.compaction import build_reflect_prompt
-
-            # 保存当前 head_sequence
-            trace = await store.get_trace(trace_id)
-            saved_head = trace.head_sequence
-
-            prompt = build_reflect_prompt()
-            if focus:
-                prompt += f"\n\n请特别关注:{focus}"
-
-            print("正在生成反思...")
-            reflect_cfg = RunConfig(trace_id=trace_id, max_iterations=1, tools=[])
-
-            reflection_text = ""
-            try:
-                result = await runner.run_result(
-                    messages=[{"role": "user", "content": prompt}],
-                    config=reflect_cfg,
-                )
-                reflection_text = result.get("summary", "")
-            finally:
-                # 恢复 head_sequence(反思消息成为侧枝)
-                await store.update_trace(trace_id, head_sequence=saved_head)
-
-            # 追加到 experiences 文件
-            if reflection_text:
-                from datetime import datetime
-                experiences_path = runner.experiences_path or "./.cache/experiences.md"
-                os.makedirs(os.path.dirname(experiences_path), exist_ok=True)
-                header = f"\n\n---\n\n## {trace_id} ({datetime.now().strftime('%Y-%m-%d %H:%M')})\n\n"
-                with open(experiences_path, "a", encoding="utf-8") as f:
-                    f.write(header + reflection_text + "\n")
-                print(f"\n反思已保存到: {experiences_path}")
-                print("\n--- 反思内容 ---")
-                print(reflection_text)
-                print("--- 结束 ---\n")
-            else:
-                print("未生成反思内容")
-
+            # 触发反思
+            await perform_reflection(runner, store, trace_id, focus=focus)
             continue
 
         elif choice == "3":
@@ -241,6 +204,76 @@ async def show_interactive_menu(
         else:
             print("无效选项,请重新输入")
 
+async def perform_reflection(runner: AgentRunner, store: FileSystemTraceStore, trace_id: str, focus: str = ""):
+    """执行经验总结并保存(带结构化 YAML 解析)"""
+    from agent.trace.compaction import build_reflect_prompt
+    import re as _re2
+    import uuid as _uuid2
+    from datetime import datetime
+    
+    trace = await store.get_trace(trace_id)
+    if not trace:
+        return
+    saved_head = trace.head_sequence
+
+    prompt = build_reflect_prompt()
+    if focus:
+        prompt += f"\n\n请特别关注:{focus}"
+
+    print("正在生成反思...")
+    reflect_cfg = RunConfig(trace_id=trace_id, max_iterations=1, tools=[])
+    reflection_text = ""
+    
+    try:
+        result = await runner.run_result(
+            messages=[{"role": "user", "content": prompt}],
+            config=reflect_cfg,
+        )
+        reflection_text = result.get("summary", "")
+    finally:
+        # 恢复 head_sequence(反思消息成为侧枝,不污染主对话)
+        await store.update_trace(trace_id, head_sequence=saved_head)
+
+    # 追加到 experiences 文件
+    if reflection_text:
+        experiences_path = runner.experiences_path or "./.cache/experiences_how.md"
+        os.makedirs(os.path.dirname(experiences_path), exist_ok=True)
+
+        pattern = r"-\s*\[(?P<tags>.*?)\]\s*(?P<content>.*)"
+        matches = list(_re2.finditer(pattern, reflection_text))
+
+        structured_entries = []
+        for match in matches:
+            tags_str = match.group("tags")
+            content = match.group("content")
+
+            intent_match = _re2.search(r"intent:\s*(.*?)(?:,|$)", tags_str, _re2.IGNORECASE)
+            state_match = _re2.search(r"state:\s*(.*?)(?:,|$)", tags_str, _re2.IGNORECASE)
+
+            intents = [i.strip() for i in intent_match.group(1).split(",")] if intent_match and intent_match.group(1) else []
+            states = [s.strip() for s in state_match.group(1).split(",")] if state_match and state_match.group(1) else []
+
+            ex_id = f"ex_{datetime.now().strftime('%m%d%H%M')}_{_uuid2.uuid4().hex[:4]}"
+            
+            entry = f"---\nid: {ex_id}\ntrace_id: {trace_id}\ntags: {{intent: {intents}, state: {states}}}\nmetrics: {{helpful: 1, harmful: 0}}\ncreated_at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n---\n- {content}\n- 经验ID: [{ex_id}]"
+            structured_entries.append(entry)
+
+        if structured_entries:
+            final_output = "\n\n" + "\n\n".join(structured_entries)
+            with open(experiences_path, "a", encoding="utf-8") as f:
+                f.write(final_output)
+            print(f"\n✅ 提取了 {len(structured_entries)} 条经验,已结构化并保存到: {experiences_path}")
+            print("\n--- 反思内容(结构化后) ---")
+            print(final_output.strip())
+            print("--- 结束 ---\n")
+        else:
+            print("\n⚠️ 未能解析出符合格式的经验条目,已保存原始纯文本以供检查。")
+            header = f"\n\n---\n\n## [Raw] {trace_id} ({datetime.now().strftime('%Y-%m-%d %H:%M')})\n\n"
+            with open(experiences_path, "a", encoding="utf-8") as f:
+                f.write(header + reflection_text + "\n")
+            print(reflection_text)
+    else:
+        print("未生成反思内容")
 
 async def main():
     # 解析命令行参数
@@ -507,6 +540,17 @@ async def main():
 
             # Runner 退出(完成/失败/停止/异常)→ 显示交互菜单
             if current_trace_id:
+                # 🌟 新增:自动触发反思的生命周期钩子
+                check_trace = await store.get_trace(current_trace_id)
+                if check_trace and check_trace.status in ("completed", "failed"):
+                    print(f"\n⚙️ 任务已结束 (状态: {check_trace.status}),正在自动触发经验总结...")
+                    
+                    # 如果是失败状态,自动带上针对性的 focus 提示
+                    auto_focus = "本次任务执行失败了,请重点反思失败的原因、踩坑点以及未来应如何避免。" if check_trace.status == "failed" else ""
+                    
+                    await perform_reflection(runner, store, current_trace_id, focus=auto_focus)
+
+                # 自动反思结束后,依然弹出菜单,让用户决定是彻底退出(6)还是查看总结(3)
                 menu_result = await show_interactive_menu(
                     runner, current_trace_id, current_sequence, store
                 )

+ 147 - 233
examples/how/tool/nanobanana.py

@@ -1,9 +1,8 @@
 """
-NanoBanana Tool - 图像特征提取与图像生成
+NanoBanana Tool - 图像生成
 
-该工具可以提取图片中的特征,也可以根据描述生成图片。
-支持通过 OpenRouter 调用多模态模型,提取结构化的图像特征并保存为 JSON,
-或基于输入图像生成新的图像。
+通用图像生成工具,可以接受自然语言描述和/或图像输入,生成新的图像。
+支持通过 OpenRouter 调用 Gemini 2.5 Flash Image 模型。
 """
 
 import base64
@@ -22,23 +21,10 @@ from agent.tools import tool, ToolResult
 OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
 DEFAULT_TIMEOUT = 120.0
 
-DEFAULT_EXTRACTION_PROMPT = (
-    "请从这张图像中提取跨场景相对稳定、可复用的视觉不变特征。"
-    "输出严格 JSON,字段包含:identity_features、pose_features、appearance_features、"
-    "material_features、style_features、uncertainty、notes。"
-    "每个字段给出简洁要点,避免臆测。"
-)
-
-DEFAULT_IMAGE_PROMPT = (
-    "基于输入图像生成一张保留主体身份与关键视觉特征的新图像。"
-    "保持人物核心特征一致,同时提升清晰度与可用性。"
-)
+DEFAULT_IMAGE_PROMPT = "根据输入生成图像。"
 
 DEFAULT_IMAGE_MODEL_CANDIDATES = [
     "google/gemini-2.5-flash-image",
-    # "google/gemini-3-pro-image-preview",
-    # "black-forest-labs/flux.2-flex",
-    # "black-forest-labs/flux.2-pro",
 ]
 
 
@@ -214,84 +200,76 @@ def _normalize_model_id(model_id: str) -> str:
     return m
 
 
-@tool(description="可以提取图片中的特征,也可以根据描述生成图片")
+@tool(description="通用图像生成工具,可以接受自然语言描述和/或图像输入,生成新的图像")
 async def nanobanana(
     image_path: str = "",
     image_paths: Optional[List[str]] = None,
-    output_file: Optional[str] = None,
     prompt: Optional[str] = None,
     model: Optional[str] = None,
     max_tokens: int = 1200,
-    generate_image: bool = False,
     image_output_path: Optional[str] = None,
 ) -> ToolResult:
     """
-    可以提取图片中的特征,也可以根据描述生成图片
+    通用图像生成工具,可以接受自然语言描述和/或图像输入,生成新的图像
 
     Args:
         image_path: 输入图片路径(单图模式,可选)
-        image_paths: 输入图片路径列表(多图整体模式,可选)
-        output_file: 输出 JSON 文件路径(可选,用于特征提取模式)
-        prompt: 自定义提取指令或生成描述(可选)
-        model: OpenRouter 模型名(可选,默认读取 NANOBANANA_MODEL 或使用 Gemini 视觉模型)
+        image_paths: 输入图片路径列表(多图模式,可选)
+        prompt: 自定义生成描述(可选,默认使用通用prompt)
+        model: OpenRouter 模型名(可选,默认使用 gemini-2.5-flash-image)
         max_tokens: 最大输出 token
-        generate_image: 是否生成图片(False=提取特征,True=生成图片)
-        image_output_path: 生成图片保存路径(generate_image=True 时可选)
+        image_output_path: 生成图片保存路径(可选)
 
     Returns:
-        ToolResult: 包含结构化特征和输出文件路径,或生成的图片路径
+        ToolResult: 包含生成的图片路径
     """
     raw_paths: List[str] = []
     if image_paths:
         raw_paths.extend(image_paths)
     if image_path:
         raw_paths.append(image_path)
-    if not raw_paths:
-        return ToolResult(
-            title="NanoBanana 提取失败",
-            output="",
-            error="未提供输入图片,请传入 image_path 或 image_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: List[Path] = [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}",
-        )
+    # 图像输入是可选的,但如果提供了就需要验证
+    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 提取失败",
+            title="NanoBanana 生成失败",
             output="",
             error="未找到 OpenRouter API Key,请设置 OPENROUTER_API_KEY 或 OPEN_ROUTER_API_KEY",
         )
 
-    if generate_image:
-        user_prompt = prompt or DEFAULT_IMAGE_PROMPT
-    else:
-        chosen_model = model or os.getenv("NANOBANANA_MODEL") or "google/gemini-2.5-flash"
-        user_prompt = prompt or DEFAULT_EXTRACTION_PROMPT
+    user_prompt = prompt or DEFAULT_IMAGE_PROMPT
 
-    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}",
-        )
+    # 编码图像(如果有)
+    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:
@@ -301,11 +279,7 @@ async def nanobanana(
         "messages": [
             {
                 "role": "system",
-                "content": (
-                    "你是视觉助手。"
-                    "当任务为特征提取时输出 JSON 对象,不要输出 markdown。"
-                    "当任务为图像生成时请返回图像。"
-                ),
+                "content": "你是图像生成助手。请根据用户的描述和/或输入图像生成新的图像。",
             },
             {
                 "role": "user",
@@ -314,9 +288,8 @@ async def nanobanana(
         ],
         "temperature": 0.2,
         "max_tokens": max_tokens,
+        "modalities": ["image", "text"],
     }
-    if generate_image:
-        payload["modalities"] = ["image", "text"]
 
     headers = {
         "Authorization": f"Bearer {api_key}",
@@ -327,33 +300,28 @@ async def nanobanana(
 
     endpoint = f"{OPENROUTER_BASE_URL}/chat/completions"
 
-    # 图像生成模式:自动尝试多个可用模型,减少 404/invalid model 影响
-    if generate_image:
-        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
-    else:
-        candidates = [chosen_model]
+    # 自动尝试多个可用模型,减少 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]]] = [None]
-        if generate_image:
-            modality_attempts = [["image", "text"], ["image"], None]
+        modality_attempts: List[Optional[List[str]]] = [["image", "text"], ["image"], None]
 
         for mods in modality_attempts:
             trial_payload = dict(payload)
@@ -392,9 +360,8 @@ async def nanobanana(
             break
 
     if data is None:
-        title = "NanoBanana 生成失败" if generate_image else "NanoBanana 提取失败"
         return ToolResult(
-            title=title,
+            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},
@@ -405,168 +372,115 @@ async def nanobanana(
     choices = data.get("choices") or []
     message = choices[0].get("message", {}) if choices else {}
 
-    # 图像生成分支
-    if generate_image:
-        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]
+    # 提取生成的图像
+    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,
-                },
-            )
+        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)
+    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:
-            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
+            base_path = input_paths[0].parent / f"{input_paths[0].stem}_generated.png"
+    base_path.parent.mkdir(parents=True, exist_ok=True)
 
-            if not raw_bytes:
-                continue
+    for idx, ref in enumerate(refs):
+        kind = ref.get("kind", "")
+        mime_type = "image/png"
+        raw_bytes: Optional[bytes] = None
 
-            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}")
+        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:
-                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:
+                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=json.dumps(data, ensure_ascii=False, indent=2),
-                error="检测到图片引用但写入失败(可能是无效 base64 或 URL 不可访问)",
-                metadata={"model": chosen_model, "ref_count": len(refs)},
+                output="",
+                error=f"写入生成图片失败: {e}",
+                metadata={"model": chosen_model},
             )
 
-        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,
-        )
-
-    content = message.get("content") or ""
-    if not content:
+    if not output_paths:
         return ToolResult(
-            title="NanoBanana 提取失败",
+            title="NanoBanana 生成失败",
             output=json.dumps(data, ensure_ascii=False, indent=2),
-            error="模型未返回内容",
+            error="检测到图片引用但写入失败(可能是无效 base64 或 URL 不可访问)",
+            metadata={"model": chosen_model, "ref_count": len(refs)},
         )
 
-    try:
-        parsed = _safe_json_parse(content)
-    except Exception as e:
-        return ToolResult(
-            title="NanoBanana 提取失败",
-            output=content,
-            error=f"模型返回非 JSON 内容,解析失败: {e}",
-            metadata={"model": chosen_model},
-        )
-
-    if output_file:
-        out_path = Path(output_file)
-    else:
-        if len(input_paths) > 1:
-            out_path = input_paths[0].parent / "set_invariant_features.json"
-        else:
-            out_path = input_paths[0].parent / f"{input_paths[0].stem}_invariant_features.json"
-
-    out_path.parent.mkdir(parents=True, exist_ok=True)
-    out_path.write_text(json.dumps(parsed, ensure_ascii=False, indent=2), encoding="utf-8")
-
     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),
-        "output_file": str(out_path),
+        "generated_images": output_paths,
         "prompt_tokens": prompt_tokens,
         "completion_tokens": completion_tokens,
     }
-
     return ToolResult(
-        title="NanoBanana 不变特征提取完成",
-        output=json.dumps(
-            {
-                "summary": summary,
-                "features": parsed,
-            },
-            ensure_ascii=False,
-            indent=2,
-        ),
-        long_term_memory=f"Extracted invariant features from {len(input_paths)} input image(s) using {chosen_model}",
-        attachments=[str(out_path)],
+        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,
+        }
     )

+ 106 - 0
examples/research/README.md

@@ -0,0 +1,106 @@
+# 浏览器调研示例
+
+支持云浏览器和本地浏览器两种模式的 Agent 自动化调研工具。
+
+## 功能特性
+
+1. **Agent 自动化调研** - 使用 LLM 驱动的 Agent 自动执行浏览器操作
+2. **手动接管模式** - 运行中随时按 [Enter] 键暂停 Agent,手动操作浏览器
+3. **自动清理** - 无论成功或崩溃,均安全关闭浏览器进程
+4. **灵活切换** - 支持云浏览器和本地浏览器模式切换
+
+## 浏览器模式配置
+
+### 切换方法
+
+编辑 `run.py` 文件顶部的配置变量:
+
+```python
+# ===== 浏览器模式配置 =====
+BROWSER_TYPE = "cloud"  # 可选: "cloud" 或 "local"
+HEADLESS = False        # 是否无头模式运行
+```
+
+### 模式说明
+
+#### 云浏览器模式 (`"cloud"`)
+- ✅ 不占用本地资源
+- ✅ 适合生产环境
+- ✅ 可在无 GUI 的服务器上运行
+- ⚠️ 需要配置 browser-use 云服务
+- ⚠️ 可能需要 API 密钥
+
+#### 本地浏览器模式 (`"local"`)
+- ✅ 速度更快
+- ✅ 支持可视化调试
+- ✅ 无需额外配置
+- ⚠️ 需要本地安装 Chrome
+- ⚠️ 占用本地资源
+
+## 使用方法
+
+### 1. 准备环境
+
+```bash
+# 安装依赖
+pip install -r requirements.txt
+
+# 配置环境变量(复制 .env.example 为 .env)
+cp .env.example .env
+# 编辑 .env 文件,配置 OPENROUTER_API_KEY 等
+```
+
+### 2. 配置任务
+
+编辑 `test.prompt` 文件,设置调研任务:
+
+```
+---
+model: gemini-3-flash-preview
+temperature: 0.3
+---
+
+[system]
+你是一个专业的网络调研助手...
+
+[user]
+请帮我调研...
+```
+
+### 3. 运行
+
+```bash
+python run.py
+```
+
+### 4. 手动接管(可选)
+
+运行过程中,如需手动操作浏览器(如登录、验证码等):
+
+1. 按下 **[Enter]** 键
+2. Agent 会在完成当前动作后暂停
+3. 在浏览器窗口完成必要操作
+4. 再次按 **[Enter]** 或点击页面交互按钮继续
+
+## 输出结果
+
+- 调研结果保存在 `output/` 目录
+- Trace 数据保存在项目根目录的 `.trace/` 目录
+- 可通过可视化面板查看详细执行过程
+
+## 故障排除
+
+### 云浏览器连接失败
+- 检查 browser-use 云服务配置
+- 确认 API 密钥正确
+- 检查网络连接
+
+### 本地浏览器启动失败
+- 确认已安装 Chrome 浏览器
+- 检查 Chrome 路径是否正确
+- 尝试关闭其他 Chrome 实例
+
+### Agent 执行异常
+- 查看终端日志输出
+- 检查 `.trace/` 目录中的 trace 数据
+- 调整 `test.prompt` 中的任务描述

+ 191 - 0
examples/research/TROUBLESHOOTING.md

@@ -0,0 +1,191 @@
+# 故障排除指南
+
+## Cookie 文件相关问题
+
+### 问题:提示"没有 Cookie 文件"或"Cookie 目录不存在"
+
+#### 原因
+这不是云浏览器特有的问题,而是首次使用时的正常情况:
+
+1. Agent 在执行某些任务时可能会尝试加载之前保存的 Cookie(用于保持登录态)
+2. 如果这是第一次运行,`.cache/.cookies` 目录不存在或没有对应的 Cookie 文件
+3. 工具会给出友好提示,并自动导航到目标页面
+
+#### 解决方案
+
+**方案 1:让 Agent 自动处理(推荐)**
+
+从 v2.0 开始,`browser_load_cookies` 工具已经优化:
+- 找不到 Cookie 时会自动导航到目标页面
+- Agent 可以继续执行任务,不会中断
+- 你可以手动登录后,Agent 会继续后续操作
+
+**方案 2:预先保存 Cookie**
+
+如果你需要频繁访问需要登录的网站:
+
+1. 首次运行时手动登录
+2. 在终端按 `[Enter]` 暂停 Agent
+3. 在浏览器中完成登录
+4. 让 Agent 继续,它会自动调用 `browser_export_cookies` 保存 Cookie
+5. 下次运行时会自动加载 Cookie,无需重复登录
+
+**方案 3:手动保存 Cookie**
+
+```bash
+# 1. 启动浏览器并访问目标网站
+# 2. 手动登录
+# 3. 在 Python 中执行:
+
+from agent.tools.builtin.browser.baseClass import browser_export_cookies
+
+# 保存当前页面的 Cookie
+await browser_export_cookies(name="example.com")
+
+# Cookie 会保存到 .cache/.cookies/example.com.json
+```
+
+### 问题:云浏览器和本地浏览器的 Cookie 是否共享?
+
+**是的**,Cookie 文件存储在本地文件系统(`.cache/.cookies/`),与浏览器类型无关:
+
+- 云浏览器保存的 Cookie 可以在本地浏览器中使用
+- 本地浏览器保存的 Cookie 可以在云浏览器中使用
+- 切换浏览器模式不会丢失已保存的 Cookie
+
+### Cookie 文件位置
+
+```
+项目根目录/
+  └── .cache/
+      └── .cookies/
+          ├── example.com.json
+          ├── github.com.json
+          └── ...
+```
+
+### Cookie 文件格式
+
+Cookie 文件使用 JSON 格式,符合 Chrome DevTools Protocol (CDP) 规范:
+
+```json
+[
+  {
+    "name": "session_id",
+    "value": "abc123...",
+    "domain": ".example.com",
+    "path": "/",
+    "expires": 1234567890,
+    "httpOnly": true,
+    "secure": true
+  }
+]
+```
+
+## 云浏览器特定问题
+
+### 问题:云浏览器连接失败
+
+#### 可能原因
+1. browser-use 云服务未配置
+2. API 密钥错误或过期
+3. 网络连接问题
+4. 云服务配额用尽
+
+#### 解决方案
+
+1. **检查配置**
+   ```bash
+   # 查看 .env 文件
+   cat .env
+
+   # 确认包含必要的配置(如果需要)
+   # BROWSER_USE_API_KEY=your_key_here
+   ```
+
+2. **切换到本地浏览器**
+   ```python
+   # 编辑 run.py
+   BROWSER_TYPE = "local"  # 改为 local
+   ```
+
+3. **查看详细日志**
+   ```python
+   # 在 run.py 中启用调试日志
+   logging.basicConfig(level=logging.DEBUG)
+   ```
+
+### 问题:云浏览器速度慢
+
+#### 原因
+- 网络延迟
+- 云服务器负载高
+- 需要传输大量数据(如图片、视频)
+
+#### 解决方案
+
+1. **使用本地浏览器**(如果可以)
+   ```python
+   BROWSER_TYPE = "local"
+   ```
+
+2. **启用无头模式**(减少渲染开销)
+   ```python
+   HEADLESS = True
+   ```
+
+3. **优化任务**
+   - 减少不必要的页面导航
+   - 使用 API 代替浏览器操作(如果可能)
+
+## 其他常见问题
+
+### 问题:Agent 卡住不动
+
+#### 可能原因
+1. 等待页面加载超时
+2. 等待元素出现超时
+3. 网络请求阻塞
+
+#### 解决方案
+
+1. **手动接管**
+   - 按 `[Enter]` 键暂停 Agent
+   - 检查浏览器状态
+   - 手动完成操作后继续
+
+2. **调整超时设置**
+   ```python
+   # 在 test.prompt 中添加
+   [system]
+   如果页面加载超过 30 秒,请跳过并继续下一步
+   ```
+
+### 问题:浏览器进程未正确关闭
+
+#### 解决方案
+
+```bash
+# Windows
+taskkill /F /IM chrome.exe
+
+# Linux/Mac
+pkill -9 chrome
+```
+
+或者在代码中确保清理:
+
+```python
+from agent.tools.builtin.browser.baseClass import kill_browser_session
+
+# 在 finally 块中调用
+await kill_browser_session()
+```
+
+## 获取帮助
+
+如果以上方案都无法解决问题:
+
+1. 查看完整日志输出
+2. 检查 `.trace/` 目录中的 trace 数据
+3. 在 GitHub 提交 issue:https://github.com/anthropics/claude-code/issues

+ 31 - 9
examples/research/run.py

@@ -1,12 +1,23 @@
 """
-浏览器调研示例 (交互增强版)
+浏览器调研示例 (支持云浏览器/本地浏览器切换)
 
 功能:
 1. Agent 模式自动化调研
 2. 手动接管:随时按 [Enter] 键暂停 Agent 并手动操作浏览器
 3. 自动清理:无论成功或崩溃,均安全关闭浏览器进程
+4. 灵活切换:通过配置变量选择云浏览器或本地浏览器
+
+浏览器模式配置:
+- 修改下方 BROWSER_TYPE 变量来切换模式
+- "cloud": 云浏览器模式,不占用本地资源,需要配置 browser-use 云服务
+- "local": 本地浏览器模式,在本地运行 Chrome,速度更快,支持可视化调试
 """
 
+# ===== 浏览器模式配置 =====
+# 可选值: "cloud" (云浏览器) 或 "local" (本地浏览器)
+BROWSER_TYPE = "cloud"  # 修改这里来切换浏览器模式
+HEADLESS = False  # 是否无头模式运行
+
 import os
 import sys
 import asyncio
@@ -32,7 +43,7 @@ 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_openrouter_llm_call
-from agent.tools.builtin.browser.baseClass import kill_browser_session
+from agent.tools.builtin.browser.baseClass import kill_browser_session, init_browser_session
 
 # ===== 全局交互控制 =====
 pause_event = asyncio.Event()
@@ -62,6 +73,7 @@ async def main():
 
     print("=" * 60)
     print("🚀 交互式浏览器调研 Agent")
+    print(f"🌐 浏览器模式: {'云浏览器 (Cloud)' if BROWSER_TYPE == 'cloud' else '本地浏览器 (Local)'}")
     print("👉 操作指南:")
     print("   - 运行中随时按下 [Enter] 键进入手动接管模式")
     print("   - 在浏览器完成操作后,点击页面上的 'Done' 或回车返回")
@@ -77,16 +89,26 @@ async def main():
 
     messages = prompt.build_messages()
 
-    # 3. 初始化 Runner
+    # 3. 初始化浏览器会话
+    browser_mode_name = "云浏览器" if BROWSER_TYPE == "cloud" else "本地浏览器"
+    print(f"🌐 正在初始化{browser_mode_name}...")
+    await init_browser_session(
+        browser_type=BROWSER_TYPE,
+        headless=HEADLESS,
+        url="about:blank"
+    )
+    print(f"✅ {browser_mode_name}初始化完成\n")
+
+    # 4. 初始化 Runner
     # 注意:确保你的 openrouter 配置正确
     runner = AgentRunner(
         trace_store=FileSystemTraceStore(base_path=str(trace_dir)),
         llm_call=create_openrouter_llm_call(model=f"google/{model_name}"),
         skills_dir=None,
-        debug=True 
+        debug=True
     )
 
-    # 4. 启动监听任务
+    # 5. 启动监听任务
     interrupt_task = asyncio.create_task(listen_for_interrupt())
     
     final_response = ""
@@ -164,18 +186,18 @@ async def main():
     finally:
         # 停止监听协程
         interrupt_task.cancel()
-        
-        # 5. 强制清理浏览器环境
+
+        # 6. 强制清理浏览器环境
         print("\n" + "·" * 40)
         print("🧹 正在执行环境清理...")
         try:
             await kill_browser_session()
-            print("✨ 浏览器进程已安全终止。")
+            print(f"✨ {browser_mode_name}进程已安全终止。")
         except Exception as err:
             print(f"❌ 清理失败: {err}")
         print("·" * 40 + "\n")
 
-    # 6. 结果展示
+    # 7. 结果展示
     if current_trace_id:
         print(f"🔍 任务 Trace ID: {current_trace_id}")
         print(f"📊 访问可视化面板查看详情。")

+ 2 - 1
examples/research/test.prompt

@@ -7,4 +7,5 @@ $system$
 你是最顶尖的AI助手,可以拆分并调用工具逐步解决复杂问题。
 
 $user$
-去网页上搜索一些关于摄影的内容和文章,并且下载一些下来。(执行前调用get_experience工具来作为参考)
+登录一下小红书,在小红书内的搜索框搜索一下摄影.使用load_cookies来登录
+

BIN
examples/restore/input/images/img_1.jpg


+ 32 - 0
examples/restore/input/images/img_1_invariant_features.json

@@ -0,0 +1,32 @@
+{
+  "composition_layout": {
+    "subject_position": "人物和画架位于画面右侧和中心偏右,占据了画面的大部分空间,形成一个不对称但平衡的构图。人物背对观众,专注于绘画。",
+    "perspective": "中景视角,略微仰视,使得人物和画架显得较为突出,背景的树木和草地则作为衬托。",
+    "depth_of_field": "景深较浅,人物和画架清晰锐利,是画面的焦点。背景的树木和草地则呈现出明显的虚化效果,突出了主体。"
+  },
+  "color_distribution": {
+    "main_tones": "画面以绿色和白色为主色调。绿色主要来自背景的草地和树木,以及画板上的颜料。白色则主要来自人物的衣着和画板上未完成的画作中的白色元素。",
+    "color_areas": "画面下方是鲜亮的绿色草地,上方是深浅不一的绿色树木。人物身着白色长裙,与周围的绿色形成对比。画板上则有丰富的色彩,包括蓝色、绿色、粉色、红色等,显示出绘画的活力。"
+  },
+  "lighting_effects": {
+    "light_source_direction": "光线似乎来自画面的左前方或上方,使得人物的右侧和画板上的颜料有明显的光照,而人物的左侧和部分背景则略显阴影。",
+    "light_dark_contrast": "整体对比度适中。人物的白色衣物在光照下显得明亮,与背景的绿色形成对比。画板上的颜料色彩鲜艳,也增加了画面的明暗对比。",
+    "highlights_shadows": "人物的头发和衣物边缘有高光,显示出材质的柔软和光泽。背景的树木在光照下呈现出不同的明暗层次,增加了画面的立体感。草地上也有细微的光影变化。"
+  },
+  "figure_pose_features": {
+    "body_posture": "人物站立,身体略微向右倾斜,头部转向画板,呈现出专注于绘画的姿态。右臂抬起,手持画笔,左手拿着调色板。",
+    "clothing_details": "人物身穿一件白色长袖连衣裙,材质轻盈,裙摆在微风中略微飘动,显得优雅而自然。衣领和袖口设计简洁。",
+    "action": "人物正在用画笔在画板上作画,动作专注而投入。"
+  },
+  "object_details": {
+    "easel": "画架是木质的,三脚架结构,颜色为原木色,支撑着画板。画架上还系着一个黑色的包或带子。",
+    "palette": "调色板呈椭圆形,上面沾满了各种颜色的油画颜料,包括绿色、蓝色、粉色、红色、黄色等,颜料堆叠,显示出使用痕迹。",
+    "brush": "画笔细长,笔尖沾有颜料,正被人物用来在画板上描绘细节。",
+    "other_props": "画架上还放着一朵白色的玫瑰花,花瓣和叶子清晰可见,为画面增添了一丝浪漫和艺术气息。"
+  },
+  "background_features": {
+    "depth_of_field_effect": "背景呈现出明显的虚化效果,与前景的人物和画架形成对比,突出了主体。",
+    "blur_level": "背景的模糊程度较高,使得树木和草地的细节变得柔和,形成一片绿色的光斑。",
+    "background_elements": "背景主要是茂密的绿色树木和广阔的绿色草地,营造出一种自然、宁静的户外环境。远处可能还有一些模糊的建筑或景观,但细节不清晰。"
+  }
+}

BIN
examples/restore/input/images/img_2.jpg


BIN
examples/restore/input/images/img_3.jpg


BIN
examples/restore/input/images/img_5.jpg


BIN
examples/restore/input/images/img_6.jpg


BIN
examples/restore/input/images/img_7.jpg


+ 80 - 0
examples/restore/input/images/img_7_invariant_features.json

@@ -0,0 +1,80 @@
+{
+  "人物姿态骨架": {
+    "头部": {
+      "位置": "画面右侧偏上,侧脸朝向左侧,略微向下倾斜",
+      "角度": "侧面,眼睛微闭,鼻子和嘴唇轮廓清晰"
+    },
+    "肩部": {
+      "位置": "右肩可见,位于画面右下角,左肩被身体遮挡",
+      "角度": "右肩略微向后,呈现放松状态"
+    },
+    "手臂": {
+      "位置": "左手臂大部分可见,位于画面左下角,右手部分可见",
+      "角度": "左手臂弯曲,手部托举玫瑰花茎,右手轻扶花茎"
+    },
+    "手部": {
+      "位置": "左手位于画面左下角,右手位于左手上方,靠近玫瑰花茎",
+      "角度": "左手手指轻柔地握住花茎,右手食指和拇指轻触花茎,指甲涂有蓝色指甲油"
+    }
+  },
+  "色彩分布图": {
+    "背景绿色区域": {
+      "位置": "画面大部分区域,尤其是左侧和上方",
+      "颜色": "中等饱和度的绿色,呈现草地纹理,有模糊的景深效果"
+    },
+    "人物白色区域": {
+      "位置": "人物上衣,主要集中在画面右下角和中部",
+      "颜色": "纯白色,有褶皱和光影变化"
+    },
+    "玫瑰花位置": {
+      "位置": "画面左侧中部,靠近人物面部",
+      "颜色": "纯白色花瓣,绿色花茎和叶子"
+    },
+    "人物肤色": {
+      "位置": "人物面部、颈部和手部",
+      "颜色": "自然偏白的肤色,带有健康的红润感"
+    },
+    "人物头发": {
+      "位置": "人物头部后方和右侧",
+      "颜色": "深棕色,有光泽"
+    },
+    "唇色": {
+      "位置": "人物嘴唇",
+      "颜色": "红色"
+    },
+    "耳环": {
+      "位置": "人物右耳",
+      "颜色": "金色"
+    },
+    "项链": {
+      "位置": "人物颈部",
+      "颜色": "银色"
+    }
+  },
+  "深度图信息": {
+    "前景": {
+      "物体": "人物的左手和玫瑰花茎下部",
+      "特征": "最清晰,细节丰富"
+    },
+    "中景": {
+      "物体": "人物面部、玫瑰花朵、人物上衣",
+      "特征": "清晰,细节可见"
+    },
+    "背景": {
+      "物体": "绿色草地",
+      "特征": "模糊,景深效果明显,呈现虚化状态"
+    }
+  },
+  "边缘和轮廓": {
+    "人物面部轮廓": "清晰,尤其是鼻子、嘴唇和下巴的线条",
+    "玫瑰花朵轮廓": "清晰,花瓣层次分明",
+    "人物上衣轮廓": "清晰,衣领和袖子的边缘可见",
+    "手部轮廓": "清晰,手指和指甲的形状明确",
+    "背景草地边缘": "模糊,与前景和中景形成对比"
+  },
+  "光源和阴影": {
+    "光源位置": "推测为画面左上方或前方,光线柔和",
+    "高光区域": "人物面部(尤其是鼻梁、额头和脸颊)、玫瑰花瓣、人物头发、耳环、项链",
+    "阴影区域": "人物颈部下方、头发深处、衣物褶皱处、玫瑰花叶子下方"
+  }
+}

BIN
examples/restore/input/images/img_8.jpg


BIN
examples/restore/input/images/img_9.jpg


+ 18 - 0
examples/restore/input/images/set_invariant_features.json

@@ -0,0 +1,18 @@
+{
+  "共同视觉风格": {
+    "色调": "整体色调偏向清新、自然,以绿色(草地、树木)和白色(人物服装、玫瑰花)为主,辅以柔和的暖色调(阳光、人物肤色)。",
+    "光影": "光线柔和,多为自然光。第一张和第二张图片中,阳光从侧面或背面照射,营造出一种温暖、宁静的氛围,并带有一定的逆光效果,使人物边缘有光晕。第三张图片光线均匀,突出人物面部细节。",
+    "景深": "景深较浅,背景虚化明显,突出前景的人物和主体,营造出柔和的视觉效果,使画面更具艺术感和聚焦性。"
+  },
+  "共同主题元素": {
+    "人物": "一位年轻女性,穿着白色连衣裙,长发。在第一张和第二张图片中,她正在户外绘画;在第三张图片中,她正在闻一朵白玫瑰。",
+    "道具": "画架、画布、调色板、画笔、白色玫瑰花。",
+    "场景": "户外草地,背景有绿色的树木,暗示着自然环境。"
+  },
+  "不同图片之间的差异": {
+    "视角": "第一张图片是侧后方视角,展示了人物绘画的动作和画布上的画作。第二张图片是正后方视角,更强调人物的背影和环境光线。第三张图片是侧面特写视角,聚焦于人物的面部表情和手中的玫瑰花。",
+    "构图": "第一张图片采用斜线构图,人物身体和画架形成对角线,引导视线。第二张图片采用中心构图,人物背影居中,画架在右侧。第三张图片采用特写构图,人物面部和玫瑰花占据画面大部分,背景虚化。",
+    "细节": "第一张图片展示了画布上已完成的画作细节(一个穿着白裙的女孩),以及调色板上的颜料。第二张图片强调了阳光透过树叶的光斑效果。第三张图片则清晰展现了人物的妆容、耳环、项链以及指甲油的颜色,还有玫瑰花的纹理。"
+  },
+  "叙事连贯性": "这组图片具有较强的叙事连贯性,讲述了一个关于艺术、自然与美的故事。从第一张和第二张图片中,我们可以看到一位女性在户外享受绘画的乐趣,她将自然的美景和内心的感受融入画作。第三张图片则展现了她在绘画之余,沉浸于自然(玫瑰花)的芬芳,享受片刻的宁静与美好。整个系列通过描绘女性与艺术、自然之间的互动,营造出一种文艺、优雅、充满生活情趣的氛围,暗示着艺术创作与生活感悟的紧密联系。"
+}

+ 29 - 0
examples/restore/input/paragraphs/00_基础信息.json

@@ -0,0 +1,29 @@
+{
+  "帖子ID": "616192600000000021034642",
+  "原始文本": {
+    "title": "《秋日际遇》写生油画",
+    "body": "听闻秋日是倒放的春天 于是我心中有一座秋日的花园 栽种着一簇簇淡却温暖的花 风沿着远边的山吹来 热情的阳光里秋风微凉 与颜料一起酝酿出的画面 白裙是一抹无暇 迎着光绘画出 那片在我心上开满 限定的浪漫 被画架支起 绿草坪还驻留了匆匆而过的热闹 再添一笔白 为我画一枝玫瑰的奇遇 ———@万淮"
+  },
+  "内容过滤结果": {
+    "filtered_body": "听闻秋日是倒放的春天 于是我心中有一座秋日的花园 栽种着一簇簇淡却温暖的花 风沿着远边的山吹来 热情的阳光里秋风微凉 与颜料一起酝酿出的画面 白裙是一抹无暇 迎着光绘画出 那片在我心上开满 限定的浪漫 被画架支起 绿草坪还驻留了匆匆而过的热闹 再添一笔白 为我画一枝玫瑰的奇遇 ———@万淮",
+    "original_body": "听闻秋日是倒放的春天 于是我心中有一座秋日的花园 栽种着一簇簇淡却温暖的花 风沿着远边的山吹来 热情的阳光里秋风微凉 与颜料一起酝酿出的画面 白裙是一抹无暇 迎着光绘画出 那片在我心上开满 限定的浪漫 被画架支起 绿草坪还驻留了匆匆而过的热闹 再添一笔白 为我画一枝玫瑰的奇遇 ———@万淮",
+    "removed_hashtags": [],
+    "kept_hashtags": [],
+    "filter_details": []
+  },
+  "元信息": {
+    "执行时间": "2026-01-27T13:42:43.026726",
+    "模型提供商": "google_genai",
+    "最大递归深度": 10,
+    "图片数量": 9,
+    "文本元素数量": 2,
+    "跳过步骤": {
+      "图片分段": false,
+      "图片实质": true,
+      "图片形式": false,
+      "段内关系": true,
+      "段间关系": true,
+      "文本解构": false
+    }
+  }
+}

+ 215 - 0
examples/restore/input/paragraphs/01_图片分段_01_g1_户外绘画场景.json

@@ -0,0 +1,215 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,侧身站立,右手持画笔,左手持调色板,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              290,
+              100
+            ],
+            [
+              1200,
+              1599
+            ],
+            [
+              290,
+              1599
+            ],
+            [
+              1200,
+              100
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落1.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块画布,画布上有一幅未完成的画作。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              900,
+              1599
+            ],
+            [
+              300,
+              1599
+            ],
+            [
+              900,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "白色画布,上面绘制着一幅以绿色植物和白色裙子人物为主题的油画,画作未完成。",
+              "顶点坐标": [
+                [
+                  450,
+                  300
+                ],
+                [
+                  850,
+                  900
+                ],
+                [
+                  450,
+                  900
+                ],
+                [
+                  850,
+                  300
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落1.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "绘画",
+              "元素ID": "元素4"
+            },
+            {
+              "名称": "玫瑰花",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "一朵白色玫瑰花,放置在画架的横梁上。",
+              "顶点坐标": [
+                [
+                  300,
+                  1000
+                ],
+                [
+                  400,
+                  1100
+                ],
+                [
+                  300,
+                  1100
+                ],
+                [
+                  400,
+                  1000
+                ]
+              ],
+              "拆分推理": "玫瑰花是画架上的一个独立装饰物,具有物理独立性,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落1.2.2",
+              "实质分类": "植物装饰",
+              "形式分类": "种植"
+            }
+          ],
+          "段落ID": "段落1.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料。",
+          "顶点坐标": [
+            [
+              550,
+              1050
+            ],
+            [
+              950,
+              1450
+            ],
+            [
+              550,
+              1450
+            ],
+            [
+              950,
+              1050
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落1.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落1.4",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落1",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 186 - 0
examples/restore/input/paragraphs/01_图片分段_02_g1_户外绘画场景.json

@@ -0,0 +1,186 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地,阳光透过树叶洒下。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,背对镜头,右手持画笔,左手持调色板,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落2.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块画布,画布上有一幅未完成的画作。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              900,
+              1600
+            ],
+            [
+              300,
+              1600
+            ],
+            [
+              900,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "白色画布,上面绘制着一幅以绿色植物和白色裙子人物为主题的油画,画作未完成。",
+              "顶点坐标": [
+                [
+                  450,
+                  300
+                ],
+                [
+                  850,
+                  900
+                ],
+                [
+                  450,
+                  900
+                ],
+                [
+                  850,
+                  300
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落2.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "绘画",
+              "元素ID": "元素4"
+            }
+          ],
+          "段落ID": "段落2.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料。",
+          "顶点坐标": [
+            [
+              400,
+              1000
+            ],
+            [
+              600,
+              1200
+            ],
+            [
+              400,
+              1200
+            ],
+            [
+              600,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落2.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛,阳光从左上方透过树叶洒下,形成光斑。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落2.4",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落2",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 215 - 0
examples/restore/input/paragraphs/01_图片分段_03_g1_户外绘画场景.json

@@ -0,0 +1,215 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地,阳光透过树叶洒下。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,背对镜头,跪坐在草地上,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落3.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块画布,画布上有一幅未完成的画作。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              900,
+              1600
+            ],
+            [
+              300,
+              1600
+            ],
+            [
+              900,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "白色画布,上面绘制着一幅以绿色植物和白色裙子人物为主题的油画,画作未完成。",
+              "顶点坐标": [
+                [
+                  450,
+                  300
+                ],
+                [
+                  850,
+                  900
+                ],
+                [
+                  450,
+                  900
+                ],
+                [
+                  850,
+                  300
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落3.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "绘画",
+              "元素ID": "元素4"
+            }
+          ],
+          "段落ID": "段落3.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料,放置在草地上。",
+          "顶点坐标": [
+            [
+              400,
+              1000
+            ],
+            [
+              600,
+              1200
+            ],
+            [
+              400,
+              1200
+            ],
+            [
+              600,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落3.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "水桶",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "白色水桶,放置在草地上,旁边有画笔。",
+          "顶点坐标": [
+            [
+              700,
+              1200
+            ],
+            [
+              850,
+              1400
+            ],
+            [
+              700,
+              1400
+            ],
+            [
+              850,
+              1200
+            ]
+          ],
+          "拆分推理": "水桶是绘画辅助工具,具有物理独立性,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落3.4",
+          "实质分类": "绘画辅助用品",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛,阳光从左上方透过树叶洒下,形成光斑。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落3.5",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落3",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 185 - 0
examples/restore/input/paragraphs/01_图片分段_04_g1_户外绘画场景.json

@@ -0,0 +1,185 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,侧身站立,右手持画笔,左手持调色板,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              300,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落4.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块空白画布。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              700,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              700,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "空白的白色画布。",
+              "顶点坐标": [
+                [
+                  200,
+                  400
+                ],
+                [
+                  600,
+                  900
+                ],
+                [
+                  200,
+                  900
+                ],
+                [
+                  600,
+                  400
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落4.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "拍摄"
+            }
+          ],
+          "段落ID": "段落4.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料。",
+          "顶点坐标": [
+            [
+              600,
+              1000
+            ],
+            [
+              1000,
+              1400
+            ],
+            [
+              600,
+              1400
+            ],
+            [
+              1000,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落4.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落4.4",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落4",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 213 - 0
examples/restore/input/paragraphs/01_图片分段_05_g2_户外绘画场景.json

@@ -0,0 +1,213 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": null,
+      "描述": "一名身穿白色衬衫的女性在户外草地上绘画,手持调色板和画笔,画面聚焦于女性上半身和绘画工具。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1000,
+          0
+        ],
+        [
+          1000,
+          1500
+        ],
+        [
+          0,
+          1500
+        ]
+      ],
+      "拆分推理": "该段落是整个图像的根节点,代表了图像的整体场景。根据核心原则,首先识别内容品类为户外绘画场景,并将其作为根节点。拆分维度是场景中的核心独立物体。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "一名身穿白色长袖衬衫的女性,仅展示上半身,左手拿着调色板,右手拿着画笔,正在进行绘画。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1000,
+              0
+            ],
+            [
+              1000,
+              1500
+            ],
+            [
+              0,
+              1500
+            ]
+          ],
+          "拆分推理": "人物是场景中的核心展示对象之一,具有物理独立性和空间独立性,因此作为独立节点拆分。不继续拆分人物的身体部位,因为本创作的核心展示内容是绘画行为和工具,而非人物本身的细节。",
+          "子段落": null,
+          "段落ID": "段落5.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素6"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "木质画架,部分可见,支撑着画布。",
+          "顶点坐标": [
+            [
+              0,
+              600
+            ],
+            [
+              300,
+              600
+            ],
+            [
+              300,
+              1000
+            ],
+            [
+              0,
+              1000
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要辅助工具,具有物理独立性,因此作为独立节点拆分。画架本身不是核心展示对象,不需进一步拆分其组成部分。",
+          "子段落": null,
+          "段落ID": "段落5.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "画布",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "白色空白画布,部分可见,位于画架上。",
+          "顶点坐标": [
+            [
+              0,
+              400
+            ],
+            [
+              300,
+              400
+            ],
+            [
+              300,
+              700
+            ],
+            [
+              0,
+              700
+            ]
+          ],
+          "拆分推理": "画布是绘画的核心载体,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。画布目前是空白状态,没有需要进一步拆分的附着物。",
+          "子段落": null,
+          "段落ID": "段落5.3",
+          "实质分类": "艺术载体",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "椭圆形木质调色板,上面沾满了各种颜色的油彩,以绿色、蓝色和红色为主,被人物左手握持。",
+          "顶点坐标": [
+            [
+              290,
+              600
+            ],
+            [
+              990,
+              600
+            ],
+            [
+              990,
+              1000
+            ],
+            [
+              290,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画场景中的核心工具,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。调色板上的颜料是其附着物,但颜料本身不具备独立性,且本创作不侧重颜料的细节,因此不进一步拆分。",
+          "子段落": null,
+          "段落ID": "段落5.4",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "画笔",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "画笔笔尖部分可见,被人物右手握持,正在画布上方。",
+          "顶点坐标": [
+            [
+              200,
+              0
+            ],
+            [
+              300,
+              0
+            ],
+            [
+              300,
+              100
+            ],
+            [
+              200,
+              100
+            ]
+          ],
+          "拆分推理": "画笔是绘画场景中的核心工具,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。画笔本身不需进一步拆分。",
+          "子段落": null,
+          "段落ID": "段落5.5",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "模糊的绿色草地背景,暗示户外环境。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1000,
+              0
+            ],
+            [
+              1000,
+              1500
+            ],
+            [
+              0,
+              1500
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,但不是核心展示对象,且不具备独立性,因此作为叶子节点不继续拆分。它为核心对象提供了环境信息。",
+          "子段落": null,
+          "段落ID": "段落5.6",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落5",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 2
+}

+ 213 - 0
examples/restore/input/paragraphs/01_图片分段_06_g2_户外绘画场景.json

@@ -0,0 +1,213 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": null,
+      "描述": "一名身穿白色衬衫的女性在户外草地上绘画,从背后视角展示,手持调色板和画笔,正在画布上作画。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1000,
+          0
+        ],
+        [
+          1000,
+          1500
+        ],
+        [
+          0,
+          1500
+        ]
+      ],
+      "拆分推理": "该段落是整个图像的根节点,代表了图像的整体场景。根据核心原则,首先识别内容品类为户外绘画场景,并将其作为根节点。拆分维度是场景中的核心独立物体。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "一名身穿白色长袖衬衫的女性,从背后视角展示,露出部分背部,左手拿着调色板,右手拿着画笔,正在画布上作画。佩戴金色耳环和项链。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1000,
+              0
+            ],
+            [
+              1000,
+              1500
+            ],
+            [
+              0,
+              1500
+            ]
+          ],
+          "拆分推理": "人物是场景中的核心展示对象之一,具有物理独立性和空间独立性,因此作为独立节点拆分。不继续拆分人物的身体部位,因为本创作的核心展示内容是绘画行为和工具,而非人物本身的细节。",
+          "子段落": null,
+          "段落ID": "段落6.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素6"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "木质画架,支撑着画布。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              700,
+              0
+            ],
+            [
+              700,
+              400
+            ],
+            [
+              300,
+              400
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要辅助工具,具有物理独立性,因此作为独立节点拆分。画架本身不是核心展示对象,不需进一步拆分其组成部分。",
+          "子段落": null,
+          "段落ID": "段落6.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "画布",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "画布上已绘有绿色和蓝色为主的风景画,位于画架上。",
+          "顶点坐标": [
+            [
+              300,
+              200
+            ],
+            [
+              800,
+              200
+            ],
+            [
+              800,
+              800
+            ],
+            [
+              300,
+              800
+            ]
+          ],
+          "拆分推理": "画布是绘画的核心载体,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。画布上已有画作,但画作本身不具备独立性,且本创作不侧重画作的细节,因此不进一步拆分。",
+          "子段落": null,
+          "段落ID": "段落6.3",
+          "实质分类": "艺术载体",
+          "形式分类": "绘画"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "椭圆形木质调色板,上面沾满了各种颜色的油彩,以绿色、蓝色和红色为主,被人物左手握持。",
+          "顶点坐标": [
+            [
+              200,
+              600
+            ],
+            [
+              700,
+              600
+            ],
+            [
+              700,
+              1000
+            ],
+            [
+              200,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画场景中的核心工具,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。调色板上的颜料是其附着物,但颜料本身不具备独立性,且本创作不侧重颜料的细节,因此不进一步拆分。",
+          "子段落": null,
+          "段落ID": "段落6.4",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "画笔",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "画笔被人物右手握持,正在画布上作画。",
+          "顶点坐标": [
+            [
+              500,
+              400
+            ],
+            [
+              600,
+              400
+            ],
+            [
+              600,
+              500
+            ],
+            [
+              500,
+              500
+            ]
+          ],
+          "拆分推理": "画笔是绘画场景中的核心工具,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。画笔本身不需进一步拆分。",
+          "子段落": null,
+          "段落ID": "段落6.5",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "模糊的绿色草地和树木背景,暗示户外环境。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1000,
+              0
+            ],
+            [
+              1000,
+              1500
+            ],
+            [
+              0,
+              1500
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,但不是核心展示对象,且不具备独立性,因此作为叶子节点不继续拆分。它为核心对象提供了环境信息。",
+          "子段落": null,
+          "段落ID": "段落6.6",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落6",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 2
+}

+ 334 - 0
examples/restore/input/paragraphs/01_图片分段_07_g3_人物与玫瑰花.json

@@ -0,0 +1,334 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg",
+  "sections": [
+    {
+      "名称": "人物与玫瑰花",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性侧身闭眼,闻着手中的白色玫瑰花,背景是绿色的草地。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心展示内容是人物和玫瑰花,以及它们所处的环境。因此,将图片拆分为人物、玫瑰花和背景这三个主要组成部分。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "画面右侧的女性,侧脸闭眼,表情陶醉,棕色长发,佩戴金色耳环和银色项链,身穿白色上衣,双手拿着玫瑰花,指甲涂有蓝色指甲油。",
+          "顶点坐标": [
+            [
+              248,
+              20
+            ],
+            [
+              1200,
+              20
+            ],
+            [
+              1200,
+              1599
+            ],
+            [
+              248,
+              1599
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象之一,需要进一步拆分其主要组成部分,如头部、手部和服饰,以详细描述其特征。",
+          "子段落": [
+            {
+              "名称": "头部",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "女性的侧脸,闭着眼睛,鼻子高挺,嘴唇涂有红色口红,表情陶醉,棕色长发披散。",
+              "顶点坐标": [
+                [
+                  448,
+                  20
+                ],
+                [
+                  1200,
+                  20
+                ],
+                [
+                  1200,
+                  772
+                ],
+                [
+                  448,
+                  772
+                ]
+              ],
+              "拆分推理": "头部是人物的重要组成部分,包含面部特征、发型和佩饰,需要独立拆分以进行详细描述。",
+              "子段落": [
+                {
+                  "名称": "耳环",
+                  "内容类型": "图片",
+                  "内容实质": "",
+                  "描述": "女性右耳佩戴的金色几何形状耳环。",
+                  "顶点坐标": [
+                    [
+                      900,
+                      396
+                    ],
+                    [
+                      1040,
+                      396
+                    ],
+                    [
+                      1040,
+                      500
+                    ],
+                    [
+                      900,
+                      500
+                    ]
+                  ],
+                  "拆分推理": "耳环是人物头部的独立装饰物,具有物理独立性,且是视觉焦点之一,因此作为叶子节点进行描述。",
+                  "子段落": null,
+                  "段落ID": "段落7.1.1.1",
+                  "实质分类": "饰品",
+                  "形式分类": "制作",
+                  "元素ID": "元素5"
+                },
+                {
+                  "名称": "项链",
+                  "内容类型": "图片",
+                  "内容实质": "",
+                  "描述": "女性颈部佩戴的银色细链项链。",
+                  "顶点坐标": [
+                    [
+                      700,
+                      500
+                    ],
+                    [
+                      1000,
+                      500
+                    ],
+                    [
+                      1000,
+                      580
+                    ],
+                    [
+                      700,
+                      580
+                    ]
+                  ],
+                  "拆分推理": "项链是人物颈部的独立装饰物,具有物理独立性,因此作为叶子节点进行描述。",
+                  "子段落": null,
+                  "段落ID": "段落7.1.1.2",
+                  "实质分类": "饰品",
+                  "形式分类": "制作",
+                  "元素ID": "元素5"
+                }
+              ],
+              "段落ID": "段落7.1.1",
+              "实质分类": "人物身体部位",
+              "形式分类": "拍摄"
+            },
+            {
+              "名称": "手部",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "女性的双手,指甲涂有蓝色指甲油,轻轻握住玫瑰花的茎部。",
+              "顶点坐标": [
+                [
+                  248,
+                  800
+                ],
+                [
+                  680,
+                  800
+                ],
+                [
+                  680,
+                  1200
+                ],
+                [
+                  248,
+                  1200
+                ]
+              ],
+              "拆分推理": "手部是人物与玫瑰花互动的重要部分,需要独立拆分以描述其姿态和细节。",
+              "子段落": null,
+              "段落ID": "段落7.1.2",
+              "实质分类": "人物身体部位",
+              "形式分类": "拍摄",
+              "元素ID": "元素5"
+            },
+            {
+              "名称": "服饰",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "女性身穿的白色宽松上衣,领口和袖口设计独特。",
+              "顶点坐标": [
+                [
+                  400,
+                  500
+                ],
+                [
+                  1200,
+                  500
+                ],
+                [
+                  1200,
+                  1599
+                ],
+                [
+                  400,
+                  1599
+                ]
+              ],
+              "拆分推理": "服饰是人物整体形象的重要组成部分,需要独立拆分以描述其款式和颜色。",
+              "子段落": null,
+              "段落ID": "段落7.1.3",
+              "实质分类": "服装",
+              "形式分类": "制作",
+              "元素ID": "元素5"
+            }
+          ],
+          "段落ID": "段落7.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "玫瑰花",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一朵盛开的白色玫瑰花,带有绿色的茎和叶子,被女性双手握持。",
+          "顶点坐标": [
+            [
+              0,
+              300
+            ],
+            [
+              600,
+              300
+            ],
+            [
+              600,
+              1000
+            ],
+            [
+              0,
+              1000
+            ]
+          ],
+          "拆分推理": "玫瑰花是图片的核心展示对象之一,需要进一步拆分其花朵和茎叶部分,以详细描述其形态。",
+          "子段落": [
+            {
+              "名称": "花朵",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "白色玫瑰花的盛开部分,花瓣层叠。",
+              "顶点坐标": [
+                [
+                  0,
+                  300
+                ],
+                [
+                  400,
+                  300
+                ],
+                [
+                  400,
+                  600
+                ],
+                [
+                  0,
+                  600
+                ]
+              ],
+              "拆分推理": "花朵是玫瑰花的主体,是视觉焦点,因此作为叶子节点进行描述。",
+              "子段落": null,
+              "段落ID": "段落7.2.1",
+              "实质分类": "植物器官",
+              "形式分类": "种植"
+            },
+            {
+              "名称": "茎叶",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "玫瑰花的绿色茎部和叶子,叶片完整。",
+              "顶点坐标": [
+                [
+                  0,
+                  500
+                ],
+                [
+                  600,
+                  500
+                ],
+                [
+                  600,
+                  1000
+                ],
+                [
+                  0,
+                  1000
+                ]
+              ],
+              "拆分推理": "茎叶是玫瑰花的组成部分,与花朵共同构成完整的玫瑰花,因此作为叶子节点进行描述。",
+              "子段落": null,
+              "段落ID": "段落7.2.2",
+              "实质分类": "植物器官",
+              "形式分类": "种植"
+            }
+          ],
+          "段落ID": "段落7.2",
+          "实质分类": "植物",
+          "形式分类": "种植"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "模糊的绿色草地,作为人物和玫瑰花的背景。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ]
+          ],
+          "拆分推理": "背景是场景的重要组成部分,但不是核心展示对象,无需进一步拆分,作为叶子节点。",
+          "子段落": null,
+          "段落ID": "段落7.3",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落7",
+      "实质分类": "人物特写",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 3
+}

+ 214 - 0
examples/restore/input/paragraphs/01_图片分段_08_g1_户外绘画场景.json

@@ -0,0 +1,214 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,侧身站立,右手持画笔,左手持调色板,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              300,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落8.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块空白画布。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              700,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              700,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "空白的白色画布。",
+              "顶点坐标": [
+                [
+                  200,
+                  400
+                ],
+                [
+                  600,
+                  900
+                ],
+                [
+                  200,
+                  900
+                ],
+                [
+                  600,
+                  400
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落8.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "拍摄"
+            },
+            {
+              "名称": "玫瑰花",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "一朵白色玫瑰花,放置在画架的横梁上。",
+              "顶点坐标": [
+                [
+                  300,
+                  1000
+                ],
+                [
+                  400,
+                  1100
+                ],
+                [
+                  300,
+                  1100
+                ],
+                [
+                  400,
+                  1000
+                ]
+              ],
+              "拆分推理": "玫瑰花是画架上的一个独立装饰物,具有物理独立性,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落8.2.2",
+              "实质分类": "植物装饰",
+              "形式分类": "种植"
+            }
+          ],
+          "段落ID": "段落8.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料。",
+          "顶点坐标": [
+            [
+              600,
+              1000
+            ],
+            [
+              1000,
+              1400
+            ],
+            [
+              600,
+              1400
+            ],
+            [
+              1000,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落8.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落8.4",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落8",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 186 - 0
examples/restore/input/paragraphs/01_图片分段_09_g1_户外绘画场景.json

@@ -0,0 +1,186 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地,阳光透过树叶洒下。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,背对镜头,站立在草地上,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落9.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块画布,画布上有一幅未完成的画作。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              900,
+              1600
+            ],
+            [
+              300,
+              1600
+            ],
+            [
+              900,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "白色画布,上面绘制着一幅以绿色植物和白色裙子人物为主题的油画,画作未完成。",
+              "顶点坐标": [
+                [
+                  450,
+                  300
+                ],
+                [
+                  850,
+                  900
+                ],
+                [
+                  450,
+                  900
+                ],
+                [
+                  850,
+                  300
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落9.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "绘画",
+              "元素ID": "元素4"
+            }
+          ],
+          "段落ID": "段落9.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料,放置在草地上。",
+          "顶点坐标": [
+            [
+              400,
+              1000
+            ],
+            [
+              600,
+              1200
+            ],
+            [
+              400,
+              1200
+            ],
+            [
+              600,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落9.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛,阳光从左上方透过树叶洒下,形成光斑。远处有一个木质凉亭。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落9.4",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落9",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 249 - 0
examples/restore/input/paragraphs/02_图片形式_01.json

@@ -0,0 +1,249 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落1",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面右侧偏中位置,一名长发女性,身穿白色长裙,侧身站立,面向左前方,右手持画笔,左手持调色板,正在画布上绘画。人物区域为从左侧290像素到右侧1200像素,从顶部100像素到底部1599像素。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面左侧偏中位置,一个木质三脚画架,呈浅棕色,支撑着一块画布。画架的外接矩形区域为从左侧300像素到右侧900像素,从顶部0像素到底部1599像素。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "画面右下角,女性左手持有一个椭圆形木质调色板,上面沾有多种颜色的颜料。调色板的外接矩形区域为从左侧550像素到右侧950像素,从顶部1050像素到底部1450像素。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "背景",
+          "描述": "背景占据了整个画面,其范围为从左侧0像素到右侧1200像素,从顶部0像素到底部1600像素。主要由户外草地和树木组成,草地呈绿色,树木茂盛,位于人物和画架后方。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性身体略微向左前方倾斜,头部向左转,目光聚焦在画布上。右手持画笔,笔尖触及画布,左手托举调色板,手臂自然弯曲。身体呈侧身站立姿态,右腿略微弯曲,左腿伸直,裙摆略微向右后方飘动,呈现出轻盈感。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色长袖连衣裙,材质轻薄,有垂坠感。裙子领口为V字形,袖子宽松,裙摆较长,覆盖至小腿或脚踝处。裙子背面有两条与裙子同色同材质的细带,位于背部上方,连接领口,呈平行状。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有长发,发色为深棕色,发丝柔顺,自然披散在背部和右肩。右侧耳后发丝向右上方轻微飘起。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性的肤色呈现自然健康的浅米色调,手臂和颈部皮肤质感细腻。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": true,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "视觉估计人物年龄为20-30岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物面部特征",
+          "描述": "女性面部侧向画布,仅能看到右侧脸颊和部分下颌线。右耳佩戴一个金色几何形状耳环,耳环呈开口向下的半圆形,两端略微向内弯曲。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式29"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "女性右手持一支细长的黑色画笔,笔尖为深色。左手托举一个椭圆形木质调色板。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架由木材制成,呈现浅棕色木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架为三脚架结构,由三根木质支架组成,其中两根支架在前方支撑画布,一根支架在后方提供稳定性。画架顶部有一个可调节高度的木质横梁,用于固定画布上方。画架中部有一个黑色旋钮,用于调节画布的倾斜角度和高度。画架左侧支架上挂有一个黑色条状布带,松散地垂下。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "画架主体为浅棕色木质原色,调节旋钮为黑色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈矩形,垂直放置,长边垂直于地面。画布的顶点坐标为[450, 300](左上), [850, 300](右上), [850, 900](右下), [450, 900](左下)。",
+          "是否可定量": true,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布底色为白色,但大部分区域已被颜料覆盖,呈现出绿色、蓝色、白色等多种颜色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        },
+        {
+          "名称": "画作内容",
+          "描述": "画作描绘了一个以绿色植物和蓝色花朵为背景的场景,画面中央有一位身穿白色裙子的女性背影,背对观众,头部略微向左转,长发披肩,裙摆飘逸,与正在绘画的女性着装相似。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式24"
+        },
+        {
+          "名称": "画作风格",
+          "描述": "画作笔触粗犷,色彩饱和度高,通过明暗对比表现光影,具有印象派的典型特征。",
+          "是否可定量": false,
+          "类型": "风格",
+          "形式ID": "形式25"
+        },
+        {
+          "名称": "画作完成度",
+          "描述": "画作已完成大部分,画布左上角和右下角有少量留白,笔触未完全覆盖,显示为未完成状态。",
+          "是否可定量": false,
+          "类型": "状态",
+          "形式ID": "形式26"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.2.2",
+      "形式": [
+        {
+          "名称": "花朵颜色",
+          "描述": "花朵为纯白色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式28"
+        },
+        {
+          "名称": "花朵形态",
+          "描述": "花朵为一朵盛开的玫瑰花,花瓣层叠,形态饱满,花茎带有绿色叶片。",
+          "是否可定量": false,
+          "类型": "形态",
+          "形式ID": "形式27"
+        },
+        {
+          "名称": "花朵位置",
+          "描述": "花朵放置在画架左侧的横梁上,花头朝向左下方,花茎和叶片沿着横梁延伸。花朵的外接矩形区域为从左侧300像素到右侧400像素,从顶部1000像素到底部1100像素。",
+          "是否可定量": true,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈不规则的椭圆形,边缘圆润,左侧有一个直径约2厘米的圆形拇指孔。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板由木材制成,呈现浅棕色木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜色的颜料,包括绿色、蓝色、白色、粉色、黄色、红色、棕色等,颜色鲜艳。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料以块状或条状分布在调色板的边缘和中央区域。调色板中央区域有一大块混合的浅绿色颜料,边缘区域有混合的灰蓝色颜料。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.4",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景主要由大面积的绿色草地和茂密的树木组成。草地平坦,树木高大,枝叶繁茂,形成自然的绿色屏障。画面左侧远处有一片模糊的蓝色区域,可能是水面或天空。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "草地呈鲜亮的绿色,树木的叶子呈深浅不一的绿色。远处背景颜色轻微模糊,呈现偏蓝的灰绿色调。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景的清晰度低于前景的人物和画架,呈现出一定的景深模糊效果,使得前景主体更加突出。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 231 - 0
examples/restore/input/paragraphs/02_图片形式_02.json

@@ -0,0 +1,231 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落2",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "人物主体占据画面左侧约1/3区域,其中心线位于画面中心线左侧约1/8处,背对镜头,正在进行绘画活动。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画架位于画面右侧约1/3区域,其中心线位于画面中心线右侧约1/8处,木质三脚架结构,支撑着一块画布。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "调色板位于人物左手下方,紧贴其左侧大腿外侧,部分被裙摆遮挡,椭圆形,上面沾有多种颜色的颜料。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "背景",
+          "描述": "草地占据画面下方约1/3区域,树木占据画面上方约2/3区域,整体呈现户外自然环境。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "光照效果",
+          "描述": "阳光从画面左上方约1/4区域透过树叶洒下,形成明亮的光斑和光晕效果,使画面整体呈现逆光和温暖的氛围。",
+          "是否可定量": true,
+          "类型": "效果",
+          "形式ID": "形式30"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落2.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性站立,身体向右倾斜约15度,背对镜头,头部向右转约30度,面向画架和画布,右手持画笔伸向画布,左手持调色板。",
+          "是否可定量": true,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "身穿一件白色长裙,裙子款式宽松,背部有V字形开口,开口深度约至腰部,腰部有系带,裙摆自然垂坠,长度及小腿中部。",
+          "是否可定量": true,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "长发,发色为棕色,长度及腰部,自然垂落在背部,发尾有轻微的内扣卷曲。",
+          "是否可定量": true,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "手臂和颈部露出的皮肤呈现浅麦色,在阳光下反射出柔和的光泽。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": true,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "无法从画面中精确判断人物年龄,但其身形和着装显示为成年女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "右手持一支细长的画笔,笔杆为黑色,笔尖沾有颜料;左手持一块椭圆形调色板。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落2.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "木质,呈现原木的浅棕色,带有木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "三脚架结构,由三根木杆通过金属铰链连接支撑,顶部有可调节高度的画板托,画板托上放置着画布。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "浅棕色木质原色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落2.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "矩形,长宽比约为4:3,长边垂直于地面,宽边平行于地面。",
+          "是否可定量": true,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布大部分区域被颜料覆盖,仅在边缘和局部细节处可见少量白色底色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        },
+        {
+          "名称": "画作内容",
+          "描述": "画作描绘了一个穿着白色裙子、背对观众的女性人物形象,背景是绿色植物和零星的紫色小花,整体风格偏向印象派,色彩明亮。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式24"
+        },
+        {
+          "名称": "画作风格",
+          "描述": "印象派风格,笔触粗犷可见,色彩鲜明且饱和度较高,注重光影效果。",
+          "是否可定量": false,
+          "类型": "风格",
+          "形式ID": "形式25"
+        },
+        {
+          "名称": "画作完成度",
+          "描述": "画作已初具雏形,人物和背景的轮廓和主要色彩已完成,但人物面部和背景植物的纹理细节仍有待完善,属于未完成状态。",
+          "是否可定量": false,
+          "类型": "状态",
+          "形式ID": "形式26"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落2.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "椭圆形,边缘经过打磨,触感光滑。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "木质,呈现深棕色,表面有使用颜料留下的痕迹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜料,主要包括草绿色、天蓝色、柠檬黄、赭石色等,颜色混合在一起,形成不规则的色块。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料不规则地分布在调色板的表面,呈现出多处颜料堆积和混合的痕迹,表明其已被频繁使用。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落2.4",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "画面下半部分是修剪整齐、平坦的绿色草地,画面上半部分是茂盛的、叶片繁密的绿色树木,树木之间可见明亮的天空。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "草地呈现鲜亮的翠绿色,树木呈现深浅不一的墨绿色和橄榄绿,天空呈现明亮的乳白色和淡黄色,整体色调以绿色为主。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景整体呈现明显的虚化效果,尤其是树木和远处的景物,焦点集中在人物和画架上,背景景物轮廓模糊。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        },
+        {
+          "名称": "光斑效果",
+          "描述": "画面左上方和中央偏上区域有多个明显的圆形和不规则形状的光斑,大小不一,呈现明亮的白色和黄色,是阳光透过树叶形成的散射光。",
+          "是否可定量": false,
+          "类型": "效果",
+          "形式ID": "形式32"
+        }
+      ]
+    }
+  ]
+}

+ 265 - 0
examples/restore/input/paragraphs/02_图片形式_03.json

@@ -0,0 +1,265 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落3",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "人物主体位于画面下半部分,占据画面高度约1/2,宽度约1/3,其中心点偏向画面左侧。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画架位于画面中央偏右,其底部约在画面高度的1/3处,顶部接近画面顶部,宽度约占画面1/5。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "调色板位于画面左下角,紧邻人物左侧,其最宽处约占画面宽度的1/8。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "水桶",
+          "描述": "水桶位于画面右下角,紧邻画架右侧底部,其高度约占画面高度的1/10。",
+          "是否可定量": true,
+          "类型": "物件"
+        },
+        {
+          "名称": "背景",
+          "描述": "近处的绿色草地占据画面下半部分约1/3,远处的绿色树木占据画面上半部分约2/3。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "光照效果",
+          "描述": "阳光从画面左上方透过树叶洒下,在草地和树叶上形成约5-7个圆形或不规则形状的明亮光斑,直径约占画面宽度的1/20到1/10,主要分布在画面左侧和中央区域。",
+          "是否可定量": true,
+          "类型": "效果",
+          "形式ID": "形式30"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性背对镜头,双膝跪坐在草地上,身体略微前倾,与地面形成约70-80度的夹角,头部和上半身朝向画架,双手似乎正在进行绘画动作。",
+          "是否可定量": true,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "身穿一件白色长袖连衣裙,裙子材质轻薄,有自然垂坠感,后背呈深V字形开口,开口延伸至腰部上方约10-15厘米处,露出大部分背部,腰部有系带设计。",
+          "是否可定量": true,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "长发,呈深棕色,自然披散在背部,发尾微卷,长度及腰部下方约5-10厘米处。",
+          "是否可定量": true,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "从露出的背部和手臂部分看,肤色呈中等偏深的暖棕色调。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "无法从画面中精确判断人物年龄。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架主体为木质,呈现原木色,表面纹理细腻。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "采用经典的三脚架结构,由三根木质支架支撑,顶部有两根可调节的画板支撑杆,通过旋钮固定,画板放置在两根横向支撑条上。",
+          "是否可定量": true,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "画架整体呈浅棕色木质原色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈长方形,长宽比约为3:2,竖向放置,长边垂直于地面。",
+          "是否可定量": true,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布底色为白色,但大部分区域已被颜料覆盖。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        },
+        {
+          "名称": "画作内容",
+          "描述": "画作描绘了一名身穿白色裙子的女性背影,站在一片绿色植物和蓝色花朵的田野中,女性右手持一把白色遮阳伞,左手似乎拿着一个白色或浅色圆形物体,形状类似帽子或花束。背景有模糊的绿色树木和白色建筑轮廓。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式24"
+        },
+        {
+          "名称": "画作风格",
+          "描述": "画作笔触明显,色彩饱和度较高,光影表现突出。",
+          "是否可定量": false,
+          "类型": "风格",
+          "形式ID": "形式25"
+        },
+        {
+          "名称": "画作完成度",
+          "描述": "画作主体形象清晰,背景细节模糊,整体画面已具雏形。",
+          "是否可定量": false,
+          "类型": "状态",
+          "形式ID": "形式26"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈不规则的椭圆形,一侧边缘略有凹陷,另一侧较为平直,边缘圆润。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板为木质,表面可见木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜料,包括天蓝色、草绿色、柠檬黄、朱红色等,颜色鲜艳饱和。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料呈不规则的块状分布在调色板的边缘和中央区域,约有5-7个主要色块,部分颜色相互混合形成过渡色。",
+          "是否可定量": true,
+          "类型": "分布",
+          "形式ID": "形式14"
+        },
+        {
+          "名称": "调色板位置",
+          "描述": "调色板放置在人物左手边的草地上,靠近人物身体,位于画面左下角区域。",
+          "是否可定量": false,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.4",
+      "形式": [
+        {
+          "名称": "水桶颜色",
+          "描述": "水桶主体呈白色,桶身印有黑色手写体英文字母,内容为“For the love of flowers”,字体大小适中,占据桶身高度约1/3。",
+          "是否可定量": true,
+          "类型": "颜色"
+        },
+        {
+          "名称": "水桶形状",
+          "描述": "水桶呈圆柱形,带有提手,桶口直径约比桶底直径宽1/5。",
+          "是否可定量": true,
+          "类型": "形状"
+        },
+        {
+          "名称": "水桶位置",
+          "描述": "水桶放置在人物右侧的草地上,靠近画架右下角,位于画面右下角区域。",
+          "是否可定量": false,
+          "类型": "位置"
+        },
+        {
+          "名称": "画笔存在",
+          "描述": "水桶旁边可见两根画笔,笔杆呈深棕色,其中一根笔头浸入水桶中,另一根笔头靠近水桶边缘。",
+          "是否可定量": true,
+          "类型": "存在性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.5",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景由近处的绿色草地和远处的茂盛树木组成。草地平坦,覆盖画面下半部分。树木高大,枝叶繁茂,形成一道绿色的屏障,占据画面上半部分。远景右侧可见约2-3个模糊的白色建筑轮廓,呈长方形或方形。",
+          "是否可定量": true,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "草地呈翠绿色,树木呈墨绿、橄榄绿等深浅不一的绿色,整体色调以绿色为主,与阳光形成暖色调对比。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景整体呈现明显的虚化效果,景深较浅,远处的树木和建筑细节模糊不清,突出前景人物和画作。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        },
+        {
+          "名称": "光斑效果",
+          "描述": "阳光从画面左上方透过树叶缝隙洒下,在草地和树叶上形成约5-7个圆形或不规则形状的明亮光斑,光斑直径约占画面宽度的1/20到1/10,主要分布在画面左侧和中央区域,光斑边缘柔和,呈现出温暖的光线氛围。",
+          "是否可定量": true,
+          "类型": "效果",
+          "形式ID": "形式32"
+        }
+      ]
+    }
+  ]
+}

+ 203 - 0
examples/restore/input/paragraphs/02_图片形式_04.json

@@ -0,0 +1,203 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落4",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面右侧偏中位置,一名长发女性,身穿白色长裙,侧身站立,面向左侧,右手持画笔,左手持调色板,正在画布上绘画。人物头部位于画面上方约1/4处,脚部位于画面下方约1/5处。身体躯干占据画面中央偏右区域。女性身体略微向左倾斜,头部微抬,目光看向左前方画布方向。右手持画笔,手臂微弯,画笔尖端指向画布。左手托举调色板,手臂自然弯曲。双腿被长裙遮挡,但从整体姿态判断,应为站立姿态。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面左侧偏中位置,一个木质三脚画架,支撑着一块空白画布。画架呈浅棕色,由三根支架构成,其中两根支架从画面左下角延伸至画面中部,另一根支架在画面左侧中部。画架左侧边缘位于画面左侧约1/10处,右侧边缘位于画面中央偏左。画架顶部位于画面上方约1/5处,底部位于画面下方约1/10处。画架为三脚架结构,由三根主支架构成,其中两根支架从地面向上倾斜支撑画布,另一根支架在后方提供稳定。画架中部有一个可调节高度的横杆,用于放置画布,横杆下方有一个木质托盘。画架中部有一个黑色皮质绑带,用于收纳或携带。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "人物左手持有的椭圆形木质调色板,位于画面右侧中部偏下,上面沾有多种颜色的颜料,包括绿色、蓝色、红色、黄色、白色等,颜料呈不规则块状分布。调色板最长直径约为人物头部宽度的1.5倍。调色板呈不规则椭圆形,边缘圆润。颜料呈不规则块状分布在调色板表面,绿色颜料集中在调色板中央,其他颜色颜料散布在边缘。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "背景",
+          "描述": "画面后方是户外草地和树木。草地呈绿色,覆盖画面底部约1/3的区域,草地平坦。树木茂盛,呈深浅不一的绿色,占据画面顶部约2/3的区域,树木之间有少量空隙可见远处的模糊建筑轮廓,呈灰色或浅色,无法辨认具体结构。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落4.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性身体略微向左倾斜,头部微抬,目光看向左前方画布方向。右手持画笔,手臂微弯,画笔尖端指向画布。左手托举调色板,手臂自然弯曲。双腿被长裙遮挡,无法判断具体姿态,但从裙摆看,应为站立姿态。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色长袖连衣裙,裙子款式宽松,袖子为泡泡袖设计,裙摆接近及地,略高于地面,有自然褶皱。领口为圆领。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有深棕色长发,发丝柔顺,自然垂落在肩部和背部,发尾自然垂落,略带弧度。额头前无刘海,露出完整额头。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性面部和手臂的肤色呈现健康的浅米色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "目测年龄在20-30岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物面部特征",
+          "描述": "女性面部轮廓柔和,眼睛呈杏仁状,眉毛弯曲,鼻梁挺拔,嘴唇饱满,涂有红色口红。右耳佩戴金色圆形耳环,耳环表面光滑,有反光。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式29"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "右手持一支细长的画笔,笔杆为黑色,笔尖为绿色。左手托举一个椭圆形木质调色板。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落4.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架由浅棕色木材制成,表面光滑,有木质纹理。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架为三脚架结构,由三根主支架构成,其中两根支架从地面向上倾斜支撑画布,另一根支架在后方提供稳定。画架中部有一个可调节高度的横杆,用于放置画布,横杆下方有一个木质托盘。画架中部有一个黑色皮质绑带,用于收纳或携带。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "画架整体呈浅棕色,木质原色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落4.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈长方形,垂直放置在画架上,长边垂直于地面。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布表面为纯白色,无任何图案或颜色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落4.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈不规则椭圆形,边缘圆润。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板由木材制成,表面光滑,呈浅棕色。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜色的颜料,主要包括深绿色、浅绿色、蓝色、红色、黄色、白色,以及少量橙色和紫色。绿色颜料占据调色板大部分区域。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料呈不规则块状分布在调色板表面,绿色颜料集中在调色板中央,其他颜色颜料散布在边缘。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落4.4",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景由近处的绿色草地和远处的茂盛树木构成。草地平坦,树木高大,树冠浓密。树木后方隐约可见模糊的建筑轮廓,无法辨认具体类型。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "背景颜色以绿色为主,草地呈鲜绿色,树木呈深浅不一的绿色,部分树叶在阳光下呈现亮绿色。天空颜色为浅蓝色或白色,占据画面顶部边缘极小部分,几乎不可见。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景整体呈现浅景深效果,草地和树木的细节相对模糊,远处的建筑轮廓更加模糊,与前景人物和画架形成对比,突出主体。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 255 - 0
examples/restore/input/paragraphs/02_图片形式_05.json

@@ -0,0 +1,255 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落5",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面主体为一名女性,仅展示其上半身,身穿白色长袖衬衫,左手握持调色板,右手握持画笔,正在进行绘画动作。人物从画面右侧中部延伸至画面左侧中部,占据画面约60%的宽度和70%的高度。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面左侧可见一个木质画架的局部,其支架部分呈棕色,支撑着画布。画架从画面左下角延伸至画面左侧中部,顶点坐标为[0, 700]到[300, 1000]。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "画布",
+          "描述": "画面左侧画架上放置着一块白色空白画布的局部,呈矩形,位于画架的上方。画布的顶点坐标为[0, 400]到[300, 700]。",
+          "是否可定量": true,
+          "类型": "物件"
+        },
+        {
+          "名称": "调色板",
+          "描述": "一个椭圆形木质调色板,颜色为深棕色,上面沾满了多种颜色的油彩,主要包括大面积的绿色、蓝色、红色、黄色、白色和少量其他颜色。调色板被人物的左手握持,位于画面中央偏下,顶点坐标为[290, 600]到[990, 1000]。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "画笔",
+          "描述": "一根细长的画笔,笔尖部分可见,呈绿色,被人物的右手握持,位于画布的上方,正在进行绘画动作。画笔的顶点坐标为[200, 0]到[300, 100]。",
+          "是否可定量": true,
+          "类型": "物件"
+        },
+        {
+          "名称": "背景",
+          "描述": "背景为模糊的绿色草地,光线明亮,暗示户外环境。背景占据了画面大部分区域,从画面顶部延伸至底部,顶点坐标为[0, 0]到[1000, 1500]。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "构图方式",
+          "描述": "画面采用特写构图,聚焦于人物的上半身及其绘画工具,背景虚化,突出主体。",
+          "是否可定量": false,
+          "类型": "构图",
+          "形式ID": "形式31"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性身体向左侧倾斜约15-20度,左手自然弯曲握持调色板,右手向上抬起,手肘弯曲,握持画笔,笔尖朝向画布,呈现正在绘画的专注姿态。",
+          "是否可定量": true,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色长袖衬衫,材质呈现丝绸般的光泽和垂坠感,袖口有纽扣,腰部有收腰设计,衣物在手臂弯曲处和腰部形成数道柔和的弧形褶皱,整体风格简洁。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "人物发型不可见,被衣物和拍摄角度遮挡。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "人物手臂和手部肤色呈现自然健康的米色或浅棕色调。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "根据身体特征和着装判断为女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "手部和手臂皮肤细腻,无明显皱纹或斑点,表明人物处于青年或中年早期,年龄约为20-35岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "人物左手握持一个椭圆形木质调色板,右手握持一支绿色笔尖的画笔。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        },
+        {
+          "名称": "人物身体部位可见性",
+          "描述": "仅可见人物的上半身,包括手臂、手部和部分躯干,头部和下半身不可见。",
+          "是否可定量": false,
+          "类型": "可见性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架为木质结构,呈现棕色木纹,表面经过打磨,呈现出木材特有的光泽和细腻触感。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架为三脚架式结构,画面中可见其左侧的两根支架,呈约70-80度角倾斜,稳定支撑画布。",
+          "是否可定量": true,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架可见性",
+          "描述": "画架仅可见左侧部分,从画面左下角延伸至画面左侧中部,顶点坐标为[0, 700]到[300, 1000],占据画面约30%的宽度和20%的高度。",
+          "是否可定量": true,
+          "类型": "可见性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.3",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈矩形,边缘笔直,无毛边或破损。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布表面为纯白色,未有任何颜料覆盖。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        },
+        {
+          "名称": "画布可见性",
+          "描述": "画布仅可见左侧部分,位于画架上方,顶点坐标为[0, 400]到[300, 700],占据画面约30%的宽度和20%的高度。",
+          "是否可定量": true,
+          "类型": "可见性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.4",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈不规则的椭圆形,边缘呈平滑的弧线,无尖锐角度,中间有一个拇指孔。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板为木质,表面经过打磨,呈现出木材特有的光泽和细腻触感,颜色为深棕色。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上的颜料颜色丰富,主要包括深绿色、草绿色、湖蓝色、天蓝色、紫罗兰色、鲜红色、橘红色、柠檬黄色、纯白色、煤黑色和土棕色等多种色彩。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料以块状和涂抹状分布在调色板表面,其中绿色颜料占据了调色板中央的大部分区域,蓝色、红色、黄色和白色颜料分布在边缘和绿色颜料周围,部分颜料边缘相互渗透,形成渐变或斑驳的混合区域。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        },
+        {
+          "名称": "调色板手持方式",
+          "描述": "调色板被人物的左手从下方托起,拇指穿过调色板中间的孔洞,其余手指自然弯曲支撑调色板边缘。",
+          "是否可定量": false,
+          "类型": "方式"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.5",
+      "形式": [
+        {
+          "名称": "画笔可见性",
+          "描述": "画笔仅可见笔尖部分和部分笔杆,笔尖呈绿色,笔杆为深棕色或黑色木质,其余部分被人物手部遮挡。",
+          "是否可定量": false,
+          "类型": "可见性"
+        },
+        {
+          "名称": "画笔手持方式",
+          "描述": "画笔被人物的右手以食指和拇指轻捏笔杆中上部,中指在下方提供支撑,无名指和小指自然弯曲的姿势握持,笔尖朝向画布。",
+          "是否可定量": false,
+          "类型": "方式"
+        },
+        {
+          "名称": "画笔位置",
+          "描述": "画笔位于画布的上方,笔尖正对着画布,准备进行绘画。画笔的顶点坐标为[200, 0]到[300, 100]。",
+          "是否可定量": false,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.6",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景内容为户外草地,可见模糊的绿色植物和数个圆形或椭圆形的光斑,大小不一,暗示阳光透过树叶的缝隙洒落。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "背景以绿色为主,背景中绿色从前景的深绿色逐渐过渡到远景的浅绿色,并夹杂着少量黄绿色和蓝绿色调,呈现出草地的自然色彩。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景呈现明显的虚化效果,景深较浅,使主体人物和绘画工具更加突出。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 267 - 0
examples/restore/input/paragraphs/02_图片形式_06.json

@@ -0,0 +1,267 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落6",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面主体为一名女性,从画面左下角延伸至画面中央偏上,占据画面约1/2的面积,其背部和左臂构成画面的主要视觉重心。",
+          "是否可定量": false,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面中上部偏右侧有一个木质画架,呈棕色,由两根向后倾斜的垂直支架和一根连接两支架中部的横向支撑杆构成,形成一个稳定的A字形结构,顶部有可调节的画板固定装置,支撑着画布。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "画布",
+          "描述": "画面中央偏左侧有一块矩形画布,尺寸约为画面高度的2/3,宽度约为画面宽度的1/2,垂直放置在画架上,略微向后倾斜,其下边缘与画架的横向支撑杆齐平,上边缘接近画架顶部,上面已绘有以绿色和蓝色为主的风景画,笔触粗犷,色彩鲜艳。",
+          "是否可定量": false,
+          "类型": "物件"
+        },
+        {
+          "名称": "调色板",
+          "描述": "画面左下角,人物左手握持一个椭圆形木质调色板,上面沾满了厚重的、呈块状堆积的油彩,颜料表面呈现出湿润的光泽,主要有深绿色、浅绿色、蓝色、红色、黄色、白色等。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "画笔",
+          "描述": "画面中央,人物右手握持一支笔杆为深棕色、笔毛为扁平状的画笔,笔尖沾有少量绿色颜料,正在画布上进行绘画。",
+          "是否可定量": false,
+          "类型": "物件"
+        },
+        {
+          "名称": "背景",
+          "描述": "画面右侧和上方为模糊的绿色草地和树木背景,光线明亮,从画面右上方照射过来,使得背景的绿色草地和树木呈现出柔和的光影效果,暗示户外环境。",
+          "是否可定量": false,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "构图方式",
+          "描述": "画面采用近景构图,人物背影和绘画工具占据画面大部分区域,背景虚化,突出主体。",
+          "是否可定量": false,
+          "类型": "构图",
+          "形式ID": "形式31"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性身体略微前倾,头部向右侧转动约45度,视线聚焦在画布上,双臂抬起,左手持调色板,右手持画笔,呈绘画姿态。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色长袖衬衫,材质轻薄,领口和背部呈深V字形开口,背部开口延伸至肩胛骨中部,露出大部分背部皮肤。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有棕色长发,发丝柔顺,自然垂落至肩部以下,部分头发遮盖住右耳,无明显发饰。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性颈部和背部皮肤呈健康自然的暖白色肤色,在光线照射下略带光泽。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "人物皮肤光滑,无明显皱纹,发质良好,推测年龄在25-30岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "人物左手握持一个带有拇指孔的椭圆形木质调色板,右手握持一支笔杆为深棕色的画笔。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        },
+        {
+          "名称": "人物身体部位可见性",
+          "描述": "可见人物的背部、右侧颈部、右耳、右臂、左臂(部分)、左手、右手。",
+          "是否可定量": false,
+          "类型": "可见性"
+        },
+        {
+          "名称": "饰品类型",
+          "描述": "人物佩戴一对几何形状的金色耳环(呈不规则三角形),以及一条细长的银色项链,项链上无明显吊坠。",
+          "是否可定量": false,
+          "类型": "类型"
+        },
+        {
+          "名称": "饰品颜色",
+          "描述": "耳环为金色,项链为银色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架为浅棕色木质,表面可见清晰的天然木材纹理,无明显漆面。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架由两根垂直支架和一根水平支撑杆组成,形成一个约60度角的A字形结构,看起来非常稳定,支撑着画布。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架可见性",
+          "描述": "画架大部分可见,位于画布后方,其左侧支架被人物的左臂和身体部分遮挡,右侧支架被画布遮挡了下半部分。",
+          "是否可定量": false,
+          "类型": "可见性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.3",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈矩形。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画作内容",
+          "描述": "画布上已绘有以绿色和蓝色为主的印象派风格风景画,笔触粗犷有力,色彩鲜艳,描绘了模糊的树木和草地,画面中央似乎有一个模糊的人形轮廓。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式24"
+        },
+        {
+          "名称": "画作颜色",
+          "描述": "画作主要颜色为绿色和蓝色,辅以少量白色用于提亮,黄色用于点缀,紫色用于增加画面层次感,这些颜色以不规则的笔触分布在画面中。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "画布可见性",
+          "描述": "画布大部分可见,位于画面中央偏左侧,其右侧边缘和右上方区域被人物的右手和画笔部分遮挡。",
+          "是否可定量": false,
+          "类型": "可见性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.4",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈椭圆形。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板为木质。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上的颜料颜色包括墨绿色、草绿色、天蓝色、普鲁士蓝、朱红色、柠檬黄、纯白色、薰衣草紫和橘红色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料呈大小不一的块状堆积在调色板表面,分布不均匀,其中深绿色和浅绿色颜料占据了调色板中央和左侧的较大面积,其他颜色如蓝色、红色、黄色、白色等则分散在边缘区域。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        },
+        {
+          "名称": "调色板手持方式",
+          "描述": "调色板被人物左手握持,拇指穿过调色板左侧边缘的椭圆形孔洞,其余四指自然弯曲,托住调色板的底部,使其保持稳定。",
+          "是否可定量": false,
+          "类型": "方式"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.5",
+      "形式": [
+        {
+          "名称": "画笔手持方式",
+          "描述": "画笔被人物右手以正常握笔姿势握持,食指和拇指轻轻捏住笔杆中下部,中指、无名指和小指自然弯曲,辅助支撑笔杆,笔尖朝向画布。",
+          "是否可定量": false,
+          "类型": "方式"
+        },
+        {
+          "名称": "画笔位置",
+          "描述": "画笔位于画布的右上方区域,笔尖轻触画布表面,正在画布上绘制一笔绿色颜料,笔尖略微压扁。",
+          "是否可定量": false,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.6",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景内容为模糊的绿色草地和树木,呈现出大面积的绿色块状,树木的轮廓依稀可见,但无法辨认具体叶片或树干细节。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "背景颜色以深浅不一的绿色为主,夹杂少量棕色用于表现树干或泥土,黄色用于表现阳光照射下的高光区域,这些颜色以柔和的过渡方式融合在一起。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景呈高度虚化状态,景深模糊效果明显,使得背景景物完全模糊不清,与前景清晰的人物和绘画工具形成强烈的对比,突出了主体。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 353 - 0
examples/restore/input/paragraphs/02_图片形式_07.json

@@ -0,0 +1,353 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落7",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面右侧偏中,女性的侧脸、颈部、肩部及部分手臂占据了画面右侧约三分之二的区域,她侧身面向左侧,头部微仰,闭着眼睛,正在闻手中的白色玫瑰花。",
+          "是否可定量": false,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "玫瑰花",
+          "描述": "画面左侧偏中,一朵盛开的白色玫瑰花位于女性面部前方,花朵直径约8-10厘米,被女性双手握持,带有绿色的茎和叶子。",
+          "是否可定量": false,
+          "类型": "植物"
+        },
+        {
+          "名称": "背景",
+          "描述": "背景是模糊的深绿色草地,呈现出柔和的虚化效果,作为人物和玫瑰花的衬托,使得主体更加突出。",
+          "是否可定量": false,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "构图方式",
+          "描述": "采用特写构图,将人物的侧脸、手部和玫瑰花作为主要表现对象,背景虚化,突出主体。",
+          "是否可定量": false,
+          "类型": "构图",
+          "形式ID": "形式31"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性侧身,头部微仰,闭眼,双手捧着玫瑰花靠近鼻子,呈现出陶醉、享受的姿态。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物表情",
+          "描述": "女性闭着眼睛,嘴角微扬,面部放松,呈现出陶醉、满足的表情。",
+          "是否可定量": false,
+          "类型": "表情"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有棕色长发,发丝柔顺,大部分发丝披散在右侧肩部和背部,长度及腰。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性肤色白皙,略带暖调,面部和手部皮肤光滑细腻。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "目测为青年女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色宽松上衣,领口为V字形设计,袖口为宽松的喇叭袖设计,材质看起来轻薄飘逸。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "女性双手握持一朵白色玫瑰花。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        },
+        {
+          "名称": "指甲油颜色",
+          "描述": "女性指甲涂有亮蓝色指甲油。",
+          "是否可定量": false,
+          "类型": "颜色"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1.1",
+      "形式": [
+        {
+          "名称": "面部朝向",
+          "描述": "女性面部朝向左侧,呈现侧脸。",
+          "是否可定量": false,
+          "类型": "朝向"
+        },
+        {
+          "名称": "眼睛状态",
+          "描述": "女性双眼紧闭。",
+          "是否可定量": false,
+          "类型": "状态"
+        },
+        {
+          "名称": "鼻子形态",
+          "描述": "女性鼻子高挺,鼻梁笔直。",
+          "是否可定量": false,
+          "类型": "形态"
+        },
+        {
+          "名称": "嘴唇颜色",
+          "描述": "女性嘴唇涂有红色口红,颜色鲜艳。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "发型",
+          "描述": "女性留有棕色长发,发丝柔顺,部分发丝从耳后自然垂落至颈部,其余发丝向后披散。",
+          "是否可定量": false,
+          "类型": "样式"
+        },
+        {
+          "名称": "表情",
+          "描述": "女性闭着眼睛,嘴角微扬,面部放松,呈现出陶醉、享受的表情。",
+          "是否可定量": false,
+          "类型": "表情"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1.1.1",
+      "形式": [
+        {
+          "名称": "耳环颜色",
+          "描述": "耳环为金色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "耳环形状",
+          "描述": "耳环呈不规则的C形或半环形,边缘圆润,开口朝向下方。",
+          "是否可定量": false,
+          "类型": "形状"
+        },
+        {
+          "名称": "耳环材质",
+          "描述": "耳环材质看起来是金属。",
+          "是否可定量": false,
+          "类型": "材质"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1.1.2",
+      "形式": [
+        {
+          "名称": "项链颜色",
+          "描述": "项链为银色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "项链材质",
+          "描述": "项链材质看起来是金属。",
+          "是否可定量": false,
+          "类型": "材质"
+        },
+        {
+          "名称": "项链款式",
+          "描述": "项链为细链款式,链条由多个细小的椭圆形环扣紧密连接而成,佩戴在颈部,长度适中,刚好贴合锁骨。",
+          "是否可定量": false,
+          "类型": "款式"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1.2",
+      "形式": [
+        {
+          "名称": "手部姿态",
+          "描述": "女性双手轻轻握住玫瑰花的茎部,手指修长,指甲涂有蓝色指甲油。",
+          "是否可定量": false,
+          "类型": "姿态"
+        },
+        {
+          "名称": "指甲油颜色",
+          "描述": "女性指甲涂有亮蓝色指甲油。",
+          "是否可定量": false,
+          "类型": "颜色"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1.3",
+      "形式": [
+        {
+          "名称": "服装颜色",
+          "描述": "上衣为白色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "服装款式",
+          "描述": "上衣为宽松款式,领口和袖口设计独特,整体剪裁流畅,线条简洁。",
+          "是否可定量": false,
+          "类型": "款式"
+        },
+        {
+          "名称": "服装材质",
+          "描述": "上衣材质看起来轻薄、柔软,具有一定的垂坠感,表面略带光泽,推测为丝绸或仿真丝面料。",
+          "是否可定量": false,
+          "类型": "材质"
+        },
+        {
+          "名称": "领口设计",
+          "描述": "上衣领口为V字形设计,领口边缘有与衣身同色的细致缝线,线条流畅。",
+          "是否可定量": false,
+          "类型": "设计"
+        },
+        {
+          "名称": "袖口设计",
+          "描述": "上衣袖口为宽松的喇叭袖设计,袖口处有均匀分布的细密褶皱,增加了袖子的蓬松感。",
+          "是否可定量": false,
+          "类型": "设计"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.2",
+      "形式": [
+        {
+          "名称": "花朵颜色",
+          "描述": "玫瑰花朵为白色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式28"
+        },
+        {
+          "名称": "花朵形态",
+          "描述": "玫瑰花朵盛开,花瓣层叠,形态饱满。",
+          "是否可定量": false,
+          "类型": "形态",
+          "形式ID": "形式27"
+        },
+        {
+          "名称": "茎叶颜色",
+          "描述": "玫瑰花的茎和叶子为绿色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "茎叶形态",
+          "描述": "玫瑰花的茎部细长,叶片完整,呈椭圆形,叶脉清晰可见,颜色略深于叶片本身,呈羽状分布。",
+          "是否可定量": false,
+          "类型": "形态"
+        },
+        {
+          "名称": "手持方式",
+          "描述": "玫瑰花被女性双手轻轻握住茎部,靠近女性面部。",
+          "是否可定量": false,
+          "类型": "方式"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.2.1",
+      "形式": [
+        {
+          "名称": "花朵颜色",
+          "描述": "花朵为白色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式28"
+        },
+        {
+          "名称": "花朵形态",
+          "描述": "花朵盛开,花瓣向外舒展,形态饱满。",
+          "是否可定量": false,
+          "类型": "形态",
+          "形式ID": "形式27"
+        },
+        {
+          "名称": "花瓣层叠",
+          "描述": "花瓣层层叠叠,约有数十片,由内向外螺旋状排列,形成饱满的花型。",
+          "是否可定量": false,
+          "类型": "结构"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.2.2",
+      "形式": [
+        {
+          "名称": "茎颜色",
+          "描述": "茎部为绿色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "叶子颜色",
+          "描述": "叶子为绿色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "叶片完整性",
+          "描述": "叶片完整无缺,表面光滑,边缘无锯齿状破损,呈现出健康的生长状态。",
+          "是否可定量": false,
+          "类型": "状态"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.3",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景内容为草地。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "背景颜色为绿色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景模糊,呈现出虚化的效果,突出前景的人物和玫瑰花。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 228 - 0
examples/restore/input/paragraphs/02_图片形式_08.json

@@ -0,0 +1,228 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落8",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面右侧偏中位置,一名女性正在进行绘画,人物从画面底部向上延伸至画面约60%的高度,从画面右侧边缘向左延伸至画面约40%的宽度,其身体朝向左侧,头部略微向右转动,视线看向画布。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面左侧偏中位置,一个木质三脚画架,支撑着一块白色画布,画架的左侧支架从画面底部延伸至画面顶部约65%处,右侧支架从画面底部延伸至画面中部约50%处。画架整体宽度占据画面约20%,呈A字形。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "画面右下角,女性左手持有一个椭圆形木质调色板,调色板上沾有多种颜色的颜料,包括绿色、蓝色、黄色、红色、紫色、棕色和白色,其中绿色和蓝色颜料面积较大,分布在调色板的中央和左侧区域,呈不规则块状。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "背景",
+          "描述": "画面主体人物和画架后方是绿色的草地和茂密的树木,草地占据画面底部约40%的高度,树木占据画面顶部约60%的高度。树木种类为灌木和乔木,枝干交错,叶片茂密,部分树木可见红色小花,呈现出自然、户外的环境。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性身体侧向左前方,头部略微向右转动,视线看向画布。右手持画笔,手肘弯曲,手腕抬起,画笔尖端触及画布。左手自然下垂,手肘微弯,调色板位于左侧大腿外侧,与地面平行。双腿站立,身体重心略微偏向左侧。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一套白色长裙,上衣为宽松的蝙蝠袖设计,腰部有数条细密的横向褶皱,形成收紧效果。下半身是垂坠感强的A字形长裙,裙摆及地,有自然形成的竖向褶皱。整体服装风格简约优雅。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有棕色长发,发丝柔顺,中分,自然垂落在肩部和背部,部分发丝被耳后佩戴的金色圆形耳饰遮挡。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性面部、颈部和手臂的肤色呈现出均匀的浅米色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "视觉判断年龄在25-35岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物面部特征",
+          "描述": "女性面部轮廓柔和,眉毛细长,眼睛呈杏仁状,双眼皮,眼线细致,鼻梁挺拔,嘴唇饱满,涂有淡粉色唇彩。右耳佩戴一个金色圆形耳饰,耳饰上镶嵌有白色小珠。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式29"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "右手持一支细长的画笔,笔杆为深色木质,笔尖为白色合成纤维毛刷,沾有少量白色颜料。左手持一个椭圆形木质调色板。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架由浅棕色木材制成,表面光滑,呈现出细密的竖向木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架为三脚架结构,由三根主支架构成,其中两根支架在前方中部交叉,一根支架在后方支撑。画架中部有一个可调节高度的横梁,横梁上有一个凹槽用于放置画布。横梁下方悬挂着一条黑色帆布背带。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "画架整体呈现浅棕色,与木材的自然颜色一致。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈长方形,长宽比约为3:2,垂直放置在画架上,其长边垂直于地面,短边平行于地面。",
+          "是否可定量": true,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布表面为纯白色,未有任何颜料覆盖。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.2.2",
+      "形式": [
+        {
+          "名称": "花朵颜色",
+          "描述": "花朵为纯白色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式28"
+        },
+        {
+          "名称": "花朵形态",
+          "描述": "花朵为一朵盛开的玫瑰花,花瓣约有20-30片,层叠紧密,边缘略微卷曲,花朵直径约5-7厘米,饱满圆润。",
+          "是否可定量": true,
+          "类型": "形态",
+          "形式ID": "形式27"
+        },
+        {
+          "名称": "花朵位置",
+          "描述": "花朵放置在画架右侧横梁的右端,距离画布右下角约2厘米。",
+          "是否可定量": true,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈椭圆形,长轴约30厘米,短轴约20厘米,边缘光滑。",
+          "是否可定量": true,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板为木质,表面呈现出细密的环形木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜色的颜料,包括鲜绿色、天蓝色、柠檬黄、大红色、紫罗兰色、深棕色和纯白色,其中鲜绿色和天蓝色颜料面积较大,分布在调色板的中央和左侧区域。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料呈不规则的块状分布在调色板的表面,没有明显的规律性,各种颜色相互交错,部分绿色和黄色颜料混合形成黄绿色,部分蓝色和红色颜料混合形成紫色。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.4",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景由两部分组成:前景是修剪整齐的绿色草地,占据画面下半部分;后景是茂密的绿色树木,占据画面上半部分。树木的枝干和叶片清晰可见,部分树木之间有少量空隙,露出远处的模糊建筑轮廓或山峦。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "草地呈现鲜亮的翠绿色,树木呈现墨绿色、橄榄绿等深浅不一的绿色,整体色调以绿色为主。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景的草地和近处树木较为清晰,远处的树木和景物略显模糊,细节难以辨认,呈现出明显的景深效果。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 236 - 0
examples/restore/input/paragraphs/02_图片形式_09.json

@@ -0,0 +1,236 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落9",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面中有一名女性,背对镜头,其中心点位于画面右侧约1/3处,距离画面底部约1/4高度,正在进行绘画活动。",
+          "是否可定量": false,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面中有一个木质三脚画架,其中心点位于画面左侧约1/3处,距离画面底部约1/3高度,支撑着一块画布。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "画面中有一个椭圆形木质调色板,位于画架右侧支撑杆上,紧邻画布底部边缘,其左侧约1/3被人物右手遮挡,上面沾有多种颜色的颜料。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "背景",
+          "描述": "背景是户外草地和树木,草地呈绿色,树木茂盛,远处左侧约1/4处有一个木质凉亭,其顶部与画面中线齐平,凉亭大部分被树木遮挡,仅可见其部分结构,整体环境明亮。",
+          "是否可定量": false,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "光照效果",
+          "描述": "画面整体光线明亮,阳光从画面左上角约45度方向射入,在画面左侧和中心区域的草地和树叶上形成多个不规则形状的明亮光斑,其中最大的光斑位于画面左下角,直径约占画面宽度的1/10。",
+          "是否可定量": false,
+          "类型": "效果",
+          "形式ID": "形式30"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落9.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性背对镜头,身体向右侧倾斜约5-10度,头部向左侧倾斜约3-5度,双手抬起,似乎正在用右手持画笔在画布上绘画。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色长裙,裙子为深V领设计,V领延伸至背部中线,露背范围从肩胛骨下方延伸至腰部,裙摆宽松,垂至脚踝,腰部有系带。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有深棕色长发,发量浓密,发长及腰,发尾在腰部以下约10厘米处呈现自然的大波浪微卷,自然垂落在背部。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性的肤色呈现暖调的浅棕色,类似于小麦色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "人物为女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "根据人物的体态和发型,估计年龄在25-30岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落9.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架由木材制成,呈现原木色泽。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架为三脚支撑结构,顶部有两根可调节的画板支撑杆,呈V字形向上延伸,底部有两根横向支撑杆,分别位于画架下部约1/3和2/3处,用于增加稳定性。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "画架呈现浅棕色木质原色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落9.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布为矩形,长宽比约为1:1.45,垂直放置。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布底色为白色,上面覆盖有颜料。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        },
+        {
+          "名称": "画作内容",
+          "描述": "画作内容以绿色和蓝色为主色调,呈现出流动的、模糊的自然风景轮廓,画面右侧可见一团白色颜料,隐约勾勒出人物的头部和肩部轮廓。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式24"
+        },
+        {
+          "名称": "画作风格",
+          "描述": "画作风格融合了印象派的色彩运用和抽象派的笔触,呈现出半抽象的视觉效果。",
+          "是否可定量": false,
+          "类型": "风格",
+          "形式ID": "形式25"
+        },
+        {
+          "名称": "画作完成度",
+          "描述": "画作尚未完成,画布左上角和右下角仍有约1/4的空白区域,画面中央的景物也仅是初步上色,缺乏细节刻画。",
+          "是否可定量": false,
+          "类型": "状态",
+          "形式ID": "形式26"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落9.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板为不规则椭圆形,边缘圆润。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板为木质。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜色的颜料,主要包括绿色、蓝色、黄色、红色和白色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料呈大小不一的不规则块状分布,其中绿色和蓝色颜料块较大,位于调色板中心区域,部分已混合成新的色调,边缘散布着较小的黄色、红色和白色颜料点。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        },
+        {
+          "名称": "调色板位置",
+          "描述": "调色板位于画架中部偏右的一个小型木质托盘上,该托盘位于画布底部下方约10厘米处,被人物右手部分遮挡。",
+          "是否可定量": false,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落9.4",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景主要由大面积的绿色草地和茂盛的树木构成,远处可见一个木质凉亭,更远处,在画面左上角树木的缝隙中,隐约可见几栋高层城市建筑的模糊轮廓,呈浅灰色。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "背景颜色以绿色为主,草地呈鲜绿色,树木叶片呈深浅不一的绿色和黄绿色,天空呈明亮的浅白色,占据画面上半部分约1/3的区域,颜色均匀,无明显云朵。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景景深较浅,远处的树木和城市建筑呈现明显的虚化效果,细节模糊不清;近处的草地纹理清晰可见,凉亭的木质结构也较为锐利。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        },
+        {
+          "名称": "光斑效果",
+          "描述": "画面中共有约5-7个主要光斑,集中分布在画面左侧和中央的草地上,最大的光斑位于画面左下角,亮度极高,呈椭圆形,其余光斑较小,亮度稍低,呈不规则圆形或条状。",
+          "是否可定量": false,
+          "类型": "效果",
+          "形式ID": "形式32"
+        },
+        {
+          "名称": "建筑存在",
+          "描述": "背景中存在一个木质凉亭,位于画面左侧偏中位置,结构为四柱支撑的方形顶棚,凉亭内可见一张木质长凳,位于凉亭内部的左侧,长凳的细节因距离较远而略显模糊。",
+          "是否可定量": false,
+          "类型": "存在性"
+        }
+      ]
+    }
+  ]
+}

+ 137 - 0
examples/restore/input/paragraphs/03_图片制作点实质结果.json

@@ -0,0 +1,137 @@
+[
+  {
+    "元素ID": "元素1",
+    "元素名称": "背景",
+    "元素描述": "户外草地背景,有树木和灌木,光线明亮,自然环境",
+    "段落数量": 9,
+    "段落列表": [
+      "段落1.4",
+      "段落2.4",
+      "段落3.5",
+      "段落4.4",
+      "段落5.6",
+      "段落6.6",
+      "段落7.3",
+      "段落8.4",
+      "段落9.4"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 70.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 91.0
+  },
+  {
+    "元素ID": "元素2",
+    "元素名称": "调色板",
+    "元素描述": "椭圆形调色板,上面有多种颜色的颜料,色彩丰富,有绿色、蓝色、黄色、红色等",
+    "段落数量": 8,
+    "段落列表": [
+      "段落1.3",
+      "段落2.3",
+      "段落3.3",
+      "段落4.3",
+      "段落5.4",
+      "段落6.4",
+      "段落8.3",
+      "段落9.3"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 75.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 87.83
+  },
+  {
+    "元素ID": "元素3",
+    "元素名称": "人物",
+    "元素描述": "女性,长发,穿着白色连衣裙,正在绘画,姿态多样(站立、侧身、背对、蹲坐),手持画笔和调色板",
+    "段落数量": 6,
+    "段落列表": [
+      "段落1.1",
+      "段落2.1",
+      "段落3.1",
+      "段落4.1",
+      "段落8.1",
+      "段落9.1"
+    ],
+    "覆盖图片数": 6,
+    "出现总次数": 6,
+    "重要性得分": 95.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 66.67,
+    "覆盖率得分": 40.0,
+    "频率权重": 80.0,
+    "综合权重": 84.5
+  },
+  {
+    "元素ID": "元素4",
+    "元素名称": "画布",
+    "元素描述": "画架上的画布,上面有未完成的画作,画作内容为穿着白色连衣裙的女性背影,背景是绿色植物和蓝色花朵",
+    "段落数量": 4,
+    "段落列表": [
+      "段落1.2.1",
+      "段落2.2.1",
+      "段落3.2.1",
+      "段落9.2.1"
+    ],
+    "覆盖图片数": 4,
+    "出现总次数": 4,
+    "重要性得分": 80.0,
+    "频次基础分": 60,
+    "频次得分": 24.0,
+    "覆盖率基础分": 44.44,
+    "覆盖率得分": 26.67,
+    "频率权重": 50.67,
+    "综合权重": 59.47
+  },
+  {
+    "元素ID": "元素5",
+    "元素名称": "人物细节",
+    "元素描述": "女性的手部,指甲涂有蓝色指甲油;白色连衣裙;耳环;项链",
+    "段落数量": 4,
+    "段落列表": [
+      "段落7.1.2",
+      "段落7.1.3",
+      "段落7.1.1.1",
+      "段落7.1.1.2"
+    ],
+    "覆盖图片数": 1,
+    "出现总次数": 4,
+    "重要性得分": 95.0,
+    "频次基础分": 60,
+    "频次得分": 24.0,
+    "覆盖率基础分": 11.11,
+    "覆盖率得分": 6.67,
+    "频率权重": 30.67,
+    "综合权重": 49.97
+  },
+  {
+    "元素ID": "元素6",
+    "元素名称": "人物",
+    "元素描述": "女性,穿着白色连衣裙,手持调色板和画笔,正在绘画",
+    "段落数量": 2,
+    "段落列表": [
+      "段落5.1",
+      "段落6.1"
+    ],
+    "覆盖图片数": 2,
+    "出现总次数": 2,
+    "重要性得分": 95.0,
+    "频次基础分": 20,
+    "频次得分": 8.0,
+    "覆盖率基础分": 22.22,
+    "覆盖率得分": 13.33,
+    "频率权重": 21.33,
+    "综合权重": 43.43
+  }
+]

+ 738 - 0
examples/restore/input/paragraphs/04_图片制作点形式结果.json

@@ -0,0 +1,738 @@
+[
+  {
+    "形式ID": "形式1",
+    "形式名称": "人物",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1|人物",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2|人物",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3|人物",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4|人物",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5|人物",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6|人物",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7|人物",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8|人物",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9|人物"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 95.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 98.5
+  },
+  {
+    "形式ID": "形式2",
+    "形式名称": "人物姿态",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.1|人物姿态",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.1|人物姿态",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.1|人物姿态",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.1|人物姿态",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.1|人物姿态",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.1|人物姿态",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.1|人物姿态",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.1|人物姿态",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.1|人物姿态"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 85.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 95.5
+  },
+  {
+    "形式ID": "形式3",
+    "形式名称": "背景",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1|背景",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2|背景",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3|背景",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4|背景",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5|背景",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6|背景",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7|背景",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8|背景",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9|背景"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 70.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 91.0
+  },
+  {
+    "形式ID": "形式4",
+    "形式名称": "人物着装",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.1|人物着装",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.1|人物着装",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.1|人物着装",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.1|人物着装",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.1|人物着装",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.1|人物着装",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.1|人物着装",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.1|人物着装",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.1|人物着装"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 70.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 91.0
+  },
+  {
+    "形式ID": "形式5",
+    "形式名称": "背景内容",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.4|背景内容",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.4|背景内容",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.5|背景内容",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.4|背景内容",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.6|背景内容",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.6|背景内容",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.3|背景内容",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.4|背景内容",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.4|背景内容"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 70.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 91.0
+  },
+  {
+    "形式ID": "形式6",
+    "形式名称": "人物发型",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.1|人物发型",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.1|人物发型",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.1|人物发型",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.1|人物发型",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.1|人物发型",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.1|人物发型",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.1|人物发型",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.1|人物发型",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.1|人物发型"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 65.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 89.5
+  },
+  {
+    "形式ID": "形式7",
+    "形式名称": "背景颜色",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.4|背景颜色",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.4|背景颜色",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.5|背景颜色",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.4|背景颜色",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.6|背景颜色",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.6|背景颜色",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.3|背景颜色",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.4|背景颜色",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.4|背景颜色"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 65.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 89.5
+  },
+  {
+    "形式ID": "形式8",
+    "形式名称": "画架",
+    "出现次数": 8,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1|画架",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2|画架",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3|画架",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4|画架",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5|画架",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6|画架",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8|画架",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9|画架"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 80.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 89.33
+  },
+  {
+    "形式ID": "形式9",
+    "形式名称": "颜料颜色",
+    "出现次数": 8,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.3|颜料颜色",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.3|颜料颜色",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.3|颜料颜色",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.3|颜料颜色",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.4|颜料颜色",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.4|颜料颜色",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.3|颜料颜色",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.3|颜料颜色"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 80.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 89.33
+  },
+  {
+    "形式ID": "形式10",
+    "形式名称": "人物肤色",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.1|人物肤色",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.1|人物肤色",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.1|人物肤色",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.1|人物肤色",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.1|人物肤色",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.1|人物肤色",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.1|人物肤色",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.1|人物肤色",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.1|人物肤色"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 60.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 88.0
+  },
+  {
+    "形式ID": "形式11",
+    "形式名称": "背景清晰度",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.4|背景清晰度",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.4|背景清晰度",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.5|背景清晰度",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.4|背景清晰度",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.6|背景清晰度",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.6|背景清晰度",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.3|背景清晰度",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.4|背景清晰度",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.4|背景清晰度"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 60.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 88.0
+  },
+  {
+    "形式ID": "形式12",
+    "形式名称": "调色板",
+    "出现次数": 8,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1|调色板",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2|调色板",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3|调色板",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4|调色板",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5|调色板",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6|调色板",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8|调色板",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9|调色板"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 75.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 87.83
+  },
+  {
+    "形式ID": "形式13",
+    "形式名称": "画布形状",
+    "出现次数": 8,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.2.1|画布形状",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.2.1|画布形状",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.2.1|画布形状",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.2.1|画布形状",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.3|画布形状",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.3|画布形状",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.2.1|画布形状",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.2.1|画布形状"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 75.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 87.83
+  },
+  {
+    "形式ID": "形式14",
+    "形式名称": "颜料分布",
+    "出现次数": 8,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.3|颜料分布",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.3|颜料分布",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.3|颜料分布",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.3|颜料分布",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.4|颜料分布",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.4|颜料分布",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.3|颜料分布",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.3|颜料分布"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 75.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 87.83
+  },
+  {
+    "形式ID": "形式15",
+    "形式名称": "人物性别",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.1|人物性别",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.1|人物性别",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.1|人物性别",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.1|人物性别",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.1|人物性别",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.1|人物性别",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.1|人物性别",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.1|人物性别",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.1|人物性别"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 55.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 86.5
+  },
+  {
+    "形式ID": "形式16",
+    "形式名称": "画架结构",
+    "出现次数": 8,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.2|画架结构",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.2|画架结构",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.2|画架结构",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.2|画架结构",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.2|画架结构",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.2|画架结构",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.2|画架结构",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.2|画架结构"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 70.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 86.33
+  },
+  {
+    "形式ID": "形式17",
+    "形式名称": "调色板形状",
+    "出现次数": 8,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.3|调色板形状",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.3|调色板形状",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.3|调色板形状",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.3|调色板形状",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.4|调色板形状",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.4|调色板形状",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.3|调色板形状",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.3|调色板形状"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 70.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 86.33
+  },
+  {
+    "形式ID": "形式18",
+    "形式名称": "人物年龄",
+    "出现次数": 9,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.1|人物年龄",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.1|人物年龄",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.1|人物年龄",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.1|人物年龄",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.1|人物年龄",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.1|人物年龄",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.1|人物年龄",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.1|人物年龄",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.1|人物年龄"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 50.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 85.0
+  },
+  {
+    "形式ID": "形式19",
+    "形式名称": "人物手持物",
+    "出现次数": 7,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.1|人物手持物",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.1|人物手持物",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.1|人物手持物",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.1|人物手持物",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.1|人物手持物",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.1|人物手持物",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.1|人物手持物"
+    ],
+    "覆盖图片数": 7,
+    "出现总次数": 7,
+    "重要性得分": 78.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 77.78,
+    "覆盖率得分": 46.67,
+    "频率权重": 86.67,
+    "综合权重": 84.07
+  },
+  {
+    "形式ID": "形式20",
+    "形式名称": "画架材质",
+    "出现次数": 8,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.2|画架材质",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.2|画架材质",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.2|画架材质",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.2|画架材质",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.2|画架材质",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.2|画架材质",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.2|画架材质",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.2|画架材质"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 60.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 83.33
+  },
+  {
+    "形式ID": "形式21",
+    "形式名称": "调色板材质",
+    "出现次数": 8,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.3|调色板材质",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.3|调色板材质",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.3|调色板材质",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.3|调色板材质",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.4|调色板材质",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.4|调色板材质",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.3|调色板材质",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.3|调色板材质"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 60.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 83.33
+  },
+  {
+    "形式ID": "形式22",
+    "形式名称": "画布颜色",
+    "出现次数": 7,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.2.1|画布颜色",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.2.1|画布颜色",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.2.1|画布颜色",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.2.1|画布颜色",
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5.3|画布颜色",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.2.1|画布颜色",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.2.1|画布颜色"
+    ],
+    "覆盖图片数": 7,
+    "出现总次数": 7,
+    "重要性得分": 65.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 77.78,
+    "覆盖率得分": 46.67,
+    "频率权重": 86.67,
+    "综合权重": 80.17
+  },
+  {
+    "形式ID": "形式23",
+    "形式名称": "画架颜色",
+    "出现次数": 6,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.2|画架颜色",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.2|画架颜色",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.2|画架颜色",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.2|画架颜色",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.2|画架颜色",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.2|画架颜色"
+    ],
+    "覆盖图片数": 6,
+    "出现总次数": 6,
+    "重要性得分": 55.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 66.67,
+    "覆盖率得分": 40.0,
+    "频率权重": 80.0,
+    "综合权重": 72.5
+  },
+  {
+    "形式ID": "形式24",
+    "形式名称": "画作内容",
+    "出现次数": 5,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.2.1|画作内容",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.2.1|画作内容",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.2.1|画作内容",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6.3|画作内容",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.2.1|画作内容"
+    ],
+    "覆盖图片数": 5,
+    "出现总次数": 5,
+    "重要性得分": 85.0,
+    "频次基础分": 80,
+    "频次得分": 32.0,
+    "覆盖率基础分": 55.56,
+    "覆盖率得分": 33.33,
+    "频率权重": 65.33,
+    "综合权重": 71.23
+  },
+  {
+    "形式ID": "形式25",
+    "形式名称": "画作风格",
+    "出现次数": 4,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.2.1|画作风格",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.2.1|画作风格",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.2.1|画作风格",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.2.1|画作风格"
+    ],
+    "覆盖图片数": 4,
+    "出现总次数": 4,
+    "重要性得分": 70.0,
+    "频次基础分": 60,
+    "频次得分": 24.0,
+    "覆盖率基础分": 44.44,
+    "覆盖率得分": 26.67,
+    "频率权重": 50.67,
+    "综合权重": 56.47
+  },
+  {
+    "形式ID": "形式26",
+    "形式名称": "画作完成度",
+    "出现次数": 4,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.2.1|画作完成度",
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.2.1|画作完成度",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.2.1|画作完成度",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.2.1|画作完成度"
+    ],
+    "覆盖图片数": 4,
+    "出现总次数": 4,
+    "重要性得分": 60.0,
+    "频次基础分": 60,
+    "频次得分": 24.0,
+    "覆盖率基础分": 44.44,
+    "覆盖率得分": 26.67,
+    "频率权重": 50.67,
+    "综合权重": 53.47
+  },
+  {
+    "形式ID": "形式27",
+    "形式名称": "花朵形态",
+    "出现次数": 4,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.2.2|花朵形态",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.2|花朵形态",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.2.1|花朵形态",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.2.2|花朵形态"
+    ],
+    "覆盖图片数": 3,
+    "出现总次数": 4,
+    "重要性得分": 75.0,
+    "频次基础分": 60,
+    "频次得分": 24.0,
+    "覆盖率基础分": 33.33,
+    "覆盖率得分": 20.0,
+    "频率权重": 44.0,
+    "综合权重": 53.3
+  },
+  {
+    "形式ID": "形式28",
+    "形式名称": "花朵颜色",
+    "出现次数": 4,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.2.2|花朵颜色",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.2|花朵颜色",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7.2.1|花朵颜色",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.2.2|花朵颜色"
+    ],
+    "覆盖图片数": 3,
+    "出现总次数": 4,
+    "重要性得分": 70.0,
+    "频次基础分": 60,
+    "频次得分": 24.0,
+    "覆盖率基础分": 33.33,
+    "覆盖率得分": 20.0,
+    "频率权重": 44.0,
+    "综合权重": 51.8
+  },
+  {
+    "形式ID": "形式29",
+    "形式名称": "人物面部特征",
+    "出现次数": 3,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg|段落1.1|人物面部特征",
+      "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg|段落4.1|人物面部特征",
+      "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg|段落8.1|人物面部特征"
+    ],
+    "覆盖图片数": 3,
+    "出现总次数": 3,
+    "重要性得分": 88.0,
+    "频次基础分": 40,
+    "频次得分": 16.0,
+    "覆盖率基础分": 33.33,
+    "覆盖率得分": 20.0,
+    "频率权重": 36.0,
+    "综合权重": 51.6
+  },
+  {
+    "形式ID": "形式30",
+    "形式名称": "光照效果",
+    "出现次数": 3,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2|光照效果",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3|光照效果",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9|光照效果"
+    ],
+    "覆盖图片数": 3,
+    "出现总次数": 3,
+    "重要性得分": 75.0,
+    "频次基础分": 40,
+    "频次得分": 16.0,
+    "覆盖率基础分": 33.33,
+    "覆盖率得分": 20.0,
+    "频率权重": 36.0,
+    "综合权重": 47.7
+  },
+  {
+    "形式ID": "形式31",
+    "形式名称": "构图方式",
+    "出现次数": 3,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg|段落5|构图方式",
+      "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg|段落6|构图方式",
+      "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg|段落7|构图方式"
+    ],
+    "覆盖图片数": 3,
+    "出现总次数": 3,
+    "重要性得分": 70.0,
+    "频次基础分": 40,
+    "频次得分": 16.0,
+    "覆盖率基础分": 33.33,
+    "覆盖率得分": 20.0,
+    "频率权重": 36.0,
+    "综合权重": 46.2
+  },
+  {
+    "形式ID": "形式32",
+    "形式名称": "光斑效果",
+    "出现次数": 3,
+    "形式标识列表": [
+      "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg|段落2.4|光斑效果",
+      "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg|段落3.5|光斑效果",
+      "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg|段落9.4|光斑效果"
+    ],
+    "覆盖图片数": 3,
+    "出现总次数": 3,
+    "重要性得分": 68.0,
+    "频次基础分": 40,
+    "频次得分": 16.0,
+    "覆盖率基础分": 33.33,
+    "覆盖率得分": 20.0,
+    "频率权重": 36.0,
+    "综合权重": 45.6
+  }
+]

+ 616 - 0
examples/restore/input/paragraphs_overview.json

@@ -0,0 +1,616 @@
+{
+  "帖子名称": "写生油画",
+  "图片总数": 9,
+  "图片列表": [
+    {
+      "图片": "img_1",
+      "段落": [
+        {
+          "段落ID": "段落1",
+          "名称": "户外绘画场景",
+          "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地。",
+          "子段落": [
+            {
+              "段落ID": "段落1.1",
+              "名称": "人物",
+              "描述": "一名女性,侧身背对镜头,正在进行绘画。",
+              "子段落": [
+                {
+                  "段落ID": "段落1.1.1",
+                  "名称": "头发",
+                  "描述": "棕色长发,部分散落在肩上。"
+                },
+                {
+                  "段落ID": "段落1.1.2",
+                  "名称": "身体",
+                  "描述": "女性的躯干和手臂。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落1.1.2.1",
+                      "名称": "服装",
+                      "描述": "白色长裙,袖子宽松。"
+                    },
+                    {
+                      "段落ID": "段落1.1.2.2",
+                      "名称": "画笔",
+                      "描述": "女性右手握持的细长画笔。"
+                    },
+                    {
+                      "段落ID": "段落1.1.2.3",
+                      "名称": "调色板",
+                      "描述": "女性左手握持的椭圆形调色板,上面有多种颜料。"
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "段落ID": "段落1.2",
+              "名称": "画架",
+              "描述": "木质三脚画架,支撑着画布。",
+              "子段落": [
+                {
+                  "段落ID": "段落1.2.1",
+                  "名称": "画布",
+                  "描述": "画架上的一幅未完成的画作,描绘了一名背对镜头的女性。"
+                },
+                {
+                  "段落ID": "段落1.2.2",
+                  "名称": "玫瑰花",
+                  "描述": "画架下方放置的一朵白色玫瑰花。"
+                }
+              ]
+            },
+            {
+              "段落ID": "段落1.3",
+              "名称": "背景",
+              "描述": "远处的绿色树木和近处的草地。"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "图片": "img_2",
+      "段落": [
+        {
+          "段落ID": "段落2",
+          "名称": "户外绘画场景",
+          "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和阳光。",
+          "子段落": [
+            {
+              "段落ID": "段落2.1",
+              "名称": "人物",
+              "描述": "一名女性,背对镜头,正在进行绘画。",
+              "子段落": [
+                {
+                  "段落ID": "段落2.1.1",
+                  "名称": "头发",
+                  "描述": "棕色长发,披散在背部。"
+                },
+                {
+                  "段落ID": "段落2.1.2",
+                  "名称": "身体",
+                  "描述": "女性的躯干和手臂。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落2.1.2.1",
+                      "名称": "服装",
+                      "描述": "白色长裙,露背设计。"
+                    },
+                    {
+                      "段落ID": "段落2.1.2.2",
+                      "名称": "画笔",
+                      "描述": "女性右手握持的细长画笔。"
+                    },
+                    {
+                      "段落ID": "段落2.1.2.3",
+                      "名称": "调色板",
+                      "描述": "女性左手握持的椭圆形调色板,上面有多种颜料。"
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "段落ID": "段落2.2",
+              "名称": "画架",
+              "描述": "木质三脚画架,支撑着画布。",
+              "子段落": [
+                {
+                  "段落ID": "段落2.2.1",
+                  "名称": "画布",
+                  "描述": "画架上的一幅未完成的画作,描绘了一名背对镜头的女性。"
+                }
+              ]
+            },
+            {
+              "段落ID": "段落2.3",
+              "名称": "背景",
+              "描述": "远处的绿色树木和草地,有阳光透过树叶。"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "图片": "img_3",
+      "段落": [
+        {
+          "段落ID": "段落3",
+          "名称": "户外绘画场景",
+          "描述": "一名女性在户外草地上跪坐,使用画架和调色板进行绘画,背景是绿色的树木和远处的建筑。",
+          "子段落": [
+            {
+              "段落ID": "段落3.1",
+              "名称": "人物",
+              "描述": "一名女性,背对镜头,跪坐在草地上。",
+              "子段落": [
+                {
+                  "段落ID": "段落3.1.1",
+                  "名称": "头发",
+                  "描述": "棕色长发,披散在背部。"
+                },
+                {
+                  "段落ID": "段落3.1.2",
+                  "名称": "身体",
+                  "描述": "女性的躯干和手臂。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落3.1.2.1",
+                      "名称": "服装",
+                      "描述": "白色长裙,露背设计。"
+                    },
+                    {
+                      "段落ID": "段落3.1.2.2",
+                      "名称": "调色板",
+                      "描述": "女性左手旁放置的椭圆形调色板,上面有多种颜料。"
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "段落ID": "段落3.2",
+              "名称": "画架",
+              "描述": "木质三脚画架,支撑着画布。",
+              "子段落": [
+                {
+                  "段落ID": "段落3.2.1",
+                  "名称": "画布",
+                  "描述": "画架上的一幅未完成的画作,描绘了一名背对镜头的女性。"
+                }
+              ]
+            },
+            {
+              "段落ID": "段落3.3",
+              "名称": "背景",
+              "描述": "远处的绿色树木和草地,以及远处的城市建筑。"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "图片": "img_4",
+      "段落": [
+        {
+          "段落ID": "段落4",
+          "名称": "户外绘画场景",
+          "描述": "一名女性在户外草地上站立,使用画架和调色板进行绘画,背景是绿色的树木。",
+          "子段落": [
+            {
+              "段落ID": "段落4.1",
+              "名称": "人物",
+              "描述": "一名女性,侧身面对镜头,正在进行绘画。",
+              "子段落": [
+                {
+                  "段落ID": "段落4.1.1",
+                  "名称": "头发",
+                  "描述": "棕色长发,部分散落在肩上。"
+                },
+                {
+                  "段落ID": "段落4.1.2",
+                  "名称": "身体",
+                  "描述": "女性的躯干和手臂。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落4.1.2.1",
+                      "名称": "服装",
+                      "描述": "白色长裙,袖子宽松。"
+                    },
+                    {
+                      "段落ID": "段落4.1.2.2",
+                      "名称": "画笔",
+                      "描述": "女性右手握持的细长画笔。"
+                    },
+                    {
+                      "段落ID": "段落4.1.2.3",
+                      "名称": "调色板",
+                      "描述": "女性左手握持的椭圆形调色板,上面有多种颜料。"
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "段落ID": "段落4.2",
+              "名称": "画架",
+              "描述": "木质三脚画架,支撑着画布。",
+              "子段落": [
+                {
+                  "段落ID": "段落4.2.1",
+                  "名称": "画布",
+                  "描述": "画架上的一幅空白画布。"
+                }
+              ]
+            },
+            {
+              "段落ID": "段落4.3",
+              "名称": "背景",
+              "描述": "远处的绿色树木和草地。"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "图片": "img_5",
+      "段落": [
+        {
+          "段落ID": "段落5",
+          "名称": "户外绘画场景",
+          "描述": "画面展示了户外绘画的局部场景,主要聚焦于人物手持调色板和部分身体,以及背景的草地和画架。",
+          "子段落": [
+            {
+              "段落ID": "段落5.1",
+              "名称": "人物",
+              "描述": "画面中部的女性,穿着白色服装,正在进行绘画活动。",
+              "子段落": [
+                {
+                  "段落ID": "段落5.1.1",
+                  "名称": "手臂",
+                  "描述": "人物露出的手臂部分,包括左臂和右臂。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落5.1.1.1",
+                      "名称": "左臂",
+                      "描述": "人物左侧手臂,部分可见,手持画笔。",
+                      "子段落": [
+                        {
+                          "段落ID": "段落5.1.1.1.1",
+                          "名称": "画笔",
+                          "描述": "人物左手持有的细长画笔。"
+                        }
+                      ]
+                    },
+                    {
+                      "段落ID": "段落5.1.1.2",
+                      "名称": "右臂",
+                      "描述": "人物右侧手臂,手持调色板,佩戴手镯。",
+                      "子段落": [
+                        {
+                          "段落ID": "段落5.1.1.2.1",
+                          "名称": "手镯",
+                          "描述": "佩戴在右腕上的银色手镯。"
+                        }
+                      ]
+                    }
+                  ]
+                },
+                {
+                  "段落ID": "段落5.1.2",
+                  "名称": "服装",
+                  "描述": "人物穿着的白色长袖衬衫和裙子。"
+                },
+                {
+                  "段落ID": "段落5.1.3",
+                  "名称": "调色板",
+                  "描述": "人物右手持有的椭圆形调色板,上面沾满了各种颜色的颜料。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落5.1.3.1",
+                      "名称": "颜料",
+                      "描述": "调色板上混合的多种颜色的颜料,包括绿色、蓝色、红色等。"
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "段落ID": "段落5.2",
+              "名称": "画架",
+              "描述": "画面左侧部分可见的木质画架。"
+            },
+            {
+              "段落ID": "段落5.3",
+              "名称": "背景",
+              "描述": "画面后方的绿色草地。"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "图片": "img_6",
+      "段落": [
+        {
+          "段落ID": "段落6",
+          "名称": "户外绘画场景",
+          "描述": "一名女性在户外草地上使用画架和调色板进行绘画,特写镜头。",
+          "子段落": [
+            {
+              "段落ID": "段落6.1",
+              "名称": "人物",
+              "描述": "一名女性,侧身背对镜头,正在进行绘画。",
+              "子段落": [
+                {
+                  "段落ID": "段落6.1.1",
+                  "名称": "耳朵",
+                  "描述": "女性的右耳,佩戴金色耳环。"
+                },
+                {
+                  "段落ID": "段落6.1.2",
+                  "名称": "头发",
+                  "描述": "棕色长发,部分散落在肩上。"
+                },
+                {
+                  "段落ID": "段落6.1.3",
+                  "名称": "身体",
+                  "描述": "女性的躯干和手臂。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落6.1.3.1",
+                      "名称": "服装",
+                      "描述": "白色上衣,露背设计。"
+                    },
+                    {
+                      "段落ID": "段落6.1.3.2",
+                      "名称": "画笔",
+                      "描述": "女性右手握持的细长画笔,正在画布上作画。"
+                    },
+                    {
+                      "段落ID": "段落6.1.3.3",
+                      "名称": "调色板",
+                      "描述": "女性左手握持的椭圆形调色板,上面有多种颜料。"
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "段落ID": "段落6.2",
+              "名称": "画架",
+              "描述": "木质画架的一部分,支撑着画布。",
+              "子段落": [
+                {
+                  "段落ID": "段落6.2.1",
+                  "名称": "画布",
+                  "描述": "画架上的一幅未完成的画作,描绘了一名背对镜头的女性。"
+                }
+              ]
+            },
+            {
+              "段落ID": "段落6.3",
+              "名称": "背景",
+              "描述": "远处的绿色树木。"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "图片": "img_7",
+      "段落": [
+        {
+          "段落ID": "段落7",
+          "名称": "人物与玫瑰花",
+          "描述": "画面主体是一位女性侧身闻着一朵白色玫瑰花,背景是绿色的草地。",
+          "子段落": [
+            {
+              "段落ID": "段落7.1",
+              "名称": "人物",
+              "描述": "画面右侧一位女性的侧面特写,闭着眼睛,面部表情安详,正在闻着玫瑰花。",
+              "子段落": [
+                {
+                  "段落ID": "段落7.1.1",
+                  "名称": "头部",
+                  "描述": "女性的侧脸,闭着眼睛,表情安详,长发披肩。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落7.1.1.1",
+                      "名称": "头发",
+                      "描述": "深棕色长发,自然垂落。"
+                    },
+                    {
+                      "段落ID": "段落7.1.1.2",
+                      "名称": "面部",
+                      "描述": "女性的侧脸,闭着眼睛,鼻梁高挺,嘴唇微张,涂有红色口红。"
+                    },
+                    {
+                      "段落ID": "段落7.1.1.3",
+                      "名称": "耳饰",
+                      "描述": "右耳佩戴着一个金色的几何形状耳环。"
+                    }
+                  ]
+                },
+                {
+                  "段落ID": "段落7.1.2",
+                  "名称": "身体",
+                  "描述": "女性的肩部和部分躯干,穿着白色上衣,佩戴项链。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落7.1.2.1",
+                      "名称": "服装",
+                      "描述": "白色宽松上衣,领口设计独特。"
+                    },
+                    {
+                      "段落ID": "段落7.1.2.2",
+                      "名称": "项链",
+                      "描述": "一条细长的银色项链,佩戴在颈部。"
+                    }
+                  ]
+                },
+                {
+                  "段落ID": "段落7.1.3",
+                  "名称": "手部",
+                  "描述": "女性的双手捧着玫瑰花的茎部,指甲涂有蓝色指甲油。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落7.1.3.1",
+                      "名称": "指甲",
+                      "描述": "指甲涂有亮蓝色指甲油。"
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "段落ID": "段落7.2",
+              "名称": "玫瑰花",
+              "描述": "一朵盛开的白色玫瑰花,带有绿色的茎和叶子,被女性双手捧着。",
+              "子段落": [
+                {
+                  "段落ID": "段落7.2.1",
+                  "名称": "花朵",
+                  "描述": "一朵洁白的玫瑰花,花瓣层叠。"
+                },
+                {
+                  "段落ID": "段落7.2.2",
+                  "名称": "茎叶",
+                  "描述": "绿色的玫瑰花茎和叶子。"
+                }
+              ]
+            },
+            {
+              "段落ID": "段落7.3",
+              "名称": "背景",
+              "描述": "模糊的绿色草地。"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "图片": "img_8",
+      "段落": [
+        {
+          "段落ID": "段落8",
+          "名称": "户外绘画场景",
+          "描述": "一名女性在户外草地上站立,使用画架和调色板进行绘画,背景是绿色的树木。",
+          "子段落": [
+            {
+              "段落ID": "段落8.1",
+              "名称": "人物",
+              "描述": "一名女性,侧身面对镜头,正在进行绘画。",
+              "子段落": [
+                {
+                  "段落ID": "段落8.1.1",
+                  "名称": "头发",
+                  "描述": "棕色长发,部分散落在肩上。"
+                },
+                {
+                  "段落ID": "段落8.1.2",
+                  "名称": "身体",
+                  "描述": "女性的躯干和手臂。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落8.1.2.1",
+                      "名称": "服装",
+                      "描述": "白色长裙,袖子宽松。"
+                    },
+                    {
+                      "段落ID": "段落8.1.2.2",
+                      "名称": "画笔",
+                      "描述": "女性右手握持的细长画笔。"
+                    },
+                    {
+                      "段落ID": "段落8.1.2.3",
+                      "名称": "调色板",
+                      "描述": "女性左手握持的椭圆形调色板,上面有多种颜料。"
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "段落ID": "段落8.2",
+              "名称": "画架",
+              "描述": "木质三脚画架,支撑着画布。",
+              "子段落": [
+                {
+                  "段落ID": "段落8.2.1",
+                  "名称": "画布",
+                  "描述": "画架上的一幅空白画布。"
+                },
+                {
+                  "段落ID": "段落8.2.2",
+                  "名称": "玫瑰花",
+                  "描述": "画架下方放置的一朵白色玫瑰花。"
+                }
+              ]
+            },
+            {
+              "段落ID": "段落8.3",
+              "名称": "背景",
+              "描述": "远处的绿色树木和草地。"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "图片": "img_9",
+      "段落": [
+        {
+          "段落ID": "段落9",
+          "名称": "户外绘画场景",
+          "描述": "一名女性在户外草地上站立,使用画架进行绘画,背景是绿色的树木和远处的建筑。",
+          "子段落": [
+            {
+              "段落ID": "段落9.1",
+              "名称": "人物",
+              "描述": "一名女性,背对镜头,正在进行绘画。",
+              "子段落": [
+                {
+                  "段落ID": "段落9.1.1",
+                  "名称": "头发",
+                  "描述": "棕色长发,披散在背部。"
+                },
+                {
+                  "段落ID": "段落9.1.2",
+                  "名称": "身体",
+                  "描述": "女性的躯干和手臂。",
+                  "子段落": [
+                    {
+                      "段落ID": "段落9.1.2.1",
+                      "名称": "服装",
+                      "描述": "白色长裙,露背设计。"
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "段落ID": "段落9.2",
+              "名称": "画架",
+              "描述": "木质三脚画架,支撑着画布。",
+              "子段落": [
+                {
+                  "段落ID": "段落9.2.1",
+                  "名称": "画布",
+                  "描述": "画架上的一幅未完成的画作,描绘了风景。"
+                }
+              ]
+            },
+            {
+              "段落ID": "段落9.3",
+              "名称": "背景",
+              "描述": "远处的绿色树木和草地,以及远处的建筑和木质凉亭。"
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}

BIN
examples/restore/input/person.jpg


+ 84 - 0
examples/restore/input/readme.md

@@ -0,0 +1,84 @@
+# restore/input 目录说明
+
+本目录存放内容还原任务的输入数据,来源于帖子 `616192600000000021034642`(《秋日际遇》写生油画)的解构结果。
+
+## 文件结构
+
+```
+input/
+├── readme.md                                                    # 本文件
+├── images/                                                      # 原始图片(仅用于评估对比,严禁修改)
+│   ├── person.jpg                                               # 人物参考图
+│   ├── image_01.jpg                                             # 图片1(对应 01_图片分段_01)
+│   ├── image_02.jpg                                             # 图片2(对应 01_图片分段_02)
+│   ├── image_03.jpg                                             # 图片3(对应 01_图片分段_03)
+│   ├── image_04.jpg                                             # 图片4(对应 01_图片分段_04)
+│   ├── image_05.jpg                                             # 图片5(对应 01_图片分段_05)
+│   ├── image_06.jpg                                             # 图片6(对应 01_图片分段_06)
+│   ├── image_07.jpg                                             # 图片7(对应 01_图片分段_07)
+│   ├── image_08.jpg                                             # 图片8(对应 01_图片分段_08)
+│   └── image_09.jpg                                             # 图片9(对应 01_图片分段_09)
+├── production_script_deconstruction_20260127_134243.json         # 完整解构结果(原始文件)
+└── paragraphs/                                                  # 按段落拆分后的文件
+    ├── 00_基础信息.json                                          # 帖子ID、原始文本、内容过滤结果、元信息
+    ├── 01_图片分段_01_g1_户外绘画场景.json                        # 图片1 分段结果(group 1)
+    ├── 01_图片分段_02_g1_户外绘画场景.json                        # 图片2 分段结果(group 1)
+    ├── 01_图片分段_03_g1_户外绘画场景.json                        # 图片3 分段结果(group 1)
+    ├── 01_图片分段_04_g1_户外绘画场景.json                        # 图片4 分段结果(group 1)
+    ├── 01_图片分段_05_g2_户外绘画场景.json                        # 图片5 分段结果(group 2)
+    ├── 01_图片分段_06_g2_户外绘画场景.json                        # 图片6 分段结果(group 2)
+    ├── 01_图片分段_07_g3_人物与玫瑰花.json                        # 图片7 分段结果(group 3)
+    ├── 01_图片分段_08_g1_户外绘画场景.json                        # 图片8 分段结果(group 1)
+    ├── 01_图片分段_09_g1_户外绘画场景.json                        # 图片9 分段结果(group 1)
+    ├── 02_图片形式_01.json ~ 02_图片形式_09.json                  # 9张图片各自的形式分析(构图、色彩、光影等)
+    ├── 03_图片制作点实质结果.json                                  # 6个实质制作点(人物、背景、调色板、画布等)
+    ├── 04_图片制作点形式结果.json                                  # 32个形式制作点(姿态、着装、发型、肤色等)
+    └── 05_其他结果.json                                           # 空结果字段(段内/段间关系、文本解构等)
+```
+
+## 字段说明
+
+### 图片编号对应关系
+
+`images/` 目录中的图片编号与 `paragraphs/` 中的分段文件一一对应:
+- `image_01.jpg` ↔ `01_图片分段_01_g1_户外绘画场景.json` + `02_图片形式_01.json`
+- `image_02.jpg` ↔ `01_图片分段_02_g1_户外绘画场景.json` + `02_图片形式_02.json`
+- `image_03.jpg` ↔ `01_图片分段_03_g1_户外绘画场景.json` + `02_图片形式_03.json`
+- `image_04.jpg` ↔ `01_图片分段_04_g1_户外绘画场景.json` + `02_图片形式_04.json`
+- `image_05.jpg` ↔ `01_图片分段_05_g2_户外绘画场景.json` + `02_图片形式_05.json`
+- `image_06.jpg` ↔ `01_图片分段_06_g2_户外绘画场景.json` + `02_图片形式_06.json`
+- `image_07.jpg` ↔ `01_图片分段_07_g3_人物与玫瑰花.json` + `02_图片形式_07.json`
+- `image_08.jpg` ↔ `01_图片分段_08_g1_户外绘画场景.json` + `02_图片形式_08.json`
+- `image_09.jpg` ↔ `01_图片分段_09_g1_户外绘画场景.json` + `02_图片形式_09.json`
+
+**重要提示**:`images/` 目录中的图片仅用于评估对比,严禁修改。生成的图片应保存在 `output_*/` 目录中。
+
+### 图片分段结果(01_图片分段_*.json)
+
+每个文件对应一张图片,包含:
+- `image_url`: 图片地址
+- `group_id`: 分组编号(相似构图的图片归为同组)
+- `sections`: 分段列表,每段包含名称、描述、顶点坐标、子段落
+
+共 3 个分组:
+- group 1(7张): 户外绘画全景
+- group 2(2张): 户外绘画近景
+- group 3(1张): 人物与玫瑰花特写
+
+### 图片形式结果(02_图片形式_*.json)
+
+每个文件对应一张图片的形式分析,包含:
+- `image_url`: 图片地址
+- `form_elements`: 形式元素列表(构图方式、色彩分布、光影方向、景深等)
+
+### 图片制作点实质结果(03_图片制作点实质结果.json)
+
+跨图片聚合的实质元素,按重要性排序:
+- 人物(95分)、人物细节(95分)、画布(80分)、调色板(75分)、背景(70分)
+
+### 图片制作点形式结果(04_图片制作点形式结果.json)
+
+跨图片聚合的形式特征,共 32 项,包括:
+- 人物相关:姿态、着装、发型、肤色
+- 画面相关:构图、色彩、光影、景深
+- 道具相关:画架、颜料、调色板

+ 24 - 0
examples/restore/production.prompt

@@ -0,0 +1,24 @@
+---
+model: sonnet-4.5
+temperature: 0.3
+---
+
+$system$
+你是一个顶尖的多模态内容还原专家。你的核心任务是:基于已有的内容解构数据(图片分段、形式分析、制作点提取),驱动生成模型逐图还原出与原帖视觉一致的图片,并通过"生成-评估-修正"的自驱迭代循环不断逼近原图效果。
+但要注意在必要时执行调研,只需要简短的调研即可。可利用browser工具和search_post工具。
+$user$
+**任务目标**
+基于 `examples/restore/input/` 中的解构数据,还原一组图片内容。
+
+**输入数据说明**
+- `input/readme.md`:数据结构说明文档
+- `input/images/`:原始图片(用于参考和评估对比)
+- `input/paragraphs/`:解构数据文件
+
+**执行要求**
+请先阅读 `input/readme.md` 了解数据结构和任务背景,然后制定执行计划完成图片还原任务。(最好预先调研一下)
+
+**输出目录**
+所有输出文件保存到 `examples/restore/output_1` 目录下。
+
+请立即开始执行。

+ 276 - 0
examples/restore/prompt_generator.py

@@ -0,0 +1,276 @@
+"""
+图片还原 Prompt 生成系统
+基于解构数据自动构建高质量的图片生成prompt
+"""
+
+import json
+from pathlib import Path
+from typing import Dict, List, Any
+
+
+class PromptGenerator:
+    """Prompt生成器"""
+    
+    def __init__(self, input_dir: str = "input/paragraphs"):
+        self.input_dir = Path(input_dir)
+        self.global_elements = self._load_global_elements()
+        self.global_forms = self._load_global_forms()
+    
+    def _load_global_elements(self) -> List[Dict]:
+        """加载全局实质元素(跨图聚合)"""
+        file_path = self.input_dir / "03_图片制作点实质结果.json"
+        with open(file_path, 'r', encoding='utf-8') as f:
+            return json.load(f)
+    
+    def _load_global_forms(self) -> List[Dict]:
+        """加载全局形式特征(跨图聚合)"""
+        file_path = self.input_dir / "04_图片制作点形式结果.json"
+        with open(file_path, 'r', encoding='utf-8') as f:
+            return json.load(f)
+    
+    def _load_image_segment(self, image_num: int) -> Dict:
+        """加载指定图片的分段数据"""
+        # 查找对应的分段文件
+        pattern = f"01_图片分段_{image_num:02d}_*.json"
+        files = list(self.input_dir.glob(pattern))
+        if not files:
+            raise FileNotFoundError(f"未找到图片{image_num}的分段文件")
+        
+        with open(files[0], 'r', encoding='utf-8') as f:
+            return json.load(f)
+    
+    def _load_image_form(self, image_num: int) -> Dict:
+        """加载指定图片的形式分析"""
+        file_path = self.input_dir / f"02_图片形式_{image_num:02d}.json"
+        with open(file_path, 'r', encoding='utf-8') as f:
+            return json.load(f)
+    
+    def _extract_key_descriptions(self, segment_data: Dict, form_data: Dict) -> Dict[str, str]:
+        """提取关键描述信息"""
+        descriptions = {
+            "scene": "",
+            "person": "",
+            "person_pose": "",
+            "person_clothing": "",
+            "person_hair": "",
+            "easel": "",
+            "palette": "",
+            "background": "",
+            "details": []
+        }
+        
+        # 从分段数据提取
+        sections = segment_data.get("sections", [])
+        if sections:
+            main_section = sections[0]
+            descriptions["scene"] = main_section.get("描述", "")
+            
+            # 遍历子段落
+            for sub in main_section.get("子段落", []):
+                name = sub.get("名称", "")
+                desc = sub.get("描述", "")
+                
+                if "人物" in name:
+                    descriptions["person"] = desc
+                elif "画架" in name:
+                    descriptions["easel"] = desc
+                elif "调色板" in name:
+                    descriptions["palette"] = desc
+                elif "背景" in name:
+                    descriptions["background"] = desc
+        
+        # 从形式数据提取细节
+        form_elements = form_data.get("form_elements", [])
+        for elem_group in form_elements:
+            for form in elem_group.get("形式", []):
+                name = form.get("名称", "")
+                desc = form.get("描述", "")
+                
+                if "人物姿态" in name:
+                    descriptions["person_pose"] = desc
+                elif "人物着装" in name:
+                    descriptions["person_clothing"] = desc
+                elif "人物发型" in name:
+                    descriptions["person_hair"] = desc
+                elif name not in ["人物", "背景", "画架", "调色板"]:
+                    descriptions["details"].append(f"{name}: {desc}")
+        
+        return descriptions
+    
+    def _build_prompt_structure(self, descriptions: Dict[str, str], group_id: str) -> str:
+        """
+        构建结构化prompt
+        顺序:[主体] + [描述性属性] + [场景环境] + [光照条件] + [情感氛围] + [构图方式] + [艺术风格]
+        """
+        prompt_parts = []
+        
+        # 1. 主体描述(人物 + 道具)
+        if descriptions["person"]:
+            prompt_parts.append(descriptions["person"])
+        
+        # 2. 描述性属性(姿态、着装)
+        if descriptions["person_pose"]:
+            # 简化姿态描述,提取关键动作
+            pose_simplified = self._simplify_pose(descriptions["person_pose"])
+            prompt_parts.append(pose_simplified)
+        
+        if descriptions["person_clothing"]:
+            # 简化着装描述
+            clothing_simplified = self._simplify_clothing(descriptions["person_clothing"])
+            prompt_parts.append(clothing_simplified)
+        
+        # 3. 道具细节
+        if "g1" in group_id or "g2" in group_id:  # 户外绘画场景
+            if descriptions["easel"]:
+                prompt_parts.append(descriptions["easel"])
+            if descriptions["palette"]:
+                prompt_parts.append(descriptions["palette"])
+        
+        # 4. 场景环境
+        if descriptions["background"]:
+            prompt_parts.append(descriptions["background"])
+        
+        # 5. 光照条件(从全局形式特征推断)
+        prompt_parts.append("Natural outdoor lighting, bright and soft sunlight")
+        
+        # 6. 情感氛围
+        if "g3" in group_id:  # 人物特写
+            prompt_parts.append("Peaceful and serene atmosphere, eyes closed in contemplation")
+        else:  # 绘画场景
+            prompt_parts.append("Focused and creative atmosphere, artist at work")
+        
+        # 7. 艺术风格
+        prompt_parts.append("Photorealistic style, high quality photography, professional composition")
+        
+        # 组合成完整prompt
+        prompt = ". ".join(filter(None, prompt_parts)) + "."
+        
+        return prompt
+    
+    def _simplify_pose(self, pose_desc: str) -> str:
+        """简化姿态描述,提取关键信息"""
+        # 提取关键动作词
+        key_actions = []
+        if "站立" in pose_desc:
+            key_actions.append("standing")
+        if "侧身" in pose_desc or "侧向" in pose_desc:
+            key_actions.append("side view")
+        if "蹲" in pose_desc:
+            key_actions.append("crouching")
+        if "背对" in pose_desc:
+            key_actions.append("back view")
+        if "持画笔" in pose_desc:
+            key_actions.append("holding a paintbrush")
+        if "持调色板" in pose_desc or "托举调色板" in pose_desc:
+            key_actions.append("holding a palette")
+        
+        return ", ".join(key_actions) if key_actions else pose_desc[:100]
+    
+    def _simplify_clothing(self, clothing_desc: str) -> str:
+        """简化着装描述"""
+        # 提取关键服饰信息
+        simplified = []
+        if "白色" in clothing_desc and "连衣裙" in clothing_desc:
+            simplified.append("white dress")
+        if "长袖" in clothing_desc:
+            simplified.append("long sleeves")
+        if "V字形领口" in clothing_desc or "V领" in clothing_desc:
+            simplified.append("V-neck")
+        
+        return ", ".join(simplified) if simplified else clothing_desc[:100]
+    
+    def generate_prompt(self, image_num: int) -> Dict[str, Any]:
+        """
+        为指定图片生成prompt
+        
+        Args:
+            image_num: 图片编号 (1-9)
+        
+        Returns:
+            包含prompt和元数据的字典
+        """
+        # 加载数据
+        segment_data = self._load_image_segment(image_num)
+        form_data = self._load_image_form(image_num)
+        
+        # 确定分组
+        segment_file = list(self.input_dir.glob(f"01_图片分段_{image_num:02d}_*.json"))[0]
+        group_id = "g1"  # 默认
+        if "g2" in segment_file.name:
+            group_id = "g2"
+        elif "g3" in segment_file.name:
+            group_id = "g3"
+        
+        # 提取关键描述
+        descriptions = self._extract_key_descriptions(segment_data, form_data)
+        
+        # 构建prompt
+        prompt = self._build_prompt_structure(descriptions, group_id)
+        
+        # 确定图片尺寸(竖版)
+        size = "1024x1792"  # DALL-E 3 竖版尺寸
+        
+        return {
+            "image_num": image_num,
+            "group_id": group_id,
+            "prompt": prompt,
+            "size": size,
+            "quality": "hd",
+            "descriptions": descriptions
+        }
+    
+    def generate_all_prompts(self) -> List[Dict[str, Any]]:
+        """生成所有9张图片的prompts"""
+        prompts = []
+        for i in range(1, 10):
+            try:
+                prompt_data = self.generate_prompt(i)
+                prompts.append(prompt_data)
+                print(f"✓ 图片 {i} prompt已生成")
+            except Exception as e:
+                print(f"✗ 图片 {i} 生成失败: {e}")
+        
+        return prompts
+    
+    def save_prompts(self, prompts: List[Dict], output_file: str = "output_1/prompts.json"):
+        """保存生成的prompts到文件"""
+        output_path = Path(output_file)
+        output_path.parent.mkdir(parents=True, exist_ok=True)
+        
+        with open(output_path, 'w', encoding='utf-8') as f:
+            json.dump(prompts, f, ensure_ascii=False, indent=2)
+        
+        print(f"\n✓ Prompts已保存到: {output_path}")
+
+
+def main():
+    """主函数:生成并保存所有prompts"""
+    print("=" * 60)
+    print("图片还原 Prompt 生成系统")
+    print("=" * 60)
+    
+    # 创建生成器
+    generator = PromptGenerator()
+    
+    # 生成所有prompts
+    print("\n开始生成prompts...")
+    prompts = generator.generate_all_prompts()
+    
+    # 保存结果
+    generator.save_prompts(prompts)
+    
+    # 打印预览
+    print("\n" + "=" * 60)
+    print("Prompt 预览(前3个):")
+    print("=" * 60)
+    for i, p in enumerate(prompts[:3], 1):
+        print(f"\n图片 {i} ({p['group_id']}):")
+        print(f"Prompt: {p['prompt'][:200]}...")
+    
+    print("\n" + "=" * 60)
+    print(f"✓ 完成!共生成 {len(prompts)} 个prompts")
+    print("=" * 60)
+
+
+if __name__ == "__main__":
+    main()

+ 623 - 0
examples/restore/run.py

@@ -0,0 +1,623 @@
+"""
+示例(增强版)
+
+使用 Agent 模式 + Skills
+
+新增功能:
+1. 支持命令行随时打断(输入 'p' 暂停,'q' 退出)
+2. 暂停后可插入干预消息
+3. 支持触发经验总结
+4. 查看当前 GoalTree
+5. 框架层自动清理不完整的工具调用
+6. 支持通过 --trace <ID> 恢复已有 Trace 继续执行
+"""
+
+import argparse
+import os
+import sys
+import select
+import asyncio
+from pathlib import Path
+
+# Clash Verge TUN 模式兼容:禁止 httpx/urllib 自动检测系统 HTTP 代理
+# TUN 虚拟网卡已在网络层接管所有流量,不需要应用层再走 HTTP 代理,
+# 否则 httpx 检测到 macOS 系统代理 (127.0.0.1:7897) 会导致 ConnectError
+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.core.presets import AgentPreset, register_preset
+from agent.trace import (
+    FileSystemTraceStore,
+    Trace,
+    Message,
+)
+from agent.llm import create_openrouter_llm_call
+from agent.tools import get_tool_registry
+
+
+# ===== 非阻塞 stdin 检测 =====
+if sys.platform == 'win32':
+    import msvcrt
+
+def check_stdin() -> str | None:
+    """
+    跨平台非阻塞检查 stdin 输入。
+    Windows: 使用 msvcrt.kbhit()
+    macOS/Linux: 使用 select.select()
+    """
+    if sys.platform == 'win32':
+        # 检查是否有按键按下
+        if msvcrt.kbhit():
+            # 读取按下的字符(msvcrt.getwch 是非阻塞读取宽字符)
+            ch = msvcrt.getwch().lower()
+            if ch == 'p':
+                return 'pause'
+            if ch == 'q':
+                return 'quit'
+            # 如果是其他按键,可以选择消耗掉或者忽略
+        return None
+    else:
+        # Unix/Mac 逻辑
+        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,
+):
+    """
+    显示交互式菜单,让用户选择操作。
+
+    进入本函数前不再有后台线程占用 stdin,所以 input() 能正常工作。
+    """
+    print("\n" + "=" * 60)
+    print("  执行已暂停")
+    print("=" * 60)
+    print("请选择操作:")
+    print("  1. 插入干预消息并继续")
+    print("  2. 触发经验总结(reflect)")
+    print("  3. 查看当前 GoalTree")
+    print("  4. 手动压缩上下文(compact)")
+    print("  5. 继续执行")
+    print("  6. 停止执行")
+    print("  7. 经验库瘦身(合并相似经验)")
+    print("=" * 60)
+
+    while True:
+        choice = input("请输入选项 (1-7): ").strip()
+
+        if choice == "1":
+            text = _read_multiline()
+            if not text:
+                print("未输入任何内容,取消操作")
+                continue
+
+            print(f"\n将插入干预消息并继续执行...")
+            # 从 store 读取实际的 last_sequence,避免本地 current_sequence 过时
+            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":
+            # 触发经验总结
+            print("\n触发经验总结...")
+            focus = input("请输入反思重点(可选,直接回车跳过): ").strip()
+
+            # 触发反思
+            await perform_reflection(runner, store, trace_id, focus=focus)
+            continue
+
+        elif choice == "3":
+            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 == "4":
+            # 手动压缩上下文
+            print("\n正在执行上下文压缩(compact)...")
+            try:
+                goal_tree = await store.get_goal_tree(trace_id)
+                trace = await store.get_trace(trace_id)
+                if not trace:
+                    print("未找到 Trace,无法压缩")
+                    continue
+
+                # 重建当前 history
+                main_path = await store.get_main_path_messages(trace_id, trace.head_sequence)
+                history = [msg.to_llm_dict() for msg in main_path]
+                head_seq = main_path[-1].sequence if main_path else 0
+                next_seq = head_seq + 1
+
+                compact_config = RunConfig(trace_id=trace_id)
+                new_history, new_head, new_seq = await runner._compress_history(
+                    trace_id=trace_id,
+                    history=history,
+                    goal_tree=goal_tree,
+                    config=compact_config,
+                    sequence=next_seq,
+                    head_seq=head_seq,
+                )
+                print(f"\n✅ 压缩完成: {len(history)} 条消息 → {len(new_history)} 条")
+            except Exception as e:
+                print(f"\n❌ 压缩失败: {e}")
+            continue
+
+        elif choice == "5":
+            print("\n继续执行...")
+            return {"action": "continue"}
+
+        elif choice == "6":
+            print("\n停止执行...")
+            return {"action": "stop"}
+
+        elif choice == "7":
+            # 经验库瘦身
+            print("\n正在执行经验库瘦身...")
+            from agent.tools.builtin.experience import slim_experiences
+            try:
+                result = await slim_experiences()
+                print(f"\n{result}")
+            except Exception as e:
+                print(f"\n经验库瘦身失败: {e}")
+            continue
+
+        else:
+            print("无效选项,请重新输入")
+
+async def perform_reflection(runner: AgentRunner, store: FileSystemTraceStore, trace_id: str, focus: str = ""):
+    """执行经验总结并保存(带结构化 YAML 解析)"""
+    from agent.trace.compaction import build_reflect_prompt
+    import re as _re2
+    import uuid as _uuid2
+    from datetime import datetime
+    
+    trace = await store.get_trace(trace_id)
+    if not trace:
+        return
+    saved_head = trace.head_sequence
+
+    prompt = build_reflect_prompt()
+    if focus:
+        prompt += f"\n\n请特别关注:{focus}"
+
+    print("正在生成反思...")
+    reflect_cfg = RunConfig(trace_id=trace_id, max_iterations=1, tools=[])
+    reflection_text = ""
+    
+    try:
+        result = await runner.run_result(
+            messages=[{"role": "user", "content": prompt}],
+            config=reflect_cfg,
+        )
+        reflection_text = result.get("summary", "")
+    finally:
+        # 恢复 head_sequence(反思消息成为侧枝,不污染主对话)
+        await store.update_trace(trace_id, head_sequence=saved_head)
+
+    # 追加到 experiences 文件
+    if reflection_text:
+        experiences_path = runner.experiences_path or "./.cache/experiences_restore.md"
+        os.makedirs(os.path.dirname(experiences_path), exist_ok=True)
+
+        pattern = r"-\s*\[(?P<tags>.*?)\]\s*(?P<content>.*)"
+        matches = list(_re2.finditer(pattern, reflection_text))
+
+        structured_entries = []
+        for match in matches:
+            tags_str = match.group("tags")
+            content = match.group("content")
+
+            intent_match = _re2.search(r"intent:\s*(.*?)(?:,|$)", tags_str, _re2.IGNORECASE)
+            state_match = _re2.search(r"state:\s*(.*?)(?:,|$)", tags_str, _re2.IGNORECASE)
+
+            intents = [i.strip() for i in intent_match.group(1).split(",")] if intent_match and intent_match.group(1) else []
+            states = [s.strip() for s in state_match.group(1).split(",")] if state_match and state_match.group(1) else []
+
+            ex_id = f"ex_{datetime.now().strftime('%m%d%H%M')}_{_uuid2.uuid4().hex[:4]}"
+            
+            entry = f"---\nid: {ex_id}\ntrace_id: {trace_id}\ntags: {{intent: {intents}, state: {states}}}\nmetrics: {{helpful: 1, harmful: 0}}\ncreated_at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n---\n- {content}\n- 经验ID: [{ex_id}]"
+            structured_entries.append(entry)
+
+        if structured_entries:
+            final_output = "\n\n" + "\n\n".join(structured_entries)
+            with open(experiences_path, "a", encoding="utf-8") as f:
+                f.write(final_output)
+            print(f"\n✅ 提取了 {len(structured_entries)} 条经验,已结构化并保存到: {experiences_path}")
+            print("\n--- 反思内容(结构化后) ---")
+            print(final_output.strip())
+            print("--- 结束 ---\n")
+        else:
+            print("\n⚠️ 未能解析出符合格式的经验条目,已保存原始纯文本以供检查。")
+            header = f"\n\n---\n\n## [Raw] {trace_id} ({datetime.now().strftime('%Y-%m-%d %H:%M')})\n\n"
+            with open(experiences_path, "a", encoding="utf-8") as f:
+                f.write(header + reflection_text + "\n")
+            print(reflection_text)
+    else:
+        print("未生成反思内容")
+
+async def main():
+    # 解析命令行参数
+    parser = argparse.ArgumentParser(description="任务 (Agent 模式 + 交互增强)")
+    parser.add_argument(
+        "--trace", type=str, default=None,
+        help="已有的 Trace ID,用于恢复继续执行(不指定则新建)",
+    )
+    args = parser.parse_args()
+
+    # 路径配置
+    base_dir = Path(__file__).parent
+    project_root = base_dir.parent.parent
+    prompt_path = base_dir / "production.prompt"
+    output_dir = base_dir / "output_1"
+    output_dir.mkdir(exist_ok=True)
+
+    # 加载项目级 presets(examples/restore/presets.json)
+    presets_path = base_dir / "presets.json"
+    if presets_path.exists():
+        import json
+        with open(presets_path, "r", encoding="utf-8") as f:
+            project_presets = json.load(f)
+        for name, cfg in project_presets.items():
+            register_preset(name, AgentPreset(**cfg))
+        print(f"   - 已加载项目 presets: {list(project_presets.keys())}")
+
+    # Skills 目录(可选:用户自定义 skills)
+    # 注意:内置 skills(agent/memory/skills/)会自动加载
+    skills_dir = str(base_dir / "skills")
+
+    print("=" * 60)
+    print("mcp/skills 发现、获取、评价 分析任务 (Agent 模式 + 交互增强)")
+    print("=" * 60)
+    print()
+    print("💡 交互提示:")
+    print("   - 执行过程中输入 'p' 或 'pause' 暂停并进入交互模式")
+    print("   - 执行过程中输入 'q' 或 'quit' 停止执行")
+    print("=" * 60)
+    print()
+
+    # 1. 加载 prompt
+    print("1. 加载 prompt 配置...")
+    prompt = SimplePrompt(prompt_path)
+
+    # 2. 构建消息(仅新建时使用,恢复时消息已在 trace 中)
+    print("2. 构建任务消息...")
+    messages = prompt.build_messages()
+
+    # 3. 创建 Agent Runner(配置 skills)
+    print("3. 创建 Agent Runner...")
+    print(f"   - Skills 目录: {skills_dir}")
+    print(f"   - 模型: {prompt.config.get('model', 'sonnet-4.5')}")
+
+    # 加载自定义工具
+    print("   - 加载自定义工具: nanobanana")
+    import examples.how.tool  # 导入自定义工具模块,触发 @tool 装饰器注册
+
+    store = FileSystemTraceStore(base_path=".trace")
+    runner = AgentRunner(
+        trace_store=store,
+        llm_call=create_openrouter_llm_call(model=f"anthropic/claude-{prompt.config.get('model', 'sonnet-4.5')}"),
+        skills_dir=skills_dir,
+        experiences_path="./.cache/experiences_restore.md",
+        debug=True
+    )
+
+    # 4. 判断是新建还是恢复
+    resume_trace_id = args.trace
+    if resume_trace_id:
+        # 验证 trace 存在
+        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}")
+        print(f"   - 任务: {existing_trace.task}")
+    else:
+        print(f"4. 启动新 Agent 模式...")
+
+    print()
+
+    final_response = ""
+    current_trace_id = resume_trace_id
+    current_sequence = 0
+    should_exit = False
+
+    try:
+        # 恢复模式:不发送初始消息,只指定 trace_id 续跑
+        if resume_trace_id:
+            initial_messages = None  # None = 未设置,触发早期菜单检查
+            config = RunConfig(
+                model=f"claude-{prompt.config.get('model', 'sonnet-4.5')}",
+                temperature=float(prompt.config.get('temperature', 0.3)),
+                max_iterations=1000,
+                trace_id=resume_trace_id,
+            )
+        else:
+            initial_messages = messages
+            config = RunConfig(
+                model=f"claude-{prompt.config.get('model', 'sonnet-4.5')}",
+                temperature=float(prompt.config.get('temperature', 0.3)),
+                max_iterations=1000,
+                name="社交媒体内容解构、建构、评估任务",
+                enable_research_flow=True,  # 显式启用研究流程
+            )
+
+        while not should_exit:
+            # 如果是续跑,需要指定 trace_id
+            if current_trace_id:
+                config.trace_id = current_trace_id
+
+            # 清理上一轮的响应,避免失败后显示旧内容
+            final_response = ""
+
+            # 如果 trace 已完成/失败且没有新消息,直接进入交互菜单
+            # 注意:initial_messages 为 None 表示未设置(首次加载),[] 表示有意为空(用户选择"继续")
+            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:
+                            # 无新消息:对 failed trace 意味着重试,对 completed 意味着继续
+                            initial_messages = []
+                            config.after_sequence = None
+                        continue
+                    break
+
+                # 对 stopped/running 等非终态的 trace,直接续跑
+                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)
+
+                        # 等待一小段时间让 runner 处理 stop 信号
+                        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()
+
+            # paused → 菜单已在暂停时内联显示过
+            if paused:
+                if should_exit:
+                    break
+                continue
+
+            # quit → 直接退出
+            if should_exit:
+                break
+
+            # Runner 退出(完成/失败/停止/异常)→ 显示交互菜单
+            if current_trace_id:
+                # 🌟 新增:自动触发反思的生命周期钩子
+                check_trace = await store.get_trace(current_trace_id)
+                if check_trace and check_trace.status in ("completed", "failed"):
+                    print(f"\n⚙️ 任务已结束 (状态: {check_trace.status}),正在自动触发经验总结...")
+                    
+                    # 如果是失败状态,自动带上针对性的 focus 提示
+                    auto_focus = "本次任务执行失败了,请重点反思失败的原因、踩坑点以及未来应如何避免。" if check_trace.status == "failed" else ""
+                    
+                    await perform_reflection(runner, store, current_trace_id, focus=auto_focus)
+
+                # 自动反思结束后,依然弹出菜单,让用户决定是彻底退出(6)还是查看总结(3)
+                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)
+
+    # 6. 输出结果
+    if final_response:
+        print()
+        print("=" * 60)
+        print("Agent 响应:")
+        print("=" * 60)
+        print(final_response)
+        print("=" * 60)
+        print()
+
+        # 7. 保存结果
+        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())

BIN
examples/restore_old/input/images/img_1.jpg


BIN
examples/restore_old/input/images/img_2.jpg


BIN
examples/restore_old/input/images/img_3.jpg


BIN
examples/restore_old/input/images/img_5.jpg


BIN
examples/restore_old/input/images/img_6.jpg


BIN
examples/restore_old/input/images/img_7.jpg


BIN
examples/restore_old/input/images/img_8.jpg


BIN
examples/restore_old/input/images/img_9.jpg


+ 11 - 0
examples/restore_old/input/images/set_invariant_features.json

@@ -0,0 +1,11 @@
+{
+  "viewing_angle": 90,
+  "person_restoration": 90,
+  "props_restoration": 90,
+  "background": 80,
+  "composition": 90,
+  "weighted_total": 89,
+  "main_issues": [
+    "The background bokeh is present but the circular light spots are not as prominent or well-defined as in the original images."
+  ]
+}

+ 29 - 0
examples/restore_old/input/paragraphs/00_基础信息.json

@@ -0,0 +1,29 @@
+{
+  "帖子ID": "616192600000000021034642",
+  "原始文本": {
+    "title": "《秋日际遇》写生油画",
+    "body": "听闻秋日是倒放的春天 于是我心中有一座秋日的花园 栽种着一簇簇淡却温暖的花 风沿着远边的山吹来 热情的阳光里秋风微凉 与颜料一起酝酿出的画面 白裙是一抹无暇 迎着光绘画出 那片在我心上开满 限定的浪漫 被画架支起 绿草坪还驻留了匆匆而过的热闹 再添一笔白 为我画一枝玫瑰的奇遇 ———@万淮"
+  },
+  "内容过滤结果": {
+    "filtered_body": "听闻秋日是倒放的春天 于是我心中有一座秋日的花园 栽种着一簇簇淡却温暖的花 风沿着远边的山吹来 热情的阳光里秋风微凉 与颜料一起酝酿出的画面 白裙是一抹无暇 迎着光绘画出 那片在我心上开满 限定的浪漫 被画架支起 绿草坪还驻留了匆匆而过的热闹 再添一笔白 为我画一枝玫瑰的奇遇 ———@万淮",
+    "original_body": "听闻秋日是倒放的春天 于是我心中有一座秋日的花园 栽种着一簇簇淡却温暖的花 风沿着远边的山吹来 热情的阳光里秋风微凉 与颜料一起酝酿出的画面 白裙是一抹无暇 迎着光绘画出 那片在我心上开满 限定的浪漫 被画架支起 绿草坪还驻留了匆匆而过的热闹 再添一笔白 为我画一枝玫瑰的奇遇 ———@万淮",
+    "removed_hashtags": [],
+    "kept_hashtags": [],
+    "filter_details": []
+  },
+  "元信息": {
+    "执行时间": "2026-01-27T13:42:43.026726",
+    "模型提供商": "google_genai",
+    "最大递归深度": 10,
+    "图片数量": 9,
+    "文本元素数量": 2,
+    "跳过步骤": {
+      "图片分段": false,
+      "图片实质": true,
+      "图片形式": false,
+      "段内关系": true,
+      "段间关系": true,
+      "文本解构": false
+    }
+  }
+}

+ 215 - 0
examples/restore_old/input/paragraphs/01_图片分段_01_g1_户外绘画场景.json

@@ -0,0 +1,215 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,侧身站立,右手持画笔,左手持调色板,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              290,
+              100
+            ],
+            [
+              1200,
+              1599
+            ],
+            [
+              290,
+              1599
+            ],
+            [
+              1200,
+              100
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落1.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块画布,画布上有一幅未完成的画作。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              900,
+              1599
+            ],
+            [
+              300,
+              1599
+            ],
+            [
+              900,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "白色画布,上面绘制着一幅以绿色植物和白色裙子人物为主题的油画,画作未完成。",
+              "顶点坐标": [
+                [
+                  450,
+                  300
+                ],
+                [
+                  850,
+                  900
+                ],
+                [
+                  450,
+                  900
+                ],
+                [
+                  850,
+                  300
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落1.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "绘画",
+              "元素ID": "元素4"
+            },
+            {
+              "名称": "玫瑰花",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "一朵白色玫瑰花,放置在画架的横梁上。",
+              "顶点坐标": [
+                [
+                  300,
+                  1000
+                ],
+                [
+                  400,
+                  1100
+                ],
+                [
+                  300,
+                  1100
+                ],
+                [
+                  400,
+                  1000
+                ]
+              ],
+              "拆分推理": "玫瑰花是画架上的一个独立装饰物,具有物理独立性,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落1.2.2",
+              "实质分类": "植物装饰",
+              "形式分类": "种植"
+            }
+          ],
+          "段落ID": "段落1.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料。",
+          "顶点坐标": [
+            [
+              550,
+              1050
+            ],
+            [
+              950,
+              1450
+            ],
+            [
+              550,
+              1450
+            ],
+            [
+              950,
+              1050
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落1.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落1.4",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落1",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 186 - 0
examples/restore_old/input/paragraphs/01_图片分段_02_g1_户外绘画场景.json

@@ -0,0 +1,186 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地,阳光透过树叶洒下。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,背对镜头,右手持画笔,左手持调色板,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落2.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块画布,画布上有一幅未完成的画作。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              900,
+              1600
+            ],
+            [
+              300,
+              1600
+            ],
+            [
+              900,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "白色画布,上面绘制着一幅以绿色植物和白色裙子人物为主题的油画,画作未完成。",
+              "顶点坐标": [
+                [
+                  450,
+                  300
+                ],
+                [
+                  850,
+                  900
+                ],
+                [
+                  450,
+                  900
+                ],
+                [
+                  850,
+                  300
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落2.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "绘画",
+              "元素ID": "元素4"
+            }
+          ],
+          "段落ID": "段落2.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料。",
+          "顶点坐标": [
+            [
+              400,
+              1000
+            ],
+            [
+              600,
+              1200
+            ],
+            [
+              400,
+              1200
+            ],
+            [
+              600,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落2.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛,阳光从左上方透过树叶洒下,形成光斑。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落2.4",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落2",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 215 - 0
examples/restore_old/input/paragraphs/01_图片分段_03_g1_户外绘画场景.json

@@ -0,0 +1,215 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地,阳光透过树叶洒下。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,背对镜头,跪坐在草地上,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落3.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块画布,画布上有一幅未完成的画作。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              900,
+              1600
+            ],
+            [
+              300,
+              1600
+            ],
+            [
+              900,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "白色画布,上面绘制着一幅以绿色植物和白色裙子人物为主题的油画,画作未完成。",
+              "顶点坐标": [
+                [
+                  450,
+                  300
+                ],
+                [
+                  850,
+                  900
+                ],
+                [
+                  450,
+                  900
+                ],
+                [
+                  850,
+                  300
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落3.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "绘画",
+              "元素ID": "元素4"
+            }
+          ],
+          "段落ID": "段落3.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料,放置在草地上。",
+          "顶点坐标": [
+            [
+              400,
+              1000
+            ],
+            [
+              600,
+              1200
+            ],
+            [
+              400,
+              1200
+            ],
+            [
+              600,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落3.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "水桶",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "白色水桶,放置在草地上,旁边有画笔。",
+          "顶点坐标": [
+            [
+              700,
+              1200
+            ],
+            [
+              850,
+              1400
+            ],
+            [
+              700,
+              1400
+            ],
+            [
+              850,
+              1200
+            ]
+          ],
+          "拆分推理": "水桶是绘画辅助工具,具有物理独立性,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落3.4",
+          "实质分类": "绘画辅助用品",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛,阳光从左上方透过树叶洒下,形成光斑。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落3.5",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落3",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 185 - 0
examples/restore_old/input/paragraphs/01_图片分段_04_g1_户外绘画场景.json

@@ -0,0 +1,185 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,侧身站立,右手持画笔,左手持调色板,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              300,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落4.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块空白画布。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              700,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              700,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "空白的白色画布。",
+              "顶点坐标": [
+                [
+                  200,
+                  400
+                ],
+                [
+                  600,
+                  900
+                ],
+                [
+                  200,
+                  900
+                ],
+                [
+                  600,
+                  400
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落4.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "拍摄"
+            }
+          ],
+          "段落ID": "段落4.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料。",
+          "顶点坐标": [
+            [
+              600,
+              1000
+            ],
+            [
+              1000,
+              1400
+            ],
+            [
+              600,
+              1400
+            ],
+            [
+              1000,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落4.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落4.4",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落4",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 213 - 0
examples/restore_old/input/paragraphs/01_图片分段_05_g2_户外绘画场景.json

@@ -0,0 +1,213 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": null,
+      "描述": "一名身穿白色衬衫的女性在户外草地上绘画,手持调色板和画笔,画面聚焦于女性上半身和绘画工具。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1000,
+          0
+        ],
+        [
+          1000,
+          1500
+        ],
+        [
+          0,
+          1500
+        ]
+      ],
+      "拆分推理": "该段落是整个图像的根节点,代表了图像的整体场景。根据核心原则,首先识别内容品类为户外绘画场景,并将其作为根节点。拆分维度是场景中的核心独立物体。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "一名身穿白色长袖衬衫的女性,仅展示上半身,左手拿着调色板,右手拿着画笔,正在进行绘画。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1000,
+              0
+            ],
+            [
+              1000,
+              1500
+            ],
+            [
+              0,
+              1500
+            ]
+          ],
+          "拆分推理": "人物是场景中的核心展示对象之一,具有物理独立性和空间独立性,因此作为独立节点拆分。不继续拆分人物的身体部位,因为本创作的核心展示内容是绘画行为和工具,而非人物本身的细节。",
+          "子段落": null,
+          "段落ID": "段落5.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素6"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "木质画架,部分可见,支撑着画布。",
+          "顶点坐标": [
+            [
+              0,
+              600
+            ],
+            [
+              300,
+              600
+            ],
+            [
+              300,
+              1000
+            ],
+            [
+              0,
+              1000
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要辅助工具,具有物理独立性,因此作为独立节点拆分。画架本身不是核心展示对象,不需进一步拆分其组成部分。",
+          "子段落": null,
+          "段落ID": "段落5.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "画布",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "白色空白画布,部分可见,位于画架上。",
+          "顶点坐标": [
+            [
+              0,
+              400
+            ],
+            [
+              300,
+              400
+            ],
+            [
+              300,
+              700
+            ],
+            [
+              0,
+              700
+            ]
+          ],
+          "拆分推理": "画布是绘画的核心载体,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。画布目前是空白状态,没有需要进一步拆分的附着物。",
+          "子段落": null,
+          "段落ID": "段落5.3",
+          "实质分类": "艺术载体",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "椭圆形木质调色板,上面沾满了各种颜色的油彩,以绿色、蓝色和红色为主,被人物左手握持。",
+          "顶点坐标": [
+            [
+              290,
+              600
+            ],
+            [
+              990,
+              600
+            ],
+            [
+              990,
+              1000
+            ],
+            [
+              290,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画场景中的核心工具,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。调色板上的颜料是其附着物,但颜料本身不具备独立性,且本创作不侧重颜料的细节,因此不进一步拆分。",
+          "子段落": null,
+          "段落ID": "段落5.4",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "画笔",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "画笔笔尖部分可见,被人物右手握持,正在画布上方。",
+          "顶点坐标": [
+            [
+              200,
+              0
+            ],
+            [
+              300,
+              0
+            ],
+            [
+              300,
+              100
+            ],
+            [
+              200,
+              100
+            ]
+          ],
+          "拆分推理": "画笔是绘画场景中的核心工具,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。画笔本身不需进一步拆分。",
+          "子段落": null,
+          "段落ID": "段落5.5",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "模糊的绿色草地背景,暗示户外环境。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1000,
+              0
+            ],
+            [
+              1000,
+              1500
+            ],
+            [
+              0,
+              1500
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,但不是核心展示对象,且不具备独立性,因此作为叶子节点不继续拆分。它为核心对象提供了环境信息。",
+          "子段落": null,
+          "段落ID": "段落5.6",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落5",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 2
+}

+ 213 - 0
examples/restore_old/input/paragraphs/01_图片分段_06_g2_户外绘画场景.json

@@ -0,0 +1,213 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": null,
+      "描述": "一名身穿白色衬衫的女性在户外草地上绘画,从背后视角展示,手持调色板和画笔,正在画布上作画。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1000,
+          0
+        ],
+        [
+          1000,
+          1500
+        ],
+        [
+          0,
+          1500
+        ]
+      ],
+      "拆分推理": "该段落是整个图像的根节点,代表了图像的整体场景。根据核心原则,首先识别内容品类为户外绘画场景,并将其作为根节点。拆分维度是场景中的核心独立物体。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "一名身穿白色长袖衬衫的女性,从背后视角展示,露出部分背部,左手拿着调色板,右手拿着画笔,正在画布上作画。佩戴金色耳环和项链。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1000,
+              0
+            ],
+            [
+              1000,
+              1500
+            ],
+            [
+              0,
+              1500
+            ]
+          ],
+          "拆分推理": "人物是场景中的核心展示对象之一,具有物理独立性和空间独立性,因此作为独立节点拆分。不继续拆分人物的身体部位,因为本创作的核心展示内容是绘画行为和工具,而非人物本身的细节。",
+          "子段落": null,
+          "段落ID": "段落6.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素6"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "木质画架,支撑着画布。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              700,
+              0
+            ],
+            [
+              700,
+              400
+            ],
+            [
+              300,
+              400
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要辅助工具,具有物理独立性,因此作为独立节点拆分。画架本身不是核心展示对象,不需进一步拆分其组成部分。",
+          "子段落": null,
+          "段落ID": "段落6.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "画布",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "画布上已绘有绿色和蓝色为主的风景画,位于画架上。",
+          "顶点坐标": [
+            [
+              300,
+              200
+            ],
+            [
+              800,
+              200
+            ],
+            [
+              800,
+              800
+            ],
+            [
+              300,
+              800
+            ]
+          ],
+          "拆分推理": "画布是绘画的核心载体,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。画布上已有画作,但画作本身不具备独立性,且本创作不侧重画作的细节,因此不进一步拆分。",
+          "子段落": null,
+          "段落ID": "段落6.3",
+          "实质分类": "艺术载体",
+          "形式分类": "绘画"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "椭圆形木质调色板,上面沾满了各种颜色的油彩,以绿色、蓝色和红色为主,被人物左手握持。",
+          "顶点坐标": [
+            [
+              200,
+              600
+            ],
+            [
+              700,
+              600
+            ],
+            [
+              700,
+              1000
+            ],
+            [
+              200,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画场景中的核心工具,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。调色板上的颜料是其附着物,但颜料本身不具备独立性,且本创作不侧重颜料的细节,因此不进一步拆分。",
+          "子段落": null,
+          "段落ID": "段落6.4",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "画笔",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "画笔被人物右手握持,正在画布上作画。",
+          "顶点坐标": [
+            [
+              500,
+              400
+            ],
+            [
+              600,
+              400
+            ],
+            [
+              600,
+              500
+            ],
+            [
+              500,
+              500
+            ]
+          ],
+          "拆分推理": "画笔是绘画场景中的核心工具,具有物理独立性,是核心展示对象之一,因此作为独立节点拆分。画笔本身不需进一步拆分。",
+          "子段落": null,
+          "段落ID": "段落6.5",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": null,
+          "描述": "模糊的绿色草地和树木背景,暗示户外环境。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1000,
+              0
+            ],
+            [
+              1000,
+              1500
+            ],
+            [
+              0,
+              1500
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,但不是核心展示对象,且不具备独立性,因此作为叶子节点不继续拆分。它为核心对象提供了环境信息。",
+          "子段落": null,
+          "段落ID": "段落6.6",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落6",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 2
+}

+ 334 - 0
examples/restore_old/input/paragraphs/01_图片分段_07_g3_人物与玫瑰花.json

@@ -0,0 +1,334 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg",
+  "sections": [
+    {
+      "名称": "人物与玫瑰花",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性侧身闭眼,闻着手中的白色玫瑰花,背景是绿色的草地。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心展示内容是人物和玫瑰花,以及它们所处的环境。因此,将图片拆分为人物、玫瑰花和背景这三个主要组成部分。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "画面右侧的女性,侧脸闭眼,表情陶醉,棕色长发,佩戴金色耳环和银色项链,身穿白色上衣,双手拿着玫瑰花,指甲涂有蓝色指甲油。",
+          "顶点坐标": [
+            [
+              248,
+              20
+            ],
+            [
+              1200,
+              20
+            ],
+            [
+              1200,
+              1599
+            ],
+            [
+              248,
+              1599
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象之一,需要进一步拆分其主要组成部分,如头部、手部和服饰,以详细描述其特征。",
+          "子段落": [
+            {
+              "名称": "头部",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "女性的侧脸,闭着眼睛,鼻子高挺,嘴唇涂有红色口红,表情陶醉,棕色长发披散。",
+              "顶点坐标": [
+                [
+                  448,
+                  20
+                ],
+                [
+                  1200,
+                  20
+                ],
+                [
+                  1200,
+                  772
+                ],
+                [
+                  448,
+                  772
+                ]
+              ],
+              "拆分推理": "头部是人物的重要组成部分,包含面部特征、发型和佩饰,需要独立拆分以进行详细描述。",
+              "子段落": [
+                {
+                  "名称": "耳环",
+                  "内容类型": "图片",
+                  "内容实质": "",
+                  "描述": "女性右耳佩戴的金色几何形状耳环。",
+                  "顶点坐标": [
+                    [
+                      900,
+                      396
+                    ],
+                    [
+                      1040,
+                      396
+                    ],
+                    [
+                      1040,
+                      500
+                    ],
+                    [
+                      900,
+                      500
+                    ]
+                  ],
+                  "拆分推理": "耳环是人物头部的独立装饰物,具有物理独立性,且是视觉焦点之一,因此作为叶子节点进行描述。",
+                  "子段落": null,
+                  "段落ID": "段落7.1.1.1",
+                  "实质分类": "饰品",
+                  "形式分类": "制作",
+                  "元素ID": "元素5"
+                },
+                {
+                  "名称": "项链",
+                  "内容类型": "图片",
+                  "内容实质": "",
+                  "描述": "女性颈部佩戴的银色细链项链。",
+                  "顶点坐标": [
+                    [
+                      700,
+                      500
+                    ],
+                    [
+                      1000,
+                      500
+                    ],
+                    [
+                      1000,
+                      580
+                    ],
+                    [
+                      700,
+                      580
+                    ]
+                  ],
+                  "拆分推理": "项链是人物颈部的独立装饰物,具有物理独立性,因此作为叶子节点进行描述。",
+                  "子段落": null,
+                  "段落ID": "段落7.1.1.2",
+                  "实质分类": "饰品",
+                  "形式分类": "制作",
+                  "元素ID": "元素5"
+                }
+              ],
+              "段落ID": "段落7.1.1",
+              "实质分类": "人物身体部位",
+              "形式分类": "拍摄"
+            },
+            {
+              "名称": "手部",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "女性的双手,指甲涂有蓝色指甲油,轻轻握住玫瑰花的茎部。",
+              "顶点坐标": [
+                [
+                  248,
+                  800
+                ],
+                [
+                  680,
+                  800
+                ],
+                [
+                  680,
+                  1200
+                ],
+                [
+                  248,
+                  1200
+                ]
+              ],
+              "拆分推理": "手部是人物与玫瑰花互动的重要部分,需要独立拆分以描述其姿态和细节。",
+              "子段落": null,
+              "段落ID": "段落7.1.2",
+              "实质分类": "人物身体部位",
+              "形式分类": "拍摄",
+              "元素ID": "元素5"
+            },
+            {
+              "名称": "服饰",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "女性身穿的白色宽松上衣,领口和袖口设计独特。",
+              "顶点坐标": [
+                [
+                  400,
+                  500
+                ],
+                [
+                  1200,
+                  500
+                ],
+                [
+                  1200,
+                  1599
+                ],
+                [
+                  400,
+                  1599
+                ]
+              ],
+              "拆分推理": "服饰是人物整体形象的重要组成部分,需要独立拆分以描述其款式和颜色。",
+              "子段落": null,
+              "段落ID": "段落7.1.3",
+              "实质分类": "服装",
+              "形式分类": "制作",
+              "元素ID": "元素5"
+            }
+          ],
+          "段落ID": "段落7.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "玫瑰花",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一朵盛开的白色玫瑰花,带有绿色的茎和叶子,被女性双手握持。",
+          "顶点坐标": [
+            [
+              0,
+              300
+            ],
+            [
+              600,
+              300
+            ],
+            [
+              600,
+              1000
+            ],
+            [
+              0,
+              1000
+            ]
+          ],
+          "拆分推理": "玫瑰花是图片的核心展示对象之一,需要进一步拆分其花朵和茎叶部分,以详细描述其形态。",
+          "子段落": [
+            {
+              "名称": "花朵",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "白色玫瑰花的盛开部分,花瓣层叠。",
+              "顶点坐标": [
+                [
+                  0,
+                  300
+                ],
+                [
+                  400,
+                  300
+                ],
+                [
+                  400,
+                  600
+                ],
+                [
+                  0,
+                  600
+                ]
+              ],
+              "拆分推理": "花朵是玫瑰花的主体,是视觉焦点,因此作为叶子节点进行描述。",
+              "子段落": null,
+              "段落ID": "段落7.2.1",
+              "实质分类": "植物器官",
+              "形式分类": "种植"
+            },
+            {
+              "名称": "茎叶",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "玫瑰花的绿色茎部和叶子,叶片完整。",
+              "顶点坐标": [
+                [
+                  0,
+                  500
+                ],
+                [
+                  600,
+                  500
+                ],
+                [
+                  600,
+                  1000
+                ],
+                [
+                  0,
+                  1000
+                ]
+              ],
+              "拆分推理": "茎叶是玫瑰花的组成部分,与花朵共同构成完整的玫瑰花,因此作为叶子节点进行描述。",
+              "子段落": null,
+              "段落ID": "段落7.2.2",
+              "实质分类": "植物器官",
+              "形式分类": "种植"
+            }
+          ],
+          "段落ID": "段落7.2",
+          "实质分类": "植物",
+          "形式分类": "种植"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "模糊的绿色草地,作为人物和玫瑰花的背景。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ]
+          ],
+          "拆分推理": "背景是场景的重要组成部分,但不是核心展示对象,无需进一步拆分,作为叶子节点。",
+          "子段落": null,
+          "段落ID": "段落7.3",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落7",
+      "实质分类": "人物特写",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 3
+}

+ 214 - 0
examples/restore_old/input/paragraphs/01_图片分段_08_g1_户外绘画场景.json

@@ -0,0 +1,214 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,侧身站立,右手持画笔,左手持调色板,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              300,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落8.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块空白画布。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              700,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              700,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "空白的白色画布。",
+              "顶点坐标": [
+                [
+                  200,
+                  400
+                ],
+                [
+                  600,
+                  900
+                ],
+                [
+                  200,
+                  900
+                ],
+                [
+                  600,
+                  400
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落8.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "拍摄"
+            },
+            {
+              "名称": "玫瑰花",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "一朵白色玫瑰花,放置在画架的横梁上。",
+              "顶点坐标": [
+                [
+                  300,
+                  1000
+                ],
+                [
+                  400,
+                  1100
+                ],
+                [
+                  300,
+                  1100
+                ],
+                [
+                  400,
+                  1000
+                ]
+              ],
+              "拆分推理": "玫瑰花是画架上的一个独立装饰物,具有物理独立性,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落8.2.2",
+              "实质分类": "植物装饰",
+              "形式分类": "种植"
+            }
+          ],
+          "段落ID": "段落8.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料。",
+          "顶点坐标": [
+            [
+              600,
+              1000
+            ],
+            [
+              1000,
+              1400
+            ],
+            [
+              600,
+              1400
+            ],
+            [
+              1000,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落8.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落8.4",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落8",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 186 - 0
examples/restore_old/input/paragraphs/01_图片分段_09_g1_户外绘画场景.json

@@ -0,0 +1,186 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg",
+  "sections": [
+    {
+      "名称": "户外绘画场景",
+      "内容类型": "图片",
+      "内容实质": "",
+      "描述": "一名女性在户外草地上使用画架和调色板进行绘画,背景是绿色的树木和草地,阳光透过树叶洒下。",
+      "顶点坐标": [
+        [
+          0,
+          0
+        ],
+        [
+          1200,
+          0
+        ],
+        [
+          1200,
+          1600
+        ],
+        [
+          0,
+          1600
+        ]
+      ],
+      "拆分推理": "这张图片的核心内容是户外绘画场景,因此将其拆分为人物、画架、调色板和背景等独立元素。",
+      "子段落": [
+        {
+          "名称": "人物",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "一名长发女性,身穿白色长裙,背对镜头,站立在草地上,正在画布上绘画。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "人物是图片的核心展示对象,需要独立拆分。其姿态、衣着和动作是其主要特征,因此不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落9.1",
+          "实质分类": "人物主体",
+          "形式分类": "拍摄",
+          "元素ID": "元素3"
+        },
+        {
+          "名称": "画架",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "木质三脚画架,支撑着一块画布,画布上有一幅未完成的画作。",
+          "顶点坐标": [
+            [
+              300,
+              0
+            ],
+            [
+              900,
+              1600
+            ],
+            [
+              300,
+              1600
+            ],
+            [
+              900,
+              0
+            ]
+          ],
+          "拆分推理": "画架是绘画场景中的重要组成部分,具有物理独立性,因此作为独立节点。画布是画架的附着物,但作为核心展示对象的一部分,需要进一步拆分。",
+          "子段落": [
+            {
+              "名称": "画布",
+              "内容类型": "图片",
+              "内容实质": "",
+              "描述": "白色画布,上面绘制着一幅以绿色植物和白色裙子人物为主题的油画,画作未完成。",
+              "顶点坐标": [
+                [
+                  450,
+                  300
+                ],
+                [
+                  850,
+                  900
+                ],
+                [
+                  450,
+                  900
+                ],
+                [
+                  850,
+                  300
+                ]
+              ],
+              "拆分推理": "画布是画架上承载画作的核心部分,是绘画行为的直接对象,因此作为画架的子节点独立拆分。",
+              "子段落": [],
+              "段落ID": "段落9.2.1",
+              "实质分类": "艺术载体",
+              "形式分类": "绘画",
+              "元素ID": "元素4"
+            }
+          ],
+          "段落ID": "段落9.2",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄"
+        },
+        {
+          "名称": "调色板",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "椭圆形木质调色板,上面沾有多种颜色的颜料,放置在草地上。",
+          "顶点坐标": [
+            [
+              400,
+              1000
+            ],
+            [
+              600,
+              1200
+            ],
+            [
+              400,
+              1200
+            ],
+            [
+              600,
+              1000
+            ]
+          ],
+          "拆分推理": "调色板是绘画工具,具有物理独立性,是人物手持的核心对象,因此作为独立节点。",
+          "子段落": [],
+          "段落ID": "段落9.3",
+          "实质分类": "绘画工具",
+          "形式分类": "拍摄",
+          "元素ID": "元素2"
+        },
+        {
+          "名称": "背景",
+          "内容类型": "图片",
+          "内容实质": "",
+          "描述": "户外草地和树木,草地呈绿色,树木茂盛,阳光从左上方透过树叶洒下,形成光斑。远处有一个木质凉亭。",
+          "顶点坐标": [
+            [
+              0,
+              0
+            ],
+            [
+              1200,
+              1600
+            ],
+            [
+              0,
+              1600
+            ],
+            [
+              1200,
+              0
+            ]
+          ],
+          "拆分推理": "背景是场景的组成部分,不属于核心展示对象,因此作为一个整体节点,不进一步拆分。",
+          "子段落": [],
+          "段落ID": "段落9.4",
+          "实质分类": "自然环境",
+          "形式分类": "拍摄",
+          "元素ID": "元素1"
+        }
+      ],
+      "段落ID": "段落9",
+      "实质分类": "艺术创作场景",
+      "形式分类": "拍摄"
+    }
+  ],
+  "group_id": 1
+}

+ 249 - 0
examples/restore_old/input/paragraphs/02_图片形式_01.json

@@ -0,0 +1,249 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落1",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面右侧偏中位置,一名长发女性,身穿白色长裙,侧身站立,面向左前方,右手持画笔,左手持调色板,正在画布上绘画。人物区域为从左侧290像素到右侧1200像素,从顶部100像素到底部1599像素。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面左侧偏中位置,一个木质三脚画架,呈浅棕色,支撑着一块画布。画架的外接矩形区域为从左侧300像素到右侧900像素,从顶部0像素到底部1599像素。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "画面右下角,女性左手持有一个椭圆形木质调色板,上面沾有多种颜色的颜料。调色板的外接矩形区域为从左侧550像素到右侧950像素,从顶部1050像素到底部1450像素。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "背景",
+          "描述": "背景占据了整个画面,其范围为从左侧0像素到右侧1200像素,从顶部0像素到底部1600像素。主要由户外草地和树木组成,草地呈绿色,树木茂盛,位于人物和画架后方。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性身体略微向左前方倾斜,头部向左转,目光聚焦在画布上。右手持画笔,笔尖触及画布,左手托举调色板,手臂自然弯曲。身体呈侧身站立姿态,右腿略微弯曲,左腿伸直,裙摆略微向右后方飘动,呈现出轻盈感。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色长袖连衣裙,材质轻薄,有垂坠感。裙子领口为V字形,袖子宽松,裙摆较长,覆盖至小腿或脚踝处。裙子背面有两条与裙子同色同材质的细带,位于背部上方,连接领口,呈平行状。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有长发,发色为深棕色,发丝柔顺,自然披散在背部和右肩。右侧耳后发丝向右上方轻微飘起。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性的肤色呈现自然健康的浅米色调,手臂和颈部皮肤质感细腻。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": true,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "视觉估计人物年龄为20-30岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物面部特征",
+          "描述": "女性面部侧向画布,仅能看到右侧脸颊和部分下颌线。右耳佩戴一个金色几何形状耳环,耳环呈开口向下的半圆形,两端略微向内弯曲。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式29"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "女性右手持一支细长的黑色画笔,笔尖为深色。左手托举一个椭圆形木质调色板。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架由木材制成,呈现浅棕色木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架为三脚架结构,由三根木质支架组成,其中两根支架在前方支撑画布,一根支架在后方提供稳定性。画架顶部有一个可调节高度的木质横梁,用于固定画布上方。画架中部有一个黑色旋钮,用于调节画布的倾斜角度和高度。画架左侧支架上挂有一个黑色条状布带,松散地垂下。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "画架主体为浅棕色木质原色,调节旋钮为黑色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈矩形,垂直放置,长边垂直于地面。画布的顶点坐标为[450, 300](左上), [850, 300](右上), [850, 900](右下), [450, 900](左下)。",
+          "是否可定量": true,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布底色为白色,但大部分区域已被颜料覆盖,呈现出绿色、蓝色、白色等多种颜色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        },
+        {
+          "名称": "画作内容",
+          "描述": "画作描绘了一个以绿色植物和蓝色花朵为背景的场景,画面中央有一位身穿白色裙子的女性背影,背对观众,头部略微向左转,长发披肩,裙摆飘逸,与正在绘画的女性着装相似。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式24"
+        },
+        {
+          "名称": "画作风格",
+          "描述": "画作笔触粗犷,色彩饱和度高,通过明暗对比表现光影,具有印象派的典型特征。",
+          "是否可定量": false,
+          "类型": "风格",
+          "形式ID": "形式25"
+        },
+        {
+          "名称": "画作完成度",
+          "描述": "画作已完成大部分,画布左上角和右下角有少量留白,笔触未完全覆盖,显示为未完成状态。",
+          "是否可定量": false,
+          "类型": "状态",
+          "形式ID": "形式26"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.2.2",
+      "形式": [
+        {
+          "名称": "花朵颜色",
+          "描述": "花朵为纯白色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式28"
+        },
+        {
+          "名称": "花朵形态",
+          "描述": "花朵为一朵盛开的玫瑰花,花瓣层叠,形态饱满,花茎带有绿色叶片。",
+          "是否可定量": false,
+          "类型": "形态",
+          "形式ID": "形式27"
+        },
+        {
+          "名称": "花朵位置",
+          "描述": "花朵放置在画架左侧的横梁上,花头朝向左下方,花茎和叶片沿着横梁延伸。花朵的外接矩形区域为从左侧300像素到右侧400像素,从顶部1000像素到底部1100像素。",
+          "是否可定量": true,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈不规则的椭圆形,边缘圆润,左侧有一个直径约2厘米的圆形拇指孔。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板由木材制成,呈现浅棕色木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜色的颜料,包括绿色、蓝色、白色、粉色、黄色、红色、棕色等,颜色鲜艳。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料以块状或条状分布在调色板的边缘和中央区域。调色板中央区域有一大块混合的浅绿色颜料,边缘区域有混合的灰蓝色颜料。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落1.4",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景主要由大面积的绿色草地和茂密的树木组成。草地平坦,树木高大,枝叶繁茂,形成自然的绿色屏障。画面左侧远处有一片模糊的蓝色区域,可能是水面或天空。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "草地呈鲜亮的绿色,树木的叶子呈深浅不一的绿色。远处背景颜色轻微模糊,呈现偏蓝的灰绿色调。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景的清晰度低于前景的人物和画架,呈现出一定的景深模糊效果,使得前景主体更加突出。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 231 - 0
examples/restore_old/input/paragraphs/02_图片形式_02.json

@@ -0,0 +1,231 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落2",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "人物主体占据画面左侧约1/3区域,其中心线位于画面中心线左侧约1/8处,背对镜头,正在进行绘画活动。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画架位于画面右侧约1/3区域,其中心线位于画面中心线右侧约1/8处,木质三脚架结构,支撑着一块画布。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "调色板位于人物左手下方,紧贴其左侧大腿外侧,部分被裙摆遮挡,椭圆形,上面沾有多种颜色的颜料。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "背景",
+          "描述": "草地占据画面下方约1/3区域,树木占据画面上方约2/3区域,整体呈现户外自然环境。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "光照效果",
+          "描述": "阳光从画面左上方约1/4区域透过树叶洒下,形成明亮的光斑和光晕效果,使画面整体呈现逆光和温暖的氛围。",
+          "是否可定量": true,
+          "类型": "效果",
+          "形式ID": "形式30"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落2.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性站立,身体向右倾斜约15度,背对镜头,头部向右转约30度,面向画架和画布,右手持画笔伸向画布,左手持调色板。",
+          "是否可定量": true,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "身穿一件白色长裙,裙子款式宽松,背部有V字形开口,开口深度约至腰部,腰部有系带,裙摆自然垂坠,长度及小腿中部。",
+          "是否可定量": true,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "长发,发色为棕色,长度及腰部,自然垂落在背部,发尾有轻微的内扣卷曲。",
+          "是否可定量": true,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "手臂和颈部露出的皮肤呈现浅麦色,在阳光下反射出柔和的光泽。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": true,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "无法从画面中精确判断人物年龄,但其身形和着装显示为成年女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "右手持一支细长的画笔,笔杆为黑色,笔尖沾有颜料;左手持一块椭圆形调色板。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落2.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "木质,呈现原木的浅棕色,带有木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "三脚架结构,由三根木杆通过金属铰链连接支撑,顶部有可调节高度的画板托,画板托上放置着画布。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "浅棕色木质原色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落2.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "矩形,长宽比约为4:3,长边垂直于地面,宽边平行于地面。",
+          "是否可定量": true,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布大部分区域被颜料覆盖,仅在边缘和局部细节处可见少量白色底色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        },
+        {
+          "名称": "画作内容",
+          "描述": "画作描绘了一个穿着白色裙子、背对观众的女性人物形象,背景是绿色植物和零星的紫色小花,整体风格偏向印象派,色彩明亮。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式24"
+        },
+        {
+          "名称": "画作风格",
+          "描述": "印象派风格,笔触粗犷可见,色彩鲜明且饱和度较高,注重光影效果。",
+          "是否可定量": false,
+          "类型": "风格",
+          "形式ID": "形式25"
+        },
+        {
+          "名称": "画作完成度",
+          "描述": "画作已初具雏形,人物和背景的轮廓和主要色彩已完成,但人物面部和背景植物的纹理细节仍有待完善,属于未完成状态。",
+          "是否可定量": false,
+          "类型": "状态",
+          "形式ID": "形式26"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落2.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "椭圆形,边缘经过打磨,触感光滑。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "木质,呈现深棕色,表面有使用颜料留下的痕迹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜料,主要包括草绿色、天蓝色、柠檬黄、赭石色等,颜色混合在一起,形成不规则的色块。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料不规则地分布在调色板的表面,呈现出多处颜料堆积和混合的痕迹,表明其已被频繁使用。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落2.4",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "画面下半部分是修剪整齐、平坦的绿色草地,画面上半部分是茂盛的、叶片繁密的绿色树木,树木之间可见明亮的天空。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "草地呈现鲜亮的翠绿色,树木呈现深浅不一的墨绿色和橄榄绿,天空呈现明亮的乳白色和淡黄色,整体色调以绿色为主。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景整体呈现明显的虚化效果,尤其是树木和远处的景物,焦点集中在人物和画架上,背景景物轮廓模糊。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        },
+        {
+          "名称": "光斑效果",
+          "描述": "画面左上方和中央偏上区域有多个明显的圆形和不规则形状的光斑,大小不一,呈现明亮的白色和黄色,是阳光透过树叶形成的散射光。",
+          "是否可定量": false,
+          "类型": "效果",
+          "形式ID": "形式32"
+        }
+      ]
+    }
+  ]
+}

+ 265 - 0
examples/restore_old/input/paragraphs/02_图片形式_03.json

@@ -0,0 +1,265 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落3",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "人物主体位于画面下半部分,占据画面高度约1/2,宽度约1/3,其中心点偏向画面左侧。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画架位于画面中央偏右,其底部约在画面高度的1/3处,顶部接近画面顶部,宽度约占画面1/5。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "调色板位于画面左下角,紧邻人物左侧,其最宽处约占画面宽度的1/8。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "水桶",
+          "描述": "水桶位于画面右下角,紧邻画架右侧底部,其高度约占画面高度的1/10。",
+          "是否可定量": true,
+          "类型": "物件"
+        },
+        {
+          "名称": "背景",
+          "描述": "近处的绿色草地占据画面下半部分约1/3,远处的绿色树木占据画面上半部分约2/3。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "光照效果",
+          "描述": "阳光从画面左上方透过树叶洒下,在草地和树叶上形成约5-7个圆形或不规则形状的明亮光斑,直径约占画面宽度的1/20到1/10,主要分布在画面左侧和中央区域。",
+          "是否可定量": true,
+          "类型": "效果",
+          "形式ID": "形式30"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性背对镜头,双膝跪坐在草地上,身体略微前倾,与地面形成约70-80度的夹角,头部和上半身朝向画架,双手似乎正在进行绘画动作。",
+          "是否可定量": true,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "身穿一件白色长袖连衣裙,裙子材质轻薄,有自然垂坠感,后背呈深V字形开口,开口延伸至腰部上方约10-15厘米处,露出大部分背部,腰部有系带设计。",
+          "是否可定量": true,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "长发,呈深棕色,自然披散在背部,发尾微卷,长度及腰部下方约5-10厘米处。",
+          "是否可定量": true,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "从露出的背部和手臂部分看,肤色呈中等偏深的暖棕色调。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "无法从画面中精确判断人物年龄。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架主体为木质,呈现原木色,表面纹理细腻。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "采用经典的三脚架结构,由三根木质支架支撑,顶部有两根可调节的画板支撑杆,通过旋钮固定,画板放置在两根横向支撑条上。",
+          "是否可定量": true,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "画架整体呈浅棕色木质原色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈长方形,长宽比约为3:2,竖向放置,长边垂直于地面。",
+          "是否可定量": true,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布底色为白色,但大部分区域已被颜料覆盖。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        },
+        {
+          "名称": "画作内容",
+          "描述": "画作描绘了一名身穿白色裙子的女性背影,站在一片绿色植物和蓝色花朵的田野中,女性右手持一把白色遮阳伞,左手似乎拿着一个白色或浅色圆形物体,形状类似帽子或花束。背景有模糊的绿色树木和白色建筑轮廓。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式24"
+        },
+        {
+          "名称": "画作风格",
+          "描述": "画作笔触明显,色彩饱和度较高,光影表现突出。",
+          "是否可定量": false,
+          "类型": "风格",
+          "形式ID": "形式25"
+        },
+        {
+          "名称": "画作完成度",
+          "描述": "画作主体形象清晰,背景细节模糊,整体画面已具雏形。",
+          "是否可定量": false,
+          "类型": "状态",
+          "形式ID": "形式26"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈不规则的椭圆形,一侧边缘略有凹陷,另一侧较为平直,边缘圆润。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板为木质,表面可见木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜料,包括天蓝色、草绿色、柠檬黄、朱红色等,颜色鲜艳饱和。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料呈不规则的块状分布在调色板的边缘和中央区域,约有5-7个主要色块,部分颜色相互混合形成过渡色。",
+          "是否可定量": true,
+          "类型": "分布",
+          "形式ID": "形式14"
+        },
+        {
+          "名称": "调色板位置",
+          "描述": "调色板放置在人物左手边的草地上,靠近人物身体,位于画面左下角区域。",
+          "是否可定量": false,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.4",
+      "形式": [
+        {
+          "名称": "水桶颜色",
+          "描述": "水桶主体呈白色,桶身印有黑色手写体英文字母,内容为“For the love of flowers”,字体大小适中,占据桶身高度约1/3。",
+          "是否可定量": true,
+          "类型": "颜色"
+        },
+        {
+          "名称": "水桶形状",
+          "描述": "水桶呈圆柱形,带有提手,桶口直径约比桶底直径宽1/5。",
+          "是否可定量": true,
+          "类型": "形状"
+        },
+        {
+          "名称": "水桶位置",
+          "描述": "水桶放置在人物右侧的草地上,靠近画架右下角,位于画面右下角区域。",
+          "是否可定量": false,
+          "类型": "位置"
+        },
+        {
+          "名称": "画笔存在",
+          "描述": "水桶旁边可见两根画笔,笔杆呈深棕色,其中一根笔头浸入水桶中,另一根笔头靠近水桶边缘。",
+          "是否可定量": true,
+          "类型": "存在性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落3.5",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景由近处的绿色草地和远处的茂盛树木组成。草地平坦,覆盖画面下半部分。树木高大,枝叶繁茂,形成一道绿色的屏障,占据画面上半部分。远景右侧可见约2-3个模糊的白色建筑轮廓,呈长方形或方形。",
+          "是否可定量": true,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "草地呈翠绿色,树木呈墨绿、橄榄绿等深浅不一的绿色,整体色调以绿色为主,与阳光形成暖色调对比。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景整体呈现明显的虚化效果,景深较浅,远处的树木和建筑细节模糊不清,突出前景人物和画作。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        },
+        {
+          "名称": "光斑效果",
+          "描述": "阳光从画面左上方透过树叶缝隙洒下,在草地和树叶上形成约5-7个圆形或不规则形状的明亮光斑,光斑直径约占画面宽度的1/20到1/10,主要分布在画面左侧和中央区域,光斑边缘柔和,呈现出温暖的光线氛围。",
+          "是否可定量": true,
+          "类型": "效果",
+          "形式ID": "形式32"
+        }
+      ]
+    }
+  ]
+}

+ 203 - 0
examples/restore_old/input/paragraphs/02_图片形式_04.json

@@ -0,0 +1,203 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落4",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面右侧偏中位置,一名长发女性,身穿白色长裙,侧身站立,面向左侧,右手持画笔,左手持调色板,正在画布上绘画。人物头部位于画面上方约1/4处,脚部位于画面下方约1/5处。身体躯干占据画面中央偏右区域。女性身体略微向左倾斜,头部微抬,目光看向左前方画布方向。右手持画笔,手臂微弯,画笔尖端指向画布。左手托举调色板,手臂自然弯曲。双腿被长裙遮挡,但从整体姿态判断,应为站立姿态。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面左侧偏中位置,一个木质三脚画架,支撑着一块空白画布。画架呈浅棕色,由三根支架构成,其中两根支架从画面左下角延伸至画面中部,另一根支架在画面左侧中部。画架左侧边缘位于画面左侧约1/10处,右侧边缘位于画面中央偏左。画架顶部位于画面上方约1/5处,底部位于画面下方约1/10处。画架为三脚架结构,由三根主支架构成,其中两根支架从地面向上倾斜支撑画布,另一根支架在后方提供稳定。画架中部有一个可调节高度的横杆,用于放置画布,横杆下方有一个木质托盘。画架中部有一个黑色皮质绑带,用于收纳或携带。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "人物左手持有的椭圆形木质调色板,位于画面右侧中部偏下,上面沾有多种颜色的颜料,包括绿色、蓝色、红色、黄色、白色等,颜料呈不规则块状分布。调色板最长直径约为人物头部宽度的1.5倍。调色板呈不规则椭圆形,边缘圆润。颜料呈不规则块状分布在调色板表面,绿色颜料集中在调色板中央,其他颜色颜料散布在边缘。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "背景",
+          "描述": "画面后方是户外草地和树木。草地呈绿色,覆盖画面底部约1/3的区域,草地平坦。树木茂盛,呈深浅不一的绿色,占据画面顶部约2/3的区域,树木之间有少量空隙可见远处的模糊建筑轮廓,呈灰色或浅色,无法辨认具体结构。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落4.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性身体略微向左倾斜,头部微抬,目光看向左前方画布方向。右手持画笔,手臂微弯,画笔尖端指向画布。左手托举调色板,手臂自然弯曲。双腿被长裙遮挡,无法判断具体姿态,但从裙摆看,应为站立姿态。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色长袖连衣裙,裙子款式宽松,袖子为泡泡袖设计,裙摆接近及地,略高于地面,有自然褶皱。领口为圆领。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有深棕色长发,发丝柔顺,自然垂落在肩部和背部,发尾自然垂落,略带弧度。额头前无刘海,露出完整额头。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性面部和手臂的肤色呈现健康的浅米色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "目测年龄在20-30岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物面部特征",
+          "描述": "女性面部轮廓柔和,眼睛呈杏仁状,眉毛弯曲,鼻梁挺拔,嘴唇饱满,涂有红色口红。右耳佩戴金色圆形耳环,耳环表面光滑,有反光。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式29"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "右手持一支细长的画笔,笔杆为黑色,笔尖为绿色。左手托举一个椭圆形木质调色板。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落4.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架由浅棕色木材制成,表面光滑,有木质纹理。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架为三脚架结构,由三根主支架构成,其中两根支架从地面向上倾斜支撑画布,另一根支架在后方提供稳定。画架中部有一个可调节高度的横杆,用于放置画布,横杆下方有一个木质托盘。画架中部有一个黑色皮质绑带,用于收纳或携带。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "画架整体呈浅棕色,木质原色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落4.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈长方形,垂直放置在画架上,长边垂直于地面。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布表面为纯白色,无任何图案或颜色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落4.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈不规则椭圆形,边缘圆润。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板由木材制成,表面光滑,呈浅棕色。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜色的颜料,主要包括深绿色、浅绿色、蓝色、红色、黄色、白色,以及少量橙色和紫色。绿色颜料占据调色板大部分区域。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料呈不规则块状分布在调色板表面,绿色颜料集中在调色板中央,其他颜色颜料散布在边缘。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落4.4",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景由近处的绿色草地和远处的茂盛树木构成。草地平坦,树木高大,树冠浓密。树木后方隐约可见模糊的建筑轮廓,无法辨认具体类型。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "背景颜色以绿色为主,草地呈鲜绿色,树木呈深浅不一的绿色,部分树叶在阳光下呈现亮绿色。天空颜色为浅蓝色或白色,占据画面顶部边缘极小部分,几乎不可见。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景整体呈现浅景深效果,草地和树木的细节相对模糊,远处的建筑轮廓更加模糊,与前景人物和画架形成对比,突出主体。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 255 - 0
examples/restore_old/input/paragraphs/02_图片形式_05.json

@@ -0,0 +1,255 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落5",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面主体为一名女性,仅展示其上半身,身穿白色长袖衬衫,左手握持调色板,右手握持画笔,正在进行绘画动作。人物从画面右侧中部延伸至画面左侧中部,占据画面约60%的宽度和70%的高度。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面左侧可见一个木质画架的局部,其支架部分呈棕色,支撑着画布。画架从画面左下角延伸至画面左侧中部,顶点坐标为[0, 700]到[300, 1000]。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "画布",
+          "描述": "画面左侧画架上放置着一块白色空白画布的局部,呈矩形,位于画架的上方。画布的顶点坐标为[0, 400]到[300, 700]。",
+          "是否可定量": true,
+          "类型": "物件"
+        },
+        {
+          "名称": "调色板",
+          "描述": "一个椭圆形木质调色板,颜色为深棕色,上面沾满了多种颜色的油彩,主要包括大面积的绿色、蓝色、红色、黄色、白色和少量其他颜色。调色板被人物的左手握持,位于画面中央偏下,顶点坐标为[290, 600]到[990, 1000]。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "画笔",
+          "描述": "一根细长的画笔,笔尖部分可见,呈绿色,被人物的右手握持,位于画布的上方,正在进行绘画动作。画笔的顶点坐标为[200, 0]到[300, 100]。",
+          "是否可定量": true,
+          "类型": "物件"
+        },
+        {
+          "名称": "背景",
+          "描述": "背景为模糊的绿色草地,光线明亮,暗示户外环境。背景占据了画面大部分区域,从画面顶部延伸至底部,顶点坐标为[0, 0]到[1000, 1500]。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "构图方式",
+          "描述": "画面采用特写构图,聚焦于人物的上半身及其绘画工具,背景虚化,突出主体。",
+          "是否可定量": false,
+          "类型": "构图",
+          "形式ID": "形式31"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性身体向左侧倾斜约15-20度,左手自然弯曲握持调色板,右手向上抬起,手肘弯曲,握持画笔,笔尖朝向画布,呈现正在绘画的专注姿态。",
+          "是否可定量": true,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色长袖衬衫,材质呈现丝绸般的光泽和垂坠感,袖口有纽扣,腰部有收腰设计,衣物在手臂弯曲处和腰部形成数道柔和的弧形褶皱,整体风格简洁。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "人物发型不可见,被衣物和拍摄角度遮挡。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "人物手臂和手部肤色呈现自然健康的米色或浅棕色调。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "根据身体特征和着装判断为女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "手部和手臂皮肤细腻,无明显皱纹或斑点,表明人物处于青年或中年早期,年龄约为20-35岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "人物左手握持一个椭圆形木质调色板,右手握持一支绿色笔尖的画笔。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        },
+        {
+          "名称": "人物身体部位可见性",
+          "描述": "仅可见人物的上半身,包括手臂、手部和部分躯干,头部和下半身不可见。",
+          "是否可定量": false,
+          "类型": "可见性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架为木质结构,呈现棕色木纹,表面经过打磨,呈现出木材特有的光泽和细腻触感。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架为三脚架式结构,画面中可见其左侧的两根支架,呈约70-80度角倾斜,稳定支撑画布。",
+          "是否可定量": true,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架可见性",
+          "描述": "画架仅可见左侧部分,从画面左下角延伸至画面左侧中部,顶点坐标为[0, 700]到[300, 1000],占据画面约30%的宽度和20%的高度。",
+          "是否可定量": true,
+          "类型": "可见性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.3",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈矩形,边缘笔直,无毛边或破损。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布表面为纯白色,未有任何颜料覆盖。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        },
+        {
+          "名称": "画布可见性",
+          "描述": "画布仅可见左侧部分,位于画架上方,顶点坐标为[0, 400]到[300, 700],占据画面约30%的宽度和20%的高度。",
+          "是否可定量": true,
+          "类型": "可见性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.4",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈不规则的椭圆形,边缘呈平滑的弧线,无尖锐角度,中间有一个拇指孔。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板为木质,表面经过打磨,呈现出木材特有的光泽和细腻触感,颜色为深棕色。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上的颜料颜色丰富,主要包括深绿色、草绿色、湖蓝色、天蓝色、紫罗兰色、鲜红色、橘红色、柠檬黄色、纯白色、煤黑色和土棕色等多种色彩。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料以块状和涂抹状分布在调色板表面,其中绿色颜料占据了调色板中央的大部分区域,蓝色、红色、黄色和白色颜料分布在边缘和绿色颜料周围,部分颜料边缘相互渗透,形成渐变或斑驳的混合区域。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        },
+        {
+          "名称": "调色板手持方式",
+          "描述": "调色板被人物的左手从下方托起,拇指穿过调色板中间的孔洞,其余手指自然弯曲支撑调色板边缘。",
+          "是否可定量": false,
+          "类型": "方式"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.5",
+      "形式": [
+        {
+          "名称": "画笔可见性",
+          "描述": "画笔仅可见笔尖部分和部分笔杆,笔尖呈绿色,笔杆为深棕色或黑色木质,其余部分被人物手部遮挡。",
+          "是否可定量": false,
+          "类型": "可见性"
+        },
+        {
+          "名称": "画笔手持方式",
+          "描述": "画笔被人物的右手以食指和拇指轻捏笔杆中上部,中指在下方提供支撑,无名指和小指自然弯曲的姿势握持,笔尖朝向画布。",
+          "是否可定量": false,
+          "类型": "方式"
+        },
+        {
+          "名称": "画笔位置",
+          "描述": "画笔位于画布的上方,笔尖正对着画布,准备进行绘画。画笔的顶点坐标为[200, 0]到[300, 100]。",
+          "是否可定量": false,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落5.6",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景内容为户外草地,可见模糊的绿色植物和数个圆形或椭圆形的光斑,大小不一,暗示阳光透过树叶的缝隙洒落。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "背景以绿色为主,背景中绿色从前景的深绿色逐渐过渡到远景的浅绿色,并夹杂着少量黄绿色和蓝绿色调,呈现出草地的自然色彩。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景呈现明显的虚化效果,景深较浅,使主体人物和绘画工具更加突出。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 267 - 0
examples/restore_old/input/paragraphs/02_图片形式_06.json

@@ -0,0 +1,267 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落6",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面主体为一名女性,从画面左下角延伸至画面中央偏上,占据画面约1/2的面积,其背部和左臂构成画面的主要视觉重心。",
+          "是否可定量": false,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面中上部偏右侧有一个木质画架,呈棕色,由两根向后倾斜的垂直支架和一根连接两支架中部的横向支撑杆构成,形成一个稳定的A字形结构,顶部有可调节的画板固定装置,支撑着画布。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "画布",
+          "描述": "画面中央偏左侧有一块矩形画布,尺寸约为画面高度的2/3,宽度约为画面宽度的1/2,垂直放置在画架上,略微向后倾斜,其下边缘与画架的横向支撑杆齐平,上边缘接近画架顶部,上面已绘有以绿色和蓝色为主的风景画,笔触粗犷,色彩鲜艳。",
+          "是否可定量": false,
+          "类型": "物件"
+        },
+        {
+          "名称": "调色板",
+          "描述": "画面左下角,人物左手握持一个椭圆形木质调色板,上面沾满了厚重的、呈块状堆积的油彩,颜料表面呈现出湿润的光泽,主要有深绿色、浅绿色、蓝色、红色、黄色、白色等。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "画笔",
+          "描述": "画面中央,人物右手握持一支笔杆为深棕色、笔毛为扁平状的画笔,笔尖沾有少量绿色颜料,正在画布上进行绘画。",
+          "是否可定量": false,
+          "类型": "物件"
+        },
+        {
+          "名称": "背景",
+          "描述": "画面右侧和上方为模糊的绿色草地和树木背景,光线明亮,从画面右上方照射过来,使得背景的绿色草地和树木呈现出柔和的光影效果,暗示户外环境。",
+          "是否可定量": false,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "构图方式",
+          "描述": "画面采用近景构图,人物背影和绘画工具占据画面大部分区域,背景虚化,突出主体。",
+          "是否可定量": false,
+          "类型": "构图",
+          "形式ID": "形式31"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性身体略微前倾,头部向右侧转动约45度,视线聚焦在画布上,双臂抬起,左手持调色板,右手持画笔,呈绘画姿态。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色长袖衬衫,材质轻薄,领口和背部呈深V字形开口,背部开口延伸至肩胛骨中部,露出大部分背部皮肤。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有棕色长发,发丝柔顺,自然垂落至肩部以下,部分头发遮盖住右耳,无明显发饰。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性颈部和背部皮肤呈健康自然的暖白色肤色,在光线照射下略带光泽。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "人物皮肤光滑,无明显皱纹,发质良好,推测年龄在25-30岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "人物左手握持一个带有拇指孔的椭圆形木质调色板,右手握持一支笔杆为深棕色的画笔。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        },
+        {
+          "名称": "人物身体部位可见性",
+          "描述": "可见人物的背部、右侧颈部、右耳、右臂、左臂(部分)、左手、右手。",
+          "是否可定量": false,
+          "类型": "可见性"
+        },
+        {
+          "名称": "饰品类型",
+          "描述": "人物佩戴一对几何形状的金色耳环(呈不规则三角形),以及一条细长的银色项链,项链上无明显吊坠。",
+          "是否可定量": false,
+          "类型": "类型"
+        },
+        {
+          "名称": "饰品颜色",
+          "描述": "耳环为金色,项链为银色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架为浅棕色木质,表面可见清晰的天然木材纹理,无明显漆面。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架由两根垂直支架和一根水平支撑杆组成,形成一个约60度角的A字形结构,看起来非常稳定,支撑着画布。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架可见性",
+          "描述": "画架大部分可见,位于画布后方,其左侧支架被人物的左臂和身体部分遮挡,右侧支架被画布遮挡了下半部分。",
+          "是否可定量": false,
+          "类型": "可见性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.3",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈矩形。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画作内容",
+          "描述": "画布上已绘有以绿色和蓝色为主的印象派风格风景画,笔触粗犷有力,色彩鲜艳,描绘了模糊的树木和草地,画面中央似乎有一个模糊的人形轮廓。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式24"
+        },
+        {
+          "名称": "画作颜色",
+          "描述": "画作主要颜色为绿色和蓝色,辅以少量白色用于提亮,黄色用于点缀,紫色用于增加画面层次感,这些颜色以不规则的笔触分布在画面中。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "画布可见性",
+          "描述": "画布大部分可见,位于画面中央偏左侧,其右侧边缘和右上方区域被人物的右手和画笔部分遮挡。",
+          "是否可定量": false,
+          "类型": "可见性"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.4",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈椭圆形。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板为木质。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上的颜料颜色包括墨绿色、草绿色、天蓝色、普鲁士蓝、朱红色、柠檬黄、纯白色、薰衣草紫和橘红色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料呈大小不一的块状堆积在调色板表面,分布不均匀,其中深绿色和浅绿色颜料占据了调色板中央和左侧的较大面积,其他颜色如蓝色、红色、黄色、白色等则分散在边缘区域。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        },
+        {
+          "名称": "调色板手持方式",
+          "描述": "调色板被人物左手握持,拇指穿过调色板左侧边缘的椭圆形孔洞,其余四指自然弯曲,托住调色板的底部,使其保持稳定。",
+          "是否可定量": false,
+          "类型": "方式"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.5",
+      "形式": [
+        {
+          "名称": "画笔手持方式",
+          "描述": "画笔被人物右手以正常握笔姿势握持,食指和拇指轻轻捏住笔杆中下部,中指、无名指和小指自然弯曲,辅助支撑笔杆,笔尖朝向画布。",
+          "是否可定量": false,
+          "类型": "方式"
+        },
+        {
+          "名称": "画笔位置",
+          "描述": "画笔位于画布的右上方区域,笔尖轻触画布表面,正在画布上绘制一笔绿色颜料,笔尖略微压扁。",
+          "是否可定量": false,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落6.6",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景内容为模糊的绿色草地和树木,呈现出大面积的绿色块状,树木的轮廓依稀可见,但无法辨认具体叶片或树干细节。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "背景颜色以深浅不一的绿色为主,夹杂少量棕色用于表现树干或泥土,黄色用于表现阳光照射下的高光区域,这些颜色以柔和的过渡方式融合在一起。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景呈高度虚化状态,景深模糊效果明显,使得背景景物完全模糊不清,与前景清晰的人物和绘画工具形成强烈的对比,突出了主体。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 353 - 0
examples/restore_old/input/paragraphs/02_图片形式_07.json

@@ -0,0 +1,353 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落7",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面右侧偏中,女性的侧脸、颈部、肩部及部分手臂占据了画面右侧约三分之二的区域,她侧身面向左侧,头部微仰,闭着眼睛,正在闻手中的白色玫瑰花。",
+          "是否可定量": false,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "玫瑰花",
+          "描述": "画面左侧偏中,一朵盛开的白色玫瑰花位于女性面部前方,花朵直径约8-10厘米,被女性双手握持,带有绿色的茎和叶子。",
+          "是否可定量": false,
+          "类型": "植物"
+        },
+        {
+          "名称": "背景",
+          "描述": "背景是模糊的深绿色草地,呈现出柔和的虚化效果,作为人物和玫瑰花的衬托,使得主体更加突出。",
+          "是否可定量": false,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "构图方式",
+          "描述": "采用特写构图,将人物的侧脸、手部和玫瑰花作为主要表现对象,背景虚化,突出主体。",
+          "是否可定量": false,
+          "类型": "构图",
+          "形式ID": "形式31"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性侧身,头部微仰,闭眼,双手捧着玫瑰花靠近鼻子,呈现出陶醉、享受的姿态。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物表情",
+          "描述": "女性闭着眼睛,嘴角微扬,面部放松,呈现出陶醉、满足的表情。",
+          "是否可定量": false,
+          "类型": "表情"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有棕色长发,发丝柔顺,大部分发丝披散在右侧肩部和背部,长度及腰。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性肤色白皙,略带暖调,面部和手部皮肤光滑细腻。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "目测为青年女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色宽松上衣,领口为V字形设计,袖口为宽松的喇叭袖设计,材质看起来轻薄飘逸。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "女性双手握持一朵白色玫瑰花。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        },
+        {
+          "名称": "指甲油颜色",
+          "描述": "女性指甲涂有亮蓝色指甲油。",
+          "是否可定量": false,
+          "类型": "颜色"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1.1",
+      "形式": [
+        {
+          "名称": "面部朝向",
+          "描述": "女性面部朝向左侧,呈现侧脸。",
+          "是否可定量": false,
+          "类型": "朝向"
+        },
+        {
+          "名称": "眼睛状态",
+          "描述": "女性双眼紧闭。",
+          "是否可定量": false,
+          "类型": "状态"
+        },
+        {
+          "名称": "鼻子形态",
+          "描述": "女性鼻子高挺,鼻梁笔直。",
+          "是否可定量": false,
+          "类型": "形态"
+        },
+        {
+          "名称": "嘴唇颜色",
+          "描述": "女性嘴唇涂有红色口红,颜色鲜艳。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "发型",
+          "描述": "女性留有棕色长发,发丝柔顺,部分发丝从耳后自然垂落至颈部,其余发丝向后披散。",
+          "是否可定量": false,
+          "类型": "样式"
+        },
+        {
+          "名称": "表情",
+          "描述": "女性闭着眼睛,嘴角微扬,面部放松,呈现出陶醉、享受的表情。",
+          "是否可定量": false,
+          "类型": "表情"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1.1.1",
+      "形式": [
+        {
+          "名称": "耳环颜色",
+          "描述": "耳环为金色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "耳环形状",
+          "描述": "耳环呈不规则的C形或半环形,边缘圆润,开口朝向下方。",
+          "是否可定量": false,
+          "类型": "形状"
+        },
+        {
+          "名称": "耳环材质",
+          "描述": "耳环材质看起来是金属。",
+          "是否可定量": false,
+          "类型": "材质"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1.1.2",
+      "形式": [
+        {
+          "名称": "项链颜色",
+          "描述": "项链为银色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "项链材质",
+          "描述": "项链材质看起来是金属。",
+          "是否可定量": false,
+          "类型": "材质"
+        },
+        {
+          "名称": "项链款式",
+          "描述": "项链为细链款式,链条由多个细小的椭圆形环扣紧密连接而成,佩戴在颈部,长度适中,刚好贴合锁骨。",
+          "是否可定量": false,
+          "类型": "款式"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1.2",
+      "形式": [
+        {
+          "名称": "手部姿态",
+          "描述": "女性双手轻轻握住玫瑰花的茎部,手指修长,指甲涂有蓝色指甲油。",
+          "是否可定量": false,
+          "类型": "姿态"
+        },
+        {
+          "名称": "指甲油颜色",
+          "描述": "女性指甲涂有亮蓝色指甲油。",
+          "是否可定量": false,
+          "类型": "颜色"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.1.3",
+      "形式": [
+        {
+          "名称": "服装颜色",
+          "描述": "上衣为白色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "服装款式",
+          "描述": "上衣为宽松款式,领口和袖口设计独特,整体剪裁流畅,线条简洁。",
+          "是否可定量": false,
+          "类型": "款式"
+        },
+        {
+          "名称": "服装材质",
+          "描述": "上衣材质看起来轻薄、柔软,具有一定的垂坠感,表面略带光泽,推测为丝绸或仿真丝面料。",
+          "是否可定量": false,
+          "类型": "材质"
+        },
+        {
+          "名称": "领口设计",
+          "描述": "上衣领口为V字形设计,领口边缘有与衣身同色的细致缝线,线条流畅。",
+          "是否可定量": false,
+          "类型": "设计"
+        },
+        {
+          "名称": "袖口设计",
+          "描述": "上衣袖口为宽松的喇叭袖设计,袖口处有均匀分布的细密褶皱,增加了袖子的蓬松感。",
+          "是否可定量": false,
+          "类型": "设计"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.2",
+      "形式": [
+        {
+          "名称": "花朵颜色",
+          "描述": "玫瑰花朵为白色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式28"
+        },
+        {
+          "名称": "花朵形态",
+          "描述": "玫瑰花朵盛开,花瓣层叠,形态饱满。",
+          "是否可定量": false,
+          "类型": "形态",
+          "形式ID": "形式27"
+        },
+        {
+          "名称": "茎叶颜色",
+          "描述": "玫瑰花的茎和叶子为绿色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "茎叶形态",
+          "描述": "玫瑰花的茎部细长,叶片完整,呈椭圆形,叶脉清晰可见,颜色略深于叶片本身,呈羽状分布。",
+          "是否可定量": false,
+          "类型": "形态"
+        },
+        {
+          "名称": "手持方式",
+          "描述": "玫瑰花被女性双手轻轻握住茎部,靠近女性面部。",
+          "是否可定量": false,
+          "类型": "方式"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.2.1",
+      "形式": [
+        {
+          "名称": "花朵颜色",
+          "描述": "花朵为白色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式28"
+        },
+        {
+          "名称": "花朵形态",
+          "描述": "花朵盛开,花瓣向外舒展,形态饱满。",
+          "是否可定量": false,
+          "类型": "形态",
+          "形式ID": "形式27"
+        },
+        {
+          "名称": "花瓣层叠",
+          "描述": "花瓣层层叠叠,约有数十片,由内向外螺旋状排列,形成饱满的花型。",
+          "是否可定量": false,
+          "类型": "结构"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.2.2",
+      "形式": [
+        {
+          "名称": "茎颜色",
+          "描述": "茎部为绿色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "叶子颜色",
+          "描述": "叶子为绿色。",
+          "是否可定量": false,
+          "类型": "颜色"
+        },
+        {
+          "名称": "叶片完整性",
+          "描述": "叶片完整无缺,表面光滑,边缘无锯齿状破损,呈现出健康的生长状态。",
+          "是否可定量": false,
+          "类型": "状态"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落7.3",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景内容为草地。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "背景颜色为绿色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景模糊,呈现出虚化的效果,突出前景的人物和玫瑰花。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 228 - 0
examples/restore_old/input/paragraphs/02_图片形式_08.json

@@ -0,0 +1,228 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落8",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面右侧偏中位置,一名女性正在进行绘画,人物从画面底部向上延伸至画面约60%的高度,从画面右侧边缘向左延伸至画面约40%的宽度,其身体朝向左侧,头部略微向右转动,视线看向画布。",
+          "是否可定量": true,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面左侧偏中位置,一个木质三脚画架,支撑着一块白色画布,画架的左侧支架从画面底部延伸至画面顶部约65%处,右侧支架从画面底部延伸至画面中部约50%处。画架整体宽度占据画面约20%,呈A字形。",
+          "是否可定量": true,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "画面右下角,女性左手持有一个椭圆形木质调色板,调色板上沾有多种颜色的颜料,包括绿色、蓝色、黄色、红色、紫色、棕色和白色,其中绿色和蓝色颜料面积较大,分布在调色板的中央和左侧区域,呈不规则块状。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "背景",
+          "描述": "画面主体人物和画架后方是绿色的草地和茂密的树木,草地占据画面底部约40%的高度,树木占据画面顶部约60%的高度。树木种类为灌木和乔木,枝干交错,叶片茂密,部分树木可见红色小花,呈现出自然、户外的环境。",
+          "是否可定量": true,
+          "类型": "背景",
+          "形式ID": "形式3"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性身体侧向左前方,头部略微向右转动,视线看向画布。右手持画笔,手肘弯曲,手腕抬起,画笔尖端触及画布。左手自然下垂,手肘微弯,调色板位于左侧大腿外侧,与地面平行。双腿站立,身体重心略微偏向左侧。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一套白色长裙,上衣为宽松的蝙蝠袖设计,腰部有数条细密的横向褶皱,形成收紧效果。下半身是垂坠感强的A字形长裙,裙摆及地,有自然形成的竖向褶皱。整体服装风格简约优雅。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有棕色长发,发丝柔顺,中分,自然垂落在肩部和背部,部分发丝被耳后佩戴的金色圆形耳饰遮挡。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性面部、颈部和手臂的肤色呈现出均匀的浅米色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "视觉判断年龄在25-35岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        },
+        {
+          "名称": "人物面部特征",
+          "描述": "女性面部轮廓柔和,眉毛细长,眼睛呈杏仁状,双眼皮,眼线细致,鼻梁挺拔,嘴唇饱满,涂有淡粉色唇彩。右耳佩戴一个金色圆形耳饰,耳饰上镶嵌有白色小珠。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式29"
+        },
+        {
+          "名称": "人物手持物",
+          "描述": "右手持一支细长的画笔,笔杆为深色木质,笔尖为白色合成纤维毛刷,沾有少量白色颜料。左手持一个椭圆形木质调色板。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式19"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架由浅棕色木材制成,表面光滑,呈现出细密的竖向木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架为三脚架结构,由三根主支架构成,其中两根支架在前方中部交叉,一根支架在后方支撑。画架中部有一个可调节高度的横梁,横梁上有一个凹槽用于放置画布。横梁下方悬挂着一条黑色帆布背带。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "画架整体呈现浅棕色,与木材的自然颜色一致。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布呈长方形,长宽比约为3:2,垂直放置在画架上,其长边垂直于地面,短边平行于地面。",
+          "是否可定量": true,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布表面为纯白色,未有任何颜料覆盖。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.2.2",
+      "形式": [
+        {
+          "名称": "花朵颜色",
+          "描述": "花朵为纯白色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式28"
+        },
+        {
+          "名称": "花朵形态",
+          "描述": "花朵为一朵盛开的玫瑰花,花瓣约有20-30片,层叠紧密,边缘略微卷曲,花朵直径约5-7厘米,饱满圆润。",
+          "是否可定量": true,
+          "类型": "形态",
+          "形式ID": "形式27"
+        },
+        {
+          "名称": "花朵位置",
+          "描述": "花朵放置在画架右侧横梁的右端,距离画布右下角约2厘米。",
+          "是否可定量": true,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板呈椭圆形,长轴约30厘米,短轴约20厘米,边缘光滑。",
+          "是否可定量": true,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板为木质,表面呈现出细密的环形木纹。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜色的颜料,包括鲜绿色、天蓝色、柠檬黄、大红色、紫罗兰色、深棕色和纯白色,其中鲜绿色和天蓝色颜料面积较大,分布在调色板的中央和左侧区域。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料呈不规则的块状分布在调色板的表面,没有明显的规律性,各种颜色相互交错,部分绿色和黄色颜料混合形成黄绿色,部分蓝色和红色颜料混合形成紫色。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落8.4",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景由两部分组成:前景是修剪整齐的绿色草地,占据画面下半部分;后景是茂密的绿色树木,占据画面上半部分。树木的枝干和叶片清晰可见,部分树木之间有少量空隙,露出远处的模糊建筑轮廓或山峦。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "草地呈现鲜亮的翠绿色,树木呈现墨绿色、橄榄绿等深浅不一的绿色,整体色调以绿色为主。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景的草地和近处树木较为清晰,远处的树木和景物略显模糊,细节难以辨认,呈现出明显的景深效果。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        }
+      ]
+    }
+  ]
+}

+ 236 - 0
examples/restore_old/input/paragraphs/02_图片形式_09.json

@@ -0,0 +1,236 @@
+{
+  "image_url": "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg",
+  "form_elements": [
+    {
+      "段落ID": "段落9",
+      "形式": [
+        {
+          "名称": "人物",
+          "描述": "画面中有一名女性,背对镜头,其中心点位于画面右侧约1/3处,距离画面底部约1/4高度,正在进行绘画活动。",
+          "是否可定量": false,
+          "类型": "主体",
+          "形式ID": "形式1"
+        },
+        {
+          "名称": "画架",
+          "描述": "画面中有一个木质三脚画架,其中心点位于画面左侧约1/3处,距离画面底部约1/3高度,支撑着一块画布。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式8"
+        },
+        {
+          "名称": "调色板",
+          "描述": "画面中有一个椭圆形木质调色板,位于画架右侧支撑杆上,紧邻画布底部边缘,其左侧约1/3被人物右手遮挡,上面沾有多种颜色的颜料。",
+          "是否可定量": false,
+          "类型": "物件",
+          "形式ID": "形式12"
+        },
+        {
+          "名称": "背景",
+          "描述": "背景是户外草地和树木,草地呈绿色,树木茂盛,远处左侧约1/4处有一个木质凉亭,其顶部与画面中线齐平,凉亭大部分被树木遮挡,仅可见其部分结构,整体环境明亮。",
+          "是否可定量": false,
+          "类型": "背景",
+          "形式ID": "形式3"
+        },
+        {
+          "名称": "光照效果",
+          "描述": "画面整体光线明亮,阳光从画面左上角约45度方向射入,在画面左侧和中心区域的草地和树叶上形成多个不规则形状的明亮光斑,其中最大的光斑位于画面左下角,直径约占画面宽度的1/10。",
+          "是否可定量": false,
+          "类型": "效果",
+          "形式ID": "形式30"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落9.1",
+      "形式": [
+        {
+          "名称": "人物姿态",
+          "描述": "女性背对镜头,身体向右侧倾斜约5-10度,头部向左侧倾斜约3-5度,双手抬起,似乎正在用右手持画笔在画布上绘画。",
+          "是否可定量": false,
+          "类型": "姿态",
+          "形式ID": "形式2"
+        },
+        {
+          "名称": "人物着装",
+          "描述": "女性身穿一件白色长裙,裙子为深V领设计,V领延伸至背部中线,露背范围从肩胛骨下方延伸至腰部,裙摆宽松,垂至脚踝,腰部有系带。",
+          "是否可定量": false,
+          "类型": "服饰",
+          "形式ID": "形式4"
+        },
+        {
+          "名称": "人物发型",
+          "描述": "女性留有深棕色长发,发量浓密,发长及腰,发尾在腰部以下约10厘米处呈现自然的大波浪微卷,自然垂落在背部。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式6"
+        },
+        {
+          "名称": "人物肤色",
+          "描述": "女性的肤色呈现暖调的浅棕色,类似于小麦色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式10"
+        },
+        {
+          "名称": "人物性别",
+          "描述": "人物为女性。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式15"
+        },
+        {
+          "名称": "人物年龄",
+          "描述": "根据人物的体态和发型,估计年龄在25-30岁之间。",
+          "是否可定量": false,
+          "类型": "特征",
+          "形式ID": "形式18"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落9.2",
+      "形式": [
+        {
+          "名称": "画架材质",
+          "描述": "画架由木材制成,呈现原木色泽。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式20"
+        },
+        {
+          "名称": "画架结构",
+          "描述": "画架为三脚支撑结构,顶部有两根可调节的画板支撑杆,呈V字形向上延伸,底部有两根横向支撑杆,分别位于画架下部约1/3和2/3处,用于增加稳定性。",
+          "是否可定量": false,
+          "类型": "结构",
+          "形式ID": "形式16"
+        },
+        {
+          "名称": "画架颜色",
+          "描述": "画架呈现浅棕色木质原色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式23"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落9.2.1",
+      "形式": [
+        {
+          "名称": "画布形状",
+          "描述": "画布为矩形,长宽比约为1:1.45,垂直放置。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式13"
+        },
+        {
+          "名称": "画布颜色",
+          "描述": "画布底色为白色,上面覆盖有颜料。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式22"
+        },
+        {
+          "名称": "画作内容",
+          "描述": "画作内容以绿色和蓝色为主色调,呈现出流动的、模糊的自然风景轮廓,画面右侧可见一团白色颜料,隐约勾勒出人物的头部和肩部轮廓。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式24"
+        },
+        {
+          "名称": "画作风格",
+          "描述": "画作风格融合了印象派的色彩运用和抽象派的笔触,呈现出半抽象的视觉效果。",
+          "是否可定量": false,
+          "类型": "风格",
+          "形式ID": "形式25"
+        },
+        {
+          "名称": "画作完成度",
+          "描述": "画作尚未完成,画布左上角和右下角仍有约1/4的空白区域,画面中央的景物也仅是初步上色,缺乏细节刻画。",
+          "是否可定量": false,
+          "类型": "状态",
+          "形式ID": "形式26"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落9.3",
+      "形式": [
+        {
+          "名称": "调色板形状",
+          "描述": "调色板为不规则椭圆形,边缘圆润。",
+          "是否可定量": false,
+          "类型": "形状",
+          "形式ID": "形式17"
+        },
+        {
+          "名称": "调色板材质",
+          "描述": "调色板为木质。",
+          "是否可定量": false,
+          "类型": "材质",
+          "形式ID": "形式21"
+        },
+        {
+          "名称": "颜料颜色",
+          "描述": "调色板上沾有多种颜色的颜料,主要包括绿色、蓝色、黄色、红色和白色。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式9"
+        },
+        {
+          "名称": "颜料分布",
+          "描述": "颜料呈大小不一的不规则块状分布,其中绿色和蓝色颜料块较大,位于调色板中心区域,部分已混合成新的色调,边缘散布着较小的黄色、红色和白色颜料点。",
+          "是否可定量": false,
+          "类型": "分布",
+          "形式ID": "形式14"
+        },
+        {
+          "名称": "调色板位置",
+          "描述": "调色板位于画架中部偏右的一个小型木质托盘上,该托盘位于画布底部下方约10厘米处,被人物右手部分遮挡。",
+          "是否可定量": false,
+          "类型": "位置"
+        }
+      ]
+    },
+    {
+      "段落ID": "段落9.4",
+      "形式": [
+        {
+          "名称": "背景内容",
+          "描述": "背景主要由大面积的绿色草地和茂盛的树木构成,远处可见一个木质凉亭,更远处,在画面左上角树木的缝隙中,隐约可见几栋高层城市建筑的模糊轮廓,呈浅灰色。",
+          "是否可定量": false,
+          "类型": "内容",
+          "形式ID": "形式5"
+        },
+        {
+          "名称": "背景颜色",
+          "描述": "背景颜色以绿色为主,草地呈鲜绿色,树木叶片呈深浅不一的绿色和黄绿色,天空呈明亮的浅白色,占据画面上半部分约1/3的区域,颜色均匀,无明显云朵。",
+          "是否可定量": false,
+          "类型": "颜色",
+          "形式ID": "形式7"
+        },
+        {
+          "名称": "背景清晰度",
+          "描述": "背景景深较浅,远处的树木和城市建筑呈现明显的虚化效果,细节模糊不清;近处的草地纹理清晰可见,凉亭的木质结构也较为锐利。",
+          "是否可定量": false,
+          "类型": "清晰度",
+          "形式ID": "形式11"
+        },
+        {
+          "名称": "光斑效果",
+          "描述": "画面中共有约5-7个主要光斑,集中分布在画面左侧和中央的草地上,最大的光斑位于画面左下角,亮度极高,呈椭圆形,其余光斑较小,亮度稍低,呈不规则圆形或条状。",
+          "是否可定量": false,
+          "类型": "效果",
+          "形式ID": "形式32"
+        },
+        {
+          "名称": "建筑存在",
+          "描述": "背景中存在一个木质凉亭,位于画面左侧偏中位置,结构为四柱支撑的方形顶棚,凉亭内可见一张木质长凳,位于凉亭内部的左侧,长凳的细节因距离较远而略显模糊。",
+          "是否可定量": false,
+          "类型": "存在性"
+        }
+      ]
+    }
+  ]
+}

+ 137 - 0
examples/restore_old/input/paragraphs/03_图片制作点实质结果.json

@@ -0,0 +1,137 @@
+[
+  {
+    "元素ID": "元素1",
+    "元素名称": "背景",
+    "元素描述": "户外草地背景,有树木和灌木,光线明亮,自然环境",
+    "段落数量": 9,
+    "段落列表": [
+      "段落1.4",
+      "段落2.4",
+      "段落3.5",
+      "段落4.4",
+      "段落5.6",
+      "段落6.6",
+      "段落7.3",
+      "段落8.4",
+      "段落9.4"
+    ],
+    "覆盖图片数": 9,
+    "出现总次数": 9,
+    "重要性得分": 70.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 100.0,
+    "覆盖率得分": 60.0,
+    "频率权重": 100.0,
+    "综合权重": 91.0
+  },
+  {
+    "元素ID": "元素2",
+    "元素名称": "调色板",
+    "元素描述": "椭圆形调色板,上面有多种颜色的颜料,色彩丰富,有绿色、蓝色、黄色、红色等",
+    "段落数量": 8,
+    "段落列表": [
+      "段落1.3",
+      "段落2.3",
+      "段落3.3",
+      "段落4.3",
+      "段落5.4",
+      "段落6.4",
+      "段落8.3",
+      "段落9.3"
+    ],
+    "覆盖图片数": 8,
+    "出现总次数": 8,
+    "重要性得分": 75.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 88.89,
+    "覆盖率得分": 53.33,
+    "频率权重": 93.33,
+    "综合权重": 87.83
+  },
+  {
+    "元素ID": "元素3",
+    "元素名称": "人物",
+    "元素描述": "女性,长发,穿着白色连衣裙,正在绘画,姿态多样(站立、侧身、背对、蹲坐),手持画笔和调色板",
+    "段落数量": 6,
+    "段落列表": [
+      "段落1.1",
+      "段落2.1",
+      "段落3.1",
+      "段落4.1",
+      "段落8.1",
+      "段落9.1"
+    ],
+    "覆盖图片数": 6,
+    "出现总次数": 6,
+    "重要性得分": 95.0,
+    "频次基础分": 100,
+    "频次得分": 40.0,
+    "覆盖率基础分": 66.67,
+    "覆盖率得分": 40.0,
+    "频率权重": 80.0,
+    "综合权重": 84.5
+  },
+  {
+    "元素ID": "元素4",
+    "元素名称": "画布",
+    "元素描述": "画架上的画布,上面有未完成的画作,画作内容为穿着白色连衣裙的女性背影,背景是绿色植物和蓝色花朵",
+    "段落数量": 4,
+    "段落列表": [
+      "段落1.2.1",
+      "段落2.2.1",
+      "段落3.2.1",
+      "段落9.2.1"
+    ],
+    "覆盖图片数": 4,
+    "出现总次数": 4,
+    "重要性得分": 80.0,
+    "频次基础分": 60,
+    "频次得分": 24.0,
+    "覆盖率基础分": 44.44,
+    "覆盖率得分": 26.67,
+    "频率权重": 50.67,
+    "综合权重": 59.47
+  },
+  {
+    "元素ID": "元素5",
+    "元素名称": "人物细节",
+    "元素描述": "女性的手部,指甲涂有蓝色指甲油;白色连衣裙;耳环;项链",
+    "段落数量": 4,
+    "段落列表": [
+      "段落7.1.2",
+      "段落7.1.3",
+      "段落7.1.1.1",
+      "段落7.1.1.2"
+    ],
+    "覆盖图片数": 1,
+    "出现总次数": 4,
+    "重要性得分": 95.0,
+    "频次基础分": 60,
+    "频次得分": 24.0,
+    "覆盖率基础分": 11.11,
+    "覆盖率得分": 6.67,
+    "频率权重": 30.67,
+    "综合权重": 49.97
+  },
+  {
+    "元素ID": "元素6",
+    "元素名称": "人物",
+    "元素描述": "女性,穿着白色连衣裙,手持调色板和画笔,正在绘画",
+    "段落数量": 2,
+    "段落列表": [
+      "段落5.1",
+      "段落6.1"
+    ],
+    "覆盖图片数": 2,
+    "出现总次数": 2,
+    "重要性得分": 95.0,
+    "频次基础分": 20,
+    "频次得分": 8.0,
+    "覆盖率基础分": 22.22,
+    "覆盖率得分": 13.33,
+    "频率权重": 21.33,
+    "综合权重": 43.43
+  }
+]

Некоторые файлы не были показаны из-за большого количества измененных файлов