API.md 33 KB

Agent Execution API - 前端对接文档

版本:v4.0 更新日期:2026-02-04


概览

本 API 提供 Agent 执行过程的实时可视化能力:

  • REST API - 查询 Trace 和 GoalTree
  • WebSocket - 实时推送 Goal/Message 更新(支持断线续传)

核心概念

  • Trace - 一次完整的 Agent 执行(主 Agent 和 Sub-Agent 都是 Trace)
  • GoalTree - 每个 Trace 的目标树
  • Goal - 一个目标节点,包含 self_stats(自身统计)和 cumulative_stats(含后代统计)
  • Message - 执行消息,对应 LLM API 的 assistant/tool 消息
  • Sub-Trace - 子 Agent(通过 explore/delegate 工具启动)

统一的 Trace 模型

主 Trace 和 Sub-Trace 使用相同的数据结构:
- 每个 Trace 有独立的 GoalTree
- 每个 Trace 有独立的 Message List
- Sub-Trace 通过 parent_trace_id 和 parent_goal_id 关联父 Trace
- Trace ID 采用层级命名:
  - 主 Trace: UUID (如 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d)
  - Sub-Trace: {parent}@{mode}-{timestamp}-{seq} (如 2f8d3a1c...@explore-20260204220012-001)

数据结构

后端存储:
1. 每个 Trace 独立存储(主 Trace 和 Sub-Traces)
2. GoalTree - 目标树结构 + 聚合统计
3. Messages - 执行记录,通过 goal_id 关联 Goal

关系:
- Goal.stats 从关联的 Messages 聚合计算
- Sub-Trace 通过 parent_trace_id 关联
- Goal 通过 sub_trace_ids 关联启动的 Sub-Traces

DAG 可视化(前端负责):

  • 从 GoalTree 生成 DAG 视图
  • 节点 = Goal 完成后的里程碑
  • 边 = 相邻节点之间的执行过程
  • Sub-Trace 可以折叠(显示为单个节点)或展开(显示内部 Goals)
  • 边的统计数据从 target Goal 的 stats 获取

REST API

基础信息

  • Base URL: http://localhost:8000
  • Content-Type: application/json

1. 列出 Traces

GET /api/traces?status=running&limit=20

查询参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | status | string | 否 | 过滤状态:running / completed / failed | | mode | string | 否 | 过滤模式:call / agent | | limit | int | 否 | 返回数量(默认 50,最大 100)|

响应示例

{
  "traces": [
    {
      "trace_id": "abc123",
      "mode": "agent",
      "task": "实现用户认证功能",
      "status": "running",
      "total_messages": 15,
      "total_tokens": 5000,
      "total_cost": 0.05,
      "current_goal_id": "2.1",
      "created_at": "2026-02-04T15:30:00"
    }
  ],
  "total": 1
}

2. 获取 Trace + GoalTree

GET /api/traces/{trace_id}

响应示例 1(主 Trace,explore 进行中):

{
  "trace_id": "abc123",
  "mode": "agent",
  "task": "实现用户认证功能",
  "status": "running",
  "parent_trace_id": null,
  "parent_goal_id": null,
  "agent_type": "main",
  "total_messages": 15,
  "total_tokens": 5000,
  "total_cost": 0.05,
  "created_at": "2026-02-04T15:30:00",
  "completed_at": null,
  "goal_tree": {
    "mission": "实现用户认证功能",
    "current_id": "2",
    "goals": [
      {
        "id": "1",
        "parent_id": null,
        "type": "normal",
        "description": "分析代码",
        "reason": "了解现有结构",
        "status": "completed",
        "summary": "用户模型在 models/user.py",
        "self_stats": { "message_count": 5, "total_tokens": 2300, "total_cost": 0.03, "preview": "glob → read × 2" },
        "cumulative_stats": { "message_count": 5, "total_tokens": 2300, "total_cost": 0.03, "preview": "glob → read × 2" }
      },
      {
        "id": "2",
        "parent_id": null,
        "type": "agent_call",
        "description": "并行探索认证方案",
        "reason": "评估不同技术选型",
        "status": "in_progress",
        "agent_call_mode": "explore",
        "sub_trace_ids": [
          {"trace_id": "abc123.A", "mission": "JWT 方案"},
          {"trace_id": "abc123.B", "mission": "Session 方案"}
        ],
        "sub_trace_metadata": null,
        "self_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null },
        "cumulative_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null }
      },
      {
        "id": "3",
        "parent_id": null,
        "type": "normal",
        "description": "完善实现",
        "reason": "基于选定方案完成实现",
        "status": "pending"
      }
    ]
  },
  "sub_traces": {
    "abc123.A": {
      "trace_id": "abc123.A",
      "parent_trace_id": "abc123",
      "parent_goal_id": "2",
      "agent_type": "explore",
      "task": "JWT 方案",
      "status": "completed",
      "total_messages": 8,
      "total_tokens": 4000,
      "total_cost": 0.05,
      "created_at": "2026-02-04T15:31:00",
      "completed_at": "2026-02-04T15:35:00"
    },
    "abc123.B": {
      "trace_id": "abc123.B",
      "parent_trace_id": "abc123",
      "parent_goal_id": "2",
      "agent_type": "explore",
      "task": "Session 方案",
      "status": "completed",
      "total_messages": 10,
      "total_tokens": 5000,
      "total_cost": 0.06
    }
  }
}

响应示例 2(主 Trace,explore 已完成并合并):

{
  "trace_id": "abc123",
  "mode": "agent",
  "task": "实现用户认证功能",
  "status": "running",
  "goal_tree": {
    "mission": "实现用户认证功能",
    "current_id": "3",
    "goals": [
      {
        "id": "1",
        "type": "normal",
        "description": "分析代码",
        "status": "completed",
        "summary": "用户模型在 models/user.py"
      },
      {
        "id": "2",
        "type": "agent_call",
        "description": "并行探索认证方案",
        "status": "completed",
        "agent_call_mode": "explore",
        "sub_trace_ids": [
          {
            "trace_id": "abc123@explore-20260204220012-001",
            "mission": "JWT 方案"
          },
          {
            "trace_id": "abc123@explore-20260204220012-002",
            "mission": "Session 方案"
          }
        ],
        "sub_trace_metadata": {
          "abc123@explore-20260204220012-001": {
            "task": "JWT 方案",
            "status": "completed",
            "summary": "实现完成,使用 JWT token,无状态但 token 较大",
            "last_message": {
              "role": "assistant",
              "description": "生成 JWT token 并返回",
              "content": "JWT 方案实现完成。使用 jsonwebtoken 库生成 token,包含 user_id 和过期时间。优点:无状态,易扩展。缺点:token 较大(约 200 字节),无法主动失效。",
              "created_at": "2026-02-05T10:30:00"
            },
            "stats": {
              "message_count": 8,
              "total_tokens": 4000,
              "total_cost": 0.05
            }
          },
          "abc123@explore-20260204220012-002": {
            "task": "Session 方案",
            "status": "completed",
            "summary": "实现完成,使用 Redis 存储 session,支持主动失效",
            "last_message": {
              "role": "assistant",
              "description": "配置 Redis 并实现 session 管理",
              "content": "Session 方案实现完成。使用 Redis 存储 session,支持过期自动清理和主动失效。优点:token 小(约 32 字节),可主动失效。缺点:需要 Redis,有网络开销。",
              "created_at": "2026-02-05T10:32:00"
            },
            "stats": {
              "message_count": 12,
              "total_tokens": 4000,
              "total_cost": 0.05
            }
          }
        },
        "summary": "探索了 2 个方案,JWT 和 Session 均可行",
        "cumulative_stats": {
          "message_count": 20,
          "total_tokens": 8000,
          "total_cost": 0.10
        }
      },
      {
        "id": "3",
        "type": "normal",
        "description": "完善实现",
        "status": "in_progress"
      }
    ]
  },
  "sub_traces": {
    "abc123@explore-20260204220012-001": { /* ... */ },
    "abc123@explore-20260204220012-002": { /* ... */ }
  }
}

3. 获取 Sub-Trace 详情

获取 Sub-Trace 的完整信息(包括 GoalTree)。

GET /api/traces/abc123.A

响应示例

{
  "trace_id": "abc123.A",
  "parent_trace_id": "abc123",
  "parent_goal_id": "2",
  "agent_type": "explore",
  "task": "JWT 方案",
  "status": "completed",
  "total_messages": 8,
  "total_tokens": 4000,
  "total_cost": 0.05,
  "created_at": "2026-02-04T15:31:00",
  "completed_at": "2026-02-04T15:35:00",
  "goal_tree": {
    "mission": "JWT 方案",
    "current_id": null,
    "goals": [
      {
        "id": "1",
        "parent_id": null,
        "type": "normal",
        "description": "JWT 设计",
        "status": "completed",
        "summary": "设计完成",
        "self_stats": { "message_count": 3, "total_tokens": 1500, "total_cost": 0.02, "preview": "read → edit" },
        "cumulative_stats": { "message_count": 3, "total_tokens": 1500, "total_cost": 0.02, "preview": "read → edit" }
      },
      {
        "id": "2",
        "parent_id": null,
        "type": "normal",
        "description": "JWT 实现",
        "status": "completed",
        "summary": "实现完成",
        "self_stats": { "message_count": 5, "total_tokens": 2500, "total_cost": 0.03, "preview": "edit × 3 → bash" },
        "cumulative_stats": { "message_count": 5, "total_tokens": 2500, "total_cost": 0.03, "preview": "edit × 3 → bash" }
      }
    ]
  },
  "sub_traces": {}
}

4. 获取 Messages(边详情)

查询指定 Trace 的 Messages(用于查看边的详细执行内容)。

GET /api/traces/abc123/messages?goal_id=1
GET /api/traces/abc123.A/messages?goal_id=2

查询参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | goal_id | string | 否 | 过滤指定 Goal 的 Messages(内部 ID,如 "1", "2")|

响应示例

{
  "trace_id": "abc123",
  "messages": [
    {
      "message_id": "msg-001",
      "trace_id": "abc123",
      "role": "assistant",
      "sequence": 1,
      "goal_id": "1",
      "content": {
        "text": "让我先读取现有的 API 设计...",
        "tool_calls": [
          {
            "id": "call_abc",
            "name": "read_file",
            "arguments": { "path": "api/routes.py" }
          }
        ]
      },
      "description": "让我先读取现有的 API 设计...",
      "tokens": 150,
      "cost": 0.002,
      "created_at": "2026-02-04T15:31:00"
    },
    {
      "message_id": "msg-002",
      "trace_id": "abc123",
      "role": "tool",
      "sequence": 2,
      "goal_id": "1",
      "tool_call_id": "call_abc",
      "content": "# API Routes\n...",
      "description": "read_file",
      "tokens": null,
      "cost": null,
      "created_at": "2026-02-04T15:31:01"
    }
  ],
  "total": 2
}

WebSocket API

连接

const ws = new WebSocket(
  'ws://localhost:8000/api/traces/{trace_id}/watch?since_event_id=0'
)

查询参数: | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | since_event_id | int | 0 | 从哪个事件 ID 开始。0 = 补发所有历史 |


事件类型

1. connected(连接成功)

连接后推送完整 Trace 信息,前端据此初始化 DAG。

{
  "event": "connected",
  "trace_id": "abc123",
  "current_event_id": 15,
  "trace": {
    "trace_id": "abc123",
    "status": "running",
    "goal_tree": {
      "mission": "实现用户认证功能",
      "current_id": "2",
      "goals": [...]
    },
    "sub_traces": {}
  }
}

前端处理

if (data.event === 'connected') {
  initDAG(data.goal_tree)
  localStorage.setItem('last_event_id', data.current_event_id)
}

2. goal_added(新增 Goal)

{
  "event": "goal_added",
  "event_id": 16,
  "goal": {
    "id": "6",
    "parent_id": "2",
    "type": "normal",
    "description": "编写测试",
    "reason": "确保代码质量",
    "status": "pending",
    "summary": null,
    "self_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null },
    "cumulative_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null }
  },
  "parent_id": "2"
}

前端处理

if (data.event === 'goal_added') {
  insertGoal(data.goal, data.parent_id)
  regenerateDAG()
}

3. goal_updated(Goal 状态变化)

包含级联完成场景:当所有子 Goal 完成时,父 Goal 自动 completed。

{
  "event": "goal_updated",
  "event_id": 17,
  "goal_id": "3",
  "updates": {
    "status": "completed",
    "summary": "接口设计完成"
  },
  "affected_goals": [
    {
      "goal_id": "3",
      "cumulative_stats": { "message_count": 3, "total_tokens": 1500, "total_cost": 0.02, "preview": "read → edit" }
    },
    {
      "goal_id": "2",
      "status": "completed",
      "summary": "功能实现完成",
      "cumulative_stats": { "message_count": 8, "total_tokens": 4200, "total_cost": 0.05, "preview": "..." }
    }
  ]
}

前端处理

if (data.event === 'goal_updated') {
  updateGoal(data.goal_id, data.updates)
  for (const g of data.affected_goals) {
    updateGoalStats(g.goal_id, g)
  }
  regenerateDAG()
}

4. message_added(新 Message)

后端更新统计后推送,包含受影响的所有 Goals。

{
  "event": "message_added",
  "event_id": 18,
  "message": {
    "message_id": "msg-018",
    "role": "assistant",
    "goal_id": "4",
    "content": { "text": "...", "tool_calls": [...] },
    "tokens": 500,
    "cost": 0.005
  },
  "affected_goals": [
    {
      "goal_id": "4",
      "self_stats": { "message_count": 6, "total_tokens": 3200, "total_cost": 0.035, "preview": "edit × 3 → bash × 2" },
      "cumulative_stats": { "message_count": 6, "total_tokens": 3200, "total_cost": 0.035, "preview": "edit × 3 → bash × 2" }
    },
    {
      "goal_id": "2",
      "cumulative_stats": { "message_count": 9, "total_tokens": 4700, "total_cost": 0.055, "preview": "read → edit × 4 → bash × 2" }
    }
  ]
}

说明

  • affected_goals[0] 是直接关联的 Goal,更新 self_stats + cumulative_stats
  • 后续是所有祖先 Goal,仅更新 cumulative_stats

前端处理

if (data.event === 'message_added') {
  for (const g of data.affected_goals) {
    updateGoalStats(g.goal_id, g)
  }
  // 根据当前展开状态更新对应边
  rerenderEdge(data.message.goal_id)
}

5. trace_completed(任务完成)

{
  "event": "trace_completed",
  "event_id": 50,
  "trace_id": "abc123",
  "total_messages": 50,
  "total_tokens": 25000,
  "total_cost": 0.25
}

前端处理

if (data.event === 'trace_completed') {
  markTraceCompleted()
  ws.close()
}

6. sub_trace_started(Sub-Trace 开始)

explore 或 delegate 工具启动 Sub-Trace 时触发。

{
  "event": "sub_trace_started",
  "event_id": 20,
  "parent_trace_id": "abc123",
  "parent_goal_id": "2",
  "sub_trace": {
    "trace_id": "abc123.A",
    "parent_trace_id": "abc123",
    "parent_goal_id": "2",
    "agent_type": "explore",
    "task": "JWT 方案",
    "status": "running",
    "total_messages": 0,
    "total_tokens": 0,
    "total_cost": 0.0
  }
}

前端处理

if (data.event === 'sub_trace_started') {
  insertSubTrace(data.parent_trace_id, data.sub_trace)
  regenerateDAG()
}

7. sub_trace_completed(Sub-Trace 完成)

Sub-Trace 执行完成后触发。

{
  "event": "sub_trace_completed",
  "event_id": 35,
  "trace_id": "abc123.A",
  "parent_trace_id": "abc123",
  "parent_goal_id": "2",
  "summary": "JWT 方案实现完成,无状态但 token 较大",
  "total_messages": 8,
  "total_tokens": 4000,
  "total_cost": 0.05
}

前端处理

if (data.event === 'sub_trace_completed') {
  updateSubTrace(data.trace_id, {
    status: 'completed',
    summary: data.summary,
    total_messages: data.total_messages,
    total_tokens: data.total_tokens,
    total_cost: data.total_cost
  })
  regenerateDAG()
}

数据模型

Trace

字段 类型 说明
trace_id string 层级化 ID(如 "abc123", "abc123.A", "abc123.A.1")
mode string call - 单次调用 / agent - Agent 模式
task string 任务描述
parent_trace_id string | null 父 Trace ID(Sub-Trace 才有)
parent_goal_id string | null 哪个 Goal 启动的(Sub-Trace 才有)
agent_type string | null "main" / "explore" / "delegate" / "compaction"
status string running / completed / failed
total_messages int Message 总数
total_tokens int Token 总消耗
total_cost float 成本总和
created_at string 创建时间(ISO 8601)
completed_at string | null 完成时间

Trace ID 规则

  • 主 Trace:标准 UUID(如 "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c")
  • Sub-Trace:{parent_uuid}@{mode}-{timestamp}-{seq}
    • 例如:"2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c@explore-20260204220012-001"
    • 使用完整 UUID 避免 ID 冲突
    • timestamp: YYYYMMDDHHmmss 格式
    • seq: 3 位序号(001, 002...)

GoalTree

字段 类型 说明
mission string 总任务描述(来自 Trace.task)
current_id string | null 当前焦点 Goal 的内部 ID
goals Goal[] 顶层目标列表

Goal

字段 类型 说明
id string 内部 ID,每个 Trace 独立编号("1", "2", "3"...)
parent_id string | null 父 Goal ID(层级关系)
type string normal / agent_call
description string 目标描述(做什么)
reason string 创建理由(为什么做)
status string pending / in_progress / completed / abandoned
summary string | null 完成/放弃时的总结
sub_trace_ids Array<{trace_id: string, mission: string}> | null 启动的 Sub-Trace 信息(仅 agent_call)
agent_call_mode string | null "explore" / "delegate" / "sequential"(仅 agent_call)
sub_trace_metadata object | null Sub-Trace 元数据(仅 agent_call,包含最后消息等)
self_stats GoalStats 自身统计
cumulative_stats GoalStats 累计统计

Goal 类型说明

normal Goal

  • 普通执行目标,对应一系列顺序执行的 Messages
  • 可以有子 Goal(通过 parent_id 构建层级)

agent_call Goal

  • 表示启动了 Sub-Agent 的特殊 Goal
  • 不直接关联 Messages,而是启动一个或多个独立的 Sub-Trace
  • 通过 sub_trace_ids 关联启动的所有 Sub-Traces
  • 通过 agent_call_mode 标记执行模式:
    • "explore" - 并行探索:启动多个 Sub-Trace 并行执行,汇总结果后合并
    • "delegate" - 单线委托:将大任务委托给单个 Sub-Trace 执行
    • "sequential" - 顺序执行:按顺序启动多个 Sub-Trace

分支探索与合并(explore 模式)

  1. 分支开始agent_call Goal 同时启动多个 Sub-Trace(如 2-5 个)
  2. 并行执行:各 Sub-Trace 独立运行,有各自的 GoalTree 和 Messages
  3. 收集元数据:执行完成后,收集每个 Sub-Trace 的:
    • 最后一条 assistant 消息(last_message
    • 执行总结(summary
    • 统计信息(stats
  4. 合并节点agent_call Goal 完成时,表示所有分支已合并
    • explore 工具返回文本摘要(给主 Agent 的 LLM 查看)
    • sub_trace_metadata 保存详细元数据(给前端和后续决策使用)

ID 设计

  • 每个 Trace 内部独立编号("1", "2", "3")
  • 层级关系通过 parent_id 维护
  • 显示序号由前端/后端动态生成

SubTraceMetadata(agent_call Goal 专用)

agent_call 类型的 Goal 会在完成时填充 sub_trace_metadata 字段,格式如下:

{
  "sub_trace_metadata": {
    "abc123@explore-20260204220012-001": {
      "task": "JWT 方案",
      "status": "completed",
      "summary": "实现完成,使用 JWT token",
      "last_message": {
        "role": "assistant",
        "description": "生成 JWT token 并返回",
        "content": "JWT 方案实现完成。使用 jsonwebtoken 库生成 token...",
        "created_at": "2026-02-05T10:30:00"
      },
      "stats": {
        "message_count": 8,
        "total_tokens": 4000,
        "total_cost": 0.05
      }
    },
    "abc123@explore-20260204220012-002": {
      "task": "Session 方案",
      "status": "completed",
      "summary": "实现完成,使用 Redis 存储",
      "last_message": {
        "role": "assistant",
        "description": "配置 Redis 并实现 session 管理",
        "content": "Session 方案实现完成。使用 Redis 存储 session...",
        "created_at": "2026-02-05T10:32:00"
      },
      "stats": {
        "message_count": 12,
        "total_tokens": 4000,
        "total_cost": 0.05
      }
    }
  }
}

字段说明

  • task - Sub-Trace 的任务描述
  • status - Sub-Trace 的最终状态(completed/failed)
  • summary - Sub-Trace 的执行总结
    • 优先使用 run_agent() 返回的 summary 字段(如果有)
    • 否则使用最后一条 assistant 消息的内容(截断至 200 字符)
    • 如果都没有,默认为 "执行完成"
  • last_message - 最后一条 assistant 消息(内容截断至 500 字符)
    • role - 固定为 "assistant"
    • description - 消息的简短描述
    • content - 消息内容(可能被截断)
    • created_at - 消息创建时间
  • stats - 统计信息(message_count, total_tokens, total_cost)

用途

  1. 辅助决策:主 Agent 可以基于各分支的最终输出,决定是否需要展开查看详细信息
  2. 前端展示:在折叠视图中显示每个分支的关键信息,用户快速判断是否需要展开
  3. 调试追踪:快速了解每个分支的执行结果,无需加载完整 Messages

GoalStats

字段 类型 说明
message_count int 消息数量
total_tokens int Token 总数
total_cost float 总成本
preview string | null 工具调用摘要(如 "read → edit × 2"

Message

字段 类型 说明
message_id string 唯一 ID
trace_id string 所属 Trace ID(可能是主 Trace 或 Sub-Trace)
role string assistant / tool
sequence int 当前 Trace 内的顺序
goal_id string | null 关联的 Goal 内部 ID(如 "1", "2")。可以为 null(初始阶段)
tool_call_id string | null tool 消息关联的 tool_call ID
content any 消息内容(和 LLM API 格式一致)
description string 消息描述(系统自动生成)
tokens int | null Token 消耗
cost float | null 成本
created_at string 创建时间

goal_id 说明

  • 通常关联到某个 Goal 的内部 ID(如 "1", "2")
  • 可以为 null:在 Trace 开始时,Agent 还没有创建任何 Goal
  • 初始阶段的 messages 用于分析任务、创建第一批 Goals

查询初始阶段 Messages

GET /api/traces/{trace_id}/messages?goal_id=_init
  • 使用特殊值 _initnull 查询 goal_id 为 null 的 messages
  • 用于前端展示 START 节点的详情

前端展示:START 虚拟节点

设计原则:诚实反映数据结构,不在后端创建额外节点。

概念

在 Trace 开始阶段,Agent 还没有创建任何 Goal,这期间产生的 Messages 的 goal_idnull。前端应该创建一个虚拟的 START 节点来展示这些初始阶段的 messages。

DAG 结构

[START] ────────→ [1:分析完成] ──→ [2:设计完成] ──→ [3:实现完成]
   │
   └─ goal_id=null 的 messages
      (分析任务、创建计划等)

START 节点

  • 类型:虚拟节点(前端创建,后端不存在)
  • ID:"START"null
  • 数据:Mission 信息、初始阶段统计

START → Goal 1 的边

  • source: "START"
  • target: 第一个真实 Goal 的 ID
  • 数据:goal_id=null 的所有 messages 的聚合统计

实现示例

// 1. 创建 START 节点
function buildDAG(goalTree, messages) {
  const nodes = []
  const edges = []

  // 创建虚拟 START 节点
  nodes.push({
    id: 'START',
    type: 'start',
    data: {
      label: 'START',
      mission: goalTree.mission,
    }
  })

  // 计算初始阶段的统计
  const initMessages = messages.filter(m => m.goal_id === null)
  const initStats = {
    message_count: initMessages.length,
    total_tokens: initMessages.reduce((sum, m) => sum + (m.tokens || 0), 0),
    total_cost: initMessages.reduce((sum, m) => sum + (m.cost || 0), 0),
    preview: generatePreview(initMessages)
  }

  // 创建 START 到第一个 Goal 的边
  const firstGoal = goalTree.goals.find(g => g.parent_id === null)
  if (firstGoal) {
    edges.push({
      source: 'START',
      target: firstGoal.id,
      data: {
        messages: initMessages,
        stats: initStats
      }
    })
  }

  // 创建其他 goals 的节点和边...
  // ...

  return { nodes, edges }
}

用户交互

悬停 START 节点

┌─────────────────────────────┐
│ START                       │
│ Mission: 实现用户认证系统   │
│                             │
│ 初始阶段统计:              │
│ • 2 messages               │
│ • 500 tokens               │
│ • $0.005                   │
└─────────────────────────────┘

点击 START 节点

  • 侧边栏显示 Mission 描述
  • 显示初始阶段的元信息

点击 START → Goal 1 边

  • 展开详情,显示所有 goal_id=null 的 messages
  • 内容示例:

    [assistant] 我需要先分析这个任务...
    [tool: goal] 创建了3个顶层目标
    

查询 API

// 获取初始阶段的 messages
async function getInitMessages(traceId) {
  const resp = await fetch(`/api/traces/${traceId}/messages?goal_id=_init`)
  const data = await resp.json()
  return data.messages  // goal_id 全部为 null
}

特殊情况

情况 1:没有初始阶段 messages

  • 如果 Agent 一启动就创建了 Goal(没有 goal_id=null 的 messages)
  • 仍然显示 START 节点,但边的统计为空

情况 2:所有 messages 都是初始阶段

  • 如果 Agent 还没有创建任何 Goal
  • 只显示 START 节点,没有其他节点

    [START]
    (分析中,3 msgs)
    

与编辑功能的集成

用户可以在 START → Goal 1 的边上编辑第一次 goal() 调用的参数:

  1. 点击 START → 1 边,展开 messages
  2. 找到 goal() 工具调用(通常是第一个或第二个 message)
  3. 点击 [编辑] 按钮
  4. 修改 add 参数(如修改初始的任务列表)
  5. 系统废弃 Goal 1, 2, 3...,重新执行

这样用户就能方便地修改整体计划。


DAG 可视化(前端实现)

后端提供 Trace(包含 GoalTree 和 Sub-Traces),前端负责生成 DAG 视图。

核心逻辑

普通 Goal(子目标)

  1. 未展开:显示父 Goal
  2. 已展开:父 Goal 被子 Goal 替代(顺序)

agent_call Goal(Sub-Trace)

  1. 未展开:显示为单个节点(代表整个 Sub-Trace)
  2. 已展开:加载 Sub-Trace 的 GoalTree,显示内部 Goals

边数据

从 target 节点的 stats 获取:

  • 折叠边(父 Goal)→ target.cumulative_stats
  • 展开边(子 Goal)→ target.self_stats
  • Sub-Trace 折叠边 → Sub-Trace 的 total_tokens/total_cost
  • Sub-Trace 展开边 → Sub-Trace 内部 Goal 的 stats

示例:普通展开(子目标)

// GoalTree(扁平列表,通过 parent_id 构建层级)
const goalTree = {
  goals: [
    { id: "1", parent_id: null, description: "分析代码" },
    { id: "2", parent_id: null, description: "实现功能" },
    { id: "3", parent_id: "2", description: "设计接口" },  // 2 的子目标
    { id: "4", parent_id: "2", description: "实现代码" },  // 2 的子目标
    { id: "5", parent_id: null, description: "测试" }
  ]
}

// 构建层级视图
function buildHierarchy(goals) {
  const map = new Map(goals.map(g => [g.id, { ...g, children: [] }]))
  const roots = []
  for (const goal of goals) {
    const node = map.get(goal.id)
    if (goal.parent_id) {
      map.get(goal.parent_id)?.children.push(node)
    } else {
      roots.push(node)
    }
  }
  return roots
}

// 展开状态
const expanded = new Set()  // 空 = 全部折叠

// 生成可见节点序列
function getVisibleGoals(nodes, expanded) {
  const result = []
  for (const node of nodes) {
    if (expanded.has(node.id) && node.children.length > 0) {
      // 展开:递归处理子节点
      result.push(...getVisibleGoals(node.children, expanded))
    } else {
      // 折叠:显示自己
      result.push(node)
    }
  }
  return result
}

// 折叠视图: [1] → [2] → [5]
// 展开 "2" 后: [1] → [3] → [4] → [5]

// 生成边
function generateEdges(visibleGoals) {
  const edges = []
  for (let i = 0; i < visibleGoals.length; i++) {
    const source = i === 0 ? null : visibleGoals[i - 1]
    const target = visibleGoals[i]
    edges.push({
      source: source?.id ?? null,
      target: target.id,
      // 边数据来自 target 的 stats
      // 如果 target 有子节点且未展开,用 cumulative_stats
      // 否则用 self_stats
      stats: hasUnexpandedChildren(target, expanded)
        ? target.cumulative_stats
        : target.self_stats
    })
  }
  return edges
}

示例:Sub-Trace 展开

// 主 Trace 的 GoalTree
const mainTrace = {
  trace_id: "abc123",
  goal_tree: {
    goals: [
      { id: "1", type: "normal", description: "分析问题" },
      { id: "2", type: "agent_call", agent_call_mode: "explore", sub_trace_ids: [
        {"trace_id": "abc123.A", "mission": "JWT 方案"},
        {"trace_id": "abc123.B", "mission": "Session 方案"}
      ] },
      { id: "3", type: "normal", description: "完善实现" }
    ]
  },
  sub_traces: {
    "abc123.A": { trace_id: "abc123.A", task: "JWT 方案", status: "completed", total_tokens: 4000 },
    "abc123.B": { trace_id: "abc123.B", task: "Session 方案", status: "completed", total_tokens: 5000 }
  }
}

// 折叠视图:[1] → [2:并行探索] → [3]

// 展开 Sub-Traces 后的视图:
//           ┌→ [abc123.A] ────┐
// [1] ──────┼                 ├──→ [3]
//           └→ [abc123.B] ────┘

// 继续展开 Sub-Trace abc123.A 内部
async function loadSubTrace(traceId) {
  const resp = await fetch(`/api/traces/${traceId}`)
  return await resp.json()  // 返回完整 Trace,含 goal_tree
}

const subTraceA = await loadSubTrace("abc123.A")
// {
//   trace_id: "abc123.A",
//   goal_tree: {
//     goals: [
//       { id: "1", description: "JWT 设计" },
//       { id: "2", description: "JWT 实现" }
//     ]
//   }
// }

// 展开后显示 Sub-Trace 内部 Goals:
//           ┌→ [A.1:JWT设计] → [A.2:JWT实现] ──┐
// [1] ──────┼                                  ├──→ [3]
//           └→ [abc123.B] ─────────────────────┘

// 注意:前端显示为 "A.1",实际查询是 GET /api/traces/abc123.A/messages?goal_id=1

视觉区分

边类型 说明 样式建议
普通边 顺序执行 实线
分叉边 explore 分支开始 虚线或带标记
汇合边 分支合并到 merge 节点 虚线或带标记
废弃边 abandoned 分支 灰色

断线续传

let lastEventId = 0

function connect(traceId) {
  const ws = new WebSocket(
    `ws://localhost:8000/api/traces/${traceId}/watch?since_event_id=${lastEventId}`
  )

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data)
    if (data.event_id) {
      lastEventId = data.event_id
      localStorage.setItem(`trace_${traceId}_event_id`, lastEventId)
    }
    // 处理事件...
  }

  ws.onclose = () => {
    setTimeout(() => connect(traceId), 3000)
  }
}

错误处理

HTTP 错误码

状态码 说明
200 成功
404 Trace 不存在
400 参数错误
500 服务器错误

WebSocket 错误

{
  "event": "error",
  "message": "Too many missed events, please reload via REST API"
}

相关文档