Files
wecom_it_smart_desk/docs/01-产品文档/00-产品规划/PRD-REQ-通用-005-选项选择持久化-v1.0.md
T
Simon facc04aa65 chore: docs 结构整改 + compose 双目录对齐(合并重建提交)
本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交
(5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。

一、docs 结构整改(整改 #14)
根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入,
随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录
树并存于 docs/,共 791 文件、双分类体系冲突。

修复动作:
- b2 同名异主题文件改名迁移保全 9 个
- C 类 39 个孤立文件按主题正确归类
- A/B1 类 222 个重复文件删除(新结构已有内容副本)
- 9 个旧独有空目录删除
- 270 处内部引用按 verified 映射改写
- 整改记录 #14 登记于 04-运维文档/部署运维

结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。
残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。

二、compose 双目录对齐(消除踩坑 A)
- docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist
  改为 src/frontend-*/dist(h5 / agent / admin / terminal)
- docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/
- 效果:本地 docker compose up 不再把根目录 stale dist 挂回,
  与线上一致,分叉隐患消除(已 docker compose config 校验通过)

防复发铁律:
- 重构须提交;仓库修复须 git stash -u 或先 commit
- 新结构须 git add 并提交,避免再次 untracked 复活
- H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
2026-08-07 22:31:32 +08:00

12 KiB
Raw Blame History

PRD - 选项选择持久化

REQ 编号: REQ-通用-005 版本: v1.0 日期: 2026-07-29 作者: 许清楚(PM+ 宋献(Simon 状态: 已批准 关联:

  • 技术方案:docs/02-技术文档/技术架构/技术方案-REQ-通用-005-选项选择持久化-v1.0.md(待架构师出)
  • 测试用例:docs/03-测试文档/03-功能测试用例/TC-REQ-通用-005-选项选择持久化-v1.0.md(待 QA 出)
  • 代码真相:src/backend/app/api/ws.py + src/backend/app/services/h5_ai_task.py
  • 相关 PRDPRD-REQ-用户-006-智能推荐重构-v1.0.md(衍生关系)

1. 问题陈述

Dify 工作流可输出 quick reply / 选择题形式的“选项消息”。AI 选项消息已以 msg_type="ai_structured"extra_data.options 持久化,但用户点选后,后端 _handle_option_select 当前故意不写 messages 表,仅进行瞬时 WebSocket 广播并将 option_label 传给 Difysrc/backend/app/api/ws.py:277-281)。坐席端的 selectedOptionLabels 又是纯内存集合,重连即失效,导致选择动作无法追溯。

优先级 业务影响 真实场景 可量化后果
P0 合规与审计留痕缺失 金融/政府客户复盘“员工选择了哪一项”时,管理端无记录 操作链不完整,无法满足可追溯要求
P0 坐席分诊效率下降 紧急报修员工已选“网络中断”,坐席进会话后看不到,只能重问 会话时长预计增加约 20%,存在 SLA 风险
P1 AI 推荐反馈闭环断裂 智能推荐卡 A/B/C 中用户选 C,但系统无结构化选择数据 A/B/C 效果不可归因,AI 投入 ROI 难评估
P1 会话连续性中断 员工刷新或退出 H5 后,历史中缺少已选内容 断点续聊上下文不完整,Dify 将选择误识别为普通发言
P2 员工体验不一致 员工看不到“我之前选了什么” 重复操作、降低信任感

目标:以零数据库迁移方式,将每次选择作为 employee 消息持久化,并在 H5、坐席、管理审计、Dify 反馈链和转人工上下文中形成一致、可追溯的数据闭环。


2. 用户故事

角色 现状 期望用户故事
员工 H5 刷新/退出后已选内容消失 作为员工,我希望每次选择都进入消息历史,以便刷新或续聊时仍能确认我选过什么
坐席 PC 仅实时内存可见,进入晚或重连后不可见 作为坐席,我希望实时及历史消息中看到“✓ 选项”,并识别最新选择,以便无需重复询问即可分诊
管理端 无选择记录可供审计、复盘 作为管理员,我希望按会话追溯选择时间、题目和选项,以便满足合规审计和投诉复盘
AI 训练/运营 选择被当作普通自然语言,且缺少选项归属 作为 AI 训练人员,我希望获得带 question_idoption_idfeedback_type 的反馈,以便准确评估推荐效果

3. 范围

3.1 In-scopev1.0 MVP

# 范围项 优先级
1 _handle_option_select 插入 msg_type="option_select" 的员工消息(src/backend/app/api/ws.py:270-281 P0
2 选择成功后使用标准 new_message 事件广播(src/backend/app/api/ws.py:284-293 P0
3 坐席 MessageBubble.vue 渲染“✓ {content}”灰色徽标,最新选择高亮(src/frontend-agent/src/components/chat/MessageBubble.vue:52-77 P0
4 H5 sendOptionSelect 补齐来源消息、客户端幂等及题目/选项标识(src/frontend-h5/src/stores/conversation.ts:1486 P0
5 客户端生成 UUID,服务端按会话与客户端消息 ID 执行 5 秒去重 P0
6 回归“发送→点选→坐席实时→坐席重连→REST 历史仍可见”完整链路 P0
7 Dify inputs 注入 feedback_type=option_select P1
8 转人工时注入 selected_options 快照 P1
9 选项展示遵循敏感信息中间四位脱敏 P0

3.2 Out-of-scope

  • 方案 B:新增 conversation_selections 表及数据库迁移。
  • 方案 C:仅向 ai_structured.extra_data 追加 selected_options
  • 多卡嵌套、父子题、条件题等复杂语义编排。
  • 跨会话选择聚合、BI 看板与推荐效果报表。

4. 功能需求

4.1 后端落库 Schema

服务端收到合法选择后,必须先完成幂等判断,再向现有 messages 表追加一行;不得覆盖原选项消息或历史选择。实现位置为 _handle_option_selectsrc/backend/app/api/ws.py:270-281)。

字段 类型/示例 必填 规则
msg_type "option_select" 新增文档化取值;现有字段 String(20),零迁移
sender_type "employee" 表示员工操作
content "网络中断" 保存原始 option_label,展示时再脱敏
extra_data.option_value "network_down" 传给业务/Dify 的选项值
extra_data.selected_from_message_id 消息 ID 关联产生选项的 ai_structured 消息
extra_data.client_msg_id UUID 幂等键
extra_data.question_id "fault_type" 题目稳定标识,支持跨卡归属
extra_data.option_id "network_down" 选项稳定标识,禁止仅靠 label 归属

持久化成功后,REST 历史接口必须按现有消息排序规则返回该记录;失败时不得向 Dify提交“已成功选择”的假状态。

4.2 WS 事件改造

当前瞬时专用广播必须改为标准 new_message 事件(src/backend/app/api/ws.py:284-293),事件 payload 应复用持久化后的消息对象,至少包含消息 ID、会话 ID、发送方、消息类型、内容、extra_data、创建时间。坐席实时态与重连后的 REST 历史态必须使用同一数据模型。

4.3 坐席端渲染规范

  • src/frontend-agent/src/components/chat/MessageBubble.vue:52-77 必须增加 msg_type === "option_select" 分支。
  • 徽标位于员工消息流原选择发生的时间位置,文案为 ✓ {mask(content)},采用灰色次要信息样式,不渲染为普通气泡。
  • 同一 question_id 多次选择全部保留;当前最新一条使用主色描边或浅色背景高亮,旧选择降级为灰色。
  • 最新判定按服务端消息时间与消息 ID 稳定排序,不依赖 selectedOptionLabels 内存集合。

4.4 H5 端字段补全

sendOptionSelectsrc/frontend-h5/src/stores/conversation.ts:1486-1518)必须发送:option_labeloption_valueselected_from_message_idclient_msg_idquestion_idoption_idclient_msg_id 在首次点击时生成 UUID;同一次请求重试必须复用,用户主动重选必须生成新 UUID。

4.5 幂等与去重规则

  • 服务端幂等键:(conversation_id, client_msg_id)
  • 去重窗口:首次受理后 5 秒;窗口内重复请求只返回首次成功结果,不新增消息、不重复广播、不重复调用 Dify。
  • 5 秒后相同 ID 仍不得被客户端主动复用;服务端可记录告警并拒绝,以避免历史重复。
  • 不同 client_msg_id 即视为撤回后的重选/再次选择,追加新行。

4.6 Dify 集成

传入 Dify Workflow 的 inputs 必须新增 feedback_type="option_select",并同时传递 question_idoption_idoption_value、脱敏后的 option_label;接入点由技术方案基于现有 Dify 调用链定位(当前调用见 src/backend/app/tasks/h5_ai_task.py:1505-1514,任务入口见 src/backend/app/tasks/h5_ai_task.py:1617-1624)。Dify 必须据此区分“用户选择了 X”与“用户自然语言说了 X”,且不破坏现有普通文本消息链路。

4.7 转人工快照

触发转人工时,系统必须按每个 question_id 取最新一条有效选择,组成 selected_options 注入坐席上下文;每项至少包含 question_idoption_id、脱敏 label、选择消息 ID、选择时间。快照仅用于快速接续,审计真相仍以 messages 表全部追加记录为准。

4.8 敏感词 Mask

选项含账号、身份证号等敏感数字串时,数据库保存原始值以满足审计权限场景;H5、坐席普通视图、WS 普通 payload、Dify inputs 和转人工快照必须将数字串中间连续四位替换为 ****。不足 4 位的敏感值全部掩码;脱敏不得改变 question_idoption_id 的匹配与最新选择判定。


5. 验收标准

编号 对应需求 验收用例与通过标准
AC-01 4.1 点选后 messages 新增 1 行:msg_type=option_selectsender_type=employee5 个 extra_data 字段完整;REST 重拉仍存在
AC-02 4.1 连续重选两次形成两行,不覆盖首次记录,均可按时间追溯
AC-03 4.2 坐席在线时收到标准 new_message;断线重连后从 REST 得到相同消息 ID 与内容
AC-04 4.3 坐席显示“✓ 选项”灰色徽标;同一 question_id 仅最新一条高亮,旧记录仍可见
AC-05 4.4 H5 每次主动选择生成合法 UUID,并携带来源消息、题目、选项标识;重试复用 UUID
AC-06 4.5 5 秒内用相同 (conversation_id, client_msg_id) 重发 3 次,仅落库、广播、调用 Dify 各 1 次
AC-07 4.6 Dify 收到 feedback_type=option_select 及题目/选项字段;普通文本仍沿用原语义
AC-08 4.7 员工选择后立即转人工,坐席上下文包含各 question_id 最新选择;坐席无需重问
AC-09 4.8 选项包含账号/身份证示例时,各普通展示及 Dify 输入中间四位为 ****,数据库审计原值不变
AC-10 全链路 完成“AI 发选项→员工点选→坐席实时 ✓→坐席重连→REST 历史仍可见”,全程无重复记录

6. 边界场景

场景 产品规则 预期结果
撤回/重选 不改旧行,使用新 client_msg_id 追加记录 全历史可见;同题最新一条高亮并进入快照
弱网重发 同一请求复用 client_msg_id5 秒窗口去重 只落库、广播、调用 Dify 一次
跨卡归属 必须联合 question_id + option_idlabel 不作为唯一键 多卡存在同名 label 时仍准确归属
敏感词 存储原值,展示与外发链路 mask 中间四位 审计可追溯,普通使用方不暴露敏感值
转人工 每题取最新选择生成 selected_options 人工坐席获取完整断点上下文,历史行不丢失

7. 非目标

  1. 不新增选择专表、不执行 Alembic 迁移。
  2. 不把选择状态回写到原 ai_structured.extra_data,避免撤回/重选语义丢失。
  3. 不建设选项编辑、撤销按钮;v1.0 的“撤回”通过再次选择表达。
  4. 不定义多卡嵌套题、跨题依赖和选择有效期。
  5. 不建设跨会话 BI、推荐转化率报表或模型自动训练流水线。
  6. 不改造所有历史 recommend_event 数据,仅保证新链路兼容。

8. 风险

风险点 等级 说明 缓解/验证
ws_manager 单例状态依赖 单进程内存去重或广播状态在重启后丢失 幂等以消息持久化查询为准,内存仅作加速;补充重启回归
多 worker 并发竞态 两个 worker 同时处理同一 UUID,5 秒内可能双写 技术方案必须定义原子去重策略;并发压测验证仅生成一条消息
Dify 反馈链语义变化 新增 feedback_type 后,旧工作流节点可能忽略或误用字段 字段向后兼容、灰度开启;验证普通文本和选项两条链
recommend_event 兼容性 现有智能推荐事件仍可能依赖旧 payload 或 label 保留旧必要字段,新增字段只增不删;覆盖单卡、多卡与转人工回归

9. 变更记录

版本 日期 变更内容 变更人 变更原因
v1.0 2026-07-29 创建 PRD,固化方案 A、6 项产品决策与 MVP 验收范围 许清楚、宋献 修复选项选择不落库导致的数据完整性问题