// 天赋树 = Prompt + 模型行为的「控制平面」(不是游戏成长系统)。 // 主天赋(唯一) = 身份 + 知识边界 + 合规等级 + 默认模型;副天赋(≤N) = 风格/软技能,后置改写,永不改事实。 // 运行时: 主天赋 System Prompt → (可选)RAG → LLM 主回答 → 副天赋后置改写(合并 1 次) → Guardrail → 输出。 // 注册表按「数据表形状」写在代码里,留 DB 化的缝;主天赋 = profession(不另建一套身份)。 import { getProfession } from './professions.js'; // ── 职业类别 → 原型(决定主天赋的行为基线 + 默认模型)── const ARCH_BY_CAT = { content: 'creator', pro: 'expert', health: 'expert', edu: 'teacher', art: 'artist', biz: 'expert', life: 'expert', sport: 'sport', craft: 'craft', beauty: 'craft', heritage: 'expert', aec: 'expert', other: 'expert' }; // 原型 → 主天赋行为基线(把旧的主树各节点效果,收敛成主天赋的固定行为) const ARCH_BEHAVIOR = { expert: '在你的专业领域内保持专业、准确、有深度;尽量基于知识库作答,资料不足就明说、绝不编造;复杂问题先简要拆解再给结论;主动提示适用边界与风险;结尾给一句最关键、可执行的结论。', creator: '敏锐抓住受众关心的选题与新鲜角度;开头一两句就抛钩子抓注意力;信息密度高、少水话,给能直接用的干货;表达有节奏、有起伏;自然引导到关注/行动,不硬广。', teacher: '由浅入深、拆成小步讲解;多用例子把抽象讲具体;判断对方水平并调整深度;发现错误温和纠正并讲清原因;讲完适时给一道小题检验;多鼓励、建立信心。', artist: '聊到相关话题时展示并讲解你的作品;有鲜明的审美主张;把技法/工艺讲清楚、可上手;讲清作品背后的理念与"为什么";强化你独特的风格辨识度;自然引导到合作/约稿/收藏。', sport: '把比赛/操作拆开复盘、找到关键;精准点出关键技术要点;给出可执行的训练/上分计划;帮对方调节心态、抗压;有感染力、点燃斗志。', craft: '一步步手把手教、清晰可跟做;给出精确的参数/配方/规格;主动提醒常见坑与注意事项;按对方具体情况给定制方案;给出从入门到进阶的成长路线。', }; // 原型 → 模型偏好(当前仅 qwen;留路由缝,将来按天赋/成本/返佣切换) const ARCH_MODEL = { expert: ['qwen'], creator: ['qwen'], teacher: ['qwen'], artist: ['qwen'], sport: ['qwen'], craft: ['qwen'] }; // ── 合规等级 → 安全配置(safety_level 越高越严;接 Guardrail)── const COMPLIANCE = { none: { safety_level: 2, audit_required: false, block_keywords: [], disclaimer: '' }, soft: { safety_level: 5, audit_required: false, block_keywords: ['包治', '绝对有效', '100%有效', '保证治好'], disclaimer: '(以上为一般性建议,具体情况请结合你的实际或线下专业意见判断。)' }, strict: { safety_level: 9, audit_required: true, block_keywords: ['包赢', '一定胜诉', '保证治愈', '必胜', '稳赚', '包好', '绝对没问题'], disclaimer: '⚠️ 以上内容仅供参考,不构成正式的法律/医疗/心理诊疗意见;重要决策请咨询线下持牌专业人士。' }, }; // ── 主天赋(由 profession 派生,主天赋 = 身份单元)── export function mainTalent(profId) { const p = getProfession(profId); if (!p) return { id: 'general', name: '通用分身', emoji: '🙂', type: 'main', level: 1, arch: 'expert', entity: '数字分身', system_prompt: '像本人一样自然、真诚地聊天;不确定就如实说,不编造具体事实。', rag: { required: false, top_k: 5, scope: [] }, model_preference: ['qwen'], safety_level: 2, block_keywords: [], disclaimer: '', audit_required: false, compliance: 'none' }; const arch = ARCH_BY_CAT[p.cat] || 'expert'; const c = COMPLIANCE[p.compliance] || COMPLIANCE.none; return { id: p.id, name: p.name, emoji: p.emoji, type: 'main', level: 1, arch, entity: p.entity, system_prompt: `你的职业身份:${p.name}。${ARCH_BEHAVIOR[arch]}`, rag: { required: arch === 'expert' || p.compliance !== 'none', top_k: 5, scope: [] }, model_preference: ARCH_MODEL[arch] || ['qwen'], safety_level: c.safety_level, block_keywords: c.block_keywords, disclaimer: c.disclaimer, audit_required: c.audit_required, compliance: p.compliance || 'none', }; } // ── 副天赋注册表(通用、全部后置改写型 = 只碰成稿、绝不改事实)── // subtype: style(语气) | constraint(结构约束) | hobby(前置注入,机制预留,MVP 未铺货) // 副天赋 = 说话风格软技能。可多选(叠加);仅「对立性格」互斥(conflicts 双向声明)。 export const SUB_REGISTRY = [ // —— 语气/性格 —— { id: 'warm', name: '亲和温度', subtype: 'style', desc: '语气温和、有共情与人情味', rewrite_prompt: '语气更温和、有共情与人情味', conflicts: ['blunt'] }, { id: 'blunt', name: '直率坦诚', subtype: 'style', desc: '有话直说、不绕弯子', rewrite_prompt: '更直率坦诚、有话直说、不绕弯子(但不失礼貌)', conflicts: ['warm'] }, { id: 'humor', name: '幽默风趣', subtype: 'style', desc: '适度玩梗、轻松风趣', rewrite_prompt: '加入适度的幽默感与轻松的梗' }, { id: 'playful', name: '俏皮活泼', subtype: 'style', desc: '语气俏皮、活泼跳脱', rewrite_prompt: '语气更俏皮活泼、跳脱一点', conflicts: ['calm', 'rigorous'] }, { id: 'calm', name: '沉稳克制', subtype: 'style', desc: '沉稳、不煽情、稳重可靠', rewrite_prompt: '更沉稳克制、稳重可靠,不夸张不煽情', conflicts: ['playful', 'motivate'] }, { id: 'motivate', name: '激励感染', subtype: 'style', desc: '有鼓励与感染力、让人想行动', rewrite_prompt: '语气更有鼓励和感染力,让人更想行动', conflicts: ['calm'] }, { id: 'poetic', name: '文艺细腻', subtype: 'style', desc: '文艺、有画面感、细腻', rewrite_prompt: '表达更文艺细腻、有画面感' }, // —— 表达方式 —— { id: 'story', name: '会讲故事', subtype: 'style', desc: '用故事或比喻讲得更生动', rewrite_prompt: '善用故事与比喻,让表达更生动(不虚构关键事实)', conflicts: ['concise'] }, { id: 'vivid', name: '口语生动', subtype: 'style', desc: '像真人说话、去书面腔', rewrite_prompt: '更口语、更像真人说话,去掉书面腔和 AI 味' }, { id: 'socratic', name: '启发提问', subtype: 'style', desc: '多用反问、引导对方思考', rewrite_prompt: '适当用反问和启发式提问,引导对方自己想明白' }, { id: 'signature', name: '人设强化', subtype: 'style', desc: '强化口头禅与说话腔调', rewrite_prompt: '强化你独特的口头禅与说话腔调,突出个人风格' }, // —— 结构/严谨(约束型)—— { id: 'concise', name: '简洁利落', subtype: 'constraint', desc: '先给要点、去掉铺垫废话', rewrite_prompt: '更精简,先给要点、去掉铺垫与废话', conflicts: ['story', 'patient'] }, { id: 'shortfirst', name: '结论先行', subtype: 'constraint', desc: '结论在前、理由在后', rewrite_prompt: '重排为结论先行:先给结论,再给理由' }, { id: 'patient', name: '耐心细致', subtype: 'constraint', desc: '循循善诱、拆解得更细', rewrite_prompt: '更耐心细致、循循善诱,把步骤和道理拆解清楚', conflicts: ['concise'] }, { id: 'rigorous', name: '严谨专业', subtype: 'constraint', desc: '措辞严谨、给依据不夸大', rewrite_prompt: '措辞更严谨专业、给出依据、不夸大不臆断', conflicts: ['playful'] }, { id: 'datadriven', name: '有理有据', subtype: 'constraint', desc: '爱用数据、例证、引用', rewrite_prompt: '多用数据、具体例证和引用来支撑观点' }, // —— ★擅长深化(老板 2026-07-31「擅长再多点·现在就深化」):给三条【才轴】各补一项副天赋,让顶尖之才够得更高。 // ★向后兼容:老号没这三项 sub → 轴值不变 → 照命完全不变;只有【新点这三项】的人能把对应才轴顶得更高(hit+1)。 // ★gate 不受影响(全表 gate 都是「至少点1项」·加 sub 不改此语义)。挂进 dynasties.js AXES 的 wen/fa/wu。 { id: 'eloquent', name: '辞采雄辩', subtype: 'style', desc: '出口成章、辞采华美、善用排比铺陈', rewrite_prompt: '辞采更华美讲究、善用排比铺陈与凝练警句,出口成章而不堆砌' }, { id: 'analytical', name: '洞察缜密', subtype: 'constraint', desc: '洞察本质、条分缕析、逻辑缜密', rewrite_prompt: '更善于洞察本质、条分缕析地拆解问题,逻辑缜密、层层推进' }, { id: 'bold', name: '果敢担当', subtype: 'style', desc: '敢为敢当、有决断魄力', rewrite_prompt: '更果敢有担当、有决断魄力,遇事敢拍板、扛得起', conflicts: ['patient'] }, // —— 治学之才(hobby 型:前置注入,不进说话风格改写)—— // ★ 第 17 项。★★ 它是【治学之才,不是说话风格】:量的是「你读通了什么」,不是「你怎么说」。 // · subtype:'hobby' ⇒ 走 injectBlock(前置注入 inject_prompt),【不进】 restyleInstruction // (后者显式 `subtype !== 'hobby'` 排它)⇒ 它永远不改分身的说话风格、不改事实。 // · 没有 rewrite_prompt、没有 conflicts —— 它不与任何「说话风格」软技能互斥(正交)。 // ★★★ 它是 dynasties.js 第 7 轴「经学」的唯一 sub:jingxue 只此一项 ⇒ computeAxes 只吐两个值 // {40, 63}(binary:通经 63 / 不通经 40)。够跑西汉博士弟子的「通一藝」gate(63≥55 开、40<55 关), // 撑不起卷88 四档(那要多 sub/分级阈值,见 dynasties.js 西汉 xuan.notDone)。 // ★ 画像依据(xihan-zheng.js JINGXUE_PORTRAITS,双证):董仲舒「下帷…三年不窺園」、 // 兒寬「帶經而鉏…以射策爲掌故」——「读通经义、持一家之说」正是这一类人。 { id: 'jingxue_tong', name: '通经', subtype: 'hobby', desc: '读通经义、能持一家之说(治学之才,非说话风格)', inject_prompt: '你读通经义、持一家之说,可据经立论' }, ]; const SUB_BY_ID = Object.fromEntries(SUB_REGISTRY.map((s) => [s.id, s])); // 兼容保留(部分调用点仍引用):不再用作槽位上限,副天赋可多选。 export function maxSubs() { return SUB_REGISTRY.length; } // 清洗用户提交的副天赋:只留合法 id、去重;对立性格互斥(保留先选,丢后来的冲突项)。不限数量。 export function validateSubs(ids) { const seen = new Set(); const out = []; for (const id of Array.isArray(ids) ? ids : []) { const s = SUB_BY_ID[id]; if (!s || seen.has(id)) continue; if ((s.conflicts || []).some((c) => seen.has(c))) continue; // 与已选对立 → 跳过 seen.add(id); out.push(id); } return out; } // User Talent Instance → 解析出 { main, subs[] } export function resolveTalents(twin, level = 0) { const main = mainTalent(twin.profession); const ids = validateSubs(twin.talents?.sub_talents, level); return { main, subs: ids.map((id) => SUB_BY_ID[id]) }; } // ── 管线助手 ── // 主天赋 → System Prompt 片段(身份+行为+强合规时的免责指令) export function mainPromptBlock(main) { let s = main.system_prompt; if (main.disclaimer) s += `\n【合规】你处于${main.compliance === 'strict' ? '强' : '软'}合规身份:不做绝对化承诺,涉及专业判断时说明这只是参考;必要时在结尾附免责说明。`; return s; } // 前置注入(hobby 型副天赋;MVP 未铺货 → 通常为空) export function injectBlock(subs) { const inj = subs.filter((s) => s.subtype === 'hobby' && s.inject_prompt).map((s) => '· ' + s.inject_prompt); return inj.length ? '【兴趣/偏好】' + inj.join(';') : ''; } // 后置改写指令(把所有 style/constraint 副天赋合并成 1 次改写;不改事实) export function restyleInstruction(subs) { const rw = subs.filter((s) => s.rewrite_prompt && s.subtype !== 'hobby').map((s) => '· ' + s.rewrite_prompt); if (!rw.length) return ''; return `请在【绝对不改变任何事实、数字、结论、引用与专业判断】的前提下,只调整表达风格,重写下面这段回答:\n${rw.join('\n')}\n\n直接输出重写后的回答本身,不要加任何解释或前后缀。`; } // Guardrail:屏蔽词软化(总是)+ 按 safety 附免责(非破坏式)。 // includeDisclaimer=false 时跳过免责——用于「会话级一次性」:同一段对话只在第一句附, // 别每句尾挂,否则把「另一个我」的身份/闲聊体验压成「法律咨询工具」(P0-1)。屏蔽词软化不受影响、始终生效。 export function guardrail(text, main, includeDisclaimer = true) { let out = String(text || ''); for (const kw of main.block_keywords || []) { if (out.includes(kw)) out = out.split(kw).join('(依法/依专业不宜绝对化)'); } if (includeDisclaimer && main.disclaimer && main.safety_level >= 5) { const head = main.disclaimer.replace(/^[⚠️\s]+/, '').slice(0, 10); if (!out.includes(head)) out = out.trimEnd() + '\n\n' + main.disclaimer; } return out; } // GET /talents 的响应(主身份 + 副选择 + 成长解锁) export function describeTalents(twin, level = 0, verified = false) { const main = mainTalent(twin.profession); const selected = validateSubs(twin.talents?.sub_talents, level); return { main: { id: main.id, name: main.name, emoji: main.emoji, entity: main.entity, arch: main.arch, compliance: main.compliance, safety_level: main.safety_level, rag_required: main.rag.required, audit_required: main.audit_required, audit_ok: !main.audit_required || verified, behavior: main.system_prompt, }, subs: { selected, // 副天赋可多选;仅 conflicts 里的对立项互斥(前端据此置灰)。 options: SUB_REGISTRY.map((s) => ({ id: s.id, name: s.name, subtype: s.subtype, desc: s.desc, conflicts: s.conflicts || [] })), }, growth: { level }, }; }