# 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 编译器。 ### 决策 C:Profile 全后端化 + 头像对象存储 - 新增 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. 头像签名 URL:bucket/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:可以进入实现阶段,但必须先完成协议文档更新并冻结字段契约。