# 天赋系统契约(Talent System Contract) 天赋树 = **Prompt + 模型行为的控制平面**,不是游戏成长系统。 - **主天赋(唯一)** = 身份:决定 System Prompt / 是否走 RAG / 合规安全等级 / 默认模型。主天赋 **就是 `twin.profession`**,不另存一套 id。 - **副天赋(≤N)** = 风格软技能:**后置改写**,只碰成稿、**绝不改事实**。 实现见 [`../../src/talents.js`](../../src/talents.js)。本目录是与线上代码对齐的**契约**,也是将来搬 DB 的蓝本。 ## 文件 | Schema | 描述 | 对应代码 | |---|---|---| | [main-talent.schema.json](main-talent.schema.json) | 主天赋身份节点 | `mainTalent(profId)` | | [sub-talent.schema.json](sub-talent.schema.json) | 副天赋注册表项 | `SUB_REGISTRY` | | [user-talent-instance.schema.json](user-talent-instance.schema.json) | 分身选定的天赋 | `twin.talents` | | [talent-registry.schema.json](talent-registry.schema.json) | 注册表整体 | 派生/静态表 | ## 派生关系(主天赋怎么来的) 不逐个职业手写主天赋,而是三张映射表交叉派生(`talents.js`): ``` profession(professions.js) ──cat──▶ ARCH_BY_CAT ──▶ arch ──▶ ARCH_BEHAVIOR(行为基线) + ARCH_MODEL(模型偏好) └─compliance─▶ COMPLIANCE ──▶ safety_level + block_keywords + disclaimer + audit_required ``` - `cat → arch`:content→creator, pro/health/biz/life/other→expert, edu→teacher, art→artist, sport→sport, craft→craft - `compliance → 安全`:none→{2,无} / soft→{5,软免责} / strict→{9,强免责+关键词+需审核} 新增一个职业只改 `professions.js` 一处;换某原型的行为只改 `ARCH_BEHAVIOR` 一处。 ## 校验规则(服务端必须兜底,不信前端) 1. **主天赋唯一**:`main_talent` 必须 ∈ 职业表(`getProfession`),否则 400 `unknown-main-talent`;`null`/无 → `general` 兜底身份。 2. **副天赋合法性**:每个 id 必须 ∈ `SUB_REGISTRY`;去重;顺序保留。 3. **副天赋槽位**:数量 ≤ `maxSubs(level) = min(3, 1 + level)`(level 来自 `computeSkillTree`)。超出**静默截断**(`validateSubs`),不报错。 4. **副天赋不改事实**:所有 style/constraint 副天赋合并成**一次**后置改写,指令强约束"不改变任何事实/数字/结论/引用";hobby 型走前置注入。 5. **审核门(软门)**:`main.audit_required && !twin.verified` → `twin.talentAudit='pending'`,**不拦截**(分身仍可用),但运行时 Guardrail 强制免责。已 `verified` → `'approved'`。 6. **Guardrail(运行时,输出前)**: - `block_keywords` 命中 → 软化替换; - `safety_level >= 5` 且回答未含免责 → 追加 `disclaimer` 到结尾。 ## 运行时管线(`services/twin.js` → `ask()`) ``` 用户输入 → 组装 System Prompt:身份(mainPromptBlock) + 前置注入(injectBlock) + 人设/记忆/RAG → (RAG 检索,main.rag) → LLM 主回答 chat(messages) ← 主天赋出稿 → 副天赋后置改写 chat([restyleInstruction, draft]) ← 合并 1 次;无副天赋则跳过 → Guardrail guardrail(text, main) ← 屏蔽词 + 免责 → 输出 ``` 成本:每轮 **1 次主调用 + 至多 1 次改写调用**(不是每个副天赋一次)。 ## API 契约 ### `GET /api/twins/:id/talents` 返回(`describeTalents`): ```jsonc { "main": { "id","name","emoji","entity","arch","compliance","safety_level", "rag_required","audit_required","audit_ok","behavior" }, "subs": { "selected": ["humor"], "max": 1, "options": [{ "id","name","subtype","desc" }] }, "growth": { "level": 0, "maxSubs": 1 } } ``` ### `PUT /api/twins/:id/talents` Body(两字段都可选,可单独改): ```jsonc { "main_talent": "lawyer", // 可选:切身份(=profession),带 audit 门 "sub_talents": ["humor","concise"] } // 可选:仅当为数组时才更新;截到 maxSubs ``` 返回同 `GET`。 ## DB 化迁移(留了缝,MVP 不做) 1. `SUB_REGISTRY` → 集合 `talent_sub_registry`(一项一文档)。 2. 主天赋映射(ARCH_BEHAVIOR / COMPLIANCE / ARCH_MODEL)→ 集合 `talent_main_config`,或物化 `talent_main_talents`(一职业一行)。 3. 每个节点加 `talent-registry.schema.json#/$defs/versioning`(`version/active/gray_pct`)→ 支持**灰度放量 + 一键回滚**。 4. `talents.js` 的纯函数签名不变,只把三张 `const` 表换成带缓存的 DB 读取;`describeTalents/resolveTalents/guardrail` 全部复用。 ## 留坑不做(架构留得住) 天赋升级 lv2/3(`level` 字段已在)· 冲突检测(医生+算命❌)· 天赋市场(官方/共创)· 会话级动态天赋 · 按天赋真路由模型/计量返佣(`model_preference` + `chat(opts.model)` 缝已在)。