|
@@ -1,1476 +0,0 @@
|
|
|
-# Context 管理与执行计划
|
|
|
|
|
-
|
|
|
|
|
-> 本文档描述 Agent 的 Context 管理、执行计划和 Sub-Agent 机制。
|
|
|
|
|
-
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## 设计目标
|
|
|
|
|
-
|
|
|
|
|
-1. **自主长程执行**:Agent 能独立执行复杂任务,无需人工频繁干预
|
|
|
|
|
-2. **有效的 Context 管理**:长任务中保持关键信息,压缩次要细节
|
|
|
|
|
-3. **支持探索和回溯**:能尝试多种方案,失败时能有效回溯
|
|
|
|
|
-4. **统一的 Agent 模型**:主 Agent 和 Sub-Agent 使用相同的 Trace 结构
|
|
|
|
|
-5. **简单的工具接口**:LLM 只需理解少量简单工具,复杂逻辑由系统处理
|
|
|
|
|
-
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## 参考方案:OpenCode 的 Context 管理
|
|
|
|
|
-
|
|
|
|
|
-### 核心架构
|
|
|
|
|
-
|
|
|
|
|
-```
|
|
|
|
|
-┌─────────────────┐
|
|
|
|
|
-│ plan.md │ ← 文本格式的计划(TODO 列表)
|
|
|
|
|
-└─────────────────┘
|
|
|
|
|
- ↓
|
|
|
|
|
-┌─────────────────┐
|
|
|
|
|
-│ 线性 Message │ ← 对话历史
|
|
|
|
|
-│ List │
|
|
|
|
|
-└─────────────────┘
|
|
|
|
|
- ↓
|
|
|
|
|
-┌─────────────────┐
|
|
|
|
|
-│ Prune + Full │ ← 两阶段压缩
|
|
|
|
|
-│ Compaction │
|
|
|
|
|
-└─────────────────┘
|
|
|
|
|
- ↓
|
|
|
|
|
-┌─────────────────┐
|
|
|
|
|
-│ Sub-Agent │ ← 隔离大任务
|
|
|
|
|
-└─────────────────┘
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### 1. Message 管理
|
|
|
|
|
-
|
|
|
|
|
-**数据结构**:
|
|
|
|
|
-- User Message: 用户输入,包含 TextPart, FilePart, CompactionPart, SubtaskPart 等
|
|
|
|
|
-- Assistant Message: LLM 输出,包含 TextPart, ToolPart, ReasoningPart 等
|
|
|
|
|
-- 每个 Message 包含多个 Part,支持流式处理
|
|
|
|
|
-
|
|
|
|
|
-**存储**:
|
|
|
|
|
-```
|
|
|
|
|
-Storage Key:
|
|
|
|
|
-["message", sessionID, messageID] -> MessageV2.Info
|
|
|
|
|
-["part", messageID, partID] -> MessageV2.Part
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### 2. Context 压缩机制
|
|
|
|
|
-
|
|
|
|
|
-**两阶段压缩**:
|
|
|
|
|
-
|
|
|
|
|
-**阶段 1: Prune(清理旧工具输出)**
|
|
|
|
|
-```
|
|
|
|
|
-参数:
|
|
|
|
|
-- PRUNE_MINIMUM = 20,000 tokens(最少删除量)
|
|
|
|
|
-- PRUNE_PROTECT = 40,000 tokens(保护阈值)
|
|
|
|
|
-- PRUNE_PROTECTED_TOOLS = ["skill"](不删除的工具)
|
|
|
|
|
-
|
|
|
|
|
-流程:
|
|
|
|
|
-1. 从后向前遍历 messages
|
|
|
|
|
-2. 跳过最后 2 轮 turns(保护最近交互)
|
|
|
|
|
-3. 跳过已有 summary 标记的 assistant 消息
|
|
|
|
|
-4. 收集已完成工具调用的输出
|
|
|
|
|
-5. 当累计 > PRUNE_PROTECT 时,标记为已 compacted
|
|
|
|
|
-6. 当删除量 > PRUNE_MINIMUM 时,执行删除
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**阶段 2: Full Compaction(上下文总结)**
|
|
|
|
|
-```
|
|
|
|
|
-流程:
|
|
|
|
|
-1. 创建新的 assistant 消息(summary=true)
|
|
|
|
|
-2. 调用 "compaction" 专用 agent
|
|
|
|
|
-3. 提示词: "Provide a detailed prompt for continuing our conversation..."
|
|
|
|
|
-4. 返回 "continue" 时自动创建新的 user 消息继续
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### 3. Plan/Todo 机制
|
|
|
|
|
-
|
|
|
|
|
-**数据结构**:
|
|
|
|
|
-```typescript
|
|
|
|
|
-Todo.Info = {
|
|
|
|
|
- id: string
|
|
|
|
|
- content: string // 任务描述
|
|
|
|
|
- status: string // pending | in_progress | completed | cancelled
|
|
|
|
|
- priority: string // high | medium | low
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**存储**:文件系统(.opencode/plans/xxx.md)或 Storage
|
|
|
|
|
-
|
|
|
|
|
-### 4. Sub-Agent 机制
|
|
|
|
|
-
|
|
|
|
|
-**Agent Mode**:
|
|
|
|
|
-- `primary`: 主代理,执行工具
|
|
|
|
|
-- `subagent`: 子代理,独立 context,结果汇总回主会话
|
|
|
|
|
-
|
|
|
|
|
-**内置 Sub-Agents**:
|
|
|
|
|
-- `general`: 通用代理,可并行执行多个任务
|
|
|
|
|
-- `explore`: 代码探索专用,仅允许查询工具
|
|
|
|
|
-- `compaction`: 上下文总结专用
|
|
|
|
|
-
|
|
|
|
|
-**Subtask 执行**:
|
|
|
|
|
-1. 创建 SubtaskPart
|
|
|
|
|
-2. 子代理独立处理(独立 message list)
|
|
|
|
|
-3. 结果通过 "The following tool was executed by the user" 汇总
|
|
|
|
|
-
|
|
|
|
|
-### 5. 优缺点分析
|
|
|
|
|
-
|
|
|
|
|
-**优点**:
|
|
|
|
|
-- 简单成熟,经过大量验证
|
|
|
|
|
-- Plan 和执行分离,用户可直接编辑 plan.md
|
|
|
|
|
-- Sub-agent 有效隔离大任务的 context
|
|
|
|
|
-
|
|
|
|
|
-**局限**:
|
|
|
|
|
-- Plan 是纯文本,与执行记录无结构化关联
|
|
|
|
|
-- 压缩是"事后"的,等满了再压缩
|
|
|
|
|
-- 回溯能力有限,无法精确回到某个状态
|
|
|
|
|
-- 不支持并行探索-合并的模式
|
|
|
|
|
-
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## 我们的方案
|
|
|
|
|
-
|
|
|
|
|
-### 核心思路
|
|
|
|
|
-
|
|
|
|
|
-```
|
|
|
|
|
-基于 OpenCode 方案,但采用更统一的架构:
|
|
|
|
|
-1. 结构化 Plan(goal 工具)
|
|
|
|
|
-2. 统一的 Trace 模型(主 Agent 和 Sub-Agent 都是 Trace)
|
|
|
|
|
-3. 并行探索-合并(explore 工具,启动独立的 Sub-Traces)
|
|
|
|
|
-4. 精确回溯(abandon + context 压缩)
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### 架构
|
|
|
|
|
-
|
|
|
|
|
-```
|
|
|
|
|
-┌─────────────────────────────────────────────┐
|
|
|
|
|
-│ 每个 Agent = 一个 Trace │
|
|
|
|
|
-│ 主 Trace 和 Sub-Trace 使用相同的数据结构 │
|
|
|
|
|
-└─────────────────────────────────────────────┘
|
|
|
|
|
- │
|
|
|
|
|
- ┌────────────┴────────────┐
|
|
|
|
|
- ↓ ↓
|
|
|
|
|
-┌─────────────────┐ ┌─────────────────┐
|
|
|
|
|
-│ GoalTree │ │ Messages │
|
|
|
|
|
-│ (层级目标) │ │ (扁平列表) │
|
|
|
|
|
-│ goal 工具维护 │ │ goal_id 关联 │
|
|
|
|
|
-└─────────────────┘ └─────────────────┘
|
|
|
|
|
- │ │
|
|
|
|
|
- ↓ ↓
|
|
|
|
|
-┌─────────────────┐ ┌─────────────────┐
|
|
|
|
|
-│ 工具调用 │ │ Sub-Traces │
|
|
|
|
|
-│ explore/ │ │ 完全独立的 │
|
|
|
|
|
-│ delegate │──────▶ Trace 实例 │
|
|
|
|
|
-└─────────────────┘ └─────────────────┘
|
|
|
|
|
- │
|
|
|
|
|
- ↓
|
|
|
|
|
-┌─────────────────────────────────────────────┐
|
|
|
|
|
-│ DAG 可视化(派生视图) │
|
|
|
|
|
-│ 从 GoalTree + Messages 生成 │
|
|
|
|
|
-│ 节点 = 结果/里程碑,边 = 动作/执行过程 │
|
|
|
|
|
-│ 边可展开/折叠,Sub-Trace 作为折叠边显示 │
|
|
|
|
|
-└─────────────────────────────────────────────┘
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### 核心概念:统一的 Trace 模型
|
|
|
|
|
-
|
|
|
|
|
-**关键设计**:每个 Agent(主 Agent 或 Sub-Agent)都是一个完整的 Trace。
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-# 主 Agent
|
|
|
|
|
-Trace(trace_id="2f8d3a1c", mode="agent", task="实现用户认证")
|
|
|
|
|
-
|
|
|
|
|
-# Sub-Agent(并行探索)
|
|
|
|
|
-Trace(trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001", parent_trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", agent_type="explore", task="JWT 方案")
|
|
|
|
|
-Trace(trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002", parent_trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", agent_type="explore", task="Session 方案")
|
|
|
|
|
-
|
|
|
|
|
-# Sub-Agent(单线委托)
|
|
|
|
|
-Trace(trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220030-001", parent_trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", agent_type="delegate", task="实现具体功能")
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**优点**:
|
|
|
|
|
-- **概念统一**:不需要额外的 SubAgentContext/BranchContext 概念
|
|
|
|
|
-- **ID 简洁**:每个 Trace 内部独立编号(1, 2, 3),不需要前缀
|
|
|
|
|
-- **完全隔离**:每个 Trace 有独立的 GoalTree、Message List、LLM Context
|
|
|
|
|
-- **自然分布式**:每个 Trace 可以独立运行、迁移、存储
|
|
|
|
|
-- **层级清晰**:从 trace_id 可以直接解析出父子关系(`@` 前是父 ID)
|
|
|
|
|
-
|
|
|
|
|
-### 数据结构
|
|
|
|
|
-
|
|
|
|
|
-后端存储两类数据,可视化的 DAG 是派生视图:
|
|
|
|
|
-
|
|
|
|
|
-1. **GoalTree**:层级目标,注入 LLM
|
|
|
|
|
-2. **Messages**:执行记录,通过 `goal_id` 关联 Goal
|
|
|
|
|
-
|
|
|
|
|
-不存在独立的"边"数据结构,边在可视化时从 Messages 聚合生成。
|
|
|
|
|
-
|
|
|
|
|
-#### Trace
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-@dataclass
|
|
|
|
|
-class Trace:
|
|
|
|
|
- """
|
|
|
|
|
- 执行轨迹 - 一次完整的 Agent 运行
|
|
|
|
|
-
|
|
|
|
|
- 主 Agent 和 Sub-Agent 使用相同的 Trace 结构
|
|
|
|
|
- """
|
|
|
|
|
- trace_id: str # 主 Trace: UUID,Sub-Trace: parent@mode-timestamp-seq
|
|
|
|
|
- mode: Literal["call", "agent"]
|
|
|
|
|
- task: Optional[str] = None
|
|
|
|
|
-
|
|
|
|
|
- # 父子关系
|
|
|
|
|
- parent_trace_id: Optional[str] = None # 父 Trace ID
|
|
|
|
|
- parent_goal_id: Optional[str] = None # 哪个 Goal 启动的
|
|
|
|
|
-
|
|
|
|
|
- # 类型标记(语义)
|
|
|
|
|
- agent_type: Optional[str] = None # "main", "explore", "delegate", "compaction"
|
|
|
|
|
-
|
|
|
|
|
- # 权限和配置
|
|
|
|
|
- context: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
- # 可以包含:
|
|
|
|
|
- # - allowed_tools: ["read", "grep"] # 工具白名单
|
|
|
|
|
- # - denied_tools: ["bash"] # 工具黑名单
|
|
|
|
|
- # - max_turns: 10 # 最大对话轮数
|
|
|
|
|
- # - timeout: 300 # 超时(秒)
|
|
|
|
|
-
|
|
|
|
|
- status: Literal["running", "completed", "failed"] = "running"
|
|
|
|
|
-
|
|
|
|
|
- # 统计
|
|
|
|
|
- total_messages: int = 0
|
|
|
|
|
- total_tokens: int = 0
|
|
|
|
|
- total_cost: float = 0.0
|
|
|
|
|
-
|
|
|
|
|
- created_at: datetime
|
|
|
|
|
- completed_at: Optional[datetime] = None
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**实现**:`agent/execution/models.py:Trace`
|
|
|
|
|
-
|
|
|
|
|
-**Trace ID 命名规则**:
|
|
|
|
|
-
|
|
|
|
|
-```
|
|
|
|
|
-主 Trace(用户直接触发):
|
|
|
|
|
- {uuid}
|
|
|
|
|
- 例如: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d
|
|
|
|
|
-
|
|
|
|
|
-Sub-Trace(Agent 启动的子任务):
|
|
|
|
|
- {parent_id}@{mode}-{timestamp}-{seq}
|
|
|
|
|
- 例如: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001
|
|
|
|
|
- 例如: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220015-002
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**格式说明**:
|
|
|
|
|
-- `parent_id`:父 Trace 的**完整 UUID**(不截断,避免冲突)
|
|
|
|
|
-- `@`:父子关系分隔符
|
|
|
|
|
-- `mode`:运行模式(`explore`, `delegate`, `compaction` 等)
|
|
|
|
|
-- `timestamp`:创建时间戳(`YYYYMMDDHHmmss`)
|
|
|
|
|
-- `seq`:同一秒内的序号(`001`, `002`, ...)
|
|
|
|
|
-
|
|
|
|
|
-**优点**:
|
|
|
|
|
-1. **零碰撞风险**:使用完整 UUID,完全避免 ID 冲突
|
|
|
|
|
-2. **可精确追溯**:从 Sub-Trace ID 直接看到完整父 Trace ID
|
|
|
|
|
-3. **无需冲突检测**:实现简单,不依赖外部状态
|
|
|
|
|
-4. **信息完整**:一眼看出触发者、模式、时间
|
|
|
|
|
-5. **层级清晰**:`@` 分隔符明确表示父子关系
|
|
|
|
|
-
|
|
|
|
|
-**从 trace_id 解析父子关系**:
|
|
|
|
|
-```python
|
|
|
|
|
-def parse_parent_trace_id(trace_id: str) -> Optional[str]:
|
|
|
|
|
- """从 trace_id 解析出 parent_trace_id"""
|
|
|
|
|
- if '@' in trace_id:
|
|
|
|
|
- return trace_id.split('@')[0]
|
|
|
|
|
- return None
|
|
|
|
|
-
|
|
|
|
|
-# 示例
|
|
|
|
|
-parse_parent_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001")
|
|
|
|
|
-# → "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d"
|
|
|
|
|
-
|
|
|
|
|
-parse_parent_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d")
|
|
|
|
|
-# → None
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**生成 Sub-Trace ID**:
|
|
|
|
|
-```python
|
|
|
|
|
-from datetime import datetime
|
|
|
|
|
-from threading import Lock
|
|
|
|
|
-from typing import Dict
|
|
|
|
|
-
|
|
|
|
|
-# 全局计数器(线程安全)
|
|
|
|
|
-_seq_lock = Lock()
|
|
|
|
|
-_seq_counter: Dict[str, int] = {} # key: "{parent_id}@{mode}-{timestamp}"
|
|
|
|
|
-
|
|
|
|
|
-def generate_sub_trace_id(parent_id: str, mode: str) -> str:
|
|
|
|
|
- """
|
|
|
|
|
- 生成 Sub-Trace ID
|
|
|
|
|
-
|
|
|
|
|
- 格式: {parent_id}@{mode}-{timestamp}-{seq}
|
|
|
|
|
-
|
|
|
|
|
- Args:
|
|
|
|
|
- parent_id: 父 Trace ID(完整 UUID,不截断)
|
|
|
|
|
- mode: 运行模式(explore, delegate, compaction)
|
|
|
|
|
-
|
|
|
|
|
- Returns:
|
|
|
|
|
- Sub-Trace ID(完整格式,无碰撞风险)
|
|
|
|
|
- """
|
|
|
|
|
- # 直接使用完整 UUID,不截断
|
|
|
|
|
- timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
|
|
|
-
|
|
|
|
|
- # 生成序号(同一秒内递增)
|
|
|
|
|
- prefix = f"{parent_id}@{mode}-{timestamp}"
|
|
|
|
|
- with _seq_lock:
|
|
|
|
|
- seq = _seq_counter.get(prefix, 0) + 1
|
|
|
|
|
- _seq_counter[prefix] = seq
|
|
|
|
|
-
|
|
|
|
|
- return f"{prefix}-{seq:03d}"
|
|
|
|
|
-
|
|
|
|
|
-# 示例
|
|
|
|
|
-generate_sub_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", "explore")
|
|
|
|
|
-# → "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001"
|
|
|
|
|
-
|
|
|
|
|
-generate_sub_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", "delegate")
|
|
|
|
|
-# → "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220030-001"
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-#### Goal
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-@dataclass
|
|
|
|
|
-class GoalStats:
|
|
|
|
|
- message_count: int = 0 # 消息数量
|
|
|
|
|
- total_tokens: int = 0 # Token 总数
|
|
|
|
|
- total_cost: float = 0.0 # 总成本
|
|
|
|
|
- preview: Optional[str] = None # 工具调用摘要,如 "read_file → edit_file → bash"
|
|
|
|
|
-
|
|
|
|
|
-GoalStatus = Literal["pending", "in_progress", "completed", "abandoned"]
|
|
|
|
|
-GoalType = Literal["normal", "agent_call"] # agent_call: 启动了 Sub-Agent
|
|
|
|
|
-
|
|
|
|
|
-@dataclass
|
|
|
|
|
-class Goal:
|
|
|
|
|
- id: str # 内部唯一 ID,纯自增("1", "2", "3"...)
|
|
|
|
|
- parent_id: Optional[str] = None # 父 Goal ID(层级关系)
|
|
|
|
|
- type: GoalType = "normal" # Goal 类型
|
|
|
|
|
-
|
|
|
|
|
- description: str # 目标描述(做什么)
|
|
|
|
|
- reason: str # 创建理由(为什么做)
|
|
|
|
|
- status: GoalStatus # pending | in_progress | completed | abandoned
|
|
|
|
|
- summary: Optional[str] = None # 完成/放弃时的总结
|
|
|
|
|
-
|
|
|
|
|
- # agent_call 特有
|
|
|
|
|
- sub_trace_ids: Optional[List[str]] = None # 启动的 Sub-Trace IDs
|
|
|
|
|
- agent_call_mode: Optional[str] = None # "explore" | "delegate" | "sequential"
|
|
|
|
|
- sub_trace_metadata: Optional[Dict[str, Dict[str, Any]]] = None # Sub-Trace 元数据
|
|
|
|
|
-
|
|
|
|
|
- # 统计(后端维护,用于可视化边的数据)
|
|
|
|
|
- self_stats: GoalStats # 自身统计(仅直接关联的 messages)
|
|
|
|
|
- cumulative_stats: GoalStats # 累计统计(自身 + 所有后代)
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**实现**:`agent/goal/models.py:Goal`
|
|
|
|
|
-
|
|
|
|
|
-**ID 设计**:
|
|
|
|
|
-- **内部 ID**:每个 Trace 独立编号("1", "2", "3"),简单自增
|
|
|
|
|
-- **层级关系**:通过 `parent_id` 字段维护
|
|
|
|
|
-- **显示序号**:`to_prompt()` 时动态生成连续有意义的编号("1", "2", "2.1", "2.2"...)
|
|
|
|
|
-
|
|
|
|
|
-**sub_trace_metadata 字段**(`agent_call` 类型 Goal 专用):
|
|
|
|
|
-存储各 Sub-Trace 的关键信息,用于辅助决策和可视化:
|
|
|
|
|
-```python
|
|
|
|
|
-{
|
|
|
|
|
- "sub_trace_id_1": {
|
|
|
|
|
- "task": "JWT 方案", # Sub-Trace 任务描述
|
|
|
|
|
- "status": "completed", # Sub-Trace 状态
|
|
|
|
|
- "summary": "实现完成,使用 JWT token", # Sub-Trace 总结
|
|
|
|
|
- "last_message": { # 最后一条 assistant 消息
|
|
|
|
|
- "role": "assistant",
|
|
|
|
|
- "description": "生成 JWT token 并返回",
|
|
|
|
|
- "content": "...", # 截断至 500 字符
|
|
|
|
|
- "created_at": "2026-02-05T10:30:00"
|
|
|
|
|
- },
|
|
|
|
|
- "stats": { # 统计信息
|
|
|
|
|
- "message_count": 8,
|
|
|
|
|
- "total_tokens": 4000,
|
|
|
|
|
- "total_cost": 0.05
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- # ... 其他 Sub-Trace
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**用途**:
|
|
|
|
|
-- 帮助主 Agent 决策:基于各分支的最终输出,决定是否需要展开查看详细信息
|
|
|
|
|
-- 前端可视化:在折叠视图中显示关键信息,用户快速判断是否需要展开
|
|
|
|
|
-- 调试追踪:快速了解每个分支的执行结果
|
|
|
|
|
-
|
|
|
|
|
-**统计更新逻辑**:
|
|
|
|
|
-- 每次添加 Message 时,更新对应 Goal 的 `self_stats`,并沿祖先链向上更新所有祖先的 `cumulative_stats`
|
|
|
|
|
-- 可视化中,折叠边使用 target Goal 的 `cumulative_stats`,展开边使用 `self_stats`
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-@dataclass
|
|
|
|
|
-class GoalTree:
|
|
|
|
|
- mission: str # 总任务描述
|
|
|
|
|
- current_id: Optional[str] = None # 当前焦点(内部 ID)
|
|
|
|
|
- goals: List[Goal] # 扁平列表,通过 parent_id 构建层级
|
|
|
|
|
- _next_id: int = 1 # 内部 ID 计数器
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**实现**:`agent/goal/models.py:GoalTree`
|
|
|
|
|
-
|
|
|
|
|
-#### Message
|
|
|
|
|
-
|
|
|
|
|
-Message 对应 LLM API 的消息,加上元数据。每条 Message 通过 `goal_id` 关联所属 Goal。
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-@dataclass
|
|
|
|
|
-class Message:
|
|
|
|
|
- message_id: str
|
|
|
|
|
- trace_id: str # 所属 Trace ID
|
|
|
|
|
- role: Literal["assistant", "tool"] # 和 LLM API 一致
|
|
|
|
|
- sequence: int # 当前 Trace 内的顺序
|
|
|
|
|
- goal_id: Optional[str] = None # 关联的 Goal 内部 ID(None = 还没有创建 Goal)
|
|
|
|
|
- tool_call_id: Optional[str] # tool 消息关联对应的 tool_call
|
|
|
|
|
- content: Any # 消息内容(和 LLM API 格式一致)
|
|
|
|
|
- description: str # 消息描述(系统自动生成)
|
|
|
|
|
-
|
|
|
|
|
- # 元数据
|
|
|
|
|
- tokens: Optional[int] = None
|
|
|
|
|
- cost: Optional[float] = None
|
|
|
|
|
- duration_ms: Optional[int] = None
|
|
|
|
|
- created_at: datetime
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**description 字段**(系统自动生成):
|
|
|
|
|
-- `assistant` 消息:优先取 content 中的 text,若无 text 则生成 "tool call: XX, XX"
|
|
|
|
|
-- `tool` 消息:使用 tool name
|
|
|
|
|
-
|
|
|
|
|
-**goal_id 说明**:
|
|
|
|
|
-- 通常关联到某个 Goal 的内部 ID(如 "1", "2")
|
|
|
|
|
-- 可以为 None:在 Trace 开始、还没有创建任何 Goal 时
|
|
|
|
|
-- 前端通过虚拟 START 节点展示 goal_id=None 的 messages
|
|
|
|
|
-
|
|
|
|
|
-**实现**:`agent/execution/models.py:Message`
|
|
|
|
|
-
|
|
|
|
|
-**Message 类型说明**:
|
|
|
|
|
-- `role="assistant"`:模型的一次返回,可能同时包含文本和多个 tool_calls
|
|
|
|
|
-- `role="tool"`:一个工具的执行结果,通过 `tool_call_id` 关联对应的 tool_call
|
|
|
|
|
-
|
|
|
|
|
-**查询 Message**:
|
|
|
|
|
-```python
|
|
|
|
|
-# 查询主 Trace 的 Messages
|
|
|
|
|
-GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d/messages?goal_id=2
|
|
|
|
|
-
|
|
|
|
|
-# 查询 Sub-Trace 的 Messages
|
|
|
|
|
-GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001/messages?goal_id=1
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### 工具设计
|
|
|
|
|
-
|
|
|
|
|
-#### goal 工具:计划管理
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-@tool
|
|
|
|
|
-def goal(
|
|
|
|
|
- add: Optional[str] = None, # 添加目标(逗号分隔多个)
|
|
|
|
|
- reason: Optional[str] = None, # 创建理由(逗号分隔多个,与 add 一一对应)
|
|
|
|
|
- after: Optional[str] = None, # 在指定目标后面添加(同层级)
|
|
|
|
|
- under: Optional[str] = None, # 为指定目标添加子目标
|
|
|
|
|
- done: Optional[str] = None, # 完成当前目标,值为 summary
|
|
|
|
|
- abandon: Optional[str] = None, # 放弃当前目标,值为原因
|
|
|
|
|
- focus: Optional[str] = None, # 切换焦点到指定 ID
|
|
|
|
|
-) -> str:
|
|
|
|
|
- """管理执行计划。"""
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**实现**:`agent/goal/tool.py:goal_tool`
|
|
|
|
|
-
|
|
|
|
|
-**位置控制**:
|
|
|
|
|
-- 不指定 `after`/`under`:添加到当前 focus 的 goal 下作为子目标(无 focus 时添加到顶层)
|
|
|
|
|
-- `after="X"`:在目标 X 后面添加兄弟节点(同层级)
|
|
|
|
|
-- `under="X"`:为目标 X 添加子目标(如已有子目标,追加到最后)
|
|
|
|
|
-- `after` 和 `under` 互斥,不可同时指定
|
|
|
|
|
-
|
|
|
|
|
-**设计原则**:优先使用 `after` 明确指定位置,`under` 用于首次拆解或追加子任务。
|
|
|
|
|
-
|
|
|
|
|
-**示例**:
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-# 1. 默认行为:添加顶层目标(无 focus)
|
|
|
|
|
-goal(add="分析代码, 实现功能, 测试")
|
|
|
|
|
-# [ ] 1. 分析代码
|
|
|
|
|
-# [ ] 2. 实现功能
|
|
|
|
|
-# [ ] 3. 测试
|
|
|
|
|
-
|
|
|
|
|
-# 2. 拆解子任务
|
|
|
|
|
-goal(add="设计接口, 实现代码", under="2")
|
|
|
|
|
-# [ ] 1. 分析代码
|
|
|
|
|
-# [ ] 2. 实现功能
|
|
|
|
|
-# [ ] 2.1 设计接口
|
|
|
|
|
-# [ ] 2.2 实现代码
|
|
|
|
|
-# [ ] 3. 测试
|
|
|
|
|
-
|
|
|
|
|
-# 3. 追加同层级任务
|
|
|
|
|
-goal(add="编写文档", after="3")
|
|
|
|
|
-# [ ] 1. 分析代码
|
|
|
|
|
-# [ ] 2. 实现功能
|
|
|
|
|
-# [ ] 2.1 设计接口
|
|
|
|
|
-# [ ] 2.2 实现代码
|
|
|
|
|
-# [ ] 3. 测试
|
|
|
|
|
-# [ ] 4. 编写文档
|
|
|
|
|
-
|
|
|
|
|
-# 4. 追加子任务(目标2已有子任务)
|
|
|
|
|
-goal(add="编写单元测试", under="2")
|
|
|
|
|
-# [ ] 1. 分析代码
|
|
|
|
|
-# [ ] 2. 实现功能
|
|
|
|
|
-# [ ] 2.1 设计接口
|
|
|
|
|
-# [ ] 2.2 实现代码
|
|
|
|
|
-# [ ] 2.3 编写单元测试
|
|
|
|
|
-# [ ] 3. 测试
|
|
|
|
|
-# [ ] 4. 编写文档
|
|
|
|
|
-
|
|
|
|
|
-# 5. 使用 after 在子任务中间插入
|
|
|
|
|
-goal(add="代码审查", after="2.2")
|
|
|
|
|
-# [ ] 1. 分析代码
|
|
|
|
|
-# [ ] 2. 实现功能
|
|
|
|
|
-# [ ] 2.1 设计接口
|
|
|
|
|
-# [ ] 2.2 实现代码
|
|
|
|
|
-# [ ] 2.3 代码审查
|
|
|
|
|
-# [ ] 2.4 编写单元测试
|
|
|
|
|
-# [ ] 3. 测试
|
|
|
|
|
-# [ ] 4. 编写文档
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**状态流转**:
|
|
|
|
|
-```
|
|
|
|
|
-pending ──focus──→ in_progress ──done──→ completed
|
|
|
|
|
- │ ↓
|
|
|
|
|
- │ (压缩 context)
|
|
|
|
|
- │ (级联:若所有兄弟都 completed,父 goal 自动 completed)
|
|
|
|
|
- │
|
|
|
|
|
- abandon
|
|
|
|
|
- ↓
|
|
|
|
|
- abandoned
|
|
|
|
|
- ↓
|
|
|
|
|
- (压缩 context)
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-#### explore 工具:并行探索
|
|
|
|
|
-
|
|
|
|
|
-启动多个独立的 Sub-Traces 并行执行。
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-@tool
|
|
|
|
|
-def explore(
|
|
|
|
|
- branches: List[str], # 探索方向列表
|
|
|
|
|
- background: Optional[str] = None, # 背景概括(可选)
|
|
|
|
|
-) -> str:
|
|
|
|
|
- """
|
|
|
|
|
- 并行探索多个方向,汇总结果。
|
|
|
|
|
-
|
|
|
|
|
- 每个方向会启动一个独立的 Sub-Trace(agent_type="explore")。
|
|
|
|
|
-
|
|
|
|
|
- - background 有值:用它初始化各 Sub-Trace 的 context
|
|
|
|
|
- - background 为空:继承主 Trace 的 message list
|
|
|
|
|
-
|
|
|
|
|
- 返回:各分支的汇总结果
|
|
|
|
|
- """
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**内部实现**:
|
|
|
|
|
-```python
|
|
|
|
|
-async def explore_tool(branches: List[str], background: Optional[str] = None) -> str:
|
|
|
|
|
- current_trace_id = get_current_trace_id()
|
|
|
|
|
- current_goal_id = get_current_goal_id()
|
|
|
|
|
-
|
|
|
|
|
- # 1. 创建 agent_call Goal
|
|
|
|
|
- goal = Goal(
|
|
|
|
|
- id=next_id(),
|
|
|
|
|
- type="agent_call",
|
|
|
|
|
- description=f"并行探索 {len(branches)} 个方案",
|
|
|
|
|
- agent_call_mode="explore",
|
|
|
|
|
- sub_trace_ids=[],
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # 2. 为每个分支创建 Sub-Trace
|
|
|
|
|
- sub_traces = []
|
|
|
|
|
- for i, desc in enumerate(branches):
|
|
|
|
|
- suffix = chr(ord('A') + i) # A, B, C...
|
|
|
|
|
- sub_trace = Trace.create(
|
|
|
|
|
- trace_id=f"{current_trace_id}.{suffix}",
|
|
|
|
|
- parent_trace_id=current_trace_id,
|
|
|
|
|
- parent_goal_id=current_goal_id,
|
|
|
|
|
- agent_type="explore",
|
|
|
|
|
- task=desc,
|
|
|
|
|
- context=get_explore_context(), # 从配置获取权限设置
|
|
|
|
|
- )
|
|
|
|
|
- sub_traces.append(sub_trace)
|
|
|
|
|
- goal.sub_trace_ids.append(sub_trace.trace_id)
|
|
|
|
|
-
|
|
|
|
|
- # 3. 并行执行
|
|
|
|
|
- results = await asyncio.gather(
|
|
|
|
|
- *[run_agent(st, background) for st in sub_traces]
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # 4. 收集元数据并汇总
|
|
|
|
|
- sub_trace_metadata = {}
|
|
|
|
|
- summary_parts = []
|
|
|
|
|
-
|
|
|
|
|
- for sub_trace, result in zip(sub_traces, results):
|
|
|
|
|
- # 获取 Sub-Trace 最新状态
|
|
|
|
|
- updated_trace = await store.get_trace(sub_trace.trace_id)
|
|
|
|
|
-
|
|
|
|
|
- # 获取最后一条 assistant 消息
|
|
|
|
|
- messages = await store.get_messages(sub_trace.trace_id)
|
|
|
|
|
- last_message = None
|
|
|
|
|
- for msg in reversed(messages):
|
|
|
|
|
- if msg.role == "assistant":
|
|
|
|
|
- last_message = msg
|
|
|
|
|
- break
|
|
|
|
|
-
|
|
|
|
|
- # 构建元数据
|
|
|
|
|
- sub_trace_metadata[sub_trace.trace_id] = {
|
|
|
|
|
- "task": sub_trace.task,
|
|
|
|
|
- "status": updated_trace.status if updated_trace else "unknown",
|
|
|
|
|
- "summary": result.get("summary", "") if isinstance(result, dict) else "",
|
|
|
|
|
- "last_message": {
|
|
|
|
|
- "role": last_message.role,
|
|
|
|
|
- "description": last_message.description,
|
|
|
|
|
- "content": last_message.content[:500] if last_message.content else None,
|
|
|
|
|
- "created_at": last_message.created_at.isoformat()
|
|
|
|
|
- } if last_message else None,
|
|
|
|
|
- "stats": {
|
|
|
|
|
- "message_count": updated_trace.total_messages,
|
|
|
|
|
- "total_tokens": updated_trace.total_tokens,
|
|
|
|
|
- "total_cost": updated_trace.total_cost
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- # 组装摘要文本
|
|
|
|
|
- summary_parts.append(f"### {sub_trace.task}")
|
|
|
|
|
- summary_parts.append(result.get("summary", "执行完成"))
|
|
|
|
|
-
|
|
|
|
|
- # 5. 更新 Goal,包含元数据
|
|
|
|
|
- await store.update_goal(
|
|
|
|
|
- current_trace_id,
|
|
|
|
|
- current_goal_id,
|
|
|
|
|
- status="completed",
|
|
|
|
|
- summary=f"探索了 {len(branches)} 个方案",
|
|
|
|
|
- sub_trace_metadata=sub_trace_metadata # 保存元数据
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # 6. 返回文本摘要(给 LLM)
|
|
|
|
|
- return "\n".join(summary_parts)
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**权限配置**:
|
|
|
|
|
-```python
|
|
|
|
|
-# 示例:系统配置中定义 Sub-Trace 的默认权限
|
|
|
|
|
-# 可以根据 agent_type 或具体场景调整
|
|
|
|
|
-
|
|
|
|
|
-def get_explore_context() -> Dict[str, Any]:
|
|
|
|
|
- """获取 explore 模式的 context 配置"""
|
|
|
|
|
- return {
|
|
|
|
|
- # 工具权限:根据实际需求配置
|
|
|
|
|
- # 选项 1:受限权限(只读,快速探索)
|
|
|
|
|
- # "allowed_tools": ["read", "grep", "glob"],
|
|
|
|
|
-
|
|
|
|
|
- # 选项 2:完整权限(需要实际实现来评估)
|
|
|
|
|
- # "allowed_tools": None, # None = 所有工具
|
|
|
|
|
-
|
|
|
|
|
- # 步数限制
|
|
|
|
|
- "max_turns": 20,
|
|
|
|
|
- }
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-#### delegate 工具:单线委托
|
|
|
|
|
-
|
|
|
|
|
-将大任务委托给独立的 Sub-Trace 执行。
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-@tool
|
|
|
|
|
-def delegate(task: str) -> str:
|
|
|
|
|
- """
|
|
|
|
|
- 将大任务委托给独立的 sub-agent。
|
|
|
|
|
-
|
|
|
|
|
- 创建一个独立的 Sub-Trace(agent_type="delegate"),
|
|
|
|
|
- 有独立的 context,权限由配置决定。
|
|
|
|
|
-
|
|
|
|
|
- 返回:任务执行结果摘要
|
|
|
|
|
- """
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**内部实现**:
|
|
|
|
|
-```python
|
|
|
|
|
-async def delegate_tool(task: str) -> str:
|
|
|
|
|
- current_trace_id = get_current_trace_id()
|
|
|
|
|
- current_goal_id = get_current_goal_id()
|
|
|
|
|
-
|
|
|
|
|
- # 1. 创建 agent_call Goal
|
|
|
|
|
- goal = Goal(
|
|
|
|
|
- id=next_id(),
|
|
|
|
|
- type="agent_call",
|
|
|
|
|
- description=f"委托任务: {task}",
|
|
|
|
|
- agent_call_mode="delegate",
|
|
|
|
|
- sub_trace_ids=[],
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # 2. 创建 Sub-Trace
|
|
|
|
|
- suffix = f"task{next_task_num()}" # task1, task2...
|
|
|
|
|
- sub_trace = Trace.create(
|
|
|
|
|
- trace_id=f"{current_trace_id}.{suffix}",
|
|
|
|
|
- parent_trace_id=current_trace_id,
|
|
|
|
|
- parent_goal_id=current_goal_id,
|
|
|
|
|
- agent_type="delegate",
|
|
|
|
|
- task=task,
|
|
|
|
|
- context=get_delegate_context(), # 从配置获取权限设置
|
|
|
|
|
- )
|
|
|
|
|
- goal.sub_trace_ids.append(sub_trace.trace_id)
|
|
|
|
|
-
|
|
|
|
|
- # 3. 执行
|
|
|
|
|
- result = await run_agent(sub_trace)
|
|
|
|
|
-
|
|
|
|
|
- # 4. 返回摘要
|
|
|
|
|
- return result.summary
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**权限配置**:
|
|
|
|
|
-```python
|
|
|
|
|
-def get_delegate_context() -> Dict[str, Any]:
|
|
|
|
|
- """获取 delegate 模式的 context 配置"""
|
|
|
|
|
- return {
|
|
|
|
|
- # 工具权限:根据实际需求配置
|
|
|
|
|
- # 通常 delegate 用于完整任务,给完整权限
|
|
|
|
|
- "allowed_tools": None, # None = 所有工具
|
|
|
|
|
-
|
|
|
|
|
- # 或者也可以限制:
|
|
|
|
|
- # "allowed_tools": ["read", "write", "edit", "bash"],
|
|
|
|
|
- # "denied_tools": ["危险工具"],
|
|
|
|
|
-
|
|
|
|
|
- # 步数限制
|
|
|
|
|
- "max_turns": 50,
|
|
|
|
|
- }
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**注意**:
|
|
|
|
|
-- `explore` 和 `delegate` 的权限配置是独立的,可以根据需求调整
|
|
|
|
|
-- 不是工具本身决定权限,而是通过 `Trace.context` 配置
|
|
|
|
|
-- 典型配置:
|
|
|
|
|
- - `explore`:可能受限(快速探索)或完整(需要实现验证)
|
|
|
|
|
- - `delegate`:通常完整权限(执行完整任务)
|
|
|
|
|
-- 但这些都不是固定的,可以根据具体场景调整
|
|
|
|
|
-
|
|
|
|
|
-### Context 管理
|
|
|
|
|
-
|
|
|
|
|
-#### 1. Plan 注入
|
|
|
|
|
-
|
|
|
|
|
-每次 LLM 调用时,在 system prompt 末尾注入当前计划状态。注入时过滤掉 abandoned 目标,使用连续的显示序号。
|
|
|
|
|
-
|
|
|
|
|
-**展示策略**:
|
|
|
|
|
-- **完整展示**:所有顶层目标、当前 focus 目标的完整父链及其子树
|
|
|
|
|
-- **折叠其他**:非关键路径的子目标可折叠显示(显示节点数和状态)
|
|
|
|
|
-- **连续编号**:显示 ID 连续且有意义("1", "2", "2.1", "2.2")
|
|
|
|
|
-
|
|
|
|
|
-```markdown
|
|
|
|
|
-## Current Plan
|
|
|
|
|
-
|
|
|
|
|
-**Mission**: 实现用户认证功能
|
|
|
|
|
-**Current**: 2.2 实现登录接口
|
|
|
|
|
-
|
|
|
|
|
-**Progress**:
|
|
|
|
|
-[✓] 1. 分析代码
|
|
|
|
|
- → 用户模型在 models/user.py,使用 bcrypt 加密
|
|
|
|
|
-[→] 2. 实现功能
|
|
|
|
|
- [✓] 2.1 设计接口
|
|
|
|
|
- → API 设计文档完成,使用 REST 风格
|
|
|
|
|
- [→] 2.2 实现登录接口 ← current
|
|
|
|
|
- [ ] 2.3 实现注册接口
|
|
|
|
|
-[ ] 3. 测试
|
|
|
|
|
- (3 subtasks)
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**实现**:`agent/goal/models.py:GoalTree.to_prompt`
|
|
|
|
|
-
|
|
|
|
|
-**注意**:当前 focus 目标的所有父目标及其子目标都会完整展示,确保 LLM 理解当前上下文。这样在使用 `after` 或 `under` 参数时,LLM 可以准确引用目标 ID。
|
|
|
|
|
-
|
|
|
|
|
-#### 2. 完成时压缩
|
|
|
|
|
-
|
|
|
|
|
-当调用 `goal(done="...")` 时:
|
|
|
|
|
-1. 找到该 goal 关联的所有 messages(通过 goal_id)
|
|
|
|
|
-2. 将详细 messages 替换为一条 summary message
|
|
|
|
|
-3. 更新 goal 状态为 completed
|
|
|
|
|
-
|
|
|
|
|
-#### 3. 回溯(Abandon)
|
|
|
|
|
-
|
|
|
|
|
-两种模式:
|
|
|
|
|
-
|
|
|
|
|
-**模式 1:需要修改的计划还没有执行**
|
|
|
|
|
-
|
|
|
|
|
-直接修改计划并继续执行。Goal 状态为 pending 时,可以直接修改 description 或删除。
|
|
|
|
|
-
|
|
|
|
|
-**模式 2:需要修改的计划已经执行**
|
|
|
|
|
-
|
|
|
|
|
-1. 将原 Goal 标记为 `abandoned`(保留在 GoalTree 数据中,但 `to_prompt()` 不展示)
|
|
|
|
|
-2. 将废弃分支关联的 messages 做 summary
|
|
|
|
|
-3. 将 summary 累积到新分支的第一条消息中(供 LLM 参考历史失败原因)
|
|
|
|
|
-4. 创建新的 Goal 继续执行
|
|
|
|
|
-
|
|
|
|
|
-**Before 回溯**:
|
|
|
|
|
-```
|
|
|
|
|
-GoalTree 数据:
|
|
|
|
|
- [✓] 1. 分析代码 (内部ID: 1)
|
|
|
|
|
- [→] 2. 实现方案 A (内部ID: 2)
|
|
|
|
|
- [ ] 3. 测试 (内部ID: 3)
|
|
|
|
|
-
|
|
|
|
|
-Messages:
|
|
|
|
|
- [分析代码的 20 条 message...]
|
|
|
|
|
- [实现方案 A 的 30 条 message...]
|
|
|
|
|
- [测试失败的 message...]
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**After 回溯**:
|
|
|
|
|
-```
|
|
|
|
|
-GoalTree 数据(含废弃):
|
|
|
|
|
- [✓] 1. 分析代码 (内部ID: 1)
|
|
|
|
|
- [✗] 2. 实现方案 A (内部ID: 2, abandoned)
|
|
|
|
|
- [→] 3. 实现方案 B (内部ID: 4, 新建)
|
|
|
|
|
- [ ] 4. 测试 (内部ID: 3)
|
|
|
|
|
-
|
|
|
|
|
-to_prompt() 输出(给 LLM,连续编号):
|
|
|
|
|
- [✓] 1. 分析代码
|
|
|
|
|
- [→] 2. 实现方案 B ← current
|
|
|
|
|
- [ ] 3. 测试
|
|
|
|
|
-
|
|
|
|
|
-Messages:
|
|
|
|
|
- [分析代码的 20 条 message...]
|
|
|
|
|
- [Summary: "尝试方案 A,因依赖问题失败"] ← 原 messages 压缩为 1 条
|
|
|
|
|
- [方案 B 第一条消息,包含废弃分支的 summary] ← 供 LLM 参考
|
|
|
|
|
- [方案 B 的后续 message...]
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**实现**:`agent/goal/compaction.py`
|
|
|
|
|
-
|
|
|
|
|
-#### 4. 用户编辑计划
|
|
|
|
|
-
|
|
|
|
|
-用户可以通过编辑 Goal 内的 goal() 工具调用来修改执行计划。
|
|
|
|
|
-
|
|
|
|
|
-**核心思路**:利用现有的 GoalTree 结构和 Message sequence,无需额外追踪字段。
|
|
|
|
|
-
|
|
|
|
|
-**编辑流程**:
|
|
|
|
|
-1. 用户选择某个 Goal,查看其内所有 messages
|
|
|
|
|
-2. 找到 goal() 调用,编辑其参数
|
|
|
|
|
-3. 系统废弃该 message 之后的所有内容:
|
|
|
|
|
- - 废弃 sequence >= 该 message 的所有 messages
|
|
|
|
|
- - 根据 GoalTree 结构,废弃受影响的 Goals(当前 Goal 的子孙、后续同级 Goals)
|
|
|
|
|
-4. 从该 Goal 重新执行
|
|
|
|
|
-
|
|
|
|
|
-**废弃判断**:
|
|
|
|
|
-- 基于 Message sequence:废弃 seq >= 编辑点的所有 messages
|
|
|
|
|
-- 基于 GoalTree 结构:废弃当前 Goal 的所有子节点、以及后续创建的 Goals
|
|
|
|
|
-- 不需要追踪"哪个 message 创建了哪个 Goal",结构本身就能判断
|
|
|
|
|
-
|
|
|
|
|
-**UI 展示**:
|
|
|
|
|
-- Goal 详情显示其内所有 messages
|
|
|
|
|
-- 突出显示 goal() 调用(assistant message 中包含 tool_calls)
|
|
|
|
|
-- 提供"编辑并重新执行"按钮
|
|
|
|
|
-
|
|
|
|
|
-**API**:
|
|
|
|
|
-```
|
|
|
|
|
-GET /api/traces/{trace_id}/messages?goal_id=X
|
|
|
|
|
- # 返回 Goal X 的所有 messages
|
|
|
|
|
-
|
|
|
|
|
-PATCH /api/traces/{trace_id}/replay
|
|
|
|
|
-{
|
|
|
|
|
- "from_message_id": "msg_123",
|
|
|
|
|
- "modified_tool_call": {
|
|
|
|
|
- "tool_call_id": "call_abc",
|
|
|
|
|
- "new_arguments": {"add": "新任务列表", "under": "2"}
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**实现**:`agent/goal/replay.py`
|
|
|
|
|
-
|
|
|
|
|
-**注意**:这是最通用、最简单的编辑方式,完全基于现有数据结构。
|
|
|
|
|
-
|
|
|
|
|
-### 可视化
|
|
|
|
|
-
|
|
|
|
|
-#### DAG 模型
|
|
|
|
|
-
|
|
|
|
|
-可视化展示为 DAG(有向无环图),不是树。
|
|
|
|
|
-
|
|
|
|
|
-**核心概念**:
|
|
|
|
|
-- **节点** = Goal 完成后的结果/里程碑
|
|
|
|
|
-- **边** = 从一个结果到下一个结果的执行过程(动作/策略)
|
|
|
|
|
-- 每个节点对应一条入边,入边的数据从该 Goal 关联的 Messages 聚合
|
|
|
|
|
-
|
|
|
|
|
-**展开/折叠**:对边操作,对应目标的层级展开。
|
|
|
|
|
-
|
|
|
|
|
-```
|
|
|
|
|
-折叠视图(只看顶层 Goals):
|
|
|
|
|
-[START] ──→ [1:分析完成] ──→ [2:实现完成] ──→ [3:测试完成]
|
|
|
|
|
- 逻辑边
|
|
|
|
|
-
|
|
|
|
|
-展开 [1]→[2] 的边(显示 Goal 2 的子目标):
|
|
|
|
|
-[START] ──→ [1:分析完成] ──→ [2.1:设计完成] ──→ [2.2:代码完成] ──→ [3:测试完成]
|
|
|
|
|
- 执行边 执行边
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-展开时,父节点 [2] 被子节点 [2.1], [2.2] **替代**。
|
|
|
|
|
-折叠时,子节点合并回父节点 [2]。
|
|
|
|
|
-
|
|
|
|
|
-嵌套展开:如果 2.1 也有子目标,可以继续展开 [1]→[2.1] 的边。
|
|
|
|
|
-
|
|
|
|
|
-**废弃分支**:在可视化中以灰色样式展示废弃分支。
|
|
|
|
|
-
|
|
|
|
|
-```
|
|
|
|
|
-[1:分析完成] ──→ [2:方案A(废弃)] ──→ ... ← 灰色
|
|
|
|
|
- ──→ [4:方案B] ──→ [3:测试] ← 正常
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-#### API
|
|
|
|
|
-
|
|
|
|
|
-后端提供 GoalTree 数据,前端负责生成 DAG 视图。
|
|
|
|
|
-
|
|
|
|
|
-**REST 端点**:
|
|
|
|
|
-```
|
|
|
|
|
-GET /api/traces/{trace_id} # 获取 Trace + GoalTree
|
|
|
|
|
-GET /api/traces/{trace_id}/messages?goal_id=2.1 # 获取 Messages(边详情)
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**响应**(GoalTree 部分):
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "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",
|
|
|
|
|
- "self_stats": {"message_count": 0, ...},
|
|
|
|
|
- "cumulative_stats": {"message_count": 11, "total_tokens": 5700, ...}
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- "id": "3",
|
|
|
|
|
- "parent_id": "2",
|
|
|
|
|
- "branch_id": null,
|
|
|
|
|
- "type": "normal",
|
|
|
|
|
- "description": "设计接口",
|
|
|
|
|
- "status": "completed",
|
|
|
|
|
- "self_stats": {...}
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- "id": "4",
|
|
|
|
|
- "parent_id": "2",
|
|
|
|
|
- "branch_id": null,
|
|
|
|
|
- "type": "normal",
|
|
|
|
|
- "description": "实现代码",
|
|
|
|
|
- "status": "in_progress",
|
|
|
|
|
- "self_stats": {...}
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- "id": "5",
|
|
|
|
|
- "parent_id": null,
|
|
|
|
|
- "branch_id": null,
|
|
|
|
|
- "type": "normal",
|
|
|
|
|
- "description": "测试",
|
|
|
|
|
- "status": "pending",
|
|
|
|
|
- ...
|
|
|
|
|
- }
|
|
|
|
|
- ]
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**DAG 生成逻辑**(前端实现):
|
|
|
|
|
-1. 根据用户展开状态,确定可见 Goal 序列
|
|
|
|
|
-2. 相邻 Goal 之间形成边
|
|
|
|
|
-3. 边的统计数据从 target Goal 的 stats 获取(折叠用 `cumulative_stats`,展开用 `self_stats`)
|
|
|
|
|
-4. 边的详细内容通过 Messages API 查询
|
|
|
|
|
-
|
|
|
|
|
-**实现**:见 [frontend/API.md](../frontend/API.md)
|
|
|
|
|
-
|
|
|
|
|
-#### WebSocket 实时推送
|
|
|
|
|
-
|
|
|
|
|
-```
|
|
|
|
|
-ws://localhost:8000/api/traces/{trace_id}/watch?since_event_id=0
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**事件类型**:
|
|
|
|
|
-
|
|
|
|
|
-| 事件 | 触发时机 | payload |
|
|
|
|
|
-|------|---------|---------|
|
|
|
|
|
-| `connected` | WebSocket 连接成功 | trace_id, current_event_id, goal_tree(完整 GoalTree) |
|
|
|
|
|
-| `goal_added` | 新增 Goal | goal 完整数据(含 self_stats, cumulative_stats) |
|
|
|
|
|
-| `goal_updated` | Goal 状态变化(含级联完成) | goal_id, updates(含 cumulative_stats),affected_goals |
|
|
|
|
|
-| `message_added` | 新 Message | message 数据(含 goal_id),affected_goals |
|
|
|
|
|
-| `trace_completed` | 执行完成 | 统计信息 |
|
|
|
|
|
-
|
|
|
|
|
-**事件详情**:
|
|
|
|
|
-
|
|
|
|
|
-**`connected`** - 连接时推送完整 GoalTree,前端据此初始化 DAG:
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "event": "connected",
|
|
|
|
|
- "trace_id": "xxx",
|
|
|
|
|
- "current_event_id": 42,
|
|
|
|
|
- "goal_tree": { "mission": "...", "goals": [...] }
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**`message_added`** - 新 Message 时,后端更新统计并推送受影响的 Goals:
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "event": "message_added",
|
|
|
|
|
- "message": { "message_id": "...", "role": "assistant", "goal_id": "2.1", "..." : "..." },
|
|
|
|
|
- "affected_goals": [
|
|
|
|
|
- { "goal_id": "2.1", "self_stats": {"message_count": 4, "total_tokens": 2000, "total_cost": 0.03, "preview": "read → edit × 2"}, "cumulative_stats": {"message_count": 4, "total_tokens": 2000, "total_cost": 0.03, "preview": "read → edit × 2"} },
|
|
|
|
|
- { "goal_id": "2", "cumulative_stats": {"message_count": 12, "total_tokens": 6800, "total_cost": 0.08, "preview": "glob → read → edit × 5 → bash"} }
|
|
|
|
|
- ]
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-`affected_goals` 包含该 Message 直接关联的 Goal(更新 self_stats + cumulative_stats)以及所有祖先 Goal(仅更新 cumulative_stats)。前端根据当前展开状态选择使用哪个 stats 渲染边。
|
|
|
|
|
-
|
|
|
|
|
-**`goal_updated`** - Goal 状态变化时推送,包含级联完成场景:
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "event": "goal_updated",
|
|
|
|
|
- "goal_id": "2.1",
|
|
|
|
|
- "updates": { "status": "completed", "summary": "接口设计完成" },
|
|
|
|
|
- "affected_goals": [
|
|
|
|
|
- { "goal_id": "2.1", "cumulative_stats": {"message_count": 4, "total_tokens": 2000, "total_cost": 0.03, "preview": "read → edit × 2"} },
|
|
|
|
|
- { "goal_id": "2", "status": "completed", "summary": "功能实现完成", "cumulative_stats": {"message_count": 12, "total_tokens": 6800, "total_cost": 0.08, "preview": "..."} }
|
|
|
|
|
- ]
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-当所有子 Goal 完成时,后端自动级联完成父 Goal,并在 `affected_goals` 中包含所有状态变更的祖先。前端收到后直接更新对应节点,无需自行计算。
|
|
|
|
|
-
|
|
|
|
|
-**`goal_added`** - 新增 Goal,携带完整 Goal 数据:
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "event": "goal_added",
|
|
|
|
|
- "goal": { "id": "2.1", "description": "设计接口", "reason": "需要先定义 API", "status": "pending", "self_stats": {}, "cumulative_stats": {} },
|
|
|
|
|
- "parent_id": "2"
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**实现**:`agent/execution/websocket.py`
|
|
|
|
|
-
|
|
|
|
|
-### 存储结构
|
|
|
|
|
-
|
|
|
|
|
-```
|
|
|
|
|
-.trace/
|
|
|
|
|
-├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d/ # 主 Trace
|
|
|
|
|
-│ ├── meta.json # Trace 元数据
|
|
|
|
|
-│ ├── goal.json # GoalTree
|
|
|
|
|
-│ ├── messages/ # Messages
|
|
|
|
|
-│ │ ├── {message_id}.json
|
|
|
|
|
-│ │ └── ...
|
|
|
|
|
-│ └── events.jsonl # 事件流
|
|
|
|
|
-│
|
|
|
|
|
-├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001/ # Sub-Trace A(并行探索)
|
|
|
|
|
-│ ├── meta.json # parent_trace_id: "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d"
|
|
|
|
|
-│ ├── goal.json # 独立的 GoalTree
|
|
|
|
|
-│ ├── messages/
|
|
|
|
|
-│ └── events.jsonl
|
|
|
|
|
-│
|
|
|
|
|
-├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002/ # Sub-Trace B(并行探索)
|
|
|
|
|
-│ └── ...
|
|
|
|
|
-│
|
|
|
|
|
-└── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220030-001/ # Sub-Trace task1(单线委托)
|
|
|
|
|
- └── ...
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**关键点**:
|
|
|
|
|
-- 每个 Trace(主/子)都是完全独立的目录
|
|
|
|
|
-- 从 trace_id 可以直接找到对应目录
|
|
|
|
|
-- 通过 `parent_trace_id` 可以追溯父子关系
|
|
|
|
|
-
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## 并行探索设计(explore 工具)
|
|
|
|
|
-
|
|
|
|
|
-### 场景
|
|
|
|
|
-
|
|
|
|
|
-```
|
|
|
|
|
-主 Trace (2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d):
|
|
|
|
|
- [1] 分析问题
|
|
|
|
|
- [2] 并行探索认证方案 (type=agent_call, mode=explore)
|
|
|
|
|
- → 启动 Sub-Traces(完整 ID)
|
|
|
|
|
- [3] 完善实现
|
|
|
|
|
-
|
|
|
|
|
-Sub-Trace A (2f8d3a1c...@explore-20260204220012-001):
|
|
|
|
|
- [1] JWT 设计
|
|
|
|
|
- [2] JWT 实现
|
|
|
|
|
- → 返回摘要:"JWT 方案实现完成"
|
|
|
|
|
-
|
|
|
|
|
-Sub-Trace B (2f8d3a1c...@explore-20260204220012-002):
|
|
|
|
|
- [1] Session 设计
|
|
|
|
|
- [2] Session 实现
|
|
|
|
|
- [3] Session 测试
|
|
|
|
|
- → 返回摘要:"Session 方案实现完成"
|
|
|
|
|
-
|
|
|
|
|
-explore 工具返回:
|
|
|
|
|
- 汇总两个分支的结果,主 Agent 继续决策
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**核心原则**:
|
|
|
|
|
-- 每个分支是独立的 Trace,有自己的 GoalTree 和 Message List
|
|
|
|
|
-- 分支内的 Goal ID 简单编号("1", "2", "3"),独立于主 Trace
|
|
|
|
|
-- 主 Trace 的 Goal 通过 `sub_trace_ids` 字段关联分支
|
|
|
|
|
-- 分支完全独立存储,可以并行运行
|
|
|
|
|
-- explore 工具自动汇总各分支 summary
|
|
|
|
|
-
|
|
|
|
|
-### 数据结构
|
|
|
|
|
-
|
|
|
|
|
-**主 Trace 的 GoalTree**:
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-# Trace: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d
|
|
|
|
|
-goals = [
|
|
|
|
|
- Goal(id="1", type="normal", description="分析问题"),
|
|
|
|
|
- Goal(
|
|
|
|
|
- id="2",
|
|
|
|
|
- type="agent_call",
|
|
|
|
|
- description="并行探索认证方案",
|
|
|
|
|
- agent_call_mode="explore",
|
|
|
|
|
- sub_trace_ids=[
|
|
|
|
|
- "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001",
|
|
|
|
|
- "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002"
|
|
|
|
|
- ],
|
|
|
|
|
- ),
|
|
|
|
|
- Goal(id="3", type="normal", description="完善实现"),
|
|
|
|
|
-]
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**Sub-Trace A 的 GoalTree**(独立编号):
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-# Trace: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001
|
|
|
|
|
-goals = [
|
|
|
|
|
- Goal(id="1", type="normal", description="JWT 设计"),
|
|
|
|
|
- Goal(id="2", type="normal", description="JWT 实现"),
|
|
|
|
|
-]
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**Sub-Trace B 的 GoalTree**(独立编号):
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-# Trace: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002
|
|
|
|
|
-goals = [
|
|
|
|
|
- Goal(id="1", type="normal", description="Session 设计"),
|
|
|
|
|
- Goal(id="2", type="normal", description="Session 实现"),
|
|
|
|
|
- Goal(id="3", type="normal", description="Session 测试"),
|
|
|
|
|
-]
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### 存储结构
|
|
|
|
|
-
|
|
|
|
|
-```
|
|
|
|
|
-.trace/
|
|
|
|
|
-├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d/ # 主 Trace
|
|
|
|
|
-│ ├── meta.json
|
|
|
|
|
-│ ├── goal.json
|
|
|
|
|
-│ └── messages/
|
|
|
|
|
-│
|
|
|
|
|
-├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001/ # Sub-Trace A
|
|
|
|
|
-│ ├── meta.json # parent_trace_id: "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d"
|
|
|
|
|
-│ ├── goal.json # 独立的 GoalTree
|
|
|
|
|
-│ └── messages/
|
|
|
|
|
-│
|
|
|
|
|
-└── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002/ # Sub-Trace B
|
|
|
|
|
- └── ...
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### DAG 可视化
|
|
|
|
|
-
|
|
|
|
|
-**折叠视图**(Sub-Trace 作为单个节点):
|
|
|
|
|
-```
|
|
|
|
|
-[1:分析] ──→ [2:并行探索] ──→ [3:完善]
|
|
|
|
|
- │
|
|
|
|
|
- (启动2个Sub-Trace)
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**展开分支视图**(显示并行路径):
|
|
|
|
|
-```
|
|
|
|
|
- ┌──→ [A:JWT方案] ────┐
|
|
|
|
|
-[1:分析] ──→ [2] ─┤ ├──→ [3:完善]
|
|
|
|
|
- └──→ [B:Session方案] ┘
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**继续展开分支 A 内部**(加载 Sub-Trace 的 GoalTree):
|
|
|
|
|
-```
|
|
|
|
|
- ┌──→ [A.1:JWT设计] → [A.2:JWT实现] ──┐
|
|
|
|
|
-[1:分析] ──→ [2] ─┤ ├──→ [3:完善]
|
|
|
|
|
- └──→ [B:Session方案] ────────────────┘
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**注意**:
|
|
|
|
|
-- `[A:JWT方案]` 是折叠视图,代表整个 Sub-Trace
|
|
|
|
|
-- `[A.1]`, `[A.2]` 是展开后显示 Sub-Trace 内部的 Goals
|
|
|
|
|
-- 前端显示为 "A.1",但后端查询使用完整 trace_id:
|
|
|
|
|
- `GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001/messages?goal_id=1`
|
|
|
|
|
-
|
|
|
|
|
-### 前端 API
|
|
|
|
|
-
|
|
|
|
|
-**REST**:返回主 Trace 的 GoalTree + Sub-Trace 列表(元数据)。
|
|
|
|
|
-
|
|
|
|
|
-```http
|
|
|
|
|
-GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-响应:
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d",
|
|
|
|
|
- "status": "running",
|
|
|
|
|
- "goal_tree": {
|
|
|
|
|
- "goals": [
|
|
|
|
|
- {"id": "1", "type": "normal", "description": "分析问题"},
|
|
|
|
|
- {
|
|
|
|
|
- "id": "2",
|
|
|
|
|
- "type": "agent_call",
|
|
|
|
|
- "description": "并行探索认证方案",
|
|
|
|
|
- "agent_call_mode": "explore",
|
|
|
|
|
- "sub_trace_ids": [
|
|
|
|
|
- "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001",
|
|
|
|
|
- "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002"
|
|
|
|
|
- ],
|
|
|
|
|
- "sub_trace_metadata": {
|
|
|
|
|
- "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001": {
|
|
|
|
|
- "task": "JWT 方案",
|
|
|
|
|
- "status": "completed",
|
|
|
|
|
- "summary": "实现完成,使用 JWT token",
|
|
|
|
|
- "last_message": {
|
|
|
|
|
- "role": "assistant",
|
|
|
|
|
- "description": "生成 JWT token 并返回",
|
|
|
|
|
- "content": "JWT 方案实现完成。使用 jsonwebtoken 库生成 token,包含 user_id 和过期时间...",
|
|
|
|
|
- "created_at": "2026-02-05T10:30:00"
|
|
|
|
|
- },
|
|
|
|
|
- "stats": {
|
|
|
|
|
- "message_count": 8,
|
|
|
|
|
- "total_tokens": 4000,
|
|
|
|
|
- "total_cost": 0.05
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002": {
|
|
|
|
|
- "task": "Session 方案",
|
|
|
|
|
- "status": "completed",
|
|
|
|
|
- "summary": "实现完成,使用 Redis 存储 session",
|
|
|
|
|
- "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
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- {"id": "3", "type": "normal", "description": "完善实现"}
|
|
|
|
|
- ]
|
|
|
|
|
- },
|
|
|
|
|
- "sub_traces": {
|
|
|
|
|
- "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001": {
|
|
|
|
|
- "trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001",
|
|
|
|
|
- "parent_trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d",
|
|
|
|
|
- "parent_goal_id": "2",
|
|
|
|
|
- "agent_type": "explore",
|
|
|
|
|
- "task": "JWT 方案",
|
|
|
|
|
- "status": "completed",
|
|
|
|
|
- "total_messages": 8,
|
|
|
|
|
- "total_tokens": 4000,
|
|
|
|
|
- "total_cost": 0.05
|
|
|
|
|
- },
|
|
|
|
|
- "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002": {
|
|
|
|
|
- "trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002",
|
|
|
|
|
- "agent_type": "explore",
|
|
|
|
|
- "task": "Session 方案",
|
|
|
|
|
- "status": "completed",
|
|
|
|
|
- ...
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**按需加载 Sub-Trace 详情**:
|
|
|
|
|
-
|
|
|
|
|
-```http
|
|
|
|
|
-GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-响应:
|
|
|
|
|
-```json
|
|
|
|
|
-{
|
|
|
|
|
- "trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001",
|
|
|
|
|
- "parent_trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d",
|
|
|
|
|
- "parent_goal_id": "2",
|
|
|
|
|
- "agent_type": "explore",
|
|
|
|
|
- "task": "JWT 方案",
|
|
|
|
|
- "status": "completed",
|
|
|
|
|
- "goal_tree": {
|
|
|
|
|
- "goals": [
|
|
|
|
|
- {"id": "1", "description": "JWT 设计", ...},
|
|
|
|
|
- {"id": "2", "description": "JWT 实现", ...}
|
|
|
|
|
- ]
|
|
|
|
|
- },
|
|
|
|
|
- "total_tokens": 4000,
|
|
|
|
|
- "total_cost": 0.05
|
|
|
|
|
-}
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**WebSocket 事件**:
|
|
|
|
|
-
|
|
|
|
|
-| 事件 | 触发时机 | payload |
|
|
|
|
|
-|------|---------|---------|
|
|
|
|
|
-| `sub_trace_started` | Sub-Trace 开始 | trace_id, parent_trace_id, parent_goal_id, agent_type |
|
|
|
|
|
-| `goal_added` | 任何 Trace 新增 Goal | trace_id, goal |
|
|
|
|
|
-| `message_added` | 任何 Trace 新 Message | trace_id, message, affected_goals |
|
|
|
|
|
-| `sub_trace_completed` | Sub-Trace 完成 | trace_id, summary, stats |
|
|
|
|
|
-| `trace_completed` | 主 Trace 完成 | trace_id, stats |
|
|
|
|
|
-
|
|
|
|
|
-### explore 工具执行流程
|
|
|
|
|
-
|
|
|
|
|
-详细流程见前面"工具设计"部分,这里展示关键步骤:
|
|
|
|
|
-
|
|
|
|
|
-```python
|
|
|
|
|
-async def explore_tool(branches: List[str]) -> str:
|
|
|
|
|
- current_trace_id = "abc123"
|
|
|
|
|
- current_goal_id = "2"
|
|
|
|
|
-
|
|
|
|
|
- # 1. 创建 agent_call Goal
|
|
|
|
|
- goal = Goal(
|
|
|
|
|
- id="2",
|
|
|
|
|
- type="agent_call",
|
|
|
|
|
- description=f"并行探索 {len(branches)} 个方案",
|
|
|
|
|
- agent_call_mode="explore",
|
|
|
|
|
- sub_trace_ids=[],
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # 2. 创建多个 Sub-Traces
|
|
|
|
|
- sub_traces = []
|
|
|
|
|
- for i, desc in enumerate(branches):
|
|
|
|
|
- sub_trace = Trace.create(
|
|
|
|
|
- trace_id=generate_sub_trace_id(current_trace_id, "explore"),
|
|
|
|
|
- parent_trace_id=current_trace_id,
|
|
|
|
|
- parent_goal_id=current_goal_id,
|
|
|
|
|
- agent_type="explore",
|
|
|
|
|
- task=desc,
|
|
|
|
|
- context={"allowed_tools": ["read", "grep", "glob"]},
|
|
|
|
|
- )
|
|
|
|
|
- sub_traces.append(sub_trace)
|
|
|
|
|
- goal.sub_trace_ids.append(sub_trace.trace_id)
|
|
|
|
|
-
|
|
|
|
|
- # 3. 并行执行
|
|
|
|
|
- results = await asyncio.gather(
|
|
|
|
|
- *[run_agent(st) for st in sub_traces]
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # 4. 汇总返回
|
|
|
|
|
- return format_results(results)
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-**汇总结果示例**:
|
|
|
|
|
-```markdown
|
|
|
|
|
-## 探索结果
|
|
|
|
|
-
|
|
|
|
|
-### 方案 A: JWT 方案
|
|
|
|
|
-实现完成。优点:无状态,易扩展。缺点:token 较大,无法主动失效。
|
|
|
|
|
-
|
|
|
|
|
-### 方案 B: Session 方案
|
|
|
|
|
-实现完成。优点:token 小,可主动失效。缺点:需要 Redis 存储。
|
|
|
|
|
-
|
|
|
|
|
----
|
|
|
|
|
-两种方案都已实现,请选择一种继续。
|
|
|
|
|
-```
|
|
|
|
|
-
|
|
|
|
|
-### Context 压缩
|
|
|
|
|
-
|
|
|
|
|
-Sub-Trace 完成后的压缩策略:
|
|
|
|
|
-
|
|
|
|
|
-1. **Sub-Trace 完成时**:Sub-Trace 的详细 context 压缩为 summary(存储在 Trace.summary)
|
|
|
|
|
-2. **explore 完成后**:所有 Sub-Traces 的 summary 汇总到主 Trace 的 tool result
|
|
|
|
|
-3. **主 Trace context**:explore 工具调用被压缩为一条包含汇总结果的 tool message
|
|
|
|
|
-
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## 与 OpenCode 方案的对比
|
|
|
|
|
-
|
|
|
|
|
-| 方面 | OpenCode | 我们的方案 |
|
|
|
|
|
-|------|----------|-----------|
|
|
|
|
|
-| Plan 格式 | 纯文本 (plan.md) | 结构化 (GoalTree JSON) |
|
|
|
|
|
-| Plan 与执行关联 | 无 | 通过 goal_id 关联 |
|
|
|
|
|
-| 执行记录 | Message List | Message List(加 goal_id 元数据) |
|
|
|
|
|
-| Sub-Agent 模型 | SubagentPart(嵌入式) | 独立 Trace(统一模型) |
|
|
|
|
|
-| Sub-Agent ID | 复杂(需要前缀) | 简单(每个 Trace 独立编号) |
|
|
|
|
|
-| 压缩时机 | 事后(context 满时) | 增量(goal 完成/放弃时) |
|
|
|
|
|
-| 并行探索 | Sub-agent(手动管理) | explore 工具(自动汇总) |
|
|
|
|
|
-| 权限控制 | 在 Sub-Agent 类型上 | 在 Trace.context 上(更灵活) |
|
|
|
|
|
-| 回溯能力 | 有限 | 精确(基于 goal 压缩 + 废弃分支 summary) |
|
|
|
|
|
-| 可视化 | 无 | DAG(边可展开/折叠) |
|
|
|
|
|
-| 分布式支持 | 困难 | 自然支持(每个 Trace 独立) |
|
|
|
|
|
-
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## 实现位置
|
|
|
|
|
-
|
|
|
|
|
-| 功能 | 文件路径 | 状态 |
|
|
|
|
|
-|------|---------|------|
|
|
|
|
|
-| Trace 数据模型 | `agent/execution/models.py` | 待调整(增加父子关系、移除 branch_id) |
|
|
|
|
|
-| Goal 数据模型 | `agent/goal/models.py` | 待调整(移除 branch_id、BranchContext) |
|
|
|
|
|
-| Trace ID 生成 | `agent/execution/trace_id.py` | 待实现(generate_sub_trace_id) |
|
|
|
|
|
-| goal 工具 | `agent/goal/tool.py` | 待调整(新增 after/under 参数) |
|
|
|
|
|
-| explore 工具 | `agent/goal/explore.py` | 待实现 |
|
|
|
|
|
-| delegate 工具 | `agent/goal/delegate.py` | 待实现 |
|
|
|
|
|
-| Context 压缩 | `agent/goal/compaction.py` | 待调整 |
|
|
|
|
|
-| TraceStore 协议 | `agent/execution/protocols.py` | 待调整(移除 branch 相关接口) |
|
|
|
|
|
-| FileSystem Store | `agent/execution/fs_store.py` | 待调整(移除 branches/ 目录) |
|
|
|
|
|
-| DAG 可视化 API | `agent/execution/api.py` | 待调整(支持 Sub-Trace) |
|
|
|
|
|
-| WebSocket 推送 | `agent/execution/websocket.py` | 待调整(统一事件格式) |
|
|
|
|
|
-| Plan 注入 | `agent/core/runner.py` | 待调整 |
|
|
|
|
|
-
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## 渐进式实现计划
|
|
|
|
|
-
|
|
|
|
|
-### Phase 1: 基础 goal 工具
|
|
|
|
|
-- GoalTree 数据结构(含 ID 映射)
|
|
|
|
|
-- goal 工具(add, after, under, done, focus)
|
|
|
|
|
-- 位置控制逻辑:after(同层级)、under(子任务)
|
|
|
|
|
-- Plan 注入到 system prompt(含显示序号生成、完整展示策略)
|
|
|
|
|
-- Message 模型(替代 Step)
|
|
|
|
|
-
|
|
|
|
|
-### Phase 2: 回溯支持
|
|
|
|
|
-- abandon 操作(两种模式)
|
|
|
|
|
-- 废弃分支 summary 累积到新分支
|
|
|
|
|
-- 基于 goal 的 context 压缩
|
|
|
|
|
-
|
|
|
|
|
-### Phase 3: 可视化
|
|
|
|
|
-- DAG 视图 API
|
|
|
|
|
-- WebSocket goal/message 事件
|
|
|
|
|
-- 展开/折叠逻辑
|
|
|
|
|
-
|
|
|
|
|
-### Phase 4: 并行探索
|
|
|
|
|
-- explore 工具
|
|
|
|
|
-- 独立 message list 管理
|
|
|
|
|
-- 结果汇总机制
|
|
|