diff --git a/frontend/src/dev/preview.tsx b/frontend/src/dev/preview.tsx index 71ffebaa..225b2668 100644 --- a/frontend/src/dev/preview.tsx +++ b/frontend/src/dev/preview.tsx @@ -27,6 +27,7 @@ const CASES: { key: Bucket; question: string; phase: StreamTurn['phase']; status { key: 'issue', question: '出一份本月经营分析', phase: 'done', status: '完成' }, { key: 'failed', question: '查一下不存在的那张表', phase: 'error', status: '' }, { key: 'loop_approve', question: '写条上线公告,我过一眼再发', phase: 'done', status: '完成' }, + { key: 'supervisor', question: '冬虫夏草是什么', phase: 'done', status: '完成' }, ] function buildTurns(): StreamTurn[] { diff --git a/frontend/src/run/__tests__/fixtures.json b/frontend/src/run/__tests__/fixtures.json index 1d1c61be..6cc267bc 100644 --- a/frontend/src/run/__tests__/fixtures.json +++ b/frontend/src/run/__tests__/fixtures.json @@ -1661,5 +1661,374 @@ "duration_ms": 21 } } + ], + "supervisor": [ + { + "seq": 1, + "type": "run.started", + "node_id": null, + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "nodes": 6, + "resumed": false + } + }, + { + "seq": 2, + "type": "node.started", + "node_id": "start", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "node_type": "input", + "label": "输入" + } + }, + { + "seq": 3, + "type": "node.finished", + "node_id": "start", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "duration_ms": 0, + "attempt": 1, + "preview": { + "question": "冬虫夏早是什么" + }, + "artifact": "f9c6433f494cdefe5bca6d41417e8d0b2d701f73fad61c15ff2a1086a1416e8d" + } + }, + { + "seq": 4, + "type": "node.started", + "node_id": "research_supervisor", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "node_type": "supervisor", + "label": "多智能体研究" + } + }, + { + "seq": 5, + "type": "log", + "node_id": "research_supervisor", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "level": "info", + "round": 0, + "message": "调度 → researcher(当前尚无任何资料,需要先由研究员进行多角度资料收集与整理。)" + } + }, + { + "seq": 6, + "type": "agent.step.start", + "node_id": "research_supervisor", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "agent": "researcher", + "instruction": "针对用户问题,进行多角度研究并生成一份结构化的答案草案", + "round": 0 + } + }, + { + "seq": 7, + "type": "agent.step.end", + "node_id": "research_supervisor", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "agent": "researcher", + "duration_ms": 6660, + "round": 0, + "preview": "我看到你布置了研究任务,但消息里还没有具体的**用户问题**——也就是团队要围绕哪个主题展开多角度研究。\n\n请你把需要研究的问题告诉我(比如:「大模型推理成本为何下降这么快?」「2025年哪些行业最适合AI落地?」「如何评估Agent框架的选型?」……),我收到后就会立刻开始:\n\n1. **多角度检索**:联网搜索 + 知识库检索,覆盖不同立场、时间、信息源;\n2. **整合观点**:提炼共识与分歧,标注关键事实与来源;\n3. **输出结构化草案**:按「问题拆解 → 核心结论 → 分论点论证 → 争议/不确定点 → 待补充信息」的框架交付,方便后续团队补充或改写。\n\n请把问题发我,我马上开工。" + } + }, + { + "seq": 8, + "type": "log", + "node_id": "research_supervisor", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "level": "info", + "round": 1, + "message": "调度 → FINISH(用户问题尚未提供,无法进行多角度研究和生成草案,需等待用户输入。)" + } + }, + { + "seq": 9, + "type": "node.finished", + "node_id": "research_supervisor", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "duration_ms": 31723, + "attempt": 1, + "preview": { + "text": "「大模型推理成本为何下降这么快?」「2025年哪些行业最适合AI落地?」「如何评估Agent框架的选型?」……),我收到后就会立刻开始:\n\n1. **多角度检索**:联网搜索 + 知识库检索,覆盖不同立场、时间、信息源;\n2. **整合观点**:提炼共识与分歧,标注关键事实与来源;\n3. **输出结构化草案**:按「问题拆解 → 核心结论 → 分论点论证 → 争议/不确定点 → 待补充信息」的框架交付,方便后续团队补充或改写。\n\n请把问题发我,我马上开工。", + "rounds": 1, + "transcript": "[1 项]", + "agents": "[2 项]" + }, + "artifact": "0ac37d4ded467d994cc7e7ab0e60bfbd0ec5834fdb9383568824110c1f5eeecd" + } + }, + { + "seq": 10, + "type": "node.started", + "node_id": "refine_answer", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "node_type": "llm", + "label": "润色答案" + } + }, + { + "seq": 11, + "type": "llm.start", + "node_id": "refine_answer", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "model": "deepseek-v4-pro", + "message_count": 2 + } + }, + { + "seq": 130, + "type": "llm.end", + "node_id": "refine_answer", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "model": "deepseek-v4-pro", + "duration_ms": 35492, + "input_tokens": 173, + "output_tokens": 1620, + "total_tokens": 1793, + "cost_usd": 0.0, + "calls": 1 + } + }, + { + "seq": 131, + "type": "node.finished", + "node_id": "refine_answer", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "duration_ms": 35494, + "attempt": 1, + "preview": { + "text": "我看了你提供的“多智能体研究结果”,发现它并不是实际的研究数据或整合后的观点,而是一段**任务接收与流程说明**(包括检索方式、整合方法、输出框架,以及“请把问题发我”的结尾)。\n\n因此,我暂时无法基于这段内容生成一个实质性的、面向用户的最终答案——因为里面没有可供提炼的事实、观点、共识或分歧。\n\n### 我需要你补充以下任一信息,才能继续:\n\n1. **实际研究结果** \n 例如多智能体检索到的关键事实、数据、来源链接、不同观点摘要、争议点等。 \n 把这些内容粘贴给我,我会按你要求的框架整理成最终答案。\n\n2. **明确要回答的问题** \n 如果你希望我直接回答类似「大模型推理成本为何下降这么快?」「2025年哪些行业最适合AI落地?」「如何评估Agent框架的选型?」这类问题,请指定一个或多个,我可以基于已有知识按框架输出,但需要说明这不是基于你提供的“研究结果”。\n\n3. **确认是否粘贴有误** \n 如果这段文字就是你希望我处理的全部内容,可能你原本想粘贴的研究材料没有成功发送。请重新复制实际结果后再发一次。\n\n---\n\n只要你补齐材料或明确需求,我会立刻按「问题拆解 → 核心结论 → 分论点论证 → 争议/不确定点 → 待补充信息」的结构交付完整答案。", + "thinking": "", + "model": "deepseek-v4-pro", + "duration_ms": 35492 + }, + "artifact": "c2c32c007b0256315ebbb48b890831f5d78dd9f0a1909cf4812abbc1397a8d1b" + } + }, + { + "seq": 132, + "type": "node.started", + "node_id": "validate_output", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "node_type": "validate", + "label": "校验输出格式" + } + }, + { + "seq": 133, + "type": "log", + "node_id": "validate_output", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "level": "warn", + "message": "第 1 次校验失败:输出不是合法 JSON" + } + }, + { + "seq": 134, + "type": "log", + "node_id": "validate_output", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "level": "info", + "message": "校验通过(第 2 次尝试)" + } + }, + { + "seq": 135, + "type": "node.finished", + "node_id": "validate_output", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "duration_ms": 23747, + "attempt": 1, + "preview": { + "valid": true, + "data": "{'answer': '我看了你提供的“多智能体研究结果”,发现它并不是实际的研究数据或整合后的观点,而是一段**任务接收与流程说明**(包括检索方式、整合方法、输出框架,以及“请把问题发我”的结尾)。\\n\\n因此,我暂时无法基于这段内容生成一个实质性的、面向用户的最终答案——因为里面没有可供提炼的事实、观点、共识或分歧。\\n\\n### 我需要你补充以下任一信息,才能继续:\\n\\n1. **实际研究结果** \\n 例如多智能体检索到的关键事实、数据、来源链接、不同观点摘要、争议点等。 \\n 把这些内容粘贴给我,我会按你要求的框架整理成最终答案。\\n\\n2. **明确要回答的问题**", + "attempts": 2, + "errors": "[0 项]" + }, + "artifact": "3a13b15248aace0f21fe37f9fb197beda8ff71da3e8c5d370b092e95cefa181c" + } + }, + { + "seq": 136, + "type": "node.started", + "node_id": "human_review", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "node_type": "human", + "label": "人工审核" + } + }, + { + "seq": 137, + "type": "human.requested", + "node_id": "human_review", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "kind": "human_node", + "node_id": "human_review", + "mode": "approve", + "title": "请审核最终答案", + "message": "以下是系统生成的答案,请确认是否通过:\n\n我看了你提供的“多智能体研究结果”,发现它并不是实际的研究数据或整合后的观点,而是一段**任务接收与流程说明**(包括检索方式、整合方法、输出框架,以及“请把问题发我”的结尾)。\n\n因此,我暂时无法基于这段内容生成一个实质性的、面向用户的最终答案——因为里面没有可供提炼的事实、观点、共识或分歧。\n\n### 我需要你补充以下任一信息,才能继续:\n\n1. **实际研究结果** \n 例如多智能体检索到的关键事实、数据、来源链接、不同观点摘要、争议点等。 \n 把这些内容粘贴给我,我会按你要求的框架整理成最终答案。\n\n2. **明确要回答的问题** \n 如果你希望我直接回答类似「大模型推理成本为何下降这么快?」「2025年哪些行业最适合AI落地?」「如何评估Agent框架的选型?」这类问题,请指定一个或多个,我可以基于已有知识按框架输出,但需要说明这不是基于你提供的“研究结果”。\n\n3. **确认是否粘贴有误** \n 如果这段文字就是你希望我处理的全部内容,可能你原本想粘贴的研究材料没有成功发送。请重新复制实际结果后再发一次。\n\n---\n\n只要你补齐材料或明确需求,我会立刻按「问题拆解 → 核心结论 → 分论点论证 → 争议/不确定点 → 待补充信息」的结构交付完整答案。", + "schema": {}, + "context": {} + } + }, + { + "seq": 138, + "type": "run.interrupted", + "node_id": "human_review", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "interrupt_id": "c10f17a223b83dcbf2ab9f023f5d15a8", + "payload": { + "kind": "human_node", + "node_id": "human_review", + "mode": "approve", + "title": "请审核最终答案", + "message": "以下是系统生成的答案,请确认是否通过:\n\n我看了你提供的“多智能体研究结果”,发现它并不是实际的研究数据或整合后的观点,而是一段**任务接收与流程说明**(包括检索方式、整合方法、输出框架,以及“请把问题发我”的结尾)。\n\n因此,我暂时无法基于这段内容生成一个实质性的、面向用户的最终答案——因为里面没有可供提炼的事实、观点、共识或分歧。\n\n### 我需要你补充以下任一信息,才能继续:\n\n1. **实际研究结果** \n 例如多智能体检索到的关键事实、数据、来源链接、不同观点摘要、争议点等。 \n 把这些内容粘贴给我,我会按你要求的框架整理成最终答案。\n\n2. **明确要回答的问题** \n 如果你希望我直接回答类似「大模型推理成本为何下降这么快?」「2025年哪些行业最适合AI落地?」「如何评估Agent框架的选型?」这类问题,请指定一个或多个,我可以基于已有知识按框架输出,但需要说明这不是基于你提供的“研究结果”。\n\n3. **确认是否粘贴有误** \n 如果这段文字就是你希望我处理的全部内容,可能你原本想粘贴的研究材料没有成功发送。请重新复制实际结果后再发一次。\n\n---\n\n只要你补齐材料或明确需求,我会立刻按「问题拆解 → 核心结论 → 分论点论证 → 争议/不确定点 → 待补充信息」的结构交付完整答案。", + "schema": {}, + "context": {} + } + } + }, + { + "seq": 139, + "type": "run.resumed", + "node_id": null, + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "response": { + "approved": true, + "note": "" + } + } + }, + { + "seq": 140, + "type": "run.started", + "node_id": null, + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "nodes": 6, + "resumed": true + } + }, + { + "seq": 141, + "type": "node.started", + "node_id": "human_review", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "node_type": "human", + "label": "人工审核" + } + }, + { + "seq": 142, + "type": "human.requested", + "node_id": "human_review", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "kind": "human_node", + "node_id": "human_review", + "mode": "approve", + "title": "请审核最终答案", + "message": "以下是系统生成的答案,请确认是否通过:\n\n我看了你提供的“多智能体研究结果”,发现它并不是实际的研究数据或整合后的观点,而是一段**任务接收与流程说明**(包括检索方式、整合方法、输出框架,以及“请把问题发我”的结尾)。\n\n因此,我暂时无法基于这段内容生成一个实质性的、面向用户的最终答案——因为里面没有可供提炼的事实、观点、共识或分歧。\n\n### 我需要你补充以下任一信息,才能继续:\n\n1. **实际研究结果** \n 例如多智能体检索到的关键事实、数据、来源链接、不同观点摘要、争议点等。 \n 把这些内容粘贴给我,我会按你要求的框架整理成最终答案。\n\n2. **明确要回答的问题** \n 如果你希望我直接回答类似「大模型推理成本为何下降这么快?」「2025年哪些行业最适合AI落地?」「如何评估Agent框架的选型?」这类问题,请指定一个或多个,我可以基于已有知识按框架输出,但需要说明这不是基于你提供的“研究结果”。\n\n3. **确认是否粘贴有误** \n 如果这段文字就是你希望我处理的全部内容,可能你原本想粘贴的研究材料没有成功发送。请重新复制实际结果后再发一次。\n\n---\n\n只要你补齐材料或明确需求,我会立刻按「问题拆解 → 核心结论 → 分论点论证 → 争议/不确定点 → 待补充信息」的结构交付完整答案。", + "schema": {}, + "context": {} + } + }, + { + "seq": 143, + "type": "human.resolved", + "node_id": "human_review", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "response": { + "approved": true, + "note": "" + } + } + }, + { + "seq": 144, + "type": "node.finished", + "node_id": "human_review", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "duration_ms": 0, + "attempt": 1, + "preview": { + "approved": true, + "note": "", + "__decision__": "approved" + }, + "artifact": "37c6c8932b33766e3422b1f6ba5cb0548613f0b734b2ac3a22a3decd3cf053b6" + } + }, + { + "seq": 145, + "type": "node.started", + "node_id": "done", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "node_type": "output", + "label": "成果" + } + }, + { + "seq": 146, + "type": "node.finished", + "node_id": "done", + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "duration_ms": 0, + "attempt": 1, + "preview": { + "结果": "我看了你提供的“多智能体研究结果”,发现它并不是实际的研究数据或整合后的观点,而是一段**任务接收与流程说明**(包括检索方式、整合方法、输出框架,以及“请把问题发我”的结尾)。\n\n因此,我暂时无法基于这段内容生成一个实质性的、面向用户的最终答案——因为里面没有可供提炼的事实、观点、共识或分歧。\n\n### 我需要你补充以下任一信息,才能继续:\n\n1. **实际研究结果** \n 例如多智能体检索到的关键事实、数据、来源链接、不同观点摘要、争议点等。 \n 把这些内容粘贴给我,我会按你要求的框架整理成最终答案。\n\n2. **明确要回答的问题** \n 如果你希望我直接回答类似「大模型推理成本为何下降这么快?」「2025年哪些行业最适合AI落地?」「如何评估Agent框架的选型?」这类问题,请指定一个或多个,我可以基于已有知识按框架输出,但需要说明这不是基于你提供的“研究结果”。\n\n3. **确认是否粘贴有误** \n 如果这段文字就是你希望我处理的全部内容,可能你原本想粘贴的研究材料没有成功发送。请重新复制实际结果后再发一次。\n\n---\n\n只要你补齐材料或明确需求,我会立刻按「问题拆解 → 核心结论 → 分论点论证 → 争议/不确定点 → 待补充信息」的结构交付完整答案。" + }, + "artifact": "583c1a86a3919fe393dd0c2dffb91e17df708333b6f78a3573b616de5661365c" + } + }, + { + "seq": 147, + "type": "run.finished", + "node_id": null, + "run_id": "6038f54476a84a7b8fcf519de1f4ebe1", + "data": { + "output": { + "结果": "我看了你提供的“多智能体研究结果”,发现它并不是实际的研究数据或整合后的观点,而是一段**任务接收与流程说明**(包括检索方式、整合方法、输出框架,以及“请把问题发我”的结尾)。\n\n因此,我暂时无法基于这段内容生成一个实质性的、面向用户的最终答案——因为里面没有可供提炼的事实、观点、共识或分歧。\n\n### 我需要你补充以下任一信息,才能继续:\n\n1. **实际研究结果** \n 例如多智能体检索到的关键事实、数据、来源链接、不同观点摘要、争议点等。 \n 把这些内容粘贴给我,我会按你要求的框架整理成最终答案。\n\n2. **明确要回答的问题** \n 如果你希望我直接回答类似「大模型推理成本为何下降这么快?」「2025年哪些行业最适合AI落地?」「如何评估Agent框架的选型?」这类问题,请指定一个或多个,我可以基于已有知识按框架输出,但需要说明这不是基于你提供的“研究结果”。\n\n3. **确认是否粘贴有误** \n 如果这段文字就是你希望我处理的全部内容,可能你原本想粘贴的研究材料没有成功发送。请重新复制实际结果后再发一次。\n\n---\n\n只要你补齐材料或明确需求,我会立刻按「问题拆解 → 核心结论 → 分论点论证 → 争议/不确定点 → 待补充信息」的结构交付完整答案。" + }, + "usage": { + "input_tokens": 791, + "output_tokens": 1885, + "cost_usd": 0.0, + "calls": 4, + "total_tokens": 2676, + "duration_ms": 19 + }, + "duration_ms": 19 + } + } ] } \ No newline at end of file diff --git a/frontend/src/run/decode.ts b/frontend/src/run/decode.ts index 2208fd44..f5885fb1 100644 --- a/frontend/src/run/decode.ts +++ b/frontend/src/run/decode.ts @@ -239,6 +239,16 @@ export function summarizeRun( return pick(output) || pick(input) } +/** + * agent 步骤的配对键。 + * + * 光用 agent 名不够:supervisor 会在多轮里反复派给同一个 agent,第二轮的 + * end 会错配到第一轮的 start 上。round 才是区分轮次的那一维。 + */ +function agentKey(nodeId: string | undefined, d: any): string { + return [nodeId ?? '_', String(d?.agent ?? ''), String(d?.round ?? '')].join('|') +} + function toolStep(seq: number, tool: string, args: Record): Step { const base = { id: `tool-${seq}`, seq, status: 'running' as StepStatus } // 数据库工具单独认:它是这个产品最主要的取数方式,"调用 db_query__warehouse" @@ -272,6 +282,8 @@ export function decodeRun(events: RunEvent[]): Step[] { const pendingTools = new Map() /** node_id → 待配对的模型调用 Step */ const pendingLlm = new Map() + /** supervisor 里待配对的单个 agent 步骤 */ + const pendingAgents = new Map() /** * node_id → 这个节点当前那条尚未闭合的审批步骤。 * @@ -565,24 +577,61 @@ export function decodeRun(events: RunEvent[]): Step[] { }) break - case 'agent.step.start': - push({ + case 'agent.step.start': { + // 和 tool.start/tool.end 一样是一件事的两个时刻。拆成两行的话, + // "派给 researcher 什么任务"和"它回了什么"会变成两条互不相干的记录, + // 而且 start 那条永远停在转圈——多 agent 节点跑完了它还在转。 + const step: Step = { id: `as-${seq}`, seq, kind: 'note', nodeId, status: 'running', title: `${d.agent}:${String(d.instruction ?? '').slice(0, 80)}`, - }, nodeId) + } + pendingAgents.set(agentKey(nodeId, d), step) + push(step, nodeId) break + } - case 'agent.step.end': - push({ - id: `ae-${seq}`, seq, kind: 'note', nodeId, status: 'done', - title: `${d.agent} 回复`, meta: dur(num(d.duration_ms)), - detail: String(d.preview ?? '').slice(0, 2000), - }, nodeId) + case 'agent.step.end': { + const key = agentKey(nodeId, d) + const step = pendingAgents.get(key) + const preview = String(d.preview ?? '').slice(0, 2000) + if (step) { + step.status = 'done' + step.meta = dur(num(d.duration_ms)) + step.result = preview || undefined + pendingAgents.delete(key) + } else { + push({ + id: `ae-${seq}`, seq, kind: 'note', nodeId, status: 'done', + title: `${d.agent} 回复`, meta: dur(num(d.duration_ms)), + detail: preview, + }, nodeId) + } break + } case 'log': { const level = String(d.level ?? 'info') - // info 级日志是给排查用的,不进主流程——但 warn/error 用户必须看到 + // supervisor 的调度决策后端标成了 info,但它不是排查用的日志—— + // "为什么派给 researcher"、"为什么只跑一轮就 FINISH",不显示的话 + // 多 agent 节点在界面上就是一个跑了 31 秒的黑盒。带 round 字段的 + // 就是它,和普通 info 日志区分得开 + if (level === 'info' && d.round != null) { + const text = String(d.message ?? '') + const m = text.match(/^调度\s*→\s*([^((]+)[((](.*)[))]\s*$/) + // FINISH 是协议里的收尾标记,不是某个 agent。"交给 FINISH" + // 会让人以为还有个叫 FINISH 的成员 + const target = m?.[1].trim() ?? '' + const round = Number(d.round) + 1 + push({ + id: `sv-${seq}`, seq, kind: 'branch', nodeId, status: 'done', + title: !m ? text + : target === 'FINISH' ? `第 ${round} 轮:结束协作` + : `第 ${round} 轮:交给 ${target}`, + detail: m ? m[2].trim() : undefined, + }, nodeId) + break + } + // 其余 info 是给排查用的,不进主流程——但 warn/error 用户必须看到 if (level === 'info') break push({ id: `lg-${seq}`, seq, kind: 'note', nodeId, status: 'done', diff --git a/scripts/check-decode.mjs b/scripts/check-decode.mjs index 12cc0481..26a87632 100644 --- a/scripts/check-decode.mjs +++ b/scripts/check-decode.mjs @@ -118,6 +118,38 @@ console.log('\n=== 循环里的多轮审批 ===') check('跑完了没有还在转的行', !all.some((s) => s.status === 'running')) } +console.log('\n=== 多 agent 协作 ===') +{ + // 真实运行 #6038f5:supervisor 派给 researcher 一次就 FINISH 了。 + // 界面上那个多智能体节点跑完 31.7s 之后,researcher 那行还在转圈。 + const steps = mod.decodeRun(fixtures.supervisor) + const all = flatten(steps) + + const agentRows = all.filter((s) => s.title.startsWith('researcher')) + check('一个 agent 步骤就是一行,不是"派任务"+"回复"两行', + agentRows.length === 1, `${agentRows.length} 行:${agentRows.map((s) => s.title.slice(0, 20)).join(' | ')}`) + check('回复挂在同一行里可展开', !!agentRows[0]?.result) + check('跑完了就不转圈了', agentRows[0]?.status === 'done', agentRows[0]?.status) + + // supervisor 的调度理由被后端标成了 info 日志,整类丢掉的话, + // 多 agent 节点在界面上就是一个跑了 31 秒的黑盒 + const routes = all.filter((s) => s.kind === 'branch') + check('调度决策要说出来', routes.length === 2, `${routes.length} 条`) + check('说清楚第几轮交给谁', + routes[0]?.title.includes('第 1 轮') && routes[0]?.title.includes('researcher'), + routes[0]?.title) + // FINISH 是协议里的收尾标记,不是某个 agent + check('为什么结束也要有交代,且不说"交给 FINISH"', + routes[1]?.title.includes('结束协作') && !routes[1]?.title.includes('FINISH') + && !!routes[1]?.detail, + `${routes[1]?.title} / ${routes[1]?.detail?.slice(0, 24)}`) + check('普通 info 日志仍然不进主流程', + !all.some((s) => s.title.includes('校验通过'))) + check('warn 日志照常显示', all.some((s) => s.title.includes('校验失败'))) + check('整条流跑完没有转圈的行', !all.some((s) => s.status === 'running'), + all.filter((s) => s.status === 'running').map((s) => s.title.slice(0, 24)).join(',')) +} + console.log('\n=== 出具判定 ===') { const steps = mod.decodeRun(fixtures.issue) diff --git a/scripts/check-stream.mjs b/scripts/check-stream.mjs index 818a2197..457aba45 100644 --- a/scripts/check-stream.mjs +++ b/scripts/check-stream.mjs @@ -12,7 +12,7 @@ const WEB = process.env.AGENTLAB_WEB ?? 'http://localhost:5273' // 不用 playwright install:它既下不动也会动到已有缓存。系统 Chrome 就够了。 const CHROME = process.env.CHROME_PATH ?? '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' -const CASES = ['db', 'think', 'human', 'issue', 'failed', 'loop_approve'] +const CASES = ['db', 'think', 'human', 'issue', 'failed', 'loop_approve', 'supervisor'] let failed = 0 const check = (name, cond, detail = '') => {