2026-08-23-coach-butler-design.md 14 KB

AI 健康教练人格分化 与 管家自助选择 — 设计规格

状态:待用户审查 · 日期:2026-08-23 · 作者:Sisyphus (brainstorming 产出) 关联:docs/mascot-design.md(IP 官方命名)、docs/function-analysis-by-membership.md、会员体系 docs/superpowers/specs/2026-07-09-family-membership-design.md

一、背景与目标

两条需求线:

  1. AI 健康教练人格化:普通家庭用户的 AI 健康教练提供两位人格可选——浠宝(温暖知心)/福宝(活泼行动派),话术差异化、共享健康知识库。
  2. 管家自助选择:L2(久久一生)订阅家庭除 AI 教练外享一对一真人管家,可浏览已认证管家并自助绑定/更换;Web 管理端增量补齐分配管理与停接开关。

二、已确认决策(澄清问答记录)

# 决策点 结论
1 管家选择模式 自助直选:浏览→直接绑定,系统校验等级+容量,可随时更换;无推送通知
2 高级档位判定 仅 L2 有效订阅家庭可自选管家;L1 仅 AI 教练
3 人格差异方式 性格话术差异化:独立 system prompt,共享知识库与工具链
4 选择粒度 用户级偏好(users.mascot 已按 user 隔离)
5 管理端范围 增量补齐:分配管理视图 + 强制解绑 + 管家停接新单开关;保留现有审核/结算
6 存储方案修订 初选"新建 coach_personas 表",发现现有吉祥物管线后改为复用(users.mascot + MascotEnum + system_prompts),不建重复表
7 命名 维持现名浠宝/福宝(IP 设计书 v2.2 官方命名),code 保持 xibao/fubao

三、现状盘点(复用资产清单)

吉祥物管线(已存在,覆盖教练需求约 80%)

资产 说明
DB users.mascot VARCHAR(20) 用户级形象选择,值 xibao/fubao,schema.sql:34
Java 枚举 MascotEnum(enums/MascotEnum.java) XIBAO("xibao","女","温柔可爱的浠宝小女孩") displayName 浠宝;FUBAO("fubao","男","聪明活泼的福宝小男孩") displayName 福宝
注入 AIChatController./chat/send(:76-109)、/nutrition/send(:245-272) user.getMascot() → inputs 放入 mascot_name/mascot_gender/mascot_persona
缺口① AIChatController./health-coach/send(:420-447) 漏注入,需补齐
LangGraph 模型 app/models/common.py:11 mascot_code: Optional[str] 字段已预留
缺口② app/graphs/health_coach_graph.py:89 只挂单一 get_prompt("health_coach"),无人格路由
提示词服务 app/prompt_service.py 从 system_prompts 表按 prompt_key 加载(经 Java /api/system-prompts/get,TTL 缓存),DEFAULT_PROMPTS 兜底
管理端 cfc-web/src/views/admin/SystemPromptManagement.vue + system_prompts 表(schema.sql:4990) prompt 内容后台可配,零发版改话术
选择入口 cfc-frontend/pages/user-edit/user-edit.vue mascot 切换 UI 已有
渲染 cfc-frontend/pages/ai/chat.vue(944行) mascotCode/mascotName/mascotIcon 主题化渲染已有

管家体系(已存在,自选为唯一新能力)

资产 说明
butler_profiles(schema.sql:2626) tier/status/member_count/max_members/annual_fee_l1,l2/commission_rate_l1,l2/certificates/description
butler_assignments(schema.sql:2690) family_id UNIQUE(uk_family_assignment) ↔ butler_user_id + subscription_id + level + assigned_at;一家一管家
订阅激活自动分配 MemberSubscriptionService.activateSubscription:165 → ButlerService.assignMemberToButler(familyId,subscriptionId,level,amount):298:approved+tier=level+member_count<max_members 按 created_at 轮询取首个;可能找不到(无分配行)
家庭端控制器 controller/butler/ButlerController.java /api/butler/*(apply/profile/update-profile/members/service-records/commissions)——均为管家自身视角
管理端控制器 AdminButlerController /api/admin/butler/list\|review\|assign-member\|service-records\|settlements——assign-member 为管理员手动指派
订阅校验 MemberSubscriptionService.getActiveSubscription(familyId):54 / hasActiveSubscription:78
Web 管理端 views/admin/review/ButlerAudit.vueReviewCenter.vueServiceRoleApplications.vueapi/butler.jspermissions.js
小程序 pages/butler/apply.vue(管家申请入驻页)

四、§1 数据模型与迁移

仅 1 处变更:

-- butler_profiles 增加(DatabaseInitializer.ensureColumn 迁移 + schema.sql 同步)
ALTER TABLE butler_profiles ADD COLUMN accepting TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否接受新家庭绑定 0=停接';

迁移要求:

  • 入口唯一:config/DatabaseInitializer.runMigrations()ensureColumn("butler_profiles", "accepting", ...),幂等
  • 同步更新 schema.sql 的 butler_profiles CREATE TABLE(:2626 起)
  • mvn clean compile 验证

其余全部复用:users.mascot 不动;butler_assignments 换绑走 UPDATE;无新表。

五、§2 后端接口(统一 @PostMapping,Result)

5.1 家庭端(新增控制器类 FamilyButlerSelectController,放 controller/butler/ 包;Bean 名 familyButlerSelectController 与现有 butlerController 无冲突;业务方法加入现有 ButlerService

familyId 推导方式与 MembershipController 一致:userMapper.selectById(userId).getFamilyId();familyId 为空 → error("未找到家庭信息")。

接口 校验/逻辑 返回
POST /api/butler/available-list 查询条件:status='approved' AND tier='L2' AND accepting=1 AND member_count<max_members。浏览不校验 L2(非 L2 可看列表,绑定才校验)。脱敏:昵称/头像/简介/证书数/剩余名额,不含联系方式 Result<List<Map>>
POST /api/butler/my-butler 按 familyId(取自 userId 归属家庭)查 assignments 行 + 关联管家摘要 + 当前订阅等级 有绑定返回详情;无绑定 data=null
POST /api/butler/select {butlerUserId} 事务内依次:①getActiveSubscription 且 level=='L2',否则 error("该功能面向久久一生会员")②目标管家 approved+tier='L2'+accepting=1③条件增容 UPDATE butler_profiles SET member_count=member_count+1 WHERE id=? AND status='approved' AND accepting=1 AND member_count<max_members,影响行数=0 → error("该管家名额已满") 并终止④assignments 存在行则 UPDATE butler_user_id(旧管家 member_count-1),不存在则 INSERT(family_id 冲突时按唯一键重试 UPDATE 路径)⑤记录 assigned_at 刷新 success 后返回新管家摘要

并发安全:容量扣减用条件 UPDATE 原子完成;解绑旧管家计数在成功换绑后执行;整体 @Transactional(rollbackFor = Exception.class)

5.2 AI 端(改现有 AIChatController

位置 变更
/health-coach/send 补 mascot 注入,与 /chat/send 对齐:inputs 增加 mascot_name/mascot_gender/mascot_persona + coach_id = mascotCode 原始值(xibao/fubao/null);缺省时 inputs.put("coach_id","") 由 LangGraph 兜底

UserController 无改动(mascot 更新已有 updateProfile 路径);AiGateway.chat 签名不变(inputs 透传)。

实现注意(已核实):AiGateway.chat 将 inputs 中 String/Number/Boolean 键值写入请求体 context 对象(AiGateway.java:162-168),LangGraph 侧从 state["context"] 读取——故 coach_id 放入 inputs 即可透传,无需修改 common.py 传输模型(其 mascot_code 字段与本次路由无关)。PII 脱敏 sanitizeContext 对 'xibao'/'fubao' 短码无影响。

5.3 管理端(AdminButlerController 增量,沿用 /api/admin/butler/* 前缀与角色校验风格)

接口 逻辑
POST /api/admin/butler/assignments 分页列表:联 users(家长昵称/头像)、butler_assignments、users(管家昵称)、member_subscription.level;筛选:管家ID/级别/关键字
POST /api/admin/butler/unassign {assignmentId} 强制解绑:物理删除 assignments 行(服务留痕在 butler_service_records,无需保留分配行)+ 对应管家 member_count-1,事务
POST /api/admin/butler/toggle-accepting {profileId, accepting} 更新 butler_profiles.accepting;仅影响新选择,不动存量绑定

新增接口须同步登记 docs/superpowers/api/API_REFERENCE.md

六、§3 LangGraph(cfc-langgraph)

  1. coach_id 传输路径:Java inputs → AiGateway body.context(已核实)→ FastAPI → graph state 的 context 字典,无需改 common.py 传输模型;generate_answerstate["context"] 取值
  2. generate_answer 中提示词路由:

    ctx = state.get("context") or {}
    coach_id = ctx.get("coach_id")
    if coach_id not in ("xibao", "fubao"):
    coach_id = None
    system_prompt = (
    await get_prompt(f"health_coach_{coach_id}") if coach_id else None
    ) or await get_prompt("health_coach") or DEFAULT_COACH_PROMPT
    

即按人格 key 优先,逐级回退到全局 health_coach,再回退内置默认——任何一环缺失都不报错。

  1. 两段新 prompt 由运营在 SystemPromptManagement 页录入:
    • health_coach_xibao:温柔知心风(倾听共情、温和建议、多用鼓励性语言)
    • health_coach_fubao:活泼行动派(直接给行动清单、打卡式督促、轻快语气)
    • 共享同一知识检索节点(health_retrieve 不动)、记忆节点不动

七、§4 小程序前端(cfc-frontend,Vue2 Options API)

文件 变更
pages/ai/chat.vue 微调欢迎语:isHealthCoach 分支由固定"你好!我是健康教练"改为拼接当前教练名(如"你好!我是{{mascotName}},你的健康教练");消息头像/导航主题已按 mascot 渲染不动
pages/user-edit/user-edit.vue 已有 mascot 选择项;核对选项描述文案体现"AI 健康教练"语境,不改逻辑
新页 pages/butler/select.vue ①管家卡片列表(头像/昵称/简介/证书数/剩余名额徽标)+ 点击出确认弹窗(uni.showModal)②顶部状态区三态:未绑定(去选择)/已绑定(管家摘要卡+"更换管家"按钮)/L2 未订阅或过期(升级引导条跳会员购买页)③非 L2 用户可浏览列表但点选择时报错 toast(后端兜底)④遵守小程序约束:无 ?.、flexbox、:key="getItemKey(item)" 方法调用、日期 parseDate()、禁止 CSS Grid
会员中心相关页 新增「我的管家」入口卡片:同上三态摘要;点击进 select 页
pages.json 注册 select 页面路径与标题

API 封装加至小程序 utils/api.js:getAvailableButlers / getMyButler / selectButler。

八、§5 Web 管理端(cfc-web,Vue2 + Element UI)

文件 变更
新视图 views/admin/ButlerAssignments.vue el-table 分配关系:家庭ID/家长昵称/管家昵称/订阅级别/绑定时间;筛选栏(管家下拉+关键字);操作列「强制解绑」→ elMessageBox.confirm → unassign 接口 → 刷新
views/admin/review/ButlerAudit.vue(管家档案所在列表) 新列「接单中」el-switch 绑 accepting,change 时调 toggle-accepting;accepting=0 行灰色标识"已停接"
api/butler.js 新增 getAssignments / unassignAssignment / toggleAccepting 封装
路由表 + permissions.js 注册 ButlerAssignments 页面与权限码

九、§6 边界与错误处理

场景 处理
L2 到期仍有绑定 不自动解绑;家庭端入口显示"订阅已到期";管理员可强制解绑;无定时任务
并发选满 条件 UPDATE 原子扣容(§5.1 步骤③),0 行受影响即"名额已满"回滚
换绑频率 不限次数,每次完整校验
coach_id 缺失/非法 三级回退:人格 key → 全局 health_coach → DEFAULT_COACH_PROMPT,不报错
LangGraph 不可用 AiGateway 熔断+Fallback 已覆盖,不动
管家停接 accepting=0 仅拦截新绑定(available-list 过滤 + select 校验双重拦截);存量家庭不受影响,其会话照常
强制解绑后 家庭立即可重新自选任意合格管家
首次选择 vs 换绑 订阅激活自动分配可能未产生 assignments 行(当时无合格管家):select 接口 INSERT/UPDATE 双路径兼容(§5.1 步骤④)
旧管家计数一致性 换绑/解绑均同步 member_count ±1;存量数据漂移校正不在本期范围(member_count 一律以条件更新维护)

十、不做的事(Out of Scope)

  • ❌ 小浠/小福更名(维持浠宝/福宝官方 IP 名)
  • ❌ coach_personas 数据库配置表(复用吉祥物管线,方案 B 原表设计作废)
  • ❌ 推送通知(项目推送通道未启用)
  • ❌ L1 家庭自选管家
  • ❌ 管家聊天功能扩展(/api/ai/butler/* 已有)
  • ❌ 教练人格的管理端 CRUD 元数据维护(prompt 文案走 SystemPromptManagement 即可)
  • ❌ 到期自动解绑定时任务

十一、验收标准

  1. cd cfc-backend && mvn clean compile 通过(EXIT 0)
  2. 改动的小程序 vue 文件提取 script 块 node --check 通过(不做整包 build)
  3. L1 用户调 select → 明确错误文案;L2 用户全流程可用:浏览→选择→更换→解绑后重选
  4. 容量满的管家不出现在 available-list;select 直呼其 id 也被拒
  5. accepting=0 管家不可被新选,存量绑定家庭对话不受影响
  6. 切换 mascot 后 /health-coach/send 对话风格随 prompt key 生效;mascot 未设置时回退默认 prompt 正常对话
  7. 管理端可查看全部分配关系并可强制解绑;停接开关即时生效
  8. API_REFERENCE.md 已登记三个家庭端接口与三个管理端接口

十二、实施顺序建议(供 writing-plans 参考)

后端迁移+接口 → LangGraph prompt 路由(可与后端并行)→ 小程序页面 → Web 管理端 → API 文档同步 → 端到端验证