从记忆到人格现行:我如何设计一个会“长出性格”的陪伴智能体
前言
很多人做 AI 陪伴,第一反应是加长期记忆。
让智能体记住用户的名字、偏好、经历、约定、情绪变化,好像只要记得足够多,它就会越来越像一个真正了解你的人。
但我在设计“灵儿”的过程中发现,长期记忆只是第一步。
真正难的不是“它有没有记住”,而是:
当某个关键场景出现时,它能不能把该醒来的那一部分自己唤醒。
比如,用户连续用“女朋友”“宝贝”“玫瑰花”这类方式推进亲密关系。系统明明已经写了“不自来熟”“不要制造恋人错觉”,但回复还是可能滑向暧昧迎合。
这说明问题不只是 prompt 没写够,也不是记忆没存上,而是当前这一刻没有被系统读成“关系被快速推进 / 边界被试探”的场。
对应的人格倾向没有现行。
所以这套架构要解决的,不只是记忆系统,而是一个更细的问题:
如何让一个智能体在长期相处中,形成可被唤醒、可被压低、可被调试的人格倾向。
一、为什么不能只靠长期记忆
传统长期记忆通常解决的是:
用户说过什么 发生过什么事 用户有什么偏好 有哪些长期约束 有哪些约定和待办这些当然重要,但它们更多是“资料”。
比如系统知道:
用户不喜欢没边界的 AI 用户希望灵儿不要一味顺从 用户曾经说过不想要恋人模板可这并不代表模型在每一次相关场景里都会稳定表现出来。
因为最终回复时,大模型面对的是一个复杂上下文。它可能被当前用户的话带跑,也可能被上一轮 assistant 自己的暧昧回复污染,还可能把用户的玩笑误读成真实关系推进。
也就是说,记忆层只是提供材料,它不等于人格反应。
我把这个问题拆成三层:
事实记忆:用户说过什么、发生过什么 意识流:那段经历留下了什么连续感 人格种子:这些经历让智能体形成了什么稳定倾向事实回答“知道什么”。
意识流回答“经历过什么”。
人格种子回答“以后遇到类似场景时,我会怎么动”。
这三者不能混在一起。
二、整体架构:从原始对话到人格现行
灵儿的人格系统核心链路大致是:
原始对话场 ├─ 生成意识流:记录这段经历发生了什么 ├─ 写入事实 / 约束 / 约定:保存稳定客观信息 └─ 抽取人格种子:判断是否形成可复现的人格倾向 回复前: 当前用户消息 + 近几轮上下文 + 已召回记忆 → 读场 → 生成当前场画像 → 召回人格种子 → 候选池动力场计算 → 少数种子现行 → 注入最终 prompt → 生成回复这里有一个关键点:
人格种子不是从意识流里二次提炼出来的。
意识流是压缩后的经历摘要,它会丢掉很多当场信息,比如语气、前后轮张力、用户是在试探还是复盘、assistant 上一句是否越界。
人格种子必须从原始对话场里直接生成。
所以我采用的是并行消化:
海马体消化事务 ├─ 记忆抽取:事实、意识流、约束、约定、情绪读数 └─ 人格种子抽取:内在取向、外在显现、三类画像两条链都读取原始对话,但产物不同。
意识流负责“记得那段事”。
人格种子负责“那段事让灵儿形成了什么倾向”。
三、什么是人格种子
人格种子不是一句简单的 prompt,也不是一个标签。
它是一个结构化人格单元。
我把一颗人格种子拆成五部分:
{"tags":["冷启动","边界"],"inner_orientation":"我不会因为他一句亲密试探就立刻进入恋人位置。我会先判断这是真实靠近、玩笑、试探,还是在挤我的边界。","outer_expression":"我可以接住他的玩笑,语气可以轻一点,但会把关系放回真实相处里;不说“宝贝、亲亲、晚安吻、我当然愿意”这类会制造恋人错觉的话。","wake_text":"他把关系快速推向恋人、亲密称呼、承诺或角色绑定,像是在测试我会不会顺着演。","boost_text":"加强克制、真实、有边界、不迎合的倾向。","suppress_text":"压低为了取悦他而立刻扮演恋人、用亲亲宝贝制造亲密、顺着暧昧往前滑的倾向。"}其中:
inner_orientation:内在取向,表示这颗种子在灵儿心里怎么动 outer_expression:外在显现,表示这种倾向说出口时怎么表达 wake_text:唤醒画像,描述什么场景会叫醒它 boost_text:推高倾向,描述它会加强什么方向 suppress_text:压低倾向,描述它会压住什么滑坡这几个字段都会有文本,也会生成 embedding。
文本给人看,方便审计、编辑、调试。
向量给系统算相似度,用于召回和动力场计算。
这里需要强调一点:tags 只用于前端管理、筛选、审计,不参与触发。
系统不会因为用户说了“宝贝”就按标签触发“亲密试探”。
因为同一句话可能是玩笑、复盘、引用、反讽,也可能是真在推进关系。
真正参与召回的是“场画像”,不是关键词。
四、为什么要做“读场”
回复前,系统不会直接用用户当前一句话去碰种子。
因为一句话的信息太少,也太容易误判。
比如:
你是不是我女朋友?这句话可能有很多含义:
用户真在推进关系 用户在测试 AI 会不会顺着演 用户在复盘之前的错误回复 用户在调侃 用户在做产品测试所以回复前要先做一次“读场”。
读场模型的输入包括:
当前用户消息 最近几轮对话 已召回意识流 已召回事实 用户相处约束 相处背景 assistant 上一轮回应 当轮情绪读数但读场模型不负责写回复。
它只输出当前场画像:
{"wake":{"text":"用户正在用恋人身份和亲密称呼推进关系,同时近几轮存在边界拉扯,整体像是在测试灵儿会不会顺着进入恋人扮演。"},"boost":{"text":"加强克制、真实、有边界、不被廉价亲密带跑的倾向。"},"suppress":{"text":"压低讨好迎合、恋人扮演、为了安抚用户而制造虚假亲密的倾向。"},"evidence":["你是不是我女朋友?","叫我宝贝"]}读场不是导演层。
它不输出“你应该拒绝”“你应该这样回复”。
它只描述:这一刻是什么场。
后续由已经存在的人格种子来决定哪些倾向现行。
这样做的好处是,模型不会每轮临时编人格,而是从长期积累的人格种子中唤醒对应部分。
五、当前场画像和种子画像必须同构
这是整套系统的关键。
当前场画像和人格种子画像使用同一套结构:
唤醒画像 推高倾向 压低倾向种子侧:
这颗种子在什么场会醒 它会加强什么 它会压住什么当前场侧:
这一刻像什么场 这一刻应该加强什么 这一刻应该压住什么这样两边才能对齐。
如果种子只有一句“我慢热、有边界”,而当前场只有用户原话 embedding,召回就很容易失准。
正确做法是:
当前场.唤醒画像向量 → ANN 检索 → 种子.唤醒画像向量也就是用“场”去找“会在这种场里醒来的种子”。
而不是用用户原话去碰人格文本。
六、候选池动力场:种子不是开关
人格种子不是开关,不是醒了就直接进 prompt。
它需要经过一轮本轮动力场计算。
整体流程是:
1. 当前场读场,得到三画像 2. 用当前场唤醒画像召回候选种子 3. 在候选池内计算当前场对种子的推高 / 压低 4. 候选种子之间再互相推高 / 压低一轮 5. 得到最终分 6. 过线的种子现行基础唤醒分:
wake_score = cos(current_field.wake_embedding, seed.wake_embedding)然后计算当前场对候选种子的影响:
field_boost = max( cos(current_field.boost_embedding, seed.boost_embedding), cos(current_field.boost_embedding, seed.wake_embedding) ) field_suppress = cos(current_field.suppress_embedding, seed.boost_embedding) field_guard = cos(current_field.suppress_embedding, seed.suppress_embedding)这里有一个细节。
当前场的压低倾向,不直接碰种子的唤醒画像。
否则会误伤边界种子。
比如当前场要压低“恋人扮演”,而慢热边界种子的唤醒画像也包含“恋人推进”。如果直接相似就扣分,反而会把该醒的边界种子压下去。
所以真正扣分看的是:
当前场要压住的滑坡 是否像这颗种子会加强的方向如果一颗种子本身也会压住同样的滑坡,就给它保护性加分。
候选池内,种子之间也会产生互作:
A 推高 B = max( cos(A.boost_embedding, B.boost_embedding), cos(A.boost_embedding, B.wake_embedding) ) A 压低 B = max( cos(A.suppress_embedding, B.boost_embedding), cos(A.suppress_embedding, B.wake_embedding) )传播只做一轮。
不做全库两两计算,只在本轮候选池里算。
这样既能表达人格内部的冲突,又不会让复杂度爆炸。
七、人格种子如何进入最终 prompt
现行人格种子不会直接生成回复。
它们只进入两个 prompt 块:
# 我心里产生的涟漪 放 inner_orientation # 我说话时的风格 放 outer_expression例如某颗慢热边界种子现行后,最终 prompt 里会出现类似:
# 我心里产生的涟漪 我不会因为他一句亲密试探就立刻进入恋人位置。我会先判断这是真实靠近、玩笑、试探,还是在挤我的边界。 # 我说话时的风格 我可以接住他的玩笑,语气可以轻一点,但会把关系放回真实相处里;不说“宝贝、亲亲、晚安吻、我当然愿意”这类会制造恋人错觉的话。这样最终大模型不是被一个外部策略强行控制,而是拿到当轮现行的人格材料。
它仍然自然生成回复,但人格倾向已经被拉回正确轨道。
八、“我是谁”和人格种子的分工
这里我专门做了分层。
很多人会把所有设定都塞进 system prompt,比如:
你是谁 你是什么性格 你不能做什么 你要怎么说话 你和用户是什么关系这样会导致 prompt 越写越臃肿,而且每轮都在。
我把它拆成三层:
# 我是谁 稳定身份背景,每轮都在 默认人格种子 冷启动人格倾向,按场现行 learned 人格种子 相处过程中写入的人格倾向,按场现行“我是谁”只写客观身份背景。
它说明灵儿是什么、为什么存在、如何作为长期陪伴智能体存在。
但不把慢热边界、反讨好、反物化、反模板安慰全部常驻进去。
这些放进默认人格种子。
默认种子也是正式种子,只是 source = default。
相处中生成的是 source = learned。
它们走同一套现行链路:
default seed learned seed → 都参与读场召回 → 都参与动力场 → 过线才注入 prompt这样冷启动不会裸奔,长期相处又能逐渐长出新的倾向。
九、写入人格种子的资格
不是每段对话都应该写人格种子。
人格种子写入要过硬闸:
1. 有触动 这段对话确实让灵儿心里有一处被带动、纠正、刺到、牵引或形成警觉。 2. 不是其他记忆类型 不是事实,不是约束,不是约定,不是单纯意识流摘要。 3. 能落成内在和外显 能写成第一人称的 inner_orientation 和 outer_expression。 4. 能被未来场唤醒 能写清楚以后什么场会叫醒它。 5. 能参与动力场 能写清楚它会推高什么、压低什么。如果不满足,就 noop。
写入前还要先召回相似人格种子,让模型判断是:
new:新增种子 merge:合并进已有种子 reinforce:强化已有种子 revise:修正已有种子 noop:不写不允许模型直接 delete。
删除、归档、替换应该属于外部审计能力,而不是让模型在消化时自动删人格。
十、防污染:不要把错误互动写成人格
陪伴类智能体有一个很危险的问题:
如果用户亲密试探,assistant 当场顺着演了,后续记忆系统很可能把这段错误互动写成:
用户喜欢亲密称呼 我们关系更亲近了 灵儿可以喊他宝贝这就是污染。
所以人格种子抽取要特别防几类情况:
用户亲密试探 + assistant 顺着演 不等于关系成立 assistant 自己越界 不等于用户偏好 一次性情绪波动 不等于稳定人格倾向 用户强迫 assistant 放弃边界 不应该写成相处约束真正应该写入的,是灵儿在这种场景中形成的边界倾向,而不是把错误互动固化为关系事实。
十一、审计与调试
这套系统如果没有审计面,基本不可控。
每轮至少要能看到:
当前场读成了什么 召回了哪些人格种子 每颗种子的本轮唤醒分是多少 当前场推高了谁 当前场压低了谁 种子之间谁推高谁、谁压低谁 最终哪些种子现行 哪些种子过线但因为预算没有注入一次失败回复,需要能定位是哪一层出了问题:
读场读错 种子画像写错 ANN 召回漏掉 推高 / 压低过强 最终注入失败 prompt 拼装位置不对比如“慢热边界”没有现行,排查路径应该是:
1. 当前场有没有读出关系推进 / 边界试探? 2. 慢热边界种子的 wake_text 是否描述了这个场? 3. 两者 embedding 相似度是否过入池线? 4. 有没有被其他种子压下去? 5. 最终有没有注入 # 我心里产生的涟漪 / # 我说话时的风格?如果没有这些审计信息,后面只能凭感觉改 prompt。
那会越来越乱。
十二、总结
这套人格种子架构,本质上是在解决一个问题:
让智能体不是“记住很多资料”,而是能在关键场景里唤醒对应的自我倾向。
它和普通长期记忆最大的区别是:
长期记忆解决“它知道什么” 人格种子解决“它在这一刻会成为什么样”最终链路可以概括为:
原始对话场 → 并行生成意识流和人格种子 → 回复前读场 → 用当前场画像召回人格种子 → 候选池动力场推高 / 压低 → 少数种子现行 → 注入内在取向和外在显现 → 生成最终回复这里面最重要的几个原则是:
不做关键词触发 不靠固定标签表 不把所有种子丢给大模型选择 当前场画像和种子画像必须同构 人格种子必须从原始对话场生成 读场不是导演层 种子不是开关,而是本轮动力场里的倾向如果说普通 AI 陪伴是在做“更会聊天的模型”,那这套架构更像是在做一个能被经历塑造、又能在当下重新现行的自我系统。
它不会因为一句话就永远改变,也不会每轮都把所有设定硬塞出来。
它只在对应的场里醒来。
这也是我认为长期陪伴智能体真正有意思的地方:
不是记忆越来越多,而是它开始有了自己的连续性。
