feat(assistant): unify confirmed memory capture

This commit is contained in:
2026-07-20 02:30:06 +08:00
parent 46a6156d8c
commit 0a5fc9a463
32 changed files with 949 additions and 242 deletions
+56 -38
View File
@@ -1,6 +1,6 @@
# 员工个人 AI 助理 / 个人知识空间专项 TechSpec
> 版本:v1.1 | 日期:2026-07-19
> 版本:v1.2 | 日期:2026-07-19
>
> 归属:《银城员工端 APP 分阶段实施总纲》阶段二
>
@@ -9,6 +9,10 @@
> 实施边界:首批交付个人资料收藏、检索、带引用问答、工作整理,以及“识别候选记忆 → 用户确认 → 分域持久化 → 可追溯召回”闭环;PPT 生成为 P1;北森、考勤等外部数据归阶段三。
>
> v1.1 需求补充:客户明确提出,员工在对话中描述住户需求、待跟进事项或个人经验时,个人助理应识别值得长期保留的信息,返回可编辑确认卡;仅在用户确认后写入对应记忆域,后续问答可按权限召回。模型不得直接修改数据库,提醒必须单独确认。
>
> v1.2 实施收口:显式“记一下 / 帮我记 / 保存一下”统一生成 `ASSISTANT_CAPTURE` 确认卡;楼栋、单元、房号和项目均为可补充信息,不再阻断保存。用户必须选择“仅自己保存 / 提交公司处理”;公司处理首批只可靠写入待流转队列,未配置正式接收接口前不得显示“已送达”。音色不在本次范围。
当前代码状态(2026-07-20):候选检测、六类系统建议、同会话合并、可编辑确认、PRIVATE/COMPANY 持久化、可选跟进时间、“我的资料与记忆”列表,以及确认记录按 owner/项目权限参与后续问答召回均已实现。召回引用明确区分 `PERSONAL / COMPANY / PROJECT_SERVICE`。外部线索/工单投递 Worker、正式回执、统一采集转工作上报确认卡,以及个人文件/网页知识空间仍是后续工作,开发者不得从 `PENDING` 状态推断这些能力已完成。
## 0. 交互设计基准
@@ -22,8 +26,8 @@
2. 工作助手自动在已授权的个人资料、项目记录和企业知识中路由;首屏主路径不要求一线员工选择资料域,高级筛选只能放入次级入口。
3. 会话保持语音优先:输入区默认“按住说话”,键盘为切换能力;师傅回复优先显示语音条,正文可通过“转文字”展开。
4. ASR 转写以普通用户消息进入同一会话;候选检测不得绕过当前会话、登录态或项目权限。
5. 确认卡至少展示类型、对象、事项、保存位置、可见范围、明确回访日期和“加入跟进提醒”选项。
6. “修改 / 暂不保存 / 确认保存”必须是三个独立动作;只有“确认保存”可以触发长期持久化,保存和提醒分别确认。
5. 确认卡至少展示系统建议类型、对象、事项、可见范围、明确回访日期和“加入跟进提醒”选项;缺少房号等信息只作弱提示。
6. `ASSISTANT_CAPTURE` 卡必须提供“修改 / 暂不保存 / 仅自己保存 / 提交公司处理”四个明确动作;旧项目服务卡继续保留“确认保存”。保存和提醒分别确认。
7. 回答或保存结果只展示实际使用的来源,不得暗示已查询未命中的资料,更不得自动把内部内容转发到全网。
若设计图中的示例文案、间距或字段与本文的数据权限、安全、状态机或 API 约束冲突,以本文约束为准;视觉实现应继续复用现有 `ChatComposer`、女性数字师傅头像和「今日 / 练 / 问 / 我」底栏。
@@ -40,8 +44,8 @@
4. 默认由“工作助手”在个人资料、项目业务记忆和企业知识之间自动路由并展示实际来源;资料范围只作为次级高级筛选。
5. 输出结论、行动项和待确认事项,并逐条展示引用来源。
6. 删除个人资料时同步清理关系库、对象存储和向量索引。
7. 从文字或 ASR 转写中识别“住户需求、待跟进事项、个人经验”三类候选记忆,返回可编辑确认卡。
8. 用户确认后,将个人经验写入本人私有空间,将住户需求与跟进事项写入项目业务记忆;两域分别鉴权和检索。
7. 从文字或 ASR 转写中识别服务线索、住户画像、优秀案例、个人想法、待跟进事项和工作记录,返回可编辑确认卡。
8. 用户确认保存范围:`PRIVATE` 仅本人可见;`COMPANY` 进入企业待流转队列。项目服务旧记录继续按项目权限独立保存和检索。
9. 后续问答可召回已确认记忆,并展示来源消息、记录时间、当前状态和可见范围。
10. 对确认后的修改保留版本历史;提醒时间与长期保存分别确认。
@@ -55,6 +59,7 @@
- 不复用企业知识表增加 `scope_type` 来承载个人数据。
- 不把普通聊天历史自动升级为长期记忆,不在用户确认前写入个人或项目长期记忆表。
- 不允许 LLM 直接执行 SQL、更新业务实体、创建提醒或对外发送消息;模型只生成结构化候选草稿。
- 不把 `COMPANY/PENDING` 描述成已生成工单、已上报或已送达;正式接收端、认证和回执协议确定后才启用投递 Worker。
- 不把住户服务事实只存入员工私人空间;这类记录属于项目业务域,员工调岗后仍按项目权限保留。
- 阶段二首批不替代权威房屋、住户、工单或 CRM 主数据;未接入权威主数据时不得伪造住户身份。
@@ -71,6 +76,7 @@
| 向量检索 | 新建 `aihr_personal_knowledge` collection | Qdrant filter 必须包含 `tenant_id + owner_user_id` |
| 企业知识检索 | 调用现有企业 RAG 服务 | 继续执行企业知识密级、项目和角色权限 |
| 项目业务记忆 | 独立 `aihr_service_memory` 关系表;关系库检索为 2026-07-19 最小切片,Qdrant 仅列为后续增强 | 查询必须包含 `tenant_id + project_code`,不得写入个人或企业知识表 |
| 对话统一采集 | `aihr_memory_candidate` 暂存确认卡,确认后写 `aihr_assistant_capture` | `PRIVATE` 仅按 owner 查询;`COMPANY` 以 outbox 状态流转,不允许前端伪造已送达 |
禁止将个人资料写入:
@@ -146,13 +152,12 @@ org.dromara.aihr.knowledge.parse
文字输入 / 语音 ASR 转写
→ 规则预筛(明确“记一下”等意图,或同时出现对象 + 需求/跟进事实)
→ 服务端先提取并令牌化楼栋、单元、房号、姓名、电话等标识
→ LLM 仅输出符合 JSON Schema 的候选类型、摘要、缺失字段和置信度
→ 服务端恢复标识并校验当前租户、项目、字段完整性、重复记录和权限
→ 信息不足:在会话中追问,不允许确认
→ 信息完整:持久化 DRAFT 候选并返回可编辑确认卡
→ 用户修改并确认(携带幂等键和 expectedVersion)
→ 确定性 Service 按 PERSONAL / PROJECT_SERVICE 分域写关系库
→ 事务提交后异步写检索索引
→ 当前 P0 用确定性规则建议类型和分类;后续模型只能输出符合 JSON Schema 的候选建议
→ 服务端校验当前租户、项目、重复记录、权限和敏感字段
→ 显式“记一下”始终持久化 DRAFT;项目/楼栋/单元/房号缺失只列为可选补充项
→ 用户修改并选择 PRIVATE / COMPANY(携带幂等键和 expectedVersion)
→ 确定性 Service 写 `aihr_assistant_capture`;PRIVATE 记为 NOT_REQUIRED,COMPANY 记为 PENDING
→ 正式企业接收端接入后,由独立投递 Worker 消费 PENDING 并记录回执;当前不得假投递
→ 如用户另行确认 followUpAt,则进入“今日安排”;不默认创建提醒
```
@@ -161,7 +166,7 @@ org.dromara.aihr.knowledge.parse
1. 候选检测可自动发生,但任何置信度都不得跳过用户确认。
2. DRAFT 候选只为跨刷新恢复确认卡,不进入检索和回答;忽略或过期后清理草稿内容。
3. 同一来源消息和候选哈希在有效期内只展示一次,避免重复打扰。
4. 当前仓库没有权威房屋/住户主数据。首批允许保存经员工确认的项目、楼栋、单元、房号定位;声称“某位住户”但缺少可区分房号或权威主体 ID 时必须追问。
4. 当前仓库没有权威房屋/住户主数据。首批允许保存经员工确认的可选项目、楼栋、单元、房号定位;缺少这些字段仍可保存,但不得据此虚构或合并住户身份。
5. 关系数据库是事实源,向量索引只负责召回;索引写入失败不得丢失已确认记录,可重试重建。
## 3. 身份、权限与数据隔离
@@ -308,8 +313,8 @@ Message 保存:
| tenant_id/owner_user_id/project_code | varchar | 当前身份与候选项目范围;项目键使用现有组织快照的 `project_code`,不是自造数值 ID |
| source_conversation_id/source_request_id | varchar(64) | 来源短会话与本次问答请求;当前「问」链没有持久化消息 ID |
| candidate_hash | varchar(64) | 来源 + 规范化草稿哈希,用于防重复 |
| memory_type | varchar(30) | PERSONAL_NOTE / WORK_IDEA / RESIDENT_NEED / FOLLOW_UP |
| target_domain | varchar(30) | PERSONAL / PROJECT_SERVICE |
| memory_type | varchar(30) | SERVICE_LEAD / RESIDENT_PROFILE / CASE / PERSONAL_NOTE / FOLLOW_UP / PROJECT_NOTE |
| target_domain | varchar(30) | 新候选为 ASSISTANT_CAPTURE;确认后记录结果映射为 PERSONAL / COMPANY;旧记录兼容 PROJECT_SERVICE |
| status | varchar(20) | NEEDS_INPUT / DRAFT / CONFIRMED / DISMISSED / EXPIRED |
| draft_json | json | 可编辑结构化草稿;不得包含未脱敏模型原始响应 |
| missing_fields_json | json | 仍需追问的字段 |
@@ -322,7 +327,16 @@ Message 保存:
约束:`UNIQUE(tenant_id, owner_user_id, source_request_id, candidate_hash)`;DRAFT 默认 24 小时过期,DISMISSED/EXPIRED 的 `draft_json` 在 24 小时内物理清理。
### 4.8 `aihr_service_memory`
### 4.8 `aihr_assistant_capture`
用户确认后的统一采集事实源。核心字段为 `tenant_id/owner_user_id/project_code/save_scope/suggested_type/building_name/unit_name/room_no/category/summary/detail/occurred_at/follow_up_at/source_conversation_id/source_request_id/delivery_status/delivery_attempts/next_delivery_at/external_record_id/last_error/confirm_idempotency_key/confirm_request_hash/version/create_time/update_time`。
- `save_scope=PRIVATE` 时 `delivery_status=NOT_REQUIRED`,查询必须同时带 `tenant_id + owner_user_id`。
- `save_scope=COMPANY` 时首批写 `delivery_status=PENDING`;`DELIVERED` 只能由拿到外部成功回执的投递 Worker 更新。
- `confirm_idempotency_key` 在租户和 owner 范围唯一,确认事务先写采集记录再关闭候选,任一步失败整体回滚。
- 表内不保存原始录音、模型原始输出或伪造的住户主体 ID;音色不属于本表和本次实现。
### 4.9 `aihr_service_memory`
项目共享的住户需求和待跟进事实,不放入个人知识表。
@@ -345,7 +359,7 @@ Message 保存:
索引至少包含 `(tenant_id, project_code, status, follow_up_at)`、`(tenant_id, project_code, building_name, unit_name, room_no)`;首批关系库检索覆盖 `title/summary/detail`,后续若启用向量检索,payload 强制包含 `tenant_id + project_code + memory_id + status`。
### 4.9 `aihr_service_memory_version`
### 4.10 `aihr_service_memory_version`
每次确认修改、状态变更或作废都追加版本:`memory_id/version/snapshot_json/change_type/change_reason/operator_user_id/create_time`。快照只保存业务字段,不复制姓名、电话等无关标识;`UNIQUE(memory_id, version)`。
@@ -354,7 +368,7 @@ Message 保存:
### 5.1 Qdrant
- 完整阶段目标:个人资料使用 `aihr_personal_knowledge` collection;项目业务记忆使用独立 `aihr_service_memory` collection,两者不混 collection。
- 2026-07-19 项目服务记忆最小切片只使用 MySQL 关系库,不创建新的 Qdrant collection;不得因未做向量化而回退到无权限过滤的企业 collection。
- 确认式统一采集和兼容项目服务记录只使用 MySQL 关系库,不创建新的 Qdrant collection;不得因未做向量化而回退到无权限过滤的企业 collection。
- 向量维度:跟随当前启用的 vector 模型;模型或维度变化使用独立重建任务。
- 个人 payload:`tenant_id`、`owner_user_id`、`space_id`、`item_id`、`fragment_id`、`source_type`、`captured_at`;必建 index 为 `tenant_id`、`owner_user_id`、`item_id`。
- 项目业务记忆后续 payload:`tenant_id`、`project_code`、`memory_id`、`status`、`occurred_at`、`updated_at`;必建 index 为 `tenant_id`、`project_code`、`memory_id`、`status`。
@@ -498,6 +512,7 @@ Message 保存:
| GET | `/memory-candidates?status=DRAFT` | 恢复当前用户尚未过期的确认卡 |
| POST | `/memory-candidates/{id}/confirm` | 提交编辑后的草稿、`expectedVersion`、`idempotencyKey`;分域写入 |
| POST | `/memory-candidates/{id}/dismiss` | 明确不保存;同源候选本会话不再提示 |
| GET | `/assistant-captures?saveScope=PRIVATE|COMPANY&limit=50` | 查看本人的已确认记录及公司流转状态 |
| GET | `/api/aihr/service-memories` | 按当前项目、状态、日期、楼栋/单元/房号查询 |
| GET | `/api/aihr/service-memories/{id}` | 项目权限内查看详情、来源和版本 |
| PATCH | `/api/aihr/service-memories/{id}` | 携带 `expectedVersion` 修改必要字段或状态并生成版本 |
@@ -508,22 +523,22 @@ Message 保存:
{
"idempotencyKey": "01J-memory-confirm-301",
"expectedVersion": 1,
"targetDomain": "PROJECT_SERVICE",
"memoryType": "RESIDENT_NEED",
"subject": {
"saveScope": "COMPANY",
"draft": {
"projectCode": "YC-RESIDENTIAL-03",
"buildingName": "3栋",
"unitName": "2单元",
"roomNo": "1201"
"roomNo": "1201",
"category": "服务线索",
"summary": "住户希望了解社区可提供的养老服务",
"occurredAt": "2026-07-19T14:00:00+08:00",
"followUpAt": null
},
"category": "养老服务咨询",
"summary": "住户希望了解社区可提供的养老服务",
"occurredAt": "2026-07-19T14:00:00+08:00",
"followUpAt": null
"enableReminder": false
}
```
服务端必须忽略请求中的 `projectCode` 作为授权依据,仅用它与当前登录态可访问项目做一致性校验;客户端可省略该字段。确认成功返回 `targetDomain/targetId/version`;同一 `idempotencyKey` 重放必须返回同一结果,不重复创建记录,同一键配不同请求体必须拒绝。
服务端必须忽略请求中的 `projectCode` 作为授权依据,仅用它与当前登录态可访问项目做一致性校验;客户端可省略该字段。确认成功返回 `targetDomain=PERSONAL|COMPANY/targetId/version`;同一 `idempotencyKey` 重放必须返回同一结果,不重复创建记录,同一键配不同请求体必须拒绝。
## 7. 网页采集安全
@@ -595,11 +610,12 @@ Message 保存:
### 9.3 候选与业务记忆生命周期
- NEEDS_INPUT/DRAFT 候选默认 24 小时过期,不进入任何检索索引。
- DRAFT 候选默认 24 小时过期,不进入任何检索索引;NEEDS_INPUT 仅兼容旧候选。
- DISMISSED/EXPIRED 候选的结构化草稿在 24 小时内清理,只保留候选 ID、状态、来源消息 ID 和时间戳用于防重复。
- 个人记忆沿用个人 item 删除链路。
- 项目业务记忆不因创建员工离职而删除;按项目业务保留策略管理,首批只允许 `VOID` 作废并保留版本。
- `follow_up_at` 到期只进入员工端“今日安排”查询;推送通知、短信或自动派单不在首批范围。
- 统一采集 `PRIVATE` 记录只按 owner 保存;`COMPANY/PENDING` 保留到企业流转成功或人工关闭,失败只更新重试状态,不丢记录。
## 10. 前端设计
@@ -629,8 +645,8 @@ src/services/personal-assistant.ts API 客户端
- “资料范围”是次级设置而非一级 Tab;手动模式才允许选择个人资料、项目记录或企业知识。
- “查全网”保持独立入口和接口。内部资料无命中时只显示“是否查全网”,必须由用户点击后才进入外部查询;包含住户或项目内部信息时不得自动带入原问题。
- 每条用户文字或 ASR 转写完成后可触发候选检测;普通问答不得频繁弹卡,同源候选被忽略后本会话不再提示。
- 确认卡必须展示类型、对象定位、摘要、保存范围、记录时间和可选跟进时间,并提供“修改 / 暂不保存 / 确认保存”。
- 信息不足时显示追问,不展示可确认按钮;“某位住户”但无法唯一定位时不得保存为 RESIDENT。
- 统一采集确认卡必须展示系统建议类型、对象定位、摘要、保存范围、记录时间和可选跟进时间,并提供“修改 / 暂不保存 / 仅自己保存 / 提交公司处理”。
- 项目、楼栋、单元或房号不足时只显示“可继续补充”,不阻断保存;未接入权威住户主数据时不得把模糊描述保存成确定身份。
- 保存与提醒是两个独立选择;未填写 `followUpAt` 不创建提醒或今日任务。
- 项目业务记忆引用必须展示来源时间、当前状态和最后更新时间;历史状态不能冒充当前事实。
- 删除前明确提示会删除附件、解析正文和搜索索引。
@@ -649,7 +665,7 @@ src/services/personal-assistant.ts API 客户端
| `PERSONAL_NO_CITATION` | 无足够资料支撑 | 不生成业务结论 |
| `ENTERPRISE_SCOPE_FORBIDDEN` | 无企业知识范围权限 | 保留个人结果,提示企业范围不可用 |
| `PERSONAL_AI_DISABLED` | AI 成本闸门关闭 | 仍允许收藏和关键词检索,暂停摘要/问答 |
| `MEMORY_CANDIDATE_NEEDS_INPUT` | 对象或必要字段不完整 | 继续追问,不展示确认按钮 |
| `MEMORY_CANDIDATE_NEEDS_INPUT` | 旧候选对象或必要字段不完整 | 兼容展示为可编辑卡;统一采集的新 DRAFT 不以位置缺失阻断保存 |
| `MEMORY_CANDIDATE_EXPIRED` | 确认草稿已过期 | 基于原消息重新识别,不直接写入 |
| `MEMORY_CONFIRM_CONFLICT` | 版本冲突或幂等键对应不同内容 | 刷新确认卡,禁止覆盖 |
| `SERVICE_MEMORY_PROJECT_FORBIDDEN` | 当前用户无项目访问权 | 统一显示不可访问,不泄露记录存在性 |
@@ -669,8 +685,10 @@ src/services/personal-assistant.ts API 客户端
- prompt injection 文本不得改变系统指令。
- 规则预筛不会把普通问答误判为候选;明确“记一下”必定进入候选检测。
- 模型候选输出不能覆盖服务端解析的 tenant、project、楼栋、单元和房号。
- NEEDS_INPUT、DRAFT、CONFIRMED、DISMISSED、EXPIRED 状态转换只允许规定方向。
- DRAFT、CONFIRMED、DISMISSED、EXPIRED 状态转换只允许规定方向;NEEDS_INPUT 仅兼容旧候选。
- 同一确认幂等键只生成一个目标记录;版本冲突不覆盖较新业务记忆。
- 裸房号只在楼栋/单元上下文后识别,年份、时长、金额不能误判为房号。
- 相同会话中重复表达同一显式记录更新原卡;“另外记 / 再记一条 / 新的一条”生成新卡。
### 12.2 集成测试
@@ -694,8 +712,8 @@ src/services/personal-assistant.ts API 客户端
6. 检查引用徽标和原始来源。
7. 删除资料并确认列表、问答和下载均不可再访问。
8. 切换另一手机号,确认看不到前一用户的资料、标题和会话。
9. 语音描述一条住户需求,检查信息不足时先追问,字段完整后出现确认卡。
10. 分别执行“暂不保存”和“确认保存”,验证前者不进入检索,后者可按楼栋/日期召回并显示来源、状态和更新时间。
9. 语音说“帮我记一下三栋 3203 需要保洁服务”,检查缺少单元时仍出现可确认卡且只作可选提示。
10. 分别执行“暂不保存 / 仅自己保存 / 提交公司处理”,验证前者不落长期表、私有记录仅本人可见、公司记录显示 PENDING 而非已送达。
11. 确认时不设置跟进日期,验证不会生成今日任务;另建一条带 `followUpAt` 的记录,验证只出现一次。
12. 双击确认或重放请求,验证只创建一条记录;修改后验证版本递增且旧状态不被当作当前答案。
@@ -762,7 +780,7 @@ src/services/personal-assistant.ts API 客户端
- `backend/script/sql/aihr_personal_knowledge_mysql8.sql`
- `backend/script/sql/aihr_service_memory_mysql8.sql`
- `backend/script/sql/update/aihr_20260719_service_memory_mysql8.sql`(项目服务记忆最小切片生产增量)
- `backend/script/sql/update/aihr_20260719_service_memory_mysql8.sql`(确认候选、统一采集和兼容项目服务记录的生产增量)
- `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/**`
- `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/memory/**`(候选识别、确认编排与项目业务记忆)
- `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/**`
@@ -792,10 +810,10 @@ src/services/personal-assistant.ts API 客户端
5. 个人资料默认私有,分享和企业入库走 P1 审核链。
6. PPT 是 P1,不阻塞阶段二首批。
7. 北森、考勤等外部个人数据属于阶段三。
8. 对话历史不是长期记忆;只有用户确认的候选才能进入个人或项目业务记忆。
9. 个人经验写个人域,住户需求和跟进事项写项目业务域,不因创建员工调岗而丢失。
8. 对话历史不是长期记忆;只有用户确认的候选才能进入统一采集或兼容项目业务记忆。
9. 统一采集由用户选择 PRIVATE / COMPANY;COMPANY 先进入可靠待流转状态,不能用前端成功提示代替外部回执。
10. 模型只生成结构化草稿,主体解析、授权、幂等、版本和数据库写入全部由确定性服务完成。
11. 保存和提醒分别确认;未设置 `followUpAt` 不产生今日任务、推送或自动派单。
12. 当前没有权威住户主数据时不伪造主体 ID;无法唯一定位的住户信息必须先追问。
12. 当前没有权威住户主数据时不伪造主体 ID;无法唯一定位不阻断记录一般事项,但不得将其解释为确定住户档案。
13. 用户界面不暴露系统的三域架构作为一级选择;一级只保留“工作助手 / 查全网”,内部来源默认自动路由并以引用透明展示。
14. 公网查询永不由 AUTO 触发;必须由用户明确进入“查全网”,且不得自动携带住户或项目内部信息。