mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 01:21:59 +00:00
多 agent 节点不再永远转圈,调度决策也说出来了
运行 #6038f5:supervisor 节点跑完 31.7s 之后,researcher 那一行还在转圈。 agent.step.start / agent.step.end 是这套事件里**唯一**一对没做配对的 (node、llm、tool、sandbox、human 都配了)。start 那条建出来就是 running, end 另起一行,于是没人去关它。这正是 decode.ts 自己文档里的第 2 条原则 ——成对事件合并成一行——我写了却没用在 agent 上。 配对键用 (node_id, agent, round):光用 agent 名不够,supervisor 会在多轮里 反复派给同一个 agent,第二轮的 end 会错配到第一轮的 start 上。 顺带把 supervisor 的调度决策捞出来。后端把它标成 info 日志,而 info 是整类 丢弃的,结果"为什么派给 researcher"、"为什么只跑一轮就结束"在界面上完全 没有交代——那个节点就是一个跑了 31 秒的黑盒。带 round 字段的 info 才是调度 决策,和普通排查日志区分得开。FINISH 译成"结束协作"而不是"交给 FINISH", 后者会让人以为还有个叫 FINISH 的成员。 真实运行已收进 fixtures,check-decode 加 9 条(含"普通 info 日志仍然不进 主流程"和"warn 日志照常显示"这两条反向约束)。
This commit is contained in:
@@ -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[] {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+59
-10
@@ -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<string, any>): 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<string, Step>()
|
||||
/** node_id → 待配对的模型调用 Step */
|
||||
const pendingLlm = new Map<string, Step>()
|
||||
/** supervisor 里待配对的单个 agent 步骤 */
|
||||
const pendingAgents = new Map<string, Step>()
|
||||
/**
|
||||
* 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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = '') => {
|
||||
|
||||
Reference in New Issue
Block a user