Files
eryao/docs/plans/2026-04-05-divination-history-profile-backend-source-plan.md
T

242 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Eryao 解卦历史与个人档案后端单一数据源改造计划
日期:2026-04-05
状态:评审中(未开始编码)
## 1. 背景与目标
当前移动端存在两类不符合目标架构的问题:
1. 个人档案(昵称、简介、头像)仍有前端本地状态路径,非后端权威数据源。
2. 首页历史解卦无法稳定由后端快照直接重建结果页,前端被迫做本地兜底。
本计划目标:
- 实现“后端为唯一数据源,前端仅缓存”。
-`DIVINATION_DERIVED` 的完整结构进入消息 `metadata.agent_output` 并持久化。
- 历史接口返回可被前端直接解析的结构化 assistant 输出(不再依赖 `ui_schema`)。
- 个人档案全链路后端化,头像使用 `avatars` bucket。
非目标:
- 本计划不直接提交代码实现。
- 本计划不包含 UI 视觉细节改稿。
## 2. 现状核对(基于仓库代码)
### 2.1 历史接口与消息转换
- 历史接口:`GET /api/v1/agent/history`,定义于 `backend/src/v1/agent/router.py`
- 当前转换逻辑在 `backend/src/v1/agent/utils.py`
- `user` 消息主要输出 `content``attachments`
- `assistant` 消息默认走 `ui_hints -> ui_schema` 编译路径。
- 历史响应结构 `HistoryMessage` 当前包含 `ui_schema`,不直接暴露结构化 `agent_output`
### 2.2 DIVINATION_DERIVED 与落库断点
- 运行时会发出 `DIVINATION_DERIVED`(见 `backend/src/core/agentscope/runtime/runner.py`)。
- 消息落库由 `backend/src/core/agentscope/events/store.py` 负责。
- 当前 `TEXT_MESSAGE_END` 持久化字段包含 `sign_level/summary/.../ui_hints`,未包含 `divination` 结构。
- 结果:历史快照难以完整重建结果页结构。
### 2.3 Profile 与头像
- 后端配置已有 `storage.avatar.bucket`,默认 `avatars``backend/src/core/config/settings.py`)。
- 当前 `v1` 仅挂载 `auth/agent/points` 路由(`backend/src/v1/router.py`),尚无 profile 专用路由。
## 3. 核心设计决策
### 决策 A:把 `divination_derived` 放入 `metadata.agent_output`
-`AgentOutput` 增加字段 `divination_derived`(强类型,禁止裸 `dict`)。
- 事件落库时把 `DIVINATION_DERIVED` 内容并入 assistant 的 `metadata.agent_output.divination_derived`
-`sign_level/summary/advice/...` 同时持久化,形成一条可回放的 assistant 结构化输出。
理由:
- 最小改动复用现有消息表,不新增历史结果表即可满足回放需求。
- 前端可直接从历史响应解析结果页,避免本地拼装。
### 决策 B:历史接口返回 `assistant.agent_output`,移除 `ui_schema`
- `HistoryMessage` 改为:
- `user`: `content + attachments`
- `assistant`: `content + agent_output`
- `ui_schema` 从接口协议中移除(迁移自通用模块的历史遗留,不在本项目范围)。
理由:
- 减少中间编译层,契约更稳定、语义更清晰。
- 前端直接消费业务数据,不依赖通用 UI 编译器。
### 决策 CProfile 全后端化 + 头像对象存储
- 新增 users/profile API,前端只保留缓存层。
- 头像上传走预签名 URL,bucket 固定 `avatars`,路径按用户隔离。
## 4. 协议与接口计划(先文档,后实现)
## 4.1 新增/修改协议文档
按“协议先行”更新以下文档:
1. `docs/protocols/divination/divination-run-protocol.md`
- 增补:历史回放时 assistant `agent_output.divination_derived` 的字段契约。
- 标记:`ui_schema` 已废弃并移除。
2. 新增:`docs/protocols/profile/profile-protocol.md`
- 定义 profile 读写与头像上传签名协议。
3. 如涉及错误码新增,更新:
- `docs/protocols/common/http-error-codes.md`
### 4.2 后端 API 契约(目标)
#### A. 历史快照(改造)
- `GET /api/v1/agent/history`
- 响应中 assistant 消息新增(或替换为)`agent_output`
- `sign_level`
- `summary`
- `conclusion`
- `focus_points`
- `advice`
- `keywords`
- `answer`
- `divination_derived`(完整卦象结构)
#### B. Profile(新增)
- `GET /api/v1/users/me/profile`
- `PATCH /api/v1/users/me/profile`
- `POST /api/v1/users/me/avatar/upload-url`
- (可选)`GET /api/v1/users/me/avatar/signed-url`
#### C. 头像上传约束
- bucket 固定:`config.storage.avatar.bucket`
- 路径前缀建议:`avatars/{user_id}/...`
- 文件类型:`image/png|image/jpeg|image/webp`
- 体积上限:`config.storage.avatar.max_size_mb`
## 5. 数据模型改造计划
### 5.1 Runtime 模型
- 文件:`backend/src/schemas/agent/runtime_models.py`
- 变更:`AgentOutput` 增加 `divination_derived` 字段(类型复用 `schemas/domain/divination.py`)。
- 规则:保持 `extra="forbid"`,禁止无类型漂移。
### 5.2 事件到落库链路
- 文件:`backend/src/core/agentscope/runtime/stage_emitter.py`
- `TEXT_MESSAGE_END` payload 带上 `divination_derived`
- 文件:`backend/src/core/agentscope/events/store.py`
- `worker_output_fields` 纳入 `divination_derived` 并写入 `metadata.agent_output`
### 5.3 历史响应转换
- 文件:`backend/src/v1/agent/utils.py`
- 删除 `ui_hints -> ui_schema` 编译路径。
- assistant 消息改为抽取并返回受控 `agent_output`
- 文件:`backend/src/v1/agent/schemas.py`
- `HistoryMessage` 改字段定义(去 `ui_schema`,加 `agent_output`)。
## 6. 前端消费与缓存策略
### 6.1 历史与结果页
- 历史列表数据源改为后端 `agent/history`
- 点开历史项时:
- 直接解析 `assistant.agent_output.divination_derived` + 解释文本字段。
- 本地仅做缓存,不做真源 fallback。
### 6.2 Profile
- 设置页资料读取改为 `GET /users/me/profile`
- 编辑资料写入 `PATCH /users/me/profile`
- 头像更新走 upload-url + 上传 + profile 更新引用路径。
### 6.3 点数
- 保持后端余额接口作为权威数据源(现有已接)。
- 前端只做短期缓存,解卦完成后强制 refresh。
## 7. 代码清理边界(你关心的“删除通用遗留”)
原则:先去引用,再删定义,最后删文件,避免误删。
分三步:
1. 第一阶段(本次改造内)
- 删除 `agent/history``ui_schema` 的输出与依赖。
- 删除前端对 `ui_schema` 的消费路径(若存在)。
2. 第二阶段(安全清理)
- 搜索 `schemas/domain``schemas/agent/ui_hints` 的实际引用。
- 对“零引用 + 非协议字段”进行清理。
3. 第三阶段(文档与测试补齐)
- 更新协议文档、错误码、回归测试。
备注:
- 不建议在同一 PR 里“功能改造 + 大规模 schema 删除”,建议拆成两个 PR,降低回归风险。
## 8. 测试计划(必须项)
### 8.1 后端单元/集成
1. `TEXT_MESSAGE_END` 持久化:`metadata.agent_output.divination_derived` 落库断言。
2. `GET /api/v1/agent/history`assistant 返回 `agent_output`,且不再返回 `ui_schema`
3. 历史分页与 owner 校验不回退。
4. profile API:读写、权限、字段约束、头像路径安全性。
5. 头像签名 URLbucket/path/mime/size 约束。
### 8.2 前端
1. 历史列表从后端数据渲染。
2. 点击历史项成功进入结果页,字段一致性校验。
3. profile 页面读写闭环(昵称/简介/头像)。
4. 点数刷新与缓存失效策略验证。
## 9. 风险与回滚
主要风险:
- 历史消息中旧数据可能没有 `divination_derived`,前端需兼容空值。
- `ui_schema` 下线后,若有隐藏调用方会断。
回滚策略:
- 协议层采用短期双读兼容窗口(仅过渡期):
- 新字段优先;旧字段仅用于读,不再写。
- 若线上异常,先回滚 history 响应变更,再保持落库新增字段不删。
## 10. 实施顺序(最小风险)
1. 协议文档更新并评审通过。
2. 后端:`AgentOutput` + 事件落库 + history 响应新增 `agent_output`(先加后切)。
3. 前端:改消费到 `agent_output`,移除本地真源。
4. 后端:移除 `ui_schema` 输出。
5. profile API + 前端接入头像上传。
6. 清理无用 schema(独立 PR)。
## 11. 验收标准(DoD
全部满足才算完成:
1. 解卦后写入的 assistant 消息在 DB 中可见 `metadata.agent_output.divination_derived`
2. 首页历史完全来自后端,清空本地缓存后仍可正确展示。
3. 历史详情可完整还原结果页,不依赖 `ui_schema`
4. profile 读写走后端,头像实际落 `avatars` bucket。
5. 前端不再把 profile/history 作为本地权威数据源。
6. 协议文档与实现一致,相关测试通过。
## 12. GSTACK REVIEW REPORT
| Review | Trigger | Why | Runs | Status | Findings |
|--------|---------|-----|------|--------|----------|
| Eng Review | `/plan-eng-review` | 锁定架构、契约、测试闭环 | 1 | Done | 确认后端单一数据源方向;建议分阶段移除 `ui_schema` 并将 schema 清理拆分独立 PR |
| CEO Review | `/plan-ceo-review` | 范围与优先级 | 0 | — | — |
| Design Review | `/plan-design-review` | UI/UX 风险 | 0 | — | — |
| DX Review | `/plan-devex-review` | 开发体验风险 | 0 | — | — |
VERDICT:可以进入实现阶段,但必须先完成协议文档更新并冻结字段契约。