feat(aihr): add owner identity profiles for realtime practice
- remove owner-impatient persona; owner-calm renamed to 业主
- aihr_realtime_owner_profile table (tenant+project unique, enabled,
version) with formal migration
- admin CRUD under moderator role gate; project code validated against
org snapshot and project name taken from snapshot authoritatively
- employee endpoint returns display-safe fields plus defaultProfileId
from the employee's own community; profile body never exposed
- session creation renders {community}/{profile} server-side; client
text can never enter instructions; invalid profileId rejected,
default resolution fails closed
- duplicate create returns 409
This commit is contained in:
@@ -15,7 +15,8 @@
|
||||
| M2 训练预习与求助 | `GET /api/train/practice/scenarios/{id}/prep-card`、`POST /api/train/practice/sessions/{sessionId}/help` | 均要求已登录;预习卡只使用当前租户已启用的 `prep_card/json_prep` 模板,模型返回必须严格为 3 条要点、3 条红线、2 条话术,否则回退场景卡。求助接口只允许 APP 员工访问其本人、同租户的活动训练会话;客户端可传 `roundIndex` 仅为兼容字段,服务端按会话实际进度落库,返回 `{sessionId,scenarioId,roundIndex,recordedAt}`。迁移表为 `aihr_practice_help_event`,不在请求路径执行 DDL。该表已随 2026-07-24 完整包发布并纳入远端 `62/62` 结构预检;完整训练写入与正式试点仍需另验。 |
|
||||
| 正式试点数据导出 | `GET /api/train/practice/export?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD` | 起止日期必填且包含结束日;只统计窗口内能通过唯一手机号或外部 ID 映射到在职组织快照的正式会话,排除重复手机号和身份碰撞。完训定义为每人至少 10 次,校准必须关联同一窗口内正式会话;CSV 同时给出校准命中数、SOP 可用数、满意度响应数/平均分,以及明细级 AI 分、人工校准分、校准人、校准时间、最终采用分、满意度分和意见,避免用四舍五入后的比率反推门禁状态;汇总和明细均携带正式人员及项目口径,不混入历史 seed/开发身份 |
|
||||
| 对练语音 | `POST /api/ai/asr`(multipart 字段 `file`,≤5MB)、`POST /api/ai/tts`(JSON `{text≤300字, voice?, voiceProfile?:{role,voice?,speed?,emotion?,dialect?}, practiceContext?:{sessionId,turnIndex,role:'customer'}}`,`dialect` 仅接受 `mandarin/cantonese/sichuanese`,非法值返回业务码 `400`;旧 `voice` 兼容;成功返回 `ossId`,客户端播放地址为受控的内联 `data:audio/*`,不返回原始 OSS URL)、`GET /api/aihr/mobile/oss/{ossId}` | ASR 按供应商分流:SiliconFlow 等 OpenAI audio 兼容服务继续走 `/audio/transcriptions` multipart;阿里云百炼 `dashscope/qianwen + qwen3-asr-flash` 走工作空间 `/compatible-mode/v1/chat/completions`,将短音频编码为 Base64 `input_audio` 并读取 `choices[0].message.content`,请求上限仍由本接口收窄为 5MB。生产 TTS 优先阿里 `qwen-audio-3.0-tts-flash`,旧 SiliconFlow CosyVoice2 保留兼容;按角色映射老师傅/业主/面试官音色,业主对练再按已有情绪分切换平静/严肃/强烈语气和语速;dialect 作为语气提示传给支持表现力提示词的 TTS,不含克隆,也不等同于方言正式验收。`practiceContext` 仅允许已认证 APP 员工把本次服务端生成的业主 TTS 绑定到本人当前或刚完成会话的指定原话术回合;服务端重新校验租户、员工身份、回合和文本,不接受员工端传 OSS ID 或任意角色。设备语音降级按方言选择语言:粤语使用 `zh-HK`,四川话无法由设备语音可靠模拟时明确提示用户转用服务端语音或文字,不伪装成普通话。模型管理需启用 `category=asr/tts` 配置;移动端优先用浏览器录音,`getUserMedia/MediaRecorder` 不可用或麦克风权限失败时,用 `audio/*` file input 选择/录制音频后继续调同一 ASR 接口;ASR/TTS 未配置或失败返回 fail,前端降级设备语音或文本;训练/每日题录音只通过 `audioOssId` 走受保护下载,历史客户端传入的 HTTP `audioUrl` 不再回显;TTS 成功音频仍写入 `sys_oss` 留痕,同时用内联 data URL 保持旧客户端可播放;移动端 OSS 下载只允许经本人或主管项目范围验证的 APP 身份,后台账号若需回放必须使用未来独立、受审计的管理端契约。 |
|
||||
| 实时对练 Beta(WebRTC) | `POST /api/train/practice/realtime/sdp`、`GET /realtime/personas`、`POST /realtime/session`、`POST /realtime/tools/invoke` | H5 浏览器及 App-Plus renderjs WebView 内的 WebRTC 直连阿里云(音频不过服务端),后端仅代理 SDP 交换(3 次/分/账号,sessionId 仅作日志关联且限字符)。人设为服务端 `AihrRealtimePersonaRegistry` 白名单:`owner-calm` 业主·常规(默认)、`owner-impatient` 业主·急躁、`digital-mentor` 数字师傅;人设绑定默认音色,客户端只选不编。`session.update`(instructions/音色/turn_detection/tools 声明)由 `/realtime/session` 组装下发,客户端原样转发;personaId 空白回落默认人设;会话记录写 Redis 30min(成功工具调用滑动续期),创建限流 6 次/分/账号。`digital-mentor` 声明 `search_knowledge` 工具:模型 function calling 命中(`response.function_call_arguments.done`)时客户端回传 `/realtime/tools/invoke`,服务端按登录态 + Redis 会话归属 + 人设工具白名单三重校验后走 `searchAuthorized` 浅层检索(不生成 LLM 答案、不落 `aihr_agent_run`;`source=realtime` 跳过 SOP 评审与知识缺口写入),output 拼接 ≤3 条片段并截断 1200 字;工具按账号限流 20 次/分,无授权/无结果/超时一律返回软着陆文案让模型如实作答,不中断语音会话。实时 Beta 不计分、不持久化训练数据;H5 与 App-Plus 均支持,App-Plus 在 renderjs 视图层执行媒体/WebRTC 并通过逻辑层复用受认证 SDP 与工具接口;需 APP 员工登录态,不启用模型 `enable_search`。配置 `AIHR_QWEN_REALTIME_ENDPOINT` / `AIHR_QWEN_REALTIME_API_KEY` / `AIHR_QWEN_REALTIME_MODEL`(模型白名单 `qwen3.5-omni-(flash|plus)-realtime`),密钥只放 `.env.local` 或外部环境变量。 |
|
||||
| 实时对练 Beta(WebRTC) | `POST /api/train/practice/realtime/sdp`、`GET /realtime/personas`、`POST /realtime/session`、`POST /realtime/tools/invoke` | H5 浏览器及 App-Plus renderjs WebView 内的 WebRTC 直连阿里云(音频不过服务端),后端仅代理 SDP 交换(3 次/分/账号,sessionId 仅作日志关联且限字符)。人设为服务端 `AihrRealtimePersonaRegistry` 白名单:`owner-calm` 业主(默认)、`digital-mentor` 数字师傅;人设绑定默认音色,客户端只选不编。`session.update`(instructions/音色/turn_detection/tools 声明)由 `/realtime/session` 组装下发,客户端原样转发;personaId 空白回落默认人设;会话记录写 Redis 30min(成功工具调用滑动续期),创建限流 6 次/分/账号。`digital-mentor` 声明 `search_knowledge` 工具:模型 function calling 命中(`response.function_call_arguments.done`)时客户端回传 `/realtime/tools/invoke`,服务端按登录态 + Redis 会话归属 + 人设工具白名单三重校验后走 `searchAuthorized` 浅层检索(不生成 LLM 答案、不落 `aihr_agent_run`;`source=realtime` 跳过 SOP 评审与知识缺口写入),output 拼接 ≤3 条片段并截断 1200 字;工具按账号限流 20 次/分,无授权/无结果/超时一律返回软着陆文案让模型如实作答,不中断语音会话。实时 Beta 不计分、不持久化训练数据;H5 与 App-Plus 均支持,App-Plus 在 renderjs 视图层执行媒体/WebRTC 并通过逻辑层复用受认证 SDP 与工具接口;需 APP 员工登录态,不启用模型 `enable_search`。配置 `AIHR_QWEN_REALTIME_ENDPOINT` / `AIHR_QWEN_REALTIME_API_KEY` / `AIHR_QWEN_REALTIME_MODEL`(模型白名单 `qwen3.5-omni-(flash|plus)-realtime`),密钥只放 `.env.local` 或外部环境变量。 |
|
||||
| 实时对练业主身份档案 | 员工 `GET /api/aihr/practice/realtime/owner-profiles`;管理 `GET/POST /api/aihr/realtime/owner-profiles`、`PUT /api/aihr/realtime/owner-profiles/{id}`、`POST /api/aihr/realtime/owner-profiles/{id}/enable`、`POST /api/aihr/realtime/owner-profiles/{id}/disable`;会话接线 `POST /api/train/practice/realtime/session` 请求体新增可空 `profileId` | 画像属内容运营数据,表 `aihr_realtime_owner_profile`(迁移 `aihr_20260823_realtime_owner_profile_mysql8.sql`,`(tenant_id, project_code)` 唯一),仅 `enabled=1` 对员工可见。管理端角色门按 question-admin 的 moderator 纪律在服务端硬校验;`projectCode` 必须存在于本租户组织快照,`projectName` 由服务端从快照取权威显示名,客户端提交的同名无效;写操作幂等口径为**同 `projectCode` 重复 create 返回 409 冲突**,请改用编辑,编辑版本递增。员工端只下发展示安全字段 `{profileId, projectCode, communityName, label}`(id 序列化为字符串,label 由服务端拼接),**不下发 concerns 或画像正文**,`defaultProfileId` 由服务端按当前员工组织项目匹配,无命中为 null。会话渲染一律服务端完成:显式 `profileId` 校验属本租户且 enabled,非法直接拒绝且不建会话;缺省时按员工组织项目自动匹配默认档案;匹配不到或查询失败 fail-closed 用无画像模板(小区占位回落「本小区」),不报错打断。画像短语(含 concerns)只由服务端按档案字段拼接渲染进 owner 模板的 `{community}`/`{profile}` 占位,**客户端任何文本不得进入 instructions**;实时 Beta 不计分、不持久化、工具白名单与三重校验不变 |
|
||||
| 案例沉淀 `/knowledge/cases` | `GET /api/knowledge/case/capabilities`、`POST /api/knowledge/case/upload`、`/organize`、`/curate`、`GET /records`、`GET /records/{caseId}`、`POST /records/{caseId}/review`、`POST /records/{caseId}/publish`、`POST /records/{caseId}/retire`、`GET /records/{caseId}/media` | `/capabilities` 返回服务端判定的案例提交/查看能力,移动端不再向普通员工展示无权提交的素材表单;`/upload` 改为 multipart 真实语音上传并走 ASR,服务端只接受 MP3/WAV/M4A/WebM/OGG/AAC/FLAC,成功后原始音频写入 `sys_oss`,案例记录只保存 `mediaOssId` 供受保护媒体接口读取,不向客户端回传原始 `mediaUrl`;`/organize` 用真实转写调 chat 模型整理案例,未配置模型时按真实 transcript 本地结构化,并从背景外的真实摘要项提取学习点;`/curate` 只进入经验候选池,`/review` 只完成业务审核,`/publish` 才能在来源授权、脱敏、适用岗位、负责人、版本和生效期齐全时进入正式经验库,`/retire` 要求填写下线原因并立即停止员工召回;APP 用户必须先以认证手机号精确校验在职 `person_phone`,再由可信主体解析 `aihr_org_snapshot` 项目范围;案例 SQL 不得把 `person_phone` 与 `ext_party_id` 作为替代查询键。上传、整理、候选、审核、发布、下线、列表和详情均按该项目范围校验,未完成正式组织映射时安全拒绝,不接受前端伪造项目范围;员工列表/详情/音频只返回 `knowledge_status=PUBLISHED` 且处于生效期的 `已入库` 案例,并叠加岗位门:`applicable_positions` 为空(通用)或包含服务端按认证手机号从 `aihr_org_snapshot` 解析的在职岗位,岗位解析不到时仅通用经验可见;管理端系统用户保留全局运营视图;移动端和管理端案例详情通过受保护媒体接口回放原始音频,主管/项目负责人可提交脱敏点评;预渲染视频样片仍待正式媒体资产接入 |
|
||||
| 案例媒体安全 | `GET /api/knowledge/case/records/{caseId}/media` | 案例详情只返回 `mediaOssId`,不返回原始 `sys_oss.url`;媒体下载会重复执行登录、后台角色或 APP 项目范围校验,再由服务端流式读取 OSS。管理端与 `mobile-uni` 通过鉴权 blob/temp 文件播放,关闭详情页时释放本地对象 URL |
|
||||
| 问·数字师傅工作 Agent `/pages/user/sop/index` | `POST /api/aihr/agent/messages`、`POST /api/aihr/agent/messages/media`、`POST /api/aihr/agent/actions/{draftId}/confirm`、`POST /api/aihr/agent/actions/{draftId}/dismiss` | 移动端文字、ASR 转写和图片/视频统一进入 Agent。服务端根据消息规划 `KNOWLEDGE_QA/RESOURCE_DELIVERY/LIVE_MY_WORK/LIVE_TEAM_WORK/PRACTICE_COACHING/CAPTURE_FACT/MEDIA_UNDERSTANDING/WEB_RESEARCH/CLARIFY/SOCIAL`,再由固定策略校验当前登录身份和工具权限;客户端不提交 `toolCode`,模型也不能自由指定身份、SQL、URL 或任意工具。知识、训练概况、当前待办、确认式记忆、媒体分析和全网查询均复用现有领域服务;普通图片问答只做本次视觉分析,不要求先具备知识空间,用户明确问现场制度/流程时才校验授权空间并补充 SOP。全网查询先返回 `NEEDS_INPUT`,只有用户明确同意后才调用外部服务。统一响应使用结构化 `status/sourceSummary/citations/resources/actionDraft/clarification`;按钮不从答案文本推断。写入动作只接受 30 分钟有效的不透明 `draftId`,最终确认继续复用既有版本、幂等与可见性规则。每次运行只审计租户、主体、意图、工具、状态、来源类型、耗时和错误码,不保存原始问题、答案或附件。 |
|
||||
|
||||
@@ -186,6 +186,7 @@ mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aih
|
||||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260820_knowledge_migration_orchestration_mysql8.sql
|
||||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260821_knowledge_shadow_gate_mysql8.sql
|
||||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260822_case_source_ref_and_best_answer_index_mysql8.sql
|
||||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260823_realtime_owner_profile_mysql8.sql
|
||||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/aihr_personal_knowledge_mysql8.sql
|
||||
```
|
||||
|
||||
@@ -197,6 +198,8 @@ mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/aihr_perso
|
||||
|
||||
`aihr_20260822_case_source_ref_and_best_answer_index_mysql8.sql` 位于全部知识生命周期迁移之后:为 `aihr_case_record` 增加 `source_ref` 来源引用列和 `(tenant_id, source_type, source_ref)` 唯一键,支撑“最佳答案转经验候选”的幂等追溯;为 `aihr_community_answer` 增加 `(tenant_id, is_best, answer_status)` 索引以服务问模块最佳答案只读检索。脚本只加列和索引,不批准、不发布、不改写任何既有案例或回答。
|
||||
|
||||
`aihr_20260823_realtime_owner_profile_mysql8.sql` 紧随 20260822 之后:新建 `aihr_realtime_owner_profile` 实时陪练业主身份档案表(`(tenant_id, project_code)` 唯一键)。画像属内容运营数据,脚本只建空表,不写入、不启用任何画像;档案由管理端 moderator 逐项目维护,仅 `enabled=1` 对员工可见并可渲染进实时会话。
|
||||
|
||||
生产物理删除不是部署步骤,也不得与 schema/JAR 发布同窗口批量执行。日常纠错只做 `withdraw`;`purge` 必须有业务/法务确认的保留分类、影响快照、异人批准和已到期 `execute_after`。活动 generation 仍包含该资产时,先构建并启用排除该资产的新 generation,系统不会允许直接清理;清理成功后,受影响的非活动代次会被自动重算并标记失效,不能再作为回滚目标。发布后先运行 reconciliation 并处理不一致,再考虑清理。ENFORCED 启用后旧 generation 保留七天;窗口内指标恶化且旧代次未失效时可从管理端回退,超过窗口或旧代次已失效时必须重新构建候选 generation 和重新通过门禁。
|
||||
|
||||
8 月 1 日受控内部试点的正式来源注册是独立的业务数据变更,不随通用 schema 自动执行。先逐一核对附件 `id + doc_id`、原文件 SHA-256、发文号/版本、生效日期和适用范围,再由已授权负责人执行 `backend/script/sql/ops/aihr_20260730_aug1_formal_source_registry_mysql8.sql`。执行后必须只读确认恰好 10 条 `FORMAL_POLICY + APPROVED` 记录;任何缺失都保持无正式依据,不得把访谈、经验萃取、AI 生成内容或草稿补进白名单。
|
||||
@@ -235,6 +238,7 @@ WHERE table_schema = DATABASE()
|
||||
'aihr_work_report', 'aihr_broadcast_message', 'aihr_broadcast_read', 'aihr_broadcast_version',
|
||||
'aihr_broadcast_target_rule', 'aihr_broadcast_target_recipient', 'aihr_broadcast_attachment',
|
||||
'aihr_direct_feedback',
|
||||
'aihr_realtime_owner_profile',
|
||||
'aihr_personal_space', 'aihr_personal_item', 'aihr_personal_fragment',
|
||||
'aihr_personal_chat_session', 'aihr_personal_chat_message', 'aihr_personal_cleanup_job',
|
||||
'aihr_personal_ocr_job', 'aihr_personal_ocr_page', 'aihr_personal_export_task',
|
||||
|
||||
Reference in New Issue
Block a user