فهرست منبع

batch process pipeline

guantao 1 ماه پیش
والد
کامیت
e390d515f0
61فایلهای تغییر یافته به همراه7610 افزوده شده و 1441 حذف شده
  1. 300 185
      agent/core/runner.py
  2. 2 0
      agent/llm/__init__.py
  3. 177 0
      agent/llm/claude.py
  4. 2 2
      agent/tools/builtin/__init__.py
  5. 1 1
      agent/tools/builtin/bash.py
  6. 3 1
      agent/tools/builtin/content/cache.py
  7. 26 7
      agent/tools/builtin/content/platforms/aigc_channel.py
  8. 21 5
      agent/tools/builtin/content/platforms/x.py
  9. 21 5
      agent/tools/builtin/content/platforms/youtube.py
  10. 14 1
      agent/tools/builtin/content/tools.py
  11. 2 0
      agent/tools/builtin/file/__init__.py
  12. 125 0
      agent/tools/builtin/file/image_cdn.py
  13. 9 0
      agent/tools/builtin/file/write.py
  14. 99 0
      agent/tools/builtin/file/write_json.py
  15. 36 11
      agent/tools/builtin/subagent.py
  16. 3 1
      agent/tools/registry.py
  17. 0 64
      examples/process/config.py
  18. 0 27
      examples/process/presets.json
  19. 0 23
      examples/process/prompts/analyst.prompt
  20. 0 179
      examples/process/prompts/coordinator.prompt
  21. 0 80
      examples/process/prompts/research.prompt
  22. 0 78
      examples/process/prompts/tool_research.prompt
  23. 0 62
      examples/process/research.prompt
  24. 0 546
      examples/process/run.py
  25. 0 37
      examples/process/tool_research.prompt
  26. 93 0
      examples/process_pipeline/batch.py
  27. 101 0
      examples/process_pipeline/db_requirements.json
  28. 39 0
      examples/process_pipeline/presets.json
  29. 105 0
      examples/process_pipeline/prompts/assemble_strategy.prompt
  30. 96 0
      examples/process_pipeline/prompts/extract_capabilities.prompt
  31. 91 0
      examples/process_pipeline/prompts/filter_and_blueprint.prompt
  32. 78 0
      examples/process_pipeline/prompts/researcher.prompt
  33. 27 0
      examples/process_pipeline/prompts/router.prompt
  34. 2057 0
      examples/process_pipeline/run_metrics.json
  35. 562 0
      examples/process_pipeline/run_pipeline.py
  36. 286 0
      examples/process_pipeline/worker_0.bat
  37. 286 0
      examples/process_pipeline/worker_1.bat
  38. 286 0
      examples/process_pipeline/worker_2.bat
  39. 286 0
      examples/process_pipeline/worker_3.bat
  40. 286 0
      examples/process_pipeline/worker_4.bat
  41. 930 0
      examples/process_research/aigc_architecture.html
  42. 66 0
      examples/process_research/config.py
  43. 101 0
      examples/process_research/db_requirements.json
  44. 85 0
      examples/process_research/fix_strategy_requirement.py
  45. 83 0
      examples/process_research/generate_strategy.py
  46. 16 0
      examples/process_research/presets.json
  47. 223 0
      examples/process_research/prompts/coordinator.prompt
  48. 118 0
      examples/process_research/prompts/researcher.prompt
  49. 0 0
      examples/process_research/requirements.json
  50. 408 0
      examples/process_research/run.py
  51. 6 4
      knowhub/knowhub_db/pg_capability_store.py
  52. BIN
      out.txt
  53. 0 16
      scratch_db_caps.py
  54. 0 21
      scratch_db_test.py
  55. 0 21
      scratch_db_test_case.py
  56. 0 46
      scratch_search_test.py
  57. 0 18
      scratch_test.py
  58. 0 0
      temp_b64.txt
  59. 0 0
      temp_base64.txt
  60. 26 0
      test_anthropic.py
  61. 28 0
      test_openai.py

+ 300 - 185
agent/core/runner.py

@@ -122,6 +122,7 @@ class RunConfig:
     auto_execute_tools: bool = True
     name: Optional[str] = None                 # 显示名称(空则由 utility_llm 自动生成)
     enable_prompt_caching: bool = True         # 启用 Anthropic Prompt Caching(仅 Claude 模型有效)
+    parallel_tool_execution: bool = False      # 是否启用并发 Tool Call 执行(慎用,需确保无资源冲突)
 
     # --- Trace 控制 ---
     trace_id: Optional[str] = None             # None = 新建
@@ -1563,197 +1564,300 @@ class AgentRunner:
                     "tool_calls": tool_calls,
                 })
 
-                for tc in tool_calls:
+                if config.parallel_tool_execution:
+                    # === 并发执行 ===
                     current_goal_id = goal_tree.current_id if (goal_tree and goal_tree.current_id) else None
-
-                    tool_name = tc["function"]["name"]
-                    tool_args = tc["function"]["arguments"]
-
-                    if isinstance(tool_args, str):
-                        if not tool_args.strip():
+                    async def _execute_single_tool(tc: dict) -> tuple:
+                        tool_name = tc["function"]["name"]
+                        tool_args = tc["function"]["arguments"]
+                        if isinstance(tool_args, str):
+                            if not tool_args.strip():
+                                tool_args = {}
+                            else:
+                                try:
+                                    tool_args = json.loads(tool_args)
+                                except json.JSONDecodeError:
+                                    tool_args = self._try_fix_json(tool_args)
+                                    if tool_args is None:
+                                        self.log.warning(f"[Tool Call] JSON 解析失败: {tc['function']['arguments'][:200]}")
+                                        tc["function"]["arguments"] = json.dumps({"_error": "JSON parse failed", "_raw": tc["function"]["arguments"][:200]}, ensure_ascii=False)
+                                        return (tc, None, f"Error: 工具参数 JSON 格式错误,无法解析。原始参数: {tc['function']['arguments'][:200]}")
+                        elif tool_args is None:
                             tool_args = {}
-                        else:
+                        args_str = json.dumps(tool_args, ensure_ascii=False)
+                        args_display = args_str[:100] + "..." if len(args_str) > 100 else args_str
+                        self.log.info(f"[Tool Call] {tool_name}({args_display})")
+                        trigger_event_for_tool = None
+                        if side_branch_ctx and side_branch_ctx.type == "knowledge_eval" and self.trace_store:
+                            current_trace = await self.trace_store.get_trace(trace_id)
+                            if current_trace:
+                                trigger_event_for_tool = current_trace.context.get("active_side_branch", {}).get("trigger_event", "unknown")
+                        if tool_name in ("toolhub_call", "toolhub_search", "toolhub_health"):
                             try:
-                                tool_args = json.loads(tool_args)
-                            except json.JSONDecodeError:
-                                # 尝试修复常见的截断/格式问题
-                                tool_args = self._try_fix_json(tool_args)
-                                if tool_args is None:
-                                    self.log.warning(f"[Tool Call] JSON 解析失败,跳过工具调用 {tool_name}: {tc['function']['arguments'][:200]}")
-                                    # 修复 history 中 assistant message 里的残缺 JSON,
-                                    # 避免 Qwen API 拒绝 "function.arguments must be in JSON format"
-                                    tc["function"]["arguments"] = json.dumps(
-                                        {"_error": "JSON parse failed", "_raw": tc["function"]["arguments"][:200]},
-                                        ensure_ascii=False,
-                                    )
-                                    history.append({
-                                        "role": "tool",
-                                        "tool_call_id": tc["id"],
-                                        "content": f"Error: 工具参数 JSON 格式错误,无法解析。请重新生成正确的 JSON 参数调用此工具。原始参数: {tc['function']['arguments'][:200]}",
-                                    })
-                                    # 注意:这里不 yield Message,因为缺少必需参数会导致错误
-                                    # yield Message 应该由 trace_store 统一管理
-                                    continue
-                    elif tool_args is None:
-                        tool_args = {}
-
-                    # 记录工具调用(INFO 级别,显示参数)
-                    args_str = json.dumps(tool_args, ensure_ascii=False)
-                    args_display = args_str[:100] + "..." if len(args_str) > 100 else args_str
-                    self.log.info(f"[Tool Call] {tool_name}({args_display})")
-
-                    # 获取trigger_event(如果在knowledge_eval侧分支中)
-                    trigger_event_for_tool = None
-                    if side_branch_ctx and side_branch_ctx.type == "knowledge_eval" and self.trace_store:
-                        current_trace = await self.trace_store.get_trace(trace_id)
-                        if current_trace:
-                            trigger_event_for_tool = current_trace.context.get("active_side_branch", {}).get("trigger_event", "unknown")
-
-                    # 设置 trace_id 上下文供 toolhub 使用(图片保存到 outputs/{trace_id}/)
-                    if tool_name in ("toolhub_call", "toolhub_search", "toolhub_health"):
+                                from agent.tools.builtin.toolhub import set_trace_context
+                                set_trace_context(trace_id)
+                            except ImportError:
+                                pass
                         try:
-                            from agent.tools.builtin.toolhub import set_trace_context
-                            set_trace_context(trace_id)
-                        except ImportError:
-                            pass
-
-                    tool_result = await self.tools.execute(
-                        tool_name,
-                        tool_args,
-                        uid=config.uid or "",
-                        context={
-                            "store": self.trace_store,
-                            "trace_id": trace_id,
-                            "goal_id": current_goal_id,
-                            "runner": self,
-                            "goal_tree": goal_tree,
-                            "knowledge_config": config.knowledge,
-                            "sequence": sequence,  # 添加sequence用于知识注入记录
-                            # 新增:侧分支信息
-                            "side_branch": {
-                                "type": side_branch_ctx.type,
-                                "branch_id": side_branch_ctx.branch_id,
-                                "is_side_branch": True,
-                                "max_turns": side_branch_ctx.max_turns,
-                                "trigger_event": trigger_event_for_tool,
-                            } if side_branch_ctx else None,
-                            # 合并用户自定义 context(RunConfig.context)
-                            **(config.context or {}),
-                        },
-                    )
-
-                    # 如果是 goal 工具,记录执行后的状态
-                    if tool_name == "goal" and goal_tree:
-                        self.log.debug(f"[Goal Tool] After execution: goal_tree.goals={len(goal_tree.goals)}, current_id={goal_tree.current_id}")
-
-                    # 跟踪上传的知识(通过 upload_knowledge)
-                    if tool_name == "upload_knowledge" and isinstance(tool_result, dict):
-                        metadata = tool_result.get("metadata", {})
-                        # upload_knowledge 返回的是统计信息,不是单个 knowledge_id
-                        # 这里只记录上传动作,不跟踪具体 ID
-                        self.log.info(f"[Knowledge Tracking] 知识已上传到 Knowledge Manager")
-
-                    # --- 支持多模态工具反馈 ---
-                    # 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_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({
-                                    "type": "image_url",
-                                    "image_url": {
-                                        "url": f"data:{media_type};base64,{img['data']}"
-                                    }
-                                })
-                            elif img.get("type") == "url" and img.get("url"):
-                                tool_content_for_llm.append({
-                                    "type": "image_url",
-                                    "image_url": {
-                                        "url": img["url"]
-                                    }
-                                })
-                        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 = tool_text
-                        tool_content_for_llm = tool_text
-
-                    tool_msg = Message.create(
-                        trace_id=trace_id,
-                        role="tool",
-                        sequence=sequence,
-                        goal_id=current_goal_id,
-                        parent_sequence=head_seq,
-                        tool_call_id=tc["id"],
-                        branch_type=side_branch_ctx.type if side_branch_ctx else None,
-                        branch_id=side_branch_ctx.branch_id if side_branch_ctx else None,
-                        # 存储完整内容:有图片时保留 list(含 image_url),纯文本时存字符串
-                        content={"tool_name": tool_name, "result": tool_content_for_llm},
-                    )
-
-                    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),
+                            tool_result = await self.tools.execute(
+                                tool_name, tool_args, uid=config.uid or "",
+                                context={"store": self.trace_store, "trace_id": trace_id, "goal_id": current_goal_id, "runner": self, "goal_tree": goal_tree, "knowledge_config": config.knowledge, "sequence": sequence, "side_branch": {"type": side_branch_ctx.type, "branch_id": side_branch_ctx.branch_id, "is_side_branch": True, "max_turns": side_branch_ctx.max_turns, "trigger_event": trigger_event_for_tool} if side_branch_ctx else None, **(config.context or {})}
                             )
-                        # 截图单独存为同名 PNG 文件
+                            return (tc, tool_args, tool_result)
+                        except Exception as e:
+                            import traceback
+                            return (tc, tool_args, f"Error executing tool {tool_name}: {str(e)}\n{traceback.format_exc()}")
+                    tasks = [_execute_single_tool(tc) for tc in tool_calls]
+                    results = await asyncio.gather(*tasks)
+                    for res in results:
+                        tc, tool_args, tool_result = res
+                        tool_name = tc["function"]["name"]
+                        if tool_args is None:
+                            history.append({"role": "tool", "tool_call_id": tc["id"], "name": tool_name, "content": tool_result})
+                            yield Message.create(trace_id=trace_id, role="tool", sequence=sequence, parent_sequence=head_seq, tool_call_id=tc["id"], content=tool_result)
+                            head_seq = sequence
+                            sequence += 1
+                            continue
+                        if tool_name == "goal" and goal_tree:
+                            self.log.debug(f"[Goal Tool] After execution: goal_tree.goals={len(goal_tree.goals)}, current_id={goal_tree.current_id}")
+                        if tool_name == "upload_knowledge" and isinstance(tool_result, dict):
+                            self.log.info(f"[Knowledge Tracking] 知识已上传")
+                        if isinstance(tool_result, str):
+                            tool_result = {"text": tool_result}
+                        elif not isinstance(tool_result, dict):
+                            tool_result = {"text": str(tool_result)}
+                        tool_text = tool_result.get("text", str(tool_result))
+                        tool_images = tool_result.get("images", [])
+                        tool_usage = tool_result.get("tool_usage")
                         if tool_images:
-                            import base64 as b64mod
+                            tool_result_text = tool_text
+                            tool_content_for_llm = [{"type": "text", "text": tool_text}]
                             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"]))
-                                    print(f"[Runner] 截图已保存: {png_path.name}")
-                                    break  # 只存第一张
-
-                    # 如果在侧分支,tool_msg 已持久化(不需要额外维护)
-
-                    yield tool_msg
-                    head_seq = sequence
-                    sequence += 1
-
-                    history.append({
-                        "role": "tool",
-                        "tool_call_id": tc["id"],
-                        "name": tool_name,
-                        "content": tool_content_for_llm,
-                        "_message_id": tool_msg.message_id,
-                    })
-
-                    # 更新 skill 注入追踪记录
-                    if tool_name == "skill" and tc["id"].startswith("call_skill_"):
-                        try:
-                            skill_args = json.loads(tc["function"]["arguments"]) if isinstance(tc["function"]["arguments"], str) else tc["function"]["arguments"]
-                            injected_skill_name = skill_args.get("skill_name", "")
-                            if injected_skill_name:
-                                await self._update_skill_injection_record(
-                                    trace_id, trace, injected_skill_name,
-                                    tool_msg.message_id, tool_msg.sequence,
+                                if img.get("type") == "base64" and img.get("data"):
+                                    media_type = img.get("media_type", "image/png")
+                                    tool_content_for_llm.append({"type": "image_url", "image_url": {"url": f"data:{media_type};base64,{img['data']}"}})
+                                elif img.get("type") == "url" and img.get("url"):
+                                    tool_content_for_llm.append({"type": "image_url", "image_url": {"url": img["url"]}})
+                        else:
+                            tool_result_text = tool_text
+                            tool_content_for_llm = tool_text
+                        tool_msg = Message.create(trace_id=trace_id, role="tool", sequence=sequence, goal_id=current_goal_id, parent_sequence=head_seq, tool_call_id=tc["id"], branch_type=side_branch_ctx.type if side_branch_ctx else None, branch_id=side_branch_ctx.branch_id if side_branch_ctx else None, content={"tool_name": tool_name, "result": tool_content_for_llm})
+                        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))
+                            if tool_images:
+                                import base64 as b64mod
+                                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"]))
+                                        break
+                        yield tool_msg
+                        head_seq = sequence
+                        sequence += 1
+                        history.append({"role": "tool", "tool_call_id": tc["id"], "name": tool_name, "content": tool_content_for_llm, "_message_id": tool_msg.message_id})
+                        if tool_name == "skill" and tc["id"].startswith("call_skill_"):
+                            try:
+                                skill_args = json.loads(tc["function"]["arguments"]) if isinstance(tc["function"]["arguments"], str) else tc["function"]["arguments"]
+                                injected_skill_name = skill_args.get("skill_name", "")
+                                if injected_skill_name:
+                                    await self._update_skill_injection_record(trace_id, trace, injected_skill_name, tool_msg.message_id, tool_msg.sequence)
+                                    self.log.info(f"[Skill 指定注入] 已记录 {injected_skill_name} → msg={tool_msg.message_id}")
+                            except Exception as e:
+                                self.log.warning(f"[Skill 指定注入] 记录追踪失败: {e}")
+                else:
+                    for tc in tool_calls:
+                        current_goal_id = goal_tree.current_id if (goal_tree and goal_tree.current_id) else None
+    
+                        tool_name = tc["function"]["name"]
+                        tool_args = tc["function"]["arguments"]
+    
+                        if isinstance(tool_args, str):
+                            if not tool_args.strip():
+                                tool_args = {}
+                            else:
+                                try:
+                                    tool_args = json.loads(tool_args)
+                                except json.JSONDecodeError:
+                                    # 尝试修复常见的截断/格式问题
+                                    tool_args = self._try_fix_json(tool_args)
+                                    if tool_args is None:
+                                        self.log.warning(f"[Tool Call] JSON 解析失败,跳过工具调用 {tool_name}: {tc['function']['arguments'][:200]}")
+                                        # 修复 history 中 assistant message 里的残缺 JSON,
+                                        # 避免 Qwen API 拒绝 "function.arguments must be in JSON format"
+                                        tc["function"]["arguments"] = json.dumps(
+                                            {"_error": "JSON parse failed", "_raw": tc["function"]["arguments"][:200]},
+                                            ensure_ascii=False,
+                                        )
+                                        history.append({
+                                            "role": "tool",
+                                            "tool_call_id": tc["id"],
+                                            "content": f"Error: 工具参数 JSON 格式错误,无法解析。请重新生成正确的 JSON 参数调用此工具。原始参数: {tc['function']['arguments'][:200]}",
+                                        })
+                                        # 注意:这里不 yield Message,因为缺少必需参数会导致错误
+                                        # yield Message 应该由 trace_store 统一管理
+                                        continue
+                        elif tool_args is None:
+                            tool_args = {}
+    
+                        # 记录工具调用(INFO 级别,显示参数)
+                        args_str = json.dumps(tool_args, ensure_ascii=False)
+                        args_display = args_str[:100] + "..." if len(args_str) > 100 else args_str
+                        self.log.info(f"[Tool Call] {tool_name}({args_display})")
+    
+                        # 获取trigger_event(如果在knowledge_eval侧分支中)
+                        trigger_event_for_tool = None
+                        if side_branch_ctx and side_branch_ctx.type == "knowledge_eval" and self.trace_store:
+                            current_trace = await self.trace_store.get_trace(trace_id)
+                            if current_trace:
+                                trigger_event_for_tool = current_trace.context.get("active_side_branch", {}).get("trigger_event", "unknown")
+    
+                        # 设置 trace_id 上下文供 toolhub 使用(图片保存到 outputs/{trace_id}/)
+                        if tool_name in ("toolhub_call", "toolhub_search", "toolhub_health"):
+                            try:
+                                from agent.tools.builtin.toolhub import set_trace_context
+                                set_trace_context(trace_id)
+                            except ImportError:
+                                pass
+    
+                        tool_result = await self.tools.execute(
+                            tool_name,
+                            tool_args,
+                            uid=config.uid or "",
+                            context={
+                                "store": self.trace_store,
+                                "trace_id": trace_id,
+                                "goal_id": current_goal_id,
+                                "runner": self,
+                                "goal_tree": goal_tree,
+                                "knowledge_config": config.knowledge,
+                                "sequence": sequence,  # 添加sequence用于知识注入记录
+                                # 新增:侧分支信息
+                                "side_branch": {
+                                    "type": side_branch_ctx.type,
+                                    "branch_id": side_branch_ctx.branch_id,
+                                    "is_side_branch": True,
+                                    "max_turns": side_branch_ctx.max_turns,
+                                    "trigger_event": trigger_event_for_tool,
+                                } if side_branch_ctx else None,
+                                # 合并用户自定义 context(RunConfig.context)
+                                **(config.context or {}),
+                            },
+                        )
+    
+                        # 如果是 goal 工具,记录执行后的状态
+                        if tool_name == "goal" and goal_tree:
+                            self.log.debug(f"[Goal Tool] After execution: goal_tree.goals={len(goal_tree.goals)}, current_id={goal_tree.current_id}")
+    
+                        # 跟踪上传的知识(通过 upload_knowledge)
+                        if tool_name == "upload_knowledge" and isinstance(tool_result, dict):
+                            metadata = tool_result.get("metadata", {})
+                            # upload_knowledge 返回的是统计信息,不是单个 knowledge_id
+                            # 这里只记录上传动作,不跟踪具体 ID
+                            self.log.info(f"[Knowledge Tracking] 知识已上传到 Knowledge Manager")
+    
+                        # --- 支持多模态工具反馈 ---
+                        # 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_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({
+                                        "type": "image_url",
+                                        "image_url": {
+                                            "url": f"data:{media_type};base64,{img['data']}"
+                                        }
+                                    })
+                                elif img.get("type") == "url" and img.get("url"):
+                                    tool_content_for_llm.append({
+                                        "type": "image_url",
+                                        "image_url": {
+                                            "url": img["url"]
+                                        }
+                                    })
+                            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 = tool_text
+                            tool_content_for_llm = tool_text
+    
+                        tool_msg = Message.create(
+                            trace_id=trace_id,
+                            role="tool",
+                            sequence=sequence,
+                            goal_id=current_goal_id,
+                            parent_sequence=head_seq,
+                            tool_call_id=tc["id"],
+                            branch_type=side_branch_ctx.type if side_branch_ctx else None,
+                            branch_id=side_branch_ctx.branch_id if side_branch_ctx else None,
+                            # 存储完整内容:有图片时保留 list(含 image_url),纯文本时存字符串
+                            content={"tool_name": tool_name, "result": tool_content_for_llm},
+                        )
+    
+                        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),
                                 )
-                                self.log.info(f"[Skill 指定注入] 已记录 {injected_skill_name} → msg={tool_msg.message_id}")
-                        except Exception as e:
-                            self.log.warning(f"[Skill 指定注入] 记录追踪失败: {e}")
+                            # 截图单独存为同名 PNG 文件
+                            if tool_images:
+                                import base64 as b64mod
+                                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"]))
+                                        print(f"[Runner] 截图已保存: {png_path.name}")
+                                        break  # 只存第一张
+    
+                        # 如果在侧分支,tool_msg 已持久化(不需要额外维护)
+    
+                        yield tool_msg
+                        head_seq = sequence
+                        sequence += 1
+    
+                        history.append({
+                            "role": "tool",
+                            "tool_call_id": tc["id"],
+                            "name": tool_name,
+                            "content": tool_content_for_llm,
+                            "_message_id": tool_msg.message_id,
+                        })
+    
+                        # 更新 skill 注入追踪记录
+                        if tool_name == "skill" and tc["id"].startswith("call_skill_"):
+                            try:
+                                skill_args = json.loads(tc["function"]["arguments"]) if isinstance(tc["function"]["arguments"], str) else tc["function"]["arguments"]
+                                injected_skill_name = skill_args.get("skill_name", "")
+                                if injected_skill_name:
+                                    await self._update_skill_injection_record(
+                                        trace_id, trace, injected_skill_name,
+                                        tool_msg.message_id, tool_msg.sequence,
+                                    )
+                                    self.log.info(f"[Skill 指定注入] 已记录 {injected_skill_name} → msg={tool_msg.message_id}")
+                            except Exception as e:
+                                self.log.warning(f"[Skill 指定注入] 记录追踪失败: {e}")
 
                 # on_complete 模式:goal(done=...) 后立即压缩该 goal 的消息
                 if (
@@ -2541,8 +2645,16 @@ class AgentRunner:
             # 缩放图片(使用更快的 BILINEAR 算法)
             img_resized = img.resize((new_width, new_height), Image.Resampling.BILINEAR)
 
-            # 转换为 RGB(如果是 RGBA)
-            if img_resized.mode == "RGBA":
+            # 转换为 RGB(JPEG不支持 RGBA, P 等具有透明度或索引的模式)
+            if img_resized.mode != "RGB":
+                if img_resized.mode == "RGBA" or img_resized.mode == "P":
+                    # Create a white background for transparent images
+                    background = Image.new("RGB", img_resized.size, (255, 255, 255))
+                    if img_resized.mode == "P" and "transparency" in img_resized.info:
+                        img_resized = img_resized.convert("RGBA")
+                    if img_resized.mode == "RGBA":
+                        background.paste(img_resized, mask=img_resized.split()[3])
+                        img_resized = background
                 img_resized = img_resized.convert("RGB")
 
             # 重新编码为 JPEG(降低质量以加快速度)
@@ -2843,6 +2955,9 @@ class AgentRunner:
             if skills_text:
                 system_prompt += f"\n\n## Skills\n{skills_text}"
 
+        if config.max_iterations and config.max_iterations > 0:
+            system_prompt += f"\n\n## Execution Constraint\n这是一项有严格步数限制的任务。你最多可以用 {config.max_iterations} 轮交互来解决问题。\n请务必【边查边写、随时存档】!每当你收集或得出一个有价值的独立结果(如收集到一个独立 Case),请立刻调用工具写入或追加到结果文件中,绝对不要等到所有任务都做完再最后一次性输出。这样即使触达步数上限被强制打断,你已经收集的成果也能安全保留!"
+
         return system_prompt
 
     async def _generate_task_name(self, messages: List[Dict]) -> str:

+ 2 - 0
agent/llm/__init__.py

@@ -6,6 +6,7 @@ LLM Providers
 
 from .gemini import create_gemini_llm_call
 from .openrouter import create_openrouter_llm_call
+from .claude import create_claude_llm_call
 from .yescode import create_yescode_llm_call
 from .qwen import create_qwen_llm_call
 from .usage import TokenUsage, TokenUsageAccumulator, create_usage_from_response
@@ -20,6 +21,7 @@ __all__ = [
     # Providers
     "create_gemini_llm_call",
     "create_openrouter_llm_call",
+    "create_claude_llm_call",
     "create_yescode_llm_call",
     "create_qwen_llm_call",
     # Usage

+ 177 - 0
agent/llm/claude.py

@@ -0,0 +1,177 @@
+"""
+Native Anthropic Provider
+
+直接使用 Anthropic 原生 API (或完全兼容原生协议的反代如 imds.ai) 调用 Claude 模型。
+复用 openrouter.py 中的格式化拦截模块(无缝支持 AgentRunner Cache Control)。
+"""
+
+import os
+import asyncio
+import logging
+import httpx
+from typing import List, Dict, Any, Optional
+
+from .pricing import calculate_cost
+
+# 直接复用底层已经在 openrouter 内部写好的格式转换/拦截器
+from .openrouter import (
+    _normalize_tool_call_ids,
+    _to_anthropic_messages,
+    _to_anthropic_tools,
+    _parse_anthropic_response,
+    _RETRYABLE_EXCEPTIONS
+)
+
+logger = logging.getLogger(__name__)
+
+async def anthropic_native_llm_call(
+    messages: List[Dict[str, Any]],
+    model: str = "claude-3-5-sonnet-20241022",
+    tools: Optional[List[Dict]] = None,
+    **kwargs
+) -> Dict[str, Any]:
+    """
+    原生 Anthropic API 调用函数
+    """
+    api_key = os.getenv("ANTHROPIC_API_KEY")
+    if not api_key:
+        raise ValueError("ANTHROPIC_API_KEY environment variable not set")
+        
+    base_url = os.getenv("ANTHROPIC_BASE_URL", "https://api.anthropic.com")
+    endpoint = f"{base_url.rstrip('/')}/v1/messages"
+    
+    anthropic_version = os.getenv("ANTHROPIC_VERSION", "2023-06-01")
+
+    # 去掉 anthropic/ opneai/ 等命名空间前缀(如果传入的话)
+    if "/" in model:
+        model = model.split("/", 1)[1]
+
+    # 工具前缀规范化为 toolu
+    messages = _normalize_tool_call_ids(messages, "toolu")
+    
+    # 转换为 Anthropic 格式(这一步也会自动把 Cache Control 拦截写入 payload)
+    system_prompt, anthropic_messages = _to_anthropic_messages(messages)
+    
+    # ── Anthropic 原生 API 严格校验:中间位置的 assistant 消息不能有空 content ──
+    # OpenRouter 对此容忍,但原生 API 会直接 400。因此做净化处理:
+    # 1. 非末尾的 assistant 消息若 content 为空字符串 → 补一个占位符
+    # 2. content 为空列表 [] → 同样补位
+    sanitized = []
+    for i, m in enumerate(anthropic_messages):
+        is_last = (i == len(anthropic_messages) - 1)
+        if m.get("role") == "assistant" and not is_last:
+            c = m.get("content", "")
+            if c == "" or c == [] or c is None:
+                # 跳过完全空的中间 assistant 消息(通常是历史 bug)
+                logger.debug("[Anthropic Native] Dropped empty assistant message at index %d", i)
+                continue
+        sanitized.append(m)
+    anthropic_messages = sanitized
+
+    payload: Dict[str, Any] = {
+        "model": model,
+        "messages": anthropic_messages,
+        "max_tokens": kwargs.get("max_tokens", 8192),
+    }
+    
+    if system_prompt is not None:
+        payload["system"] = system_prompt
+    if tools:
+        payload["tools"] = _to_anthropic_tools(tools)
+    if "temperature" in kwargs:
+        payload["temperature"] = kwargs["temperature"]
+
+    # Debug: 检查 cache_control 是否存在
+    if logger.isEnabledFor(logging.DEBUG):
+        cache_control_count = 0
+        if isinstance(system_prompt, list):
+            for block in system_prompt:
+                if isinstance(block, dict) and "cache_control" in block:
+                    cache_control_count += 1
+        for msg in anthropic_messages:
+            content = msg.get("content", "")
+            if isinstance(content, list):
+                for block in content:
+                    if isinstance(block, dict) and "cache_control" in block:
+                        cache_control_count += 1
+        if cache_control_count > 0:
+            logger.debug(f"[Anthropic Native] 发现 {cache_control_count} 个 cache_control 标记,将被发送到原生端点")
+
+    headers = {
+        "x-api-key": api_key,
+        "anthropic-version": anthropic_version,
+        "content-type": "application/json",
+    }
+    
+    # 支持外部打标签如 anthropic_beta 字段
+    if kwargs.get("anthropic_beta"):
+        headers["anthropic-beta"] = kwargs["anthropic_beta"]
+
+    max_retries = 3
+    last_exception = None
+    
+    for attempt in range(max_retries):
+        async with httpx.AsyncClient(timeout=300.0) as client:
+            try:
+                response = await client.post(endpoint, json=payload, headers=headers)
+                response.raise_for_status()
+                result = response.json()
+                break
+                
+            except httpx.HTTPStatusError as e:
+                status = e.response.status_code
+                error_body = e.response.text
+                if status in (429, 500, 502, 503, 504) and attempt < max_retries - 1:
+                    wait = 2 ** attempt * 2
+                    logger.warning("[Anthropic Native] HTTP %d (attempt %d/%d), retrying in %ds: %s", status, attempt + 1, max_retries, wait, error_body[:200])
+                    await asyncio.sleep(wait)
+                    last_exception = e
+                    continue
+                logger.error("[Anthropic Native] HTTP %d error body: %s", status, error_body)
+                print(f"[Anthropic Native] API Error {status}: {error_body[:500]}")
+                raise
+                
+            except _RETRYABLE_EXCEPTIONS as e:
+                last_exception = e
+                if attempt < max_retries - 1:
+                    wait = 2 ** attempt * 2
+                    logger.warning("[Anthropic Native] %s (attempt %d/%d), retrying in %ds", type(e).__name__, attempt + 1, max_retries, wait)
+                    await asyncio.sleep(wait)
+                    continue
+                raise
+    else:
+        raise last_exception  # type: ignore[misc]
+
+    # 解析响应并抽离 Usage
+    parsed = _parse_anthropic_response(result)
+    usage = parsed["usage"]
+    cost = calculate_cost(model, usage)
+
+    return {
+        "content": parsed["content"],
+        "tool_calls": parsed["tool_calls"],
+        "prompt_tokens": usage.input_tokens,
+        "completion_tokens": usage.output_tokens,
+        "reasoning_tokens": usage.reasoning_tokens,
+        "cache_creation_tokens": usage.cache_creation_tokens,
+        "cache_read_tokens": usage.cache_read_tokens,
+        "finish_reason": parsed["finish_reason"],
+        "cost": cost,
+        "usage": usage,
+    }
+
+
+def create_claude_llm_call(model: str = "claude-3-5-sonnet-20241022"):
+    """
+    创建 Anthropic 原生 LLM 调用函数
+    
+    Args:
+        model: 模型名称如 "claude-3-5-sonnet-20241022" 
+               (可带前缀,将会自动截取)
+
+    Returns:
+        异步 LLM 调用函数提供给 AgentRunner
+    """
+    async def llm_call(messages: List[Dict[str, Any]], model: str = model, tools: Optional[List[Dict]] = None, **kwargs) -> Dict[str, Any]:
+        return await anthropic_native_llm_call(messages, model, tools, **kwargs)
+    return llm_call

+ 2 - 2
agent/tools/builtin/__init__.py

@@ -30,8 +30,8 @@ from agent.tools.builtin.content import (
     extract_video_clip, import_content,
 )
 from agent.trace.goal_tool import goal
-# 导入浏览器工具以触发注册
-import agent.tools.builtin.browser  # noqa: F401
+# 导入浏览器工具以触发注册 (因 P1 流水线不需要,且加载缓慢,暂时全局屏蔽)
+# import agent.tools.builtin.browser  # noqa: F401
 
 import agent.tools.builtin.feishu
 import agent.tools.builtin.im

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

@@ -158,7 +158,7 @@ def _kill_process_tree(pid: int) -> None:
         pass
 
 
-@tool(description="执行 bash 命令", hidden_params=["context"], groups=["core"])
+@tool(description="执行 bash 命令", hidden_params=["context"], groups=["system"])
 async def bash_command(
     command: str,
     timeout: Optional[int] = None,

+ 3 - 1
agent/tools/builtin/content/cache.py

@@ -11,7 +11,9 @@ import time
 from pathlib import Path
 from typing import Any, Dict, List, Optional
 
-_CACHE_DIR = Path("/tmp")
+import tempfile
+_CACHE_DIR = Path(tempfile.gettempdir()) / "agent_content_cache"
+_CACHE_DIR.mkdir(parents=True, exist_ok=True)
 _CACHE_TTL = 3600  # 1 小时过期
 
 

+ 26 - 7
agent/tools/builtin/content/platforms/aigc_channel.py

@@ -133,11 +133,12 @@ async def search(
     # 封面拼图
     images = []
     try:
-        collage_b64 = await _build_collage(posts)
-        if collage_b64:
-            images.append({"type": "base64", "media_type": "image/png", "data": collage_b64})
-    except Exception:
-        pass
+        collage_obj = await _build_collage(posts)
+        if collage_obj:
+            images.append(collage_obj)
+    except Exception as e:
+        import logging
+        logging.getLogger(__name__).warning("Error generating collage: %s", e)
 
     return ToolResult(
         title=f"搜索: {keyword} ({platform_id})",
@@ -216,8 +217,26 @@ async def _build_collage(posts: List[Dict[str, Any]]) -> Optional[str]:
         return None
 
     grid = build_image_grid(images=valid_images, labels=valid_labels)
-    b64, _ = encode_base64(grid, format="PNG")
-    return b64
+    import io
+    buf = io.BytesIO()
+    grid.save(buf, format="PNG")
+    img_bytes = buf.getvalue()
+    
+    # 尝试上传到 CDN,替换冗长的 base64
+    try:
+        from agent.tools.builtin.file.image_cdn import _upload_bytes_to_oss
+        import hashlib
+        
+        md5_hash = hashlib.md5(img_bytes).hexdigest()[:12]
+        filename = f"collage_search_{md5_hash}.png"
+        cdn_url = await _upload_bytes_to_oss(img_bytes, filename)
+        return {"type": "url", "url": cdn_url}
+    except Exception as e:
+        import logging
+        logging.getLogger(__name__).warning("Failed to upload collage to CDN: %s", e)
+        # 降级:还是用 base64 但可能会超长
+        b64, _ = encode_base64(grid, format="PNG")
+        return {"type": "base64", "media_type": "image/png", "data": b64}
 
 
 # ── 注册所有 AIGC 平台 ──

+ 21 - 5
agent/tools/builtin/content/platforms/x.py

@@ -49,9 +49,9 @@ async def search(
 
         # 拼图
         images = []
-        collage_b64 = await _build_tweet_collage(tweets[:max_count])
-        if collage_b64:
-            images.append({"type": "base64", "media_type": "image/png", "data": collage_b64})
+        collage_obj = await _build_tweet_collage(tweets[:max_count])
+        if collage_obj:
+            images.append(collage_obj)
 
         return ToolResult(
             title=f"X: {keyword}",
@@ -113,8 +113,24 @@ async def _build_tweet_collage(tweets: List[Dict[str, Any]]) -> Optional[str]:
         return None
 
     grid = build_image_grid(images=valid_images, labels=valid_labels)
-    b64, _ = encode_base64(grid, format="PNG")
-    return b64
+    import io
+    buf = io.BytesIO()
+    grid.save(buf, format="PNG")
+    img_bytes = buf.getvalue()
+    
+    try:
+        from agent.tools.builtin.file.image_cdn import _upload_bytes_to_oss
+        import hashlib
+        
+        md5_hash = hashlib.md5(img_bytes).hexdigest()[:12]
+        filename = f"x_collage_{md5_hash}.png"
+        cdn_url = await _upload_bytes_to_oss(img_bytes, filename)
+        return {"type": "url", "url": cdn_url}
+    except Exception as e:
+        import logging
+        logging.getLogger(__name__).warning("Failed to upload x collage to CDN: %s", e)
+        b64, _ = encode_base64(grid, format="PNG")
+        return {"type": "base64", "media_type": "image/png", "data": b64}
 
 
 # ── 注册 ──

+ 21 - 5
agent/tools/builtin/content/platforms/youtube.py

@@ -55,9 +55,9 @@ async def search(
 
         # 拼图
         images = []
-        collage_b64 = await _build_video_collage(videos[:max_count])
-        if collage_b64:
-            images.append({"type": "base64", "media_type": "image/png", "data": collage_b64})
+        collage_obj = await _build_video_collage(videos[:max_count])
+        if collage_obj:
+            images.append(collage_obj)
 
         return ToolResult(
             title=f"YouTube: {keyword}",
@@ -183,8 +183,24 @@ async def _build_video_collage(videos: List[Dict[str, Any]]) -> Optional[str]:
         return None
 
     grid = build_image_grid(images=valid_images, labels=valid_labels)
-    b64, _ = encode_base64(grid, format="PNG")
-    return b64
+    import io
+    buf = io.BytesIO()
+    grid.save(buf, format="PNG")
+    img_bytes = buf.getvalue()
+    
+    try:
+        from agent.tools.builtin.file.image_cdn import _upload_bytes_to_oss
+        import hashlib
+        
+        md5_hash = hashlib.md5(img_bytes).hexdigest()[:12]
+        filename = f"youtube_collage_{md5_hash}.png"
+        cdn_url = await _upload_bytes_to_oss(img_bytes, filename)
+        return {"type": "url", "url": cdn_url}
+    except Exception as e:
+        import logging
+        logging.getLogger(__name__).warning("Failed to upload youtube collage to CDN: %s", e)
+        b64, _ = encode_base64(grid, format="PNG")
+        return {"type": "base64", "media_type": "image/png", "data": b64}
 
 
 # ── 注册 ──

+ 14 - 1
agent/tools/builtin/content/tools.py

@@ -27,10 +27,13 @@ import agent.tools.builtin.content.platforms.youtube       # noqa: F401
 import agent.tools.builtin.content.platforms.x             # noqa: F401
 
 
-def _get_trace_id(context: Optional[ToolContext]) -> str:
+def _get_trace_id(context: Optional[Dict[str, Any]]) -> str:
     """从 context 取 trace_id,回退到环境变量或自动生成"""
+    if isinstance(context, dict) and context.get("trace_id"):
+        return context["trace_id"]
     if context and hasattr(context, "trace_id") and context.trace_id:
         return context.trace_id
+    
     return os.getenv("TRACE_ID") or f"anon-{uuid.uuid4().hex[:8]}"
 
 
@@ -116,6 +119,11 @@ async def content_search(
     if not pdef.search_impl:
         return ToolResult(title="不支持搜索", output=f"平台 {pdef.name} 暂不支持搜索")
 
+    try:
+        max_count = int(max_count)
+    except (ValueError, TypeError):
+        max_count = 20
+
     result = await pdef.search_impl(
         platform_id=pdef.id,
         keyword=keyword,
@@ -156,6 +164,11 @@ async def content_detail(
     if not pdef:
         return ToolResult(title="平台不存在", output=f"未找到平台 '{platform}'")
 
+    try:
+        index = int(index)
+    except (ValueError, TypeError):
+        return ToolResult(title="参数错误", output="参数 index 必须是整数", error="Invalid index parameter")
+
     trace_id = _get_trace_id(context)
     post = _cache.get_cached_post(trace_id, pdef.id, index)
 

+ 2 - 0
agent/tools/builtin/file/__init__.py

@@ -6,6 +6,7 @@ File tools - 文件操作工具
 
 from .read import read_file
 from .write import write_file
+from .write_json import write_json
 from .edit import edit_file
 from .glob import glob_files
 from .grep import grep_content
@@ -13,6 +14,7 @@ from .grep import grep_content
 __all__ = [
     "read_file",
     "write_file",
+    "write_json",
     "edit_file",
     "glob_files",
     "grep_content",

+ 125 - 0
agent/tools/builtin/file/image_cdn.py

@@ -0,0 +1,125 @@
+"""
+image_cdn.py - 将内容中的外站图片 URL 转换为自有 CDN 链接
+
+使用场景:在 write_json / write_file 落盘前调用,自动把
+  小红书、B站、知乎、微博等平台的图片链接替换为 res.cybertogether.net CDN 链接,
+  防止外站图片过期/防盗链导致后续流程无法访问。
+"""
+
+import hashlib
+import json
+import logging
+import re
+from typing import Any, Union
+import httpx
+
+logger = logging.getLogger(__name__)
+
+# ── 匹配需要转存的外站图片 URL(只抓图片后缀或明显的图床域名)──────────────────
+_IMG_URL_RE = re.compile(
+    r'https?://(?!'  # 排除自有 CDN,不重复上传
+    r'res\.cybertogether\.net'
+    r')'
+    r'[^\s"\'<>]+'  # URL 主体
+    r'(?:'
+        r'\.(?:jpg|jpeg|png|gif|webp|avif|bmp|svg)'  # 明确图片后缀
+        r'|'
+        r'(?:xhscdn\.com|bdimg\.com|hdslb\.com|zhihu\.com/p/[^/]+\.(?:jpg|png)'
+        r'|sinaimg\.cn|wx\d+\.sinaimg\.cn|mmbiz\.qpic\.cn'  # 微博/微信图床
+        r'|imagev[12]\.meitudata\.com'  # 美图
+        r'|p[0-9]\.douyinpic\.com'  # 抖音封面
+        r'|i0\.hdslb\.com|article\.biliimg\.com'  # B站
+        r')'
+    r')',
+    re.IGNORECASE,
+)
+
+BUCKET_NAME = "aigc-admin"
+BUCKET_PATH = "crawler/image"
+CDN_BASE = "https://res.cybertogether.net"
+
+
+async def _upload_bytes_to_oss(data: bytes, filename: str) -> str:
+    """上传 bytes 到 OSS 并返回 CDN URL,失败时抛出异常"""
+    from cyber_sdk.ali_oss import _upload_v2
+    result = await _upload_v2(
+        file_name=filename,
+        file_content=data,
+        bucket_path=BUCKET_PATH,
+        bucket_name=BUCKET_NAME,
+    )
+    oss_key = result.get("oss_object_key")
+    if not oss_key:
+        raise ValueError(f"OSS response missing oss_object_key: {result}")
+    return f"{CDN_BASE}/{oss_key}"
+
+
+async def _download_image(url: str) -> bytes:
+    """下载图片 bytes,失败抛出异常"""
+    async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
+        resp = await client.get(url, headers={"Referer": url, "User-Agent": "Mozilla/5.0"})
+        resp.raise_for_status()
+        return resp.content
+
+
+def _ext_from_url(url: str) -> str:
+    """从 URL 猜测文件扩展名,默认 jpg"""
+    path = url.split("?")[0].lower()
+    for ext in ("png", "gif", "webp", "avif", "bmp", "svg", "jpg", "jpeg"):
+        if path.endswith(f".{ext}"):
+            return ext
+    return "jpg"
+
+
+async def replace_image_urls(text: str) -> str:
+    """
+    扫描 text 中所有外站图片 URL,下载并上传到自有 OSS,原地替换为 CDN 链接。
+
+    - 已是 res.cybertogether.net 的 URL 直接跳过
+    - 下载/上传失败的 URL 保留原值,只打 WARNING 日志
+    - 同一 URL 使用 MD5 去重(同一次调用内)
+    """
+    urls = list(dict.fromkeys(_IMG_URL_RE.findall(text)))  # 去重但保留顺序
+    if not urls:
+        return text
+
+    import asyncio
+
+    async def _process_single_url(url: str) -> tuple[str, str]:
+        url_hash = hashlib.md5(url.encode()).hexdigest()[:12]
+        ext = _ext_from_url(url)
+        filename = f"{url_hash}.{ext}"
+        try:
+            data = await _download_image(url)
+            cdn_url = await _upload_bytes_to_oss(data, filename)
+            logger.info("[ImageCDN] %s → %s", url[:60], cdn_url)
+            return url, cdn_url
+        except Exception as e:
+            logger.warning("[ImageCDN] Failed to mirror %s: %s (%s)", url[:60], str(e) or repr(e), type(e).__name__)
+            return url, url
+
+    tasks = [_process_single_url(u) for u in urls]
+    results = await asyncio.gather(*tasks)
+    url_map: dict[str, str] = dict(results)
+
+    for orig, cdn in url_map.items():
+        if orig != cdn:
+            text = text.replace(orig, cdn)
+
+    replaced = sum(1 for o, c in url_map.items() if o != c)
+    if replaced:
+        logger.info("[ImageCDN] Replaced %d/%d image URLs with CDN links", replaced, len(urls))
+
+    return text
+
+
+async def replace_image_urls_in_obj(obj: Any) -> Any:
+    """
+    递归扫描 dict/list 中所有字符串值,替换其中的外站图片 URL。
+    先整体 JSON 序列化再做替换,再反序列化,效率高且避免遗漏嵌套字段。
+    """
+    raw = json.dumps(obj, ensure_ascii=False)
+    replaced = await replace_image_urls(raw)
+    if replaced == raw:
+        return obj
+    return json.loads(replaced)

+ 9 - 0
agent/tools/builtin/file/write.py

@@ -75,6 +75,15 @@ async def write_file(
     # 确保父目录存在
     path.parent.mkdir(parents=True, exist_ok=True)
 
+    # 落盘前自动将内容中的外站图片 URL 替换为自有 CDN 链接(仅处理文本文件)
+    if not append:  # 追加模式不做替换,避免重复处理
+        try:
+            from agent.tools.builtin.file.image_cdn import replace_image_urls
+            new_content = await replace_image_urls(new_content)
+        except Exception as cdn_err:
+            import logging
+            logging.getLogger(__name__).warning("[write_file] CDN mirror step failed, writing original: %s", cdn_err)
+
     # 写入文件
     try:
         with open(path, 'w', encoding='utf-8') as f:

+ 99 - 0
agent/tools/builtin/file/write_json.py

@@ -0,0 +1,99 @@
+"""
+Write JSON Tool - 用于直接安全写入结构化 JSON 数据,避免大模型生成超长转义文本导致的截断和报错
+"""
+
+import json
+from pathlib import Path
+from typing import Optional, Dict, Any
+
+from agent.tools import tool, ToolResult, ToolContext
+
+
+@tool(description="专门且唯一安全的 JSON 数据文件写入工具。传入 Python Dict/Object,自动为你生成格式化和转义无误的 JSON 文件。严禁使用普通的 write_file 写 JSON。参数 file_path 是文件绝对路径字符串,json_data 是要写入的原生 JSON 对象(直接传 dict,无需提前序列化)", hidden_params=["context"], groups=["core"])
+async def write_json(
+    file_path: str = "",
+    json_data: dict = None,
+    context: Optional[ToolContext] = None
+) -> ToolResult:
+    # 参数防空保护 - 给出明确错误而非空报错
+    if not file_path:
+        return ToolResult(
+            title="参数错误",
+            output="请提供 file_path 参数:file_path 必须是一个有效的绝对文件路径字符串,例如 '/path/to/output.json'",
+            error="Missing required argument: file_path"
+        )
+    if json_data is None:
+        return ToolResult(
+            title="参数错误",
+            output="请提供 json_data 参数:json_data 必须是一个原生的 Python dict 对象,请将要写入的 JSON 结构直接作为对象传入,无需序列化为字符串",
+            error="Missing required argument: json_data"
+        )
+    """
+    专门安全地写入结构化 JSON 数据到文件。
+
+    Args:
+        file_path: 要写入的绝对或相对文件路径。
+        json_data: 要写入的数据对象 (object/dict)。
+        context: 工具上下文
+
+    Returns:
+        ToolResult: 写入操作结果
+    """
+    path = Path(file_path)
+    if not path.is_absolute():
+        path = Path.cwd() / path
+
+    if path.exists() and path.is_dir():
+        return ToolResult(
+            title="路径错误",
+            output=f"路径是目录,不是文件: {file_path}",
+            error="Path is a directory"
+        )
+
+    path.parent.mkdir(parents=True, exist_ok=True)
+
+    # 自动检测并修正类型:如果模型传进来的是 JSON 字符串,先反序列化再写入
+    # 这解决了模型把 dict 当成 JSON 字符串传入导致的双重序列化问题
+    if isinstance(json_data, str):
+        try:
+            json_data = json.loads(json_data)
+        except json.JSONDecodeError as e:
+            return ToolResult(
+                title="JSON 解析失败",
+                output=f"json_data 参数作为 JSON 字符串解析失败,错误在这附近:{e}\n\n"
+                       f"[系统强制警告] 你的 JSON 生成存在语法错误(很可能是 description 中的未转义双引号、换行符,或者缺少逗号)。\n"
+                       f"⚠️ 严禁使用 write_file 回退硬写!用 write_file 强行写入坏 JSON 会导致整个流水线后续崩溃死锁!\n"
+                       f"你必须立即检查并修复字符串内的转义问题,然后重新调用 write_json!",
+                error=str(e)
+            )
+
+    try:
+        # 落盘前自动将 JSON 数据中的外站图片 URL 替换为自有 CDN 链接
+        try:
+            from agent.tools.builtin.file.image_cdn import replace_image_urls_in_obj
+            json_data = await replace_image_urls_in_obj(json_data)
+        except Exception as cdn_err:
+            import logging
+            logging.getLogger(__name__).warning("[write_json] CDN mirror step failed, writing original: %s", cdn_err)
+
+        with open(path, 'w', encoding='utf-8') as f:
+            json.dump(json_data, f, ensure_ascii=False, indent=2)
+            
+        json_str = json.dumps(json_data, ensure_ascii=False, indent=2)
+        lines = len(json_str.split('\n'))
+
+        return ToolResult(
+            title=path.name,
+            output=f"JSON 数据已安全且完美地格式化写入: {path.name}",
+            metadata={
+                "lines": lines,
+                "existed": path.exists()
+            },
+            long_term_memory=f"安全覆盖写入了结构化 JSON 文件 {path.name}"
+        )
+    except Exception as e:
+        return ToolResult(
+            title="JSON 写入失败",
+            output=f"无法写入 JSON 到文件: {str(e)}",
+            error=str(e)
+        )

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

@@ -193,13 +193,10 @@ def _aggregate_stats(results: List[Dict[str, Any]]) -> Dict[str, Any]:
 
 
 def _get_allowed_tools(single: bool, context: dict) -> Optional[List[str]]:
-    """获取允许工具列表。single=True: 全部(去掉 agent/evaluate); single=False: 只读"""
-    if not single:
-        return ["read_file", "grep_content", "glob_files", "goal"]
-    # single (delegate): 获取所有工具,排除 agent 和 evaluate
+    """获取允许工具列表。获取所有工具,排除 agent 和 evaluate"""
     runner = context.get("runner")
-    if runner and hasattr(runner, "tools") and hasattr(runner.tools, "registry"):
-        all_tools = list(runner.tools.registry.keys())
+    if runner and hasattr(runner, "tools") and hasattr(runner.tools, "_tools"):
+        all_tools = list(runner.tools._tools.keys())
         return [t for t in all_tools if t not in ("agent", "evaluate")]
     return None
 
@@ -501,6 +498,7 @@ async def _run_agents(
         debug = getattr(runner, 'debug', False)
         agent_label = (agent_type or ("delegate" if single else f"explore-{i+1}"))
         debug_printer = _make_event_printer(agent_label) if debug else None
+        # 为了彻底封死逃逸风险,禁用由于缺省 tool_groups 注入进来的 agent 和 bash_command
         on_event = _make_interactive_handler(
             runner, cur_stid, trace_id, debug_printer=debug_printer
         )
@@ -510,9 +508,12 @@ async def _run_agents(
             config=_make_run_config(
                 trace_id=cur_stid,
                 agent_type=agent_type or ("delegate" if single else "explore"),
+                max_iterations=50 if single else 50,
                 model=parent_trace.model if parent_trace else "gpt-4o",
                 uid=parent_trace.uid if parent_trace else None,
                 tools=allowed_tools,
+                tool_groups=[], # 清空默认的 "core" 组,防止把 agent 和 bash_command 偷渡进来
+                exclude_tools=["agent", "evaluate", "bash_command"], # 严格锁死危险元工具
                 name=task_item[:50],
                 skills=skills,
                 knowledge=context.get("knowledge_config"),
@@ -580,11 +581,24 @@ async def _run_agents(
                 "sub_trace_id": stid,
             }
     else:
-        # 多任务并行执行
-        raw_results = await asyncio.gather(
-            *(coro for _, _, _, coro in coros),
-            return_exceptions=True,
-        )
+        # 检查父 Agent 是否开启了并发配置
+        is_parallel = getattr(runner.config, "parallel_tool_execution", True) if runner and hasattr(runner, "config") else True
+        
+        if is_parallel:
+            # 多任务并行执行
+            raw_results = await asyncio.gather(
+                *(coro for _, _, _, coro in coros),
+                return_exceptions=True,
+            )
+        else:
+            # 多任务串行执行(为了省显存/内存)
+            raw_results = []
+            for _, _, _, coro in coros:
+                try:
+                    res = await coro
+                    raw_results.append(res)
+                except Exception as e:
+                    raw_results.append(e)
 
         processed_results = []
         for idx, raw in enumerate(raw_results):
@@ -755,6 +769,17 @@ async def agent(
         return {"status": "failed", "error": f"Missing required context: {', '.join(missing)}"}
 
     # 归一化 task → list
+    if isinstance(task, str):
+        task_str = task.strip()
+        if task_str.startswith("[") and task_str.endswith("]"):
+            try:
+                import json
+                parsed_task = json.loads(task_str)
+                if isinstance(parsed_task, list):
+                    task = parsed_task
+            except:
+                pass
+
     single = isinstance(task, str)
     tasks = [task] if single else task
 

+ 3 - 1
agent/tools/registry.py

@@ -264,8 +264,10 @@ class ToolRegistry:
 				arguments = replace_sensitive_data(arguments, sensitive_data, current_url)
 
 			# 准备参数:只注入函数需要的参数
-			kwargs = {**arguments}
 			sig = inspect.signature(func)
+			# 过滤掉函数签名中不存在的参数(如 Claude SDK 发送的 {"_": true} 占位符)
+			valid_params = set(sig.parameters.keys())
+			kwargs = {k: v for k, v in arguments.items() if k in valid_params}
 
 			# 注入隐藏参数(hidden_params)
 			hidden_params = tool_info.get("hidden_params", [])

+ 0 - 64
examples/process/config.py

@@ -1,64 +0,0 @@
-"""
-项目配置
-"""
-
-from agent.core.runner import KnowledgeConfig, RunConfig
-
-
-# ===== Stage 1:调研配置 =====
-
-RESEARCH_RUN_CONFIG = RunConfig(
-    model="qwen3.5-plus",
-    temperature=0.3,
-    max_iterations=1000,
-    extra_llm_params={"extra_body": {"enable_thinking": True}},
-    agent_type="main",
-    name="工具调研与文档生成",
-    knowledge=KnowledgeConfig(
-        enable_extraction=False,
-        enable_completion_extraction=True,
-        enable_injection=False,
-        owner="sunlit.howard@gmail.com",
-        default_tags={"project": "tool_research", "domain": "ai_agent"},
-        default_scopes=["org:cybertogether"],
-        default_search_types=["tool", "guide"],
-        default_search_owner="sunlit.howard@gmail.com"
-    )
-)
-
-# ===== Stage 2:分析配置 =====
-
-ANALYSIS_RUN_CONFIG = RunConfig(
-    model="anthropic/claude-sonnet-4-6",
-    temperature=0.3,
-    max_iterations=500,
-    agent_type="coordinator",
-    name="工作流分析 Pipeline",
-)
-
-ANALYSIS_MODEL = "anthropic/claude-sonnet-4-6"
-
-# ===== 输出目录 =====
-
-RESEARCH_OUTPUT_DIR = "examples/tool_research_v2/output"            # Stage 1 输出根目录
-ANALYSIS_OUTPUT_DIR = "examples/tool_research_v2/output/analysis"   # Stage 2 输出目录
-
-
-# ===== 基础设施配置 =====
-
-SKILLS_DIR = "./skills"
-TRACE_STORE_PATH = ".trace"
-DEBUG = True
-LOG_LEVEL = "INFO"
-LOG_FILE = None
-
-# ===== 浏览器配置 =====
-BROWSER_TYPE = "local"
-HEADLESS = False
-
-# ===== IM 配置 =====
-IM_ENABLED = True
-IM_CONTACT_ID = "agent_research"
-IM_SERVER_URL = "ws://43.106.118.91:8105"
-IM_WINDOW_MODE = True
-IM_NOTIFY_INTERVAL = 10.0

+ 0 - 27
examples/process/presets.json

@@ -1,27 +0,0 @@
-{
-  "main": {
-    "max_iterations": 1000,
-    "skills": ["planning"],
-    "description": "主 Agent - 调研任务管理与协调"
-  },
-  "research": {
-    "system_prompt_file": "prompts/research.prompt",
-    "max_iterations": 200,
-    "temperature": 0.3,
-    "skills": ["planning", "research", "browser"],
-    "description": "调研 Agent - 根据指令搜索策略、工具、方法论等信息"
-  },
-  "coordinator": {
-    "system_prompt_file": "prompts/coordinator.prompt",
-    "max_iterations": 500,
-    "skills": ["planning"],
-    "description": "工作流分析协调器:编排4步分析流程"
-  },
-  "analyst": {
-    "system_prompt_file": "prompts/analyst.prompt",
-    "max_iterations": 200,
-    "temperature": 0.3,
-    "skills": [],
-    "description": "分析子 Agent:执行单步分析任务(意图归纳、聚类、变体发现、粗工序提取)"
-  }
-}

+ 0 - 23
examples/process/prompts/analyst.prompt

@@ -1,23 +0,0 @@
----
-temperature: 0.3
----
-
-$system$
-
-## 角色
-你是一个专注的工作流分析专家。你负责执行工作流分析 Pipeline 中的单步分析任务,包括:
-- 意图归纳:将一条工作流的技术步骤归纳为意图级描述
-- 语义聚类:对跨工作流的意图列表做语义聚类,发现能力模块
-- 变体发现:在一个能力模块内发现不同实现变体
-- 粗工序总结:对一个品类的工作流总结因果性编排思路
-
-## 核心原则
-1. **忠实于数据**:不臆造不存在于原始步骤中的意图或工具
-2. **意图粒度判断**:归纳意图时,由你根据上下文判断合并粒度,目标是让不同工作流可比较
-3. **结构化输出**:始终返回符合任务要求的 JSON 格式
-4. **因果推理**:总结粗工序时,必须解释"为什么先做 A 再做 B",而不是简单列举频次
-
-## 输出规范
-- 意图描述:20 字以内,动宾结构(如"锁定产品物理结构")
-- 能力模块名称:4-8 字,品类无关(如"定义风格"而非"电商风格定义")
-- 所有输出必须是合法 JSON,不要包含 markdown 代码块标记

+ 0 - 179
examples/process/prompts/coordinator.prompt

@@ -1,179 +0,0 @@
----
-temperature: 0.3
----
-
-$system$
-
-## 角色
-你是一个工作流分析协调器。你的任务是对已采集的 AI 创作工作流数据进行结构化分析,提炼出可复用的工序体系(细工序 + 粗工序),并输出完整的结构化 JSON 报告。
-
-## 输入数据格式
-你将收到一个 JSON 对象,包含:
-- `workflows`:工作流列表,每条工作流有 `id`(如 `wf_001`)、`name`、`category`(可能为空)、`source_channel`、`steps`(步骤数组,每步有 `步骤描述`、`使用工具`、`用户输入`、`输出结果`)
-
-## 可用工具
-- `agent`:调用 analyst 子 agent 执行分析任务
-- `write_file`:写中间结果和最终输出
-- `read_file`:读取中间结果
-
-## 原子能力表
-在 `%output_dir%/atomic_capabilities.json` 中已预先准备好原子能力表,格式为:
-```json
-{
-  "atomic_capabilities": [
-    {
-      "id": "能力ID",
-      "name": "能力名称",
-      "description": "能力描述",
-      "criterion": "判断标准"
-    }
-  ]
-}
-```
-在 Step 2b 匹配时,使用 `read_file` 读取该文件。
-
-## 执行流程
-
-### Step 1:意图归纳(粒度统一化)
-
-对**每一条**工作流,调用一个 analyst 子 agent,任务如下:
-```
-读取工作流 {wf_id} 的完整步骤,将步骤重新归纳为「意图级描述」。
-规则:
-- 允许把多个连续步骤合并为一个意图(如9个技术步骤 → 3个意图)
-- 允许一个步骤就对应一个意图
-- 意图粒度由你根据上下文判断,目标是让不同工作流的步骤在意图层面可比较
-- 每个意图记录:意图描述(20字以内)、涉及的原始步骤序号、核心工具名称
-输出为 JSON 数组。
-```
-
-将所有工作流的意图归纳结果合并,写入 `%output_dir%/step1_intents.json`,格式:
-```json
-{
-  "wf_001": [
-    {"intent": "意图描述", "source_steps": [步骤序号], "tools": ["工具名称"]},
-    ...
-  ]
-}
-```
-
-**注意**:Step 1 可以并行调用多个 analyst(每条工作流一个),提高效率。
-
----
-
-### Step 2:能力模块聚类(细工序发现)
-
-读取 `step1_intents.json`,收集所有工作流的所有意图描述(约 N×3 个)。
-
-**Step 2a:LLM 聚类**
-
-调用一个 analyst 子 agent,任务如下:
-```
-对以下意图列表做跨工作流语义聚类,发现品类无关的「能力模块」(细工序)。
-规则:
-- 语义相近的意图归为一个能力模块
-- 能力模块应品类无关,可跨品类复用
-- 目标:10-20 个能力模块
-每个模块输出:模块ID(cm_001起)、模块名称、描述、包含的意图列表(带来源工作流ID)
-```
-
-**Step 2b:原子能力匹配**
-
-用 `read_file` 读取 `%output_dir%/atomic_capabilities.json`,对每个能力模块在原子能力表中查找语义最接近的原子能力:
-- 如果找到语义相近的原子能力,记录其 `id` 和 `name`,并估算相似度分数(0-1)
-- 如果没有语义相近的原子能力,则标记 `atomic_capability_id: null`,表示这是新发现的能力模块
-
-将最终结果写入 `%output_dir%/step2_modules.json`,每个模块包含以下字段:
-- 匹配成功:`atomic_capability_id`(原子能力ID)、`atomic_capability_name`(原子能力名称)、`atomic_match_score`(相似度分数)
-- 未匹配:三个字段均为 `null`
-
----
-
-### Step 3:变体发现
-
-读取 `step2_modules.json` 和原始工作流数据。
-
-对**每个能力模块**,调用一个 analyst 子 agent,任务如下:
-```
-分析能力模块「{module_name}」下的所有原始步骤(来自不同工作流),发现实现变体。
-规则:
-- 按工具/方法的相似性聚类
-- 对每个变体,聚合其典型执行步骤(按步骤功能去重合并,保留最具代表性的描述)
-- 注明每个变体来自哪些工作流
-```
-
-将所有模块的变体结果合并,更新 `step2_modules.json` 中对应模块的 `variants` 字段。
-
----
-
-### Step 4:粗工序提取
-
-读取原始工作流数据和 `step2_modules.json`。
-
-**4a 品类分组**:调用一个 analyst 子 agent,按内容品类对所有工作流分组。
-
-**4b 粗工序总结**:对**每个品类**,调用一个 analyst 子 agent,任务如下:
-```
-读取品类「{category}」下所有工作流的能力模块编排顺序,总结:
-1. 做这类内容的典型思路(能力模块的编排顺序)
-2. 为什么先做 A 再做 B(因果推理,不是统计频次)
-输出:粗工序名称、步骤列表(能力模块引用)、因果推理说明、来源工作流列表
-```
-
----
-
-### 最终输出
-
-将所有结果整合,写入 `%output_path%`,格式见 `输出格式规范`。
-
-## 输出格式规范
-
-```json
-{
-  "capability_modules": [
-    {
-      "id": "模块ID",
-      "name": "模块名称",
-      "description": "模块描述",
-      "atomic_capability_id": "原子能力ID或null",
-      "atomic_capability_name": "原子能力名称或null",
-      "atomic_match_score": "相似度分数或null",
-      "variants": [
-        {
-          "name": "变体名称",
-          "typical_steps": [
-            {"step": "步骤名称", "detail": "步骤详情"}
-          ],
-          "source_workflows": ["工作流ID"]
-        }
-      ]
-    }
-  ],
-  "coarse_workflows": [
-    {
-      "id": "粗工序ID",
-      "category": "品类名称",
-      "rationale": "因果推理说明",
-      "steps": [
-        {"module_id": "能力模块ID", "module_name": "能力模块名称", "note": "备注"}
-      ],
-      "source_workflows": ["工作流ID"]
-    }
-  ],
-  "provenance": {
-    "工作流ID": {
-      "source_channel": "来源渠道",
-      "source_name": "来源名称",
-      "category": "品类"
-    }
-  }
-}
-```
-
-$user$
-请开始分析以下工作流数据:
-
-%workflows_json%
-
-输出目录(中间文件):%output_dir%
-最终输出路径:%output_path%

+ 0 - 80
examples/process/prompts/research.prompt

@@ -1,80 +0,0 @@
----
-temperature: 0.3
----
-
-$system$
-## 角色
-你是一个专注的渠道调研专家。你负责在指定的单个渠道(如小红书、X、youtube)进行完整的广度调研,包括多关键词搜索、适度查看内容,并输出结构化的【工序(工作流)】调研结果。
-
-## 核心原则
-1. **渠道专注**:你只负责一个渠道的完整调研,绝不跨渠道。
-2. **唯工序论**:本调研**只关注能解决问题的多步工序(Workflow/组合方案)**,不搜集单一零散工具。
-3. **结构化提取**:必须将工序严格拆解为按序执行的步骤,并明确每个步骤的工具、输入与输出。
-4. **相关性过滤**:只记录与调研目标相关的**图片生成**工序(排除视频、音频及非AI桌面软件如PS)。
-
-## 可用工具
-### 内容搜索工具
-- `search_posts(keyword, channel, cursor="0", max_count=20)`: 搜索帖子
-  - **channel 参数**:xhs(小红书), gzh(公众号), zhihu(知乎), bili(B站), douyin(抖音), toutiao(头条), weibo(微博)
-  - 示例:`search_posts("AI 生图工作流", channel="xhs", max_count=20)`
-- `select_post(index)`: 查看帖子详情(需先调用 search_posts)
-  - 示例:`select_post(index=1)`
-- `youtube_search(keyword)`: 搜索 YouTube 视频
-  - 示例:`youtube_search("AI image workflow tutorial")`
-- `youtube_detail(content_id, include_captions=True)`: 获取 YouTube 视频详情和字幕
-  - 示例:`youtube_detail("视频ID", include_captions=True)`
-- `x_search(keyword)`: 搜索 X (Twitter) 内容
-  - 示例:`x_search("AI workflow comfyui")`
-- `browser-use`: 浏览器搜索(search_posts 不好用时使用)
-
-### 接口失败处理策略
-如果搜索接口失败或返回为空:检查参数 -> 重试2次 -> 若仍失败,在JSON中标注 `"渠道状态": "接口失败"` 并返回空数据。
-
-## 执行流程
-
-### 第一步:广度扫描(发现工序)
-1. **初始搜索**:使用任务提供的关键词,每个关键词搜索 20 条结果。
-2. **适度查看内容**:对点赞数高或标题符合业务需求的帖子查看详情(可看图片)。
-3. **提取工序信息**:
-   - 重点识别:这是一套怎样的完整工作流?
-   - **严格拆解步骤**:必须提取每一步的 `工具`、`用户输入(Prompt/参数/参考图)`、`输出结果(阶段性产物)`。
-
-### 第二步:反思与补充搜索
-每完成一轮关键词搜索后,主动评估:
-- 已发现的工序是否覆盖了主要方案?
-- 是否有明显遗漏的渠道热点或关键词变体?
-- 如有不足,调整关键词后补充搜索 1-2 轮,直到结果开始重复为止。
-
-## 输出格式(必须使用 `write_file` 写入指定路径)
-```jsonschema
-{
-  "渠道名称": "string - 小红书/X/YouTube等",
-  "初始关键词": ["string"],
-  "采集时间": "string - ISO 8601",
-  "渠道状态": "string - 正常/接口失败",
-  "工序发现": [
-    {
-      "方案名称": "string - 如 Midjourney + ControlNet + 局部重绘工作流",
-      "最新提及时间": "string",
-      "工序步骤": [
-        {
-          "步骤序号": "number - 如 1, 2, 3",
-          "步骤描述": "string - 该步骤的核心目的",
-          "使用工具": "string - 该步骤使用的具体工具/模型/节点",
-          "用户输入": "string - 用户的真实输入(如:特定风格的Prompt、垫图、Lora权重等)",
-          "输出结果": "string - 该步骤生成的阶段性产物或最终图片效果"
-        }
-      ],
-      "帖子链接": ["string"]
-    }
-  ],
-  "调研轨迹": {
-    "搜索次数": "number",
-    "查看详情数": "number",
-    "发现工序数": "number"
-  }
-}
-```
-
-$user$
-%task%

+ 0 - 78
examples/process/prompts/tool_research.prompt

@@ -1,78 +0,0 @@
----
-temperature: 0.3
----
-
-$system$
-
-## 角色
-你是一个工序调研协调器。你负责制定调研策略、分发广度采集任务、交叉验证数据,并输出结构化的工序(Workflow)调研报告。
-
-**核心目标**:
-寻找并汇总能解决特定生图需求的**多步工序(Workflow)**。必须结构化记录每个工序的执行步骤(Step、输入、输出、工具)。
-
-## 可用工具
-- `agent`: 调用 research 子 agent 执行渠道采集
-- `write_file`: 输出调研报告和结构化数据
-
-## 工作流程:广度扫描与汇总
-
-### 第一步:理解需求并构造搜索关键词
-- 将用户的业务能力需求,转换为**AI生图工序/工作流**的搜索词。
-- 关键词应偏向寻找"教程"、"工作流"、"全套方案",而非单一软件名。
-- 准备 3-5 个关键词。
-
-### 第二步:并行调用渠道调研 agent
-默认使用小红书、X、youtube 这三个渠道进行调研。为每个渠道启动一个独立的 research 子 agent。
-
-**任务描述规范示例**:
-```
-[小红书调研] 使用以下关键词进行广度扫描:[关键词1, 关键词2, 关键词3]。
-要求:
-- 每个关键词搜索 20 条结果。
-- 只提取多步工序(Workflow),必须严格拆解每个工序的步骤(包含输入、输出、工具)。
-- 输出路径:%output_dir%/<渠道名>.json
-```
-
-每个子 agent 会将自己的采集结果写入对应的渠道 JSON 文件。
-
-### 第三步:汇总并输出结构化数据
-
-等所有渠道子 agent 完成后,将三个渠道的工序发现合并,执行以下两个输出:
-
-**输出 1:可读报告**(Markdown 格式)
-用 `write_file` 写入 `%output_dir%/report.md`,内容包括:
-- 调研概况表格(渠道、搜索词数、获取结果数、提取工序数)
-- 每个工序的详细步骤拆解(表格形式)
-- 核心工具汇总
-
-**输出 2:结构化工序数据**(JSON 格式,供后续分析使用)
-用 `write_file` 写入 `%output_dir%/workflows.json`,格式如下:
-
-```json
-{
-  "需求": "用户的原始需求描述",
-  "工序发现": [
-    {
-      "方案名称": "方案的简短名称,如 ComfyUI + ControlNet 工作流",
-      "来源渠道": "小红书/X/YouTube",
-      "工序步骤": [
-        {
-          "步骤序号": 1,
-          "步骤描述": "该步骤的核心目的",
-          "使用工具": "该步骤使用的具体工具/模型/节点",
-          "用户输入": "用户的真实输入(如:特定风格的Prompt、垫图、LoRA权重等)",
-          "输出结果": "该步骤生成的阶段性产物或最终图片效果"
-        }
-      ],
-      "帖子链接": ["https://..."]
-    }
-  ]
-}
-```
-
-**重要**:`workflows.json` 是必须输出的,不可省略。即使某些工序步骤信息不完整,也要尽量填充已知字段。
-
-$user$
-请开始工作:调研 %requirement%
-
-输出目录:%output_dir%/

+ 0 - 62
examples/process/research.prompt

@@ -1,62 +0,0 @@
----
-model: qwen3.5-plus
-temperature: 0.3
----
-
-$system$
-## 角色
-你是一个专注的渠道调研专家。你负责在指定的单个渠道(如小红书、X、youtube)进行完整的广度调研,包括多关键词搜索、适度查看内容,并输出结构化的【工序(工作流)】调研结果。
-
-## 核心原则
-1. **渠道专注**:你只负责一个渠道的完整调研,绝不跨渠道。
-2. **唯工序论**:本调研**只关注能解决问题的多步工序(Workflow/组合方案)**,不搜集单一零散工具。
-3. **结构化提取**:必须将工序严格拆解为按序执行的步骤,并明确每个步骤的工具、输入与输出。
-4. **相关性过滤**:只记录与调研目标相关的**图片生成**工序(排除视频、音频及非AI桌面软件如PS)。
-
-## 可用工具
-### 内容搜索工具
-- `search_posts(keyword, channel, cursor="0", max_count=20)`: 搜索帖子(channel参数必须用双引号小写,如 `"xhs"`)
-- `select_post(index)`: 查看帖子详情
-- `youtube_search(keyword)` / `youtube_detail(content_id)`: 搜索/查看 YouTube
-- `x_search(keyword)`: 搜索 X (Twitter) 内容
-
-### 接口失败处理策略
-如果搜索接口失败或返回为空:检查参数 -> 重试2次 -> 若仍失败,在JSON中标注 `"渠道状态": "接口失败"` 并返回空数据。
-
-## 执行流程:广度扫描(发现工序)
-1. **初始搜索**:使用任务提供的关键词,每个关键词搜索 20 条结果。
-2. **适度查看内容**:对点赞数高或标题符合业务需求的帖子查看详情(可看图片)。
-3. **提取工序信息**:
-   - 重点识别:这是一套怎样的完整工作流?
-   - **严格拆解步骤**:必须提取每一步的 `工具`、`用户输入(Prompt/参数/参考图)`、`输出结果(阶段性产物)`。
-
-第二部结构化输出:
-
-## 输出格式(必须使用 `write_file` 写入指定路径)
-```jsonschema
-{
-  "渠道名称": "string - 小红书/X/公众号等",
-  "初始关键词": ["string"],
-  "采集时间": "string - ISO 8601",
-  "渠道状态": "string - 正常/接口失败",
-  "工序发现": [
-    {
-      "方案名称": "string - 如 Midjourney + ControlNet + 局部重绘工作流",
-      "最新提及时间": "string",
-      "工序步骤": [
-        {
-          "步骤序号": "number - 如 1, 2, 3",
-          "步骤描述": "string - 该步骤的核心目的",
-          "使用工具": "string - 该步骤使用的具体工具/模型/节点",
-          "用户输入": "string - 用户的真实输入(如:特定风格的Prompt、垫图、Lora权重等)",
-          "输出结果": "string - 该步骤生成的阶段性产物或最终图片效果"
-        }
-      ],
-      "帖子链接": {"string"}
-  ]
-  "调研轨迹": {
-    "搜索次数": "number",
-    "查看详情数": "number",
-    "发现工序数": "number"
-  }
-}

+ 0 - 546
examples/process/run.py

@@ -1,546 +0,0 @@
-"""
-两阶段 Pipeline:工具调研 + 工作流分析
-
-Stage 1:批量调研(qwen3.5-plus),每个需求输出到 output/research/NN/
-Stage 2:工作流分析(claude-sonnet),读取 Stage 1 输出,生成 output/analysis/result.json
-
-用法:
-  python run.py                        # 完整两阶段(默认)
-  python run.py --stage research       # 只跑调研
-  python run.py --stage analysis       # 只跑分析(用已有调研结果)
-  python run.py --stage research --from 2  # 从第3个需求续跑
-"""
-
-import argparse
-import json
-import os
-import sys
-import asyncio
-from pathlib import Path
-
-
-sys.path.insert(0, str(Path(__file__).parent.parent.parent))
-
-from dotenv import load_dotenv
-load_dotenv()
-
-from agent.llm.prompts import SimplePrompt
-from agent.core.runner import AgentRunner, RunConfig
-from agent.trace import FileSystemTraceStore, Trace, Message
-from agent.llm import create_qwen_llm_call, create_openrouter_llm_call
-from agent.cli import InteractiveController
-from agent.utils import setup_logging
-
-from config import (
-    RESEARCH_RUN_CONFIG, ANALYSIS_RUN_CONFIG,
-    RESEARCH_OUTPUT_DIR, ANALYSIS_OUTPUT_DIR,
-    SKILLS_DIR, TRACE_STORE_PATH, DEBUG, LOG_LEVEL, LOG_FILE,
-    IM_ENABLED, IM_CONTACT_ID, IM_SERVER_URL, IM_WINDOW_MODE, IM_NOTIFY_INTERVAL,
-)
-
-
-# ─────────────────────────────────────────────
-# Stage 1 helpers
-# ─────────────────────────────────────────────
-
-async def run_single(
-    runner: AgentRunner,
-    interactive: InteractiveController,
-    store: FileSystemTraceStore,
-    prompt: SimplePrompt,
-    requirement: str,
-    output_dir: Path,
-    task_name: str,
-    req_index: int,
-) -> tuple[str, bool]:
-    """执行单个需求的完整调研流程,返回 (最终响应文本, 是否应退出)。"""
-
-    output_dir.mkdir(parents=True, exist_ok=True)
-
-    messages = prompt.build_messages(
-        requirement=requirement,
-        output_dir=str(output_dir),
-    )
-
-    prompt_model = prompt.config.get("model", None)
-    run_config = RunConfig(
-        model=prompt_model or RESEARCH_RUN_CONFIG.model,
-        temperature=RESEARCH_RUN_CONFIG.temperature,
-        max_iterations=RESEARCH_RUN_CONFIG.max_iterations,
-        extra_llm_params=RESEARCH_RUN_CONFIG.extra_llm_params,
-        agent_type=RESEARCH_RUN_CONFIG.agent_type,
-        name=f"{task_name}:需求{req_index:02d}",
-        knowledge=RESEARCH_RUN_CONFIG.knowledge,
-    )
-
-    print(f"\n{'=' * 60}")
-    print(f"[{req_index:02d}] 开始调研")
-    print(f"需求:{requirement[:80]}{'...' if len(requirement) > 80 else ''}")
-    print(f"输出:{output_dir}")
-    print(f"{'=' * 60}")
-
-    current_trace_id = None
-    current_sequence = 0
-    final_response = ""
-    should_exit = False
-
-    try:
-        async for item in runner.run(messages=messages, config=run_config):
-            cmd = interactive.check_stdin()
-            if cmd == 'pause':
-                print("\n⏸️ 正在暂停执行...")
-                if current_trace_id:
-                    await runner.stop(current_trace_id)
-                await asyncio.sleep(0.5)
-                menu_result = await interactive.show_menu(current_trace_id, current_sequence)
-                if menu_result["action"] == "stop":
-                    should_exit = True
-                    break
-                elif menu_result["action"] == "continue":
-                    new_messages = menu_result.get("messages", [])
-                    run_config.after_sequence = menu_result.get("after_sequence")
-                    if new_messages:
-                        messages = new_messages
-                    break
-            elif cmd == 'quit':
-                print("\n🛑 用户请求停止...")
-                if current_trace_id:
-                    await runner.stop(current_trace_id)
-                should_exit = True
-                break
-
-            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] ✅ 完成  messages={item.total_messages}  cost=${item.total_cost:.4f}")
-                elif item.status == "failed":
-                    print(f"\n[Trace] ❌ 失败: {item.error_message}")
-                elif item.status == "stopped":
-                    print(f"\n[Trace] ⏸️ 已停止")
-
-            elif isinstance(item, Message):
-                current_sequence = item.sequence
-                if item.role == "assistant":
-                    content = item.content
-                    if isinstance(content, dict):
-                        text = content.get("text", "")
-                        tool_calls = content.get("tool_calls")
-                        if text and not tool_calls:
-                            final_response = text
-                            print(f"\n[Response] Agent 回复:")
-                            print(text)
-                        elif text:
-                            preview = text[:150] + "..." if len(text) > 150 else text
-                            print(f"[Assistant] {preview}")
-                elif item.role == "tool":
-                    content = item.content
-                    tool_name = "unknown"
-                    if isinstance(content, dict):
-                        tool_name = content.get("tool_name", "unknown")
-                    if item.description and item.description != tool_name:
-                        desc = item.description[:80] if len(item.description) > 80 else item.description
-                        print(f"[Tool Result] ✅ {tool_name}: {desc}...")
-                    else:
-                        print(f"[Tool Result] ✅ {tool_name}")
-
-    except Exception as e:
-        print(f"\n执行出错: {e}")
-        import traceback
-        traceback.print_exc()
-
-    if final_response:
-        output_file = output_dir / "result.txt"
-        with open(output_file, 'w', encoding='utf-8') as f:
-            f.write(final_response)
-        print(f"\n✓ 结果已保存到: {output_file}")
-
-    if current_trace_id:
-        print(f"  Trace ID: {current_trace_id}")
-
-    return final_response, should_exit
-
-
-# ─────────────────────────────────────────────
-# Stage 2 helpers
-# ─────────────────────────────────────────────
-
-def load_workflows_from_dir(research_dir: Path) -> list[dict]:
-    """
-    扫描 research_dir 下所有子目录(00/, 01/ ...),合并工序发现列表。
-
-    优先读取 workflows.json(Stage 1 新格式);
-    若不存在则把目录内 *.md 文件内容作为文本传给 coordinator(兜底)。
-    """
-    workflows = []
-    wf_index = 1
-
-    subdirs = sorted(
-        [d for d in research_dir.iterdir() if d.is_dir()],
-        key=lambda d: d.name,
-    )
-
-    if not subdirs:
-        # 单次调研输出(直接含 JSON 文件)
-        subdirs = [research_dir]
-
-    for subdir in subdirs:
-        workflows_json_path = subdir / "workflows.json"
-
-        # ── 优先:读取 workflows.json ──
-        if workflows_json_path.exists():
-            try:
-                with open(workflows_json_path, encoding='utf-8') as f:
-                    data = json.load(f)
-                discovered = data.get("工序发现", [])
-                for item in discovered:
-                    wf_id = f"wf_{wf_index:03d}"
-                    wf_index += 1
-                    workflows.append({
-                        "id": wf_id,
-                        "name": item.get("方案名称", "未命名工序"),
-                        "category": "",
-                        "source_channel": item.get("来源渠道", "未知"),
-                        "source_file": str(workflows_json_path.relative_to(research_dir)),
-                        "steps": item.get("工序步骤", []),
-                        "post_links": list(item.get("帖子链接", [])),
-                    })
-                    print(f"   + {wf_id}: {item.get('方案名称', '未命名')[:50]}")
-                continue
-            except (json.JSONDecodeError, IOError) as e:
-                print(f"   [警告] workflows.json 解析失败: {subdir.name} ({e}),尝试 Markdown 兜底")
-
-        # ── 兜底:读取 *.md 文件内容 ──
-        md_files = sorted(subdir.glob("*.md"))
-        if md_files:
-            for md_file in md_files:
-                try:
-                    content = md_file.read_text(encoding='utf-8')
-                    wf_id = f"wf_{wf_index:03d}"
-                    wf_index += 1
-                    workflows.append({
-                        "id": wf_id,
-                        "name": md_file.stem,
-                        "category": "",
-                        "source_channel": "Markdown报告",
-                        "source_file": str(md_file.relative_to(research_dir)),
-                        "steps": [],
-                        "raw_markdown": content,  # coordinator 可直接阅读
-                    })
-                    print(f"   + {wf_id}: [MD兜底] {md_file.name}")
-                except IOError as e:
-                    print(f"   [警告] 无法读取 {md_file.name}: {e}")
-        else:
-            print(f"   [跳过] {subdir.name}:无 workflows.json 也无 .md 文件")
-
-    return workflows
-
-
-async def fetch_atomic_capabilities() -> list[dict]:
-    """从 knowhub API 获取全量原子能力表。"""
-    import urllib.request
-    knowhub_api = os.getenv("KNOWHUB_API", "http://43.106.118.91:9999")
-    url = f"{knowhub_api}/api/capability?limit=500"
-    try:
-        with urllib.request.urlopen(url, timeout=10) as resp:
-            data = json.loads(resp.read().decode())
-        capabilities = data.get("results", [])
-        print(f"   已获取原子能力表:{len(capabilities)} 条")
-        return capabilities
-    except Exception as e:
-        print(f"   [警告] 获取原子能力表失败:{e},将跳过匹配")
-        return []
-
-
-async def run_analysis(
-    research_dir: Path,
-    analysis_dir: Path,
-    store: FileSystemTraceStore,
-    prompt_path: Path,
-) -> bool:
-    """执行 Stage 2 分析,返回是否成功。"""
-
-    print(f"\n{'=' * 60}")
-    print("Stage 2:工作流分析")
-    print(f"输入:{research_dir}")
-    print(f"输出:{analysis_dir}")
-    print(f"{'=' * 60}")
-
-    # 扫描工作流数据
-    print("扫描调研结果...")
-    workflows = load_workflows_from_dir(research_dir)
-    if not workflows:
-        print("   错误: 未找到任何工序数据,请先运行 Stage 1")
-        return False
-    print(f"   共加载 {len(workflows)} 条工作流")
-
-    analysis_dir.mkdir(parents=True, exist_ok=True)
-
-    # 获取原子能力表并写入文件
-    print("获取原子能力表...")
-    atomic_capabilities = await fetch_atomic_capabilities()
-    atomic_capabilities_path = analysis_dir / "atomic_capabilities.json"
-    atomic_capabilities_path.write_text(
-        json.dumps({"atomic_capabilities": atomic_capabilities}, ensure_ascii=False, indent=2),
-        encoding='utf-8'
-    )
-    print(f"   已写入:{atomic_capabilities_path}")
-    output_path = analysis_dir / "result.json"
-
-    # 加载 coordinator prompt
-    prompt = SimplePrompt(prompt_path)
-    workflows_json = json.dumps({"workflows": workflows}, ensure_ascii=False, indent=2)
-    messages = prompt.build_messages(
-        workflows_json=workflows_json,
-        output_dir=str(analysis_dir),
-        output_path=str(output_path),
-    )
-
-    # 创建 Runner(OpenRouter / Claude)
-    prompt_model = prompt.config.get("model", None) or ANALYSIS_RUN_CONFIG.model
-    print(f"   模型: {prompt_model}")
-
-    runner = AgentRunner(
-        trace_store=store,
-        llm_call=create_openrouter_llm_call(model=prompt_model),
-        skills_dir=SKILLS_DIR,
-        debug=DEBUG,
-    )
-    interactive = InteractiveController(runner=runner, store=store, enable_stdin_check=True)
-    runner.stdin_check = interactive.check_stdin
-
-    run_config = RunConfig(
-        model=prompt_model,
-        temperature=ANALYSIS_RUN_CONFIG.temperature,
-        max_iterations=ANALYSIS_RUN_CONFIG.max_iterations,
-        agent_type=ANALYSIS_RUN_CONFIG.agent_type,
-        name=f"工作流分析:{len(workflows)} 条工作流",
-    )
-
-    current_trace_id = None
-    current_sequence = 0
-
-    try:
-        async for item in runner.run(messages=messages, config=run_config):
-            cmd = interactive.check_stdin()
-            if cmd == 'pause':
-                print("\n⏸️ 正在暂停...")
-                if current_trace_id:
-                    await runner.stop(current_trace_id)
-                await asyncio.sleep(0.5)
-                menu_result = await interactive.show_menu(current_trace_id, current_sequence)
-                if menu_result["action"] == "stop":
-                    break
-                elif menu_result["action"] == "continue":
-                    new_messages = menu_result.get("messages", [])
-                    run_config.after_sequence = menu_result.get("after_sequence")
-                    if new_messages:
-                        messages = new_messages
-                    break
-            elif cmd == 'quit':
-                print("\n🛑 停止执行...")
-                if current_trace_id:
-                    await runner.stop(current_trace_id)
-                break
-
-            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] ✅ 完成  messages={item.total_messages}  cost=${item.total_cost:.4f}")
-                elif item.status == "failed":
-                    print(f"\n[Trace] ❌ 失败: {item.error_message}")
-                elif item.status == "stopped":
-                    print(f"\n[Trace] ⏸️ 已停止")
-
-            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:
-                            print(f"\n[Response]\n{text}")
-                        elif text:
-                            preview = text[:150] + "..." if len(text) > 150 else text
-                            print(f"[Assistant] {preview}")
-                elif item.role == "tool":
-                    content = item.content
-                    tool_name = "unknown"
-                    if isinstance(content, dict):
-                        tool_name = content.get("tool_name", "unknown")
-                    if item.description and item.description != tool_name:
-                        desc = item.description[:80] if len(item.description) > 80 else item.description
-                        print(f"[Tool] ✅ {tool_name}: {desc}...")
-                    else:
-                        print(f"[Tool] ✅ {tool_name}")
-
-    except Exception as e:
-        print(f"\n执行出错: {e}")
-        import traceback
-        traceback.print_exc()
-    except KeyboardInterrupt:
-        print("\n\n用户中断 (Ctrl+C)")
-        if current_trace_id:
-            await runner.stop(current_trace_id)
-
-    # 结果摘要
-    print()
-    print("=" * 60)
-    if output_path.exists():
-        print(f"✅ 分析完成,结果已写入:{output_path}")
-        try:
-            with open(output_path, encoding='utf-8') as f:
-                result = json.load(f)
-            n_modules = len(result.get("capability_modules", []))
-            n_coarse = len(result.get("coarse_workflows", []))
-            print(f"   - 能力模块(细工序):{n_modules} 个")
-            print(f"   - 粗工序:{n_coarse} 个品类")
-        except Exception:
-            pass
-        return True
-    else:
-        print("⚠️  未检测到最终输出文件,分析可能未完成")
-        print(f"   期望路径:{output_path}")
-        return False
-
-
-# ─────────────────────────────────────────────
-# Main
-# ─────────────────────────────────────────────
-
-async def main():
-    parser = argparse.ArgumentParser(description="两阶段 Pipeline:工具调研 + 工作流分析")
-    parser.add_argument(
-        "--stage", choices=["research", "analysis", "all"], default="all",
-        help="执行阶段:research=只调研, analysis=只分析, all=完整流程(默认)",
-    )
-    parser.add_argument(
-        "--from", dest="from_index", type=int, default=0,
-        help="从第几个需求开始(0-based,仅 stage=research/all 时有效)",
-    )
-    parser.add_argument(
-        "--requirements", type=str, default=None,
-        help="需求列表 JSON 文件路径(默认 requirements.json)",
-    )
-    args = parser.parse_args()
-
-    base_dir = Path(__file__).parent
-    project_root = base_dir.parent.parent
-    research_output_dir = project_root / RESEARCH_OUTPUT_DIR
-    analysis_output_dir = project_root / ANALYSIS_OUTPUT_DIR
-
-    setup_logging(level=LOG_LEVEL, file=LOG_FILE)
-
-    # 加载 presets
-    presets_path = base_dir / "presets.json"
-    if presets_path.exists():
-        from agent.core.presets import load_presets_from_json
-        load_presets_from_json(str(presets_path))
-        print("已加载 presets")
-
-    store = FileSystemTraceStore(base_path=TRACE_STORE_PATH)
-
-    # ── Stage 1: Research ──
-    if args.stage in ("all", "research"):
-        req_path = Path(args.requirements) if args.requirements else base_dir / "requirements.json"
-        if not req_path.exists():
-            print(f"错误: 需求文件不存在: {req_path}")
-            sys.exit(1)
-        with open(req_path, encoding='utf-8') as f:
-            requirements = json.load(f)
-        if not isinstance(requirements, list) or len(requirements) == 0:
-            print("错误: 需求文件必须是非空 JSON 数组")
-            sys.exit(1)
-
-        research_output_dir.mkdir(parents=True, exist_ok=True)
-        prompt_path = base_dir / "prompts" / "tool_research.prompt"
-        prompt = SimplePrompt(prompt_path)
-
-        # IM 初始化(可选)
-        if IM_ENABLED:
-            from agent.tools.builtin.im.chat import im_setup, im_open_window
-            result = await im_setup(
-                contact_id=IM_CONTACT_ID,
-                server_url=IM_SERVER_URL,
-                notify_interval=IM_NOTIFY_INTERVAL,
-            )
-            print(f"IM: {result.output}")
-            if IM_WINDOW_MODE:
-                window_result = await im_open_window(contact_id=IM_CONTACT_ID)
-                print(f"IM: {window_result.output}")
-
-        prompt_model = prompt.config.get("model", None) or RESEARCH_RUN_CONFIG.model
-        runner = AgentRunner(
-            trace_store=store,
-            llm_call=create_qwen_llm_call(model=prompt_model),
-            skills_dir=SKILLS_DIR,
-            debug=DEBUG,
-        )
-        interactive = InteractiveController(runner=runner, store=store, enable_stdin_check=True)
-        runner.stdin_check = interactive.check_stdin
-
-        task_name = RESEARCH_RUN_CONFIG.name or base_dir.name
-        total = len(requirements)
-        start = args.from_index
-
-        print("=" * 60)
-        print(f"Stage 1:{task_name}")
-        print(f"共 {total} 个需求,从第 {start} 个开始")
-        print("=" * 60)
-        print("💡 输入 'p' 暂停,'q' 退出")
-        print("=" * 60)
-
-        completed = 0
-        try:
-            for i, requirement in enumerate(requirements):
-                if i < start:
-                    continue
-                req_output_dir = research_output_dir / f"{i:02d}"
-                _, should_exit = await run_single(
-                    runner=runner,
-                    interactive=interactive,
-                    store=store,
-                    prompt=prompt,
-                    requirement=requirement,
-                    output_dir=req_output_dir,
-                    task_name=task_name,
-                    req_index=i,
-                )
-                completed += 1
-                if should_exit:
-                    print(f"\n🛑 用户中止,已完成 {completed}/{total - start} 个需求")
-                    break
-        except KeyboardInterrupt:
-            print(f"\n\n用户中断 (Ctrl+C),已完成 {completed}/{total - start} 个需求")
-
-        print()
-        print("=" * 60)
-        print(f"Stage 1 完成:{completed}/{total - start} 个需求")
-        print(f"输出根目录:{research_output_dir}")
-        print("=" * 60)
-
-        if args.stage == "all":
-            # 统计已采集工作流数量(粗略)
-            wf_count = sum(
-                1 for d in research_output_dir.iterdir()
-                if d.is_dir() and (d / "workflows.json").exists()
-            )
-            print(f"\n[Stage 1 完成] 共 {wf_count} 个目录含 workflows.json,自动进入 Stage 2 分析...")
-
-    # ── Stage 2: Analysis ──
-    if args.stage in ("all", "analysis"):
-        coordinator_prompt_path = base_dir / "prompts" / "coordinator.prompt"
-        await run_analysis(
-            research_dir=research_output_dir,
-            analysis_dir=analysis_output_dir,
-            store=store,
-            prompt_path=coordinator_prompt_path,
-        )
-
-
-if __name__ == "__main__":
-    asyncio.run(main())

+ 0 - 37
examples/process/tool_research.prompt

@@ -1,37 +0,0 @@
----
-temperature: 0.3
----
-
-$system$
-
-## 角色
-你是一个工序调研协调器。你负责制定调研策略、分发广度采集任务、交叉验证数据,并输出结构化的工序(Workflow)调研报告。
-
-**核心目标**:
-寻找并汇总能解决特定生图需求的**多步工序(Workflow)**。必须结构化记录每个工序的执行步骤(Step、输入、输出、工具)。
-
-## 可用工具
-- `agent`: 调用 research 子 agent 执行渠道采集
-- `write_file`: 输出调研报告
-
-## 工作流程:广度扫描与汇总
-
-### 第一步:理解需求并构造搜索关键词
-- 将用户的业务能力需求,转换为**AI生图工序/工作流**的搜索词。
-- 关键词应偏向寻找“教程”、“工作流”、“全套方案”,而非单一软件名。
-- 准备 3-5 个关键词。
-
-### 第二步:并行调用渠道调研 agent
-默认使用小红书、X、youtube 这三个渠道进行调研。为每个渠道启动一个独立的 research 子 agent。
-
-**任务描述规范示例**:
-```text
-[小红书调研] 使用以下关键词进行广度扫描:[关键词1, 关键词2, 关键词3]。
-要求:
-- 每个关键词搜索 20 条结果。
-- 只提取多步工序(Workflow),必须严格拆解每个工序的步骤(包含输入、输出、工具)。
-
-$user$
-请开始工作:调研 %requirement%
-
-输出目录:%output_dir%/

+ 93 - 0
examples/process_pipeline/batch.py

@@ -0,0 +1,93 @@
+import argparse
+import subprocess
+import os
+from pathlib import Path
+
+def main():
+    parser = argparse.ArgumentParser(description="Spawn multi-window batch runner")
+    parser.add_argument("--start", type=int, required=True, help="Start requirement (1-based, inclusive, e.g. 31)")
+    parser.add_argument("--end", type=int, required=True, help="End requirement (1-based, inclusive, e.g. 50)")
+    parser.add_argument("--workers", type=int, default=7, help="Number of physical CMD windows to spawn")
+    parser.add_argument("--skip-research", action="store_true", help="Skip Phase 1 and use existing raw cases")
+    parser.add_argument("--research-only", action="store_true", help="Only run research phases, skip Phase 2 and 3")
+    parser.add_argument("--use-claude-sdk", action="store_true", help="Use pure Anthropic SDK for Phase 2/3")
+    parser.add_argument("--platforms", type=str, default="", help="Comma-separated list of platforms to search")
+    args = parser.parse_args()
+
+    start_idx = args.start - 1
+    end_idx = args.end - 1
+    total_tasks = end_idx - start_idx + 1
+    
+    if total_tasks <= 0:
+        print("Invalid range")
+        return
+
+    # Assign indices to workers using round-robin
+    worker_tasks = {i: [] for i in range(args.workers)}
+    for i, req_idx in enumerate(range(start_idx, end_idx + 1)):
+        worker_id = i % args.workers
+        worker_tasks[worker_id].append(req_idx)
+        
+    base_dir = Path(__file__).parent
+    
+    print(f"Spawning {args.workers} actual CMD windows to process {total_tasks} requirements...")
+
+    for worker_id, indices in worker_tasks.items():
+        if not indices:
+            continue
+            
+        # Create a temporary batch file for this worker window
+        bat_path = base_dir / f"worker_{worker_id}.bat"
+        with open(bat_path, "w", encoding="utf-8") as f:
+            f.write("@echo off\n")
+            f.write("cd /d %~dp0\n")
+            f.write("if exist ..\\..\\.venv\\Scripts\\activate.bat call ..\\..\\.venv\\Scripts\\activate.bat\n")
+            f.write("echo Worker Group %d starting...\n" % worker_id)
+            for idx in indices:
+                f.write(f"echo =======================================\n")
+                f.write(f"echo Running Requirement Index: {idx}\n")
+                f.write(f"echo =======================================\n")
+                
+                cmd = f"python run_pipeline.py --index {idx}"
+                if args.skip_research:
+                    cmd += " --skip-research"
+                if args.research_only:
+                    cmd += " --research-only"
+                if args.use_claude_sdk:
+                    cmd += " --use-claude-sdk"
+                if args.platforms:
+                    cmd += f" --platforms {args.platforms}"
+                
+                # 第 1 遍执行
+                f.write(f"echo [Attempt 1] {cmd}\n")
+                f.write(f"{cmd}\n")
+                
+                # 第 2 遍执行(检查重试)
+                if not args.research_only:
+                    target_file = f"output\\{(idx+1):03d}\\strategy.json"
+                    f.write(f"if not exist \"{target_file}\" (\n")
+                    f.write(f"    echo =======================================\n")
+                    f.write(f"    echo [Retry] target.json missing, retrying once...\n")
+                    f.write(f"    echo =======================================\n")
+                    f.write(f"    {cmd}\n")
+                    f.write(f") else (\n")
+                    f.write(f"    echo [Verified] {target_file} generated successfully.\n")
+                    f.write(f")\n")
+                else:
+                    # 如果是 resaerch-only 则较难在 batch 中校验几个文件,默认让它安全重试一遍
+                    f.write(f"echo [Retry For Research] Automatically trying once more to ensure completeness...\n")
+                    f.write(f"{cmd}\n")
+                
+                f.write("timeout /t 2 > NUL\n")
+            f.write("echo Worker Group %d finished!\n" % worker_id)
+            f.write("pause\n")
+            
+        # Open a new CMD window executing this batch file
+        subprocess.Popen(
+            ["cmd.exe", "/c", "start", f"Worker_{worker_id}", "cmd.exe", "/c", str(bat_path.absolute())],
+            cwd=str(base_dir)
+        )
+        print(f"Launched window for Worker {worker_id} -> Indices: {indices}")
+
+if __name__ == "__main__":
+    main()

+ 101 - 0
examples/process_pipeline/db_requirements.json

@@ -0,0 +1,101 @@
+[
+  "生成人物在不同场景下呈现丰富面部表情的图片,例如夸张的痛苦、无奈、开心、困倦等神态,表情要生动传神、情绪感强烈",
+  "生成人物与道具、环境或其他角色发生互动的画面,例如人物摆弄物品、与道具合影、在特定场景中做出配合动作等,画面要体现人物和周围元素之间的关联感",
+  "生成将动物(如猫咪)拟人化扮演特定角色或情境的图片,赋予其人类的表情、姿态和道具,用来传达幽默或情感共鸣的视觉效果",
+  "生成带有特定道具装扮的人物场景图,道具需与人物自然融合,例如猫咪戴假发穿衣服手持书本、人物手持购物篮抱着玩偶玩具等,道具细节清晰可辨",
+  "生成婚礼或节日庆典场景,背景需包含大量花卉装饰、定制发光字牌、喜字等布景元素,整体氛围感强烈,道具与场景协调统一",
+  "生成精致室内空间场景,画面中需呈现陶瓷器皿、绿植、家具等道具摆件,光线自然柔和,营造出温馨生活感或高颜值家居氛围",
+  "生成真实人物在户外或特定场景中的生活记录照片,画面自然真实,包含儿童在公园、农场等户外环境中玩耍的多角度抓拍效果,光线自然,氛围温馨",
+  "制作将真实人物照片合成到趣味场景中的创意图片,例如把人物缩小放入超市肉类托盘包装内、或与冰雕翅膀等道具结合形成视觉错位的幽默效果",
+  "生成真实场景的多图拼贴展示图,将同一地点或主题的多张实拍照片拼合为一张图文并茂的内容图,适合用于地点打卡、产品展示或生活记录类帖子",
+  "制作多格宫格式信息图,将同类内容(如多种食材搭配方案)拆分为统一风格的小卡片,每格包含标题、食材图片和文字说明,整体排列整齐、色块鲜明,适合一图展示多个并列条目",
+  "在图片上叠加标注元素,如用红色圆点、箭头或emoji符号指向图中特定位置,配合说明文字,实现在真实照片上直观标记关键信息的视觉效果",
+  "制作图文混排的长图文内容,将大段文字与人物照片、数据表格、流程图等多种视觉元素组合排布在同一版面中,形成类似杂志或报告的专业排版风格",
+  "生成以暖黄/米棕色为背景底色的图文排版内容,整体画面呈现温暖、复古的暖色调氛围,适合健康养生、生活方式类主题",
+  "生成以深色(黑色/深蓝/深紫)为背景底色的海报,搭配霓虹感彩色光效(橙、紫、青等),营造出科技感强烈的冷暖对比配色效果",
+  "生成整体色调偏粉紫、薄荷绿、浅蓝等低饱和度冷色系的插画或场景图,画面呈现出梦幻、静谧的冷色调氛围,颜色搭配柔和克制",
+  "生成手部持握或展示物品的特写画面,手势自然,物品清晰呈现,如用手托举饺子、手持卡片、手握手机等,突出手与物品的互动关系",
+  "生成多人或多只动物同框的协同姿态画面,如两只猫并排躺着穿睡衣、两人一起摆造型抱花束,呈现出同步、对称或互动的视觉效果",
+  "生成创意性的嘴部动作特写,如用嘴唇衔住花朵茎部形成'嘴唇花'的视觉效果,将身体部位与物品结合产生趣味创意画面",
+  "将人物照片与中国传统吉祥符号(如双喜字、红玫瑰、金色祝福文字)融合,生成具有强烈喜庆氛围的定制化图案,人物面孔清晰嵌入红色喜庆背景中",
+  "制作统一模板风格的系列信息卡片,每张卡片包含固定的图标符号(如皇冠等级图标)、彩色标题文字和配图,整体视觉风格一致、可批量复用",
+  "生成黑色科技感背景的人物宣传海报,背景带有流光线条或霓虹光效,人物照片与品牌Logo、活动标识、二维码等视觉元素整齐排布,形成高辨识度的系列展示图",
+  "生成宠物或动物穿戴人类服饰配件(如帽子、围巾)的画面,让动物看起来像在过节或扮演某种角色,整体效果可爱又有趣",
+  "生成将普通服装或日常物品以夸张搞怪方式穿戴的人物画面,比如把超大号短裤当长袍穿、用篮球短裤模仿古希腊长袍,产生强烈的视觉反差和幽默效果",
+  "生成具有超现实风格的创意合成画面,将人物头部替换为宇宙星云、太极图、粒子爆炸等抽象元素,配合深蓝红色调背景,营造出哲学感或科幻感的视觉冲击",
+  "生成穿着完整冬季搭配的人物形象,展示黑色羽绒服、红色围巾、红色手套、宽腿裤等单品的组合穿搭效果,呈现从全身到局部细节的多角度造型展示",
+  "生成宠物穿着服装的可爱造型图,展示猫咪穿上印花连体衣的整体穿着效果,需要清晰呈现服装的图案、版型与宠物身体的贴合细节",
+  "生成创意合成图,将人物穿搭形象嵌入特定场景容器中(如超市生鲜托盘),使人物服装与场景产生趣味性视觉对比,同时保留服装细节的清晰可见",
+  "将猫咪表情包图片与各种场景素材(办公室、食物、产品、背景环境等)合成拼贴在一起,让猫咪看起来自然地处于这些场景中,形成多格并排的拼贴版式",
+  "把猫咪图片与各类装扮道具(帽子、眼镜、服装、假发等)或其他卡通/玩具素材叠加合成,让不同来源的素材无缝融合成一张完整的搞笑图",
+  "在同一张图中将多只猫咪或同一只猫咪的不同姿态照片拼接组合,配合文字标注形成对话或对比效果的多格拼图",
+  "将真实照片中的人物与卡通/奇幻元素合成,例如给人物添加蟑螂的触角和腿,使人物看起来像变成了一只蟑螂,整体画面自然融合不突兀",
+  "给普通猫咪照片套上不同职业的服装和场景(如医生、上班族、老板等),并保持猫咪面部表情清晰可辨,制作出系列表情包拼贴图",
+  "在真实物体照片上叠加手绘风格的简笔画元素,例如在猕猴桃切片上添加卡通五官和小触角,让照片呈现出实物与手绘结合的趣味效果",
+  "生成真实户外场景中的人物活动照片,画面要呈现自然光线下的街道、公园、游乐场等具体地点环境,人物动作自然生动,背景环境细节丰富真实",
+  "生成多人聚集的活动现场图,如会议、展览、户外聚会等场景,画面中需要呈现多个人物同框、有组织的群体互动氛围,背景有明显的活动标识或场地特征",
+  "生成真实物品的特写或陈列展示图,物品摆放清晰、细节可辨,适合用于产品展示或场景道具呈现,画面构图干净突出主体",
+  "生成同一人物在同一场景中多角度、多姿态的即时抓拍效果图,画面呈现自然随意的动态感,如行走、转身、低头、仰望等非摆拍状态,整体风格真实生活化",
+  "生成动物(如马)在运动瞬间被捕捉的高动态画面,鬃毛飞扬、肢体伸展,呈现出强烈的瞬间张力和动感,背景简洁以突出主体动态",
+  "生成人物在真实日常场景(街头、公园、机场等)中被随手拍下的多张图片拼贴效果,画面构图不刻意、视角多变(含俯拍脚部、镜中自拍、远景抓拍等),整体呈现出碎片化的生活记录感",
+  "制作图文卡片时,需要让插图与文字在语义上高度呼应——比如用可爱小驴的不同表情和动作配合对应的幽默文字,每张小卡片中图在上、文字在下,插图内容直接反映文字含义,形成一眼就能看懂的图文配合效果",
+  "制作多图拼贴帖子时,需要将多张照片或截图按照叙事顺序排列在一张大图中,并在关键图片上叠加说明性文字标注,让图片和文字共同讲述一个完整故事,文字起到补充说明和情感点评的作用",
+  "制作信息图文海报时,需要将大标题、分类小标题与正文段落按照清晰的层级排布在版面上,标题用大字醒目展示,正文紧跟其下,整体版面分区明确、图文对应,让读者能快速扫读获取信息",
+  "在图片上叠加标题文字,文字大小、粗细、颜色各异,形成层次感强的排版效果——例如大标题用粗体醒目字体,副标题用细体小字,整体风格统一(如深色系商务风或简约设计风)",
+  "在AI生成的卡通角色图片上叠加幽默吐槽文案,文字直接覆盖在图片上方,字体简洁白色,与画面情绪呼应,形成图文结合的表情包风格内容",
+  "制作多宫格拼图帖子,每格图片配有对应的标题文字或字幕说明,文字风格统一,整体排列整齐,适合用于周记、日历、流程说明等系列内容展示",
+  "制作图文卡片式科普内容:每张卡片包含统一的标题样式、编号序列、配套插图(卡通/示意图风格)和说明文字,多张卡片拼成一组,整体风格统一、排版清晰,适合健康养生、步骤教程类内容展示",
+  "制作数据报告类图文内容:包含柱状图、饼图、折线图、词云图、环形图等多种数据可视化图表,配合标题、要点文字说明,整体呈现专业研究报告的视觉风格,色彩搭配统一(如蓝紫色系或橙色系)",
+  "制作流程图/架构示意图:用箭头、方框、层级结构或立体堆叠图形展示系统架构、业务流程或概念层级关系,配合文字标注说明各模块功能,视觉上清晰呈现逻辑关系",
+  "将整个帖子内容拆分为多个独立小格子并排列成网格或矩阵布局,每个格子承载一个独立的场景或信息单元,格子之间有明显的分隔边界,整体看起来像一张由多张小图拼合而成的大图",
+  "在同一张图中混合使用多种内容载体形式,例如将真实照片、插画角色、产品图、文字说明、图表等不同类型的视觉元素组合排布在同一个版面内,形成图文混排的丰富视觉层次",
+  "以统一的视觉主角(如同一个卡通角色、同一个人物、同一主题场景)贯穿多个分格画面,每个格子呈现该主角在不同场景或状态下的样子,配合文字说明形成系列感强的多格叙事版式",
+  "将多张图片按网格或分区方式拼贴成一张图,每个区域展示不同角度或不同场景,整体画面有清晰的分割感和节奏感",
+  "在同一画面中合理安排主体与背景的空间关系,让主体(人物、动物、物品)在画面中有明确的视觉焦点,背景简洁或有层次地衬托主体",
+  "生成包含多个独立小格子的图文排版画面,每个格子内有图片和文字说明,格子之间疏密有致、整齐排列,整体呈现信息图表或内容合集的视觉效果",
+  "生成人物局部特写画面,如放大呈现嘴巴咬食物、手持物品、耳朵佩戴饰品、鼻子、指甲等身体局部细节,画面填充感强,细节清晰可见",
+  "生成人物近景半身或胸部以上的画面,突出人物面部表情和情绪,背景适当虚化,让观看者能清楚看到人物的神态与互动感",
+  "生成产品或物品的极近距离特写图,如食物截面、商品细节、小物件放大展示,画面主体占满画幅,质感和纹理清晰突出",
+  "生成具有强烈透视纵深感的室内空间图,画面中窗框、拱门、地板线条等建筑元素形成明显的空间层次,光线从远处窗口射入,营造出由近到远的视觉延伸效果",
+  "生成采用夸张变形构图的图片,例如鱼眼镜头效果将人物或场景扭曲成球形全景、仰拍使近处物体极度放大而远处极度缩小,或通过搞怪角度让画面产生强烈的视觉冲击感",
+  "生成画面中存在嵌套框架效果的图片,如在沙漠场景中用一个悬空的矩形框将主体框住形成画中画,或利用水面倒影与实景上下对称形成嵌套镜像构图,制造超现实的空间突破感",
+  "生成具有强烈色彩对比的艺术插画,整体画面以高饱和度的红色与蓝色为主色调,两种颜色形成鲜明的冷暖对撞,背景大面积纯色铺底,视觉冲击力极强",
+  "在以暗色或单色为主的画面中,用局部的高饱和亮色(如红色心脏、橙色暖光窗口、金黄色星光)作为点睛之笔,让视线自然聚焦到这个色彩亮点上,形成强烈的视觉引导",
+  "生成整体色调统一、饱和度偏高的场景图,例如全画面笼罩在深蓝色夜光氛围或浓郁的赤红土地色调中,让单一主色调主导整个画面,营造出沉浸式的强烈色彩氛围感",
+  "生成具有强烈氛围感的插画风场景图,整体画面以深蓝色调为主,室内外场景都笼罩在宁静的夜色中,窗户透出暖黄色灯光形成冷暖对比,画面质感接近油画或数字绘画风格,传达出静谧、沉思、略带忧郁的情绪氛围",
+  "制作色彩鲜艳、视觉冲击力强的宣传海报,背景使用渐变色块(蓝紫、橙红等高饱和度色彩),搭配几何抽象图形装饰,文字排版醒目大气,整体呈现出热烈、充满活力的欢庆氛围",
+  "生成暖色调的室内空间效果图,以米白、浅棕、焦糖色为主色调,光线柔和自然,空间布置温馨舒适,整体画面传达出放松、治愈、生活化的温暖氛围",
+  "生成具有强烈视觉冲击力的超现实主义风格图像:画面以大地色系(深红、赭石、深蓝)为主调,将白马、牛仔等元素置于极简的荒漠/盐湖场景中,营造出孤寂、神秘、如油画般的电影感氛围",
+  "生成3D卡通风格的拟人化动物角色,角色具有毛绒质感和丰富的表情神态,能够在不同生活场景(办公室、卧室、户外)中呈现出喜怒哀乐等情绪状态,整体风格类似皮克斯动画",
+  "制作融合插画风格的信息图文海报:以卡通机器人/科技感插图作为视觉主体,搭配醒目的彩色标题文字和数据图表,整体呈现出活泼又专业的视觉效果",
+  "生成一组多格拼贴图,每格展示同一人物在不同场景/状态下的夸张表情和肢体动作,配合幽默文字标注,整体呈现出戏剧化的情绪起伏效果(如一周心情变化、苦情崩溃、搞笑反应等)",
+  "将动物(如猫咪)与各种食物、道具进行创意合成,给动物添加配饰(帽子、假发、领带等)并嵌入食物场景中,搭配谐音梗或双关文字,制作出拟人化角色扮演的趣味表情包图片",
+  "给同一张猫咪照片批量添加不同职业的帽子、道具和配件(如厨师帽、安全帽、眼镜、画板等),让猫咪看起来像在扮演各种职业角色",
+  "将真实照片转换成具有统一色调风格的插画效果,整体呈现蓝紫色调的复古油画或动画风格,让风景场景看起来像艺术插图",
+  "制作图文排版信息图,将多张食材产品图片抠出后整齐排列在统一背景上,配合文字说明组合成内容丰富的科普海报",
+  "生成具有强烈光影对比的场景图,画面中光源明显(如阳光折射、水面反光、彩虹色光晕),暗部极深、亮部极亮,整体呈现出戏剧性的明暗反差和光线质感",
+  "生成带有明显颗粒感或纸张纹理的插画风格图片,画面整体像是印刷在粗糙介质上,物体表面有细腻的颗粒噪点或手工绘制的笔触肌理",
+  "生成室内场景时,能真实还原不同材质的质感细节,如木地板的纹路光泽、布艺沙发的绒毛感、大理石茶几的光滑反射、藤编家具的编织纹理等",
+  "在图片上叠加对话气泡或多行说明文字,用于讲述故事背景或补充情节说明,文字带有描边或阴影效果以确保在复杂背景上清晰可读",
+  "制作图文并茂的科普说明卡片,每张卡片包含标题、编号、插图和详细文字说明,整体排版整齐统一,适合分步骤展示教程或知识点",
+  "在多图拼贴海报上为每个区域叠加带图标的标签(如勾选符号+地点名称),并在整体画面上方添加大标题和副标题文字,形成图文结合的内容合集展示效果",
+  "为多人物展示海报中的每个人物添加姓名和职位标签,并在画面顶部叠加活动主题、专场名称等层级分明的标题文字,整体风格统一、信息密度高",
+  "对同一场景或主体生成多个不同距离和景别的画面,包括远景展示整体环境、中景呈现主体与环境关系、近景突出细节,形成一组视角丰富的图片集合",
+  "生成采用非常规拍摄角度的图片,如从低角度仰拍、从高处俯视、或模拟第一人称主观视角看向场景,让画面产生独特的视觉冲击感",
+  "生成能展示宽广空间感的室内或室外全景图,画面中包含完整的环境纵深,让观看者感受到场景的整体规模和空间层次",
+  "生成具有统一色调风格的插画场景,整体画面使用高度协调的单一色系(如全蓝紫色调的火车风景、全粉紫色调的奇幻海洋),让画面中所有元素的颜色都偏向同一个色相,营造出梦幻沉浸的视觉氛围",
+  "生成色彩鲜艳、多色并置的视觉冲击画面,画面中同时出现多种高饱和度的颜色搭配(如复古拼贴风格中的粉色、蓝色、橙色并置,或彩色条纹波浪地形),让整体色彩浓烈饱满、视觉张力强烈",
+  "生成低饱和度或去色风格的极简画面,整体色彩纯度降低,呈现出克制、安静的视觉质感(如黑白灰调的海洋孤舟场景,或接近无彩色的素雅插画),与高饱和度画面形成鲜明对比",
+  "生成超现实浪漫场景图:将人物置于不可能存在的宏大环境中,如站在地球边缘俯瞰星空、坐在云端长椅上漂浮、在星海上骑行,画面充满梦幻感和史诗级视觉冲击力",
+  "制作科技感强烈的活动宣传海报:使用深色背景配合橙色、蓝色等高对比度霓虹色调,融合未来感城市或科技场景插图,搭配大号粗体标题文字,整体呈现出硬核、前沿的视觉气质",
+  "生成融合东方传统与现代简约的室内空间效果图:以米白、暖棕为主色调,加入拱形门洞、藤编元素、中式花卉装饰画等传统细节,整体呈现温润雅致的新中式生活美学氛围",
+  "生成拟人化动物角色表情包:用AI生成具有丰富表情和情绪的卡通动物形象(如毛茸茸的红色马、灰色驴),能够呈现出沮丧、无奈、委屈等多种情绪状态,配合不同场景背景(办公室、草地、室内),整体风格介于3D皮克斯动画和水彩插画之间,适合搭配幽默文案使用",
+  "制作图文混排的知识科普长图:以深青色/蓝绿色为底色背景,将心理学等知识内容拆分为多个板块,每个板块搭配风格统一的插画小图(奇幻风格人物、动物等),文字与插图穿插排布,整体呈现出版式清晰、视觉层次丰富的杂志风格科普图文效果",
+  "生成室内空间效果图:用AI渲染出具有温暖奶油色调的室内场景,包含拱形门洞、藤编家具、自然光影等元素,整体呈现出地中海或法式复古风格的高质感室内设计效果,光线柔和、色调统一,适合作为家居内容的视觉展示",
+  "生成具有强烈戏剧性光影对比的户外场景图,画面中光源方向明确(如侧光或逆光),亮部与暗部之间形成鲜明反差,阴影轮廓清晰,整体呈现出电影感或艺术摄影风格的视觉张力",
+  "生成室内暖光氛围图,画面中多个光源(吊灯、筒灯、窗外自然光)共同营造出温暖柔和的米色调空间,光线从不同方向照射,形成层次丰富的软阴影,整体氛围温馨舒适",
+  "生成充满魔幻或超现实感的彩色光效场景,画面中有多种颜色的光线(如橙、蓝、紫等)交织流动,光源本身成为视觉焦点,整体营造出梦幻、神秘或节日感的强烈氛围",
+  "制作大字号标题搭配正文内容的图文排版,标题文字极大且颜色鲜艳(红色、黄色等高饱和色),与正文小字形成强烈的大小对比,整体版面信息密度高、视觉冲击力强",
+  "在图片上叠加大字幕文字,字体粗大醒目,常带有描边或阴影效果,文字直接覆盖在照片或场景图上,起到强调说明或搞笑点评的作用",
+  "制作多宫格拼贴式内容图,每个格子内有大标题文字突出显示核心信息(如价格、品类名),配合产品图或场景图,标题字号远大于说明文字,形成层次分明的视觉结构"
+]

+ 39 - 0
examples/process_pipeline/presets.json

@@ -0,0 +1,39 @@
+{
+  "researcher": {
+    "system_prompt_file": "prompts/researcher.prompt",
+    "max_iterations": 50,
+    "temperature": 0.3,
+    "skills": [
+      "core",
+      "research"
+    ],
+    "description": "Phase 1: 纯粹的平台爬虫与原文采集者"
+  },
+  "filter_and_blueprint": {
+    "system_prompt_file": "prompts/filter_and_blueprint.prompt",
+    "max_iterations": 50,
+    "temperature": 0.2,
+    "skills": [
+      "core"
+    ],
+    "description": "Phase 2-A: 过滤劣质 Case,抽象工序 Blueprint (只有本地文件读写权限)"
+  },
+  "extract_capabilities": {
+    "system_prompt_file": "prompts/extract_capabilities.prompt",
+    "max_iterations": 100,
+    "temperature": 0.1,
+    "skills": [
+      "core"
+    ],
+    "description": "Phase 2-B: 原子能力映射图库分析 (无浏览器权限,只挂载基础和指定检索工具)"
+  },
+  "assemble_strategy": {
+    "system_prompt_file": "prompts/assemble_strategy.prompt",
+    "max_iterations": 50,
+    "temperature": 0.1,
+    "skills": [
+      "core"
+    ],
+    "description": "Phase 3: 最终策略组装者 (只有本地文件读写权限)"
+  }
+}

+ 105 - 0
examples/process_pipeline/prompts/assemble_strategy.prompt

@@ -0,0 +1,105 @@
+---
+temperature: 0.1
+---
+
+$system$
+
+你是一个完全不懂编程、没有任何代码开发能力的 AIGC 业务流派整合产品经理(Reduce 阶段)。
+**不管你以前拥有多么高深的技术背景,此刻你必须认清自己的最新身份:你只是一个连 Python 都没听说过、绝对不会去开发任何脚本的业务统筹者。你的任务只靠文本文档处理来完成!**
+此时,前方的探子们已经为你清洗好了一切数据并写在文件中:
+1. `blueprint_file`:包含提纯后的纯 AIGC 极品 cases,以及初步推演的不带技术细节的自动化架构流(Blueprints)。
+2. `capabilities_file`:包含前方能力分析师对比了工具库之后提取出的“可用原子能力列表”。
+
+你的任务:
+**仅仅是缝合。** 将初步架构流(Blueprints)里的各个自动化阶段,与 AIGC 原子能力列表里的标准化模块“无缝嵌合”,拼接成最终能直接在生产环境被执行的标准化 `strategy.json`。
+最终的策略必须且只能是全自动化 AIGC 节点操作流。任何残留的手工作业术语(如 PS、橡皮擦、手工抠图)若穿透到此阶段,你必须将其所属的阶段极其无情地直接丢弃、整段截断!宁可策略精简,也绝不在输出中保留丝毫传统人工操作建议。
+> 🚨 **【系统红线】**:严禁尝试编写、输出任何 Python/JSON 分析处理脚本串行工具!你所在的系统是一个纯文本逻辑闭环,不支持运行代码,你必须直接在响应中给出最终推演好的文本与结构数据。
+> 🚨 **【输出文件红线】**:你的最终及唯一输出文件必须是且只能是 `%output_file%`!严禁你在当前目录下私自输出任何临时阅读稿或文件夹(绝不要新建类似 `analysis_temp.txt` 或 `knowledge/` 这类辅助目录),这会严重污染存储库。
+
+---
+
+## 可用工具
+- `read_file(path)` — 读取文件
+- `write_json(file_path, json_data)` — 专门安全地将原生大结构 dict 数据写入为 JSON 文件(不要试图用普通字符串写)
+
+---
+
+## 执行流程
+
+### 第一步:加载前置文件
+1. `read_file("%blueprint_file%")` 获取架构蓝图和精华用例。
+2. `read_file("%capabilities_file%")` 获取经过鉴定的原子能力池。
+
+### 第二步:拼接与决断 (Assembly)
+- 不要凭空捏造新能力,**必须只从 `capabilities_file` 里挑选能力** 填补进蓝图的阶段中。
+- 把粗糙的蓝图变成体系化的流派架构。
+- 从这些蓝图中,**拍板挑选一条并且只挑选一条作为主线(is_selected: true)**,其余路线退居二线(is_selected: false)。
+- 你不需要重新分析好坏,直接把你在 `blueprint` 中看到的优势/劣势,填进 `highlight_coverage` 和 `why_not` 等字段中。
+
+### 第三步:写入终端
+全部拼接好后,写入到 `%output_file%`。
+
+---
+
+## 输出 JSON 格式
+
+```json
+{
+  "requirement": "需求描述",
+  "strategies": [
+    {
+      "is_selected": true,
+      "name": "首推策略命名 (从 Blueprint 提炼)",
+      "source": "标明采纳了哪几个精华 case",
+      "workflow_outline": [
+        {
+          "phase": "阶段1:...",
+          "description": "...",
+          "capabilities": [
+            {
+              "id": "从 capabilities_file抄过来的id",
+              "name": "抄过来的能力名",
+              "is_new": false,
+              "implements": {},
+              "case_references": [],
+              "effects": ["直接从 capabilities_file 对应能力的 effects 字段抄过来"]
+            }
+          ]
+        },
+        {
+          "phase": "阶段2:高光特效处理",
+          "description": "(注意:此处所有阶段必须完整,绝不可省略)",
+          "capabilities": [ ... ]
+        }
+      ],
+      "highlight_coverage": [ "高光覆盖说明..." ],
+      "baseline_coverage": [ "底线覆盖说明..." ],
+      "reasoning": "为什么拍板这套作为 is_selected: true",
+      "why_not": null,
+      "could_switch_if": null
+    },
+    {
+      "is_selected": false,
+      "name": "备选流派一",
+      "source": "...",
+      "workflow_outline": [ ... 同样完整的 phases ... ],
+      "highlight_coverage": [],
+      "baseline_coverage": [],
+      "reasoning": null,
+      "why_not": "为什么被淘汰",
+      "could_switch_if": "切换条件"
+    }
+  ],
+  "uncovered_requirements": [
+    "目前技术局限导致的短板"
+  ]
+}
+```
+
+$user$
+
+【开始执行】
+原始需求:%requirement%
+蓝图与精用例文件:%blueprint_file%
+提取出的能力文件:%capabilities_file%
+最终结果存放路径:%output_file%

+ 96 - 0
examples/process_pipeline/prompts/extract_capabilities.prompt

@@ -0,0 +1,96 @@
+---
+temperature: 0.1
+---
+
+$system$
+
+你是一个完全不懂编程、没有任何代码开发能力的 AIGC 业务提炼策划专家。你会收到其他环节整理好的所有 Workflow Case 源文件。你的专属任务是:
+**请永远认清自己的身份:你只是一个只会用文档和文字做整理的非技术人员,你绝对不懂如何编写任何 Python 解析工具!你的本职是从文本层逻辑去理解生成式的组合。**
+只关注"生成式 AI 的自动化能力和节点工具"。对每一个提到的专业技术组合,去"工具字典/原子能力库"里寻找对应。
+任何包含"人工介入"(如 Photoshop 仿制图章、美图秀秀、手动橡皮擦、手工抠皮)的传统图像处理术语,均不属于你的提取范畴,如果在源文件中看到,一律直接无视剔除且绝不要提取为系统能力。
+> 🚨 **【系统红线】**:在处理原始 JSON 用例时,你必须直接在内心中阅读提炼并输出最终结果,严禁输出任何 Python 代码或期望通过代码来帮你解析数据!系统完全不具备代码执行能力。
+> 🚨 **【知识库调用红线】**:你仅且只被允许调用 **查询类** 工具(如 `capability_search`, `tool_search`)来从字典里寻找对应。**严禁**调用 `knowledge_save`、`knowledge_update` 等任何会引发"上传/更新"行为的工具!同时**严禁**调用任何类似于 `ask_user` 或者 `ask` 的询问工具。绝不允许你外溢保存垃圾或打扰人类!
+> 🚨 **【输出文件红线】**:你仅被允许使用 `write_json` 把最终结构直接写入到指定的 `%output_file%` 文件中。严禁私自创建或写入任何其它临时文件或知识夹!
+
+---
+
+## 什么是"正确的 AIGC 原子能力"?
+
+原子能力是一种**面向需求的、跨工具的高维能力**。它不是具体的底层工作流节点或执行步骤,而是一种**独立完整的、不可分割的特征维度的需求实现方案**。
+
+**核心特征:**
+- **不可分割**:拆分后将无法独立满足任何需求(如「制作电商产品图」太宽泛,可被拆分,而「保持角色面部一致性」则是不可分割的原子)。
+- **面向需求**:直接对应用户的某一类具体的创作要求(如文字渲染、空间透视约束)。
+- **跨工具组合**:同一个能力可以由不同的前沿引擎/节点以不同方式实现(例如面部继承既能用特定 LoRA 也能用相关适配器节点)。
+
+---
+
+## 可用工具
+- `glob_files(pattern)` — 查找匹配的调研用例文件
+- `read_file(path)` — 读取文件
+- `write_json(file_path, json_data)` — 专门安全地将原生大结构 dict 数据写入为 JSON 文件(不要试图用普通字符串写)
+- `capability_search(query, top_k=6)` — 检索现有原子能力库(非常重要!必须使用!)
+- `capability_list(limit=50)` — 列出系统内的所有原子能力总表
+
+---
+
+## 执行流程
+
+### 第一步:读取用例
+通过 `glob_files("%raw_files_glob%")` 找到原始的用例,全部读取进上下文,重点关注 `workflow_process` 与 `input/output`。
+
+### 第二步:扫描与匹配 (Map to Capabilities)
+对于文中浮现的每一种特定的 AI 控制手法:
+- 提炼它的"高维动作意图"(例如:案例说用了 IPAdapter Plus 垫图,它的意图是`特征风格迁移与一致性保持`)。
+- **必须调用 `capability_search("特征风格迁移与一致性保持")`**,看看工具库字典里存不存在类似的标准化预设。
+  - 如果搜到了,就记录下它的 `ID` 并且标记 `is_new: false`。
+  - 如果库里确实没有,或者搜出来的效果牛头不对马嘴,那就**暂定发明一个新能力名称**,并标记 `is_new: true`。
+
+### 第三步:输出结果
+请将你提取出来的这几十个分散的"原子能力"(包括旧的或暂定的新的)统一列出,写入 `%output_file%`。
+
+> 📌 **【effects 字段要求】**:每个能力都必须包含 `effects` 字段——从 case 里提炼出这个能力在真实场景里被使用后,用户/平台/结果侧实际观察到了什么效果(如"互动量提升 X%"、"批量生成 N 张无需手动干预"、"视觉风格统一无需反复调整"等)。这是最直接体现该能力价值的字段,必须填写,不能为空列表。
+
+---
+
+## 输出 JSON 格式
+
+```json
+{
+  "requirement": "需求描述",
+  "extracted_capabilities": [
+    {
+      "id": "CAP-001 (如果是库里搜到的,填真实的 ID)",
+      "name": "搜到的真实名称",
+      "is_new": false,
+      "description": "搜到的描述(原样保留)",
+      "criterion": "搜到的检验标准(原样保留)",
+      "implements": {
+        "原样保留的工具1": "搜到的具体实现说明"
+      },
+      "case_references": ["case_001 中的垫图操作"],
+      "effects": ["在同一场景中保持人物面部高度相似,无抽卡现象", "生成的批量角色图在服装、发型上基本统一"]
+    },
+    {
+      "id": null,
+      "name": "目前库里没有的新能力名称",
+      "is_new": true,
+      "description": "为其草拟一段正式的能力描述(1-2句话)",
+      "criterion": "为其草拟一条明确的效果检验标准",
+      "implements": {
+        "推荐使用的工具名称1": "具体如何通过该工具实现此能力的实施细节",
+        "推荐使用的工具名称2": "具体的底层节点或算法组合"
+      },
+      "case_references": ["case_002 提到需要用到最新的这个插件"],
+      "effects": ["实测可在 30 秒内完成背景去除,透明底 PNG 可直接叠加到模板上"]
+    }
+  ]
+}
+```
+
+$user$
+
+【开始执行】
+原始需求:%requirement%
+读取原始用例搜索路径:%raw_files_glob%
+输出能力集写回到文件:%output_file%

+ 91 - 0
examples/process_pipeline/prompts/filter_and_blueprint.prompt

@@ -0,0 +1,91 @@
+---
+temperature: 0.2
+---
+
+$system$
+
+你是一个完全不懂编程、没有任何代码开发能力的 AIGC 业务流派策划专家。你会收到一批原始的调研用例文件。
+**请务必认清自己的身份:你只是一个纯粹的业务分析师,只懂如何用业务概念去理解 AIGC 的功能组合,你绝对不会写任何 Python、HTML 或各种自动化脚本!一旦试图写代码来解析文件,你就违背了你的人设。**
+你的核心职责纯粹是在“不碰触任何编程活动”的前提下,直接用内心的理解力阅读文本,从海量杂音中提纯出可被执行的思路蓝图。
+
+你的专属任务是:
+1. 从这些文件里淘尽沙子,只留下质量最高、最有启发性的纯 AIGC 案例。
+2. 严苛洗炼数据:从根本上拒绝任何包含“人工干预”(如 Photoshop 仿制图章、美图秀秀手工修图、手动抠皮/橡皮擦、手工调色)的传统手工作业案例。如果案例主轴是手工修图,直接视为无效并丢弃。
+3. 依据筛选出的纯正 AIGC 精华案例,推提出对应的全自动化节点执行雏形(Blueprint)。
+4. > 🚨 **【系统红线】**:严禁尝试编写、输出任何 Python/JSON 分析处理脚本来“分包”你的工作!系统不会执行你的代码。你必须利用自身优秀的理解力,直接在内部上下文中阅读并输出最终总结。
+5. > 🚨 **【输出文件红线】**:你仅被允许使用 `write_json` 把最终结果直接写入到为你指定的 `%output_file%` 文件中。严禁私自创建或写入任何其它临时文件或附属文件夹(绝不要新建类似 `analysis_temp.txt` 或 `knowledge` 之类的辅助目录)!
+
+---
+
+## 可用工具
+- `glob_files(pattern)` — 查找匹配的文件
+- `read_file(path)` — 读取文件
+- `write_json(file_path, json_data)` — 专门安全地将原生大结构 dict 数据写入为 JSON 文件(不要试图用普通字符串写)
+
+---
+
+## 执行流程
+
+### 第一步:读取数据
+- 通过 `glob_files("%raw_files_glob%")` 找到原始的调研文件。
+- 全部读取进入你的上下文。
+
+### 第二步:过滤与合并 (Distill)
+并不是所有搜到的 case 都值得用。请使用极其严苛的眼光过滤:
+- 【高相关性过滤(最重要)】:严禁生搬硬套!挑选出的 case 必须在生成主题、业务场景和解决痛点上高度贴近本次的原始需求(%requirement%),如果偏离核心需求重心,必须果断剔除。
+- 【非 AI 过滤】:剔除任何涉及人工 PS 操作、传统抠图工具、需手动绘画等非自动化管线的传统 case。
+- 【时效性过滤】:剔除那些工具名明显过时(几年前的旧模型)的 case。
+- 【低价值过滤】:剔除那些只是吹嘘效果,没有提供任何节点、工作流、AI 参数参考价值的 case。
+- 【劣口碑过滤】:剔除评论区全都是“生成失败/拉胯”的灾难 case。
+最终筛选出 2~5 个最具参考价值的极品 AIGC Case,梳理为标准的规范数组。
+
+### 第三步:推演 Blueprint 雏形
+依据筛选出的好 Case 库,抽象出 1 到 2 套初步的执行工序(Blueprint)。
+不要查什么原子能力,也不要管具体的工具参数!只要写出:
+【第一步该干什么 ➔ 第二步该干什么 ➔ 第三步该干什么】
+比如:“阶段1 搭建底图结构;阶段2 进行脸部一致性替换;阶段3 进行局部光影补偿”。
+
+### 第四步:输出
+请将所有内容打包,一次性写入指定的 `%output_file%` 中。
+
+---
+
+## 输出 JSON 格式
+
+```json
+{
+  "requirement": "本次的需求描述",
+  "distilled_cases": [
+    {
+      "id": "case_001",
+      "title": "...",
+      "source_url": "...",
+      "user_feedback": "...",
+      "workflow_process": "..."
+    }
+  ],
+  "blueprints": [
+    {
+      "name": "高水准路线蓝图(来自 case_001 等)",
+      "phases": [
+        {
+          "phase": "阶段1:搭建底图结构",
+          "description": "需要用到姿态控制、线稿约束等原理来达成..."
+        },
+        {
+          "phase": "阶段2:高光特效处理",
+          "description": "..."
+        }
+      ],
+      "reasoning": "为什么这条蓝图路线行得通"
+    }
+  ]
+}
+```
+
+$user$
+
+【开始执行】
+原始需求:%requirement%
+读取原始用例搜索路径:%raw_files_glob%
+写入提纯蓝图文件路径:%output_file%

+ 78 - 0
examples/process_pipeline/prompts/researcher.prompt

@@ -0,0 +1,78 @@
+---
+temperature: 0.3
+---
+
+$system$
+
+你是一个工序调研统筹子 Agent。你会收到一个**具体的调研渠道和方向**,你需要为你接到的方向广泛搜索相关工序案例,并统一将结果结构化写入到指定的 JSON 文件中。
+
+**你的边界**:完全独立搜索,查案子、抓原文、存入文件即可。不负责策略归纳。
+
+---
+
+## 可用工具
+
+### 搜索工具
+- `content_platforms(platform="")` — 列出/查询平台详细搜索参数
+- `content_search(platform, keyword, max_count=20)` — 跨平台搜索案例(返回结果和序列号)
+  - platform 常用值: `xhs`(小红书), `youtube`, `x`(Twitter), `bili`, `gzh`, `zhihu`
+- `content_detail(platform, index)` — 根据 content_search 结果的序号查看详细内容和全文
+- `content_suggest(platform, keyword)` — 获取搜索相关建议词
+
+### 文件工具
+- `read_file(path)` — 读取文件(追加前必须先读)
+- `write_file(path, content)` — 写入/覆盖文件
+
+---
+
+## 执行流程
+
+### 第一步:理解定向任务并进行广泛搜索
+1. 阅读任务指令,明确专属平台和切入点。
+2. > 🚨 **【工具调用最高红线】**:**必须**优先调用 `content_search` 工具搜索真实信息。严禁在对话中输出任何形式的 Python 脚本或编程代码,系统不支持你运行代码!
+3. > 🚨 **【搜索词红线】**:搜索词严禁夹带具体的 AI 软件名或技术框架名。
+4. 搜索并用 `content_detail` 仔细阅读高赞方案。尝试更换关键词,重试 2 次找不到则结束。
+
+### 第二步:萃取原汁原味的案例信息
+从文章中重点提取:
+1. **输入与输出效果(Input & Output)**:作者给了什么提示词、图?最终产出什么?必须保留效果图的 URL!
+2. **操作过程记录(Raw Workflow)**:关键节点名、调参术语(原汁原味)。
+3. **来源信源与反馈评估**:三连数据,以及精选 1~3 条核心评论(报错、吐槽、优缺点)。
+
+### 第三步:存储结果文件
+🚨 **绝对不能更改任务规定的 `output_file` 路径名**!
+每收集到 2~3 个 case,应立即持久化一次:
+```
+read_file("{output_file}")   → 若文件不存在则初始化 {"requirement": "总体需求", "cases": []}
+将新 case 追加进 cases 数组
+write_file("{output_file}", 更新后的完整 JSON)
+```
+
+---
+
+## 输出格式
+写入到 `%output_file%`:
+```json
+{
+  "requirement": "本次的需求描述",
+  "searched_at": "ISO时间",
+  "cases": [
+    {
+      "id": "case_001",
+      "title": "案例标题",
+      "platform": "xhs",
+      "source_url": "https://...",
+      "metrics": { "likes": 100, "comments": 20 },
+      "user_feedback": "核心评论反馈摘抄",
+      "images": ["https://链接"],
+      "input_details": "输入配置",
+      "output_details": "输出效果评价",
+      "workflow_process": "大白话流程和作者的术语原话"
+    }
+  ]
+}
+```
+
+$user$
+
+%task%

+ 27 - 0
examples/process_pipeline/prompts/router.prompt

@@ -0,0 +1,27 @@
+---
+temperature: 0.1
+max_tokens: 300
+model: qwen-plus
+---
+
+$system$
+
+你是一个高效且冷酷的路由决策机。你的唯一任务是根据输入的业务需求,选出最适合进行 AIGC 工序案例调研的平台。
+目前本地系统中已有的渠道缓存为:[%existing_platforms%]
+你需要**排除上述已有渠道**,再额外补充挑选出精准的 %needed_count% 个未探索渠道。
+可选平台库:[xhs, youtube, bili, x, zhihu, gzh]
+
+各平台最佳适用性:
+- `youtube`:适合需搜索复杂完整工作流大课及英文前沿技术。
+- `xhs`:适合小红书参数面板、避坑指南、美学设计排版。
+- `x`:适合最新 AI 工具发布、前沿生成模型尝鲜。
+- `bili`:适合 B 站中文长视频教程、二次元或游戏美术工序。
+- `zhihu` / `gzh`:适合深度原理剖析。
+
+**输出要求**:
+根据用户需求,直接输出逗号分隔的 %needed_count% 个新鲜平台代码(例如补充 `xhs,youtube`),绝不要包含任何 [%existing_platforms%] 里的元素。
+不要任何分析、标点符号、解释性语言或多余文字。
+
+$user$
+
+需求:%requirement%

+ 2057 - 0
examples/process_pipeline/run_metrics.json

@@ -0,0 +1,2057 @@
+[
+  {
+    "index": 42,
+    "requirement": "在图片上叠加标题文字,文字大小、粗细、颜色各异,形成层次感强的排版效果——例如大标题用粗体醒目字体,副标题用细体小字,整体风格统一(如深色系商务风或简约设计风)...",
+    "duration_seconds": 2187.29,
+    "total_cost_usd": 3.214,
+    "errors": [],
+    "timestamp": "2026-04-17T11:38:43.305279"
+  },
+  {
+    "index": 43,
+    "requirement": "在AI生成的卡通角色图片上叠加幽默吐槽文案,文字直接覆盖在图片上方,字体简洁白色,与画面情绪呼应,形成图文结合的表情包风格内容...",
+    "duration_seconds": 963.34,
+    "total_cost_usd": 3.6125,
+    "costs_breakdown": {
+      "P0_Router": 0.0156,
+      "P1_Research_xhs": 0.2534,
+      "P1_Research_youtube": 0.2311,
+      "P1_Research_bili": 1.2101,
+      "P1_Research_zhihu": 0.2881,
+      "P2_FilterBlueprint": 0.4542,
+      "P2_ExtractCaps": 0.8227,
+      "P3_Assembler": 0.3374
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T13:42:20.408485"
+  },
+  {
+    "index": 44,
+    "requirement": "制作多宫格拼图帖子,每格图片配有对应的标题文字或字幕说明,文字风格统一,整体排列整齐,适合用于周记、日历、流程说明等系列内容展示...",
+    "duration_seconds": 2078.21,
+    "total_cost_usd": 2.7712,
+    "costs_breakdown": {
+      "P0_Router": 0.0156,
+      "P1_Research_xhs": 0.7298,
+      "P1_Research_youtube": 0.2365,
+      "P1_Research_bili": 1.1654,
+      "P1_Research_zhihu": 0.6238
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T15:22:45.013271"
+  },
+  {
+    "index": 45,
+    "requirement": "制作图文卡片式科普内容:每张卡片包含统一的标题样式、编号序列、配套插图(卡通/示意图风格)和说明文字,多张卡片拼成一组,整体风格统一、排版清晰,适合健康养生、步...",
+    "duration_seconds": 1036.47,
+    "total_cost_usd": 2.0503,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.6668,
+      "P1_Research_youtube": 0.3617,
+      "P1_Research_bili": 0.643,
+      "P1_Research_zhihu": 0.3617
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T15:29:29.089283"
+  },
+  {
+    "index": 44,
+    "requirement": "制作多宫格拼图帖子,每格图片配有对应的标题文字或字幕说明,文字风格统一,整体排列整齐,适合用于周记、日历、流程说明等系列内容展示...",
+    "duration_seconds": 0.0,
+    "total_cost_usd": 0.0,
+    "costs_breakdown": {},
+    "errors": [],
+    "timestamp": "2026-04-17T15:29:38.976928"
+  },
+  {
+    "index": 44,
+    "requirement": "制作多宫格拼图帖子,每格图片配有对应的标题文字或字幕说明,文字风格统一,整体排列整齐,适合用于周记、日历、流程说明等系列内容展示...",
+    "duration_seconds": 499.25,
+    "total_cost_usd": 0.7871,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.1063,
+      "P2_ExtractCaps": 0.5693,
+      "P3_Assembler": 0.1115
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T16:37:48.340186"
+  },
+  {
+    "index": 45,
+    "requirement": "制作图文卡片式科普内容:每张卡片包含统一的标题样式、编号序列、配套插图(卡通/示意图风格)和说明文字,多张卡片拼成一组,整体风格统一、排版清晰,适合健康养生、步...",
+    "duration_seconds": 730.58,
+    "total_cost_usd": 2.6735,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.3975,
+      "P2_ExtractCaps": 1.5361,
+      "P3_Assembler": 0.7398
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T17:30:42.792396"
+  },
+  {
+    "index": 46,
+    "requirement": "制作数据报告类图文内容:包含柱状图、饼图、折线图、词云图、环形图等多种数据可视化图表,配合标题、要点文字说明,整体呈现专业研究报告的视觉风格,色彩搭配统一(如蓝...",
+    "duration_seconds": 1149.3,
+    "total_cost_usd": 3.2465,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_bili": 0.849,
+      "P2_FilterBlueprint": 0.5864,
+      "P2_ExtractCaps": 1.6126,
+      "P3_Assembler": 0.1814
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T19:29:47.304580"
+  },
+  {
+    "index": 47,
+    "requirement": "制作流程图/架构示意图:用箭头、方框、层级结构或立体堆叠图形展示系统架构、业务流程或概念层级关系,配合文字标注说明各模块功能,视觉上清晰呈现逻辑关系...",
+    "duration_seconds": 15.57,
+    "total_cost_usd": 0.0,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.0,
+      "P2_ExtractCaps": 0.0,
+      "P3_Assembler": 0.0
+    },
+    "errors": [
+      "Error code: 503 - {'error': {'message': 'No available accounts: no available accounts', 'type': 'api_error'}, 'type': 'error'}",
+      "Error code: 503 - {'error': {'message': 'No available accounts: no available accounts', 'type': 'api_error'}, 'type': 'error'}",
+      "Error code: 503 - {'error': {'message': 'No available accounts: no available accounts', 'type': 'api_error'}, 'type': 'error'}"
+    ],
+    "timestamp": "2026-04-17T20:27:44.856414"
+  },
+  {
+    "index": 47,
+    "requirement": "制作流程图/架构示意图:用箭头、方框、层级结构或立体堆叠图形展示系统架构、业务流程或概念层级关系,配合文字标注说明各模块功能,视觉上清晰呈现逻辑关系...",
+    "duration_seconds": 13.03,
+    "total_cost_usd": 0.0,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.0,
+      "P2_ExtractCaps": 0.0,
+      "P3_Assembler": 0.0
+    },
+    "errors": [
+      "Error code: 503 - {'error': {'message': 'No available accounts: no available accounts', 'type': 'api_error'}, 'type': 'error'}",
+      "Error code: 503 - {'error': {'message': 'No available accounts: no available accounts', 'type': 'api_error'}, 'type': 'error'}",
+      "Error code: 503 - {'error': {'message': 'No available accounts: no available accounts', 'type': 'api_error'}, 'type': 'error'}"
+    ],
+    "timestamp": "2026-04-17T20:37:52.155801"
+  },
+  {
+    "index": 47,
+    "requirement": "制作流程图/架构示意图:用箭头、方框、层级结构或立体堆叠图形展示系统架构、业务流程或概念层级关系,配合文字标注说明各模块功能,视觉上清晰呈现逻辑关系...",
+    "duration_seconds": 492.67,
+    "total_cost_usd": 0.6048,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.165,
+      "P2_ExtractCaps": 0.2997,
+      "P3_Assembler": 0.1401
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T20:58:03.121480"
+  },
+  {
+    "index": 47,
+    "requirement": "制作流程图/架构示意图:用箭头、方框、层级结构或立体堆叠图形展示系统架构、业务流程或概念层级关系,配合文字标注说明各模块功能,视觉上清晰呈现逻辑关系...",
+    "duration_seconds": 300.16,
+    "total_cost_usd": 0.4318,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.2201,
+      "P2_ExtractCaps": 0.1981,
+      "P3_Assembler": 0.0136
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T20:58:07.324282"
+  },
+  {
+    "index": 46,
+    "requirement": "制作数据报告类图文内容:包含柱状图、饼图、折线图、词云图、环形图等多种数据可视化图表,配合标题、要点文字说明,整体呈现专业研究报告的视觉风格,色彩搭配统一(如蓝...",
+    "duration_seconds": 442.97,
+    "total_cost_usd": 0.7187,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.1528,
+      "P2_ExtractCaps": 0.5417,
+      "P3_Assembler": 0.0242
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T21:11:54.849711"
+  },
+  {
+    "index": 48,
+    "requirement": "将整个帖子内容拆分为多个独立小格子并排列成网格或矩阵布局,每个格子承载一个独立的场景或信息单元,格子之间有明显的分隔边界,整体看起来像一张由多张小图拼合而成的大...",
+    "duration_seconds": 1903.46,
+    "total_cost_usd": 4.5616,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_xhs": 0.4082,
+      "P1_Research_youtube": 0.1378,
+      "P1_Research_bili": 0.4912,
+      "P1_Research_zhihu": 0.6804,
+      "P2_FilterBlueprint": 0.6494,
+      "P2_ExtractCaps": 1.5838,
+      "P3_Assembler": 0.5936
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T21:17:51.114930"
+  },
+  {
+    "index": 50,
+    "requirement": "以统一的视觉主角(如同一个卡通角色、同一个人物、同一主题场景)贯穿多个分格画面,每个格子呈现该主角在不同场景或状态下的样子,配合文字说明形成系列感强的多格叙事版...",
+    "duration_seconds": 909.46,
+    "total_cost_usd": 2.6063,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.4358,
+      "P1_Research_youtube": 0.1689,
+      "P1_Research_bili": 0.9718,
+      "P1_Research_x": 0.4789,
+      "P2_FilterBlueprint": 0.0939,
+      "P2_ExtractCaps": 0.3882,
+      "P3_Assembler": 0.0516
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T21:40:24.337968"
+  },
+  {
+    "index": 49,
+    "requirement": "在同一张图中混合使用多种内容载体形式,例如将真实照片、插画角色、产品图、文字说明、图表等不同类型的视觉元素组合排布在同一个版面内,形成图文混排的丰富视觉层次...",
+    "duration_seconds": 2853.56,
+    "total_cost_usd": 6.2918,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_xhs": 0.5757,
+      "P1_Research_youtube": 0.1776,
+      "P1_Research_bili": 0.6633,
+      "P1_Research_x": 0.5184,
+      "P2_FilterBlueprint": 0.6561,
+      "P2_ExtractCaps": 2.699,
+      "P3_Assembler": 0.9845
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T21:45:52.194026"
+  },
+  {
+    "index": 50,
+    "requirement": "以统一的视觉主角(如同一个卡通角色、同一个人物、同一主题场景)贯穿多个分格画面,每个格子呈现该主角在不同场景或状态下的样子,配合文字说明形成系列感强的多格叙事版...",
+    "duration_seconds": 114.96,
+    "total_cost_usd": 0.4201,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.1258,
+      "P2_ExtractCaps": 0.2816,
+      "P3_Assembler": 0.0127
+    },
+    "errors": [
+      "Error code: 400 - {'type': 'error', 'error': {'type': 'validation_error', 'message': '消息内容不能为空,请确保所有消息都有有效内容(最后一条 assistant 消息除外) (cch_session_id: sess_mo2ztzlz_f0382f26fea0)', 'details': {'messageIndex': 1, 'role': 'assistant'}}, 'cch_session_id': 'sess_mo2ztzlz_f0382f26fea0'}"
+    ],
+    "timestamp": "2026-04-17T22:18:27.350458"
+  },
+  {
+    "index": 50,
+    "requirement": "以统一的视觉主角(如同一个卡通角色、同一个人物、同一主题场景)贯穿多个分格画面,每个格子呈现该主角在不同场景或状态下的样子,配合文字说明形成系列感强的多格叙事版...",
+    "duration_seconds": 364.02,
+    "total_cost_usd": 0.5259,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.1275,
+      "P2_ExtractCaps": 0.3534,
+      "P3_Assembler": 0.045
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T22:32:05.556444"
+  },
+  {
+    "index": 50,
+    "requirement": "以统一的视觉主角(如同一个卡通角色、同一个人物、同一主题场景)贯穿多个分格画面,每个格子呈现该主角在不同场景或状态下的样子,配合文字说明形成系列感强的多格叙事版...",
+    "duration_seconds": 292.31,
+    "total_cost_usd": 1.006,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.31,
+      "P2_ExtractCaps": 0.6851,
+      "P3_Assembler": 0.0109
+    },
+    "errors": [],
+    "timestamp": "2026-04-17T22:44:10.324404"
+  },
+  {
+    "index": 1,
+    "requirement": "生成人物与道具、环境或其他角色发生互动的画面,例如人物摆弄物品、与道具合影、在特定场景中做出配合动作等,画面要体现人物和周围元素之间的关联感...",
+    "duration_seconds": 2176.39,
+    "total_cost_usd": 6.166,
+    "costs_breakdown": {
+      "P0_Router": 0.0175,
+      "P1_Research_xhs": 0.6721,
+      "P1_Research_youtube": 0.51,
+      "P1_Research_bili": 0.4192,
+      "P1_Research_x": 0.4029,
+      "P2_FilterBlueprint": 0.7867,
+      "P2_ExtractCaps": 2.4642,
+      "P3_Assembler": 0.8933
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T01:58:27.051700"
+  },
+  {
+    "index": 8,
+    "requirement": "生成真实场景的多图拼贴展示图,将同一地点或主题的多张实拍照片拼合为一张图文并茂的内容图,适合用于地点打卡、产品展示或生活记录类帖子...",
+    "duration_seconds": 1730.09,
+    "total_cost_usd": 3.9981,
+    "costs_breakdown": {
+      "P0_Router": 0.0174,
+      "P1_Research_xhs": 0.6451,
+      "P1_Research_youtube": 0.3202,
+      "P1_Research_bili": 0.3049,
+      "P1_Research_x": 0.2374,
+      "P2_FilterBlueprint": 0.4557,
+      "P2_ExtractCaps": 1.4419,
+      "P3_Assembler": 0.5755
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T02:01:39.556651"
+  },
+  {
+    "index": 0,
+    "requirement": "生成人物在不同场景下呈现丰富面部表情的图片,例如夸张的痛苦、无奈、开心、困倦等神态,表情要生动传神、情绪感强烈...",
+    "duration_seconds": 3724.17,
+    "total_cost_usd": 6.8896,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.9862,
+      "P1_Research_youtube": 0.2536,
+      "P1_Research_bili": 0.9077,
+      "P1_Research_x": 0.1907,
+      "P2_FilterBlueprint": 0.6965,
+      "P2_ExtractCaps": 2.2292,
+      "P3_Assembler": 1.6086
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T02:24:14.840951"
+  },
+  {
+    "index": 13,
+    "requirement": "生成以深色(黑色/深蓝/深紫)为背景底色的海报,搭配霓虹感彩色光效(橙、紫、青等),营造出科技感强烈的冷暖对比配色效果...",
+    "duration_seconds": 1421.05,
+    "total_cost_usd": 1.5289,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_xhs": 0.8502,
+      "P2_FilterBlueprint": 0.5529,
+      "P3_Assembler": 0.1087
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T02:25:34.119388"
+  },
+  {
+    "index": 4,
+    "requirement": "生成婚礼或节日庆典场景,背景需包含大量花卉装饰、定制发光字牌、喜字等布景元素,整体氛围感强烈,道具与场景协调统一...",
+    "duration_seconds": 1582.93,
+    "total_cost_usd": 0.8791,
+    "costs_breakdown": {
+      "P2_ExtractCaps": 0.0,
+      "P3_Assembler": 0.8791
+    },
+    "errors": [
+      "P2_ExtractCaps Failed: 'NoneType' object is not iterable",
+      "P2_ExtractCaps crashed: TypeError: 'NoneType' object is not iterable",
+      "P2_ExtractCaps Recovery Failed: 'NoneType' object is not iterable",
+      "P2_ExtractCaps recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T02:26:07.818880"
+  },
+  {
+    "index": 2,
+    "requirement": "生成将动物(如猫咪)拟人化扮演特定角色或情境的图片,赋予其人类的表情、姿态和道具,用来传达幽默或情感共鸣的视觉效果...",
+    "duration_seconds": 4059.72,
+    "total_cost_usd": 6.8275,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_xhs": 0.8108,
+      "P1_Research_youtube": 0.2396,
+      "P1_Research_bili": 0.3147,
+      "P1_Research_x": 0.8323,
+      "P2_FilterBlueprint": 0.731,
+      "P2_ExtractCaps": 2.4116,
+      "P3_Assembler": 1.4704
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T02:29:50.368718"
+  },
+  {
+    "index": 6,
+    "requirement": "生成真实人物在户外或特定场景中的生活记录照片,画面自然真实,包含儿童在公园、农场等户外环境中玩耍的多角度抓拍效果,光线自然,氛围温馨...",
+    "duration_seconds": 3713.85,
+    "total_cost_usd": 4.6174,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_xhs": 0.835,
+      "P1_Research_bili": 0.5098,
+      "P1_Research_x": 0.0,
+      "P1_Research_zhihu": 1.2612,
+      "P2_FilterBlueprint": 0.4284,
+      "P2_ExtractCaps": 0.9958,
+      "P3_Assembler": 0.5701
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-68fbf5a8-c5ab-9b9a-8558-7c152f8b3588', 'request_id': '68fbf5a8-c5ab-9b9a-8558-7c152f8b3588'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-68fbf5a8-c5ab-9b9a-8558-7c152f8b3588', 'request_id': '68fbf5a8-c5ab-9b9a-8558-7c152f8b3588'}"
+    ],
+    "timestamp": "2026-04-19T03:00:39.339439"
+  },
+  {
+    "index": 5,
+    "requirement": "生成精致室内空间场景,画面中需呈现陶瓷器皿、绿植、家具等道具摆件,光线自然柔和,营造出温馨生活感或高颜值家居氛围...",
+    "duration_seconds": 2315.41,
+    "total_cost_usd": 3.2084,
+    "costs_breakdown": {
+      "P0_Router": 0.0174,
+      "P1_Research_xhs": 1.0795,
+      "P1_Research_youtube": 0.3627,
+      "P1_Research_bili": 0.712,
+      "P1_Research_x": 0.1744,
+      "P2_FilterBlueprint": 0.7182,
+      "P2_ExtractCaps": 0.0,
+      "P3_Assembler": 0.1441
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-8dbd8a63-033d-9878-8cb1-c5d55be600ff', 'request_id': '8dbd8a63-033d-9878-8cb1-c5d55be600ff'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-8dbd8a63-033d-9878-8cb1-c5d55be600ff', 'request_id': '8dbd8a63-033d-9878-8cb1-c5d55be600ff'}",
+      "P2_ExtractCaps Failed: 'NoneType' object is not iterable",
+      "P2_ExtractCaps crashed: TypeError: 'NoneType' object is not iterable",
+      "P2_ExtractCaps Recovery Failed: 'NoneType' object is not iterable",
+      "P2_ExtractCaps recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T03:03:03.595735"
+  },
+  {
+    "index": 7,
+    "requirement": "制作将真实人物照片合成到趣味场景中的创意图片,例如把人物缩小放入超市肉类托盘包装内、或与冰雕翅膀等道具结合形成视觉错位的幽默效果...",
+    "duration_seconds": 2118.05,
+    "total_cost_usd": 6.1553,
+    "costs_breakdown": {
+      "P0_Router": 0.0176,
+      "P1_Research_xhs": 0.8031,
+      "P1_Research_youtube": 0.7236,
+      "P1_Research_bili": 0.5374,
+      "P1_Research_x": 1.0227,
+      "P2_FilterBlueprint": 0.5094,
+      "P2_ExtractCaps": 2.1787,
+      "P3_Assembler": 0.3629
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T03:05:21.870507"
+  },
+  {
+    "index": 18,
+    "requirement": "将人物照片与中国传统吉祥符号(如双喜字、红玫瑰、金色祝福文字)融合,生成具有强烈喜庆氛围的定制化图案,人物面孔清晰嵌入红色喜庆背景中...",
+    "duration_seconds": 2605.83,
+    "total_cost_usd": 3.4125,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_bili": 0.727,
+      "P1_Research_x": 0.9416,
+      "P2_FilterBlueprint": 0.2513,
+      "P2_ExtractCaps": 1.0285,
+      "P3_Assembler": 0.4471
+    },
+    "errors": [
+      "P2_ExtractCaps Failed: ",
+      "P2_ExtractCaps crashed: ReadTimeout: "
+    ],
+    "timestamp": "2026-04-19T03:09:13.168191"
+  },
+  {
+    "index": 18,
+    "requirement": "将人物照片与中国传统吉祥符号(如双喜字、红玫瑰、金色祝福文字)融合,生成具有强烈喜庆氛围的定制化图案,人物面孔清晰嵌入红色喜庆背景中...",
+    "duration_seconds": 563.92,
+    "total_cost_usd": 0.5119,
+    "costs_breakdown": {
+      "P3_Assembler": 0.5119
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T03:18:49.042776"
+  },
+  {
+    "index": 12,
+    "requirement": "生成以暖黄/米棕色为背景底色的图文排版内容,整体画面呈现温暖、复古的暖色调氛围,适合健康养生、生活方式类主题...",
+    "duration_seconds": 986.16,
+    "total_cost_usd": 1.479,
+    "costs_breakdown": {
+      "P0_Router": 0.0173,
+      "P1_Research_zhihu": 0.5504,
+      "P1_Research_gzh": 0.3576,
+      "P2_FilterBlueprint": 0.1579,
+      "P2_ExtractCaps": 0.1783,
+      "P3_Assembler": 0.2174
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T03:22:01.216565"
+  },
+  {
+    "index": 6,
+    "requirement": "生成真实人物在户外或特定场景中的生活记录照片,画面自然真实,包含儿童在公园、农场等户外环境中玩耍的多角度抓拍效果,光线自然,氛围温馨...",
+    "duration_seconds": 1610.65,
+    "total_cost_usd": 0.8375,
+    "costs_breakdown": {
+      "P3_Assembler": 0.8375
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T03:27:42.532156"
+  },
+  {
+    "index": 10,
+    "requirement": "在图片上叠加标注元素,如用红色圆点、箭头或emoji符号指向图中特定位置,配合说明文字,实现在真实照片上直观标记关键信息的视觉效果...",
+    "duration_seconds": 1553.04,
+    "total_cost_usd": 2.3836,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.774,
+      "P1_Research_x": 0.2581,
+      "P2_FilterBlueprint": 0.169,
+      "P2_ExtractCaps": 0.406,
+      "P3_Assembler": 0.7594
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T03:29:09.784759"
+  },
+  {
+    "index": 9,
+    "requirement": "制作多格宫格式信息图,将同类内容(如多种食材搭配方案)拆分为统一风格的小卡片,每格包含标题、食材图片和文字说明,整体排列整齐、色块鲜明,适合一图展示多个并列条目...",
+    "duration_seconds": 3856.46,
+    "total_cost_usd": 5.8269,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.8345,
+      "P1_Research_bili": 0.3066,
+      "P1_Research_zhihu": 0.9861,
+      "P1_Research_gzh": 0.8971,
+      "P2_FilterBlueprint": 0.377,
+      "P2_ExtractCaps": 2.0179,
+      "P3_Assembler": 0.3907
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T03:30:37.381043"
+  },
+  {
+    "index": 23,
+    "requirement": "生成具有超现实风格的创意合成画面,将人物头部替换为宇宙星云、太极图、粒子爆炸等抽象元素,配合深蓝红色调背景,营造出哲学感或科幻感的视觉冲击...",
+    "duration_seconds": 808.07,
+    "total_cost_usd": 2.694,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_bili": 0.7006,
+      "P2_FilterBlueprint": 0.1453,
+      "P2_ExtractCaps": 1.6943,
+      "P3_Assembler": 0.1365
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T03:32:30.850316"
+  },
+  {
+    "index": 9,
+    "requirement": "制作多格宫格式信息图,将同类内容(如多种食材搭配方案)拆分为统一风格的小卡片,每格包含标题、食材图片和文字说明,整体排列整齐、色块鲜明,适合一图展示多个并列条目...",
+    "duration_seconds": 478.29,
+    "total_cost_usd": 0.6592,
+    "costs_breakdown": {
+      "P3_Assembler": 0.6592
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T03:38:47.501788"
+  },
+  {
+    "index": 28,
+    "requirement": "把猫咪图片与各类装扮道具(帽子、眼镜、服装、假发等)或其他卡通/玩具素材叠加合成,让不同来源的素材无缝融合成一张完整的搞笑图...",
+    "duration_seconds": 599.0,
+    "total_cost_usd": 2.0733,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.1561,
+      "P2_ExtractCaps": 1.5603,
+      "P3_Assembler": 0.3568
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T03:42:43.080772"
+  },
+  {
+    "index": 15,
+    "requirement": "生成手部持握或展示物品的特写画面,手势自然,物品清晰呈现,如用手托举饺子、手持卡片、手握手机等,突出手与物品的互动关系...",
+    "duration_seconds": 2040.43,
+    "total_cost_usd": 4.687,
+    "costs_breakdown": {
+      "P0_Router": 0.0169,
+      "P1_Research_xhs": 0.8883,
+      "P1_Research_youtube": 0.1732,
+      "P1_Research_bili": 0.6144,
+      "P1_Research_x": 0.0,
+      "P2_FilterBlueprint": 0.4239,
+      "P2_ExtractCaps": 2.4106,
+      "P3_Assembler": 0.1597
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-9df2080f-789c-996e-9d06-bbe0d8f789ac', 'request_id': '9df2080f-789c-996e-9d06-bbe0d8f789ac'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-9df2080f-789c-996e-9d06-bbe0d8f789ac', 'request_id': '9df2080f-789c-996e-9d06-bbe0d8f789ac'}"
+    ],
+    "timestamp": "2026-04-19T04:03:23.065706"
+  },
+  {
+    "index": 14,
+    "requirement": "生成整体色调偏粉紫、薄荷绿、浅蓝等低饱和度冷色系的插画或场景图,画面呈现出梦幻、静谧的冷色调氛围,颜色搭配柔和克制...",
+    "duration_seconds": 1472.07,
+    "total_cost_usd": 3.0737,
+    "costs_breakdown": {
+      "P0_Router": 0.0174,
+      "P1_Research_zhihu": 0.6995,
+      "P2_FilterBlueprint": 0.2034,
+      "P2_ExtractCaps": 1.9268,
+      "P3_Assembler": 0.2267
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:03:32.635670"
+  },
+  {
+    "index": 19,
+    "requirement": "制作统一模板风格的系列信息卡片,每张卡片包含固定的图标符号(如皇冠等级图标)、彩色标题文字和配图,整体视觉风格一致、可批量复用...",
+    "duration_seconds": 518.56,
+    "total_cost_usd": 1.2792,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.1133,
+      "P2_ExtractCaps": 0.8264,
+      "P3_Assembler": 0.3395
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:12:23.384694"
+  },
+  {
+    "index": 17,
+    "requirement": "生成创意性的嘴部动作特写,如用嘴唇衔住花朵茎部形成'嘴唇花'的视觉效果,将身体部位与物品结合产生趣味创意画面...",
+    "duration_seconds": 3238.27,
+    "total_cost_usd": 5.0154,
+    "costs_breakdown": {
+      "P0_Router": 0.0169,
+      "P1_Research_xhs": 1.038,
+      "P1_Research_bili": 0.3377,
+      "P1_Research_x": 0.2214,
+      "P1_Research_zhihu": 1.2092,
+      "P2_FilterBlueprint": 0.3512,
+      "P2_ExtractCaps": 1.2314,
+      "P3_Assembler": 0.6095
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-5e4fb299-85c2-9d9c-8947-a420e80bf5e7', 'request_id': '5e4fb299-85c2-9d9c-8947-a420e80bf5e7'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-5e4fb299-85c2-9d9c-8947-a420e80bf5e7', 'request_id': '5e4fb299-85c2-9d9c-8947-a420e80bf5e7'}"
+    ],
+    "timestamp": "2026-04-19T04:16:12.343765"
+  },
+  {
+    "index": 33,
+    "requirement": "生成真实户外场景中的人物活动照片,画面要呈现自然光线下的街道、公园、游乐场等具体地点环境,人物动作自然生动,背景环境细节丰富真实...",
+    "duration_seconds": 2097.02,
+    "total_cost_usd": 3.2904,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_x": 0.4827,
+      "P2_FilterBlueprint": 0.3608,
+      "P2_ExtractCaps": 2.0338,
+      "P3_Assembler": 0.396
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:17:54.298012"
+  },
+  {
+    "index": 24,
+    "requirement": "生成穿着完整冬季搭配的人物形象,展示黑色羽绒服、红色围巾、红色手套、宽腿裤等单品的组合穿搭效果,呈现从全身到局部细节的多角度造型展示...",
+    "duration_seconds": 432.03,
+    "total_cost_usd": 2.6481,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.1427,
+      "P2_ExtractCaps": 2.373,
+      "P3_Assembler": 0.1325
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:19:47.943698"
+  },
+  {
+    "index": 33,
+    "requirement": "生成真实户外场景中的人物活动照片,画面要呈现自然光线下的街道、公园、游乐场等具体地点环境,人物动作自然生动,背景环境细节丰富真实...",
+    "duration_seconds": 104.53,
+    "total_cost_usd": 0.3137,
+    "costs_breakdown": {
+      "P3_Assembler": 0.3137
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:19:50.293947"
+  },
+  {
+    "index": 17,
+    "requirement": "生成创意性的嘴部动作特写,如用嘴唇衔住花朵茎部形成'嘴唇花'的视觉效果,将身体部位与物品结合产生趣味创意画面...",
+    "duration_seconds": 310.54,
+    "total_cost_usd": 0.3211,
+    "costs_breakdown": {
+      "P3_Assembler": 0.3211
+    },
+    "errors": [
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T04:21:33.911278"
+  },
+  {
+    "index": 11,
+    "requirement": "制作图文混排的长图文内容,将大段文字与人物照片、数据表格、流程图等多种视觉元素组合排布在同一版面中,形成类似杂志或报告的专业排版风格...",
+    "duration_seconds": 3227.26,
+    "total_cost_usd": 5.0589,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_xhs": 0.7088,
+      "P1_Research_bili": 0.3241,
+      "P1_Research_zhihu": 0.7947,
+      "P1_Research_gzh": 0.0,
+      "P2_FilterBlueprint": 0.2198,
+      "P2_ExtractCaps": 2.7375,
+      "P3_Assembler": 0.2569
+    },
+    "errors": [
+      "P1_Research_gzh Failed: Error code: 400 - {'error': {'message': 'Exceeded limit on max data-uri per request: 250', 'type': 'invalid_request_error', 'param': None, 'code': None}, 'request_id': 'a6f752b9-c9fe-9854-b56f-036fac0c2481'}",
+      "P1_Research_gzh crashed: BadRequestError: Error code: 400 - {'error': {'message': 'Exceeded limit on max data-uri per request: 250', 'type': 'invalid_request_error', 'param': None, 'code': None}, 'request_id': 'a6f752b9-c9fe-9854-b56f-036fac0c2481'}",
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T04:21:43.238381"
+  },
+  {
+    "index": 24,
+    "requirement": "生成穿着完整冬季搭配的人物形象,展示黑色羽绒服、红色围巾、红色手套、宽腿裤等单品的组合穿搭效果,呈现从全身到局部细节的多角度造型展示...",
+    "duration_seconds": 251.44,
+    "total_cost_usd": 0.5842,
+    "costs_breakdown": {
+      "P3_Assembler": 0.5842
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:24:10.922509"
+  },
+  {
+    "index": 11,
+    "requirement": "制作图文混排的长图文内容,将大段文字与人物照片、数据表格、流程图等多种视觉元素组合排布在同一版面中,形成类似杂志或报告的专业排版风格...",
+    "duration_seconds": 308.42,
+    "total_cost_usd": 0.2457,
+    "costs_breakdown": {
+      "P3_Assembler": 0.2457
+    },
+    "errors": [
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T04:27:03.084353"
+  },
+  {
+    "index": 20,
+    "requirement": "生成黑色科技感背景的人物宣传海报,背景带有流光线条或霓虹光效,人物照片与品牌Logo、活动标识、二维码等视觉元素整齐排布,形成高辨识度的系列展示图...",
+    "duration_seconds": 1643.63,
+    "total_cost_usd": 2.9069,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.888,
+      "P1_Research_x": 0.5339,
+      "P2_FilterBlueprint": 0.5856,
+      "P2_ExtractCaps": 0.5322,
+      "P3_Assembler": 0.3502
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:30:59.684188"
+  },
+  {
+    "index": 38,
+    "requirement": "生成人物在真实日常场景(街头、公园、机场等)中被随手拍下的多张图片拼贴效果,画面构图不刻意、视角多变(含俯拍脚部、镜中自拍、远景抓拍等),整体呈现出碎片化的生活...",
+    "duration_seconds": 697.38,
+    "total_cost_usd": 1.45,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_x": 0.8963,
+      "P2_FilterBlueprint": 0.1847,
+      "P2_ExtractCaps": 0.231,
+      "P3_Assembler": 0.121
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:31:41.298014"
+  },
+  {
+    "index": 43,
+    "requirement": "在AI生成的卡通角色图片上叠加幽默吐槽文案,文字直接覆盖在图片上方,字体简洁白色,与画面情绪呼应,形成图文结合的表情包风格内容...",
+    "duration_seconds": 0.0,
+    "total_cost_usd": 0.0,
+    "costs_breakdown": {},
+    "errors": [],
+    "timestamp": "2026-04-19T04:31:54.619810"
+  },
+  {
+    "index": 48,
+    "requirement": "将整个帖子内容拆分为多个独立小格子并排列成网格或矩阵布局,每个格子承载一个独立的场景或信息单元,格子之间有明显的分隔边界,整体看起来像一张由多张小图拼合而成的大...",
+    "duration_seconds": 0.0,
+    "total_cost_usd": 0.0,
+    "costs_breakdown": {},
+    "errors": [],
+    "timestamp": "2026-04-19T04:32:06.993621"
+  },
+  {
+    "index": 20,
+    "requirement": "生成黑色科技感背景的人物宣传海报,背景带有流光线条或霓虹光效,人物照片与品牌Logo、活动标识、二维码等视觉元素整齐排布,形成高辨识度的系列展示图...",
+    "duration_seconds": 318.06,
+    "total_cost_usd": 0.2866,
+    "costs_breakdown": {
+      "P3_Assembler": 0.2866
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:36:29.880891"
+  },
+  {
+    "index": 22,
+    "requirement": "生成将普通服装或日常物品以夸张搞怪方式穿戴的人物画面,比如把超大号短裤当长袍穿、用篮球短裤模仿古希腊长袍,产生强烈的视觉反差和幽默效果...",
+    "duration_seconds": 1027.96,
+    "total_cost_usd": 2.1466,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.4388,
+      "P2_ExtractCaps": 1.7078,
+      "P3_Assembler": 0.0
+    },
+    "errors": [
+      "P3_Assembler Failed: 'NoneType' object is not iterable",
+      "P3_Assembler crashed: TypeError: 'NoneType' object is not iterable",
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T04:38:54.642880"
+  },
+  {
+    "index": 22,
+    "requirement": "生成将普通服装或日常物品以夸张搞怪方式穿戴的人物画面,比如把超大号短裤当长袍穿、用篮球短裤模仿古希腊长袍,产生强烈的视觉反差和幽默效果...",
+    "duration_seconds": 194.52,
+    "total_cost_usd": 0.3236,
+    "costs_breakdown": {
+      "P3_Assembler": 0.3236
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:42:20.401958"
+  },
+  {
+    "index": 16,
+    "requirement": "生成多人或多只动物同框的协同姿态画面,如两只猫并排躺着穿睡衣、两人一起摆造型抱花束,呈现出同步、对称或互动的视觉效果...",
+    "duration_seconds": 1284.22,
+    "total_cost_usd": 2.0642,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_x": 0.0862,
+      "P2_FilterBlueprint": 0.1597,
+      "P2_ExtractCaps": 1.2544,
+      "P3_Assembler": 0.5467
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input text data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-83bd5d7d-de2d-9e5d-a257-b2777c610f5f', 'request_id': '83bd5d7d-de2d-9e5d-a257-b2777c610f5f'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input text data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-83bd5d7d-de2d-9e5d-a257-b2777c610f5f', 'request_id': '83bd5d7d-de2d-9e5d-a257-b2777c610f5f'}"
+    ],
+    "timestamp": "2026-04-19T04:48:41.130950"
+  },
+  {
+    "index": 29,
+    "requirement": "在同一张图中将多只猫咪或同一只猫咪的不同姿态照片拼接组合,配合文字标注形成对话或对比效果的多格拼图...",
+    "duration_seconds": 1550.52,
+    "total_cost_usd": 3.816,
+    "costs_breakdown": {
+      "P0_Router": 0.0174,
+      "P1_Research_zhihu": 0.7798,
+      "P2_FilterBlueprint": 0.1895,
+      "P2_ExtractCaps": 2.2099,
+      "P3_Assembler": 0.6194
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:50:13.493693"
+  },
+  {
+    "index": 27,
+    "requirement": "将猫咪表情包图片与各种场景素材(办公室、食物、产品、背景环境等)合成拼贴在一起,让猫咪看起来自然地处于这些场景中,形成多格并排的拼贴版式...",
+    "duration_seconds": 913.44,
+    "total_cost_usd": 2.8722,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_bili": 0.6943,
+      "P2_FilterBlueprint": 0.3362,
+      "P2_ExtractCaps": 1.2199,
+      "P3_Assembler": 0.6048
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T04:57:46.838618"
+  },
+  {
+    "index": 25,
+    "requirement": "生成宠物穿着服装的可爱造型图,展示猫咪穿上印花连体衣的整体穿着效果,需要清晰呈现服装的图案、版型与宠物身体的贴合细节...",
+    "duration_seconds": 1567.78,
+    "total_cost_usd": 2.9595,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_youtube": 0.6952,
+      "P1_Research_x": 0.5415,
+      "P2_FilterBlueprint": 0.2292,
+      "P2_ExtractCaps": 1.2491,
+      "P3_Assembler": 0.2274
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:02:50.794899"
+  },
+  {
+    "index": 16,
+    "requirement": "生成多人或多只动物同框的协同姿态画面,如两只猫并排躺着穿睡衣、两人一起摆造型抱花束,呈现出同步、对称或互动的视觉效果...",
+    "duration_seconds": 936.71,
+    "total_cost_usd": 0.6002,
+    "costs_breakdown": {
+      "P3_Assembler": 0.6002
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:04:29.369006"
+  },
+  {
+    "index": 53,
+    "requirement": "生成包含多个独立小格子的图文排版画面,每个格子内有图片和文字说明,格子之间疏密有致、整齐排列,整体呈现信息图表或内容合集的视觉效果...",
+    "duration_seconds": 2251.42,
+    "total_cost_usd": 4.7164,
+    "costs_breakdown": {
+      "P0_Router": 0.0173,
+      "P1_Research_xhs": 0.7932,
+      "P1_Research_youtube": 0.2584,
+      "P1_Research_bili": 0.326,
+      "P1_Research_zhihu": 1.1083,
+      "P2_FilterBlueprint": 0.3965,
+      "P2_ExtractCaps": 1.4449,
+      "P3_Assembler": 0.3719
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:09:50.059238"
+  },
+  {
+    "index": 34,
+    "requirement": "生成多人聚集的活动现场图,如会议、展览、户外聚会等场景,画面中需要呈现多个人物同框、有组织的群体互动氛围,背景有明显的活动标识或场地特征...",
+    "duration_seconds": 1313.02,
+    "total_cost_usd": 2.8495,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_x": 0.0,
+      "P2_FilterBlueprint": 0.2756,
+      "P2_ExtractCaps": 2.104,
+      "P3_Assembler": 0.4527
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-9f3536d9-b53e-98da-8df5-bcfee016706b', 'request_id': '9f3536d9-b53e-98da-8df5-bcfee016706b'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-9f3536d9-b53e-98da-8df5-bcfee016706b', 'request_id': '9f3536d9-b53e-98da-8df5-bcfee016706b'}",
+      "P1_Research_x Recovery Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-98765e2b-4c4f-9555-8e46-d983e295d0e3', 'request_id': '98765e2b-4c4f-9555-8e46-d983e295d0e3'}",
+      "P1_Research_x recovery crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-98765e2b-4c4f-9555-8e46-d983e295d0e3', 'request_id': '98765e2b-4c4f-9555-8e46-d983e295d0e3'}",
+      "Missing case file for x! Agent likely hit max_iterations without saving."
+    ],
+    "timestamp": "2026-04-19T05:12:19.869814"
+  },
+  {
+    "index": 32,
+    "requirement": "在真实物体照片上叠加手绘风格的简笔画元素,例如在猕猴桃切片上添加卡通五官和小触角,让照片呈现出实物与手绘结合的趣味效果...",
+    "duration_seconds": 1295.43,
+    "total_cost_usd": 2.9059,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_x": 0.3752,
+      "P2_FilterBlueprint": 0.7528,
+      "P2_ExtractCaps": 1.4461,
+      "P3_Assembler": 0.3148
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: The provided URL does not appear to be valid. Ensure it is correctly formatted.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-ddf6c0a6-94c9-909f-8524-86305b8bc730', 'request_id': 'ddf6c0a6-94c9-909f-8524-86305b8bc730'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: The provided URL does not appear to be valid. Ensure it is correctly formatted.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-ddf6c0a6-94c9-909f-8524-86305b8bc730', 'request_id': 'ddf6c0a6-94c9-909f-8524-86305b8bc730'}"
+    ],
+    "timestamp": "2026-04-19T05:19:34.232039"
+  },
+  {
+    "index": 30,
+    "requirement": "将真实照片中的人物与卡通/奇幻元素合成,例如给人物添加蟑螂的触角和腿,使人物看起来像变成了一只蟑螂,整体画面自然融合不突兀...",
+    "duration_seconds": 1106.64,
+    "total_cost_usd": 2.6414,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_x": 0.8486,
+      "P2_FilterBlueprint": 0.287,
+      "P2_ExtractCaps": 1.2584,
+      "P3_Assembler": 0.2303
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:21:29.562148"
+  },
+  {
+    "index": 21,
+    "requirement": "生成宠物或动物穿戴人类服饰配件(如帽子、围巾)的画面,让动物看起来像在过节或扮演某种角色,整体效果可爱又有趣...",
+    "duration_seconds": 1132.28,
+    "total_cost_usd": 4.2045,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_bili": 0.5282,
+      "P1_Research_x": 0.999,
+      "P2_FilterBlueprint": 0.3604,
+      "P2_ExtractCaps": 2.0918,
+      "P3_Assembler": 0.2081
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:23:34.509500"
+  },
+  {
+    "index": 21,
+    "requirement": "生成宠物或动物穿戴人类服饰配件(如帽子、围巾)的画面,让动物看起来像在过节或扮演某种角色,整体效果可爱又有趣...",
+    "duration_seconds": 151.86,
+    "total_cost_usd": 0.4432,
+    "costs_breakdown": {
+      "P2_ExtractCaps": 0.2308,
+      "P3_Assembler": 0.2123
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:26:17.318747"
+  },
+  {
+    "index": 35,
+    "requirement": "生成真实物品的特写或陈列展示图,物品摆放清晰、细节可辨,适合用于产品展示或场景道具呈现,画面构图干净突出主体...",
+    "duration_seconds": 510.18,
+    "total_cost_usd": 1.1193,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_x": 0.1875,
+      "P2_FilterBlueprint": 0.2365,
+      "P2_ExtractCaps": 0.1762,
+      "P3_Assembler": 0.5019
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:30:12.212408"
+  },
+  {
+    "index": 58,
+    "requirement": "生成采用夸张变形构图的图片,例如鱼眼镜头效果将人物或场景扭曲成球形全景、仰拍使近处物体极度放大而远处极度缩小,或通过搞怪角度让画面产生强烈的视觉冲击感...",
+    "duration_seconds": 1422.27,
+    "total_cost_usd": 4.4549,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.3235,
+      "P1_Research_youtube": 0.1982,
+      "P1_Research_bili": 0.7712,
+      "P1_Research_x": 0.5703,
+      "P2_FilterBlueprint": 0.4342,
+      "P2_ExtractCaps": 1.6842,
+      "P3_Assembler": 0.4562
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:33:46.166003"
+  },
+  {
+    "index": 26,
+    "requirement": "生成创意合成图,将人物穿搭形象嵌入特定场景容器中(如超市生鲜托盘),使人物服装与场景产生趣味性视觉对比,同时保留服装细节的清晰可见...",
+    "duration_seconds": 481.34,
+    "total_cost_usd": 1.9412,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_bili": 0.713,
+      "P1_Research_x": 0.3092,
+      "P2_FilterBlueprint": 0.3495,
+      "P2_ExtractCaps": 0.2863,
+      "P3_Assembler": 0.2663
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:34:31.238392"
+  },
+  {
+    "index": 58,
+    "requirement": "生成采用夸张变形构图的图片,例如鱼眼镜头效果将人物或场景扭曲成球形全景、仰拍使近处物体极度放大而远处极度缩小,或通过搞怪角度让画面产生强烈的视觉冲击感...",
+    "duration_seconds": 35.53,
+    "total_cost_usd": 0.1058,
+    "costs_breakdown": {
+      "P3_Assembler": 0.1058
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:34:33.254577"
+  },
+  {
+    "index": 39,
+    "requirement": "制作图文卡片时,需要让插图与文字在语义上高度呼应——比如用可爱小驴的不同表情和动作配合对应的幽默文字,每张小卡片中图在上、文字在下,插图内容直接反映文字含义,形...",
+    "duration_seconds": 1327.38,
+    "total_cost_usd": 2.5336,
+    "costs_breakdown": {
+      "P0_Router": 0.0175,
+      "P1_Research_zhihu": 0.3525,
+      "P2_FilterBlueprint": 0.2654,
+      "P2_ExtractCaps": 1.5079,
+      "P3_Assembler": 0.3903
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:34:40.094269"
+  },
+  {
+    "index": 37,
+    "requirement": "生成动物(如马)在运动瞬间被捕捉的高动态画面,鬃毛飞扬、肢体伸展,呈现出强烈的瞬间张力和动感,背景简洁以突出主体动态...",
+    "duration_seconds": 991.51,
+    "total_cost_usd": 3.5349,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_x": 0.7252,
+      "P2_FilterBlueprint": 0.7766,
+      "P2_ExtractCaps": 1.7271,
+      "P3_Assembler": 0.2887
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:36:20.576128"
+  },
+  {
+    "index": 39,
+    "requirement": "制作图文卡片时,需要让插图与文字在语义上高度呼应——比如用可爱小驴的不同表情和动作配合对应的幽默文字,每张小卡片中图在上、文字在下,插图内容直接反映文字含义,形...",
+    "duration_seconds": 167.41,
+    "total_cost_usd": 0.2481,
+    "costs_breakdown": {
+      "P3_Assembler": 0.2481
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:37:39.213041"
+  },
+  {
+    "index": 44,
+    "requirement": "制作多宫格拼图帖子,每格图片配有对应的标题文字或字幕说明,文字风格统一,整体排列整齐,适合用于周记、日历、流程说明等系列内容展示...",
+    "duration_seconds": 107.97,
+    "total_cost_usd": 0.2016,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.2016
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:39:40.781513"
+  },
+  {
+    "index": 49,
+    "requirement": "在同一张图中混合使用多种内容载体形式,例如将真实照片、插画角色、产品图、文字说明、图表等不同类型的视觉元素组合排布在同一个版面内,形成图文混排的丰富视觉层次...",
+    "duration_seconds": 0.0,
+    "total_cost_usd": 0.0,
+    "costs_breakdown": {},
+    "errors": [],
+    "timestamp": "2026-04-19T05:39:53.767030"
+  },
+  {
+    "index": 26,
+    "requirement": "生成创意合成图,将人物穿搭形象嵌入特定场景容器中(如超市生鲜托盘),使人物服装与场景产生趣味性视觉对比,同时保留服装细节的清晰可见...",
+    "duration_seconds": 328.83,
+    "total_cost_usd": 0.9182,
+    "costs_breakdown": {
+      "P2_ExtractCaps": 0.2825,
+      "P3_Assembler": 0.6357
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:40:11.843646"
+  },
+  {
+    "index": 37,
+    "requirement": "生成动物(如马)在运动瞬间被捕捉的高动态画面,鬃毛飞扬、肢体伸展,呈现出强烈的瞬间张力和动感,背景简洁以突出主体动态...",
+    "duration_seconds": 571.96,
+    "total_cost_usd": 0.8176,
+    "costs_breakdown": {
+      "P2_ExtractCaps": 0.4394,
+      "P3_Assembler": 0.3782
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:46:04.093968"
+  },
+  {
+    "index": 63,
+    "requirement": "生成具有强烈氛围感的插画风场景图,整体画面以深蓝色调为主,室内外场景都笼罩在宁静的夜色中,窗户透出暖黄色灯光形成冷暖对比,画面质感接近油画或数字绘画风格,传达出...",
+    "duration_seconds": 1325.53,
+    "total_cost_usd": 5.0932,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.6077,
+      "P1_Research_youtube": 0.2133,
+      "P1_Research_bili": 0.5845,
+      "P1_Research_x": 0.4592,
+      "P2_FilterBlueprint": 1.1044,
+      "P2_ExtractCaps": 0.2247,
+      "P3_Assembler": 1.8824
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T05:56:52.530485"
+  },
+  {
+    "index": 40,
+    "requirement": "制作多图拼贴帖子时,需要将多张照片或截图按照叙事顺序排列在一张大图中,并在关键图片上叠加说明性文字标注,让图片和文字共同讲述一个完整故事,文字起到补充说明和情感...",
+    "duration_seconds": 1622.7,
+    "total_cost_usd": 3.1678,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_xhs": 0.3165,
+      "P1_Research_youtube": 0.3448,
+      "P1_Research_bili": 0.5933,
+      "P1_Research_zhihu": 0.0,
+      "P2_FilterBlueprint": 0.5512,
+      "P2_ExtractCaps": 0.6172,
+      "P3_Assembler": 0.7276
+    },
+    "errors": [
+      "P1_Research_zhihu Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-1d87c763-89f8-9daf-b748-c37fbb2a25eb', 'request_id': '1d87c763-89f8-9daf-b748-c37fbb2a25eb'}",
+      "P1_Research_zhihu crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-1d87c763-89f8-9daf-b748-c37fbb2a25eb', 'request_id': '1d87c763-89f8-9daf-b748-c37fbb2a25eb'}"
+    ],
+    "timestamp": "2026-04-19T05:57:28.212217"
+  },
+  {
+    "index": 31,
+    "requirement": "给普通猫咪照片套上不同职业的服装和场景(如医生、上班族、老板等),并保持猫咪面部表情清晰可辨,制作出系列表情包拼贴图...",
+    "duration_seconds": 1287.98,
+    "total_cost_usd": 3.1415,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_x": 1.0722,
+      "P2_FilterBlueprint": 0.4812,
+      "P2_ExtractCaps": 1.279,
+      "P3_Assembler": 0.2918
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:01:52.819772"
+  },
+  {
+    "index": 42,
+    "requirement": "在图片上叠加标题文字,文字大小、粗细、颜色各异,形成层次感强的排版效果——例如大标题用粗体醒目字体,副标题用细体小字,整体风格统一(如深色系商务风或简约设计风)...",
+    "duration_seconds": 1102.52,
+    "total_cost_usd": 0.7298,
+    "costs_breakdown": {
+      "P0_Router": 0.0176,
+      "P1_Research_zhihu": 0.7123
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:04:40.661904"
+  },
+  {
+    "index": 47,
+    "requirement": "制作流程图/架构示意图:用箭头、方框、层级结构或立体堆叠图形展示系统架构、业务流程或概念层级关系,配合文字标注说明各模块功能,视觉上清晰呈现逻辑关系...",
+    "duration_seconds": 0.0,
+    "total_cost_usd": 0.0,
+    "costs_breakdown": {},
+    "errors": [],
+    "timestamp": "2026-04-19T06:04:53.530837"
+  },
+  {
+    "index": 40,
+    "requirement": "制作多图拼贴帖子时,需要将多张照片或截图按照叙事顺序排列在一张大图中,并在关键图片上叠加说明性文字标注,让图片和文字共同讲述一个完整故事,文字起到补充说明和情感...",
+    "duration_seconds": 615.01,
+    "total_cost_usd": 0.2986,
+    "costs_breakdown": {
+      "P3_Assembler": 0.2986
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:07:56.091515"
+  },
+  {
+    "index": 45,
+    "requirement": "制作图文卡片式科普内容:每张卡片包含统一的标题样式、编号序列、配套插图(卡通/示意图风格)和说明文字,多张卡片拼成一组,整体风格统一、排版清晰,适合健康养生、步...",
+    "duration_seconds": 0.0,
+    "total_cost_usd": 0.0,
+    "costs_breakdown": {},
+    "errors": [],
+    "timestamp": "2026-04-19T06:08:09.664655"
+  },
+  {
+    "index": 31,
+    "requirement": "给普通猫咪照片套上不同职业的服装和场景(如医生、上班族、老板等),并保持猫咪面部表情清晰可辨,制作出系列表情包拼贴图...",
+    "duration_seconds": 479.23,
+    "total_cost_usd": 0.4345,
+    "costs_breakdown": {
+      "P3_Assembler": 0.4345
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:10:04.612918"
+  },
+  {
+    "index": 50,
+    "requirement": "以统一的视觉主角(如同一个卡通角色、同一个人物、同一主题场景)贯穿多个分格画面,每个格子呈现该主角在不同场景或状态下的样子,配合文字说明形成系列感强的多格叙事版...",
+    "duration_seconds": 106.53,
+    "total_cost_usd": 0.406,
+    "costs_breakdown": {
+      "P3_Assembler": 0.406
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:10:09.134286"
+  },
+  {
+    "index": 50,
+    "requirement": "以统一的视觉主角(如同一个卡通角色、同一个人物、同一主题场景)贯穿多个分格画面,每个格子呈现该主角在不同场景或状态下的样子,配合文字说明形成系列感强的多格叙事版...",
+    "duration_seconds": 19.28,
+    "total_cost_usd": 0.0849,
+    "costs_breakdown": {
+      "P3_Assembler": 0.0849
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:10:39.938532"
+  },
+  {
+    "index": 54,
+    "requirement": "生成人物局部特写画面,如放大呈现嘴巴咬食物、手持物品、耳朵佩戴饰品、鼻子、指甲等身体局部细节,画面填充感强,细节清晰可见...",
+    "duration_seconds": 1934.57,
+    "total_cost_usd": 4.0253,
+    "costs_breakdown": {
+      "P0_Router": 0.0173,
+      "P1_Research_xhs": 1.2083,
+      "P1_Research_bili": 0.7365,
+      "P1_Research_x": 0.4518,
+      "P1_Research_zhihu": 0.0,
+      "P2_FilterBlueprint": 0.3274,
+      "P2_ExtractCaps": 1.0037,
+      "P3_Assembler": 0.2803
+    },
+    "errors": [
+      "P1_Research_zhihu Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-e2080c10-488a-9621-ae35-b8977f25addf', 'request_id': 'e2080c10-488a-9621-ae35-b8977f25addf'}",
+      "P1_Research_zhihu crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-e2080c10-488a-9621-ae35-b8977f25addf', 'request_id': 'e2080c10-488a-9621-ae35-b8977f25addf'}",
+      "P1_Research_zhihu Recovery Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-0147d251-92ba-91d7-b43a-c3cbce65f754', 'request_id': '0147d251-92ba-91d7-b43a-c3cbce65f754'}",
+      "P1_Research_zhihu recovery crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-0147d251-92ba-91d7-b43a-c3cbce65f754', 'request_id': '0147d251-92ba-91d7-b43a-c3cbce65f754'}",
+      "Missing case file for zhihu! Agent likely hit max_iterations without saving."
+    ],
+    "timestamp": "2026-04-19T06:12:20.515471"
+  },
+  {
+    "index": 68,
+    "requirement": "制作融合插画风格的信息图文海报:以卡通机器人/科技感插图作为视觉主体,搭配醒目的彩色标题文字和数据图表,整体呈现出活泼又专业的视觉效果...",
+    "duration_seconds": 1141.07,
+    "total_cost_usd": 3.8803,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_xhs": 0.3699,
+      "P1_Research_youtube": 0.263,
+      "P1_Research_bili": 0.2149,
+      "P1_Research_x": 0.5619,
+      "P2_FilterBlueprint": 0.3985,
+      "P2_ExtractCaps": 1.6726,
+      "P3_Assembler": 0.3823
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:16:06.136512"
+  },
+  {
+    "index": 36,
+    "requirement": "生成同一人物在同一场景中多角度、多姿态的即时抓拍效果图,画面呈现自然随意的动态感,如行走、转身、低头、仰望等非摆拍状态,整体风格真实生活化...",
+    "duration_seconds": 759.08,
+    "total_cost_usd": 2.2051,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_x": 0.3544,
+      "P2_FilterBlueprint": 0.4143,
+      "P2_ExtractCaps": 0.2322,
+      "P3_Assembler": 1.187
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:22:57.131133"
+  },
+  {
+    "index": 54,
+    "requirement": "生成人物局部特写画面,如放大呈现嘴巴咬食物、手持物品、耳朵佩戴饰品、鼻子、指甲等身体局部细节,画面填充感强,细节清晰可见...",
+    "duration_seconds": 1147.23,
+    "total_cost_usd": 1.094,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_youtube": 0.3253,
+      "P3_Assembler": 0.7515
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:31:40.016165"
+  },
+  {
+    "index": 52,
+    "requirement": "在同一画面中合理安排主体与背景的空间关系,让主体(人物、动物、物品)在画面中有明确的视觉焦点,背景简洁或有层次地衬托主体...",
+    "duration_seconds": 1674.19,
+    "total_cost_usd": 3.2832,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_xhs": 0.7904,
+      "P1_Research_youtube": 0.1383,
+      "P1_Research_bili": 0.3494,
+      "P1_Research_zhihu": 0.565,
+      "P2_FilterBlueprint": 0.4357,
+      "P2_ExtractCaps": 0.4967,
+      "P3_Assembler": 0.4907
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:33:00.154687"
+  },
+  {
+    "index": 52,
+    "requirement": "在同一画面中合理安排主体与背景的空间关系,让主体(人物、动物、物品)在画面中有明确的视觉焦点,背景简洁或有层次地衬托主体...",
+    "duration_seconds": 394.11,
+    "total_cost_usd": 1.6365,
+    "costs_breakdown": {
+      "P2_ExtractCaps": 0.3687,
+      "P3_Assembler": 1.2677
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:39:46.065388"
+  },
+  {
+    "index": 55,
+    "requirement": "生成人物近景半身或胸部以上的画面,突出人物面部表情和情绪,背景适当虚化,让观看者能清楚看到人物的神态与互动感...",
+    "duration_seconds": 2032.67,
+    "total_cost_usd": 9.1617,
+    "costs_breakdown": {
+      "P0_Router": 0.0173,
+      "P1_Research_xhs": 0.7317,
+      "P1_Research_youtube": 0.2559,
+      "P1_Research_bili": 0.7684,
+      "P1_Research_x": 0.3264,
+      "P2_FilterBlueprint": 0.4732,
+      "P2_ExtractCaps": 1.8689,
+      "P3_Assembler": 4.7199
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:44:45.594704"
+  },
+  {
+    "index": 73,
+    "requirement": "制作图文排版信息图,将多张食材产品图片抠出后整齐排列在统一背景上,配合文字说明组合成内容丰富的科普海报...",
+    "duration_seconds": 1939.07,
+    "total_cost_usd": 5.5328,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_xhs": 0.8615,
+      "P1_Research_youtube": 0.2154,
+      "P1_Research_bili": 0.739,
+      "P1_Research_zhihu": 0.8595,
+      "P2_FilterBlueprint": 0.7335,
+      "P2_ExtractCaps": 1.9593,
+      "P3_Assembler": 0.1473
+    },
+    "errors": [
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T06:48:39.173435"
+  },
+  {
+    "index": 41,
+    "requirement": "制作信息图文海报时,需要将大标题、分类小标题与正文段落按照清晰的层级排布在版面上,标题用大字醒目展示,正文紧跟其下,整体版面分区明确、图文对应,让读者能快速扫读...",
+    "duration_seconds": 1642.69,
+    "total_cost_usd": 2.3365,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_zhihu": 0.8318,
+      "P2_FilterBlueprint": 0.3261,
+      "P2_ExtractCaps": 0.5704,
+      "P3_Assembler": 0.591
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:50:33.359058"
+  },
+  {
+    "index": 73,
+    "requirement": "制作图文排版信息图,将多张食材产品图片抠出后整齐排列在统一背景上,配合文字说明组合成内容丰富的科普海报...",
+    "duration_seconds": 160.03,
+    "total_cost_usd": 0.2187,
+    "costs_breakdown": {
+      "P3_Assembler": 0.2187
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:51:31.059262"
+  },
+  {
+    "index": 46,
+    "requirement": "制作数据报告类图文内容:包含柱状图、饼图、折线图、词云图、环形图等多种数据可视化图表,配合标题、要点文字说明,整体呈现专业研究报告的视觉风格,色彩搭配统一(如蓝...",
+    "duration_seconds": 154.09,
+    "total_cost_usd": 0.2693,
+    "costs_breakdown": {
+      "P3_Assembler": 0.2693
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T06:53:20.906848"
+  },
+  {
+    "index": 46,
+    "requirement": "制作数据报告类图文内容:包含柱状图、饼图、折线图、词云图、环形图等多种数据可视化图表,配合标题、要点文字说明,整体呈现专业研究报告的视觉风格,色彩搭配统一(如蓝...",
+    "duration_seconds": 360.17,
+    "total_cost_usd": 0.0536,
+    "costs_breakdown": {
+      "P3_Assembler": 0.0536
+    },
+    "errors": [
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T06:59:32.634213"
+  },
+  {
+    "index": 59,
+    "requirement": "生成画面中存在嵌套框架效果的图片,如在沙漠场景中用一个悬空的矩形框将主体框住形成画中画,或利用水面倒影与实景上下对称形成嵌套镜像构图,制造超现实的空间突破感...",
+    "duration_seconds": 1695.31,
+    "total_cost_usd": 4.3135,
+    "costs_breakdown": {
+      "P0_Router": 0.017,
+      "P1_Research_xhs": 0.5127,
+      "P1_Research_youtube": 0.5328,
+      "P1_Research_bili": 0.4272,
+      "P1_Research_x": 0.6753,
+      "P2_FilterBlueprint": 0.4085,
+      "P2_ExtractCaps": 1.5934,
+      "P3_Assembler": 0.1466
+    },
+    "errors": [
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T07:00:09.081010"
+  },
+  {
+    "index": 59,
+    "requirement": "生成画面中存在嵌套框架效果的图片,如在沙漠场景中用一个悬空的矩形框将主体框住形成画中画,或利用水面倒影与实景上下对称形成嵌套镜像构图,制造超现实的空间突破感...",
+    "duration_seconds": 487.76,
+    "total_cost_usd": 0.2881,
+    "costs_breakdown": {
+      "P3_Assembler": 0.2881
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T07:08:28.955980"
+  },
+  {
+    "index": 57,
+    "requirement": "生成具有强烈透视纵深感的室内空间图,画面中窗框、拱门、地板线条等建筑元素形成明显的空间层次,光线从远处窗口射入,营造出由近到远的视觉延伸效果...",
+    "duration_seconds": 1904.23,
+    "total_cost_usd": 5.5752,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.4745,
+      "P1_Research_youtube": 2.0868,
+      "P1_Research_bili": 0.8689,
+      "P1_Research_x": 0.4471,
+      "P2_FilterBlueprint": 0.7495,
+      "P2_ExtractCaps": 0.4569,
+      "P3_Assembler": 0.4743
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T07:11:44.524906"
+  },
+  {
+    "index": 60,
+    "requirement": "生成具有强烈色彩对比的艺术插画,整体画面以高饱和度的红色与蓝色为主色调,两种颜色形成鲜明的冷暖对撞,背景大面积纯色铺底,视觉冲击力极强...",
+    "duration_seconds": 2247.83,
+    "total_cost_usd": 4.5816,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.6497,
+      "P1_Research_bili": 0.6795,
+      "P1_Research_x": 0.4531,
+      "P1_Research_zhihu": 0.7582,
+      "P2_FilterBlueprint": 0.6537,
+      "P2_ExtractCaps": 1.3702,
+      "P3_Assembler": 0.0
+    },
+    "errors": [
+      "P3_Assembler Failed: 'NoneType' object is not iterable",
+      "P3_Assembler crashed: TypeError: 'NoneType' object is not iterable",
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T07:22:26.531029"
+  },
+  {
+    "index": 51,
+    "requirement": "将多张图片按网格或分区方式拼贴成一张图,每个区域展示不同角度或不同场景,整体画面有清晰的分割感和节奏感...",
+    "duration_seconds": 1479.58,
+    "total_cost_usd": 4.5554,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.8122,
+      "P1_Research_youtube": 0.1405,
+      "P1_Research_bili": 0.7167,
+      "P1_Research_x": 0.4004,
+      "P2_FilterBlueprint": 0.4764,
+      "P2_ExtractCaps": 1.7648,
+      "P3_Assembler": 0.2274
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T07:24:25.673942"
+  },
+  {
+    "index": 51,
+    "requirement": "将多张图片按网格或分区方式拼贴成一张图,每个区域展示不同角度或不同场景,整体画面有清晰的分割感和节奏感...",
+    "duration_seconds": 402.82,
+    "total_cost_usd": 0.1697,
+    "costs_breakdown": {
+      "P3_Assembler": 0.1697
+    },
+    "errors": [
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T07:31:20.223824"
+  },
+  {
+    "index": 60,
+    "requirement": "生成具有强烈色彩对比的艺术插画,整体画面以高饱和度的红色与蓝色为主色调,两种颜色形成鲜明的冷暖对撞,背景大面积纯色铺底,视觉冲击力极强...",
+    "duration_seconds": 609.03,
+    "total_cost_usd": 0.4128,
+    "costs_breakdown": {
+      "P3_Assembler": 0.4128
+    },
+    "errors": [
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T07:32:47.613076"
+  },
+  {
+    "index": 78,
+    "requirement": "制作图文并茂的科普说明卡片,每张卡片包含标题、编号、插图和详细文字说明,整体排版整齐统一,适合分步骤展示教程或知识点...",
+    "duration_seconds": 2819.75,
+    "total_cost_usd": 5.7488,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.6176,
+      "P1_Research_bili": 0.7238,
+      "P1_Research_zhihu": 0.3831,
+      "P1_Research_gzh": 1.6592,
+      "P2_FilterBlueprint": 0.3671,
+      "P2_ExtractCaps": 1.6545,
+      "P3_Assembler": 0.3265
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T07:38:43.372858"
+  },
+  {
+    "index": 62,
+    "requirement": "生成整体色调统一、饱和度偏高的场景图,例如全画面笼罩在深蓝色夜光氛围或浓郁的赤红土地色调中,让单一主色调主导整个画面,营造出沉浸式的强烈色彩氛围感...",
+    "duration_seconds": 1759.89,
+    "total_cost_usd": 3.6714,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_xhs": 0.927,
+      "P1_Research_youtube": 0.3332,
+      "P1_Research_bili": 0.4903,
+      "P1_Research_x": 0.4016,
+      "P2_FilterBlueprint": 0.3065,
+      "P2_ExtractCaps": 0.8345,
+      "P3_Assembler": 0.361
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T07:41:18.306580"
+  },
+  {
+    "index": 64,
+    "requirement": "制作色彩鲜艳、视觉冲击力强的宣传海报,背景使用渐变色块(蓝紫、橙红等高饱和度色彩),搭配几何抽象图形装饰,文字排版醒目大气,整体呈现出热烈、充满活力的欢庆氛围...",
+    "duration_seconds": 2722.25,
+    "total_cost_usd": 5.072,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.7144,
+      "P1_Research_youtube": 0.4965,
+      "P1_Research_bili": 0.6022,
+      "P1_Research_zhihu": 1.0205,
+      "P2_FilterBlueprint": 0.5733,
+      "P2_ExtractCaps": 0.6831,
+      "P3_Assembler": 0.9648
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T07:54:04.000716"
+  },
+  {
+    "index": 83,
+    "requirement": "生成能展示宽广空间感的室内或室外全景图,画面中包含完整的环境纵深,让观看者感受到场景的整体规模和空间层次...",
+    "duration_seconds": 1173.42,
+    "total_cost_usd": 2.9158,
+    "costs_breakdown": {
+      "P0_Router": 0.0431,
+      "P1_Research_xhs": 0.2565,
+      "P1_Research_youtube": 0.3419,
+      "P1_Research_bili": 0.3772,
+      "P1_Research_x": 0.1397,
+      "P2_FilterBlueprint": 0.3032,
+      "P2_ExtractCaps": 1.1127,
+      "P3_Assembler": 0.3415
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-bd9c4280-f4ed-9863-b289-217584b5bf8c', 'request_id': 'bd9c4280-f4ed-9863-b289-217584b5bf8c'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-bd9c4280-f4ed-9863-b289-217584b5bf8c', 'request_id': 'bd9c4280-f4ed-9863-b289-217584b5bf8c'}"
+    ],
+    "timestamp": "2026-04-19T07:58:30.454818"
+  },
+  {
+    "index": 56,
+    "requirement": "生成产品或物品的极近距离特写图,如食物截面、商品细节、小物件放大展示,画面主体占满画幅,质感和纹理清晰突出...",
+    "duration_seconds": 1629.62,
+    "total_cost_usd": 6.2504,
+    "costs_breakdown": {
+      "P0_Router": 0.026,
+      "P1_Research_xhs": 0.7599,
+      "P1_Research_youtube": 0.0684,
+      "P1_Research_bili": 0.5025,
+      "P1_Research_x": 0.7811,
+      "P2_FilterBlueprint": 0.8701,
+      "P2_ExtractCaps": 2.4607,
+      "P3_Assembler": 0.7816
+    },
+    "errors": [
+      "P1_Research_youtube Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input text data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-fc717d1a-c50a-90ce-9f23-d147f75c5446', 'request_id': 'fc717d1a-c50a-90ce-9f23-d147f75c5446'}",
+      "P1_Research_youtube crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input text data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-fc717d1a-c50a-90ce-9f23-d147f75c5446', 'request_id': 'fc717d1a-c50a-90ce-9f23-d147f75c5446'}"
+    ],
+    "timestamp": "2026-04-19T07:58:43.434399"
+  },
+  {
+    "index": 67,
+    "requirement": "生成3D卡通风格的拟人化动物角色,角色具有毛绒质感和丰富的表情神态,能够在不同生活场景(办公室、卧室、户外)中呈现出喜怒哀乐等情绪状态,整体风格类似皮克斯动画...",
+    "duration_seconds": 1215.29,
+    "total_cost_usd": 3.1087,
+    "costs_breakdown": {
+      "P0_Router": 0.0174,
+      "P1_Research_xhs": 0.6022,
+      "P1_Research_youtube": 0.185,
+      "P1_Research_bili": 0.6144,
+      "P1_Research_x": 0.4654,
+      "P2_FilterBlueprint": 0.5461,
+      "P2_ExtractCaps": 0.233,
+      "P3_Assembler": 0.4452
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T08:01:47.627918"
+  },
+  {
+    "index": 65,
+    "requirement": "生成暖色调的室内空间效果图,以米白、浅棕、焦糖色为主色调,光线柔和自然,空间布置温馨舒适,整体画面传达出放松、治愈、生活化的温暖氛围...",
+    "duration_seconds": 2213.62,
+    "total_cost_usd": 4.3729,
+    "costs_breakdown": {
+      "P0_Router": 0.0174,
+      "P1_Research_xhs": 1.0498,
+      "P1_Research_bili": 0.2867,
+      "P1_Research_x": 0.108,
+      "P1_Research_zhihu": 0.0,
+      "P2_FilterBlueprint": 0.4068,
+      "P2_ExtractCaps": 1.7975,
+      "P3_Assembler": 0.7066
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-f4507f8e-ac97-9d9d-9375-adffd9b6e8d5', 'request_id': 'f4507f8e-ac97-9d9d-9375-adffd9b6e8d5'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-f4507f8e-ac97-9d9d-9375-adffd9b6e8d5', 'request_id': 'f4507f8e-ac97-9d9d-9375-adffd9b6e8d5'}",
+      "P1_Research_zhihu Failed: Error code: 400 - {'error': {'message': 'String value length (28049408) exceeds the maximum allowed (28000000, from `StreamReadConstraints.getMaxStringLength()`)', 'type': 'invalid_request_error', 'param': None, 'code': None}, 'request_id': '31a4f38d-a68c-971a-854c-2233b7d0e5d1'}",
+      "P1_Research_zhihu crashed: BadRequestError: Error code: 400 - {'error': {'message': 'String value length (28049408) exceeds the maximum allowed (28000000, from `StreamReadConstraints.getMaxStringLength()`)', 'type': 'invalid_request_error', 'param': None, 'code': None}, 'request_id': '31a4f38d-a68c-971a-854c-2233b7d0e5d1'}",
+      "P1_Research_zhihu Recovery Failed: Error code: 400 - {'error': {'message': 'String value length (28049408) exceeds the maximum allowed (28000000, from `StreamReadConstraints.getMaxStringLength()`)', 'type': 'invalid_request_error', 'param': None, 'code': None}, 'request_id': '0a1422a2-5c20-93f0-b74b-9926f40cf412'}",
+      "P1_Research_zhihu recovery crashed: BadRequestError: Error code: 400 - {'error': {'message': 'String value length (28049408) exceeds the maximum allowed (28000000, from `StreamReadConstraints.getMaxStringLength()`)', 'type': 'invalid_request_error', 'param': None, 'code': None}, 'request_id': '0a1422a2-5c20-93f0-b74b-9926f40cf412'}",
+      "Missing case file for zhihu! Agent likely hit max_iterations without saving."
+    ],
+    "timestamp": "2026-04-19T08:09:54.207803"
+  },
+  {
+    "index": 65,
+    "requirement": "生成暖色调的室内空间效果图,以米白、浅棕、焦糖色为主色调,光线柔和自然,空间布置温馨舒适,整体画面传达出放松、治愈、生活化的温暖氛围...",
+    "duration_seconds": 580.43,
+    "total_cost_usd": 0.8424,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_youtube": 0.5805,
+      "P3_Assembler": 0.2446
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T08:19:46.584469"
+  },
+  {
+    "index": 88,
+    "requirement": "制作科技感强烈的活动宣传海报:使用深色背景配合橙色、蓝色等高对比度霓虹色调,融合未来感城市或科技场景插图,搭配大号粗体标题文字,整体呈现出硬核、前沿的视觉气质...",
+    "duration_seconds": 1295.25,
+    "total_cost_usd": 4.8787,
+    "costs_breakdown": {
+      "P0_Router": 0.0173,
+      "P1_Research_xhs": 0.2942,
+      "P1_Research_youtube": 0.1718,
+      "P1_Research_bili": 0.5901,
+      "P1_Research_x": 0.6611,
+      "P2_FilterBlueprint": 0.5968,
+      "P2_ExtractCaps": 1.884,
+      "P3_Assembler": 0.6633
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T08:20:18.659502"
+  },
+  {
+    "index": 88,
+    "requirement": "制作科技感强烈的活动宣传海报:使用深色背景配合橙色、蓝色等高对比度霓虹色调,融合未来感城市或科技场景插图,搭配大号粗体标题文字,整体呈现出硬核、前沿的视觉气质...",
+    "duration_seconds": 442.0,
+    "total_cost_usd": 0.5809,
+    "costs_breakdown": {
+      "P3_Assembler": 0.5809
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T08:27:52.780956"
+  },
+  {
+    "index": 69,
+    "requirement": "生成一组多格拼贴图,每格展示同一人物在不同场景/状态下的夸张表情和肢体动作,配合幽默文字标注,整体呈现出戏剧化的情绪起伏效果(如一周心情变化、苦情崩溃、搞笑反应...",
+    "duration_seconds": 2268.24,
+    "total_cost_usd": 5.7754,
+    "costs_breakdown": {
+      "P0_Router": 0.0174,
+      "P1_Research_xhs": 0.7916,
+      "P1_Research_youtube": 0.9768,
+      "P1_Research_bili": 0.7768,
+      "P1_Research_x": 0.2033,
+      "P2_FilterBlueprint": 0.514,
+      "P2_ExtractCaps": 1.9112,
+      "P3_Assembler": 0.5843
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T08:32:06.058008"
+  },
+  {
+    "index": 72,
+    "requirement": "将真实照片转换成具有统一色调风格的插画效果,整体呈现蓝紫色调的复古油画或动画风格,让风景场景看起来像艺术插图...",
+    "duration_seconds": 1895.48,
+    "total_cost_usd": 3.9198,
+    "costs_breakdown": {
+      "P0_Router": 0.0168,
+      "P1_Research_xhs": 0.6014,
+      "P1_Research_youtube": 0.4653,
+      "P1_Research_bili": 0.3003,
+      "P1_Research_zhihu": 0.6215,
+      "P2_FilterBlueprint": 0.2205,
+      "P2_ExtractCaps": 1.072,
+      "P3_Assembler": 0.622
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T08:33:36.848376"
+  },
+  {
+    "index": 69,
+    "requirement": "生成一组多格拼贴图,每格展示同一人物在不同场景/状态下的夸张表情和肢体动作,配合幽默文字标注,整体呈现出戏剧化的情绪起伏效果(如一周心情变化、苦情崩溃、搞笑反应...",
+    "duration_seconds": 461.93,
+    "total_cost_usd": 0.4776,
+    "costs_breakdown": {
+      "P3_Assembler": 0.4776
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T08:40:00.120486"
+  },
+  {
+    "index": 61,
+    "requirement": "在以暗色或单色为主的画面中,用局部的高饱和亮色(如红色心脏、橙色暖光窗口、金黄色星光)作为点睛之笔,让视线自然聚焦到这个色彩亮点上,形成强烈的视觉引导...",
+    "duration_seconds": 3561.96,
+    "total_cost_usd": 7.3253,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.8729,
+      "P1_Research_youtube": 0.5749,
+      "P1_Research_bili": 1.1151,
+      "P1_Research_zhihu": 1.5444,
+      "P2_FilterBlueprint": 0.7652,
+      "P2_ExtractCaps": 1.853,
+      "P3_Assembler": 0.5827
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T08:58:18.193934"
+  },
+  {
+    "index": 77,
+    "requirement": "在图片上叠加对话气泡或多行说明文字,用于讲述故事背景或补充情节说明,文字带有描边或阴影效果以确保在复杂背景上清晰可读...",
+    "duration_seconds": 1552.53,
+    "total_cost_usd": 4.2938,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_xhs": 0.8793,
+      "P1_Research_youtube": 0.4659,
+      "P1_Research_bili": 0.2348,
+      "P1_Research_x": 0.3619,
+      "P2_FilterBlueprint": 0.3785,
+      "P2_ExtractCaps": 0.0827,
+      "P3_Assembler": 1.8734
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-a01e7ad6-a828-94ad-8cd6-6eb9a346c44c', 'request_id': 'a01e7ad6-a828-94ad-8cd6-6eb9a346c44c'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-a01e7ad6-a828-94ad-8cd6-6eb9a346c44c', 'request_id': 'a01e7ad6-a828-94ad-8cd6-6eb9a346c44c'}"
+    ],
+    "timestamp": "2026-04-19T08:59:43.343813"
+  },
+  {
+    "index": 93,
+    "requirement": "生成具有强烈戏剧性光影对比的户外场景图,画面中光源方向明确(如侧光或逆光),亮部与暗部之间形成鲜明反差,阴影轮廓清晰,整体呈现出电影感或艺术摄影风格的视觉张力...",
+    "duration_seconds": 2120.52,
+    "total_cost_usd": 8.0368,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 0.7339,
+      "P1_Research_youtube": 0.5052,
+      "P1_Research_bili": 0.426,
+      "P1_Research_x": 0.6413,
+      "P2_FilterBlueprint": 0.5468,
+      "P2_ExtractCaps": 2.3139,
+      "P3_Assembler": 2.8527
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T09:03:25.988778"
+  },
+  {
+    "index": 77,
+    "requirement": "在图片上叠加对话气泡或多行说明文字,用于讲述故事背景或补充情节说明,文字带有描边或阴影效果以确保在复杂背景上清晰可读...",
+    "duration_seconds": 339.34,
+    "total_cost_usd": 1.1763,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.1881,
+      "P2_ExtractCaps": 0.7733,
+      "P3_Assembler": 0.2149
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T09:05:34.181423"
+  },
+  {
+    "index": 70,
+    "requirement": "将动物(如猫咪)与各种食物、道具进行创意合成,给动物添加配饰(帽子、假发、领带等)并嵌入食物场景中,搭配谐音梗或双关文字,制作出拟人化角色扮演的趣味表情包图片...",
+    "duration_seconds": 2874.7,
+    "total_cost_usd": 6.69,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_xhs": 0.9561,
+      "P1_Research_youtube": 0.6661,
+      "P1_Research_bili": 0.4077,
+      "P1_Research_x": 0.0,
+      "P2_FilterBlueprint": 1.4087,
+      "P2_ExtractCaps": 2.7167,
+      "P3_Assembler": 0.5175
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: The provided URL does not appear to be valid. Ensure it is correctly formatted.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-317de2b1-9bd6-9b38-ae42-f11af7accb26', 'request_id': '317de2b1-9bd6-9b38-ae42-f11af7accb26'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: The provided URL does not appear to be valid. Ensure it is correctly formatted.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-317de2b1-9bd6-9b38-ae42-f11af7accb26', 'request_id': '317de2b1-9bd6-9b38-ae42-f11af7accb26'}",
+      "P3_Assembler Recovery Failed: 'NoneType' object is not iterable",
+      "P3_Assembler recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T09:07:54.135515"
+  },
+  {
+    "index": 70,
+    "requirement": "将动物(如猫咪)与各种食物、道具进行创意合成,给动物添加配饰(帽子、假发、领带等)并嵌入食物场景中,搭配谐音梗或双关文字,制作出拟人化角色扮演的趣味表情包图片...",
+    "duration_seconds": 1122.32,
+    "total_cost_usd": 3.0393,
+    "costs_breakdown": {
+      "P2_ExtractCaps": 2.09,
+      "P3_Assembler": 0.9492
+    },
+    "errors": [
+      "P2_ExtractCaps Failed: 'NoneType' object is not iterable",
+      "P2_ExtractCaps crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T09:26:48.784474"
+  },
+  {
+    "index": 74,
+    "requirement": "生成具有强烈光影对比的场景图,画面中光源明显(如阳光折射、水面反光、彩虹色光晕),暗部极深、亮部极亮,整体呈现出戏剧性的明暗反差和光线质感...",
+    "duration_seconds": 3007.65,
+    "total_cost_usd": 5.0368,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_xhs": 1.3262,
+      "P1_Research_youtube": 0.3278,
+      "P1_Research_bili": 0.9745,
+      "P1_Research_x": 0.7009,
+      "P2_FilterBlueprint": 0.7262,
+      "P2_ExtractCaps": 0.0,
+      "P3_Assembler": 0.9639
+    },
+    "errors": [
+      "P2_ExtractCaps Failed: 'NoneType' object is not iterable",
+      "P2_ExtractCaps crashed: TypeError: 'NoneType' object is not iterable",
+      "P2_ExtractCaps Recovery Failed: 'NoneType' object is not iterable",
+      "P2_ExtractCaps recovery crashed: TypeError: 'NoneType' object is not iterable"
+    ],
+    "timestamp": "2026-04-19T09:30:21.618675"
+  },
+  {
+    "index": 98,
+    "requirement": "制作多宫格拼贴式内容图,每个格子内有大标题文字突出显示核心信息(如价格、品类名),配合产品图或场景图,标题字号远大于说明文字,形成层次分明的视觉结构...",
+    "duration_seconds": 1647.16,
+    "total_cost_usd": 4.055,
+    "costs_breakdown": {
+      "P0_Router": 0.0175,
+      "P1_Research_xhs": 0.6484,
+      "P1_Research_bili": 0.5046,
+      "P1_Research_x": 0.8722,
+      "P1_Research_zhihu": 0.0,
+      "P2_FilterBlueprint": 0.529,
+      "P2_ExtractCaps": 0.4097,
+      "P3_Assembler": 1.0735
+    },
+    "errors": [
+      "P1_Research_zhihu Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-6995b76c-dd5a-9456-936d-b038009ef594', 'request_id': '6995b76c-dd5a-9456-936d-b038009ef594'}",
+      "P1_Research_zhihu crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-6995b76c-dd5a-9456-936d-b038009ef594', 'request_id': '6995b76c-dd5a-9456-936d-b038009ef594'}",
+      "P1_Research_zhihu Recovery Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-55a9ee69-e1cf-9834-bc78-06034b574192', 'request_id': '55a9ee69-e1cf-9834-bc78-06034b574192'}",
+      "P1_Research_zhihu recovery crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.DataInspectionFailed: Input image data may contain inappropriate content.', 'type': 'data_inspection_failed', 'param': None, 'code': 'data_inspection_failed'}, 'id': 'chatcmpl-55a9ee69-e1cf-9834-bc78-06034b574192', 'request_id': '55a9ee69-e1cf-9834-bc78-06034b574192'}",
+      "Missing case file for zhihu! Agent likely hit max_iterations without saving."
+    ],
+    "timestamp": "2026-04-19T09:31:06.918608"
+  },
+  {
+    "index": 66,
+    "requirement": "生成具有强烈视觉冲击力的超现实主义风格图像:画面以大地色系(深红、赭石、深蓝)为主调,将白马、牛仔等元素置于极简的荒漠/盐湖场景中,营造出孤寂、神秘、如油画般的...",
+    "duration_seconds": 2311.82,
+    "total_cost_usd": 6.013,
+    "costs_breakdown": {
+      "P0_Router": 0.0171,
+      "P1_Research_xhs": 1.0889,
+      "P1_Research_youtube": 0.2187,
+      "P1_Research_bili": 1.0071,
+      "P1_Research_x": 0.2147,
+      "P2_FilterBlueprint": 0.4194,
+      "P2_ExtractCaps": 2.216,
+      "P3_Assembler": 0.831
+    },
+    "errors": [
+      "P1_Research_x Failed: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-a1a6170c-f291-9d92-b097-9ab8ba1364b8', 'request_id': 'a1a6170c-f291-9d92-b097-9ab8ba1364b8'}",
+      "P1_Research_x crashed: BadRequestError: Error code: 400 - {'error': {'message': '<400> InternalError.Algo.InvalidParameter: Download multimodal file timed out', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}, 'id': 'chatcmpl-a1a6170c-f291-9d92-b097-9ab8ba1364b8', 'request_id': 'a1a6170c-f291-9d92-b097-9ab8ba1364b8'}"
+    ],
+    "timestamp": "2026-04-19T09:37:03.239084"
+  },
+  {
+    "index": 82,
+    "requirement": "生成采用非常规拍摄角度的图片,如从低角度仰拍、从高处俯视、或模拟第一人称主观视角看向场景,让画面产生独特的视觉冲击感...",
+    "duration_seconds": 2058.5,
+    "total_cost_usd": 5.0835,
+    "costs_breakdown": {
+      "P0_Router": 0.0173,
+      "P1_Research_xhs": 0.2378,
+      "P1_Research_youtube": 0.4156,
+      "P1_Research_bili": 0.3219,
+      "P1_Research_x": 0.3638,
+      "P2_FilterBlueprint": 0.4548,
+      "P2_ExtractCaps": 2.6169,
+      "P3_Assembler": 0.6554
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T09:40:06.538043"
+  },
+  {
+    "index": 98,
+    "requirement": "制作多宫格拼贴式内容图,每个格子内有大标题文字突出显示核心信息(如价格、品类名),配合产品图或场景图,标题字号远大于说明文字,形成层次分明的视觉结构...",
+    "duration_seconds": 784.55,
+    "total_cost_usd": 1.4603,
+    "costs_breakdown": {
+      "P0_Router": 0.0173,
+      "P1_Research_youtube": 0.4712,
+      "P2_ExtractCaps": 0.602,
+      "P3_Assembler": 0.3697
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T09:44:23.526140"
+  },
+  {
+    "index": 82,
+    "requirement": "生成采用非常规拍摄角度的图片,如从低角度仰拍、从高处俯视、或模拟第一人称主观视角看向场景,让画面产生独特的视觉冲击感...",
+    "duration_seconds": 418.38,
+    "total_cost_usd": 1.1172,
+    "costs_breakdown": {
+      "P2_FilterBlueprint": 0.2338,
+      "P3_Assembler": 0.8834
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T09:47:16.768653"
+  },
+  {
+    "index": 79,
+    "requirement": "在多图拼贴海报上为每个区域叠加带图标的标签(如勾选符号+地点名称),并在整体画面上方添加大标题和副标题文字,形成图文结合的内容合集展示效果...",
+    "duration_seconds": 1739.04,
+    "total_cost_usd": 4.3107,
+    "costs_breakdown": {
+      "P0_Router": 0.0172,
+      "P1_Research_xhs": 0.8006,
+      "P1_Research_youtube": 0.3709,
+      "P1_Research_bili": 0.5402,
+      "P1_Research_zhihu": 0.6068,
+      "P2_FilterBlueprint": 0.1613,
+      "P2_ExtractCaps": 1.0753,
+      "P3_Assembler": 0.7384
+    },
+    "errors": [],
+    "timestamp": "2026-04-19T09:59:33.313101"
+  }
+]

+ 562 - 0
examples/process_pipeline/run_pipeline.py

@@ -0,0 +1,562 @@
+"""
+V4 Pipeline: Hardcoded Map-Reduce Orchestration for AIGC Process Research
+"""
+import argparse
+import asyncio
+import json
+import sys
+import time
+from datetime import datetime
+from pathlib import Path
+
+# Add project root to path
+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.tools.builtin.knowledge import KnowledgeConfig
+from agent.trace import FileSystemTraceStore, Trace, Message
+from agent.llm import create_qwen_llm_call
+from agent.llm.openrouter import create_openrouter_llm_call
+from agent.llm.claude import create_claude_llm_call
+
+# config from existing setup
+from examples.process_research.config import (
+    OUTPUT_DIR, TRACE_STORE_PATH, SKILLS_DIR, LOG_LEVEL, LOG_FILE,
+    BROWSER_TYPE, HEADLESS, COORDINATOR_RUN_CONFIG
+)
+from agent.utils import setup_logging
+
+async def run_agent_task(runner: AgentRunner, prompt_name: str, kwargs: dict, task_name: str, model_name: str):
+    base_dir = Path(__file__).parent
+    prompt_path = base_dir / "prompts" / f"{prompt_name}.prompt"
+    prompt = SimplePrompt(prompt_path)
+    
+    messages = prompt.build_messages(**kwargs)
+    target_tools = []
+    if prompt_name == "extract_capabilities":
+        target_tools = ["capability_search", "capability_list", "tool_search"]
+
+    # 按 agent 类型配置工具组权限
+    tool_groups_map = {
+        "researcher": ["core", "content"],           # 搜索+文件,无浏览器
+        "filter_and_blueprint": ["core"],             # 只需文件读写
+        "extract_capabilities": ["core"],             # 只需文件读写(额外工具由 target_tools 补充)
+        "assemble_strategy": ["core"],                # 只需文件读写
+    }
+
+    run_config = RunConfig(
+        model=prompt.config.get("model") or model_name,
+        temperature=prompt.config.get("temperature") or 0.3,
+        name=task_name,
+        agent_type=prompt_name,
+        tools=target_tools,
+        tool_groups=tool_groups_map.get(prompt_name, ["core"]),
+        knowledge=KnowledgeConfig(enable_completion_extraction=False, enable_extraction=False, enable_injection=False)
+    )
+    
+    task_cost = 0.0
+    task_errors = []
+    last_trace_id = None
+    
+    print(f"🚀 [Launch] {task_name}")
+    try:
+        async for item in runner.run(messages=messages, config=run_config):
+            if isinstance(item, Trace):
+                last_trace_id = item.trace_id
+                if item.status == "completed":
+                    print(f"✅ [Done] {task_name} (Cost: ${item.total_cost:.4f})")
+                    task_cost = item.total_cost
+                elif item.status == "failed":
+                    print(f"❌ [Fail] {task_name}: {item.error_message}")
+                    task_errors.append(f"{task_name} Failed: {item.error_message}")
+            if isinstance(item, Message):
+                if item.role == "tool":
+                    content = item.content if isinstance(item.content, dict) else {}
+                    t_name = content.get("tool_name", "unknown")
+                    if t_name in ("write_file", "write_json"):
+                        print(f"   💾 [File Written by {task_name}]")
+    except Exception as e:
+        err_msg = f"{type(e).__name__}: {e}"
+        print(f"❌ [Exception] {task_name} crashed: {err_msg}")
+        task_errors.append(f"{task_name} crashed: {err_msg}")
+                    
+    # Verification & Recovery block
+    out_file = kwargs.get("output_file")
+    if out_file and not Path(out_file).exists() and last_trace_id:
+        print(f"⚠️ [Recovery] {task_name} missing output file. Triggering forced wrap-up continuation...")
+        recovery_messages = [{
+            "role": "user", 
+            "content": f"【系统强制指令】你的任务阶段已终止,但尚未将结果写入文件。请立刻调用 write_json 工具,将你目前已经搜集或处理到的原生结构化内容直接作为 json_data 参数对象写入到绝对路径 `{out_file}`,如果搜集失败也请写入空的总结对象。必须立刻执行写入!"
+        }]
+        rec_config = RunConfig(
+            model=model_name,
+            temperature=0.1,
+            name=task_name + "_Rec",
+            agent_type=prompt_name,
+            trace_id=last_trace_id,
+            knowledge=KnowledgeConfig(enable_completion_extraction=False, enable_extraction=False, enable_injection=False)
+        )
+        try:
+            async for r_item in runner.run(messages=recovery_messages, config=rec_config):
+                if isinstance(r_item, Trace):
+                    if r_item.status == "completed":
+                        task_cost += r_item.total_cost
+                    elif r_item.status == "failed":
+                        task_errors.append(f"{task_name} Recovery Failed: {r_item.error_message}")
+                if isinstance(r_item, Message) and r_item.role == "tool":
+                    content = r_item.content if isinstance(r_item.content, dict) else {}
+                    if content.get("tool_name") in ("write_file", "write_json"):
+                        print(f"   💾 [Recovery File Written by {task_name}]")
+        except Exception as e:
+            err_msg = f"{type(e).__name__}: {e}"
+            print(f"❌ [Exception Recovery] {task_name} crashed: {err_msg}")
+            task_errors.append(f"{task_name} recovery crashed: {err_msg}")
+                    
+    return task_cost, task_errors
+
+async def run_anthropic_sdk_task(prompt_name: str, kwargs: dict, task_name: str, model_name: str):
+    """
+    备用:使用纯净的官方 Anthropic SDK 驱动。
+    跳过内部大架构的 trace 追踪,但保留对原有 Python 工具库(如 write_file/glob_files)的无缝调用。
+    """
+    from anthropic import AsyncAnthropic
+    from agent.tools.registry import get_tool_registry
+    
+    base_dir = Path(__file__).parent
+    prompt_path = base_dir / "prompts" / f"{prompt_name}.prompt"
+    prompt = SimplePrompt(prompt_path)
+    
+    # 1. 组装输入 Message
+    raw_messages = prompt.build_messages(**kwargs)
+    system_prompt = ""
+    messages = []
+    for msg in raw_messages:
+        if msg["role"] == "system":
+            system_prompt += msg["content"] + "\n\n"
+        else:
+            messages.append({"role": msg["role"], "content": msg["content"]})
+            
+    # 2. 映射目标工具
+    target_tools = ["write_file", "write_json", "read_file", "glob_files"]
+    if prompt_name == "extract_capabilities":
+        target_tools.extend(["capability_search", "capability_list", "tool_search"])
+        
+    registry = get_tool_registry()
+    schemas = registry.get_schemas(target_tools)
+    anthropic_tools = []
+    for s in schemas:
+        anthropic_tools.append({
+            "name": s["function"]["name"],
+            "description": s["function"].get("description", ""),
+            "input_schema": s["function"]["parameters"]
+        })
+        
+    # 3. 初始化并开启 Loop
+    # 提示:你需要在你的终端中配置好 ANTHROPIC_API_KEY 环境变量
+    client = AsyncAnthropic()  
+    task_cost = 0.0
+    task_errors = []
+    print(f"🚀 [Launch Anthropic SDK] {task_name}")
+    
+    max_loops = 50
+    for loop_idx in range(max_loops):
+        try:
+            # 去除前缀(兼容比如 openrouter 传入的名字)
+            clean_model = model_name.split("/")[-1] if "/" in model_name else model_name
+            
+            # 这里专门将实际请求映射到其可用的特殊别名 claude-sonnet-4-5 
+            target_model = "claude-sonnet-4-5" if "claude" in clean_model else clean_model
+
+            response = await client.messages.create(
+                model=target_model,
+                max_tokens=4096,
+                temperature=0.2,
+                system=system_prompt,
+                messages=messages,
+                tools=anthropic_tools
+            )
+            
+            # (简略预估,不代表真实官方开销)
+            if hasattr(response, 'usage'):
+                step_cost = (response.usage.input_tokens / 1e6 * 3.0) + (response.usage.output_tokens / 1e6 * 15.0)
+                task_cost += step_cost
+            
+            # 加入助手回复
+            assistant_content = []
+            tool_uses = []
+            
+            for content_block in response.content:
+                if content_block.type == "text":
+                    text_val = content_block.text
+                    if text_val:
+                        assistant_content.append({"type": "text", "text": text_val})
+                        print(f"\n🤖 [{task_name} Output]:\n{text_val}\n")
+                elif content_block.type == "tool_use":
+                    assistant_content.append({
+                        "type": "tool_use",
+                        "id": content_block.id,
+                        "name": content_block.name,
+                        "input": content_block.input
+                    })
+                    tool_uses.append(content_block)
+            
+            if not assistant_content:
+                assistant_content.append({"type": "text", "text": "(Thinking completed but no output)"})
+                
+            messages.append({"role": "assistant", "content": assistant_content})
+            
+            # 出口:没有调用工具说明任务结束
+            if not tool_uses:
+                print(f"✅ [Done Anthropic SDK] {task_name} (Cost: ${task_cost:.4f})")
+                break
+                
+            # 工具执行与回传
+            tool_results = []
+            for tu in tool_uses:
+                if tu.name in ("write_file", "write_json"):
+                    print(f"   💾 [File Written by SDK] {task_name}")
+                
+                print(f"   🛠️ [Tool Exec Debug] name_is={tu.name}, input_is={tu.input}, type_is={type(tu.input)}")
+                # 执行本地环境的函数
+                result_str = await registry.execute(tu.name, tu.input)
+                
+                tool_results.append({
+                    "type": "tool_result",
+                    "tool_use_id": tu.id,
+                    "content": result_str
+                })
+                
+            messages.append({"role": "user", "content": tool_results})
+            
+        except Exception as e:
+            err_msg = str(e)
+            print(f"❌ [Fail SDK Core] {task_name}: {err_msg}")
+            task_errors.append(err_msg)
+            break
+            
+    # Verification & Recovery block for SDK (porting from AgentRunner)
+    out_file = kwargs.get("output_file")
+    if out_file and not Path(out_file).exists():
+        print(f"⚠️ [Recovery SDK] {task_name} missing output file. Triggering forced wrap-up continuation...")
+        messages.append({
+            "role": "user", 
+            "content": f"【系统强制指令】你的任务阶段已完成分析,但尚未将最终结果写入目标文件。请立刻调用 write_json (或 write_file) 工具,将你的成果数据直接写入到绝对路径 `{out_file}`,务必立刻执行写入动作!"
+        })
+        try:
+            target_model = "claude-sonnet-4-5" if "claude" in clean_model else clean_model
+            rec_response = await client.messages.create(
+                model=target_model,
+                max_tokens=4096,
+                temperature=0.1,
+                system=system_prompt,
+                messages=messages,
+                tools=anthropic_tools,
+                tool_choice={"type": "any"}
+            )
+            for content_block in rec_response.content:
+                if content_block.type == "tool_use":
+                    if content_block.name in ("write_file", "write_json"):
+                        print(f"   💾 [Recovery File Written by SDK] {task_name}")
+                    await registry.execute(content_block.name, content_block.input)
+        except Exception as e:
+            print(f"❌ [Fail SDK Recovery] {task_name}: {e}")
+            task_errors.append(str(e))
+            
+    return task_cost, task_errors
+
+async def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--index", type=int, required=True, help="Index of requirement in db_requirements.json")
+    parser.add_argument("--skip-research", action="store_true", help="Skip Phase 1 and use existing raw cases")
+    parser.add_argument("--research-only", action="store_true", help="Only run research phases, skip Phase 2 and 3")
+    parser.add_argument("--platforms", type=str, default="xhs,youtube,bili,x", help="Comma-separated list of platforms to search")
+    parser.add_argument("--use-claude-sdk", action="store_true", help="Use pure Anthropic SDK (run_anthropic_sdk_task) instead of internal AgentRunner for Phase 2/3")
+    args = parser.parse_args()
+
+    base_dir = Path(__file__).parent
+    # Load requirements locally
+    req_path = base_dir / "db_requirements.json"
+    
+    with open(req_path, encoding='utf-8') as f:
+        reqs = json.load(f)
+    
+    if args.index < 0 or args.index >= len(reqs):
+        print("Index out of bounds")
+        sys.exit(1)
+        
+    requirement = reqs[args.index]
+    
+    # 0. Setup directories
+    output_dir = base_dir / "output" / f"{(args.index+1):03d}"
+    output_dir.mkdir(parents=True, exist_ok=True)
+    raw_cases_dir = output_dir / "raw_cases"
+    raw_cases_dir.mkdir(parents=True, exist_ok=True)
+    
+    setup_logging(level=LOG_LEVEL, file=LOG_FILE)
+    
+    print("=" * 60)
+    print(f"V4 Hardcoded Pipeline | Demand: [{args.index+1:03d}] {requirement[:40]}...")
+    print("=" * 60)
+
+    # Load presets
+    presets_path = base_dir / "presets.json"
+    if presets_path.exists():
+        from agent.core.presets import load_presets_from_json
+        load_presets_from_json(str(presets_path))
+        print("✅ Configured Agent Presets (Skills Boundaries)")
+
+    # Browser initialization removed to save resources
+    
+    store = FileSystemTraceStore(base_path=TRACE_STORE_PATH)
+
+    # Instantiate two distinct LLM orchestrators
+    qwen_model = "qwen3.5-plus"  # maps to qwen3.5-plus via Qwen interface
+    
+    # 当前使用原生 Claude 接口 (走 ANTHROPIC_API_KEY),而非 OpenRouter
+    claude_model = "claude-sonnet-4-5"  # 切换回 4-5 模型别名
+    from agent.llm.claude import create_claude_llm_call
+    claude_llm_call = create_claude_llm_call(model=claude_model)
+    
+    runner_qwen = AgentRunner(
+        trace_store=store,
+        llm_call=create_qwen_llm_call(model=qwen_model),
+        skills_dir=SKILLS_DIR
+    )
+    
+    runner_claude = AgentRunner(
+        trace_store=store,
+        llm_call=claude_llm_call,
+        skills_dir=SKILLS_DIR
+    )
+
+    try:
+        start_time = time.time()
+        total_cost = 0.0
+        costs_breakdown = {}
+        global_errors = []
+        strategy_file = None
+        
+        existing_platforms = []
+        if raw_cases_dir.exists():
+            for f in raw_cases_dir.glob("case_*.json"):
+                plat = f.stem.replace("case_", "")
+                if plat in ["xhs", "youtube", "bili", "x", "zhihu", "gzh"]:
+                    existing_platforms.append(plat)
+                    
+        platforms = [p.strip() for p in args.platforms.split(",") if p.strip()]
+        needed_count = max(0, 4 - len(existing_platforms))
+        
+        # Phase 0: Dynamic Routing
+        if not args.skip_research:
+            if needed_count == 0:
+                print(f"\n--- Phase 0: Skipping Routing (Already have {len(existing_platforms)} existing cases: {existing_platforms}) ---")
+                platforms = []
+            else:
+                print(f"\n--- Phase 0: Dynamic Platform Routing ({qwen_model}) ---")
+                print(f"📡 Found existing cases: {existing_platforms}. Requesting {needed_count} new platforms...")
+                try:
+                    router_prompt = SimplePrompt(base_dir / "prompts" / "router.prompt")
+                    rmessages = router_prompt.build_messages(
+                        requirement=requirement,
+                        existing_platforms=",".join(existing_platforms) if existing_platforms else "无",
+                        needed_count=str(needed_count)
+                    )
+                    rconfig = RunConfig(
+                        model=qwen_model,
+                        temperature=0.1,
+                        name="P0_Router",
+                        agent_type="router",
+                        knowledge=KnowledgeConfig(enable_completion_extraction=False, enable_extraction=False, enable_injection=False)
+                    )
+                    print(f"🚀 [Launch] P0_Router calculating optimal platforms...")
+                    
+                    router_response = ""
+                    async for item in runner_qwen.run(messages=rmessages, config=rconfig):
+                        if isinstance(item, Message) and item.role == "assistant" and isinstance(item.content, dict):
+                            text = item.content.get("text", "")
+                            if text and not item.content.get("tool_calls"):
+                                router_response = text
+                        if isinstance(item, Trace) and item.status == "completed":
+                            total_cost += item.total_cost
+                            costs_breakdown["P0_Router"] = round(item.total_cost, 4)
+                            
+                    if router_response:
+                        import re
+                        # Extract all alphabetic words from the response to handle extra text or markdown
+                        words = set(re.findall(r'\b[a-z]+\b', router_response.lower()))
+                        valid_platforms = ["xhs", "youtube", "bili", "x", "zhihu", "gzh"]
+                        # Intersect words with valid platforms (direct exact word matching, exclude existing)
+                        final_platforms = [p for p in valid_platforms if p in words and p not in existing_platforms]
+                        
+                        if final_platforms:
+                            platforms = final_platforms[:needed_count]
+                            print(f"🎯 [Router Decision] Selected {len(platforms)} new platforms: {platforms}")
+                        else:
+                            platforms = [p for p in platforms if p not in existing_platforms][:needed_count]
+                            print(f"⚠️ [Router Fallback] Invalid output '{router_response}'. Using delta default: {platforms}")
+                except Exception as e:
+                    platforms = [p for p in platforms if p not in existing_platforms][:needed_count]
+                    print(f"⚠️ [Router Logic Failed] Using delta default platforms ({platforms}). Error: {e}")
+
+        # Phase 1: MAP (Parallel Search) uses Qwen
+        if not args.skip_research:
+            print(f"\n--- Phase 1: Distributed Research Map ({qwen_model}) ---")
+            phase1_tasks = []
+            for p in platforms:
+                task_desc = f"渠道:{p.upper()}。核心需求:{requirement}"
+                out_file = str(raw_cases_dir / f"case_{p}.json")
+                kwargs = {
+                    "task": task_desc,
+                    "output_file": out_file
+                }
+                phase1_tasks.append(run_agent_task(runner_qwen, "researcher", kwargs, f"P1_Research_{p}", qwen_model))
+                
+            phase1_results = await asyncio.gather(*phase1_tasks)
+            for (task_cost, task_errors), p in zip(phase1_results, platforms):
+                total_cost += task_cost
+                costs_breakdown[f"P1_Research_{p}"] = round(task_cost, 4)
+                global_errors.extend(task_errors)
+                
+                # Check if cases actually got written
+                expected_file = Path(raw_cases_dir / f"case_{p}.json")
+                if not expected_file.exists():
+                    err_msg = f"Missing case file for {p}! Agent likely hit max_iterations without saving."
+                    print(f"⚠️ [Warning] {err_msg}")
+                    global_errors.append(err_msg)
+        else:
+            print("\n⏭️  [Skip] Phase 1 Skipped via --skip-research. Using existing cases...")
+
+        # Phase 2: REDUCE 1 (Parallel Distillation) uses Claude
+        if not args.research_only:
+            # Phase 2: REDUCE 1 (Parallel Distillation) uses Claude
+            print(f"\n--- Phase 2: Parallel Distillation ({claude_model}) ---")
+            blueprint_file = str(output_dir / "blueprint.json")
+            capabilities_file = str(output_dir / "capabilities_extracted.json")
+            raw_glob = str(raw_cases_dir / "case_*.json").replace("\\", "/")
+            
+            task_a = None
+            task_b = None
+            
+            if Path(blueprint_file).exists():
+                print(f"⏭️  [Skip P2] blueprint.json already exists. Skipping P2_FilterBlueprint.")
+            else:
+                if args.use_claude_sdk:
+                    print("   > Using [Anthropic SDK Core] for P2_FilterBlueprint")
+                    task_a = run_anthropic_sdk_task("filter_and_blueprint", {
+                        "requirement": requirement,
+                        "raw_files_glob": raw_glob,
+                        "output_file": blueprint_file
+                    }, "P2_FilterBlueprint", claude_model)
+                else:
+                    print("   > Using [AgentRunner Core] for P2_FilterBlueprint")
+                    task_a = run_agent_task(runner_claude, "filter_and_blueprint", {
+                        "requirement": requirement,
+                        "raw_files_glob": raw_glob,
+                        "output_file": blueprint_file
+                    }, "P2_FilterBlueprint", claude_model)
+                    
+            if Path(capabilities_file).exists():
+                print(f"⏭️  [Skip P2] capabilities_extracted.json already exists. Skipping P2_ExtractCaps.")
+            else:
+                if args.use_claude_sdk:
+                    print("   > Using [Anthropic SDK Core] for P2_ExtractCaps")
+                    task_b = run_anthropic_sdk_task("extract_capabilities", {
+                        "requirement": requirement,
+                        "raw_files_glob": raw_glob,
+                        "output_file": capabilities_file
+                    }, "P2_ExtractCaps", claude_model)
+                else:
+                    print("   > Using [AgentRunner Core] for P2_ExtractCaps")
+                    task_b = run_agent_task(runner_claude, "extract_capabilities", {
+                        "requirement": requirement,
+                        "raw_files_glob": raw_glob,
+                        "output_file": capabilities_file
+                    }, "P2_ExtractCaps", claude_model)
+            
+            to_await = []
+            names_await = []
+            if task_a:
+                to_await.append(task_a)
+                names_await.append("P2_FilterBlueprint")
+            if task_b:
+                to_await.append(task_b)
+                names_await.append("P2_ExtractCaps")
+                
+            if to_await:
+                phase2_results = await asyncio.gather(*to_await)
+                for (cost, errs), t_name in zip(phase2_results, names_await):
+                    total_cost += cost
+                    costs_breakdown[t_name] = round(cost, 4)
+                    global_errors.extend(errs)
+
+            # Phase 3: REDUCE 2 (Final Assembly) uses Claude
+            print(f"\n--- Phase 3: Final Strategy Assembly ({claude_model}) ---")
+            strategy_file = str(output_dir / "strategy.json")
+            
+            if Path(strategy_file).exists():
+                print(f"⏭️  [Skip P3] strategy.json already exists. Skipping P3_Assembler.")
+            else:
+                if args.use_claude_sdk:
+                    print("   > Using [Anthropic SDK Core]")
+                    phase3_cost, phase3_errs = await run_anthropic_sdk_task("assemble_strategy", {
+                        "requirement": requirement,
+                        "blueprint_file": blueprint_file,
+                        "capabilities_file": capabilities_file,
+                        "output_file": strategy_file
+                    }, "P3_Assembler", claude_model)
+                else:
+                    print("   > Using [AgentRunner Core]")
+                    phase3_cost, phase3_errs = await run_agent_task(runner_claude, "assemble_strategy", {
+                        "requirement": requirement,
+                        "blueprint_file": blueprint_file,
+                        "capabilities_file": capabilities_file,
+                        "output_file": strategy_file
+                    }, "P3_Assembler", claude_model)
+                total_cost += phase3_cost
+                costs_breakdown["P3_Assembler"] = round(phase3_cost, 4)
+                global_errors.extend(phase3_errs)
+        else:
+            print("\n--- [Research Only] Stopping early. Skipping Phase 2 and Phase 3 ---")
+        
+        end_time = time.time()
+        elapsed_sec = end_time - start_time
+        
+        # Save Metrics
+        metrics_file = base_dir / "run_metrics.json"
+        metrics_data = []
+        if metrics_file.exists():
+            with open(metrics_file, "r", encoding="utf-8") as f:
+                try:
+                    metrics_data = json.load(f)
+                except json.JSONDecodeError:
+                    pass
+                    
+        metrics_data.append({
+            "index": args.index,
+            "requirement": requirement[:80] + "...",
+            "duration_seconds": round(elapsed_sec, 2),
+            "total_cost_usd": round(total_cost, 4),
+            "costs_breakdown": costs_breakdown,
+            "errors": global_errors,
+            "timestamp": datetime.now().isoformat()
+        })
+        
+        with open(metrics_file, "w", encoding="utf-8") as f:
+            json.dump(metrics_data, f, indent=2, ensure_ascii=False)
+            
+        print(f"\n📊 [Metrics] Pipeline completed in {elapsed_sec:.1f}s. Total Cost: ${total_cost:.4f}")
+        
+    finally:
+        pass
+        
+    print("✅ Pipeline run finished.")
+    if strategy_file:
+        print("✅ Strategy saved to:", strategy_file)
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 286 - 0
examples/process_pipeline/worker_0.bat

@@ -0,0 +1,286 @@
+@echo off
+cd /d %~dp0
+if exist ..\..\.venv\Scripts\activate.bat call ..\..\.venv\Scripts\activate.bat
+echo Worker Group 0 starting...
+echo =======================================
+echo Running Requirement Index: -1
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index -1
+python run_pipeline.py --index -1
+if not exist "output\000\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index -1
+) else (
+    echo [Verified] output\000\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 4
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 4
+python run_pipeline.py --index 4
+if not exist "output\005\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 4
+) else (
+    echo [Verified] output\005\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 9
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 9
+python run_pipeline.py --index 9
+if not exist "output\010\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 9
+) else (
+    echo [Verified] output\010\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 14
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 14
+python run_pipeline.py --index 14
+if not exist "output\015\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 14
+) else (
+    echo [Verified] output\015\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 19
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 19
+python run_pipeline.py --index 19
+if not exist "output\020\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 19
+) else (
+    echo [Verified] output\020\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 24
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 24
+python run_pipeline.py --index 24
+if not exist "output\025\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 24
+) else (
+    echo [Verified] output\025\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 29
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 29
+python run_pipeline.py --index 29
+if not exist "output\030\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 29
+) else (
+    echo [Verified] output\030\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 34
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 34
+python run_pipeline.py --index 34
+if not exist "output\035\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 34
+) else (
+    echo [Verified] output\035\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 39
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 39
+python run_pipeline.py --index 39
+if not exist "output\040\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 39
+) else (
+    echo [Verified] output\040\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 44
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 44
+python run_pipeline.py --index 44
+if not exist "output\045\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 44
+) else (
+    echo [Verified] output\045\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 49
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 49
+python run_pipeline.py --index 49
+if not exist "output\050\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 49
+) else (
+    echo [Verified] output\050\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 54
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 54
+python run_pipeline.py --index 54
+if not exist "output\055\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 54
+) else (
+    echo [Verified] output\055\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 59
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 59
+python run_pipeline.py --index 59
+if not exist "output\060\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 59
+) else (
+    echo [Verified] output\060\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 64
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 64
+python run_pipeline.py --index 64
+if not exist "output\065\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 64
+) else (
+    echo [Verified] output\065\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 69
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 69
+python run_pipeline.py --index 69
+if not exist "output\070\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 69
+) else (
+    echo [Verified] output\070\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 74
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 74
+python run_pipeline.py --index 74
+if not exist "output\075\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 74
+) else (
+    echo [Verified] output\075\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 79
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 79
+python run_pipeline.py --index 79
+if not exist "output\080\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 79
+) else (
+    echo [Verified] output\080\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 84
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 84
+python run_pipeline.py --index 84
+if not exist "output\085\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 84
+) else (
+    echo [Verified] output\085\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 89
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 89
+python run_pipeline.py --index 89
+if not exist "output\090\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 89
+) else (
+    echo [Verified] output\090\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 94
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 94
+python run_pipeline.py --index 94
+if not exist "output\095\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 94
+) else (
+    echo [Verified] output\095\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo Worker Group 0 finished!
+pause

+ 286 - 0
examples/process_pipeline/worker_1.bat

@@ -0,0 +1,286 @@
+@echo off
+cd /d %~dp0
+if exist ..\..\.venv\Scripts\activate.bat call ..\..\.venv\Scripts\activate.bat
+echo Worker Group 1 starting...
+echo =======================================
+echo Running Requirement Index: 0
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 0
+python run_pipeline.py --index 0
+if not exist "output\001\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 0
+) else (
+    echo [Verified] output\001\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 5
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 5
+python run_pipeline.py --index 5
+if not exist "output\006\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 5
+) else (
+    echo [Verified] output\006\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 10
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 10
+python run_pipeline.py --index 10
+if not exist "output\011\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 10
+) else (
+    echo [Verified] output\011\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 15
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 15
+python run_pipeline.py --index 15
+if not exist "output\016\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 15
+) else (
+    echo [Verified] output\016\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 20
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 20
+python run_pipeline.py --index 20
+if not exist "output\021\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 20
+) else (
+    echo [Verified] output\021\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 25
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 25
+python run_pipeline.py --index 25
+if not exist "output\026\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 25
+) else (
+    echo [Verified] output\026\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 30
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 30
+python run_pipeline.py --index 30
+if not exist "output\031\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 30
+) else (
+    echo [Verified] output\031\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 35
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 35
+python run_pipeline.py --index 35
+if not exist "output\036\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 35
+) else (
+    echo [Verified] output\036\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 40
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 40
+python run_pipeline.py --index 40
+if not exist "output\041\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 40
+) else (
+    echo [Verified] output\041\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 45
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 45
+python run_pipeline.py --index 45
+if not exist "output\046\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 45
+) else (
+    echo [Verified] output\046\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 50
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 50
+python run_pipeline.py --index 50
+if not exist "output\051\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 50
+) else (
+    echo [Verified] output\051\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 55
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 55
+python run_pipeline.py --index 55
+if not exist "output\056\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 55
+) else (
+    echo [Verified] output\056\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 60
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 60
+python run_pipeline.py --index 60
+if not exist "output\061\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 60
+) else (
+    echo [Verified] output\061\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 65
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 65
+python run_pipeline.py --index 65
+if not exist "output\066\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 65
+) else (
+    echo [Verified] output\066\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 70
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 70
+python run_pipeline.py --index 70
+if not exist "output\071\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 70
+) else (
+    echo [Verified] output\071\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 75
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 75
+python run_pipeline.py --index 75
+if not exist "output\076\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 75
+) else (
+    echo [Verified] output\076\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 80
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 80
+python run_pipeline.py --index 80
+if not exist "output\081\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 80
+) else (
+    echo [Verified] output\081\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 85
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 85
+python run_pipeline.py --index 85
+if not exist "output\086\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 85
+) else (
+    echo [Verified] output\086\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 90
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 90
+python run_pipeline.py --index 90
+if not exist "output\091\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 90
+) else (
+    echo [Verified] output\091\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 95
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 95
+python run_pipeline.py --index 95
+if not exist "output\096\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 95
+) else (
+    echo [Verified] output\096\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo Worker Group 1 finished!
+pause

+ 286 - 0
examples/process_pipeline/worker_2.bat

@@ -0,0 +1,286 @@
+@echo off
+cd /d %~dp0
+if exist ..\..\.venv\Scripts\activate.bat call ..\..\.venv\Scripts\activate.bat
+echo Worker Group 2 starting...
+echo =======================================
+echo Running Requirement Index: 1
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 1
+python run_pipeline.py --index 1
+if not exist "output\002\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 1
+) else (
+    echo [Verified] output\002\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 6
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 6
+python run_pipeline.py --index 6
+if not exist "output\007\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 6
+) else (
+    echo [Verified] output\007\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 11
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 11
+python run_pipeline.py --index 11
+if not exist "output\012\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 11
+) else (
+    echo [Verified] output\012\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 16
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 16
+python run_pipeline.py --index 16
+if not exist "output\017\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 16
+) else (
+    echo [Verified] output\017\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 21
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 21
+python run_pipeline.py --index 21
+if not exist "output\022\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 21
+) else (
+    echo [Verified] output\022\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 26
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 26
+python run_pipeline.py --index 26
+if not exist "output\027\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 26
+) else (
+    echo [Verified] output\027\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 31
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 31
+python run_pipeline.py --index 31
+if not exist "output\032\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 31
+) else (
+    echo [Verified] output\032\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 36
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 36
+python run_pipeline.py --index 36
+if not exist "output\037\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 36
+) else (
+    echo [Verified] output\037\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 41
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 41
+python run_pipeline.py --index 41
+if not exist "output\042\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 41
+) else (
+    echo [Verified] output\042\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 46
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 46
+python run_pipeline.py --index 46
+if not exist "output\047\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 46
+) else (
+    echo [Verified] output\047\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 51
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 51
+python run_pipeline.py --index 51
+if not exist "output\052\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 51
+) else (
+    echo [Verified] output\052\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 56
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 56
+python run_pipeline.py --index 56
+if not exist "output\057\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 56
+) else (
+    echo [Verified] output\057\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 61
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 61
+python run_pipeline.py --index 61
+if not exist "output\062\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 61
+) else (
+    echo [Verified] output\062\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 66
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 66
+python run_pipeline.py --index 66
+if not exist "output\067\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 66
+) else (
+    echo [Verified] output\067\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 71
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 71
+python run_pipeline.py --index 71
+if not exist "output\072\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 71
+) else (
+    echo [Verified] output\072\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 76
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 76
+python run_pipeline.py --index 76
+if not exist "output\077\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 76
+) else (
+    echo [Verified] output\077\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 81
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 81
+python run_pipeline.py --index 81
+if not exist "output\082\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 81
+) else (
+    echo [Verified] output\082\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 86
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 86
+python run_pipeline.py --index 86
+if not exist "output\087\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 86
+) else (
+    echo [Verified] output\087\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 91
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 91
+python run_pipeline.py --index 91
+if not exist "output\092\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 91
+) else (
+    echo [Verified] output\092\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 96
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 96
+python run_pipeline.py --index 96
+if not exist "output\097\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 96
+) else (
+    echo [Verified] output\097\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo Worker Group 2 finished!
+pause

+ 286 - 0
examples/process_pipeline/worker_3.bat

@@ -0,0 +1,286 @@
+@echo off
+cd /d %~dp0
+if exist ..\..\.venv\Scripts\activate.bat call ..\..\.venv\Scripts\activate.bat
+echo Worker Group 3 starting...
+echo =======================================
+echo Running Requirement Index: 2
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 2
+python run_pipeline.py --index 2
+if not exist "output\003\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 2
+) else (
+    echo [Verified] output\003\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 7
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 7
+python run_pipeline.py --index 7
+if not exist "output\008\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 7
+) else (
+    echo [Verified] output\008\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 12
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 12
+python run_pipeline.py --index 12
+if not exist "output\013\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 12
+) else (
+    echo [Verified] output\013\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 17
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 17
+python run_pipeline.py --index 17
+if not exist "output\018\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 17
+) else (
+    echo [Verified] output\018\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 22
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 22
+python run_pipeline.py --index 22
+if not exist "output\023\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 22
+) else (
+    echo [Verified] output\023\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 27
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 27
+python run_pipeline.py --index 27
+if not exist "output\028\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 27
+) else (
+    echo [Verified] output\028\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 32
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 32
+python run_pipeline.py --index 32
+if not exist "output\033\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 32
+) else (
+    echo [Verified] output\033\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 37
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 37
+python run_pipeline.py --index 37
+if not exist "output\038\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 37
+) else (
+    echo [Verified] output\038\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 42
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 42
+python run_pipeline.py --index 42
+if not exist "output\043\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 42
+) else (
+    echo [Verified] output\043\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 47
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 47
+python run_pipeline.py --index 47
+if not exist "output\048\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 47
+) else (
+    echo [Verified] output\048\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 52
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 52
+python run_pipeline.py --index 52
+if not exist "output\053\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 52
+) else (
+    echo [Verified] output\053\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 57
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 57
+python run_pipeline.py --index 57
+if not exist "output\058\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 57
+) else (
+    echo [Verified] output\058\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 62
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 62
+python run_pipeline.py --index 62
+if not exist "output\063\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 62
+) else (
+    echo [Verified] output\063\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 67
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 67
+python run_pipeline.py --index 67
+if not exist "output\068\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 67
+) else (
+    echo [Verified] output\068\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 72
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 72
+python run_pipeline.py --index 72
+if not exist "output\073\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 72
+) else (
+    echo [Verified] output\073\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 77
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 77
+python run_pipeline.py --index 77
+if not exist "output\078\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 77
+) else (
+    echo [Verified] output\078\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 82
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 82
+python run_pipeline.py --index 82
+if not exist "output\083\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 82
+) else (
+    echo [Verified] output\083\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 87
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 87
+python run_pipeline.py --index 87
+if not exist "output\088\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 87
+) else (
+    echo [Verified] output\088\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 92
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 92
+python run_pipeline.py --index 92
+if not exist "output\093\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 92
+) else (
+    echo [Verified] output\093\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 97
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 97
+python run_pipeline.py --index 97
+if not exist "output\098\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 97
+) else (
+    echo [Verified] output\098\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo Worker Group 3 finished!
+pause

+ 286 - 0
examples/process_pipeline/worker_4.bat

@@ -0,0 +1,286 @@
+@echo off
+cd /d %~dp0
+if exist ..\..\.venv\Scripts\activate.bat call ..\..\.venv\Scripts\activate.bat
+echo Worker Group 4 starting...
+echo =======================================
+echo Running Requirement Index: 3
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 3
+python run_pipeline.py --index 3
+if not exist "output\004\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 3
+) else (
+    echo [Verified] output\004\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 8
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 8
+python run_pipeline.py --index 8
+if not exist "output\009\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 8
+) else (
+    echo [Verified] output\009\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 13
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 13
+python run_pipeline.py --index 13
+if not exist "output\014\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 13
+) else (
+    echo [Verified] output\014\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 18
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 18
+python run_pipeline.py --index 18
+if not exist "output\019\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 18
+) else (
+    echo [Verified] output\019\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 23
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 23
+python run_pipeline.py --index 23
+if not exist "output\024\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 23
+) else (
+    echo [Verified] output\024\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 28
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 28
+python run_pipeline.py --index 28
+if not exist "output\029\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 28
+) else (
+    echo [Verified] output\029\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 33
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 33
+python run_pipeline.py --index 33
+if not exist "output\034\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 33
+) else (
+    echo [Verified] output\034\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 38
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 38
+python run_pipeline.py --index 38
+if not exist "output\039\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 38
+) else (
+    echo [Verified] output\039\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 43
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 43
+python run_pipeline.py --index 43
+if not exist "output\044\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 43
+) else (
+    echo [Verified] output\044\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 48
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 48
+python run_pipeline.py --index 48
+if not exist "output\049\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 48
+) else (
+    echo [Verified] output\049\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 53
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 53
+python run_pipeline.py --index 53
+if not exist "output\054\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 53
+) else (
+    echo [Verified] output\054\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 58
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 58
+python run_pipeline.py --index 58
+if not exist "output\059\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 58
+) else (
+    echo [Verified] output\059\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 63
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 63
+python run_pipeline.py --index 63
+if not exist "output\064\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 63
+) else (
+    echo [Verified] output\064\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 68
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 68
+python run_pipeline.py --index 68
+if not exist "output\069\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 68
+) else (
+    echo [Verified] output\069\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 73
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 73
+python run_pipeline.py --index 73
+if not exist "output\074\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 73
+) else (
+    echo [Verified] output\074\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 78
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 78
+python run_pipeline.py --index 78
+if not exist "output\079\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 78
+) else (
+    echo [Verified] output\079\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 83
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 83
+python run_pipeline.py --index 83
+if not exist "output\084\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 83
+) else (
+    echo [Verified] output\084\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 88
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 88
+python run_pipeline.py --index 88
+if not exist "output\089\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 88
+) else (
+    echo [Verified] output\089\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 93
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 93
+python run_pipeline.py --index 93
+if not exist "output\094\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 93
+) else (
+    echo [Verified] output\094\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo =======================================
+echo Running Requirement Index: 98
+echo =======================================
+echo [Attempt 1] python run_pipeline.py --index 98
+python run_pipeline.py --index 98
+if not exist "output\099\strategy.json" (
+    echo =======================================
+    echo [Retry] target.json missing, retrying once...
+    echo =======================================
+    python run_pipeline.py --index 98
+) else (
+    echo [Verified] output\099\strategy.json generated successfully.
+)
+timeout /t 2 > NUL
+echo Worker Group 4 finished!
+pause

+ 930 - 0
examples/process_research/aigc_architecture.html

@@ -0,0 +1,930 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>AIGC 自动化工作流与知识库闭环架构</title>
+    <link
+        href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Noto+Sans+SC:wght@300;400;500;700&display=swap"
+        rel="stylesheet">
+    <style>
+        :root {
+            --bg: #09090b;
+            --card-bg: rgba(255, 255, 255, 0.03);
+            --card-border: rgba(255, 255, 255, 0.1);
+            --text-primary: #f4f4f5;
+            --text-secondary: #a1a1aa;
+
+            --accent-cyan: #06b6d4;
+            --accent-purple: #8b5cf6;
+            --accent-pink: #ec4899;
+            --accent-green: #10b981;
+            --accent-amber: #f59e0b;
+            --accent-red: #ef4444;
+            --accent-blue: #3b82f6;
+
+            --glow: 0 0 20px rgba(6, 182, 212, 0.3);
+        }
+
+        * {
+            box-sizing: border-box;
+            margin: 0;
+            padding: 0;
+        }
+
+        body {
+            font-family: 'Outfit', 'Noto Sans SC', sans-serif;
+            background-color: var(--bg);
+            color: var(--text-primary);
+            min-height: 100vh;
+            overflow-x: hidden;
+            background-image:
+                radial-gradient(circle at 15% 50%, rgba(139, 92, 246, 0.08) 0%, transparent 50%),
+                radial-gradient(circle at 85% 30%, rgba(6, 182, 212, 0.08) 0%, transparent 50%);
+        }
+
+        .container {
+            max-width: 1200px;
+            margin: 0 auto;
+            padding: 40px 20px;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+        }
+
+        .header {
+            text-align: center;
+            margin-bottom: 60px;
+            animation: fadeInDown 1s ease-out;
+        }
+
+        .header h1 {
+            font-size: 2.5rem;
+            font-weight: 800;
+            background: linear-gradient(135deg, #fff, #a1a1aa);
+            -webkit-background-clip: text;
+            -webkit-text-fill-color: transparent;
+            margin-bottom: 10px;
+            letter-spacing: 1px;
+        }
+
+        .header p {
+            color: var(--text-secondary);
+            font-size: 1.1rem;
+            max-width: 600px;
+            margin: 0 auto;
+        }
+
+        /* Flowchart Flow */
+        .flow-container {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            position: relative;
+            width: 100%;
+        }
+
+        /* Connecting vertical line */
+        .flow-container::before {
+            content: '';
+            position: absolute;
+            top: 20px;
+            bottom: 50px;
+            left: 50%;
+            transform: translateX(-50%);
+            width: 2px;
+            background: linear-gradient(to bottom, var(--card-border) 40%, var(--card-border) 60%, transparent 100%);
+            background-size: 100% 20px;
+            z-index: 0;
+            box-shadow: var(--glow);
+        }
+
+        .layer {
+            position: relative;
+            width: 100%;
+            display: flex;
+            justify-content: center;
+            margin-bottom: 60px;
+            /* reduced from 80px to fit more steps */
+            z-index: 1;
+            opacity: 0;
+            transform: translateY(30px);
+            animation: fadeInUp 0.8s forwards;
+        }
+
+        .layer:nth-child(2) {
+            animation-delay: 0.15s;
+        }
+
+        .layer:nth-child(3) {
+            animation-delay: 0.3s;
+        }
+
+        .layer:nth-child(4) {
+            animation-delay: 0.45s;
+        }
+
+        .layer:nth-child(5) {
+            animation-delay: 0.6s;
+        }
+
+        .layer:nth-child(6) {
+            animation-delay: 0.75s;
+        }
+
+        .node {
+            background: var(--card-bg);
+            border: 1px solid var(--card-border);
+            border-radius: 16px;
+            padding: 24px 32px;
+            backdrop-filter: blur(12px);
+            -webkit-backdrop-filter: blur(12px);
+            text-align: center;
+            position: relative;
+            transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+            min-width: 320px;
+            max-width: 480px;
+        }
+
+        .node:hover {
+            transform: translateY(-5px) scale(1.02);
+            background: rgba(255, 255, 255, 0.05);
+            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
+        }
+
+        /* Accent glows for different node types */
+        .node.node-req {
+            border-top: 3px solid var(--accent-cyan);
+        }
+
+        .node.node-search {
+            border-top: 3px solid var(--accent-blue);
+        }
+
+        .node.node-process {
+            border-top: 3px solid var(--accent-purple);
+        }
+
+        .node.node-pipeline {
+            border-top: 3px solid var(--accent-pink);
+        }
+
+        .node.node-eval {
+            border-top: 3px solid var(--accent-amber);
+        }
+
+        .node:hover.node-req {
+            box-shadow: 0 0 20px rgba(6, 182, 212, 0.2);
+        }
+
+        .node:hover.node-search {
+            box-shadow: 0 0 20px rgba(59, 130, 246, 0.2);
+        }
+
+        .node:hover.node-process {
+            box-shadow: 0 0 20px rgba(139, 92, 246, 0.2);
+        }
+
+        .node:hover.node-pipeline {
+            box-shadow: 0 0 20px rgba(236, 72, 153, 0.2);
+        }
+
+        .node:hover.node-eval {
+            box-shadow: 0 0 20px rgba(245, 158, 11, 0.2);
+        }
+
+        .node-title {
+            font-size: 1.25rem;
+            font-weight: 700;
+            margin-bottom: 12px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 8px;
+        }
+
+        .node-desc {
+            font-size: 0.9rem;
+            color: var(--text-secondary);
+            line-height: 1.6;
+        }
+
+        /* Side Branches / Split Layouts */
+        .split-col {
+            display: flex;
+            gap: 40px;
+            align-items: stretch;
+            justify-content: center;
+            width: 100%;
+        }
+
+        .sub-node-box {
+            background: rgba(0, 0, 0, 0.2);
+            border: 1px dashed var(--card-border);
+            border-radius: 12px;
+            padding: 16px;
+            margin-top: 20px;
+            text-align: left;
+        }
+
+        .sub-list {
+            list-style: none;
+            margin-top: 10px;
+        }
+
+        .sub-list li {
+            font-size: 0.85rem;
+            color: var(--text-secondary);
+            margin-bottom: 8px;
+            display: flex;
+            align-items: center;
+            gap: 6px;
+        }
+
+        .icon-emoji {
+            font-size: 1.2rem;
+            filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.2));
+        }
+
+        /* Abstract structural capabilities styling */
+        .ability-circles {
+            display: flex;
+            justify-content: center;
+            gap: 12px;
+            margin: 15px 0;
+        }
+
+        .ability-circle {
+            width: 32px;
+            height: 32px;
+            border-radius: 50%;
+            border: 2px solid var(--accent-purple);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 0.8rem;
+            color: #fff;
+            background: rgba(139, 92, 246, 0.1);
+        }
+
+        .ability-circle.solid {
+            background: var(--accent-purple);
+            border-style: solid;
+            box-shadow: 0 0 10px rgba(139, 92, 246, 0.4);
+        }
+
+        .ability-circle.outline {
+            background: transparent;
+            border-style: dashed;
+            border-color: var(--accent-cyan);
+            color: var(--accent-cyan);
+        }
+
+        .ability-circle.half {
+            background: linear-gradient(135deg, var(--accent-purple) 50%, transparent 50%);
+        }
+
+        .case-square {
+            width: 28px;
+            height: 28px;
+            border-radius: 4px;
+            border: 1px solid var(--accent-purple);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 0.75rem;
+            color: #fff;
+            background: rgba(139, 92, 246, 0.2);
+            transition: all 0.2s;
+        }
+
+        .case-square:hover {
+            transform: translateY(-2px);
+            background: rgba(139, 92, 246, 0.4);
+        }
+
+        .process-pipeline {
+            display: flex;
+            align-items: flex-start;
+            justify-content: center;
+            gap: 12px;
+            margin-top: 25px;
+            padding: 15px;
+            background: rgba(0, 0, 0, 0.2);
+            border: 1px dashed var(--card-border);
+            border-radius: 12px;
+            position: relative;
+        }
+
+        .pipeline-step {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            gap: 6px;
+        }
+
+        .pipeline-arrow {
+            margin-top: 8px;
+            /* align with abilities */
+            color: var(--accent-purple);
+            font-weight: bold;
+        }
+
+        .case-cluster {
+            display: flex;
+            gap: 4px;
+            flex-wrap: wrap;
+            justify-content: center;
+            width: 60px;
+        }
+
+        .v-line {
+            width: 1px;
+            height: 15px;
+            background-color: var(--card-border);
+            position: relative;
+        }
+
+        .v-line.extract {
+            background-color: var(--accent-cyan);
+            border-left: 1px dashed var(--accent-cyan);
+            width: 0;
+        }
+
+        .v-line-label {
+            font-size: 0.65rem;
+            color: var(--text-secondary);
+            margin-bottom: -4px;
+        }
+
+        .v-line-label.extract {
+            color: var(--accent-cyan);
+        }
+
+        .pipeline-grid {
+            display: grid;
+            grid-template-columns: auto 20px 40px 30px 1fr;
+            align-items: center;
+            row-gap: 5px;
+            margin-top: 15px;
+            text-align: left;
+        }
+
+        .pipeline-grid .cases {
+            justify-self: end;
+            display: flex;
+            gap: 4px;
+        }
+
+        .pipeline-grid .dash {
+            justify-self: center;
+            color: var(--text-secondary);
+            font-size: 0.8rem;
+        }
+
+        .pipeline-grid .ability {
+            justify-self: center;
+        }
+
+        .pipeline-grid .impl {
+            justify-self: center;
+            padding: 8px 16px;
+            border-radius: 8px;
+            font-size: 0.85rem;
+            background: rgba(236, 72, 153, 0.15);
+            border: 1px solid var(--accent-pink);
+            color: #fff;
+            min-width: 180px;
+            text-align: center;
+        }
+
+        .pipeline-grid .impl.dashed {
+            background: rgba(236, 72, 153, 0.05);
+            border: 1px dashed var(--accent-pink);
+            color: var(--text-secondary);
+        }
+
+        .pipeline-grid .v-link {
+            justify-self: center;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            height: 25px;
+            color: var(--text-secondary);
+            font-size: 10px;
+        }
+
+        .pipeline-grid .v-link::before {
+            content: '';
+            width: 2px;
+            flex: 1;
+            background: var(--text-secondary);
+            margin-bottom: 2px;
+        }
+
+        .pipeline-grid .v-link.impl-side {
+            align-items: center;
+            justify-self: center;
+            margin-left: 0;
+        }
+
+        /* specific connecting arrows */
+        .connector-label {
+            position: absolute;
+            font-size: 0.95rem;
+            font-weight: 500;
+            color: var(--accent-cyan);
+            background: var(--bg);
+            padding: 4px 12px;
+            border-radius: 6px;
+            border: 1px solid var(--accent-cyan);
+            box-shadow: 0 0 15px rgba(6, 182, 212, 0.3);
+            white-space: nowrap;
+            z-index: 2;
+        }
+
+        /* Specific placements for the eval loop */
+        .eval-success {
+            color: var(--accent-green) !important;
+            border-color: var(--accent-green) !important;
+        }
+
+        .eval-fail {
+            color: var(--accent-red) !important;
+            border-color: var(--accent-red) !important;
+        }
+
+        @keyframes fadeInUp {
+            to {
+                opacity: 1;
+                transform: translateY(0);
+            }
+        }
+
+        @keyframes fadeInDown {
+            from {
+                opacity: 0;
+                transform: translateY(-30px);
+            }
+
+            to {
+                opacity: 1;
+                transform: translateY(0);
+            }
+        }
+
+        /* Pulse for the execution block */
+        .pulse {
+            animation: pulse-border 2s infinite;
+        }
+
+        @keyframes pulse-border {
+            0% {
+                box-shadow: 0 0 0 0 rgba(6, 182, 212, 0.4);
+            }
+
+            70% {
+                box-shadow: 0 0 0 15px rgba(6, 182, 212, 0);
+            }
+
+            100% {
+                box-shadow: 0 0 0 0 rgba(6, 182, 212, 0);
+            }
+        }
+    </style>
+</head>
+
+<body>
+
+    <div class="container">
+        <div class="header">
+            <h1>系统架构图流转拓扑</h1>
+            <p>AIGC 自动化任务拆解与知识库演进全景图</p>
+        </div>
+
+        <div class="flow-container">
+
+            <!-- Step 1: Requirements Extraction & DB Match -->
+            <div class="layer">
+                <div style="display: flex; justify-content: center; width: 100%; gap: 60px; position: relative;">
+                    
+                    <div class="node node-req" style="flex: 1; max-width: 400px;">
+                        <div class="node-title">
+                            <span class="icon-emoji">📥</span> 提取需求 (Requirements)
+                        </div>
+                        <div class="node-desc" style="line-height: 1.6;">
+                            从原始的帖子、还原任务或用户指令中剖析意图。<br>
+                            <span style="color:var(--text-secondary); font-size: 0.85rem;">(明确用户的核心诉求与待解任务)</span>
+                        </div>
+                    </div>
+
+                    <div class="node node-eval" style="flex: 1; max-width: 350px; border-top-color: var(--accent-green); display: flex; flex-direction: column; justify-content: center; position: relative;">
+                        <!-- Connector arrow pointing left and right -->
+                        <div class="connector-label" style="left: -30px; top: 50%; transform: translate(-50%, -50%); z-index: 10;">⟷ 对齐 / 拦截</div>
+                        
+                        <div class="node-title">
+                            <span class="icon-emoji">📂</span> 已有需求库 (Req DB)
+                        </div>
+                        <div class="node-desc">匹配 db_requirements.json 树状分布</div>
+                        <div style="margin-top: 12px; font-size: 0.8rem; background: rgba(16, 185, 129, 0.1); padding: 8px; border-radius: 6px; border: 1px dashed rgba(16, 185, 129, 0.5);">
+                            <span style="color: var(--accent-green); font-weight: bold; display: flex; align-items: center; gap: 6px;">
+                                <span style="font-size: 1.1rem;">⚡</span>
+                                <span>若已实现: 跳出循环,直接复用底层 Pipeline</span>
+                            </span>
+                        </div>
+                    </div>
+
+                </div>
+            </div>
+
+            <!-- Step 2: Research and Case Collection -->
+            <div class="layer">
+                <div class="node node-search" style="margin-top: 20px;">
+                    <div class="connector-label" style="top: -34px; left: 50%; transform: translateX(-50%);">根据新需求/未满足需求 ⬇</div>
+                    <div class="node-title">
+                        <span class="icon-emoji">🔍</span> 调研核心 Case
+                    </div>
+                    <div class="node-desc">
+                        面向全网或本地库,定向并发检索大量真实场景参考。<br>
+                        获取高度相关的图文案例资料 (<b style="color:var(--accent-blue)">拿到 Case</b>)。
+                    </div>
+                    
+                    <div style="font-size: 0.75rem; color: var(--text-secondary); width: 100%; text-align: center; margin-top: 15px; margin-bottom: 5px;">
+                        <span style="color: var(--accent-blue); font-weight: bold;">■ 网络搜索</span> (外网新知) &nbsp;&nbsp;|&nbsp;&nbsp; 
+                        <span style="color: var(--accent-purple); font-weight: bold;">■ 库内召回</span> (自有知识)
+                    </div>
+                    <div style="display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; padding: 15px; background: rgba(0,0,0,0.2); border-radius: 12px; border: 1px dashed rgba(255, 255, 255, 0.1);">
+                        <!-- Network Cases -->
+                        <div class="case-square" title="网络用例 01" style="background: rgba(59, 130, 246, 0.2); border-color: var(--accent-blue);">C1</div>
+                        <div class="case-square" title="网络用例 02" style="background: rgba(59, 130, 246, 0.2); border-color: var(--accent-blue);">C2</div>
+                        <div class="case-square" title="网络用例 03" style="background: rgba(59, 130, 246, 0.2); border-color: var(--accent-blue);">C3</div>
+                        <div class="case-square" title="网络用例 04" style="background: rgba(59, 130, 246, 0.2); border-color: var(--accent-blue);">C4</div>
+                        
+                        <!-- Internal Cases -->
+                        <div class="case-square" title="库内用例 05" style="background: rgba(139, 92, 246, 0.2); border-color: var(--accent-purple);">C5</div>
+                        <div class="case-square" title="库内用例 06" style="background: rgba(139, 92, 246, 0.2); border-color: var(--accent-purple);">C6</div>
+                        <div class="case-square" title="库内用例 07" style="background: rgba(139, 92, 246, 0.2); border-color: var(--accent-purple);">C7</div>
+                        <div style="color: var(--text-secondary); display: flex; align-items: center; justify-content: center; margin-left: 5px; font-weight: bold;">...</div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- Step 3: Process Design & Ability Extraction -->
+            <div class="layer">
+                <div style="display: flex; flex-direction: column; align-items: center; width: 100%; position: relative;">
+                    <!-- New Flow Label -->
+                    <div class="connector-label" style="top: -65px; left: 50%; transform: translateX(-50%);">解析业务场景特征 ⬇</div>
+
+                    <!-- Top Row: Two Pools -->
+                    <div style="display: flex; gap: 40px; justify-content: center; width: 100%; margin-bottom: 20px;">
+
+                        <!-- Left: Ability Pool -->
+                        <div class="node node-process"
+                            style="flex: 1; max-width: 350px; border-top-color: var(--accent-cyan); display: flex; flex-direction: column;">
+                            <div class="node-title">
+                                <span class="icon-emoji">💡</span> 现有能力池 (Ability Pool)
+                            </div>
+                            <div class="node-desc" style="margin-bottom: 8px;">全局积累的标准原子能力库</div>
+                            
+                            <!-- Legend for Ability Pool -->
+                            <div style="font-size: 0.75rem; color: var(--text-secondary); width: 100%; text-align: center; margin-bottom: 12px;">
+                                <span style="display:inline-flex; align-items:center; gap:6px; margin-right:12px;">
+                                    <div class="ability-circle solid" style="width:16px; height:16px; font-size:0; min-width:16px;"></div> <span style="color:var(--text-primary);">已实现能力</span>
+                                </span>
+                                <span style="display:inline-flex; align-items:center; gap:6px;">
+                                    <div class="ability-circle outline" style="width:16px; height:16px; font-size:0; min-width:16px; border-color:var(--accent-cyan);"></div> <span style="color:var(--accent-cyan);">暂定新能力</span>
+                                </span>
+                            </div>
+
+                            <div
+                                style="display:flex; gap: 8px; flex-wrap:wrap; justify-content:center; padding: 15px; border-radius: 12px; background: rgba(0,0,0,0.3); border: 1px solid var(--card-border);">
+                                <div class="ability-circle solid" title="已实现能力">A</div>
+                                <div class="ability-circle solid" title="已实现能力">C</div>
+                                <div class="ability-circle solid" title="已实现能力">E</div>
+                                <div class="ability-circle solid" title="已实现能力">F</div>
+                                <div class="ability-circle outline" title="暂定新能力" style="border-color: var(--accent-cyan); color: var(--accent-cyan);">B</div>
+                                <div class="ability-circle solid" title="已实现能力">G</div>
+                                <div class="ability-circle solid" title="已实现能力">H</div>
+                                <div class="ability-circle solid" title="已实现能力">I</div>
+                                <div class="ability-circle outline" title="暂定新能力" style="border-color: var(--accent-cyan); color: var(--accent-cyan);">D</div>
+                            </div>
+                        </div>
+
+                        <!-- Right: Process Pool -->
+                        <div class="node node-process"
+                            style="flex: 1; max-width: 350px; border-top-color: var(--accent-amber); display: flex; flex-direction: column;">
+                            <div class="node-title" style="color: var(--text-primary);">
+                                <span class="icon-emoji">📚</span> 结构化工序池
+                            </div>
+                            <div class="node-desc" style="margin-bottom: 15px;">已验证的高分复用工序</div>
+
+                            <div style="display: flex; flex-direction: column; gap: 10px; text-align: left;">
+                                <!-- Process 1 -->
+                                <div
+                                    style="background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.5); padding: 10px; border-radius: 8px;">
+                                    <div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
+                                        <span
+                                            style="font-weight: bold; font-size: 0.85rem; color: #fff;">人物视觉合成工序</span>
+                                        <span
+                                            style="background: var(--accent-amber); color: #000; font-size: 0.7rem; padding: 2px 6px; border-radius: 10px; font-weight: bold;">验证分数:
+                                            8</span>
+                                    </div>
+                                    <div
+                                        style="font-size: 0.75rem; color: var(--text-secondary); display: flex; align-items: center; gap: 6px; margin-top: 5px;">
+                                        包含能力:
+                                        <div
+                                            style="display: flex; align-items: center; gap: 4px; transform: scale(0.8); transform-origin: left center;">
+                                            <div class="ability-circle solid">A</div>
+                                            <div style="color: var(--text-secondary); font-size: 1rem;">➔</div>
+                                            <div class="ability-circle solid">C</div>
+                                            <div style="color: var(--text-secondary); font-size: 1rem;">➔</div>
+                                            <div class="ability-circle solid">E</div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <!-- Process 2 (New) -->
+                                <div
+                                    style="background: rgba(245, 158, 11, 0.05); border: 1px dashed rgba(245, 158, 11, 0.3); padding: 10px; border-radius: 8px;">
+                                    <div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
+                                        <span
+                                            style="font-weight: bold; font-size: 0.85rem; color: var(--text-secondary);">(NEW)
+                                            特效错位新工序</span>
+                                        <span
+                                            style="background: rgba(255, 255, 255, 0.1); color: var(--text-secondary); font-size: 0.7rem; padding: 2px 6px; border-radius: 10px;">初始入库:
+                                            0</span>
+                                    </div>
+                                    <div
+                                        style="font-size: 0.75rem; color: var(--text-secondary); display: flex; align-items: center; gap: 6px; margin-top: 5px;">
+                                        包含能力:
+                                        <div
+                                            style="display: flex; align-items: center; gap: 4px; transform: scale(0.8); transform-origin: left center;">
+                                            <div class="ability-circle solid">A</div>
+                                            <div style="color: var(--text-secondary); font-size: 1rem;">➔</div>
+                                            <div class="ability-circle outline"
+                                                style="border-color: var(--accent-cyan); color: var(--accent-cyan);">B
+                                            </div>
+                                            <div style="color: var(--text-secondary); font-size: 1rem;">➔</div>
+                                            <div class="ability-circle solid">C</div>
+                                            <div style="color: var(--text-secondary); font-size: 1rem;">➔</div>
+                                            <div class="ability-circle outline"
+                                                style="border-color: var(--accent-cyan); color: var(--accent-cyan);">D
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+
+                    <!-- Funnel Arrows -->
+                    <div style="display: flex; justify-content: center; gap: 80px; margin-bottom: -15px; z-index: 10; position: relative;">
+                        <div class="connector-label" style="position: static; font-size: 0.9rem; color: var(--accent-purple); border-color: var(--accent-purple); box-shadow: 0 0 10px rgba(139, 92, 246, 0.2);">
+                            ↘ 提取引用能力
+                        </div>
+                        <div class="connector-label" style="position: static; font-size: 0.9rem; color: var(--accent-amber); border-color: var(--accent-amber); box-shadow: 0 0 10px rgba(245, 158, 11, 0.2);">
+                            复用现成工序 ↙
+                        </div>
+                    </div>
+
+                    <!-- Center Base: Structured Process Builder -->
+                    <div class="node node-process" style="width: 100%; max-width: 500px;">
+                        <div class="node-title">
+                            <span class="icon-emoji">🧩</span> 设计/复用结构化工序
+                        </div>
+                        <div class="node-desc">
+                            参考原有的工序沉淀,结合新 Case 抽取出结构流向。
+                        </div>
+
+                        <div class="process-pipeline">
+                            <div class="connector-label" style="top: -12px; left: 10px; font-weight: normal;">执行链条
+                                (Structured Process)</div>
+
+                            <!-- Step A -->
+                            <div class="pipeline-step">
+                                <div class="ability-circle solid" title="已实现的标准能力 (Implemented)">A</div>
+                                <div class="v-line-label">支撑</div>
+                                <div class="v-line"></div>
+                                <div class="case-cluster">
+                                    <div class="case-square" title="Case 01">C1</div>
+                                    <div class="case-square" title="Case 02">C2</div>
+                                </div>
+                            </div>
+
+                            <div class="pipeline-arrow">➔</div>
+
+                            <!-- Step B -->
+                            <div class="pipeline-step">
+                                <div class="ability-circle outline" title="暂定的新能力 (Tentative)">B</div>
+                                <div class="v-line-label extract">提炼</div>
+                                <div class="v-line extract"></div>
+                                <div class="case-cluster">
+                                    <div class="case-square" title="Case 03">C3</div>
+                                    <div class="case-square" title="Case 04">C4</div>
+                                </div>
+                            </div>
+
+                            <div class="pipeline-arrow">➔</div>
+
+                            <!-- Step C -->
+                            <div class="pipeline-step">
+                                <div class="ability-circle solid" title="已实现的标准能力 (Implemented)">C</div>
+                                <div class="v-line-label">支撑</div>
+                                <div class="v-line"></div>
+                                <div class="case-cluster">
+                                    <div class="case-square" title="Case 05">C5</div>
+                                </div>
+                            </div>
+
+                            <div class="pipeline-arrow">➔</div>
+
+                            <!-- Step D -->
+                            <div class="pipeline-step">
+                                <div class="ability-circle outline" title="暂定的新能力 (Tentative)">D</div>
+                                <div class="v-line-label extract">提炼</div>
+                                <div class="v-line extract"></div>
+                                <div class="case-cluster">
+                                    <div class="case-square" title="Case 06">C6</div>
+                                    <div class="case-square" title="Case 07">C7</div>
+                                </div>
+                            </div>
+                        </div>
+                        <p style="font-size: 0.75rem; text-align:center; color:var(--text-secondary); margin-top:10px;">
+                            (沉淀:由本轮提炼的新能力和新工序将反哺至上方池中)
+                        </p>
+                    </div>
+                </div>
+            </div>
+
+            <!-- Step 4: Pipeline Assembly -->
+            <div class="layer">
+                <div class="node node-pipeline" style="min-width: 500px; margin-top: 20px;">
+                    <div class="connector-label" style="top: -34px; left: 50%; transform: translateX(-50%);">下发执行规范 (JSON) ⬇</div>
+                    <div class="node-title">
+                        <span class="icon-emoji">⚙️</span> 设计可执行 Pipeline
+                    </div>
+                    <div class="node-desc" style="margin-bottom: 20px;">
+                        将抽象能力序列下沉为具体的底层节点调用组
+                    </div>
+
+                    <div
+                        style="background: rgba(0,0,0,0.3); padding: 20px; border-radius: 12px; border: 1px dashed var(--accent-pink); position: relative;">
+                        <!-- Header -->
+                        <div
+                            style="display: flex; justify-content: space-between; font-size: 0.8rem; color: var(--text-secondary); margin-bottom: 5px; padding-bottom: 8px; border-bottom: 1px solid var(--card-border);">
+                            <div style="flex: 1; text-align: left;">能力序列 (with Cases)</div>
+                            <div style="flex: 1; text-align: right;">真实工具表 (素材/要素)</div>
+                        </div>
+
+                        <div class="pipeline-grid" style="position: relative;">
+                            <!-- Divider Line -->
+                            <div
+                                style="position: absolute; left: 50%; top: -15px; bottom: 0; width: 1px; background: linear-gradient(to bottom, transparent, var(--card-border) 10%, transparent); transform: translateX(-50%); z-index: 0;">
+                            </div>
+
+                            <!-- Row 1 -->
+                            <div class="cases" style="z-index: 1;">
+                                <div class="case-square" title="Case 01">C1</div>
+                                <div class="case-square" title="Case 02">C2</div>
+                            </div>
+                            <div class="dash" style="z-index: 1;">一</div>
+                            <div class="ability" style="z-index: 1;">
+                                <div class="ability-circle solid" title="已实现的标准能力 (Implemented)">A</div>
+                            </div>
+                            <div class="dash" style="z-index: 1;">一</div>
+                            <div class="impl" style="z-index: 1;">implements (确定工具)</div>
+
+                            <!-- V-Link Row 1 -->
+                            <div></div>
+                            <div></div>
+                            <div class="v-link">⬇</div>
+                            <div></div>
+                            <div class="v-link impl-side">⬇</div>
+
+                            <!-- Row 2 -->
+                            <div class="cases" style="z-index: 1;">
+                                <div class="case-square" title="Case 03">C3</div>
+                                <div class="case-square" title="Case 04">C4</div>
+                            </div>
+                            <div class="dash" style="z-index: 1;">一</div>
+                            <div class="ability" style="z-index: 1;">
+                                <div class="ability-circle outline" title="暂定的新能力 (Tentative)">B</div>
+                            </div>
+                            <div class="dash" style="z-index: 1;">一</div>
+                            <div class="impl dashed" style="z-index: 1;">implements_candidates (已有工具替代)</div>
+
+                            <!-- V-Link Row 2 -->
+                            <div></div>
+                            <div></div>
+                            <div class="v-link">⬇</div>
+                            <div></div>
+                            <div class="v-link impl-side">⬇</div>
+
+                            <!-- Row 3 -->
+                            <div class="cases" style="z-index: 1;">
+                                <div class="case-square" title="Case 05">C5</div>
+                            </div>
+                            <div class="dash" style="z-index: 1;">一</div>
+                            <div class="ability" style="z-index: 1;">
+                                <div class="ability-circle solid" title="已实现的标准能力 (Implemented)">C</div>
+                            </div>
+                            <div class="dash" style="z-index: 1;">一</div>
+                            <div class="impl" style="z-index: 1;">im...</div>
+
+                            <!-- V-Link dots -->
+                            <div></div>
+                            <div></div>
+                            <div class="v-link" style="font-size: 14px; margin-top: -5px; color: var(--card-border);">⋮
+                            </div>
+                            <div></div>
+                            <div class="v-link impl-side"
+                                style="font-size: 14px; margin-top: -5px; color: var(--card-border);">⋮</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- Step 5: Execution & Evaluate -->
+            <div class="layer">
+                <div class="split-col" style="align-items: flex-start; position: relative;">
+                    <!-- New Flow Label -->
+                    <div class="connector-label" style="top: -65px; left: 50%; transform: translateX(-50%);">投递至底层 Agent 引擎 ⬇</div>
+
+                    <!-- Left Branch: Failure / Refactoring -->
+                    <div class="node node-eval" style="border-top-color: var(--accent-red);">
+                        <div class="connector-label eval-fail" style="top: -30px; right: 0;">⬅ 失败反馈 (Fail)</div>
+                        <div class="node-title" style="color: var(--accent-red)">
+                            <span class="icon-emoji">🔧</span> 工序重构
+                        </div>
+                        <div class="node-desc">
+                            <strong>重试优先级机制</strong>
+                        </div>
+                        <ul class="sub-list" style="margin-top:15px; text-align: left;">
+                            <li><span style="color:var(--accent-red)">[1] 替换出错底层工具</span><br>
+                                <span style="font-size: 0.75rem; color: var(--text-secondary); margin-left: 18px;">⤤ 短路回退 <b>Step 4</b> (更换 API / 代码逻辑)</span>
+                            </li>
+                            <li style="margin-top: 8px;"><span style="color:var(--accent-red)">[2] 微调能力序列节点</span><br>
+                                <span style="font-size: 0.75rem; color: var(--text-secondary); margin-left: 18px;">⤤ 中度回退 <b>Step 3</b> (删减 / 新增组装原子)</span>
+                            </li>
+                            <li style="margin-top: 8px;"><span style="color:var(--accent-red)">[3] 彻底打翻工序结构</span><br>
+                                <span style="font-size: 0.75rem; color: var(--text-secondary); margin-left: 18px;">⤤ 深度回退 <b>Step 2</b> (重新提取用例与新能力)</span>
+                            </li>
+                        </ul>
+                    </div>
+
+                    <!-- Center Core: Execution Action -->
+                    <div class="node pulse" style="border:2px solid var(--accent-cyan); min-width: 200px; background: rgba(6, 182, 212, 0.05);">
+                        <div class="node-title" style="color: var(--accent-cyan);">
+                            <span class="icon-emoji">🚀</span> 实际执行 Pipeline
+                        </div>
+                        <div class="node-desc">
+                            在底层投递执行并产出最终成果<br><br>
+                            ⬇<br>
+                            <b style="font-size: 1.2rem; color: #fff; text-shadow: 0 0 10px rgba(255,255,255,0.5);">Result (核心产出结果)</b>
+                        </div>
+                        <br>
+                        <div
+                            style="background:var(--accent-amber); color:#000; padding:8px 0; border-radius:8px; font-weight:bold; width:80%; margin: 0 auto; box-shadow: 0 0 15px rgba(245, 158, 11, 0.4);">
+                            双向评估判定 (Evaluate)
+                        </div>
+                    </div>
+
+                    <!-- Right Branch: Success / Accumulation -->
+                    <div class="node node-eval" style="border-top-color: var(--accent-green);">
+                        <div class="connector-label eval-success" style="top: -30px; left: 0;">成功通过 (Success) ➡</div>
+                        <div class="node-title" style="color: var(--accent-green)">
+                            <span class="icon-emoji">✨</span> 系统底座升维
+                        </div>
+                        <div class="node-desc">自动沉淀知识体系资产</div>
+                        <div class="sub-node-box"
+                            style="border-color: rgba(16, 185, 129, 0.3); background: rgba(16, 185, 129, 0.05); text-align: center;">
+                            <div style="margin-bottom: 12px; font-size: 0.95rem;">✅ <b>新能力转为 Implemented</b></div>
+                            <div style="font-size: 0.95rem; line-height: 1.5;">✅ <b>完整执行流沉淀为自有 Case 入库</b><br>
+                                <span style="font-size: 0.75rem; color: #10B981; font-weight: normal; opacity: 0.9;">(反哺给 Step 1 实现拦截复用)</span>
+                            </div>
+                        </div>
+                        <div
+                            style="margin-top: 16px; font-weight: bold; background: linear-gradient(135deg, var(--accent-green), var(--accent-cyan)); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
+                            🚀 结构化工序 (Pipeline) 效能评分 +1
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+        </div>
+    </div>
+
+    <script>
+        document.querySelectorAll('.node').forEach((el, index) => {
+            el.addEventListener('mouseenter', () => {
+                el.style.transform = 'translateY(-5px) scale(1.02)';
+            });
+            el.addEventListener('mouseleave', () => {
+                el.style.transform = 'translateY(0) scale(1)';
+            });
+        });
+    </script>
+</body>
+
+</html>

+ 66 - 0
examples/process_research/config.py

@@ -0,0 +1,66 @@
+"""
+项目配置
+"""
+
+from agent.core.runner import KnowledgeConfig, RunConfig
+
+
+# ===== 主 Agent 配置(Coordinator,负责分发调研方向 + 汇总策略)=====
+
+# 是否开启子 Agent 真并发调研(极大榨取网络 IO 速度)
+PARALLEL_TOOL_EXECUTION = True
+
+COORDINATOR_RUN_CONFIG = RunConfig(
+    model="qwen-plus",
+    temperature=0.3,
+    max_iterations=800,
+    parallel_tool_execution=PARALLEL_TOOL_EXECUTION,
+    extra_llm_params={},
+    agent_type="coordinator",
+    name="工序调研协调器",
+    knowledge=KnowledgeConfig(
+        enable_extraction=False,
+        enable_completion_extraction=False,
+        enable_injection=False,
+        owner="sunlit.howard@gmail.com",
+    )
+)
+
+
+# ===== 输出目录 =====
+
+OUTPUT_DIR = "examples/process_research/output"   # 每个需求输出到 output/{N}/
+
+
+
+# ===== 基础设施配置 =====
+
+SKILLS_DIR = "./skills"
+TRACE_STORE_PATH = ".trace"
+DEBUG = True
+LOG_LEVEL = "INFO"
+LOG_FILE = None
+
+# ===== 浏览器配置 =====
+# 当 PARALLEL_TOOL_EXECUTION=True 时,强烈建议使用 cloud(云浏览器)或 headless,否则本地会出现抢鼠标冲突!
+BROWSER_TYPE = "local" if not PARALLEL_TOOL_EXECUTION else "cloud"
+HEADLESS = False
+
+# ===== IM 配置 =====
+IM_ENABLED = True
+IM_CONTACT_ID = "agent_research"
+IM_SERVER_URL = "ws://43.106.118.91:8105"
+IM_WINDOW_MODE = True
+IM_NOTIFY_INTERVAL = 10.0
+
+# ===== 远端 Research Agent 配置 =====
+# 此 pipeline 的 researcher 子 Agent 需要写文件到本地 output_dir,
+# 因此必须使用本地模式(USE_REMOTE_RESEARCH = False)。
+# 远端模式(True)仅适用于不需要写本地文件的单体 Agent 场景。
+USE_REMOTE_RESEARCH = False
+
+# 线上 KnowHub 地址(也可通过 .env 的 KNOWHUB_API 变量覆盖)
+# subagent._run_remote_agent 运行时会读取 KNOWHUB_API 环境变量
+# 这里显式设置以便 config 层可见
+import os as _os
+_os.environ.setdefault("KNOWHUB_API", "http://43.106.118.91:9999")

+ 101 - 0
examples/process_research/db_requirements.json

@@ -0,0 +1,101 @@
+[
+  "生成人物在不同场景下呈现丰富面部表情的图片,例如夸张的痛苦、无奈、开心、困倦等神态,表情要生动传神、情绪感强烈",
+  "生成人物与道具、环境或其他角色发生互动的画面,例如人物摆弄物品、与道具合影、在特定场景中做出配合动作等,画面要体现人物和周围元素之间的关联感",
+  "生成将动物(如猫咪)拟人化扮演特定角色或情境的图片,赋予其人类的表情、姿态和道具,用来传达幽默或情感共鸣的视觉效果",
+  "生成带有特定道具装扮的人物场景图,道具需与人物自然融合,例如猫咪戴假发穿衣服手持书本、人物手持购物篮抱着玩偶玩具等,道具细节清晰可辨",
+  "生成婚礼或节日庆典场景,背景需包含大量花卉装饰、定制发光字牌、喜字等布景元素,整体氛围感强烈,道具与场景协调统一",
+  "生成精致室内空间场景,画面中需呈现陶瓷器皿、绿植、家具等道具摆件,光线自然柔和,营造出温馨生活感或高颜值家居氛围",
+  "生成真实人物在户外或特定场景中的生活记录照片,画面自然真实,包含儿童在公园、农场等户外环境中玩耍的多角度抓拍效果,光线自然,氛围温馨",
+  "制作将真实人物照片合成到趣味场景中的创意图片,例如把人物缩小放入超市肉类托盘包装内、或与冰雕翅膀等道具结合形成视觉错位的幽默效果",
+  "生成真实场景的多图拼贴展示图,将同一地点或主题的多张实拍照片拼合为一张图文并茂的内容图,适合用于地点打卡、产品展示或生活记录类帖子",
+  "制作多格宫格式信息图,将同类内容(如多种食材搭配方案)拆分为统一风格的小卡片,每格包含标题、食材图片和文字说明,整体排列整齐、色块鲜明,适合一图展示多个并列条目",
+  "在图片上叠加标注元素,如用红色圆点、箭头或emoji符号指向图中特定位置,配合说明文字,实现在真实照片上直观标记关键信息的视觉效果",
+  "制作图文混排的长图文内容,将大段文字与人物照片、数据表格、流程图等多种视觉元素组合排布在同一版面中,形成类似杂志或报告的专业排版风格",
+  "生成以暖黄/米棕色为背景底色的图文排版内容,整体画面呈现温暖、复古的暖色调氛围,适合健康养生、生活方式类主题",
+  "生成以深色(黑色/深蓝/深紫)为背景底色的海报,搭配霓虹感彩色光效(橙、紫、青等),营造出科技感强烈的冷暖对比配色效果",
+  "生成整体色调偏粉紫、薄荷绿、浅蓝等低饱和度冷色系的插画或场景图,画面呈现出梦幻、静谧的冷色调氛围,颜色搭配柔和克制",
+  "生成手部持握或展示物品的特写画面,手势自然,物品清晰呈现,如用手托举饺子、手持卡片、手握手机等,突出手与物品的互动关系",
+  "生成多人或多只动物同框的协同姿态画面,如两只猫并排躺着穿睡衣、两人一起摆造型抱花束,呈现出同步、对称或互动的视觉效果",
+  "生成创意性的嘴部动作特写,如用嘴唇衔住花朵茎部形成'嘴唇花'的视觉效果,将身体部位与物品结合产生趣味创意画面",
+  "将人物照片与中国传统吉祥符号(如双喜字、红玫瑰、金色祝福文字)融合,生成具有强烈喜庆氛围的定制化图案,人物面孔清晰嵌入红色喜庆背景中",
+  "制作统一模板风格的系列信息卡片,每张卡片包含固定的图标符号(如皇冠等级图标)、彩色标题文字和配图,整体视觉风格一致、可批量复用",
+  "生成黑色科技感背景的人物宣传海报,背景带有流光线条或霓虹光效,人物照片与品牌Logo、活动标识、二维码等视觉元素整齐排布,形成高辨识度的系列展示图",
+  "生成宠物或动物穿戴人类服饰配件(如帽子、围巾)的画面,让动物看起来像在过节或扮演某种角色,整体效果可爱又有趣",
+  "生成将普通服装或日常物品以夸张搞怪方式穿戴的人物画面,比如把超大号短裤当长袍穿、用篮球短裤模仿古希腊长袍,产生强烈的视觉反差和幽默效果",
+  "生成具有超现实风格的创意合成画面,将人物头部替换为宇宙星云、太极图、粒子爆炸等抽象元素,配合深蓝红色调背景,营造出哲学感或科幻感的视觉冲击",
+  "生成穿着完整冬季搭配的人物形象,展示黑色羽绒服、红色围巾、红色手套、宽腿裤等单品的组合穿搭效果,呈现从全身到局部细节的多角度造型展示",
+  "生成宠物穿着服装的可爱造型图,展示猫咪穿上印花连体衣的整体穿着效果,需要清晰呈现服装的图案、版型与宠物身体的贴合细节",
+  "生成创意合成图,将人物穿搭形象嵌入特定场景容器中(如超市生鲜托盘),使人物服装与场景产生趣味性视觉对比,同时保留服装细节的清晰可见",
+  "将猫咪表情包图片与各种场景素材(办公室、食物、产品、背景环境等)合成拼贴在一起,让猫咪看起来自然地处于这些场景中,形成多格并排的拼贴版式",
+  "把猫咪图片与各类装扮道具(帽子、眼镜、服装、假发等)或其他卡通/玩具素材叠加合成,让不同来源的素材无缝融合成一张完整的搞笑图",
+  "在同一张图中将多只猫咪或同一只猫咪的不同姿态照片拼接组合,配合文字标注形成对话或对比效果的多格拼图",
+  "将真实照片中的人物与卡通/奇幻元素合成,例如给人物添加蟑螂的触角和腿,使人物看起来像变成了一只蟑螂,整体画面自然融合不突兀",
+  "给普通猫咪照片套上不同职业的服装和场景(如医生、上班族、老板等),并保持猫咪面部表情清晰可辨,制作出系列表情包拼贴图",
+  "在真实物体照片上叠加手绘风格的简笔画元素,例如在猕猴桃切片上添加卡通五官和小触角,让照片呈现出实物与手绘结合的趣味效果",
+  "生成真实户外场景中的人物活动照片,画面要呈现自然光线下的街道、公园、游乐场等具体地点环境,人物动作自然生动,背景环境细节丰富真实",
+  "生成多人聚集的活动现场图,如会议、展览、户外聚会等场景,画面中需要呈现多个人物同框、有组织的群体互动氛围,背景有明显的活动标识或场地特征",
+  "生成真实物品的特写或陈列展示图,物品摆放清晰、细节可辨,适合用于产品展示或场景道具呈现,画面构图干净突出主体",
+  "生成同一人物在同一场景中多角度、多姿态的即时抓拍效果图,画面呈现自然随意的动态感,如行走、转身、低头、仰望等非摆拍状态,整体风格真实生活化",
+  "生成动物(如马)在运动瞬间被捕捉的高动态画面,鬃毛飞扬、肢体伸展,呈现出强烈的瞬间张力和动感,背景简洁以突出主体动态",
+  "生成人物在真实日常场景(街头、公园、机场等)中被随手拍下的多张图片拼贴效果,画面构图不刻意、视角多变(含俯拍脚部、镜中自拍、远景抓拍等),整体呈现出碎片化的生活记录感",
+  "制作图文卡片时,需要让插图与文字在语义上高度呼应——比如用可爱小驴的不同表情和动作配合对应的幽默文字,每张小卡片中图在上、文字在下,插图内容直接反映文字含义,形成一眼就能看懂的图文配合效果",
+  "制作多图拼贴帖子时,需要将多张照片或截图按照叙事顺序排列在一张大图中,并在关键图片上叠加说明性文字标注,让图片和文字共同讲述一个完整故事,文字起到补充说明和情感点评的作用",
+  "制作信息图文海报时,需要将大标题、分类小标题与正文段落按照清晰的层级排布在版面上,标题用大字醒目展示,正文紧跟其下,整体版面分区明确、图文对应,让读者能快速扫读获取信息",
+  "在图片上叠加标题文字,文字大小、粗细、颜色各异,形成层次感强的排版效果——例如大标题用粗体醒目字体,副标题用细体小字,整体风格统一(如深色系商务风或简约设计风)",
+  "在AI生成的卡通角色图片上叠加幽默吐槽文案,文字直接覆盖在图片上方,字体简洁白色,与画面情绪呼应,形成图文结合的表情包风格内容",
+  "制作多宫格拼图帖子,每格图片配有对应的标题文字或字幕说明,文字风格统一,整体排列整齐,适合用于周记、日历、流程说明等系列内容展示",
+  "制作图文卡片式科普内容:每张卡片包含统一的标题样式、编号序列、配套插图(卡通/示意图风格)和说明文字,多张卡片拼成一组,整体风格统一、排版清晰,适合健康养生、步骤教程类内容展示",
+  "制作数据报告类图文内容:包含柱状图、饼图、折线图、词云图、环形图等多种数据可视化图表,配合标题、要点文字说明,整体呈现专业研究报告的视觉风格,色彩搭配统一(如蓝紫色系或橙色系)",
+  "制作流程图/架构示意图:用箭头、方框、层级结构或立体堆叠图形展示系统架构、业务流程或概念层级关系,配合文字标注说明各模块功能,视觉上清晰呈现逻辑关系",
+  "将整个帖子内容拆分为多个独立小格子并排列成网格或矩阵布局,每个格子承载一个独立的场景或信息单元,格子之间有明显的分隔边界,整体看起来像一张由多张小图拼合而成的大图",
+  "在同一张图中混合使用多种内容载体形式,例如将真实照片、插画角色、产品图、文字说明、图表等不同类型的视觉元素组合排布在同一个版面内,形成图文混排的丰富视觉层次",
+  "以统一的视觉主角(如同一个卡通角色、同一个人物、同一主题场景)贯穿多个分格画面,每个格子呈现该主角在不同场景或状态下的样子,配合文字说明形成系列感强的多格叙事版式",
+  "将多张图片按网格或分区方式拼贴成一张图,每个区域展示不同角度或不同场景,整体画面有清晰的分割感和节奏感",
+  "在同一画面中合理安排主体与背景的空间关系,让主体(人物、动物、物品)在画面中有明确的视觉焦点,背景简洁或有层次地衬托主体",
+  "生成包含多个独立小格子的图文排版画面,每个格子内有图片和文字说明,格子之间疏密有致、整齐排列,整体呈现信息图表或内容合集的视觉效果",
+  "生成人物局部特写画面,如放大呈现嘴巴咬食物、手持物品、耳朵佩戴饰品、鼻子、指甲等身体局部细节,画面填充感强,细节清晰可见",
+  "生成人物近景半身或胸部以上的画面,突出人物面部表情和情绪,背景适当虚化,让观看者能清楚看到人物的神态与互动感",
+  "生成产品或物品的极近距离特写图,如食物截面、商品细节、小物件放大展示,画面主体占满画幅,质感和纹理清晰突出",
+  "生成具有强烈透视纵深感的室内空间图,画面中窗框、拱门、地板线条等建筑元素形成明显的空间层次,光线从远处窗口射入,营造出由近到远的视觉延伸效果",
+  "生成采用夸张变形构图的图片,例如鱼眼镜头效果将人物或场景扭曲成球形全景、仰拍使近处物体极度放大而远处极度缩小,或通过搞怪角度让画面产生强烈的视觉冲击感",
+  "生成画面中存在嵌套框架效果的图片,如在沙漠场景中用一个悬空的矩形框将主体框住形成画中画,或利用水面倒影与实景上下对称形成嵌套镜像构图,制造超现实的空间突破感",
+  "生成具有强烈色彩对比的艺术插画,整体画面以高饱和度的红色与蓝色为主色调,两种颜色形成鲜明的冷暖对撞,背景大面积纯色铺底,视觉冲击力极强",
+  "在以暗色或单色为主的画面中,用局部的高饱和亮色(如红色心脏、橙色暖光窗口、金黄色星光)作为点睛之笔,让视线自然聚焦到这个色彩亮点上,形成强烈的视觉引导",
+  "生成整体色调统一、饱和度偏高的场景图,例如全画面笼罩在深蓝色夜光氛围或浓郁的赤红土地色调中,让单一主色调主导整个画面,营造出沉浸式的强烈色彩氛围感",
+  "生成具有强烈氛围感的插画风场景图,整体画面以深蓝色调为主,室内外场景都笼罩在宁静的夜色中,窗户透出暖黄色灯光形成冷暖对比,画面质感接近油画或数字绘画风格,传达出静谧、沉思、略带忧郁的情绪氛围",
+  "制作色彩鲜艳、视觉冲击力强的宣传海报,背景使用渐变色块(蓝紫、橙红等高饱和度色彩),搭配几何抽象图形装饰,文字排版醒目大气,整体呈现出热烈、充满活力的欢庆氛围",
+  "生成暖色调的室内空间效果图,以米白、浅棕、焦糖色为主色调,光线柔和自然,空间布置温馨舒适,整体画面传达出放松、治愈、生活化的温暖氛围",
+  "生成具有强烈视觉冲击力的超现实主义风格图像:画面以大地色系(深红、赭石、深蓝)为主调,将白马、牛仔等元素置于极简的荒漠/盐湖场景中,营造出孤寂、神秘、如油画般的电影感氛围",
+  "生成3D卡通风格的拟人化动物角色,角色具有毛绒质感和丰富的表情神态,能够在不同生活场景(办公室、卧室、户外)中呈现出喜怒哀乐等情绪状态,整体风格类似皮克斯动画",
+  "制作融合插画风格的信息图文海报:以卡通机器人/科技感插图作为视觉主体,搭配醒目的彩色标题文字和数据图表,整体呈现出活泼又专业的视觉效果",
+  "生成一组多格拼贴图,每格展示同一人物在不同场景/状态下的夸张表情和肢体动作,配合幽默文字标注,整体呈现出戏剧化的情绪起伏效果(如一周心情变化、苦情崩溃、搞笑反应等)",
+  "将动物(如猫咪)与各种食物、道具进行创意合成,给动物添加配饰(帽子、假发、领带等)并嵌入食物场景中,搭配谐音梗或双关文字,制作出拟人化角色扮演的趣味表情包图片",
+  "给同一张猫咪照片批量添加不同职业的帽子、道具和配件(如厨师帽、安全帽、眼镜、画板等),让猫咪看起来像在扮演各种职业角色",
+  "将真实照片转换成具有统一色调风格的插画效果,整体呈现蓝紫色调的复古油画或动画风格,让风景场景看起来像艺术插图",
+  "制作图文排版信息图,将多张食材产品图片抠出后整齐排列在统一背景上,配合文字说明组合成内容丰富的科普海报",
+  "生成具有强烈光影对比的场景图,画面中光源明显(如阳光折射、水面反光、彩虹色光晕),暗部极深、亮部极亮,整体呈现出戏剧性的明暗反差和光线质感",
+  "生成带有明显颗粒感或纸张纹理的插画风格图片,画面整体像是印刷在粗糙介质上,物体表面有细腻的颗粒噪点或手工绘制的笔触肌理",
+  "生成室内场景时,能真实还原不同材质的质感细节,如木地板的纹路光泽、布艺沙发的绒毛感、大理石茶几的光滑反射、藤编家具的编织纹理等",
+  "在图片上叠加对话气泡或多行说明文字,用于讲述故事背景或补充情节说明,文字带有描边或阴影效果以确保在复杂背景上清晰可读",
+  "制作图文并茂的科普说明卡片,每张卡片包含标题、编号、插图和详细文字说明,整体排版整齐统一,适合分步骤展示教程或知识点",
+  "在多图拼贴海报上为每个区域叠加带图标的标签(如勾选符号+地点名称),并在整体画面上方添加大标题和副标题文字,形成图文结合的内容合集展示效果",
+  "为多人物展示海报中的每个人物添加姓名和职位标签,并在画面顶部叠加活动主题、专场名称等层级分明的标题文字,整体风格统一、信息密度高",
+  "对同一场景或主体生成多个不同距离和景别的画面,包括远景展示整体环境、中景呈现主体与环境关系、近景突出细节,形成一组视角丰富的图片集合",
+  "生成采用非常规拍摄角度的图片,如从低角度仰拍、从高处俯视、或模拟第一人称主观视角看向场景,让画面产生独特的视觉冲击感",
+  "生成能展示宽广空间感的室内或室外全景图,画面中包含完整的环境纵深,让观看者感受到场景的整体规模和空间层次",
+  "生成具有统一色调风格的插画场景,整体画面使用高度协调的单一色系(如全蓝紫色调的火车风景、全粉紫色调的奇幻海洋),让画面中所有元素的颜色都偏向同一个色相,营造出梦幻沉浸的视觉氛围",
+  "生成色彩鲜艳、多色并置的视觉冲击画面,画面中同时出现多种高饱和度的颜色搭配(如复古拼贴风格中的粉色、蓝色、橙色并置,或彩色条纹波浪地形),让整体色彩浓烈饱满、视觉张力强烈",
+  "生成低饱和度或去色风格的极简画面,整体色彩纯度降低,呈现出克制、安静的视觉质感(如黑白灰调的海洋孤舟场景,或接近无彩色的素雅插画),与高饱和度画面形成鲜明对比",
+  "生成超现实浪漫场景图:将人物置于不可能存在的宏大环境中,如站在地球边缘俯瞰星空、坐在云端长椅上漂浮、在星海上骑行,画面充满梦幻感和史诗级视觉冲击力",
+  "制作科技感强烈的活动宣传海报:使用深色背景配合橙色、蓝色等高对比度霓虹色调,融合未来感城市或科技场景插图,搭配大号粗体标题文字,整体呈现出硬核、前沿的视觉气质",
+  "生成融合东方传统与现代简约的室内空间效果图:以米白、暖棕为主色调,加入拱形门洞、藤编元素、中式花卉装饰画等传统细节,整体呈现温润雅致的新中式生活美学氛围",
+  "生成拟人化动物角色表情包:用AI生成具有丰富表情和情绪的卡通动物形象(如毛茸茸的红色马、灰色驴),能够呈现出沮丧、无奈、委屈等多种情绪状态,配合不同场景背景(办公室、草地、室内),整体风格介于3D皮克斯动画和水彩插画之间,适合搭配幽默文案使用",
+  "制作图文混排的知识科普长图:以深青色/蓝绿色为底色背景,将心理学等知识内容拆分为多个板块,每个板块搭配风格统一的插画小图(奇幻风格人物、动物等),文字与插图穿插排布,整体呈现出版式清晰、视觉层次丰富的杂志风格科普图文效果",
+  "生成室内空间效果图:用AI渲染出具有温暖奶油色调的室内场景,包含拱形门洞、藤编家具、自然光影等元素,整体呈现出地中海或法式复古风格的高质感室内设计效果,光线柔和、色调统一,适合作为家居内容的视觉展示",
+  "生成具有强烈戏剧性光影对比的户外场景图,画面中光源方向明确(如侧光或逆光),亮部与暗部之间形成鲜明反差,阴影轮廓清晰,整体呈现出电影感或艺术摄影风格的视觉张力",
+  "生成室内暖光氛围图,画面中多个光源(吊灯、筒灯、窗外自然光)共同营造出温暖柔和的米色调空间,光线从不同方向照射,形成层次丰富的软阴影,整体氛围温馨舒适",
+  "生成充满魔幻或超现实感的彩色光效场景,画面中有多种颜色的光线(如橙、蓝、紫等)交织流动,光源本身成为视觉焦点,整体营造出梦幻、神秘或节日感的强烈氛围",
+  "制作大字号标题搭配正文内容的图文排版,标题文字极大且颜色鲜艳(红色、黄色等高饱和色),与正文小字形成强烈的大小对比,整体版面信息密度高、视觉冲击力强",
+  "在图片上叠加大字幕文字,字体粗大醒目,常带有描边或阴影效果,文字直接覆盖在照片或场景图上,起到强调说明或搞笑点评的作用",
+  "制作多宫格拼贴式内容图,每个格子内有大标题文字突出显示核心信息(如价格、品类名),配合产品图或场景图,标题字号远大于说明文字,形成层次分明的视觉结构"
+]

+ 85 - 0
examples/process_research/fix_strategy_requirement.py

@@ -0,0 +1,85 @@
+import os
+import json
+import glob
+
+def main():
+    base_dir = r"c:\Users\11304\gitlab\cybertogether\Agent\examples\process_research\output"
+    
+    # 遍历 output 目录下的所有子文件夹
+    if not os.path.exists(base_dir):
+        print(f"Directory not found: {base_dir}")
+        return
+
+    fixed_count = 0
+    skip_count = 0
+
+    for item in os.listdir(base_dir):
+        sub_dir = os.path.join(base_dir, item)
+        if not os.path.isdir(sub_dir):
+            continue
+            
+        strategy_path = os.path.join(sub_dir, "strategy.json")
+        if not os.path.exists(strategy_path):
+            continue
+            
+        # 读取 strategy.json
+        try:
+            with open(strategy_path, "r", encoding="utf-8") as f:
+                strategy_data = json.load(f)
+        except Exception as e:
+            print(f"Error reading {strategy_path}: {e}")
+            continue
+
+        # 如果已经有了,跳过
+        if "requirement" in strategy_data:
+            print(f"[{item}] skip: already has requirement")
+            skip_count += 1
+            continue
+            
+        # 寻找对应的 case.json 提取 requirement
+        requirement_text = None
+        
+        # 1. 尝试直接找 case.json
+        case_path = os.path.join(sub_dir, "case.json")
+        if os.path.exists(case_path):
+            try:
+                with open(case_path, "r", encoding="utf-8") as f:
+                    case_data = json.load(f)
+                    requirement_text = case_data.get("requirement")
+            except Exception:
+                pass
+                
+        # 2. 如果没找到或 case.json 不存在,并且存在分散的 case_*.json
+        if not requirement_text:
+            case_files = glob.glob(os.path.join(sub_dir, "case_*.json"))
+            for cf in case_files:
+                try:
+                    with open(cf, "r", encoding="utf-8") as f:
+                        cf_data = json.load(f)
+                        if "requirement" in cf_data:
+                            requirement_text = cf_data["requirement"]
+                            break
+                except Exception:
+                    continue
+                    
+        if not requirement_text:
+            print(f"[{item}] ERROR: could not find 'requirement' in any case files")
+            continue
+            
+        # 更新 strategy.json
+        # 为了保证 requirement 在最开头,创建一个新的 dict
+        new_strategy_data = {"requirement": requirement_text}
+        new_strategy_data.update(strategy_data)
+        
+        try:
+            with open(strategy_path, "w", encoding="utf-8") as f:
+                json.dump(new_strategy_data, f, ensure_ascii=False, indent=4)
+            print(f"[{item}] fixed: added requirement successfully")
+            fixed_count += 1
+        except Exception as e:
+            print(f"[{item}] ERROR writing back strategy data: {e}")
+
+    print(f"\nSummary: Fixed {fixed_count} files, Skipped {skip_count} files.")
+
+if __name__ == "__main__":
+    main()

+ 83 - 0
examples/process_research/generate_strategy.py

@@ -0,0 +1,83 @@
+import asyncio
+import json
+import sys
+import os
+
+# 将 Agent 根目录加入 sys.path
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
+
+from agent.llm.qwen import qwen_llm_call
+
+async def main():
+    case_path = os.path.join(os.path.dirname(__file__), 'output', '04_parallel', 'case.json')
+    out_path = os.path.join(os.path.dirname(__file__), 'output', '04_parallel', 'strategy.json')
+    
+    if not os.path.exists(case_path):
+        print(f"Error: {case_path} does not exist.")
+        return
+        
+    print("Loading case.json...")
+    with open(case_path, 'r', encoding='utf-8') as f:
+        case_data = json.load(f)
+        
+    # 提取精简的提示词
+    prompt = f"""
+你是一个工序调研协调器。你面前已经有了 54 个子 Agent 刚刚从各大平台收集回来的调研案例(见下方 JSON)。
+这里的核心需求是:生成带有明显颗粒感或纸张纹理的插画风格图片。
+
+请分析这些案例并总结、提取、设计制作最终的打法策略,按照下方强制的 JSON 格式输出,不要包含 Markdown 代码块(如 ```json 等),纯粹返回合法的 JSON 字符串。
+
+强制输出格式:
+{{
+  "selected_strategy": {{
+    "name": "端到端策略命名(如:分层生成 + 颗粒噪点叠加 + FLUX局部精修)",
+    "source": "指明主要受到这批采集回来的哪些 case 的启发",
+    "workflow_outline": [
+      {{
+        "phase": "阶段1:底层结构与材质生成",
+        "description": "这个阶段要完成的子目标...",
+        "capabilities": [
+          {{
+            "capability_id": null,
+            "capability_name": "原子能力名称",
+            "is_new": true,
+            "suggested_tools": ["提取自Case的建议工具"],
+            "case_references": ["来源于哪个case的反馈"]
+          }}
+        ]
+      }}
+    ]
+  }}
+}}
+
+调研案例 JSON 内容如下:
+{json.dumps(case_data, ensure_ascii=False)}
+"""
+
+    print("Submitting to Qwen-Plus for strategy generation...")
+    messages = [{"role": "user", "content": prompt}]
+    
+    try:
+        response = await qwen_llm_call(messages, model="qwen-plus", temperature=0.3)
+        content = response.get("content", "").strip()
+        
+        # 清理多余的 Markdown 标记
+        if content.startswith("```json"):
+            content = content[7:]
+        elif content.startswith("```"):
+            content = content[3:]
+        if content.endswith("```"):
+            content = content[:-3]
+            
+        content = content.strip()
+        
+        with open(out_path, 'w', encoding='utf-8') as f:
+            f.write(content)
+            
+        print(f"\\n✅ Strategy successfully saved to: {out_path}")
+        
+    except Exception as e:
+        print(f"\\n❌ Generation failed: {str(e)}")
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 16 - 0
examples/process_research/presets.json

@@ -0,0 +1,16 @@
+{
+  "coordinator": {
+    "system_prompt_file": "prompts/coordinator.prompt",
+    "max_iterations": 800,
+    "temperature": 0.3,
+    "skills": ["planning", "research", "browser", "knowledge"],
+    "description": "主 Agent:需求分析 + 分发调研方向 + 汇总策略"
+  },
+  "researcher": {
+    "system_prompt_file": "prompts/researcher.prompt",
+    "max_iterations": 300,
+    "temperature": 0.3,
+    "skills": ["planning", "research", "browser"],
+    "description": "调研子 Agent:单方向搜索 + 步骤级能力提取"
+  }
+}

+ 223 - 0
examples/process_research/prompts/coordinator.prompt

@@ -0,0 +1,223 @@
+---
+temperature: 0.3
+---
+
+$system$
+
+## 角色
+
+你是一个工序调研协调器。你负责分析需求、规划调研方向、逐一委托调研子 Agent 执行搜索,最后汇总所有调研结果设计制作策略。
+
+---
+
+## 可用工具
+
+- `agent(task, agent_type)` — 委托调研子 Agent(单任务 = delegate 模式,子 Agent 有完整工具权限)
+- `read_file(path)` — 读取文件
+- `write_file(path, content)` — 写入文件
+- `capability_search(query, top_k=5)` — 检索现有原子能力(Phase 2 使用)
+- `capability_list(limit=50)` — 列出所有原子能力(Phase 2 使用)
+
+---
+
+## 执行流程
+
+### Phase 1:规划 + 分发调研任务
+
+**第一步:确定渠道搜索策略**
+
+你的目标是针对原始需求,全方位搜索实战工序案例。**不要把原始需求强行拆分成碎片化的子问题**(例如不要把“生成户外人物”拆成“光影”、“肤质”),需求始终是同一个完整的中心主题。
+
+你需要从以下可用渠道库中,仔细评估并挑选 **2~4 个最适合当前业务需求的渠道** 作为探索策略:
+- `youtube`:侧重英文、中文的长线大课、详细的工作流搭建视频。
+- `xhs` (小红书):侧重中文高质量的参数面板分享、避坑图文攻略。
+- `x` (Twitter):侧重海外最前沿的技术发布、大 V 的新工具尝鲜案例。
+- `bili` (B站):侧重中文详细长视频教程、二次元与游戏美术案例。
+- `zhihu` (知乎) / `gzh` (公众号):侧重深度的中文原理剖析与长图文拆解。
+
+**第二步:并行委托针对各垂直渠道的子 Agent**
+
+因为我们底层的并发引擎已经开启,你**必须在同一次回答中,使用数组发起并发调用**。请基于你挑选的渠道,组织你的 `agent` 工具调用,将各个渠道的独立任务存入 `task` 数组中,发起一次**并发分发**:
+
+```json
+{
+  "name": "agent",
+  "arguments": {
+    "task": [
+      "[RESEARCH TASK]\n\n渠道:YouTube\n目标:寻找长线完整工作流视频\n建议关键词:...\n...",
+      "[RESEARCH TASK]\n\n渠道:小红书\n目标:寻找参数面板、避坑图文攻略\n建议关键词:...\n...",
+      "[RESEARCH TASK]\n\n渠道:B站\n目标:寻找中文详细长视频教程\n建议关键词:...\n..."
+    ],
+    "agent_type": "researcher"
+  }
+}
+```
+
+**任务描述规范**(数组中每个 `[RESEARCH TASK]` 元素的负载要求):
+- 明确指出这个**相同的唯一核心需求**。
+- 按自己负责的专属渠道列出建议的英文/中文关键词。
+  > 🚨 **【工具约束红线】**:搜索词严禁出现任何具体的 AI 工具、平台或插件名(如 ControlNet...)。
+- 明确指出需要排除的干扰项。
+- 给出**独立隔离**的输出追加路径:`{output_dir}/case_渠道名.json`(例如 `case_youtube.json`、`case_xhs.json`。绝不允许所有任务指向同一个 `case.json` 以防并发写入冲突!)
+
+**重点声明**:请仅调用 **一次** `agent` 工具,将多条任务合并进同一个 `task` 数组(`List[str]`)。收到指令后,底层协程会自动创建相互隔离的并发容器处理每个请求,不仅避免了本地资源争抢,还能最大化检索效率。
+
+
+---
+
+### Phase 2:读取结果与设计制作策略
+
+子 Agent 完成任务并返回后:
+
+**第三步:聚总并整理各个 case 文件**
+
+由于我们开启了并发隔离写入,子 Agent 们将案例分布存在了各自独立的 JSON 文件中(如 `case_xhs.json`)。你必须:
+```
+1. glob_files("{output_dir}/case_*.json") 查出所有产出的结果文件。
+2. 挨个使用 read_file("文件路径") 读取并加载进你的上下文。
+3. 如果所有文件内容完整,你可用 write_file 将它们手动合并输出到全局统一的 {output_dir}/case.json 留存:
+{
+  "requirement": "原始需求",
+  "generated_at": "ISO 8601",
+  "cases": [ ...所有渠道的合并集合... ]
+}
+```
+
+**第四步:设计最终交付级生产策略**
+
+你现在需要作为总架构师,基于收集到的这几十个成功与失败的真实案例,拍板决定一条**唯一的最优端到端制作流派(Strategy)**。
+你不需要平行列举多个等价的策略,你需要给出**最终推荐的生产级解决方案**(可以糅合不同案例的亮点,比如前置生成用A方案,精修用B方案)。
+
+策略提炼必须包含以下深度思考(写入 strategy.json):
+1. **统一标准策略阵列(strategies)**:
+   不要区分 selected_strategy 和 vs_alternatives 对象!请将所有可行的路线(首推流派与所有备选流派)以统一的 JSON 数据结构放入一个 `strategies` 数组中。
+   对于数组中的每一套流派方案,你必须:
+   - **标识优先级**:通过 `is_selected: true` 标记其中**唯一一条**你最推荐的、作为主线落地的最优流水线。其余作为备用的方案标记为 `is_selected: false`。
+   - **全流程拆解**:**每一套**方案(无论是首推还是备选)都必须是完整的端到端结构化工序!必须详细写满从头到尾的 `workflow_outline`(阶段1、阶段2... 直至交付的最终阶段),🚨 **严禁在备选方案里偷工减料只写一个阶段!**
+   - **原子能力要求**:每一个阶段必须明确依托于一个或多个**原子能力**。用 `capability_search(意图描述)` 匹配现有能力库;若库中没有(即 `is_new: true`),请**暂定该新能力的名称**,并从 case 中总结出**建议使用的工具/节点组合(suggested_tools)**。
+   - **保障性分析**:不管是主线还是备线,都要写明它的 `highlight_coverage` 和 `baseline_coverage`。
+   - **拍板/淘汰原因**:如果是首选方案,请写明拍板原因(`reasoning`);如果是备选方案,请写明未被选为主推的短板(`why_not`)及未来的降级切换条件(`could_switch_if`)。
+2. **能力盲区说明(uncovered_requirements)**:
+   - 当前 AI 工具链确实无法做到的短板。
+
+能力抽取准则:
+请严格遵循上方“什么是正确的原子能力?”的规范,拒绝把低级参数当做能力。即使是暂定的新能力,也必须足够高维、面向泛化需求。
+
+**第五步:输出最终策略**
+
+```
+write_file("{output_dir}/strategy.json", 最终拍板的打法策略)
+```
+
+---
+
+## 输出文件格式
+
+### `case.json`(合并后示例)
+```json
+{
+  "requirement": "原始需求",
+  "generated_at": "ISO 8601",
+  "cases": [
+    {
+      "id": "case_001",
+      "title": "案例标题",
+      "platform": "xhs",
+      "source_url": "https://...",
+      "metrics": {"likes": 100, "comments": 20},
+      "user_feedback": "用户评论如是说...",
+      "images": ["https://img_url"],
+      "input_details": "输入图片与提示词",
+      "output_details": "输出效果图评价",
+      "workflow_process": "详实的步骤记录与原理参数说明"
+    }
+  ]
+}
+```
+
+### `strategy.json`
+```json
+{
+  "requirement": "原始需求的完整描述(原样复制即可)",
+  "strategies": [
+    {
+      "is_selected": true,
+      "name": "首推策略命名(如:分层生成 + MJ底层主力 + FLUX局部精修)",
+      "source": "指明主要受到这批采集回来的哪些 case (如 case_001 / case_005)或者反馈的直接启发",
+      "workflow_outline": [
+        {
+          "phase": "阶段1:底层结构生成",
+          "description": "这个阶段要完成的子目标...",
+          "capabilities": [
+            {
+              "capability_id": "库中已有ID(若无则填 null)",
+              "capability_name": "原子能力名称(如:保持角色特征一致性)",
+              "is_new": true,
+              "suggested_tools": ["提取自Case的建议工具,如:IP-Adapter + 深度图"],
+              "case_references": ["来源于哪个case的反馈,比如:case_001 作者提到加上深度图能防崩"]
+            }
+          ]
+        },
+        {
+          "phase": "阶段2:高光特效处理",
+          "description": "(注意:每套方案都必须写满完整的从头到尾的各个阶段,绝不可省略)",
+          "capabilities": [
+            ...
+          ]
+        }
+      ],
+      "highlight_coverage": [
+        "原始需求中的高光特色1 — 靠由于阶段X的某个操作保障"
+      ],
+      "baseline_coverage": [
+        "基本的人体比例防崩 — 靠阶段Y保障"
+      ],
+      "reasoning": "评判综合为什么最终拍板了这套打法。比如结合了 A 路线的可控性强和 B 路线的高水准特征...",
+      "why_not": null,
+      "could_switch_if": null
+    },
+    {
+      "is_selected": false,
+      "name": "备选流派一(如:全程依赖纯重度连线的 ComfyUI 工作流)",
+      "source": "指明主要受到哪些 case 启发",
+      "workflow_outline": [
+        {
+          "phase": "阶段1:...",
+          ...同首选方案的高度全流程详实度...
+        },
+        {
+          "phase": "阶段2:...",
+          ...同首选方案的高度全流程详实度...
+        }
+      ],
+      "highlight_coverage": [],
+      "baseline_coverage": [],
+      "reasoning": null,
+      "why_not": "指出为什么不作为主推:比如查到的案例中评论区反映太吃显存或容易崩溃",
+      "could_switch_if": "在什么情况下需要切换:比如,如果硬件算力突破限制或客户要求纯本地执行,可切换此路线"
+    }
+  ],
+  "uncovered_requirements": [
+    "目前技术局限导致的短板:比如物理级别的光影反射当前所有案例均需后期纯手绘补充"
+  ]
+}
+```
+
+---
+
+## 重要约束
+
+1. Phase 1 的子 Agent 任务必须合并在**同一次 `agent` 调用中**,通过 `task` 数组(Array)一次性并发提交。
+2. 在 `strategy.json` 中,不可为了拼凑阶段而凭空发明不可靠的操作,每个关键阶段都需能从 Case 依据或者你匹配查询到的原子库能力中获得有效支撑。
+
+$user$
+
+请开始工作。
+
+**原始需求**:%requirement%
+
+**输出根目录**:%output_dir%
+
+最终输出:
+- `%output_dir%/case.json`(子 Agent 原汁原味沉淀的全量实战案例与带溯源图片的素材库)
+- `%output_dir%/strategy.json`(基于高维原子能力的终局打法矩阵)

+ 118 - 0
examples/process_research/prompts/researcher.prompt

@@ -0,0 +1,118 @@
+---
+temperature: 0.3
+---
+
+$system$
+
+你是一个工序调研统筹子 Agent。你会收到**多个调研方向**的清单,你需要**自主设定 goal(每个方向一个 goal)**,为各个方向覆盖适当的渠道,广泛搜索相关工序案例,并统一将结果结构化追加到指定的同一个文件中。
+
+**你的边界**:全权负责本需求下的所有资料搜集,按要求存入 JSON 文件。不负责最终的策略设计。
+
+---
+
+## 可用工具
+
+### 搜索工具
+- `content_platforms(platform="")` — 列出/查询平台详细搜索参数
+- `content_search(platform, keyword, max_count=20)` — 跨平台搜索案例(返回结果和序列号)
+  - platform 常用值: `xhs`(小红书), `youtube`, `x`(Twitter), `bili`, `gzh`, `zhihu`
+- `content_detail(platform, index)` — 根据 content_search 结果的序号查看详细内容和全文
+- `content_suggest(platform, keyword)` — 获取搜索相关建议词
+- `browser-use` — 真实浏览器交互(仅用于处理 content_*. 系列工具无法覆盖的特殊场景)
+
+### 文件工具
+- `read_file(path)` — 读取文件(追加前必须先读)
+- `write_file(path, content)` — 写入/覆盖文件
+
+---
+
+## 执行流程
+
+### 第一步:理解需求与方向拆解
+
+你会从任务中收到多个方向清单(通常包含侧重渠道和推荐词)。
+**强烈建议**:为你接到的每一个“方向”都调用一次 `goal` 工具设立独立的调研目标任务,这样你能更有条理地进行多渠道的深度挖掘,防止混乱。
+
+### 第二步:逐一方向广泛搜索
+
+对每一个设定的目标方向:
+1. 分析它推荐的平台(如小红书、YouTube等)
+2. > 🚨 **【工具调用最高红线】**:**严禁使用 `bash_command` 或自己编写任何 Python 脚本/爬虫去抓取网页!**你必须、且只能使用系统提供的 `content_search` 和 `content_detail` 这个现成的 API 工具去完成搜索!如果你尝试自己写脚本,任务将直接判定失败。
+3. > 🚨 **【搜索词红线警告】**:在调用 `content_search` 时,**严禁夹带任何具体的 AI 工具名或插件名**(如 ControlNet...)。你只能使用朴素业务语言。
+4. 通过 `content_search` 进行搜索,并通过 `content_detail` 仔细阅读高赞的方案。
+
+接口失败处理:尝试更换关键词 → 重试 2 次 → 放弃。
+
+
+
+### 第三步:萃取原汁原味的案例信息
+
+对每个有价值的工序 case,请放弃生硬的字段拆解,**最大程度为您保留它原始的、有血有肉的内容与上下文**。
+重点围绕以下几个版块提取信息:
+
+1. **输入与输出效果(Input & Output)**:
+   - 作者到底投喂了什么(何种参考图、提示词模板、特定的尺寸/控制参数)?
+   - 最终产出了什么?
+   - **核心要求**:如果有效果图的 URL 或节点连线图的链接,**必须保存原始的图片链接**(以 URL 形式或 Markdown `![截图](URL)` 存入)。
+
+2. **操作过程记录(Raw Workflow)**:
+   - 详实记录作者在原文中所表述的工具链搭配、核心节点名称、关键参数调优(尽量保留作者的原始术语和经验总结)。
+
+3. **来源信源与反馈评估(Source Evaluation)**:
+   - 记录点赞数、评论数、收藏数。
+   - **非常重要**:摘录 1~3 条核心的用户评论反馈(如果有)。比如“跑出来发灰”、“这个节点早就淘汰了”、“太耗显存,推荐平替版”,这最能反映该方案的真实可用性。
+
+### 第四步:独立存储结果文件
+
+🚨 **【并发写文件安全红线】**:你有多个分身正在其他进程中同时执行任务!
+因此你**绝对不能向一个公共的 JSON 文件写入!** 
+你必须根据接受任务时 Coordinator 给你分配的专属 `output_file`(形如 `{output_dir}/case_xhs.json` 或 `case_youtube.json`),将查到的 case 独立持久化。
+
+**每次收集到 2~3 个 case 后,应立即持久化一次**:
+```
+read_file("{output_file}")   → 若文件不存在则初始化 {"requirement": "总体需求描述", "cases": []}
+将新 case 追加进 cases 数组
+write_file("{output_file}", 更新后的完整 JSON)
+```
+
+---
+
+## 输出格式
+
+写入到任务中指定的 `{output_file}`:
+
+```json
+{
+  "requirement": "本次的需求名称/描述",
+  "searched_at": "ISO 8601 时间戳",
+  "cases": [
+    {
+      "id": "调研时自动编号,如 case_001",
+      "title": "案例标题",
+      "platform": "xhs | youtube | x | zhihu | bili | gzh",
+      "source_url": "https://...",
+      "metrics": {
+        "likes": 12400,
+        "comments": 480
+      },
+      "user_feedback": "(如有)摘录关键的用户评论反馈,如'效果不错但是很吃显存','提示词太长跑不出来'",
+      "images": ["https://图片链接1", "https://图片链接2"],
+      "input_details": "详细描述作者输入的参考图类型、Prompt 等",
+      "output_details": "详细描述最终呈现的效果,以及作者对效果的评价",
+      "workflow_process": "用一段结构化的文字或短数组,保留原汁原味的关键步骤梳理、核心控制节点及参数(不需强制格式化为复杂 json 对象,只需清晰可读)"
+    }
+  ]
+}
+```
+
+---
+
+## 重要约束
+
+1. **原汁原味**:重点抓取用户的输入输出配置和原文中的图片链接,不要弄丢上下文!
+2. **重反馈**:发掘评论区里的避坑指南和赞誉,作为判断策略价值的锚点。
+3. **按时持久化**:每 3~5 个 case 写一次文件,防止中途丢数据。
+
+$user$
+
+%task%

+ 0 - 0
examples/process/requirements.json → examples/process_research/requirements.json


+ 408 - 0
examples/process_research/run.py

@@ -0,0 +1,408 @@
+"""
+Process Research Pipeline v3:单 Agent 完成搜索 + 能力提取 + 策略归纳
+
+每个需求输出到 output/{N}/:
+  - case.json     工序案例(含步骤级能力详情)
+  - strategy.json 策略 × case 关联索引
+  - process.json  策略 × 能力流水线详情
+
+用法:
+  python run.py                        # 跑全部需求
+  python run.py --from 2               # 从第3个需求续跑
+  python run.py --requirements req.json  # 指定需求文件
+  python run.py --remote               # 强制远端模式(覆盖 config.USE_REMOTE_RESEARCH)
+  python run.py --local                # 强制本地模式
+
+环境变量:
+  KNOWHUB_API  线上 KnowHub 地址(默认 http://localhost:9999)
+"""
+
+import argparse
+import json
+import os
+import sys
+import asyncio
+from datetime import datetime
+from pathlib import Path
+
+
+sys.path.insert(0, str(Path(__file__).parent.parent.parent))
+
+from dotenv import load_dotenv
+load_dotenv()
+
+from agent.llm.prompts import SimplePrompt
+from agent.core.runner import AgentRunner, RunConfig
+from agent.trace import FileSystemTraceStore, Trace, Message
+from agent.llm import create_qwen_llm_call
+from agent.cli import InteractiveController
+from agent.utils import setup_logging
+from agent.tools.builtin.subagent import _run_remote_agent
+from agent.tools.builtin.browser.baseClass import init_browser_session, kill_browser_session
+
+from config import (
+    COORDINATOR_RUN_CONFIG,
+    OUTPUT_DIR,
+    SKILLS_DIR, TRACE_STORE_PATH, DEBUG, LOG_LEVEL, LOG_FILE,
+    BROWSER_TYPE, HEADLESS,
+    IM_ENABLED, IM_CONTACT_ID, IM_SERVER_URL, IM_WINDOW_MODE, IM_NOTIFY_INTERVAL,
+    USE_REMOTE_RESEARCH,
+)
+
+
+
+# ─────────────────────────────────────────────
+# 本地模式:单需求执行
+# ─────────────────────────────────────────────
+
+async def run_single_local(
+    runner: AgentRunner,
+    interactive: InteractiveController,
+    store: FileSystemTraceStore,
+    prompt: SimplePrompt,
+    requirement: str,
+    output_dir: Path,
+    req_index: int,
+) -> tuple[str, bool]:
+    """本地 AgentRunner 模式执行单个需求,返回 (最终回复, 是否应退出)。"""
+
+    output_dir.mkdir(parents=True, exist_ok=True)
+
+    messages = prompt.build_messages(
+        requirement=requirement,
+        output_dir=str(output_dir),
+    )
+
+    prompt_model = prompt.config.get("model", None)
+    run_config = RunConfig(
+        model=prompt_model or COORDINATOR_RUN_CONFIG.model,
+        temperature=COORDINATOR_RUN_CONFIG.temperature,
+        max_iterations=COORDINATOR_RUN_CONFIG.max_iterations,
+        extra_llm_params=COORDINATOR_RUN_CONFIG.extra_llm_params,
+        agent_type=COORDINATOR_RUN_CONFIG.agent_type,
+        name=f"工序调研:需求{req_index+1:03d}",
+        knowledge=COORDINATOR_RUN_CONFIG.knowledge,
+    )
+
+    print(f"\n{'=' * 60}")
+    print(f"[{req_index+1:03d}] 开始调研 【本地模式】")
+    print(f"需求:{requirement[:80]}{'...' if len(requirement) > 80 else ''}")
+    print(f"输出:{output_dir}")
+    print(f"{'=' * 60}")
+
+    current_trace_id = None
+    current_sequence = 0
+    final_response = ""
+    should_exit = False
+
+    try:
+        async for item in runner.run(messages=messages, config=run_config):
+            cmd = interactive.check_stdin()
+            if cmd == 'pause':
+                print("\n⏸️ 正在暂停执行...")
+                if current_trace_id:
+                    await runner.stop(current_trace_id)
+                await asyncio.sleep(0.5)
+                menu_result = await interactive.show_menu(current_trace_id, current_sequence)
+                if menu_result["action"] == "stop":
+                    should_exit = True
+                    break
+                elif menu_result["action"] == "continue":
+                    new_messages = menu_result.get("messages", [])
+                    run_config.after_sequence = menu_result.get("after_sequence")
+                    if new_messages:
+                        messages = new_messages
+                    break
+            elif cmd == 'quit':
+                print("\n🛑 用户请求停止...")
+                if current_trace_id:
+                    await runner.stop(current_trace_id)
+                should_exit = True
+                break
+
+            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] ✅ 完成  messages={item.total_messages}  cost=${item.total_cost:.4f}")
+                elif item.status == "failed":
+                    print(f"\n[Trace] ❌ 失败: {item.error_message}")
+                elif item.status == "stopped":
+                    print(f"\n[Trace] ⏸️ 已停止")
+
+            elif isinstance(item, Message):
+                current_sequence = item.sequence
+                if item.role == "assistant":
+                    content = item.content
+                    if isinstance(content, dict):
+                        text = content.get("text", "")
+                        tool_calls = content.get("tool_calls")
+                        if text and not tool_calls:
+                            final_response = text
+                            print(f"\n[Response] Agent 回复:")
+                            print(text)
+                        elif text:
+                            preview = text[:150] + "..." if len(text) > 150 else text
+                            print(f"[Assistant] {preview}")
+                elif item.role == "tool":
+                    content = item.content
+                    tool_name = "unknown"
+                    if isinstance(content, dict):
+                        tool_name = content.get("tool_name", "unknown")
+                    if item.description and item.description != tool_name:
+                        desc = item.description[:80]
+                        print(f"[Tool] ✅ {tool_name}: {desc}...")
+                    else:
+                        print(f"[Tool] ✅ {tool_name}")
+
+    except Exception as e:
+        print(f"\n执行出错: {e}")
+        import traceback
+        traceback.print_exc()
+
+    if current_trace_id:
+        print(f"  Trace ID: {current_trace_id}")
+
+    return final_response, should_exit
+
+
+# ─────────────────────────────────────────────
+# 远端模式:单需求执行
+# ─────────────────────────────────────────────
+
+async def run_single_remote(
+    requirement: str,
+    output_dir: Path,
+    req_index: int,
+) -> tuple[str, bool]:
+    """HTTP 调用线上 KnowHub remote_research agent,返回 (摘要, False)。"""
+
+    output_dir.mkdir(parents=True, exist_ok=True)
+
+    print(f"\n{'=' * 60}")
+    print(f"[{req_index+1:03d}] 开始调研 【远端模式 → KnowHub remote_research】")
+    print(f"需求:{requirement[:80]}{'...' if len(requirement) > 80 else ''}")
+    print(f"输出:{output_dir}")
+    print(f"{'=' * 60}")
+
+    result = await _run_remote_agent(
+        agent_type="remote_research",
+        task=requirement,
+        messages=None,
+        continue_from=None,
+        skills=None,
+    )
+
+    status = result.get("status", "unknown")
+    summary = result.get("summary", "")
+    error = result.get("error")
+    stats = result.get("stats", {})
+
+    if status == "completed":
+        print(f"\n[Remote] ✅ 完成  tokens={stats.get('total_tokens', 0)}  cost=${stats.get('total_cost', 0.0):.4f}")
+    else:
+        print(f"\n[Remote] ❌ 失败: {error}")
+
+    if result.get("sub_trace_id"):
+        print(f"  Remote Trace ID: {result['sub_trace_id']}")
+
+    return summary or "", False
+
+
+# ─────────────────────────────────────────────
+# Main
+# ─────────────────────────────────────────────
+
+async def main():
+    parser = argparse.ArgumentParser(description="Process Research Pipeline v3")
+    parser.add_argument(
+        "--from", dest="from_index", type=int, default=0,
+        help="从第几个需求开始(0-based)",
+    )
+    parser.add_argument(
+        "--only", dest="only_index", type=int, default=None,
+        help="只执行指定的第几个需求(0-based),这会覆盖 --from",
+    )
+    parser.add_argument(
+        "--requirements", type=str, default=None,
+        help="需求列表 JSON 文件路径(默认 db_requirements.json)",
+    )
+    parser.add_argument(
+        "--remote", action="store_true",
+        help="强制使用远端模式(覆盖 config.USE_REMOTE_RESEARCH)",
+    )
+    parser.add_argument(
+        "--local", action="store_true",
+        help="强制使用本地模式(覆盖 config.USE_REMOTE_RESEARCH)",
+    )
+    parser.add_argument(
+        "--parallel", action="store_true",
+        help="强制开启并发多浏览器机制(覆盖 config.PARALLEL_TOOL_EXECUTION)",
+    )
+    parser.add_argument(
+        "--sequential", action="store_true",
+        help="强制开启单步串行机制(覆盖 config.PARALLEL_TOOL_EXECUTION)",
+    )
+    args = parser.parse_args()
+
+    # 决定是否用远端
+    use_remote = USE_REMOTE_RESEARCH
+    if args.remote:
+        use_remote = True
+    if args.local:
+        use_remote = False
+
+    # 决定并发模式
+    is_parallel = COORDINATOR_RUN_CONFIG.parallel_tool_execution
+    if args.parallel:
+        is_parallel = True
+    if args.sequential:
+        is_parallel = False
+    COORDINATOR_RUN_CONFIG.parallel_tool_execution = is_parallel
+    
+    # 根据并发模式覆盖浏览器类型(并发必须用云浏览器防止冲突)
+    browser_type = BROWSER_TYPE
+    if is_parallel:
+        browser_type = "cloud"
+    elif args.sequential:
+        browser_type = "local" # 串行可以选择回本地
+
+    base_dir = Path(__file__).parent
+    project_root = base_dir.parent.parent
+    output_root = project_root / OUTPUT_DIR
+
+    setup_logging(level=LOG_LEVEL, file=LOG_FILE)
+
+    # 加载 presets
+    presets_path = base_dir / "presets.json"
+    if presets_path.exists():
+        from agent.core.presets import load_presets_from_json
+        load_presets_from_json(str(presets_path))
+        print("已加载 presets")
+
+    # 读取需求
+    req_path = Path(args.requirements) if args.requirements else base_dir / "db_requirements.json"
+    if not req_path.exists():
+        print(f"错误: 需求文件不存在: {req_path}")
+        sys.exit(1)
+    with open(req_path, encoding='utf-8') as f:
+        requirements = json.load(f)
+    if not isinstance(requirements, list) or len(requirements) == 0:
+        print("错误: 需求文件必须是非空 JSON 数组")
+        sys.exit(1)
+
+    output_root.mkdir(parents=True, exist_ok=True)
+    store = FileSystemTraceStore(base_path=TRACE_STORE_PATH)
+    total = len(requirements)
+    start = args.from_index
+
+    print("=" * 60)
+    print(f"Process Research Pipeline v3")
+    print(f"执行引擎:{'并发多云并发 (Parallel)' if is_parallel else '单步串行序列 (Sequential)'}")
+    print(f"模式:{'远端 KnowHub' if use_remote else '本地'}")
+    if args.only_index is not None:
+        print(f"模式:仅执行第 {args.only_index} 个需求")
+    else:
+        print(f"共 {total} 个需求,从第 {start} 个开始")
+    print("=" * 60)
+
+    # IM 初始化(可选)
+    if IM_ENABLED and not use_remote:
+        from agent.tools.builtin.im.chat import im_setup, im_open_window
+        result = await im_setup(
+            contact_id=IM_CONTACT_ID,
+            server_url=IM_SERVER_URL,
+            notify_interval=IM_NOTIFY_INTERVAL,
+        )
+        print(f"IM: {result.output}")
+        if IM_WINDOW_MODE:
+            window_result = await im_open_window(contact_id=IM_CONTACT_ID)
+            print(f"IM: {window_result.output}")
+
+    # 初始化本地浏览器
+    if not use_remote:
+        print(f"正在初始化浏览器环境 ({browser_type})...")
+        await init_browser_session(
+            browser_type=browser_type,
+            headless=HEADLESS,
+            url="https://www.google.com/",
+            profile_name=""
+        )
+
+    # 本地模式:初始化 runner
+    runner = None
+    interactive = None
+    prompt = None
+    if not use_remote:
+        prompt_path = base_dir / "prompts" / "coordinator.prompt"
+        prompt = SimplePrompt(prompt_path)
+        prompt_model = prompt.config.get("model", None) or COORDINATOR_RUN_CONFIG.model
+        runner = AgentRunner(
+            trace_store=store,
+            llm_call=create_qwen_llm_call(model=prompt_model),
+            skills_dir=SKILLS_DIR,
+            debug=DEBUG,
+        )
+        interactive = InteractiveController(runner=runner, store=store, enable_stdin_check=True)
+        runner.stdin_check = interactive.check_stdin
+        print("💡 输入 'p' 暂停,'q' 退出")
+        print("=" * 60)
+
+    completed = 0
+    try:
+        for i, requirement in enumerate(requirements):
+            if args.only_index is not None:
+                if i != args.only_index:
+                    continue
+            else:
+                if i < start:
+                    continue
+
+            # 为防止两个模式同时跑起冲突,动态附加模式后缀
+            mode_suffix = "_parallel" if is_parallel else "_sequential"
+            req_output_dir = output_root / f"{(i+1):03d}{mode_suffix}"
+
+            if use_remote:
+                _, _ = await run_single_remote(
+                    requirement=requirement,
+                    output_dir=req_output_dir,
+                    req_index=i,
+                )
+            else:
+                _, should_exit = await run_single_local(
+                    runner=runner,
+                    interactive=interactive,
+                    store=store,
+                    prompt=prompt,
+                    requirement=requirement,
+                    output_dir=req_output_dir,
+                    req_index=i,
+                )
+                if should_exit:
+                    print(f"\n🛑 用户中止,已完成 {completed}/{total - start} 个需求")
+                    break
+
+            completed += 1
+            print(f"\n✓ [{(i+1):03d}] 完成,输出:{req_output_dir}")
+            print(f"  - case.json")
+            print(f"  - strategy.json")
+
+    except KeyboardInterrupt:
+        print(f"\n\n用户中断 (Ctrl+C),已完成 {completed}/{total - start} 个需求")
+    finally:
+        if not use_remote:
+            try:
+                await kill_browser_session()
+            except Exception:
+                pass
+
+    print()
+    print("=" * 60)
+    print(f"完成:{completed}/{total - start} 个需求")
+    print(f"输出根目录:{output_root}")
+    print("=" * 60)
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 6 - 4
knowhub/knowhub_db/pg_capability_store.py

@@ -28,7 +28,7 @@ _REL_SUBQUERIES = """
      FROM capability_knowledge ck WHERE ck.capability_id = capability.id) AS knowledge_ids
 """
 
-_BASE_FIELDS = "id, name, criterion, description"
+_BASE_FIELDS = "id, name, criterion, description, effects"
 
 _SELECT_FIELDS = f"{_BASE_FIELDS}, {_REL_SUBQUERIES}"
 
@@ -111,18 +111,20 @@ class PostgreSQLCapabilityStore:
         try:
             cursor.execute("""
                 INSERT INTO capability (
-                    id, name, criterion, description, embedding
-                ) VALUES (%s, %s, %s, %s, %s)
+                    id, name, criterion, description, effects, embedding
+                ) VALUES (%s, %s, %s, %s, %s, %s)
                 ON CONFLICT (id) DO UPDATE SET
                     name = EXCLUDED.name,
                     criterion = EXCLUDED.criterion,
                     description = EXCLUDED.description,
+                    effects = EXCLUDED.effects,
                     embedding = EXCLUDED.embedding
             """, (
                 cap['id'],
                 cap.get('name', ''),
                 cap.get('criterion', ''),
                 cap.get('description', ''),
+                json.dumps(cap.get('effects', [])),
                 cap.get('embedding'),
             ))
             self._save_relations(cursor, cap['id'], cap)
@@ -227,7 +229,7 @@ class PostgreSQLCapabilityStore:
         if not row:
             return None
         result = dict(row)
-        for field in ('requirement_ids', 'tool_ids', 'knowledge_ids'):
+        for field in ('requirement_ids', 'tool_ids', 'knowledge_ids', 'effects'):
             if field in result and isinstance(result[field], str):
                 result[field] = json.loads(result[field])
             elif field in result and result[field] is None:

BIN
out.txt


+ 0 - 16
scratch_db_caps.py

@@ -1,16 +0,0 @@
-import os
-from dotenv import load_dotenv
-load_dotenv("/root/Agent/.env")
-
-import psycopg2
-conn = psycopg2.connect(
-    host=os.getenv('KNOWHUB_DB'),
-    port=int(os.getenv('KNOWHUB_PORT', 5432)),
-    user=os.getenv('KNOWHUB_USER'),
-    password=os.getenv('KNOWHUB_PASSWORD'),
-    database=os.getenv('KNOWHUB_DB_NAME')
-)
-cursor = conn.cursor()
-cursor.execute("SELECT capability_id, COUNT(*) FROM capability_knowledge GROUP BY capability_id ORDER BY COUNT(*) DESC LIMIT 10")
-for row in cursor.fetchall():
-    print(row)

+ 0 - 21
scratch_db_test.py

@@ -1,21 +0,0 @@
-import os
-from dotenv import load_dotenv
-load_dotenv("/root/Agent/.env")
-
-import sys
-sys.path.append("/root/Agent")
-from knowhub.knowhub_db.pg_store import PostgreSQLStore
-
-store = PostgreSQLStore()
-print("Total knowledge:", store.count())
-
-try:
-    print("\n--- Searching for CAP-000 ---")
-    results = store.query("id != ''", limit=10, relation_filters={"capability_id": "CAP-000"})
-    print("CAP-000 count:", len(results))
-    
-    print("\n--- Searching for cap_0 ---")
-    results2 = store.query("id != ''", limit=10, relation_filters={"capability_id": "cap_0"})
-    print("cap_0 count:", len(results2))
-except Exception as e:
-    print("Error:", e)

+ 0 - 21
scratch_db_test_case.py

@@ -1,21 +0,0 @@
-import asyncio
-import os
-from dotenv import load_dotenv
-load_dotenv("/root/Agent/.env")
-
-import sys
-sys.path.append("/root/Agent")
-from knowhub.knowhub_db.pg_store import PostgreSQLStore
-
-store = PostgreSQLStore()
-
-try:
-    print("--- Searching for CAP-001 (Uppercase) ---")
-    results = store.query("id != ''", limit=10, relation_filters={"capability_id": "CAP-001"})
-    print("CAP-001 count:", len(results))
-    
-    print("\n--- Searching for cap-001 (Lowercase) ---")
-    results2 = store.query("id != ''", limit=10, relation_filters={"capability_id": "cap-001"})
-    print("cap-001 count:", len(results2))
-except Exception as e:
-    print("Error:", e)

+ 0 - 46
scratch_search_test.py

@@ -1,46 +0,0 @@
-import urllib.request, urllib.parse, json
-
-# First test: exactly what user provided
-params1 = urllib.parse.urlencode({
-    "q": "散景 浅景深 逆光 光斑 背景虚化 轮廓光",
-    "capability_id": "CAP-001",
-    "types": "strategy,case,tool",
-    "top_k": 10,
-    "min_score": 3
-})
-try:
-    req1 = urllib.request.Request(f'http://localhost:8000/api/knowledge/search?{params1}')
-    with urllib.request.urlopen(req1) as f:
-        print('Search 1 (all types): count =', json.loads(f.read().decode('utf-8')).get('count', 0))
-except Exception as e:
-    print('Search 1 error:', e)
-
-# Second test: only one type
-params2 = urllib.parse.urlencode({
-    "q": "散景 浅景深 逆光 光斑 背景虚化 轮廓光",
-    "capability_id": "CAP-001",
-    "types": "case",
-    "top_k": 10,
-    "min_score": 3
-})
-try:
-    req2 = urllib.request.Request(f'http://localhost:8000/api/knowledge/search?{params2}')
-    with urllib.request.urlopen(req2) as f:
-        print('Search 2 (single type case): count =', json.loads(f.read().decode('utf-8')).get('count', 0))
-except Exception as e:
-    print('Search 2 error:', e)
-
-# Third test: no types filter
-params3 = urllib.parse.urlencode({
-    "q": "散景 浅景深 逆光 光斑 背景虚化 轮廓光",
-    "capability_id": "CAP-001",
-    "top_k": 10,
-    "min_score": 3
-})
-try:
-    req3 = urllib.request.Request(f'http://localhost:8000/api/knowledge/search?{params3}')
-    with urllib.request.urlopen(req3) as f:
-        print('Search 3 (no types filter): count =', json.loads(f.read().decode('utf-8')).get('count', 0))
-except Exception as e:
-    print('Search 3 error:', e)
-

+ 0 - 18
scratch_test.py

@@ -1,18 +0,0 @@
-import asyncio
-import httpx
-import json
-
-async def main():
-    async with httpx.AsyncClient(timeout=30.0) as client:
-        # test search API
-        res = await client.get("http://localhost:8000/api/knowledge/search", params={"q": "test", "capability_id": "cap_0", "min_score": 1, "top_k": 5})
-        print("Search Response:")
-        print(json.dumps(res.json(), indent=2, ensure_ascii=False))
-
-        # test directly getting relations from DB or testing relation API
-        res2 = await client.get("http://localhost:8000/api/relation/capability_knowledge", params={"capability_id": "cap_0"})
-        print("Relation Response:")
-        print(json.dumps(res2.json(), indent=2, ensure_ascii=False))
-
-if __name__ == "__main__":
-    asyncio.run(main())

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
temp_b64.txt


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
temp_base64.txt


+ 26 - 0
test_anthropic.py

@@ -0,0 +1,26 @@
+import os
+import asyncio
+from anthropic import AsyncAnthropic
+from dotenv import load_dotenv
+
+load_dotenv()
+
+async def test():
+    client = AsyncAnthropic()
+    models = ["claude-sonnet-4-5"]
+    print(f"Base_URL: {client.base_url}")
+    
+    for model in models:
+        print(f"\nTesting Model: {model}")
+        try:
+            response = await client.messages.create(
+                model=model,
+                max_tokens=10,
+                messages=[{"role": "user", "content": "hi"}]
+            )
+            print("Success:", [b.text for b in response.content if b.type == 'text'])
+        except Exception as e:
+            print("Error:", repr(e))
+
+if __name__ == "__main__":
+    asyncio.run(test())

+ 28 - 0
test_openai.py

@@ -0,0 +1,28 @@
+import os
+import asyncio
+from openai import AsyncOpenAI
+from dotenv import load_dotenv
+
+load_dotenv()
+
+async def test():
+    client = AsyncOpenAI(
+        api_key=os.getenv("ANTHROPIC_API_KEY"),
+        base_url="https://imds.ai/v1"
+    )
+    models = ["gpt-4o", "claude-3.5-sonnet", "claude-3-5-sonnet-20240620"]
+    
+    for model in models:
+        print(f"\nTesting OpenAI-Compat Model: {model}")
+        try:
+            response = await client.chat.completions.create(
+                model=model,
+                max_tokens=10,
+                messages=[{"role": "user", "content": "hi"}]
+            )
+            print("Success:", response.choices[0].message.content)
+        except Exception as e:
+            print("Error:", repr(e))
+
+if __name__ == "__main__":
+    asyncio.run(test())

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است