版本:v3.0 更新日期:2026-02-04
本 API 提供 Agent 执行过程的实时可视化能力:
核心概念:
数据结构:
后端存储两类数据:
1. GoalTree(嵌套 JSON)- 目标树结构 + 聚合统计(stats)
2. Messages(扁平列表)- 详细执行记录,通过 goal_id 关联 Goal
关系:Goal.stats 是从关联的 Messages 聚合计算出来的
DAG 可视化(前端负责):
cumulative_stats,展开用 self_stats)http://localhost:8000application/jsonGET /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
}
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": {}
}
查询 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
}
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" }
}
const ws = new WebSocket(
'ws://localhost:8000/api/traces/{trace_id}/watch?since_event_id=0'
)
查询参数:
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| since_event_id | int | 0 | 从哪个事件 ID 开始。0 = 补发所有历史 |
连接后推送完整 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)
}
{
"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()
}
包含级联完成场景:当所有子 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()
}
后端更新统计后推送,包含受影响的所有 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_statscumulative_stats前端处理:
if (data.event === 'message_added') {
for (const g of data.affected_goals) {
updateGoalStats(g.goal_id, g)
}
// 根据当前展开状态更新对应边
rerenderEdge(data.message.goal_id)
}
{
"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()
}
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()
}
分支内所有 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()
}
分支内的 Message 会包含 branch_id 和 affected_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_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 | 完成时间 |
| 字段 | 类型 | 说明 |
|---|---|---|
mission |
string | 总任务描述(来自 Trace.task) |
current_id |
string | null | 当前焦点 Goal 的内部 ID |
goals |
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 设计:
parent_id 维护branch_id 维护分支元数据(主请求返回,不含内部 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 | 最新消息(用于预览) |
分支详情(按需加载)
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 分支 ID |
description |
string | 探索方向描述 |
status |
string | 状态 |
summary |
string | null | 总结 |
goal_tree |
GoalTree | 分支内的 GoalTree |
cumulative_stats |
GoalStats | 累计统计 |
| 字段 | 类型 | 说明 |
|---|---|---|
message_count |
int | 消息数量 |
total_tokens |
int | Token 总数 |
total_cost |
float | 总成本 |
preview |
string | null | 工具调用摘要(如 "read → edit × 2") |
| 字段 | 类型 | 说明 |
|---|---|---|
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 | 创建时间 |
后端只提供 GoalTree(扁平列表 + parent_id),前端负责生成 DAG 视图。
普通 Goal(子目标):
explore Goal(分支):
从 target 节点的 stats 获取:
target.cumulative_statstarget.self_statsbranch.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
}
// 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)
}
}
| 状态码 | 说明 |
|---|---|
| 200 | 成功 |
| 404 | Trace 不存在 |
| 400 | 参数错误 |
| 500 | 服务器错误 |
{
"event": "error",
"message": "Too many missed events, please reload via REST API"
}