|
@@ -1,25 +1,41 @@
|
|
|
# Agent Execution API - 前端对接文档
|
|
# Agent Execution API - 前端对接文档
|
|
|
|
|
|
|
|
-> 版本:v2.0
|
|
|
|
|
-> 更新日期:2026-02-03
|
|
|
|
|
|
|
+> 版本:v3.0
|
|
|
|
|
+> 更新日期:2026-02-04
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 📋 概览
|
|
|
|
|
|
|
+## 概览
|
|
|
|
|
|
|
|
-本 API 提供 Agent 执行过程的实时可视化能力,包括:
|
|
|
|
|
-- **REST API** - 查询历史数据、获取完整 Step 树
|
|
|
|
|
-- **WebSocket** - 实时推送 Step 更新(支持断线续传)
|
|
|
|
|
|
|
+本 API 提供 Agent 执行过程的实时可视化能力:
|
|
|
|
|
+- **REST API** - 查询 Trace 和 GoalTree
|
|
|
|
|
+- **WebSocket** - 实时推送 Goal/Message 更新(支持断线续传)
|
|
|
|
|
|
|
|
**核心概念**:
|
|
**核心概念**:
|
|
|
-- **Trace** - 一次完整的任务执行(如一次 Agent 运行)
|
|
|
|
|
-- **Step** - 执行过程中的一个原子操作,形成树结构
|
|
|
|
|
-- **Event** - Step 的变更事件(用于 WebSocket 推送和断线续传)
|
|
|
|
|
|
|
+- **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
|
|
|
|
|
|
|
|
-可以运行 python3 examples/feature_extract/run.py 来生成数据。
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 🌐 REST API
|
|
|
|
|
|
|
+## REST API
|
|
|
|
|
|
|
|
### 基础信息
|
|
### 基础信息
|
|
|
|
|
|
|
@@ -30,8 +46,6 @@
|
|
|
|
|
|
|
|
### 1. 列出 Traces
|
|
### 1. 列出 Traces
|
|
|
|
|
|
|
|
-获取 Trace 列表(支持过滤)
|
|
|
|
|
-
|
|
|
|
|
```http
|
|
```http
|
|
|
GET /api/traces?status=running&limit=20
|
|
GET /api/traces?status=running&limit=20
|
|
|
```
|
|
```
|
|
@@ -50,12 +64,13 @@ GET /api/traces?status=running&limit=20
|
|
|
{
|
|
{
|
|
|
"trace_id": "abc123",
|
|
"trace_id": "abc123",
|
|
|
"mode": "agent",
|
|
"mode": "agent",
|
|
|
- "task": "分析代码库结构",
|
|
|
|
|
|
|
+ "task": "实现用户认证功能",
|
|
|
"status": "running",
|
|
"status": "running",
|
|
|
- "total_steps": 15,
|
|
|
|
|
|
|
+ "total_messages": 15,
|
|
|
"total_tokens": 5000,
|
|
"total_tokens": 5000,
|
|
|
"total_cost": 0.05,
|
|
"total_cost": 0.05,
|
|
|
- "created_at": "2026-02-03T15:30:00"
|
|
|
|
|
|
|
+ "current_goal_id": "2.1",
|
|
|
|
|
+ "created_at": "2026-02-04T15:30:00"
|
|
|
}
|
|
}
|
|
|
],
|
|
],
|
|
|
"total": 1
|
|
"total": 1
|
|
@@ -64,7 +79,7 @@ GET /api/traces?status=running&limit=20
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-### 2. 获取 Trace 元数据
|
|
|
|
|
|
|
+### 2. 获取 Trace + GoalTree
|
|
|
|
|
|
|
|
```http
|
|
```http
|
|
|
GET /api/traces/{trace_id}
|
|
GET /api/traces/{trace_id}
|
|
@@ -75,114 +90,212 @@ GET /api/traces/{trace_id}
|
|
|
{
|
|
{
|
|
|
"trace_id": "abc123",
|
|
"trace_id": "abc123",
|
|
|
"mode": "agent",
|
|
"mode": "agent",
|
|
|
- "task": "分析代码库结构",
|
|
|
|
|
|
|
+ "task": "实现用户认证功能",
|
|
|
"status": "running",
|
|
"status": "running",
|
|
|
- "total_steps": 15,
|
|
|
|
|
|
|
+ "total_messages": 15,
|
|
|
"total_tokens": 5000,
|
|
"total_tokens": 5000,
|
|
|
"total_cost": 0.05,
|
|
"total_cost": 0.05,
|
|
|
- "total_duration_ms": 12345,
|
|
|
|
|
- "last_sequence": 15,
|
|
|
|
|
- "last_event_id": 15,
|
|
|
|
|
- "created_at": "2026-02-03T15:30:00",
|
|
|
|
|
- "completed_at": null
|
|
|
|
|
|
|
+ "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. 获取完整 Step 树 ⭐ 重要
|
|
|
|
|
|
|
+### 3. 获取 Messages(边详情)
|
|
|
|
|
|
|
|
-获取 Trace 的完整 Step 树(适合小型 Trace,<100 个 Step)
|
|
|
|
|
|
|
+查询 Goal 关联的所有 Messages(用于查看边的详细执行内容)。
|
|
|
|
|
|
|
|
```http
|
|
```http
|
|
|
-GET /api/traces/{trace_id}/tree?view=compact&max_depth=10
|
|
|
|
|
|
|
+GET /api/traces/{trace_id}/messages?goal_id=3
|
|
|
|
|
+GET /api/traces/{trace_id}/messages?branch_id=A
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
**查询参数**:
|
|
**查询参数**:
|
|
|
-| 参数 | 类型 | 默认值 | 说明 |
|
|
|
|
|
-|------|------|--------|------|
|
|
|
|
|
-| `view` | string | `compact` | `compact` - 精简视图 / `full` - 完整视图 |
|
|
|
|
|
-| `max_depth` | int | 无限 | 最大树深度 |
|
|
|
|
|
|
|
+| 参数 | 类型 | 必填 | 说明 |
|
|
|
|
|
+|------|------|------|------|
|
|
|
|
|
+| `goal_id` | string | 否 | 过滤指定 Goal 的 Messages(内部 ID)|
|
|
|
|
|
+| `branch_id` | string | 否 | 过滤指定分支的所有 Messages |
|
|
|
|
|
|
|
|
**响应示例**:
|
|
**响应示例**:
|
|
|
```json
|
|
```json
|
|
|
{
|
|
{
|
|
|
- "trace_id": "abc123",
|
|
|
|
|
- "root_steps": [
|
|
|
|
|
|
|
+ "messages": [
|
|
|
{
|
|
{
|
|
|
- "step_id": "step-001",
|
|
|
|
|
- "step_type": "thought",
|
|
|
|
|
- "status": "completed",
|
|
|
|
|
- "sequence": 1,
|
|
|
|
|
- "parent_id": null,
|
|
|
|
|
- "description": "分析项目结构...",
|
|
|
|
|
- "has_children": true,
|
|
|
|
|
- "children_count": 2,
|
|
|
|
|
- "duration_ms": 1234,
|
|
|
|
|
- "tokens": 500,
|
|
|
|
|
- "cost": 0.005,
|
|
|
|
|
- "created_at": "2026-02-03T15:30:01",
|
|
|
|
|
- "data": {
|
|
|
|
|
- "content": "让我先看看项目的目录结构...",
|
|
|
|
|
- "model": "claude-sonnet-4.5"
|
|
|
|
|
|
|
+ "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" }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
},
|
|
},
|
|
|
- "children": [
|
|
|
|
|
- {
|
|
|
|
|
- "step_id": "step-002",
|
|
|
|
|
- "step_type": "action",
|
|
|
|
|
- "status": "completed",
|
|
|
|
|
- "parent_id": "step-001",
|
|
|
|
|
- "description": "glob_files(**/*.py)",
|
|
|
|
|
- "data": {
|
|
|
|
|
- "tool_name": "glob_files",
|
|
|
|
|
- "arguments": {"pattern": "**/*.py"}
|
|
|
|
|
- },
|
|
|
|
|
- "children": [
|
|
|
|
|
- {
|
|
|
|
|
- "step_id": "step-003",
|
|
|
|
|
- "step_type": "result",
|
|
|
|
|
- "status": "completed",
|
|
|
|
|
- "parent_id": "step-002",
|
|
|
|
|
- "data": {
|
|
|
|
|
- "tool_name": "glob_files",
|
|
|
|
|
- "output": ["src/main.py", "src/utils.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
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**注意**:
|
|
|
|
|
-- `children` 字段包含嵌套的子节点(递归结构)
|
|
|
|
|
-- `compact` 视图:`data` 中的大字段(如 `messages`)会被省略
|
|
|
|
|
-- `full` 视图:返回所有字段(数据量可能很大)
|
|
|
|
|
-
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-### 4. 懒加载单个节点
|
|
|
|
|
-
|
|
|
|
|
-适用于大型 Trace(>100 Step),按需加载子树
|
|
|
|
|
|
|
+### 4. 获取分支详情(按需加载)
|
|
|
|
|
|
|
|
```http
|
|
```http
|
|
|
-GET /api/traces/{trace_id}/node/{step_id}?expand=true&max_depth=2
|
|
|
|
|
|
|
+GET /api/traces/{trace_id}/branches/{branch_id}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**查询参数**:
|
|
|
|
|
-| 参数 | 类型 | 默认值 | 说明 |
|
|
|
|
|
-|------|------|--------|------|
|
|
|
|
|
-| `expand` | bool | `false` | 是否展开子节点 |
|
|
|
|
|
-| `max_depth` | int | 1 | 展开深度 |
|
|
|
|
|
-| `view` | string | `compact` | 视图类型 |
|
|
|
|
|
-
|
|
|
|
|
-**响应**:与 `/tree` 格式相同,但只返回指定节点及其子树。
|
|
|
|
|
|
|
+**响应示例**:
|
|
|
|
|
+```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
|
|
|
|
|
|
|
+## WebSocket API
|
|
|
|
|
|
|
|
### 连接
|
|
### 连接
|
|
|
|
|
|
|
@@ -195,7 +308,7 @@ const ws = new WebSocket(
|
|
|
**查询参数**:
|
|
**查询参数**:
|
|
|
| 参数 | 类型 | 默认值 | 说明 |
|
|
| 参数 | 类型 | 默认值 | 说明 |
|
|
|
|------|------|--------|------|
|
|
|------|------|--------|------|
|
|
|
-| `since_event_id` | int | `0` | 从哪个事件 ID 开始<br>`0` = 补发所有历史<br>`>0` = 只补发指定 ID 之后的 |
|
|
|
|
|
|
|
+| `since_event_id` | int | `0` | 从哪个事件 ID 开始。`0` = 补发所有历史 |
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -203,195 +316,283 @@ const ws = new WebSocket(
|
|
|
|
|
|
|
|
#### 1. connected(连接成功)
|
|
#### 1. connected(连接成功)
|
|
|
|
|
|
|
|
|
|
+连接后推送完整 GoalTree,前端据此初始化 DAG。
|
|
|
|
|
+
|
|
|
```json
|
|
```json
|
|
|
{
|
|
{
|
|
|
"event": "connected",
|
|
"event": "connected",
|
|
|
"trace_id": "abc123",
|
|
"trace_id": "abc123",
|
|
|
- "current_event_id": 15
|
|
|
|
|
|
|
+ "current_event_id": 15,
|
|
|
|
|
+ "goal_tree": {
|
|
|
|
|
+ "mission": "实现用户认证功能",
|
|
|
|
|
+ "current_id": "2.1",
|
|
|
|
|
+ "goals": [...]
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**说明**:连接建立后的第一条消息,包含当前最新的 event_id
|
|
|
|
|
-
|
|
|
|
|
**前端处理**:
|
|
**前端处理**:
|
|
|
```javascript
|
|
```javascript
|
|
|
if (data.event === 'connected') {
|
|
if (data.event === 'connected') {
|
|
|
- // 保存 event_id 用于断线重连
|
|
|
|
|
|
|
+ initDAG(data.goal_tree)
|
|
|
localStorage.setItem('last_event_id', data.current_event_id)
|
|
localStorage.setItem('last_event_id', data.current_event_id)
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-#### 2. step_added(新增 Step)⭐ 最常用
|
|
|
|
|
|
|
+#### 2. goal_added(新增 Goal)
|
|
|
|
|
|
|
|
```json
|
|
```json
|
|
|
{
|
|
{
|
|
|
- "event": "step_added",
|
|
|
|
|
|
|
+ "event": "goal_added",
|
|
|
"event_id": 16,
|
|
"event_id": 16,
|
|
|
- "ts": "2026-02-03T15:30:10.123456",
|
|
|
|
|
- "step": {
|
|
|
|
|
- "step_id": "step-016",
|
|
|
|
|
- "step_type": "action",
|
|
|
|
|
- "status": "completed",
|
|
|
|
|
- "sequence": 16,
|
|
|
|
|
- "parent_id": "step-001",
|
|
|
|
|
- "description": "read_file(config.yaml)",
|
|
|
|
|
- "has_children": false,
|
|
|
|
|
- "children_count": 0,
|
|
|
|
|
- "duration_ms": 50,
|
|
|
|
|
- "data": {
|
|
|
|
|
- "tool_name": "read_file",
|
|
|
|
|
- "arguments": {"file_path": "config.yaml"}
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ "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
|
|
```javascript
|
|
|
-if (data.event === 'step_added') {
|
|
|
|
|
- // 添加到树结构
|
|
|
|
|
- addStepToTree(data.step)
|
|
|
|
|
-
|
|
|
|
|
- // 更新 event_id
|
|
|
|
|
- localStorage.setItem('last_event_id', data.event_id)
|
|
|
|
|
|
|
+if (data.event === 'goal_added') {
|
|
|
|
|
+ insertGoal(data.goal, data.parent_id)
|
|
|
|
|
+ regenerateDAG()
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-#### 3. step_updated(Step 更新)
|
|
|
|
|
|
|
+#### 3. goal_updated(Goal 状态变化)
|
|
|
|
|
+
|
|
|
|
|
+包含级联完成场景:当所有子 Goal 完成时,父 Goal 自动 completed。
|
|
|
|
|
|
|
|
```json
|
|
```json
|
|
|
{
|
|
{
|
|
|
- "event": "step_updated",
|
|
|
|
|
|
|
+ "event": "goal_updated",
|
|
|
"event_id": 17,
|
|
"event_id": 17,
|
|
|
- "ts": "2026-02-03T15:30:15.123456",
|
|
|
|
|
- "step_id": "step-016",
|
|
|
|
|
- "patch": {
|
|
|
|
|
|
|
+ "goal_id": "3",
|
|
|
|
|
+ "updates": {
|
|
|
"status": "completed",
|
|
"status": "completed",
|
|
|
- "duration_ms": 1234
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ "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": "..." }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**说明**:`patch` 是增量更新(只包含变化的字段)
|
|
|
|
|
-
|
|
|
|
|
**前端处理**:
|
|
**前端处理**:
|
|
|
```javascript
|
|
```javascript
|
|
|
-if (data.event === 'step_updated') {
|
|
|
|
|
- const step = findStepById(data.step_id)
|
|
|
|
|
- Object.assign(step, data.patch)
|
|
|
|
|
- updateUI()
|
|
|
|
|
|
|
+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. trace_completed(任务完成)
|
|
|
|
|
|
|
+#### 4. message_added(新 Message)
|
|
|
|
|
+
|
|
|
|
|
+后端更新统计后推送,包含受影响的所有 Goals。
|
|
|
|
|
|
|
|
```json
|
|
```json
|
|
|
{
|
|
{
|
|
|
- "event": "trace_completed",
|
|
|
|
|
|
|
+ "event": "message_added",
|
|
|
"event_id": 18,
|
|
"event_id": 18,
|
|
|
- "ts": "2026-02-03T15:35:00.123456",
|
|
|
|
|
- "trace_id": "abc123",
|
|
|
|
|
- "total_steps": 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
|
|
```javascript
|
|
|
-if (data.event === 'trace_completed') {
|
|
|
|
|
- console.log('Task completed!')
|
|
|
|
|
- ws.close()
|
|
|
|
|
|
|
+if (data.event === 'message_added') {
|
|
|
|
|
+ for (const g of data.affected_goals) {
|
|
|
|
|
+ updateGoalStats(g.goal_id, g)
|
|
|
|
|
+ }
|
|
|
|
|
+ // 根据当前展开状态更新对应边
|
|
|
|
|
+ rerenderEdge(data.message.goal_id)
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-#### 5. error(错误)
|
|
|
|
|
|
|
+#### 5. trace_completed(任务完成)
|
|
|
|
|
|
|
|
```json
|
|
```json
|
|
|
{
|
|
{
|
|
|
- "event": "error",
|
|
|
|
|
- "message": "Too many missed events (150), please reload full tree via REST API"
|
|
|
|
|
|
|
+ "event": "trace_completed",
|
|
|
|
|
+ "event_id": 50,
|
|
|
|
|
+ "trace_id": "abc123",
|
|
|
|
|
+ "total_messages": 50,
|
|
|
|
|
+ "total_tokens": 25000,
|
|
|
|
|
+ "total_cost": 0.25
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**说明**:
|
|
|
|
|
-- 如果断线期间产生超过 100 条事件,会收到此错误
|
|
|
|
|
-- 此时应该用 REST API 重新加载完整树
|
|
|
|
|
|
|
+**前端处理**:
|
|
|
|
|
+```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
|
|
```javascript
|
|
|
-let lastEventId = 0
|
|
|
|
|
|
|
+if (data.event === 'branch_started') {
|
|
|
|
|
+ insertBranch(data.explore_start_id, data.branch)
|
|
|
|
|
+ regenerateDAG()
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-// 初次连接
|
|
|
|
|
-const ws = new WebSocket(
|
|
|
|
|
- `ws://localhost:8000/api/traces/abc123/watch?since_event_id=0`
|
|
|
|
|
-)
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-ws.onmessage = (event) => {
|
|
|
|
|
- const data = JSON.parse(event.data)
|
|
|
|
|
|
|
+#### 7. branch_completed(分支完成)
|
|
|
|
|
|
|
|
- // 保存最新 event_id
|
|
|
|
|
- if (data.event_id) {
|
|
|
|
|
- lastEventId = data.event_id
|
|
|
|
|
- localStorage.setItem('trace_abc123_event_id', lastEventId)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+分支内所有 Goals 完成后触发。
|
|
|
|
|
|
|
|
-ws.onclose = () => {
|
|
|
|
|
- // 3 秒后重连
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- // 从上次的 event_id 继续
|
|
|
|
|
- const ws2 = new WebSocket(
|
|
|
|
|
- `ws://localhost:8000/api/traces/abc123/watch?since_event_id=${lastEventId}`
|
|
|
|
|
- )
|
|
|
|
|
- // 服务器会补发 lastEventId 之后的所有事件
|
|
|
|
|
- }, 3000)
|
|
|
|
|
|
|
+```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": "..." }
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**注意**:
|
|
|
|
|
-- 每条消息都有 `event_id` 和 `ts` 字段
|
|
|
|
|
-- 重连时传入 `since_event_id`,服务器自动补发缺失的事件(最多 100 条)
|
|
|
|
|
-- 超过 100 条会返回错误,需要用 REST API 重新加载
|
|
|
|
|
|
|
+**前端处理**:
|
|
|
|
|
+```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
|
|
```javascript
|
|
|
-// 每 30 秒发送心跳
|
|
|
|
|
-const heartbeat = setInterval(() => {
|
|
|
|
|
- if (ws.readyState === WebSocket.OPEN) {
|
|
|
|
|
- ws.send('ping')
|
|
|
|
|
|
|
+if (data.event === 'message_added') {
|
|
|
|
|
+ for (const g of data.affected_goals) {
|
|
|
|
|
+ updateGoalStats(g.goal_id, g)
|
|
|
}
|
|
}
|
|
|
-}, 30000)
|
|
|
|
|
-
|
|
|
|
|
-ws.onmessage = (event) => {
|
|
|
|
|
- const data = JSON.parse(event.data)
|
|
|
|
|
- if (data.event === 'pong') {
|
|
|
|
|
- console.log('Connection alive')
|
|
|
|
|
|
|
+ // 分支内消息还需更新分支统计
|
|
|
|
|
+ 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
|
|
|
|
|
|
|
@@ -399,261 +600,371 @@ ws.onmessage = (event) => {
|
|
|
|------|------|------|
|
|
|------|------|------|
|
|
|
| `trace_id` | string | 唯一 ID |
|
|
| `trace_id` | string | 唯一 ID |
|
|
|
| `mode` | string | `call` - 单次调用 / `agent` - Agent 模式 |
|
|
| `mode` | string | `call` - 单次调用 / `agent` - Agent 模式 |
|
|
|
-| `task` | string | 任务描述(Agent 模式)|
|
|
|
|
|
|
|
+| `task` | string | 任务描述 |
|
|
|
| `status` | string | `running` / `completed` / `failed` |
|
|
| `status` | string | `running` / `completed` / `failed` |
|
|
|
-| `total_steps` | int | Step 总数 |
|
|
|
|
|
|
|
+| `total_messages` | int | Message 总数 |
|
|
|
| `total_tokens` | int | Token 总消耗 |
|
|
| `total_tokens` | int | Token 总消耗 |
|
|
|
| `total_cost` | float | 成本总和 |
|
|
| `total_cost` | float | 成本总和 |
|
|
|
-| `total_duration_ms` | int | 总耗时(毫秒)|
|
|
|
|
|
-| `last_sequence` | int | 最新 Step 的 sequence |
|
|
|
|
|
-| `last_event_id` | int | 最新事件 ID |
|
|
|
|
|
|
|
+| `current_goal_id` | string | 当前焦点 Goal 的内部 ID |
|
|
|
| `created_at` | string | 创建时间(ISO 8601)|
|
|
| `created_at` | string | 创建时间(ISO 8601)|
|
|
|
| `completed_at` | string \| null | 完成时间 |
|
|
| `completed_at` | string \| null | 完成时间 |
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-### Step
|
|
|
|
|
-
|
|
|
|
|
-#### 顶层字段(所有 Step 共有)
|
|
|
|
|
|
|
+### GoalTree
|
|
|
|
|
|
|
|
| 字段 | 类型 | 说明 |
|
|
| 字段 | 类型 | 说明 |
|
|
|
|------|------|------|
|
|
|------|------|------|
|
|
|
-| `step_id` | string | 唯一 ID |
|
|
|
|
|
-| `trace_id` | string | 所属 Trace ID |
|
|
|
|
|
-| `step_type` | string | 步骤类型(见下表)|
|
|
|
|
|
-| `status` | string | 状态(见下表)|
|
|
|
|
|
-| `sequence` | int | 在 Trace 中的顺序(递增)|
|
|
|
|
|
-| `parent_id` | string \| null | 父节点 ID |
|
|
|
|
|
-| `description` | string | 简短描述 |
|
|
|
|
|
-| `summary` | string \| null | 总结(仅 `evaluation` 类型)|
|
|
|
|
|
-| `has_children` | bool | 是否有子节点 |
|
|
|
|
|
-| `children_count` | int | 子节点数量 |
|
|
|
|
|
-| `duration_ms` | int \| null | 耗时(毫秒)|
|
|
|
|
|
-| `tokens` | int \| null | Token 消耗 |
|
|
|
|
|
-| `cost` | float \| null | 成本 |
|
|
|
|
|
-| `created_at` | string | 创建时间 |
|
|
|
|
|
-| `data` | object | 类型相关的详细数据 |
|
|
|
|
|
|
|
+| `mission` | string | 总任务描述(来自 Trace.task)|
|
|
|
|
|
+| `current_id` | string \| null | 当前焦点 Goal 的内部 ID |
|
|
|
|
|
+| `goals` | Goal[] | 顶层目标列表 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-#### step_type(步骤类型)
|
|
|
|
|
|
|
+### Goal
|
|
|
|
|
|
|
|
-| 类型 | 说明 | 来源 |
|
|
|
|
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
|------|------|------|
|
|
|------|------|------|
|
|
|
-| `goal` | 目标/计划 | LLM |
|
|
|
|
|
-| `thought` | 思考/分析 | LLM |
|
|
|
|
|
-| `evaluation` | 评估总结 | LLM |
|
|
|
|
|
-| `response` | 最终回复 | LLM |
|
|
|
|
|
-| `action` | 工具调用 | System |
|
|
|
|
|
-| `result` | 工具结果 | System |
|
|
|
|
|
-| `memory_read` | 读取记忆 | System |
|
|
|
|
|
-| `memory_write` | 写入记忆 | System |
|
|
|
|
|
-
|
|
|
|
|
-#### status(步骤状态)
|
|
|
|
|
-
|
|
|
|
|
-| 状态 | 说明 |
|
|
|
|
|
-|------|------|
|
|
|
|
|
-| `planned` | 计划中(未执行)|
|
|
|
|
|
-| `in_progress` | 执行中 |
|
|
|
|
|
-| `awaiting_approval` | 等待用户确认 |
|
|
|
|
|
-| `completed` | 已完成 |
|
|
|
|
|
-| `failed` | 失败 |
|
|
|
|
|
-| `skipped` | 跳过 |
|
|
|
|
|
-
|
|
|
|
|
-#### data 字段(按 step_type)
|
|
|
|
|
-
|
|
|
|
|
-**thought / response**:
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "model": "claude-sonnet-4.5",
|
|
|
|
|
- "content": "让我先分析...",
|
|
|
|
|
- "messages": [...], // full 视图才有
|
|
|
|
|
- "tool_calls": [...] // 如果有工具调用
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
|
|
+| `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` 维护
|
|
|
|
|
+- 显示序号由前端/后端动态生成
|
|
|
|
|
|
|
|
-**action**:
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "tool_name": "read_file",
|
|
|
|
|
- "arguments": {
|
|
|
|
|
- "file_path": "config.yaml"
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-**result**:
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "tool_name": "read_file",
|
|
|
|
|
- "output": "file content...",
|
|
|
|
|
- "error": null
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
|
|
+### BranchContext
|
|
|
|
|
|
|
|
-**memory_read**:
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "experiences_count": 5,
|
|
|
|
|
- "skills_count": 3
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
|
|
+分支元数据(主请求返回,不含内部 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 | 最新消息(用于预览)|
|
|
|
|
|
|
|
|
-## 🎯 推荐的实现方案
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-### 方案 1:纯 WebSocket(简单场景)
|
|
|
|
|
|
|
+### BranchDetail
|
|
|
|
|
|
|
|
-适用于:实时监控进行中的任务,Step 数量 < 100
|
|
|
|
|
|
|
+分支详情(按需加载)
|
|
|
|
|
|
|
|
-```javascript
|
|
|
|
|
-// 只用 WebSocket,自动获取历史
|
|
|
|
|
-const ws = new WebSocket(
|
|
|
|
|
- 'ws://localhost:8000/api/traces/abc123/watch?since_event_id=0'
|
|
|
|
|
-)
|
|
|
|
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| `id` | string | 分支 ID |
|
|
|
|
|
+| `description` | string | 探索方向描述 |
|
|
|
|
|
+| `status` | string | 状态 |
|
|
|
|
|
+| `summary` | string \| null | 总结 |
|
|
|
|
|
+| `goal_tree` | GoalTree | 分支内的 GoalTree |
|
|
|
|
|
+| `cumulative_stats` | GoalStats | 累计统计 |
|
|
|
|
|
|
|
|
-ws.onmessage = (event) => {
|
|
|
|
|
- const data = JSON.parse(event.data)
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
- if (data.event === 'step_added') {
|
|
|
|
|
- // 历史 + 新增的 Step 都会收到
|
|
|
|
|
- addStepToTree(data.step)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
|
|
+### GoalStats
|
|
|
|
|
|
|
|
-**优点**:代码简单
|
|
|
|
|
-**缺点**:超过 100 个 Step 会失败
|
|
|
|
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| `message_count` | int | 消息数量 |
|
|
|
|
|
+| `total_tokens` | int | Token 总数 |
|
|
|
|
|
+| `total_cost` | float | 总成本 |
|
|
|
|
|
+| `preview` | string \| null | 工具调用摘要(如 `"read → edit × 2"`)|
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-### 方案 2:REST + WebSocket(生产推荐)
|
|
|
|
|
|
|
+### Message
|
|
|
|
|
|
|
|
-适用于:查看历史任务,或 Step 数量 > 100
|
|
|
|
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| `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 | 创建时间 |
|
|
|
|
|
|
|
|
-```javascript
|
|
|
|
|
-// 1. 先用 REST API 获取完整树
|
|
|
|
|
-const response = await fetch(
|
|
|
|
|
- `/api/traces/${traceId}/tree?view=compact`
|
|
|
|
|
-)
|
|
|
|
|
-const treeData = await response.json()
|
|
|
|
|
-renderTree(treeData.root_steps)
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-// 2. 连接 WebSocket 监听增量更新
|
|
|
|
|
-const ws = new WebSocket(
|
|
|
|
|
- `ws://localhost:8000/api/traces/${traceId}/watch?since_event_id=0`
|
|
|
|
|
-)
|
|
|
|
|
|
|
+## DAG 可视化(前端实现)
|
|
|
|
|
|
|
|
-ws.onmessage = (event) => {
|
|
|
|
|
- const data = JSON.parse(event.data)
|
|
|
|
|
- if (data.event === 'step_added') {
|
|
|
|
|
- addStepToTree(data.step) // 只处理新增的
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
|
|
+后端只提供 GoalTree(扁平列表 + parent_id),前端负责生成 DAG 视图。
|
|
|
|
|
|
|
|
-**优点**:可靠,支持大型 Trace
|
|
|
|
|
-**缺点**:略复杂
|
|
|
|
|
|
|
+### 核心逻辑
|
|
|
|
|
|
|
|
----
|
|
|
|
|
|
|
+**普通 Goal(子目标)**:
|
|
|
|
|
+1. 未展开:显示父 Goal
|
|
|
|
|
+2. 已展开:父 Goal 被子 Goal **替代**(顺序)
|
|
|
|
|
|
|
|
-## 🐛 错误处理
|
|
|
|
|
|
|
+**explore Goal(分支)**:
|
|
|
|
|
+1. 未展开:显示单个 explore_start → explore_merge 节点
|
|
|
|
|
+2. 已展开:显示**并行分支路径**(分叉-汇合)
|
|
|
|
|
|
|
|
-### HTTP 错误码
|
|
|
|
|
|
|
+### 边数据
|
|
|
|
|
|
|
|
-| 状态码 | 说明 |
|
|
|
|
|
-|--------|------|
|
|
|
|
|
-| 200 | 成功 |
|
|
|
|
|
-| 404 | Trace/Step 不存在 |
|
|
|
|
|
-| 400 | 参数错误 |
|
|
|
|
|
-| 500 | 服务器错误 |
|
|
|
|
|
|
|
+从 target 节点的 stats 获取:
|
|
|
|
|
+- 折叠边 → `target.cumulative_stats`
|
|
|
|
|
+- 展开边 → `target.self_stats`
|
|
|
|
|
+- 分支边 → `branch.cumulative_stats`
|
|
|
|
|
|
|
|
-### WebSocket 错误
|
|
|
|
|
|
|
+### 示例:普通展开(子目标)
|
|
|
|
|
|
|
|
```javascript
|
|
```javascript
|
|
|
-ws.onerror = (error) => {
|
|
|
|
|
- console.error('WebSocket error:', error)
|
|
|
|
|
- // 重连
|
|
|
|
|
|
|
+// 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: "测试" }
|
|
|
|
|
+ ]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-ws.onclose = (event) => {
|
|
|
|
|
- console.log('Connection closed:', event.code, event.reason)
|
|
|
|
|
- // 自动重连
|
|
|
|
|
|
|
+// 构建层级视图
|
|
|
|
|
+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
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-### 1. 保存 event_id 用于断线重连
|
|
|
|
|
|
|
+### 示例:分支展开(explore)
|
|
|
|
|
|
|
|
```javascript
|
|
```javascript
|
|
|
-ws.onmessage = (event) => {
|
|
|
|
|
- const data = JSON.parse(event.data)
|
|
|
|
|
- if (data.event_id) {
|
|
|
|
|
- localStorage.setItem(
|
|
|
|
|
- `trace_${traceId}_event_id`,
|
|
|
|
|
- data.event_id
|
|
|
|
|
- )
|
|
|
|
|
|
|
+// 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 }
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### 2. 实现自动重连
|
|
|
|
|
|
|
+### 视觉区分
|
|
|
|
|
+
|
|
|
|
|
+| 边类型 | 说明 | 样式建议 |
|
|
|
|
|
+|--------|------|---------|
|
|
|
|
|
+| 普通边 | 顺序执行 | 实线 |
|
|
|
|
|
+| 分叉边 | explore 分支开始 | 虚线或带标记 |
|
|
|
|
|
+| 汇合边 | 分支合并到 merge 节点 | 虚线或带标记 |
|
|
|
|
|
+| 废弃边 | abandoned 分支 | 灰色 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 断线续传
|
|
|
|
|
|
|
|
```javascript
|
|
```javascript
|
|
|
-function connectWebSocket(traceId) {
|
|
|
|
|
- const lastEventId = localStorage.getItem(`trace_${traceId}_event_id`) || 0
|
|
|
|
|
|
|
+let lastEventId = 0
|
|
|
|
|
+
|
|
|
|
|
+function connect(traceId) {
|
|
|
const ws = new WebSocket(
|
|
const ws = new WebSocket(
|
|
|
`ws://localhost:8000/api/traces/${traceId}/watch?since_event_id=${lastEventId}`
|
|
`ws://localhost:8000/api/traces/${traceId}/watch?since_event_id=${lastEventId}`
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- ws.onclose = () => {
|
|
|
|
|
- setTimeout(() => connectWebSocket(traceId), 3000)
|
|
|
|
|
|
|
+ ws.onmessage = (event) => {
|
|
|
|
|
+ const data = JSON.parse(event.data)
|
|
|
|
|
+ if (data.event_id) {
|
|
|
|
|
+ lastEventId = data.event_id
|
|
|
|
|
+ localStorage.setItem(`trace_${traceId}_event_id`, lastEventId)
|
|
|
|
|
+ }
|
|
|
|
|
+ // 处理事件...
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return ws
|
|
|
|
|
|
|
+ ws.onclose = () => {
|
|
|
|
|
+ setTimeout(() => connect(traceId), 3000)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### 3. 使用 compact 视图减少流量
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-```javascript
|
|
|
|
|
-// ✅ 推荐
|
|
|
|
|
-const response = await fetch(`/api/traces/${id}/tree?view=compact`)
|
|
|
|
|
|
|
+## 错误处理
|
|
|
|
|
|
|
|
-// ❌ 避免(数据量可能很大)
|
|
|
|
|
-const response = await fetch(`/api/traces/${id}/tree?view=full`)
|
|
|
|
|
-```
|
|
|
|
|
|
|
+### HTTP 错误码
|
|
|
|
|
|
|
|
-### 4. 按需懒加载(大型 Trace)
|
|
|
|
|
|
|
+| 状态码 | 说明 |
|
|
|
|
|
+|--------|------|
|
|
|
|
|
+| 200 | 成功 |
|
|
|
|
|
+| 404 | Trace 不存在 |
|
|
|
|
|
+| 400 | 参数错误 |
|
|
|
|
|
+| 500 | 服务器错误 |
|
|
|
|
|
|
|
|
-```javascript
|
|
|
|
|
-// 初次只加载第一层
|
|
|
|
|
-const tree = await fetch(
|
|
|
|
|
- `/api/traces/${id}/tree?max_depth=1`
|
|
|
|
|
-).then(r => r.json())
|
|
|
|
|
-
|
|
|
|
|
-// 用户点击展开时,懒加载子树
|
|
|
|
|
-async function onExpand(stepId) {
|
|
|
|
|
- const node = await fetch(
|
|
|
|
|
- `/api/traces/${id}/node/${stepId}?expand=true&max_depth=1`
|
|
|
|
|
- ).then(r => r.json())
|
|
|
|
|
-
|
|
|
|
|
- appendChildren(stepId, node.children)
|
|
|
|
|
|
|
+### WebSocket 错误
|
|
|
|
|
+
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "event": "error",
|
|
|
|
|
+ "message": "Too many missed events, please reload via REST API"
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 🔗 相关文档
|
|
|
|
|
-
|
|
|
|
|
-- [Step 树结构详解](./step-tree.md)
|
|
|
|
|
-- [API 接口规范](./trace-api.md)
|
|
|
|
|
-- [架构设计文档](./README.md)
|
|
|
|
|
-
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## 📞 问题反馈
|
|
|
|
|
|
|
+## 相关文档
|
|
|
|
|
|
|
|
-如有问题请提 Issue:https://github.com/anthropics/agent/issues
|
|
|
|
|
|
|
+- [Context 管理设计](../docs/context-management.md) - Goal 机制完整设计
|
|
|
|
|
+- [Trace 模块说明](../docs/trace-api.md) - 后端实现细节
|