# 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 ```http GET /api/traces?status=running&limit=20 ``` **查询参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `status` | string | 否 | 过滤状态:`running` / `completed` / `failed` | | `mode` | string | 否 | 过滤模式:`call` / `agent` | | `limit` | int | 否 | 返回数量(默认 50,最大 100)| **响应示例**: ```json { "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 ```http GET /api/traces/{trace_id} ``` **响应示例**: ```json { "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(用于查看边的详细执行内容)。 ```http 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 | **响应示例**: ```json { "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. 获取分支详情(按需加载) ```http GET /api/traces/{trace_id}/branches/{branch_id} ``` **响应示例**: ```json { "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 ### 连接 ```javascript 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。 ```json { "event": "connected", "trace_id": "abc123", "current_event_id": 15, "goal_tree": { "mission": "实现用户认证功能", "current_id": "2.1", "goals": [...] } } ``` **前端处理**: ```javascript if (data.event === 'connected') { initDAG(data.goal_tree) localStorage.setItem('last_event_id', data.current_event_id) } ``` --- #### 2. goal_added(新增 Goal) ```json { "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" } ``` **前端处理**: ```javascript if (data.event === 'goal_added') { insertGoal(data.goal, data.parent_id) regenerateDAG() } ``` --- #### 3. goal_updated(Goal 状态变化) 包含级联完成场景:当所有子 Goal 完成时,父 Goal 自动 completed。 ```json { "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": "..." } } ] } ``` **前端处理**: ```javascript 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。 ```json { "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` **前端处理**: ```javascript 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(任务完成) ```json { "event": "trace_completed", "event_id": 50, "trace_id": "abc123", "total_messages": 50, "total_tokens": 25000, "total_cost": 0.25 } ``` **前端处理**: ```javascript if (data.event === 'trace_completed') { markTraceCompleted() ws.close() } ``` --- #### 6. branch_started(分支开始探索) explore 工具触发并行探索时,为每个分支发送此事件。 ```json { "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 } } ``` **前端处理**: ```javascript if (data.event === 'branch_started') { insertBranch(data.explore_start_id, data.branch) regenerateDAG() } ``` --- #### 7. branch_completed(分支完成) 分支内所有 Goals 完成后触发。 ```json { "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": "..." } } ``` **前端处理**: ```javascript 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_id` 和 `affected_branches` 字段: ```json { "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": {...} } ] } ``` **前端处理**: ```javascript 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` ### 示例:普通展开(子目标) ```javascript // 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) ```javascript // 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 分支 | 灰色 | --- ## 断线续传 ```javascript 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 错误 ```json { "event": "error", "message": "Too many missed events, please reload via REST API" } ``` --- ## 相关文档 - [Context 管理设计](../docs/context-management.md) - Goal 机制完整设计 - [Trace 模块说明](../docs/trace-api.md) - 后端实现细节