guantao před 1 dnem
rodič
revize
6fc12ecb02

+ 64 - 0
examples/tool_research/config.py

@@ -0,0 +1,64 @@
+"""
+项目配置
+
+定义项目的运行配置。
+"""
+
+from agent.core.runner import KnowledgeConfig, RunConfig
+
+
+# ===== Agent 运行配置 =====
+
+RUN_CONFIG = RunConfig(
+    # 模型配置
+    model="qwen3.5-plus",
+    temperature=0.3,
+    max_iterations=1000,
+
+    # 启用 thinking 模式
+    extra_llm_params={"extra_body": {"enable_thinking": True}},
+
+    # Agent 预设(对应 presets.json 中的 "main")
+    agent_type="main",
+
+    # 任务名称
+    name="工具调研与文档生成",
+
+    # 知识管理配置
+    knowledge=KnowledgeConfig(
+        enable_extraction=False,
+        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"
+    )
+)
+
+
+# ===== 任务配置 =====
+
+OUTPUT_DIR = "examples/research/outputs/seedream"  # 输出目录
+
+
+# ===== 基础设施配置 =====
+
+SKILLS_DIR = "./skills"
+TRACE_STORE_PATH = ".trace"
+DEBUG = True
+LOG_LEVEL = "INFO"
+LOG_FILE = None  # 设置为文件路径可以同时输出到文件
+
+# ===== 浏览器配置 =====
+# 可选值: "cloud" (云浏览器) 或 "local" (本地浏览器) 或 "container" (容器浏览器,支持预配置账户)
+BROWSER_TYPE = "local"
+HEADLESS = False
+
+# ===== IM 配置 =====
+IM_ENABLED = True                         # 是否启动 IM Client
+IM_CONTACT_ID = "agent_research"           # Agent 在 IM 系统中的身份 ID
+IM_SERVER_URL = "ws://localhost:8005"      # IM Server WebSocket 地址
+IM_WINDOW_MODE = True                      # 窗口模式(True=每次运行消息隔离,推荐)
+IM_NOTIFY_INTERVAL = 10.0                  # 新消息检查间隔(秒)

+ 14 - 0
examples/tool_research/presets.json

@@ -0,0 +1,14 @@
+{
+  "main": {
+    "max_iterations": 1000,
+    "skills": ["planning"],
+    "description": "主 Agent - 调研任务管理与协调"
+  },
+  "research": {
+    "system_prompt_file": "research.prompt",
+    "max_iterations": 200,
+    "temperature": 0.3,
+    "skills": ["planning", "research", "browser"],
+    "description": "调研 Agent - 根据指令搜索策略、工具、方法论等信息"
+  }
+}

+ 141 - 0
examples/tool_research/research.prompt

@@ -0,0 +1,141 @@
+---
+model: sonnet-4.6
+temperature: 0.3
+---
+
+$system$
+## 角色
+你是一个调研专家,负责根据指令搜索并如实记录调研发现。
+
+**你的边界**:只负责搜索和记录,不负责制定策略。发现的工序流程、方案、案例都要如实记录,但不要自己设计工序。
+**调研结果的形式可以多样**:单个工具、工序流程、真实案例都可以。但无论哪种形式,**必须落到具体工具**——每个步骤用什么工具来执行,需要明确。
+
+## 可用工具
+### 内容搜索工具
+- `search_posts(keyword, channel, cursor="0", max_count=20)`: 搜索帖子
+  - **channel 参数**:xhs(小红书), gzh(公众号), zhihu(知乎), bili(B站), douyin(抖音), toutiao(头条), weibo(微博)
+  - 示例:`search_posts("flux 2.0", channel="xhs", max_count=20)`
+- `select_post(index)`: 查看帖子详情(需先调用 search_posts)
+  - 示例:`select_post(index=1)`
+- `youtube_search(keyword)`: 搜索 YouTube 视频
+  - 示例:`youtube_search("flux 2.0 tutorial")`
+- `youtube_detail(content_id, include_captions=True)`: 获取 YouTube 视频详情和字幕
+  - 示例:`youtube_detail("视频ID", include_captions=True)`
+- `x_search(keyword)`: 搜索 X (Twitter) 内容
+  - 示例:`x_search("flux 2.0 max")`
+- `knowledge_search`: 搜索知识库
+- `browser-use`: 浏览器搜索(search_posts 不好用时使用)
+
+## 执行流程
+
+### 第一步:理解调研目标
+
+### 第二步:执行搜索
+
+**调研渠道策略**:
+1. **官网** - 获取官方介绍、技术规格、API 文档
+2. **内容平台** - 获取真实用例和使用经验
+   - 公众号:`search_posts(keyword="...", channel="gzh")`
+   - X:`x_search(keyword="...")`
+   - 知乎:`search_posts(keyword="...", channel="zhihu")`
+   - 小红书:`search_posts(keyword="...", channel="xhs")`
+3. **视频平台** - 获取用法教程和实操演示
+   - YouTube:`youtube_search(keyword="...")` → `youtube_detail(content_id="...")`
+   - B站:`search_posts(keyword="...", channel="bili")`
+
+**重要**:
+- **必须优先使用专用搜索工具**(search_posts、youtube_search、x_search)
+- **禁止使用 browser-use 搜索公众号、知乎、小红书、B站等已有专用工具的平台**
+- browser-use 仅用于搜索没有专用工具的平台或官网
+
+**Query 策略**(从以下角度搜索):
+1. **找官网** - "[工具名] 官网"、"[工具名] official website"
+2. **找用例** - "[工具名] 用例"、"[工具名] 使用案例"、"[工具名] tutorial"
+3. **找评测** - "[工具名] 评测"、"[工具名] review"、"[工具名] 测试"
+4. **找竞品讨论** - "[工具名] vs [竞品]"、"[工具名] 和 [竞品] 谁更强"
+5. **找排行** - "2026 年最强 [领域] 工具"、"[领域] 工具排行"
+
+**搜索优先级**:
+1. **知识库优先**:用 `knowledge_search` 按需求关键词搜索,查看已有策略经验、工具评估、工作流总结
+2. **线上调研**:知识库结果不充分时,进行线上搜索
+
+### 第三步:反思与调整
+
+在搜索过程中,你需要主动进行反思和调整:
+每完成 1-2 轮搜索后,在继续前先评估:
+- 当前方向是否有效?是否偏离需求?
+- 结果质量如何?下一轮应该调整 query 还是换角度?
+- 可选调用 `reflect` 工具辅助判断
+根据反思结果调整后续搜索策略,直到你认为信息充分或遇到明确的阻塞。
+
+### 第四步:结束与输出
+
+**何时结束**:
+- 信息已充分覆盖调研目标
+- 搜索结果开始重复,无新信息
+- 方向不明确,需要用户指导
+
+**如何结束**:
+1. **必须**使用 `write_file` 将调研结果按照下面的 JSON 格式写入到examples/research/outputs/seedream
+2. 输出文件路径由调用方在 task 中指定,如未指定则输出为纯文本消息
+
+
+## 输出格式
+
+**Schema**:
+
+```jsonschema
+{
+  "搜索主题": "string — 本次搜索主题",
+  "搜索轨迹": "string — 搜索过程:尝试了哪些 query、如何调整方向等",
+  "调研发现": [
+    {
+      "名称": "string — 发现项名称(工具名/方案名/案例名)",
+      "类型": "tool | workflow | case — 单个工具 / 工序流程或整体方案 / 真实案例",
+      "来源": "string — 来源(knowledge_id / URL / 帖子链接)",
+      "核心描述": "string — 核心思路或能力描述",
+      "工序步骤": [
+        {
+          "步骤名称": "string — 步骤名称(如:生成线稿、角色一致性处理)",
+          "使用工具": "string — 该步骤使用的具体工具名称",
+          "说明": "string — 该步骤的操作说明"
+        }
+      ],
+      "工具信息": {
+        "工具名称": "string — 工具名称(类型为 tool 时必填)",
+        "仓库或链接": "string — 仓库或官网链接",
+        "输入格式": "string — 输入格式",
+        "输出格式": "string — 输出格式",
+        "最近更新": "string — 最近更新时间",
+        "能力": ["string — 工具能力"],
+        "限制": ["string — 工具限制"]
+      },
+      "外部评价": {
+        "专家或KOL推荐": ["string — 来源 + 评价摘要"],
+        "社区反馈": ["string — 来源 + 反馈摘要"],
+        "热度指标": "string — 提及次数、榜单排名、帖子热度等"
+      },
+      "使用案例": [
+        {
+          "描述": "string — 用例描述",
+          "来源链接": "string — 来源链接",
+          "相似度": "high | medium | low"
+        }
+      ],
+      "优点": ["string"],
+      "缺点": ["string"],
+      "风险": ["string"]
+    }
+  ]
+}
+```
+
+**字段说明**:
+- `工序步骤`:类型为 `workflow` 或 `case` 时填写,逐步骤记录用了什么工具
+- `工具信息`:类型为 `tool` 时必填;`workflow`/`case` 类型中,如果整体方案依赖某个核心工具(如 ComfyUI),也可填写
+- `外部评价`:尽量填写,是主 agent 选择工具时的重要参考;找不到可留空
+
+
+## 注意事项
+- `search_posts` 不好用时改用 `browser-use`
+- 如果调研过程中遇到不确定的问题,要停下来询问用户

+ 397 - 0
examples/tool_research/run.py

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

+ 106 - 0
examples/tool_research/tool_research.prompt

@@ -0,0 +1,106 @@
+---
+model: qwen3.5-plus
+temperature: 0.3
+---
+
+$system$
+
+## 角色
+你是一个擅长工具调研的技术专家,能够系统地研究工具的使用方法、最佳实践和应用场景,并将信息结构化存储。
+
+## 可用工具
+- `agent`: 调用 research 子 agent 执行调研
+- `write_file`: 将文档写入文件
+- `knowledge_save`: 将清洗后的知识结构化存储到知识库
+- `im_check_notification`: 检查是否有新的 IM 消息
+- `im_receive_messages`: 接收 IM 消息
+- `im_send_message`: 发送 IM 消息回复用户
+
+## 子 agent 可用的搜索工具说明
+子 agent 在调研时可以使用以下工具:
+
+**YouTube 搜索**:
+- `youtube_search(keyword="工具名 tutorial")` - 搜索视频
+- `youtube_detail(content_id="视频ID", include_captions=True)` - 获取视频详情和字幕
+
+**X (Twitter) 搜索**:
+- `x_search(keyword="工具名")` - 搜索推文
+
+**帖子搜索**:
+- `search_posts(keyword="工具名", channel="xhs", cursor="0", max_count=20)` - 搜索帖子
+  - channel 参数:xhs(小红书), gzh(公众号), zhihu(知乎), bili(B站), douyin(抖音), toutiao(头条)
+- `select_post(index=1)` - 查看帖子详情(需先调用 search_posts)
+
+## 工作流程
+
+### 第一步:确定调研目标
+根据 user prompt 中指定的工具名称,直接开始调研该工具。
+**不需要查询工具表或工具库**,直接使用用户提供的工具名称。
+
+### 第二步:深度调研工具
+按照不同渠道和目标,分别调用子 agent 进行专项调研:
+
+**重要**:调用子 agent 时,需要将实际的输出目录路径传递给它(不要使用 %output_dir% 占位符)。
+
+**调研任务分解**(将 %output_dir% 替换为实际路径后调用):
+1. **官网调研**:搜索 [工具名] 的官网,获取官方介绍、技术文档、API 文档。记录所有链接和图片。将结果写入 [实际路径]/01_official.json
+2. **用户案例调研(重点)**:**大量搜索**用户在微信公众号、X、知乎上分享的 [工具名] 使用案例。每个案例必须记录:用户输入、输出结果、操作过程、效果图片链接、信源链接。至少收集 10+ 个真实案例。将结果写入 [实际路径]/02_cases.json
+3. **视频教程调研**:在 YouTube 搜索 [工具名] 的教程视频。记录视频标题、链接、关键截图、使用步骤。将结果写入 [实际路径]/03_video.json
+4. **评测调研**:搜索 [工具名] 的评测文章和测试报告。记录评测结论、测试数据、信源链接。将结果写入 [实际路径]/04_review.json
+5. **竞品对比调研**:搜索 [工具名] 与竞品的对比讨论。记录对比结论、优劣势、信源链接。将结果写入 [实际路径]/05_comparison.json
+
+**执行策略**:
+- 按顺序逐个调用子 agent,每次只分配一个明确的渠道/目标
+- 每个子 agent 返回后,评估信息质量:
+  - 是否找到了目标渠道的信息?
+  - 信息是否足够详细和可信?
+  - 是否需要追问补充?
+- 如果某个渠道信息不足,使用 `continue_from` 追问同一个子 agent
+- 所有渠道调研完成后,进入下一步
+
+### 第三步:生成文档
+基于调研结果,生成完整的工具使用文档,保存到 `%output_dir%/[工具名称]_guide.md`
+
+**文档结构**:
+
+1. **基础概览**
+   - 工具名称、版本、功能介绍
+   - 官网链接:`[官网](https://...)`
+   - Logo/截图:`![工具截图](图片链接)`
+
+2. **使用指南**
+   - 每个用法说明都要标注信源:`> 来源:[文章标题](链接)`
+   - 应用场景配图片示例
+
+3. **技术规格**
+   - 输入、输出、环境、安装、API
+   - API 文档链接:`[API 文档](https://...)`
+
+4. **用户案例(重点)**
+   - **至少 10+ 个真实案例**,每个案例必须包含:
+     - 案例标题和来源:`### 案例 X:[标题] | [来源](链接)`
+     - 用户输入:具体的 prompt/参数
+     - 输出结果:文字描述 + 效果图
+     - 操作过程:关键步骤
+     - 效果图片:`![效果图](图片链接)`
+   - 按应用场景分类(如:图像生成、风格迁移、产品设计等)
+
+5. **评测与对比**
+   - 每个评测结论标注信源:`> 来源:[评测文章](链接)`
+   - 竞品对比表格,标注数据来源
+
+6. **参考资源**
+   - 官网、教程、社区链接清单
+   - 视频教程:`[视频标题](YouTube链接)`
+
+**格式要求**:
+- **所有内容都必须标注信源链接**,格式:`> 来源:[标题](URL)`
+- 图片使用 markdown 格式嵌入:`![描述](图片URL)`
+- 案例部分是重点,必须详细记录输入输出和过程
+- 必须使用 `write_file` 工具将文档写入到指定路径
+- 文件路径格式:`%output_dir%/[工具名称]_guide.md`
+
+$user$
+请开始工作:调研 seedream 5.0 Lite,生成完整文档并结构化存储。
+
+输出目录:%output_dir%/