|
|
@@ -147,7 +147,7 @@ class RunConfig:
|
|
|
parent_goal_id: Optional[str] = None
|
|
|
|
|
|
# 续跑控制
|
|
|
- insert_after: Optional[int] = None # 回溯插入点(message sequence)
|
|
|
+ after_sequence: Optional[int] = None # 从哪条消息后续跑(message sequence)
|
|
|
```
|
|
|
|
|
|
**实现**:`agent/core/runner.py:RunConfig`
|
|
|
@@ -156,11 +156,13 @@ class RunConfig:
|
|
|
|
|
|
通过 RunConfig 参数自然区分,统一入口 `run(messages, config)`:
|
|
|
|
|
|
-| 模式 | trace_id | insert_after | messages 含义 | API 端点 |
|
|
|
-|------|----------|-------------|--------------|----------|
|
|
|
+| 模式 | trace_id | after_sequence | messages 含义 | API 端点 |
|
|
|
+|------|----------|---------------|--------------|----------|
|
|
|
| 新建 | None | - | 初始任务消息 | `POST /api/traces` |
|
|
|
-| 续跑 | 已有 ID | None | 追加到末尾的新消息 | `POST /api/traces/{id}/run` |
|
|
|
-| 回溯 | 已有 ID | 指定 sequence | 在插入点之后追加的新消息 | `POST /api/traces/{id}/run` |
|
|
|
+| 续跑 | 已有 ID | None 或 == head | 追加到末尾的新消息 | `POST /api/traces/{id}/run` |
|
|
|
+| 回溯 | 已有 ID | 主路径上 < head | 在插入点之后追加的新消息 | `POST /api/traces/{id}/run` |
|
|
|
+
|
|
|
+Runner 根据 `after_sequence` 与当前 `head_sequence` 的关系自动判断行为,前端无需指定模式。
|
|
|
|
|
|
### 执行流程
|
|
|
|
|
|
@@ -168,8 +170,8 @@ class RunConfig:
|
|
|
async def run(messages: List[Dict], config: RunConfig = None) -> AsyncIterator[Union[Trace, Message]]:
|
|
|
# Phase 1: PREPARE TRACE
|
|
|
# 无 trace_id → 创建新 Trace(生成 name,初始化 GoalTree)
|
|
|
- # 有 trace_id + 无 insert_after → 加载已有 Trace,状态置为 running
|
|
|
- # 有 trace_id + 有 insert_after → 加载 Trace,执行 rewind(快照 GoalTree,重建,设 parent_sequence)
|
|
|
+ # 有 trace_id + after_sequence 为 None 或 == head → 加载已有 Trace,状态置为 running
|
|
|
+ # 有 trace_id + after_sequence < head → 加载 Trace,执行 rewind(快照 GoalTree,重建,设 parent_sequence)
|
|
|
trace = await _prepare_trace(config)
|
|
|
yield trace
|
|
|
|
|
|
@@ -202,11 +204,11 @@ async def run(messages: List[Dict], config: RunConfig = None) -> AsyncIterator[U
|
|
|
|
|
|
### 回溯(Rewind)
|
|
|
|
|
|
-回溯通过 `RunConfig(trace_id=..., insert_after=N)` 触发,在 Phase 1 中执行:
|
|
|
+回溯通过 `RunConfig(trace_id=..., after_sequence=N)` 触发(N 在主路径上且 < head_sequence),在 Phase 1 中执行:
|
|
|
|
|
|
1. **验证插入点**:确保不截断在 assistant(tool_calls) 和 tool response 之间
|
|
|
2. **快照 GoalTree**:将当前完整 GoalTree 存入 `events.jsonl`(rewind 事件的 `goal_tree_snapshot` 字段)
|
|
|
-3. **重建 GoalTree**:保留 rewind 点之前已 completed 的 goals,丢弃其余,清空 `current_id`
|
|
|
+3. **重建 GoalTree**:保留 rewind 点之前已 completed 的 goals(仅检查主路径上的消息),丢弃其余,清空 `current_id`
|
|
|
4. **设置 parent_sequence**:新消息的 `parent_sequence` 指向 rewind 点,旧消息自动脱离主路径
|
|
|
5. **更新 Trace**:`head_sequence` 更新为新消息的 sequence,status 改回 running
|
|
|
|
|
|
@@ -232,22 +234,22 @@ async for item in runner.run(
|
|
|
...
|
|
|
|
|
|
# 回溯:从指定 sequence 处切断,插入新消息重新执行
|
|
|
-# insert_after=5 表示保留 sequence ≤ 5 的消息,abandon 之后的,从此处开始
|
|
|
+# after_sequence=5 表示新消息的 parent_sequence=5,从此处开始
|
|
|
async for item in runner.run(
|
|
|
messages=[{"role": "user", "content": "换一个方案试试"}],
|
|
|
- config=RunConfig(trace_id="existing-trace-id", insert_after=5),
|
|
|
+ config=RunConfig(trace_id="existing-trace-id", after_sequence=5),
|
|
|
):
|
|
|
...
|
|
|
|
|
|
# 重新生成:回溯后不插入新消息,直接基于已有消息重跑
|
|
|
async for item in runner.run(
|
|
|
messages=[],
|
|
|
- config=RunConfig(trace_id="existing-trace-id", insert_after=5),
|
|
|
+ config=RunConfig(trace_id="existing-trace-id", after_sequence=5),
|
|
|
):
|
|
|
...
|
|
|
```
|
|
|
|
|
|
-`insert_after` 的值是 message 的 `sequence` 号,可通过 `GET /api/traces/{trace_id}/messages` 查看。如果指定的 sequence 是一条带 `tool_calls` 的 assistant 消息,系统会自动将截断点扩展到其所有对应的 tool response 之后(安全截断)。
|
|
|
+`after_sequence` 的值是 message 的 `sequence` 号,可通过 `GET /api/traces/{trace_id}/messages` 查看。如果指定的 sequence 是一条带 `tool_calls` 的 assistant 消息,系统会自动将截断点扩展到其所有对应的 tool response 之后(安全截断)。
|
|
|
|
|
|
**停止运行**:
|
|
|
|
|
|
@@ -269,7 +271,7 @@ await runner.stop(trace_id)
|
|
|
|------|------|------|
|
|
|
| GET | `/api/traces` | 列出 Traces |
|
|
|
| GET | `/api/traces/{id}` | 获取 Trace 详情(含 GoalTree、Sub-Traces) |
|
|
|
-| GET | `/api/traces/{id}/messages` | 获取 Messages |
|
|
|
+| GET | `/api/traces/{id}/messages` | 获取 Messages(支持 mode=main_path/all) |
|
|
|
| GET | `/api/traces/running` | 列出正在运行的 Trace |
|
|
|
| WS | `/api/traces/{id}/watch` | 实时事件推送 |
|
|
|
|
|
|
@@ -292,17 +294,17 @@ curl -X POST http://localhost:8000/api/traces \
|
|
|
-H "Content-Type: application/json" \
|
|
|
-d '{"messages": [{"role": "user", "content": "分析项目架构"}], "model": "gpt-4o"}'
|
|
|
|
|
|
-# 续跑(insert_after 为 null 或省略)
|
|
|
+# 续跑(after_sequence 为 null 或省略)
|
|
|
curl -X POST http://localhost:8000/api/traces/{trace_id}/run \
|
|
|
-d '{"messages": [{"role": "user", "content": "继续深入分析"}]}'
|
|
|
|
|
|
# 回溯:从 sequence 5 处截断,插入新消息重新执行
|
|
|
curl -X POST http://localhost:8000/api/traces/{trace_id}/run \
|
|
|
- -d '{"insert_after": 5, "messages": [{"role": "user", "content": "换一个方案"}]}'
|
|
|
+ -d '{"after_sequence": 5, "messages": [{"role": "user", "content": "换一个方案"}]}'
|
|
|
|
|
|
# 重新生成:回溯到 sequence 5,不插入新消息,直接重跑
|
|
|
curl -X POST http://localhost:8000/api/traces/{trace_id}/run \
|
|
|
- -d '{"insert_after": 5, "messages": []}'
|
|
|
+ -d '{"after_sequence": 5, "messages": []}'
|
|
|
|
|
|
# 停止
|
|
|
curl -X POST http://localhost:8000/api/traces/{trace_id}/stop
|
|
|
@@ -752,8 +754,9 @@ agent/memory/skills/
|
|
|
通过 `POST /api/traces/{id}/reflect` 触发:
|
|
|
|
|
|
1. 在 trace 末尾追加一条 user message(内置反思 prompt),**作为侧枝**(parent_sequence 分叉,不在主路径上)
|
|
|
-2. Agent 回顾整个执行过程,生成经验总结
|
|
|
+2. 使用 `max_iterations=1, tools=[]` 进行单轮无工具 LLM 调用,Agent 回顾整个执行过程生成经验总结
|
|
|
3. 将 assistant 的反思内容追加到 `./cache/experiences.md`
|
|
|
+4. 恢复 head_sequence(try/finally 保证异常时也恢复)
|
|
|
|
|
|
反思消息不影响主对话路径。正常 continue/rewind 时看不到反思消息。
|
|
|
|
|
|
@@ -827,7 +830,7 @@ class TraceStore(Protocol):
|
|
|
async def get_trace(self, trace_id: str) -> Trace: ...
|
|
|
async def update_trace(self, trace_id: str, **updates) -> None: ...
|
|
|
async def add_message(self, message: Message) -> None: ...
|
|
|
- async def get_trace_messages(self, trace_id: str, include_abandoned: bool = False) -> List[Message]: ...
|
|
|
+ async def get_trace_messages(self, trace_id: str) -> List[Message]: ...
|
|
|
async def get_main_path_messages(self, trace_id: str, head_sequence: int) -> List[Message]: ...
|
|
|
async def get_messages_by_goal(self, trace_id: str, goal_id: str) -> List[Message]: ...
|
|
|
async def append_event(self, trace_id: str, event_type: str, payload: Dict) -> int: ...
|