人物描述失控,最直观的表现是:你给AI设定了一个“蓝瞳、冷静、说话简短”的角色,前两轮还很准确,到了第三轮蓝瞳变成灰瞳,人物开始用长句感叹,甚至性格也往温柔方向漂移。很多人把这种现象归因为上下文窗口不够或者模型能力不足,但实际测试中,更常见的原因是角色描述本身缺乏层次,所有信息被压成一段平铺文本,模型很难判断哪些属性在任何情况下都不能变,哪些属性只是临时状态。要解决这个问题,不能继续往提示词里堆更多描述,而是要把角色卡结构化,并给属性设定明确的优先级。

一、人物描述失控的根源:平铺描述与隐性冲突
当角色设定以整段自然语言出现时,模型会把它当作一段背景信息来处理。背景信息在生成过程中会被前面的对话内容不断挤压,尤其是当上下文变长时,模型对早期描述的关注度会下降。这不是简单的遗忘,而是注意力分配问题:所有属性共享同一层级,没有任何机制告诉模型哪一条必须被优先保留。出现冲突时,模型会根据自己的训练偏好临时选择一个,结果就是角色漂移。
另一个容易被忽略的问题是属性之间的隐性冲突。比如你同时写了“角色沉默寡言”和“角色在紧张时会详细解释自己的计划”,这两条看似都有用,但模型在具体场景中很难判断该遵循哪一条。如果沉默寡言是核心性格,紧张时详细解释是特定情境表现,那么必须让模型知道后者的优先级低于前者,否则就会出现人物前后不一致。平铺描述无法表达这种层级关系,只有引入结构才能解决。
二、分层描述法:把角色卡拆成核心层、表现层与情境层
分层描述法的核心思路是把角色信息按照稳定程度划分成不同层级。最稳定的属性放在核心层,例如姓名、基础外貌、核心性格、说话风格的总基调;表现层放可观察但可能随情境变化的特征,例如当前情绪、临时装扮、当前语气;情境层放只在特定条件下生效的描述,例如战斗时更果断、面对特定人物时更谨慎。可变层则用于记录短期状态,例如受伤、醉酒、淋雨后的状态。这样模型每次生成时都能快速定位哪些是长期约束,哪些是短期条件。
以JSON结构存储角色卡是比较直接的方式。下面是一个简化示例,核心层只放不可变信息,表现层与情境层分开维护。这样即使在长对话中截断或压缩历史,也能优先保留核心层内容。
{
"core": {
"name": "林澈",
"eye_color": "蓝色",
"personality": ["冷静", "克制", "不轻易表露情绪"],
"speech_style": "短句为主,很少用语气词"
},
"surface": {
"current_mood": "平静",
"current_outfit": "深灰色风衣",
"current_expression": "眉头微皱"
},
"situational": {
"when_threatened": "语速加快,但仍保持逻辑",
"when_with_ally": "略微放松,会多说半句"
},
"volatile": {
"fatigue": "低",
"injury": "无"
}
}
这个结构本身不增加总信息量,但它改变了信息的组织方式。模型在生成时更容易按照层级去检索,而不是面对一整段混杂文字。实际操作中,你还可以把核心层进一步排序,比如外貌中的瞳色、发色属于一旦设定就不能变的属性,而表情则属于表现层。将稳定属性前置,也能减少生成时被忽略的概率。
分层描述法并不能自动解决冲突,它只是给冲突解决提供了基础。如果两个属性都放在核心层,例如“极其冷静”和“极易暴怒”同时存在,冲突仍然会发生。因此下一步需要给属性设置优先级,让冲突可以被规则裁决。
三、角色属性优先级排序:冲突检测与覆盖策略
角色属性优先级排序可以理解为一套简单的冲突裁决机制。给每个属性分配一个优先级数值,高优先级的属性在冲突时覆盖低优先级属性。比如核心性格优先级为100,情境表现优先级为60,临时状态优先级为30。当“冷静”和“情绪激动”同时被触发时,系统会比较两边来源的优先级,最终输出高优先级约束下的表达方式。比如人物可以语速变快、音量提高,但不会出现冲动决策,因为决策逻辑受核心性格保护。
实现上不必复杂,用一张权重表加一个检查函数就够了。下面是一个简单的Python示例,演示如何判断当前输出是否违反了高优先级属性。
PRIORITY = {
"core_personality": 100,
"core_appearance": 95,
"speech_style": 90,
"situational": 60,
"volatile": 30
}
def check_conflict(generated_content, character_card):
# 这里的匹配逻辑可以替换为更精细的文本分析
for attr, priority in character_card.items():
if attr in generated_content:
# 假设每条属性带有自己的优先级
attr_priority = character_card[attr]["priority"]
if attr_priority < 50:
print(f"低优先级属性 {attr} 被输出,需要人工检查")
# 如果低优先级属性压制了高优先级属性,则给出警告
return True
实际使用中,你不需要让AI真的去执行这段代码,而是可以把优先级规则写进系统提示词或角色卡说明里。例如明确告诉模型:当核心性格与情境描述发生冲突时,以核心性格为准;当表情状态与核心外貌冲突时,以核心外貌为准。这样模型在采样时就有了一条明确的决策路径,而不是每次都重新权衡全部信息。
排序的另一个作用是控制上下文裁剪。在长对话中,如果必须截断历史,可以按优先级保留信息:核心层全部保留,情境层只保留最近三次触发,可变层直接丢弃或压缩成状态摘要。这样即使上下文窗口有限,人物长期一致性也不会崩盘。你在系统里维护两个缓冲区,一个是高优先级长期记忆,一个是低优先级短期状态,生成时先加载长期记忆,再叠加短期状态,冲突时按优先级覆盖。
四、从提示词到生成结果:一套可落地的稳定性流程
把分层描述法和优先级排序结合起来,可以形成一套比较完整的角色稳定性控制流程。第一步,把角色卡按核心层、表现层、情境层、可变层拆分,并给每条属性标注优先级。第二步,在每次调用模型前,只把当前场景需要的情境层和可变层拼接到核心层之后,避免无关信息抢占注意力。第三步,在生成结果返回后,用简单的关键词或正则规则检查是否出现了与核心层冲突的描述,如果出现就要求模型重新生成或进行修正。
下面是一个系统提示词片段的示例,它把优先级规则直接写入格式说明,模型在生成时可以有意识地遵循。
角色信息分四层: 1. 核心层:不可改变,优先级最高。 2. 表现层:可能随情境变化,但不得与核心层冲突。 3. 情境层:只在特定条件下生效。 4. 可变层:临时状态,可被后续事件覆盖。 当不同层级的描述发生冲突时,按以下顺序覆盖: 核心层 > 表现层 > 情境层 > 可变层。
这个片段虽然简单,但它把隐性的层级关系变成了显性规则。很多角色漂移并不是模型做不到,而是提示词里没有把规则说清楚。加入优先级后,模型在生成“蓝瞳”还是“灰瞳”时,如果核心层明确写了蓝瞳,那么表现层里即使出现“灰瞳”也应被视为无效。开发者可以在后处理阶段做一次硬校验:凡是核心层禁止出现的词,直接替换或触发重试。
还有一种更工程化的做法是给角色属性建立版本号。每次角色状态发生变化,只更新对应层级的字段,并记录一次版本快照。这样在调试生成结果时,可以快速定位是哪一层被错误覆盖。比如角色在中途受伤,可变层增加了“左臂划伤”,但后续对话里左臂又正常活动,说明可变层被其他状态忽略,需要提高可变层的优先级或加强状态校验。版本快照让这些问题从玄学变成可排查的逻辑问题。
综合来看,解决人物描述失控不需要推翻现有提示词,也不需要一味增加描述字数。把角色信息按稳定程度分层,再为每一层设定优先级和冲突覆盖规则,就能让模型更稳定地把握角色核心。对于小说生成、AI角色扮演、游戏NPC对话等场景,这套方法都能明显降低OOC概率,而且实现成本很低。