Talegorithm 1 неделя назад
Родитель
Сommit
2f341faef1

+ 6 - 0
agent/core/runner.py

@@ -291,16 +291,22 @@ class AgentRunner:
         self,
         messages: List[Dict],
         config: Optional[RunConfig] = None,
+        on_event: Optional[Callable] = None,
     ) -> Dict[str, Any]:
         """
         结果模式 — 消费 run(),返回结构化结果。
 
         主要用于 agent/evaluate 工具内部。
+
+        Args:
+            on_event: 可选回调,每个 Trace/Message 事件触发一次,用于实时输出子 Agent 执行过程。
         """
         last_assistant_text = ""
         final_trace: Optional[Trace] = None
 
         async for item in self.run(messages=messages, config=config):
+            if on_event:
+                on_event(item)
             if isinstance(item, Message) and item.role == "assistant":
                 content = item.content
                 text = ""

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

@@ -276,6 +276,48 @@ def _build_evaluate_prompt(goal_description: str, messages: Optional[Messages])
     return "\n".join(lines)
 
 
+def _make_event_printer(label: str):
+    """
+    创建子 Agent 执行过程打印函数。
+
+    当父 runner.debug=True 时,传给 run_result(on_event=...),
+    实时输出子 Agent 的工具调用和助手消息。
+    """
+    prefix = f"  [{label}]"
+
+    def on_event(item):
+        from agent.trace.models import Trace, Message
+        if isinstance(item, Message):
+            if item.role == "assistant":
+                content = item.content
+                if isinstance(content, dict):
+                    text = content.get("text", "")
+                    tool_calls = content.get("tool_calls")
+                    if text:
+                        preview = text[:120] + "..." if len(text) > 120 else text
+                        print(f"{prefix} {preview}")
+                    if tool_calls:
+                        for tc in tool_calls:
+                            name = tc.get("function", {}).get("name", "unknown")
+                            print(f"{prefix} 🛠️  {name}")
+            elif item.role == "tool":
+                content = item.content
+                if isinstance(content, dict):
+                    name = content.get("tool_name", "unknown")
+                    desc = item.description or ""
+                    desc_short = (desc[:60] + "...") if len(desc) > 60 else desc
+                    suffix = f": {desc_short}" if desc_short else ""
+                    print(f"{prefix} ✅ {name}{suffix}")
+        elif isinstance(item, Trace):
+            if item.status == "completed":
+                print(f"{prefix} ✓ 完成")
+            elif item.status == "failed":
+                err = (item.error_message or "")[:80]
+                print(f"{prefix} ✗ 失败: {err}")
+
+    return on_event
+
+
 # ===== 统一内部执行函数 =====
 
 async def _run_agents(
@@ -365,6 +407,10 @@ async def _run_agents(
         agent_msgs = list(msgs) + [{"role": "user", "content": task_item}]
         allowed_tools = _get_allowed_tools(single, context)
 
+        debug = getattr(runner, 'debug', False)
+        agent_label = (agent_type or ("delegate" if single else f"explore-{i+1}"))
+        on_event = _make_event_printer(agent_label) if debug else None
+
         coro = runner.run_result(
             messages=agent_msgs,
             config=_make_run_config(
@@ -376,6 +422,7 @@ async def _run_agents(
                 name=task_item[:50],
                 skills=skills,
             ),
+            on_event=on_event,
         )
         coros.append((i, cur_stid, collab_name, coro))
 
@@ -664,6 +711,7 @@ async def evaluate(
                 tools=allowed_tools,
                 name=f"评估: {goal_id}",
             ),
+            on_event=_make_event_printer("evaluate") if getattr(runner, 'debug', False) else None,
         )
 
         await broadcast_sub_trace_completed(

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

@@ -0,0 +1,26 @@
+{
+  "channel_content_id": "616192600000000021034642",
+  "link": "https://www.xiaohongshu.com/explore/616192600000000021034642",
+  "comment_count": 0,
+  "images": [
+    "http://res.cybertogether.net/crawler/image/5b94399f3bdef0a80b98e2734e110ca2.jpeg",
+    "http://res.cybertogether.net/crawler/image/6d80c193ccd0b047e0f3354ed6aca355.jpeg",
+    "http://res.cybertogether.net/crawler/image/2ba333062a7370ce229696fc36b9a060.jpeg",
+    "http://res.cybertogether.net/crawler/image/8187a1ad4e56295ab13d881d0ef7c934.jpeg",
+    "http://res.cybertogether.net/crawler/image/16fc8596b7c12031e910eb517859045c.jpeg",
+    "http://res.cybertogether.net/crawler/image/15a29cb486344bc10e90402371e21c92.jpeg",
+    "http://res.cybertogether.net/crawler/image/e70bbea964cfcf0225744da00e8e7939.jpeg",
+    "http://res.cybertogether.net/crawler/image/d20b73ad445c7dce64983159bc6cdae0.jpeg",
+    "http://res.cybertogether.net/crawler/image/c4c73c1b32f8066cc40a43ce61f61364.jpeg"
+  ],
+  "like_count": 411,
+  "body_text": "听闻秋日是倒放的春天\n于是我心中有一座秋日的花园\n栽种着一簇簇淡却温暖的花\n风沿着远边的山吹来\n热情的阳光里秋风微凉\n与颜料一起酝酿出的画面\n白裙是一抹无暇\n迎着光绘画出\n那片在我心上开满\n限定的浪漫\n被画架支起\n绿草坪还驻留了匆匆而过的热闹\n再添一笔白\n为我画一枝玫瑰的奇遇\n———@万淮 #草地拍照[话题]##画画[话题]#",
+  "title": "《秋日际遇》写生油画",
+  "collect_count": 181,
+  "channel_account_id": "584fc4a36a6a693eef600ec3",
+  "channel_account_name": "糯米和Kilala",
+  "content_type": "note",
+  "video": "",
+  "publish_timestamp": 1633784416000,
+  "publish_time": "2021-10-09 21:00:16"
+}

+ 2 - 2
examples/how/presets.json

@@ -2,13 +2,13 @@
   "deconstruct": {
     "max_iterations": 500,
     "temperature": 0.3,
-    "skills": ["planning", "deconstruct"],
+    "skills": ["planning", "research", "browser", "deconstruct"],
     "description": "解构 Agent,将社交媒体帖子解构为可还原的结构化制作脚本"
   },
   "construct": {
     "max_iterations": 500,
     "temperature": 0.7,
-    "skills": ["planning", "construct"],
+    "skills": ["planning", "research", "browser", "construct"],
     "description": "建构 Agent,基于解构产物生成内容并输出执行报告"
   }
 }

+ 1 - 1
examples/how/test.prompt → examples/how/production.prompt

@@ -37,7 +37,7 @@ $system$
 
 ## 最终输出
 
-输出最终解构产物 JSON,并附上一段简短的研究备注(这篇内容的核心创作规律是什么,迭代过程中发现了什么)。
+输出最终解构产物 制作表JSON,并附上一段简短的研究备注(这篇内容的核心创作规律是什么,迭代过程中发现了什么)。
 
 $user$
 请对下面这篇社交媒体帖子进行解构-建构-评估迭代,产出高质量解构产物。

+ 1 - 1
examples/how/run.py

@@ -207,7 +207,7 @@ async def main():
     # 路径配置
     base_dir = Path(__file__).parent
     project_root = base_dir.parent.parent
-    prompt_path = base_dir / "test.prompt"
+    prompt_path = base_dir / "production.prompt"
     output_dir = base_dir / "output_1"
     output_dir.mkdir(exist_ok=True)