docs(agent): 记录Trace问题与修复方案
This commit is contained in:
@@ -142,6 +142,7 @@ pnpm test
|
|||||||
| [Git 使用细则](docs/Git使用细则-团队开发版.md) | 分支、提交、PR、Review 与合并流程 |
|
| [Git 使用细则](docs/Git使用细则-团队开发版.md) | 分支、提交、PR、Review 与合并流程 |
|
||||||
| [代码注释与 TODO 约定](docs/代码注释与TODO约定.md) | 注释原则、TODO 格式、领域标签与当前待办索引 |
|
| [代码注释与 TODO 约定](docs/代码注释与TODO约定.md) | 注释原则、TODO 格式、领域标签与当前待办索引 |
|
||||||
| [后端审阅复盘](docs/后端全面审阅问题与修复复盘.md) | 后端问题原因、后果与修复方案 |
|
| [后端审阅复盘](docs/后端全面审阅问题与修复复盘.md) | 后端问题原因、后果与修复方案 |
|
||||||
|
| [Agent Trace 复盘](docs/Agent-Core第二阶段问题与修复复盘.md) | Agent 持久化、SSE 恢复、事件契约与脱敏问题复盘 |
|
||||||
| [Knowledge/Retrieval 复盘](docs/Knowledge与Retrieval-Core问题与修复复盘.md) | 检索与事务问题复盘 |
|
| [Knowledge/Retrieval 复盘](docs/Knowledge与Retrieval-Core问题与修复复盘.md) | 检索与事务问题复盘 |
|
||||||
| [前端审阅复盘](docs/前端合并审阅问题与修复复盘.md) | 前端工程、契约和交互问题复盘 |
|
| [前端审阅复盘](docs/前端合并审阅问题与修复复盘.md) | 前端工程、契约和交互问题复盘 |
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,457 @@
|
|||||||
|
# Agent Core 第二阶段:Trace 持久化与 SSE 恢复问题复盘
|
||||||
|
|
||||||
|
> 审阅与修复日期:2026-09-01
|
||||||
|
> 涉及分支:`feat/agent-trace-persistence`
|
||||||
|
> 功能提交:`3cb197a feat(agent): 持久化Trace并支持SSE恢复`
|
||||||
|
> 文档用途:记录 Agent Run、Trace、SSE 恢复和审计数据安全问题的形成原因、实际后果、解决思路与落地方案,供后续开发文档、比赛材料和技术博客写作使用。
|
||||||
|
|
||||||
|
## 1. 背景与结论
|
||||||
|
|
||||||
|
第一阶段 Agent Runtime 已经能够完成模型调用、Tool Calling、权限确认、取消、Usage 和 Citation,但 Run 与 Event 仍以进程内字典和列表为事实来源。第一阶段审阅加入的 Run/Event 数量上限解决了内存无界增长,却没有解决重启丢失、断线续传、Benchmark 复用和敏感数据审计等第二阶段问题。
|
||||||
|
|
||||||
|
本轮处理了 8 类问题:
|
||||||
|
|
||||||
|
| 编号 | 问题 | 级别 | 处理结果 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| A-01 | Agent Run 与 Event 只存在于进程内存 | P0 | 增加 SQLite v3 Schema 和 Trace Repository |
|
||||||
|
| A-02 | Event 裁剪后 sequence 可能重复 | P0 | 改为独立单调序号并建立数据库幂等键 |
|
||||||
|
| A-03 | SSE 断线后无法从指定事件恢复 | P1 | 支持 `Last-Event-ID`、`after_sequence` 和 SSE `id` |
|
||||||
|
| A-04 | 缺少可分页 Trace 与 Benchmark 配置快照 | P1 | 增加 Trace API、统计摘要与配置快照 |
|
||||||
|
| A-05 | Trace 缺少模型调用、父子关系和权限结果 | P1 | 增加第二阶段事件及耗时/parent 字段 |
|
||||||
|
| A-06 | Trace 可能保存 Secret 和超大 Tool Result | P0 | Run、Request、Config、Event 统一脱敏与截断 |
|
||||||
|
| A-07 | 进程重启后未终止 Run 永久显示运行中 | P1 | 自动收束为 `AGENT_PROCESS_RESTARTED` |
|
||||||
|
| A-08 | 前端 DTO 与 SSE Client 无法消费恢复协议 | P1 | 同步 TypeScript Contract、Service、标签和 SSE id |
|
||||||
|
|
||||||
|
修复后的验证基线为:后端 80 项测试、前端 27 项测试、TypeScript 类型检查和生产构建通过。
|
||||||
|
|
||||||
|
## 2. A-01:Agent Run 与 Event 只存在于进程内存
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
旧 `AgentRuntime` 使用 `_records: dict[str, RunRecord]` 保存全部运行状态。每个 `RunRecord` 内部再保存 `events`、实时订阅队列和异步任务。创建、查询、列表和 SSE 回放都只读取这个字典,没有 Repository 边界。
|
||||||
|
|
||||||
|
第一阶段为控制内存加入了最多 200 个 Run、每个 Run 2000 个 Event 的限制。这是必要的资源保护,但只是减少进程内数据量,不能替代持久化。
|
||||||
|
|
||||||
|
### 后果
|
||||||
|
|
||||||
|
- AI Core 重启后,历史 Run、Tool Result、Citation 和错误全部丢失;
|
||||||
|
- 前端刷新或重新连接后,只能读取当前进程尚未淘汰的数据;
|
||||||
|
- Agent Benchmark 无法使用稳定的历史事实计算指标;
|
||||||
|
- Run 列表随着进程重启清空,界面记录和实际操作脱节;
|
||||||
|
- 内存裁剪后的旧事件无法再恢复。
|
||||||
|
|
||||||
|
### 解决思路
|
||||||
|
|
||||||
|
将 SQLite 中的 Run/Event 作为唯一持久化事实,把内存降级为执行上下文和实时订阅窗口。Runtime 继续负责状态机,Repository 负责落库、分页、恢复和统计,Router 不直接访问数据库。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
|
||||||
|
新增 SQLite v3 migration:
|
||||||
|
|
||||||
|
```text
|
||||||
|
agent_runs
|
||||||
|
├── run_id
|
||||||
|
├── status
|
||||||
|
├── run_json
|
||||||
|
├── request_json
|
||||||
|
├── config_snapshot_json
|
||||||
|
├── created_at
|
||||||
|
└── updated_at
|
||||||
|
|
||||||
|
agent_events
|
||||||
|
├── run_id
|
||||||
|
├── sequence
|
||||||
|
├── event
|
||||||
|
├── data_json
|
||||||
|
└── timestamp
|
||||||
|
```
|
||||||
|
|
||||||
|
新增 `AgentTraceRepository`,负责:
|
||||||
|
|
||||||
|
- 创建 Run 及请求、配置快照;
|
||||||
|
- 在同一事务中更新 Run 并追加 Event;
|
||||||
|
- 按创建时间分页列出 Run;
|
||||||
|
- 按 sequence 读取 Event;
|
||||||
|
- 生成 Trace 分页响应和统计摘要;
|
||||||
|
- 恢复进程中断的非终态 Run。
|
||||||
|
|
||||||
|
内存中的 2000 条 Event 上限继续保留,但只用于实时订阅窗口;完整记录由 SQLite 管理。
|
||||||
|
|
||||||
|
## 3. A-02:Event 裁剪后 sequence 可能重复
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
旧 `_publish()` 使用下面的方式生成序号:
|
||||||
|
|
||||||
|
```python
|
||||||
|
sequence = len(record.events)
|
||||||
|
```
|
||||||
|
|
||||||
|
当事件数量超过 2000 后,Runtime 会删除列表头部。列表长度重新回到 2000,后续事件仍会得到 2000,形成重复 sequence。
|
||||||
|
|
||||||
|
### 后果
|
||||||
|
|
||||||
|
- `run_id + sequence` 无法作为幂等键;
|
||||||
|
- 前端按 sequence 去重时会错误丢弃新事件;
|
||||||
|
- 时间线排序出现同序号节点;
|
||||||
|
- 持久化后会触发主键冲突,或者在错误的覆盖策略下破坏旧事件;
|
||||||
|
- Benchmark 无法可靠还原 Tool Call 顺序。
|
||||||
|
|
||||||
|
### 解决思路
|
||||||
|
|
||||||
|
sequence 应属于 Run 的逻辑时钟,不能从当前缓存长度推导。内存是否裁剪不得影响序号。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
|
||||||
|
- `RunRecord` 增加独立的 `next_sequence`;
|
||||||
|
- 每次发布读取当前值,再原子递增;
|
||||||
|
- `agent_events` 使用 `(run_id, sequence)` 复合主键;
|
||||||
|
- Repository 对重复写入使用幂等插入,不覆盖已经存在的事件事实;
|
||||||
|
- Trace 和 SSE 均严格按 sequence 升序返回。
|
||||||
|
|
||||||
|
## 4. A-03:SSE 断线后无法恢复
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
旧 SSE 接口只能从内存列表头部重新回放全部历史,再切换到实时队列。协议帧只有 `event:` 和 `data:`,没有 SSE 标准的 `id:`。接口也不读取 `Last-Event-ID` 或查询游标。
|
||||||
|
|
||||||
|
前端即使知道自己最后处理到哪个 sequence,也无法把该位置传回服务端。
|
||||||
|
|
||||||
|
### 后果
|
||||||
|
|
||||||
|
- 短暂断网或页面切换后只能从头回放;
|
||||||
|
- 长 Run 重连会重复传输大量事件;
|
||||||
|
- 前端需要依赖本地去重掩盖服务端缺少恢复能力;
|
||||||
|
- AI Core 重启后无法续传,因为历史事件本身也不存在;
|
||||||
|
- 实时与历史交界处容易漏事件或重复事件。
|
||||||
|
|
||||||
|
### 解决思路
|
||||||
|
|
||||||
|
恢复协议以 sequence 为游标。服务端先注册实时订阅,再读取 `sequence > cursor` 的持久化历史,随后消费实时队列;交界处允许重复,但 Runtime 和前端都按 sequence 去重。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
|
||||||
|
接口支持两种游标输入:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/agent/runs/{run_id}/events?after_sequence=42
|
||||||
|
Last-Event-ID: 42
|
||||||
|
```
|
||||||
|
|
||||||
|
返回帧包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
id: 43
|
||||||
|
event: ToolResult
|
||||||
|
data: {"run_id":"...","sequence":43,"data":{...}}
|
||||||
|
```
|
||||||
|
|
||||||
|
具体处理:
|
||||||
|
|
||||||
|
- `after_sequence` 优先于 `Last-Event-ID`;
|
||||||
|
- 默认游标为 `-1`,表示从 sequence 0 开始;
|
||||||
|
- 非整数或小于 `-1` 的 Header 返回 `TRACE_CURSOR_INVALID`;
|
||||||
|
- 历史回放读取 SQLite,不依赖内存窗口;
|
||||||
|
- 历史与实时交界处按最后已发送 sequence 跳过重复项;
|
||||||
|
- 终态 Run 回放完终止事件后关闭连接。
|
||||||
|
|
||||||
|
## 5. A-04:缺少可分页 Trace 与 Benchmark 配置快照
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
旧接口只有 Run 状态和 SSE。SSE 适合实时消费,不适合报告页随机访问、大 Trace 分页或 Benchmark 批量计算。Run 也没有保存创建时的 Provider、Model、Skill、允许工具和限制参数快照。
|
||||||
|
|
||||||
|
### 后果
|
||||||
|
|
||||||
|
- Trace 页面只能依赖一次长连接重建全部状态;
|
||||||
|
- Benchmark 需要绕过正式接口读取 Runtime 内部对象;
|
||||||
|
- Provider 或 Skill 配置变化后,旧结果失去可解释性;
|
||||||
|
- 大 Trace 无法受控分页,接口响应体会持续增大;
|
||||||
|
- 前端和 Benchmark 容易各自实现一套不一致的统计逻辑。
|
||||||
|
|
||||||
|
### 解决思路
|
||||||
|
|
||||||
|
提供面向读取的 Trace Snapshot API,但只返回平铺事实,不在后端生成前端树形布局。前端按 parent ID 和 sequence 构造时间线,Benchmark 从同一事件计算指标。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
|
||||||
|
新增接口:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/agent/runs/{run_id}/trace?after_sequence=-1&limit=200
|
||||||
|
```
|
||||||
|
|
||||||
|
响应包含:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"run_id": "run_123",
|
||||||
|
"status": "completed",
|
||||||
|
"items": [],
|
||||||
|
"next_sequence": 199,
|
||||||
|
"has_more": true,
|
||||||
|
"summary": {
|
||||||
|
"model_calls": 2,
|
||||||
|
"tool_calls": 3,
|
||||||
|
"duration_ms": 1530,
|
||||||
|
"token_usage": 2048,
|
||||||
|
"errors": 0
|
||||||
|
},
|
||||||
|
"config_snapshot": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
配置快照保存:
|
||||||
|
|
||||||
|
- Provider ID 和类型;
|
||||||
|
- Model;
|
||||||
|
- Capability;
|
||||||
|
- Skill ID;
|
||||||
|
- 允许的 Tool;
|
||||||
|
- `max_steps`、Token Budget 和网络权限;
|
||||||
|
- 经过脱敏的 Metadata。
|
||||||
|
|
||||||
|
## 6. A-05:Trace 缺少关键执行事实
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
第一阶段事件能够表达 Run、Tool、Permission Request、Usage 和 Citation,但没有明确表示一次模型调用的开始、完成或失败。Tool Call 与模型轮次之间也没有 parent ID,Tool Result 缺少统一耗时。权限接口只唤醒 Future,不记录最终决定。
|
||||||
|
|
||||||
|
### 后果
|
||||||
|
|
||||||
|
- 前端无法展示“模型调用 → 多个 Tool → 下一次模型调用”的完整树;
|
||||||
|
- Agent Benchmark 无法计算模型调用次数和平均步骤耗时;
|
||||||
|
- 并发 Tool Call 时难以判断属于哪个模型轮次;
|
||||||
|
- 权限卡片消失后,Trace 中只保留“请求过权限”,不知道用户允许还是拒绝;
|
||||||
|
- Provider 失败只能看到最终 RunFailed,缺少模型调用级上下文。
|
||||||
|
|
||||||
|
### 解决思路
|
||||||
|
|
||||||
|
保持现有 AgentEvent envelope 不变,只增加事件类型和可选数据字段。调用关系使用稳定 ID 表达,不让前端根据相邻位置猜测父子关系。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
|
||||||
|
新增事件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ModelCallStarted
|
||||||
|
ModelCallCompleted
|
||||||
|
ModelCallFailed
|
||||||
|
PermissionResolved
|
||||||
|
```
|
||||||
|
|
||||||
|
补充字段:
|
||||||
|
|
||||||
|
- Model Call:`model_call_id`、Provider、Model、Step、Finish Reason、Token、Duration;
|
||||||
|
- Tool Call/Result:`parent_model_call_id`、`duration_ms`;
|
||||||
|
- Permission Resolved:`request_id`、Permission、Decision;
|
||||||
|
- Model Call Failed:`error_code` 和耗时,不写入第三方原始敏感异常。
|
||||||
|
|
||||||
|
模型与 Tool 耗时使用单调时钟计算,避免系统时间调整影响 Duration。
|
||||||
|
|
||||||
|
## 7. A-06:Trace 可能保存 Secret 和超大结果
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
Tool 参数和输出来自模型、插件或外部服务,属于不可信数据。旧事件直接保存 `ToolCall.model_dump()` 和 `ToolResult.model_dump()`。`notes.read`、附件或第三方 Tool 可以返回大段正文,参数也可能包含 `api_key`、Authorization 或 Password。
|
||||||
|
|
||||||
|
初版持久化修复只净化了 `agent_events.data_json`。提交前审阅发现,`agent_runs.run_json` 中的 `tool_results`、`output` 和 `input` 仍可能保存同一份敏感值,说明“只在事件层脱敏”并不完整。
|
||||||
|
|
||||||
|
### 后果
|
||||||
|
|
||||||
|
- API Key 或 Bearer Token 可能进入 SQLite、备份和测试产物;
|
||||||
|
- Trace API 不返回 Secret,但数据库中的 Run Snapshot 仍可能泄露;
|
||||||
|
- 单个 Tool Result 可以让 Event 和 Run JSON 快速膨胀;
|
||||||
|
- 前端展开节点时可能因为超大 JSON 卡顿;
|
||||||
|
- Benchmark Dataset 或报告导出可能间接携带密钥。
|
||||||
|
|
||||||
|
### 解决思路
|
||||||
|
|
||||||
|
所有进入持久化边界的数据统一经过同一个净化函数,不能分别在 Router、Runtime 和 Repository 中维护不同规则。净化必须同时覆盖键名、常见密钥值模式、递归深度、字符串长度和集合大小。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
|
||||||
|
统一处理以下对象:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AgentRun Snapshot
|
||||||
|
AgentRunCreateRequest Snapshot
|
||||||
|
Config Snapshot
|
||||||
|
AgentEvent Data
|
||||||
|
```
|
||||||
|
|
||||||
|
净化规则:
|
||||||
|
|
||||||
|
- `api_key`、Authorization、Access/Refresh Token、Password、Secret 等键替换为 `[REDACTED]`;
|
||||||
|
- 常见 `sk-...` 和 `Bearer ...` 字符串模式直接替换;
|
||||||
|
- 单字符串最多保留 4096 个字符;
|
||||||
|
- 单集合最多保留 100 项;
|
||||||
|
- 递归深度最多 8 层;
|
||||||
|
- 超限位置使用明确的 `[TRUNCATED]` 或 `[MAX_DEPTH]` 标记;
|
||||||
|
- `credential_id` 等非明文引用保留,不误判为 Secret。
|
||||||
|
|
||||||
|
回归测试直接读取 `agent_runs` 原始 SQLite 字段,确认测试密钥没有落盘,避免只验证 API 响应造成假安全。
|
||||||
|
|
||||||
|
## 8. A-07:重启后未终止 Run 永久显示运行中
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
Agent 的异步 Task 和 Permission Future 不能跨进程恢复。持久化 Run 后,如果直接返回数据库状态,重启前处于 `queued`、`running` 或 `waiting_permission` 的记录会一直保持非终态,但新进程中没有对应 Task 可以继续执行。
|
||||||
|
|
||||||
|
### 后果
|
||||||
|
|
||||||
|
- 前端长期显示“运行中”或“等待授权”;
|
||||||
|
- SSE 订阅等待一个永远不会到来的终止事件;
|
||||||
|
- Benchmark Runner 无法判断 Case 已中断;
|
||||||
|
- 用户取消该 Run 时,新进程找不到实际 Task;
|
||||||
|
- 统计中的成功率和耗时被悬挂 Run 污染。
|
||||||
|
|
||||||
|
### 解决思路
|
||||||
|
|
||||||
|
本阶段提供“状态恢复”,不伪装成“执行恢复”。没有可重放状态机、Provider 幂等令牌和 Tool 副作用日志之前,自动继续执行会造成重复写入或重复网络请求。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
|
||||||
|
新 Runtime 首次读取不属于当前进程的非终态 Run 时:
|
||||||
|
|
||||||
|
- 状态改为 `failed`;
|
||||||
|
- 错误码设为 `AGENT_PROCESS_RESTARTED`;
|
||||||
|
- 错误信息说明进程在完成前重启;
|
||||||
|
- 使用数据库最大 sequence 加一,追加唯一 `RunFailed` 事件;
|
||||||
|
- 后续 Run 查询、Trace 和 SSE 都返回同一终态事实。
|
||||||
|
|
||||||
|
已经完成、失败或取消的 Run 不修改,可以在重启后继续查询和回放。
|
||||||
|
|
||||||
|
## 9. A-08:前端无法消费第二阶段恢复协议
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
后端增加新事件和 SSE `id` 后,前端 `AgentEventType` 仍只包含第一阶段事件。`SseClient` 只解析 `event` 和 `data`,忽略 `id`,也没有发送 `Last-Event-ID`。因此仅完成后端并不能形成可联调的 Contract。
|
||||||
|
|
||||||
|
### 后果
|
||||||
|
|
||||||
|
- `Record<AgentEventType, string>` 中文标签无法通过类型检查;
|
||||||
|
- 前端不知道 Model Call 和 Permission Resolved 的类型;
|
||||||
|
- 断线后无法把最后事件 ID 传回后端;
|
||||||
|
- Trace 可视化负责人需要自行猜测 Wire DTO;
|
||||||
|
- 后端恢复能力只能通过 Curl 使用,页面调用链没有闭环。
|
||||||
|
|
||||||
|
### 解决思路
|
||||||
|
|
||||||
|
范侧只提供稳定的前端接口适配,不越过分工实现 Trace Visualization。Pydantic Contract、TypeScript Wire DTO 和 Service 必须在同一功能提交中同步。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
|
||||||
|
- TypeScript 增加四类第二阶段 Agent Event;
|
||||||
|
- 增加 `AgentTraceSummary` 和 `AgentTraceResponse`;
|
||||||
|
- `agentService.getAgentTrace()` 封装分页查询;
|
||||||
|
- `streamAgentEvents()` 接受 `afterSequence`;
|
||||||
|
- `SseClient` 发送 `Last-Event-ID` 并解析返回帧的 `id`;
|
||||||
|
- 新事件增加中文标签和详情字段名;
|
||||||
|
- 增加 SSE 请求头和 Event ID 单元测试。
|
||||||
|
|
||||||
|
Trace 时间线、树形布局、筛选、节点展开和 Citation 跳转仍由前端负责人实现。
|
||||||
|
|
||||||
|
## 10. 事务、顺序与恢复不变量
|
||||||
|
|
||||||
|
本轮修复明确了以下不变量:
|
||||||
|
|
||||||
|
1. `run_id + sequence` 唯一标识一条 Agent Event。
|
||||||
|
2. sequence 在一个 Run 内只增不减,不受内存裁剪影响。
|
||||||
|
3. 发布事件时,在同一 SQLite 事务中更新 Run Snapshot 并追加 Event。
|
||||||
|
4. SSE、Trace 页面和 Agent Benchmark 读取同一份 `agent_events`,不建立旁路。
|
||||||
|
5. 终止事件为 `RunCompleted`、`RunFailed` 或 `RunCancelled`;终态 Run 不再产生业务事件。
|
||||||
|
6. 重启后不能安全继续执行的 Run 必须明确失败,不能永久悬挂。
|
||||||
|
7. 所有持久化 Trace 数据先脱敏、再写入。
|
||||||
|
8. 前端按 `run_id + sequence` 去重,不能依赖一次网络读取对应一条 SSE Event。
|
||||||
|
|
||||||
|
## 11. 验证方法
|
||||||
|
|
||||||
|
后端:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd backend
|
||||||
|
uv run python -m compileall -q app
|
||||||
|
uv run pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
前端:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd frontend
|
||||||
|
pnpm test
|
||||||
|
pnpm type-check
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
仓库检查:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
验证结果:
|
||||||
|
|
||||||
|
```text
|
||||||
|
backend pytest 80 passed
|
||||||
|
backend compileall passed
|
||||||
|
frontend vitest 11 files / 27 tests passed
|
||||||
|
frontend type-check passed
|
||||||
|
frontend build passed
|
||||||
|
git diff --check passed
|
||||||
|
```
|
||||||
|
|
||||||
|
本轮新增回归覆盖:
|
||||||
|
|
||||||
|
- 完成 Run 在新 Runtime 中恢复查询;
|
||||||
|
- Trace 多页读取和游标无重复;
|
||||||
|
- SSE `Last-Event-ID`、`after_sequence` 和 `id:` 帧;
|
||||||
|
- Model Call、Permission Resolved 和 Tool parent ID;
|
||||||
|
- 进程中断 Run 自动生成唯一终止事件;
|
||||||
|
- API Key、Authorization 和超长 Tool 参数净化;
|
||||||
|
- 直接检查 SQLite,确认 Secret 未进入 Run/Request/Config Snapshot;
|
||||||
|
- OpenAPI 发布 Trace 路径;
|
||||||
|
- 前端 SSE Client 发送和解析恢复游标。
|
||||||
|
|
||||||
|
测试仍会出现本机 `.pytest_cache` 无写入权限警告,不影响 80 项用例结果,也不涉及产品代码。
|
||||||
|
|
||||||
|
## 12. 当前边界与后续工作
|
||||||
|
|
||||||
|
### 12.1 本阶段明确不做
|
||||||
|
|
||||||
|
- 不在后端生成前端 Trace 树形布局;
|
||||||
|
- 不在进程重启后自动重放未完成 Tool 副作用;
|
||||||
|
- 不把 Secret 明文放入 Trace、日志或 Benchmark;
|
||||||
|
- 不为 Benchmark 建立绕过 Agent Runtime 的专用执行协议。
|
||||||
|
|
||||||
|
### 12.2 后续需要继续处理
|
||||||
|
|
||||||
|
- 增加 Trace 保留、归档和被 Benchmark 引用时的保护策略;
|
||||||
|
- 引入保留窗口后实现 `TRACE_CURSOR_EXPIRED`;
|
||||||
|
- 根据桌面网络策略增加有上限的指数退避自动重连;
|
||||||
|
- 评估高频 Token Event 的批量写入,减少 SQLite 连接与事务开销;
|
||||||
|
- Agent Benchmark 接入正式 Trace 并验证指标字段是否充足;
|
||||||
|
- 前端完成 Trace Timeline/Tree、筛选、节点详情和 Citation 跳转;
|
||||||
|
- 多进程或远程执行出现需求后,再设计带租约和幂等副作用的执行恢复。
|
||||||
|
|
||||||
|
## 13. 可复用经验
|
||||||
|
|
||||||
|
### 13.1 资源上限不等于持久化
|
||||||
|
|
||||||
|
限制内存 Run 和 Event 数量只能防止进程膨胀,不能解决重启、审计和报告复现。临时保护措施应在文档中明确标注,不能被误认为最终架构已经完成。
|
||||||
|
|
||||||
|
### 13.2 游标必须独立于缓存结构
|
||||||
|
|
||||||
|
只要 sequence 来源于 `len(list)`、数组下标或当前页位置,裁剪和分页就可能破坏唯一性。可恢复事件流必须使用独立、单调且可持久化的逻辑序号。
|
||||||
|
|
||||||
|
### 13.3 恢复读取不等于恢复执行
|
||||||
|
|
||||||
|
恢复 Run/Trace 查询相对安全;恢复一个包含 Tool 副作用的执行任务需要额外的幂等、租约和补偿机制。在没有这些机制时,明确失败比重复执行更可靠。
|
||||||
|
|
||||||
|
### 13.4 脱敏要覆盖全部持久化副本
|
||||||
|
|
||||||
|
同一敏感值可能同时出现在 Event、Run Snapshot、Request、Config、日志和报告中。只检查最终 API 响应无法证明数据没有落盘,安全测试应直接验证持久化介质。
|
||||||
|
|
||||||
|
### 13.5 生产者和消费者 Contract 必须同时更新
|
||||||
|
|
||||||
|
后端新增事件类型、字段或 SSE 规则时,至少同步 Pydantic、OpenAPI、TypeScript DTO、Service 和协议测试。可视化页面可以由另一成员开发,但不能让对方从后端实现反推 Contract。
|
||||||
Reference in New Issue
Block a user