# 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 ```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} ``` **响应示例 1**(主 Trace,explore 进行中): ```json { "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 已完成并合并): ```json { "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)。 ```http GET /api/traces/abc123.A ``` **响应示例**: ```json { "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(用于查看边的详细执行内容)。 ```http 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")| **响应示例**: ```json { "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 ### 连接 ```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(连接成功) 连接后推送完整 Trace 信息,前端据此初始化 DAG。 ```json { "event": "connected", "trace_id": "abc123", "current_event_id": 15, "trace": { "trace_id": "abc123", "status": "running", "goal_tree": { "mission": "实现用户认证功能", "current_id": "2", "goals": [...] }, "sub_traces": {} } } ``` **前端处理**: ```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", "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", "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. sub_trace_started(Sub-Trace 开始) explore 或 delegate 工具启动 Sub-Trace 时触发。 ```json { "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 } } ``` **前端处理**: ```javascript if (data.event === 'sub_trace_started') { insertSubTrace(data.parent_trace_id, data.sub_trace) regenerateDAG() } ``` --- #### 7. sub_trace_completed(Sub-Trace 完成) Sub-Trace 执行完成后触发。 ```json { "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 } ``` **前端处理**: ```javascript 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-1e5b7a9f3c4d") - Sub-Trace:`{parent_uuid}@{mode}-{timestamp}-{seq}` - 例如:"2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@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` 字段,格式如下: ```json { "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**: ```http GET /api/traces/{trace_id}/messages?goal_id=_init ``` - 使用特殊值 `_init` 或 `null` 查询 goal_id 为 null 的 messages - 用于前端展示 START 节点的详情 --- ## 前端展示:START 虚拟节点 **设计原则**:诚实反映数据结构,不在后端创建额外节点。 ### 概念 在 Trace 开始阶段,Agent 还没有创建任何 Goal,这期间产生的 Messages 的 `goal_id` 为 `null`。前端应该创建一个虚拟的 **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 的聚合统计 ### 实现示例 ```javascript // 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**: ```javascript // 获取初始阶段的 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 ### 示例:普通展开(子目标) ```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 } ``` ### 示例:Sub-Trace 展开 ```javascript // 主 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 分支 | 灰色 | --- ## 断线续传 ```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) - 后端实现细节