|
|
@@ -356,6 +356,7 @@ class Goal:
|
|
|
# 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)
|
|
|
@@ -369,6 +370,35 @@ class Goal:
|
|
|
- **层级关系**:通过 `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`
|
|
|
@@ -395,7 +425,7 @@ class Message:
|
|
|
trace_id: str # 所属 Trace ID
|
|
|
role: Literal["assistant", "tool"] # 和 LLM API 一致
|
|
|
sequence: int # 当前 Trace 内的顺序
|
|
|
- goal_id: str # 关联的 Goal 内部 ID(如 "1", "2")
|
|
|
+ goal_id: Optional[str] = None # 关联的 Goal 内部 ID(None = 还没有创建 Goal)
|
|
|
tool_call_id: Optional[str] # tool 消息关联对应的 tool_call
|
|
|
content: Any # 消息内容(和 LLM API 格式一致)
|
|
|
description: str # 消息描述(系统自动生成)
|
|
|
@@ -411,6 +441,11 @@ class Message:
|
|
|
- `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 类型说明**:
|
|
|
@@ -434,34 +469,72 @@ GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001/
|
|
|
@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, # 切换焦点到指定显示序号
|
|
|
+ focus: Optional[str] = None, # 切换焦点到指定 ID
|
|
|
) -> str:
|
|
|
"""管理执行计划。"""
|
|
|
```
|
|
|
|
|
|
**实现**:`agent/goal/tool.py:goal_tool`
|
|
|
|
|
|
-**层级支持**:`add` 添加到当前 focus 的 goal 下作为子目标。
|
|
|
+**位置控制**:
|
|
|
+- 不指定 `after`/`under`:添加到当前 focus 的 goal 下作为子目标(无 focus 时添加到顶层)
|
|
|
+- `after="X"`:在目标 X 后面添加兄弟节点(同层级)
|
|
|
+- `under="X"`:为目标 X 添加子目标(如已有子目标,追加到最后)
|
|
|
+- `after` 和 `under` 互斥,不可同时指定
|
|
|
+
|
|
|
+**设计原则**:优先使用 `after` 明确指定位置,`under` 用于首次拆解或追加子任务。
|
|
|
+
|
|
|
+**示例**:
|
|
|
|
|
|
```python
|
|
|
-# 没有 focus 时,添加到顶层
|
|
|
+# 1. 默认行为:添加顶层目标(无 focus)
|
|
|
goal(add="分析代码, 实现功能, 测试")
|
|
|
-# 结果:
|
|
|
# [ ] 1. 分析代码
|
|
|
# [ ] 2. 实现功能
|
|
|
# [ ] 3. 测试
|
|
|
|
|
|
-# focus 到某个 goal 后,add 添加为其子目标
|
|
|
-goal(focus="2")
|
|
|
-goal(add="设计接口, 实现代码")
|
|
|
-# 结果:
|
|
|
+# 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. 实现功能
|
|
|
# [ ] 2.1 设计接口
|
|
|
# [ ] 2.2 实现代码
|
|
|
+# [ ] 2.3 代码审查
|
|
|
+# [ ] 2.4 编写单元测试
|
|
|
# [ ] 3. 测试
|
|
|
+# [ ] 4. 编写文档
|
|
|
```
|
|
|
|
|
|
**状态流转**:
|
|
|
@@ -535,9 +608,55 @@ async def explore_tool(branches: List[str], background: Optional[str] = None) ->
|
|
|
*[run_agent(st, background) for st in sub_traces]
|
|
|
)
|
|
|
|
|
|
- # 4. 汇总返回
|
|
|
- summary = format_explore_results(results)
|
|
|
- return summary
|
|
|
+ # 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)
|
|
|
```
|
|
|
|
|
|
**权限配置**:
|
|
|
@@ -641,7 +760,12 @@ def get_delegate_context() -> Dict[str, Any]:
|
|
|
|
|
|
#### 1. Plan 注入
|
|
|
|
|
|
-每次 LLM 调用时,在 system prompt 末尾注入当前计划状态。注入时过滤掉 abandoned 目标,使用连续的显示序号:
|
|
|
+每次 LLM 调用时,在 system prompt 末尾注入当前计划状态。注入时过滤掉 abandoned 目标,使用连续的显示序号。
|
|
|
+
|
|
|
+**展示策略**:
|
|
|
+- **完整展示**:所有顶层目标、当前 focus 目标的完整父链及其子树
|
|
|
+- **折叠其他**:非关键路径的子目标可折叠显示(显示节点数和状态)
|
|
|
+- **连续编号**:显示 ID 连续且有意义("1", "2", "2.1", "2.2")
|
|
|
|
|
|
```markdown
|
|
|
## Current Plan
|
|
|
@@ -654,13 +778,17 @@ def get_delegate_context() -> Dict[str, Any]:
|
|
|
→ 用户模型在 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="...")` 时:
|
|
|
@@ -718,6 +846,49 @@ Messages:
|
|
|
|
|
|
**实现**:`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 模型
|
|
|
@@ -1080,7 +1251,41 @@ GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d
|
|
|
"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": "完善实现"}
|
|
|
]
|
|
|
@@ -1234,7 +1439,7 @@ Sub-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` | 待调整 |
|
|
|
+| goal 工具 | `agent/goal/tool.py` | 待调整(新增 after/under 参数) |
|
|
|
| explore 工具 | `agent/goal/explore.py` | 待实现 |
|
|
|
| delegate 工具 | `agent/goal/delegate.py` | 待实现 |
|
|
|
| Context 压缩 | `agent/goal/compaction.py` | 待调整 |
|
|
|
@@ -1250,8 +1455,9 @@ Sub-Trace 完成后的压缩策略:
|
|
|
|
|
|
### Phase 1: 基础 goal 工具
|
|
|
- GoalTree 数据结构(含 ID 映射)
|
|
|
-- goal 工具(add, done, focus)
|
|
|
-- Plan 注入到 system prompt(含显示序号生成)
|
|
|
+- goal 工具(add, after, under, done, focus)
|
|
|
+- 位置控制逻辑:after(同层级)、under(子任务)
|
|
|
+- Plan 注入到 system prompt(含显示序号生成、完整展示策略)
|
|
|
- Message 模型(替代 Step)
|
|
|
|
|
|
### Phase 2: 回溯支持
|