API.md 25 KB

Agent Execution API - 前端对接文档

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


概览

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

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

核心概念

  • Trace - 一次完整的任务执行
  • GoalTree - 嵌套的目标树
  • Goal - 一个目标节点,包含 self_stats(自身统计)和 cumulative_stats(含后代统计)
  • Message - 执行消息,对应 LLM API 的 assistant/tool 消息,是详细执行数据

数据结构

后端存储两类数据:
1. GoalTree(嵌套 JSON)- 目标树结构 + 聚合统计(stats)
2. Messages(扁平列表)- 详细执行记录,通过 goal_id 关联 Goal

关系:Goal.stats 是从关联的 Messages 聚合计算出来的

DAG 可视化(前端负责):

  • 从 GoalTree 生成 DAG 视图
  • 节点 = Goal 完成后的里程碑
  • 边 = 相邻节点之间的执行过程
  • 边的统计数据从 target Goal 的 stats 获取(折叠用 cumulative_stats,展开用 self_stats
  • 边的详细内容需要查询该 Goal 关联的 Messages

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}

响应示例

{
  "trace_id": "abc123",
  "mode": "agent",
  "task": "实现用户认证功能",
  "status": "running",
  "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": "4",
    "goals": [
      {
        "id": "1",
        "parent_id": null,
        "branch_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,
        "branch_id": null,
        "type": "normal",
        "description": "实现功能",
        "reason": "核心任务",
        "status": "in_progress",
        "summary": null,
        "self_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null },
        "cumulative_stats": { "message_count": 8, "total_tokens": 4200, "total_cost": 0.05, "preview": "read → edit × 3 → bash" }
      },
      {
        "id": "3",
        "parent_id": "2",
        "branch_id": null,
        "type": "normal",
        "description": "设计接口",
        "reason": "先定义 API 契约",
        "status": "completed",
        "summary": "RESTful API 设计完成",
        "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": "4",
        "parent_id": "2",
        "branch_id": null,
        "type": "normal",
        "description": "实现代码",
        "reason": "按设计实现",
        "status": "in_progress",
        "summary": null,
        "self_stats": { "message_count": 5, "total_tokens": 2700, "total_cost": 0.03, "preview": "edit × 3 → bash" },
        "cumulative_stats": { "message_count": 5, "total_tokens": 2700, "total_cost": 0.03, "preview": "edit × 3 → bash" }
      },
      {
        "id": "5",
        "parent_id": null,
        "branch_id": null,
        "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 }
      }
    ]
  },
  "branches": {}
}

3. 获取 Messages(边详情)

查询 Goal 关联的所有 Messages(用于查看边的详细执行内容)。

GET /api/traces/{trace_id}/messages?goal_id=3
GET /api/traces/{trace_id}/messages?branch_id=A

查询参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | goal_id | string | 否 | 过滤指定 Goal 的 Messages(内部 ID)| | branch_id | string | 否 | 过滤指定分支的所有 Messages |

响应示例

{
  "messages": [
    {
      "message_id": "msg-001",
      "role": "assistant",
      "sequence": 6,
      "goal_id": "3",
      "branch_id": null,
      "content": {
        "text": "让我先读取现有的 API 设计...",
        "tool_calls": [
          {
            "id": "call_abc",
            "name": "read_file",
            "arguments": { "path": "api/routes.py" }
          }
        ]
      },
      "tokens": 150,
      "cost": 0.002,
      "created_at": "2026-02-04T15:31:00"
    },
    {
      "message_id": "msg-002",
      "role": "tool",
      "sequence": 7,
      "goal_id": "3",
      "branch_id": null,
      "tool_call_id": "call_abc",
      "content": "# API Routes\n...",
      "tokens": null,
      "cost": null,
      "created_at": "2026-02-04T15:31:01"
    },
    {
      "message_id": "msg-003",
      "role": "assistant",
      "sequence": 8,
      "goal_id": "3",
      "branch_id": null,
      "content": {
        "text": "现有 API 使用 REST 风格,我来设计新的认证接口...",
        "tool_calls": [
          {
            "id": "call_def",
            "name": "edit_file",
            "arguments": { "path": "api/auth.py", "content": "..." }
          }
        ]
      },
      "tokens": 300,
      "cost": 0.004,
      "created_at": "2026-02-04T15:31:30"
    }
  ],
  "total": 3
}

4. 获取分支详情(按需加载)

GET /api/traces/{trace_id}/branches/{branch_id}

响应示例

{
  "id": "A",
  "explore_start_id": "6",
  "description": "JWT 方案",
  "status": "completed",
  "summary": "JWT 方案实现完成,无状态但 token 较大",
  "goal_tree": {
    "mission": "探索 JWT 方案",
    "current_id": null,
    "goals": [
      {
        "id": "1",
        "parent_id": null,
        "branch_id": "A",
        "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,
        "branch_id": "A",
        "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" }
      }
    ]
  },
  "cumulative_stats": { "message_count": 8, "total_tokens": 4000, "total_cost": 0.05, "preview": "read → edit × 4 → bash" }
}

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(连接成功)

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

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

前端处理

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",
    "branch_id": null,
    "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",
    "branch_id": null,
    "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. branch_started(分支开始探索)

explore 工具触发并行探索时,为每个分支发送此事件。

{
  "event": "branch_started",
  "event_id": 20,
  "explore_start_id": "6",
  "branch": {
    "id": "A",
    "explore_start_id": "6",
    "description": "JWT 方案",
    "status": "exploring",
    "summary": null,
    "cumulative_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null },
    "goal_count": 0,
    "last_message": null
  }
}

前端处理

if (data.event === 'branch_started') {
  insertBranch(data.explore_start_id, data.branch)
  regenerateDAG()
}

7. branch_completed(分支完成)

分支内所有 Goals 完成后触发。

{
  "event": "branch_completed",
  "event_id": 35,
  "explore_start_id": "6",
  "branch_id": "A",
  "summary": "JWT 方案实现完成,无状态但 token 较大",
  "cumulative_stats": { "message_count": 8, "total_tokens": 4000, "total_cost": 0.05, "preview": "read → edit × 3" },
  "last_message": { "role": "assistant", "content": "JWT 实现完成...", "created_at": "..." }
}

前端处理

if (data.event === 'branch_completed') {
  updateBranch(data.explore_start_id, data.branch_id, {
    status: 'completed',
    summary: data.summary,
    cumulative_stats: data.cumulative_stats,
    last_message: data.last_message
  })
  regenerateDAG()
}

8. message_added 扩展(分支内消息)

分支内的 Message 会包含 branch_idaffected_branches 字段:

{
  "event": "message_added",
  "event_id": 25,
  "message": {
    "message_id": "msg-025",
    "role": "assistant",
    "goal_id": "1",
    "branch_id": "A",
    "content": { "text": "...", "tool_calls": [...] },
    "tokens": 300,
    "cost": 0.004
  },
  "affected_goals": [
    { "goal_id": "1", "self_stats": {...}, "cumulative_stats": {...} }
  ],
  "affected_branches": [
    { "branch_id": "A", "explore_start_id": "6", "cumulative_stats": {...} }
  ]
}

前端处理

if (data.event === 'message_added') {
  for (const g of data.affected_goals) {
    updateGoalStats(g.goal_id, g)
  }
  // 分支内消息还需更新分支统计
  if (data.affected_branches) {
    for (const b of data.affected_branches) {
      updateBranchStats(b.explore_start_id, b.branch_id, b.cumulative_stats)
    }
  }
  rerenderEdge(data.message.goal_id, data.message.branch_id)
}

数据模型

Trace

字段 类型 说明
trace_id string 唯一 ID
mode string call - 单次调用 / agent - Agent 模式
task string 任务描述
status string running / completed / failed
total_messages int Message 总数
total_tokens int Token 总消耗
total_cost float 成本总和
current_goal_id string 当前焦点 Goal 的内部 ID
created_at string 创建时间(ISO 8601)
completed_at string | null 完成时间

GoalTree

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

Goal

字段 类型 说明
id string 内部唯一 ID,纯自增("1", "2", "3"...)
parent_id string | null 父 Goal ID(层级关系)
branch_id string | null 所属分支 ID(null=主线)
type string normal / explore_start / explore_merge
description string 目标描述(做什么)
reason string 创建理由(为什么做)
status string pending / in_progress / completed / abandoned
summary string | null 完成/放弃时的总结
branch_ids string[] | null 关联的分支 ID(仅 explore_start)
explore_start_id string | null 关联的 explore_start Goal(仅 explore_merge)
merge_summary string | null 各分支汇总(仅 explore_merge)
selected_branch string | null 选中的分支(仅 explore_merge)
self_stats GoalStats 自身统计
cumulative_stats GoalStats 累计统计

ID 设计

  • 内部 ID 纯自增,不管层级、分支、废弃
  • 层级关系通过 parent_id 维护
  • 分支关系通过 branch_id 维护
  • 显示序号由前端/后端动态生成

BranchContext

分支元数据(主请求返回,不含内部 GoalTree)

字段 类型 说明
id string 分支 ID(如 "A", "B"
explore_start_id string 关联的 explore_start Goal ID
description string 探索方向描述
status string exploring / completed / abandoned
summary string | null 完成时的总结
cumulative_stats GoalStats 累计统计
goal_count int 分支内 Goal 数量
last_message Message | null 最新消息(用于预览)

BranchDetail

分支详情(按需加载)

字段 类型 说明
id string 分支 ID
description string 探索方向描述
status string 状态
summary string | null 总结
goal_tree GoalTree 分支内的 GoalTree
cumulative_stats GoalStats 累计统计

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
branch_id string | null 所属分支(null=主线)
role string assistant / tool
sequence int 全局顺序
goal_id string 关联的 Goal 内部 ID
tool_call_id string | null tool 消息关联的 tool_call ID
content any 消息内容(和 LLM API 格式一致)
tokens int | null Token 消耗
cost float | null 成本
created_at string 创建时间

DAG 可视化(前端实现)

后端只提供 GoalTree(扁平列表 + parent_id),前端负责生成 DAG 视图。

核心逻辑

普通 Goal(子目标)

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

explore Goal(分支)

  1. 未展开:显示单个 explore_start → explore_merge 节点
  2. 已展开:显示并行分支路径(分叉-汇合)

边数据

从 target 节点的 stats 获取:

  • 折叠边 → target.cumulative_stats
  • 展开边 → target.self_stats
  • 分支边 → branch.cumulative_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
}

示例:分支展开(explore)

// GoalTree 含 explore Goals + 分支元数据
const goalTree = {
  goals: [
    { id: "1", parent_id: null, type: "normal", description: "分析问题" },
    { id: "2", parent_id: null, type: "explore_start", branch_ids: ["A", "B"] },
    { id: "3", parent_id: null, type: "explore_merge", explore_start_id: "2" },
    { id: "4", parent_id: null, type: "normal", description: "完善实现" }
  ]
}

const branches = {
  "A": { id: "A", description: "JWT 方案", cumulative_stats: {...}, goal_count: 2 },
  "B": { id: "B", description: "Session 方案", cumulative_stats: {...}, goal_count: 3 }
}

// 折叠视图: [1] → [2:explore_start] → [3:explore_merge] → [4]

// 展开分支后的视图:
//           ┌→ [A:JWT] ────┐
// [1] ──────┼              ├──→ [3] ──→ [4]
//           └→ [B:Session] ┘

// 分支展开时,需要按需加载分支详情
async function loadBranchDetail(traceId, branchId) {
  const resp = await fetch(`/api/traces/${traceId}/branches/${branchId}`)
  return await resp.json()  // 返回 BranchDetail,含 goal_tree
}

// 分支展开时,生成分叉结构
function generateBranchDAG(prevGoal, exploreStartGoal, exploreEndGoal, nextGoal, branches, expandedBranches) {
  const nodes = []
  const edges = []

  for (const branchId of exploreStartGoal.branch_ids) {
    const branch = branches[branchId]

    if (expandedBranches.has(branchId) && branch.goalTree) {
      // 分支已加载且展开:显示分支内的 Goals
      const branchNodes = getVisibleGoals(buildHierarchy(branch.goalTree.goals), expandedBranches)
      nodes.push(...branchNodes)

      // 分叉边:前一个节点 → 分支第一个节点
      if (branchNodes.length > 0) {
        edges.push({
          source: prevGoal?.id ?? null,
          target: branchNodes[0].id,
          stats: branchNodes[0].self_stats,
          isBranchEdge: true,
          branchId: branchId
        })

        // 分支内部边
        for (let i = 1; i < branchNodes.length; i++) {
          edges.push({
            source: branchNodes[i - 1].id,
            target: branchNodes[i].id,
            stats: branchNodes[i].self_stats,
            branchId: branchId
          })
        }

        // 汇合边:分支最后一个节点 → explore_merge
        edges.push({
          source: branchNodes[branchNodes.length - 1].id,
          target: exploreEndGoal.id,
          stats: null,
          isMergeEdge: true,
          branchId: branchId
        })
      }
    } else {
      // 分支折叠:显示为单个分支节点
      const branchNode = {
        id: `branch_${branchId}`,
        description: branch.description,
        isBranchNode: true,
        branchId: branchId
      }
      nodes.push(branchNode)

      edges.push({
        source: prevGoal?.id ?? null,
        target: branchNode.id,
        stats: branch.cumulative_stats,
        isBranchEdge: true
      })

      edges.push({
        source: branchNode.id,
        target: exploreEndGoal.id,
        stats: null,
        isMergeEdge: true
      })
    }
  }

  return { nodes, edges }
}

视觉区分

边类型 说明 样式建议
普通边 顺序执行 实线
分叉边 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"
}

相关文档