195 lines
10 KiB
Markdown
195 lines
10 KiB
Markdown
# 群聊人设卡(Galgame 风格人物卡构建器)设计文档
|
||||
|
|
|
|||
|
|
> 本文档固化设计决策,作为各层实现的唯一依据。标注 ⏳ 的为草案/待定项。
|
|||
|
|
|
|||
|
|
## 1. 定位
|
|||
|
|
|
|||
|
|
**目标**:基于群聊发言语料,蒸馏群成员的形象风格,生成 galgame 风格的人物卡(人设)。
|
|||
|
|
|
|||
|
|
**非目标**:
|
|||
|
|
- 不做关系网分析("相处模式"只是画像的一个段落,不是独立产品)
|
|||
|
|
- 不做真实身份推断(年龄/职业/住址等现实信息只能进"不确定信息"段)
|
|||
|
|
- 不生成真实样貌(样貌参考为虚构,永远带标注)
|
|||
|
|
|
|||
|
|
**消费方**:① 展示给人看(人物卡);② 可选:作为扮演 prompt 注入(MaiBot 兼容方向)。
|
|||
|
|
|
|||
|
|
## 2. 整体架构
|
|||
|
|
|
|||
|
|
功能分层:采集 → 治理 → 存储 → 调度 → 分析 → 呈现。
|
|||
|
|
|
|||
|
|
两级流水线(借鉴 MaiBot 印象机制):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
群聊消息 ──五维落库──> 语料 ──(攒够 N 条)──> 印象(LLM 自然语言) ──(攒够 M 条)──> 画像(九段协议) ──> 版本化快照
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **语料 → 印象**:每次对"上次印象之后的新语料"生成一段自然语言印象(话题/氛围/互动),存 `persona_impression`。印象是增量中间产物,画像不重读全部原文。
|
|||
|
|
- **印象 → 画像**:从印象集 + 规则统计(@/回复 互动、活跃时段)生成九段人物卡。
|
|||
|
|
- 画像每次生成都是新版本(version +1),永久留档可对比。
|
|||
|
|
|
|||
|
|
## 3. 数据层(已实现 ✅)
|
|||
|
|
|
|||
|
|
五张表,前缀 `persona_`:
|
|||
|
|
|
|||
|
|
### persona_group —— 群采集开关(数据来源总闸门)
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| group_id | BigInteger PK | 群号 |
|
|||
|
|
| enabled | Boolean 默认 False | 该群是否开启采集(默认关,需显式开启) |
|
|||
|
|
| updated_at | DateTime | 最后变更时间 |
|
|||
|
|
|
|||
|
|
群关闭 → 该群所有人一律不采集;群开启后,个人还需 opt-in(两级闸门)。
|
|||
|
|
|
|||
|
|
### persona_user —— 参与者名单(按群 opt-in)
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| user_id | BigInteger PK | 参与人 QQ |
|
|||
|
|
| group_id | BigInteger PK | 所在群 |
|
|||
|
|
| joined_at | DateTime | 加入时间 |
|
|||
|
|
|
|||
|
|
### persona_chat_log —— 采集语料(五维 + 发言段链条)
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| id | Integer PK 自增 | 全局有序,印象覆盖区间用它表示 |
|
|||
|
|
| user_id | BigInteger | 谁发的 |
|
|||
|
|
| group_id | BigInteger | |
|
|||
|
|
| nickname | String(64) | 群昵称快照 |
|
|||
|
|
| content | Text | 内容(纯文本,已脱敏截断) |
|
|||
|
|
| target_user_id | BigInteger NULL | 发给谁(ev.reply 的 sender / @ 对象;无则 NULL=群聊漫谈) |
|
|||
|
|
| target_inherited | Boolean | target 是否从发言段链条继承(对上一句的解释/补充仍算发给同一对象) |
|
|||
|
|
| follows_id | Integer NULL | 发言段链条:同一说话人的上一条语料 id(间隔 ≤ 5 分钟) |
|
|||
|
|
| reply_to_content | Text NULL | 被回复内容快照(对方不在语料里也能知道他在回应什么) |
|
|||
|
|
| created_at | DateTime | 几点发的 |
|
|||
|
|
|
|||
|
|
索引:`(user_id, group_id, created_at)`。滚动保留:单用户单群上限 3000 条(⏳ 常量待定)。
|
|||
|
|
|
|||
|
|
**发言段链条**(解决"不带 @/回复 的后续补充丢目标"):`@B 借我玩玩` → 下一条 `我的意思是借号不是借人`(无显式目标)继承 target=B 并打 `target_inherited` 标记;分析层窗口组装可沿 `follows_id` 回溯整段发言。
|
|||
|
|
|
|||
|
|
### persona_impression —— 印象(两级流水线中间产物)
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| id | Integer PK 自增 | |
|
|||
|
|
| user_id / group_id | BigInteger | |
|
|||
|
|
| content | Text | LLM 生成的自然语言印象 |
|
|||
|
|
| cover_from_id / cover_to_id | Integer | 覆盖的语料 id 区间(增量依据) |
|
|||
|
|
| model | String(64) | 生成模型 |
|
|||
|
|
| created_at | DateTime | |
|
|||
|
|
|
|||
|
|
### persona_image —— 图片识别结果缓存(⏳ 多模态预留,未启用)
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| hash | String(64) PK | 图片 hash(对应 chat_log.image_hashes) |
|
|||
|
|
| description | Text | 多模态识别结果(表情包梗/截图内容) |
|
|||
|
|
| model | String(64) | 识别模型 |
|
|||
|
|
| recognized_at | DateTime | |
|
|||
|
|
|
|||
|
|
**预留接口**:`vision.py`(BaseImageRecognizer,当前为 Noop 占位)。将来接入多模态 LLM 后:异步后台识别(绝不在消息路径同步调)、同一 hash 只识别一次(缓存复用)、失败不影响采集。识别描述供分析层窗口组装喂给总结 LLM。
|
|||
|
|
|
|||
|
|
### persona_summary —— 画像快照(版本化)
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| id | Integer PK 自增 | |
|
|||
|
|
| user_id / group_id | BigInteger | |
|
|||
|
|
| version | Integer | 每次生成 +1,同人同群唯一 |
|
|||
|
|
| card_text | Text | 九段 markdown 画像原文 |
|
|||
|
|
| corpus_count | Integer | 语料覆盖条数(元信息) |
|
|||
|
|
| impression_count | Integer | 使用的印象条数(元信息) |
|
|||
|
|
| model | String(64) | 生成模型 |
|
|||
|
|
| created_at | DateTime | |
|
|||
|
|
|
|||
|
|
约束:`UNIQUE(user_id, group_id, version)`(并发写入由调度层锁保护)。
|
|||
|
|
|
|||
|
|
## 4. 九段画像协议(已定稿 ✅)
|
|||
|
|
|
|||
|
|
格式:markdown 固定标题 + 有界 bullet,代码可解析、人可编辑、LLM 可生成、可注入 prompt。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
# 人物卡 · {主称呼}
|
|||
|
|
语料 {N} 条 · 时间跨度 {start}-{end} · 版本 v{n} · 生成于 {date}
|
|||
|
|
|
|||
|
|
## 身份印象 ≤4 条 群内可见的:自称方式、群角色(吐槽役)、常用昵称
|
|||
|
|
## 性格特征 ≤6 条 毒舌但心软 / 重度拖延 / 嘴硬
|
|||
|
|
## 说话风格 ≤5 条 爱用"草"开头、句尾 wwww、先吐槽再给结论
|
|||
|
|
## 口头禅语录 ≤6 条 带原文引用:"有一说一,这个图确实带"
|
|||
|
|
## 兴趣话题 ≤5 条 明日方舟(资深);聊工作→抱怨、聊感情→回避
|
|||
|
|
## 相处模式 ≤4 条 对小B互怼最多,对新人客气(一句话式,非关系网)
|
|||
|
|
## 时间画像 ≤3 条 深夜 22-02 点活跃,白天潜水
|
|||
|
|
## 样貌参考 ≤3 条 🎨 虚构标注:基于气质的参考描述
|
|||
|
|
## 不确定信息 ≤3 条 疑是学生(语料出现"上课"),未证实
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 段内 bullet 为纯文本,可带原文引用(口头禅语录段必须带原文)
|
|||
|
|
- 样貌参考段**必须**带 🎨 虚构标注与设计依据("基于 XX 气质")
|
|||
|
|
- 现实身份信息只能出现在"不确定信息"段
|
|||
|
|
- ⏳ 每段具体生成约束(prompt 细则)属分析层,待细化
|
|||
|
|
|
|||
|
|
## 5. 证据纪律(已定稿 ✅)
|
|||
|
|
|
|||
|
|
三层防编造(借鉴 MaiBot):
|
|||
|
|
|
|||
|
|
1. 印象 prompt 明文约束:"不要添加语料中没有依据的新事实"
|
|||
|
|
2. 规则统计(互动对象、活跃时段)优先于 LLM 分类结果
|
|||
|
|
3. LLM 分类结果默认降级进"不确定信息"段——**模型说的不算稳定真相**
|
|||
|
|
|
|||
|
|
指纹缓存(⏳):证据(印象集 + 统计)hash 未变则不重新生成画像。
|
|||
|
|
|
|||
|
|
## 6. 采集与治理(✅ 消息路径已实现,⏳ 阈值待调)
|
|||
|
|
|
|||
|
|
实现位置:`__init__.py`(on_message 入口 + 鉴权 + 采样)+ `processor.py`(纯函数治理,可单测)。
|
|||
|
|
|
|||
|
|
**两级闸门**:群开关 `persona_group.enabled`(群级,默认关)+ 个人 opt-in `persona_user`(个人,群内开启才生效)。两条同时满足才采集。
|
|||
|
|
|
|||
|
|
- 群开关控制:`开启人设采集 @群` / `关闭人设采集`(⏳ 命令名待定,权限:超管/群主)
|
|||
|
|
- 只采集 `persona_user` 名单内成员(opt-in),退出即停
|
|||
|
|
|
|||
|
|
**脱敏(硬约束:本地正则完成,绝不经过 LLM——LLM 只接触脱敏后文本)**:
|
|||
|
|
敏感值**替换为占位符**而非丢弃整条,保留对话语境(如"借号"互动是人格素材,凭证不是)。
|
|||
|
|
实现:`processor.desensitize()`,四层,覆盖场景:借号/验证码代收/密码口令/兑换码卡密/密保/收入/联系方式变体/位置。
|
|||
|
|
|
|||
|
|
| 规则层 | 模式 | 占位符 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 明确模式(按最长优先排序,防截胡) | 身份证 → 银行卡 → 邮箱 → 手机号(含分隔变体) → IP → wxid → 坐标 → 车牌 | `[身份证]` `[银行卡]` `[邮箱]` `[手机号]` `[IP]` `[微信号]` `[坐标]` `[车牌]` |
|
|||
|
|
| 定位式(同条"关键词+值") | `密码是 xyz789` `账号 abc123` `激活码 ABCDE-1` `验证码是 123456` `VX: xxx` `月薪 25000`(连接词支持 是/为/冒号/空格) | `[密码]` `[账号]` `[兑换码]` `[验证码]` `[微信号]` `[收入]` |
|
|||
|
|
| 跨条语境 | 关键词在附近消息(如"收下验证码"→ 下一条 `123456`)→ 本条值按语境类型替换 | `[验证码]` `[密码]` `[兑换码]` |
|
|||
|
|
| 兜底 | 裸长数字串 ≥6 位 → `[账号]`;字母+数字混合 ≥6 位 → `[密码]`(保守替换,宁误杀不放过) | `[账号]` `[密码]` |
|
|||
|
|
|
|||
|
|
语境来源:本群最近 3 条已治理文本(复用复读检测的内存窗口)。
|
|||
|
|
待扩展场景(⏳):密保答案("你妈妈的名字")、QQ 号文本、代充代练语境。
|
|||
|
|
|
|||
|
|
- 噪声过滤:纯表情图(无文字)、复读、命令/签到、长链接轰炸
|
|||
|
|
- 长文截断(约 200 字/条)
|
|||
|
|
- 采样:连续刷屏 5 秒内只记 1 条
|
|||
|
|
|
|||
|
|
## 7. 调度与触发(⏳ 草案)
|
|||
|
|
|
|||
|
|
| 方式 | 条件 |
|
|||
|
|
|---|---|
|
|||
|
|
| 手动 | 管理员 `生成人物卡 @xxx`(强制,无视阈值) |
|
|||
|
|
| 印象 | 新语料 ≥ 50 条(⏳)且距上次印象 ≥ 24h |
|
|||
|
|
| 画像 | 新印象 ≥ 5 条(⏳)或语料显著增长;首次需语料 ≥ 200 条 |
|
|||
|
|
| 防重入 | 同人同群生成中加锁 |
|
|||
|
|
|
|||
|
|
## 8. 入口层命令集(⏳ 草案)
|
|||
|
|
|
|||
|
|
`加入人设` `退出人设`(opt-in 控制)、`查看人设 @xxx`(展示九段卡)、`生成人设 @xxx`(管理员强制)。
|
|||
|
|
|
|||
|
|
## 9. 借鉴与不借鉴 MaiBot(已定稿 ✅)
|
|||
|
|
|
|||
|
|
**借鉴**:两级流水线(印象机制)、九段协议格式(段落文本协议)、证据纪律三层、指纹缓存、防串人(证据绑定 user_id)。
|
|||
|
|
|
|||
|
|
**不借鉴**:向量库 + BM25 双路召回 + PPR(语料量级 SQL 直查即可)、完整 A_memorix 记忆系统、md5 person_id(QQ 号即 id)。
|
|||
|
|
|
|||
|
|
## 10. 开发阶段
|
|||
|
|
|
|||
|
|
- **Phase 1**:数据层(四表 + 仓储)✅ 本文档落盘时完成
|
|||
|
|
- **Phase 2**:治理层(采集过滤 + opt-in 命令)
|
|||
|
|
- **Phase 3**:分析层(印象/画像生成,LLM 客户端)
|
|||
|
|
- **Phase 4**:调度层(触发/锁)+ 呈现层(人物卡展示)
|
|||
|
|
- ⏳ 后续可选:galgame 风格卡面图片渲染(协议文本为渲染源)
|