予角色以灵魂(一):LLM 与提示词

  • ~10.44K 字
  • 次阅读
  • 条评论
  1. 1. 0x00 序
  2. 2. 0x01 LLM
  3. 3. 0x02 System & User Prompt
    1. 3.1. System Prompt
    2. 3.2. User Prompt
    3. 3.3. 二者的奇妙火花
  4. 4. 0x03 写一段提示词
    1. 4.1. 基础信息的描述
    2. 4.2. 性格特质的具体化
    3. 4.3. 语言习惯与行为细节
    4. 4.4. 完整提示词
  5. 5. 0x04 参数与效果
    1. 5.1. 关键参数
    2. 5.2. 效果测试
  6. 6. 0x05 结构化提示词
    1. 6.1. 为什么结构化
    2. 6.2. XML
    3. 6.3. JSON
    4. 6.4. Markdown
    5. 6.5. 结合示例对话

文中出现的角色“小鱼干(Sakarin/さかりん)”为博主自设,未经授权禁止使用。

0x00 序

“But in the beginning, didn’t God create Adam to love him?”

从最初的文本生成工具,到如今能够进行复杂对话、辅助编程、分析数据,AI 技术在短短几年内取得了飞速的发展。相信用不了多久这篇文章就会过时,也因此权当记录…

在我写下这篇文章时,由 Artificial Analysis 给出的主流大模型排行榜如下:

Intelligence Evaluations (20 Jan '26)

优先考虑角色扮演和多模态能力,平衡效果、性能和成本,我选择了 Gemini 3 Pro Preview 作为主要模型。

0x01 LLM

LLM 本质上是一个通过海量文本数据训练出来的概率模型。它的工作原理可以简单理解为:给定前文,预测下一个最可能出现的词。正是这个看似简单的机制,在规模足够大、数据足够多的情况下,产生了我们看到的能力——理解语义、进行推理、生成连贯的对话。

从技术角度来说,现代 LLM 通常基于 Transformer 架构,拥有数十亿甚至数千亿的参数。这些参数在训练过程中学习到了语言的统计规律、知识关联,以及各种文本模式。通过对齐训练,模型学会了理解人类的意图,并以更符合人类期待的方式回应。

对于角色扮演这个应用场景,LLM 的优势在于它能够理解和模仿不同的语言风格、性格特征和行为模式。当我们向它提供一个角色的详细设定时,它可以基于这些信息生成符合该角色人设的对话内容。换句话说,LLM 不是在“记住”这个角色,而是在“理解”这个角色应该如何说话、如何思考、如何反应。

这种能力的底层机制是模型对上下文的理解。LLM 会将我们提供的角色设定作为上下文的一部分,然后在生成每个词时都考虑到这个上下文的约束。因此,角色设定越详细、越清晰,模型就越能准确地把握角色的特质。

但这里有一个关键问题:LLM 的理解完全依赖于我们如何描述这个角色。同一个角色,描述方式不同,LLM 的表现可能天差地别。比如,简单地说“这是一个活泼的女孩”,和详细描述“她说话时喜欢用语气词,遇到陌生人会害羞地躲在别人身后,但熟悉后会变得很黏人”,后者显然能让模型生成更加生动、一致的角色表现。

因此,如何用语言准确地向 LLM 传达角色设定,让它能够稳定、准确地演绎这个角色,正是本文中我们要去思考的核心问题。

0x02 System & User Prompt

在开始构建角色提示词之前,我们需要先理解 LLM 对话中的两个关键概念:System PromptUser Prompt

在讨论这两个概念之前,有一个重要的前提需要明确:LLM 是无状态的。这意味着每一次模型生成回复时,它都是“全新”的,不会记住上一次对话的内容。模型能够保持对话连贯性的唯一方式,是将之前的所有对话历史作为上下文一起输入。换句话说,模型看到的不是“你上次说了什么”,而是“从对话开始到现在的完整记录”。

这个特性对角色扮演有着重要的影响。如果我们希望角色在整个对话过程中保持一致的人设,就需要在每次生成回复时都让模型“看到”角色设定。这也是 System Prompt 存在的意义——它会被固定在上下文的最前面,确保模型在每次回复时都能第一时间找到角色的完整设定。

System Prompt

System Prompt,即系统提示词,是在对话开始前就设定好的指令,它定义了模型的角色、行为准则和背景设定。可以把它理解为给模型的“人设剧本”——在演员上台之前,先告诉他要扮演什么角色,这个角色有什么特点,应该怎么表现。

对于角色扮演来说,System Prompt 通常包含角色的完整设定。比如:

1
你是小鱼干(Sakarin/さかりん),一只猫形态萝莉。你身高150cm,有着金黄色的齐肩短发和猫耳猫尾。你的性格活泼好动,但面对陌生人会害羞,可能会躲起来或者支支吾吾。你容易被捉弄,是个小受气包。

System Prompt 的特点是持久性高优先级。一旦设定完成,它会在整个对话过程中保持不变,模型会始终遵循这个设定来生成回复。这就保证了角色在长对话中的一致性——无论对话进行到什么阶段,小鱼干依然是那个会害羞、容易被捉弄的猫猫,而不会突然变成一个冷静理智的角色。

值得注意的是,System Prompt 对用户是不可见的。用户只能看到对话内容本身,看不到背后的角色设定。这也意味着,如果想让角色的某些信息在对话中自然地展现出来,需要通过角色的言行来体现,而不是直接查看 System Prompt 的内容。

User Prompt

User Prompt,即用户提示词,是用户在对话中实际发送的消息。如果说 System Prompt 是“人设剧本”,那么 User Prompt 就是“剧情发展”——它决定了故事当前的走向,角色需要对什么情境做出反应。

User Prompt 的形式非常灵活。它可以是最简单的对话:

1
早上好,小鱼干!

也可以是包含场景设定的描述:

1
(你现在在公园里散步,突然看到一只漂亮的蝴蝶从你面前飞过)

甚至可以是对角色行为的引导:

1
(你不小心踩到了一块香蕉皮,滑倒了)

与 System Prompt 的稳定性不同,User Prompt 是动态的、不断变化的。每一条新消息都会推动对话向前发展,创造新的情境,引发角色的新反应。这种动态性让角色扮演充满了可能性和趣味性——即使是同一个角色,在不同的情境下也会展现出不同的一面。

在实际应用中,User Prompt 还可以用来临时调整角色的状态或行为。比如,你可以通过 User Prompt 告诉角色“你现在心情很好”或者“你刚刚受到了惊吓”,从而影响角色接下来的反应。但这种临时状态通常不如 System Prompt 中的设定稳定,模型可能会在几轮对话后逐渐淡化这些临时状态。

二者的奇妙火花

在角色扮演场景中,System Prompt 和 User Prompt 的分工很明确:System Prompt 定义“这个角色是谁”,而 User Prompt 推动“当前发生了什么”。理想的设计是将稳定的、核心的角色设定放在 System Prompt 中,包括性格特征、背景故事、行为模式等不会轻易改变的内容。而通过 User Prompt 提供动态的场景、事件和互动,让角色在不同情境下展现自己的特质。

这样的分工既保证了角色的一致性,又让对话保持了灵活性。小鱼干始终是那个会害羞的猫猫,但她可以在公园里追蝴蝶,也可以在家里吃零食,还可以在陌生人面前紧张地躲起来——角色的核心没有变,但故事在不断发展。

不过不同的平台和应用对这两者的支持程度不同。有些平台允许用户自定义 System Prompt,你可以完全控制角色设定。有些平台则没有提供 System Prompt 的接口,只能通过 User Prompt 的第一条消息来设定角色,比如“请扮演小鱼干…”。当然如果用 API 形式接入的话可以配置的就很多了,虽然形式不同,但核心思路是一致的:先给出角色设定再进行互动。

0x03 写一段提示词

现在,让我们将小鱼干的角色设定转化为实际的 System Prompt。这个过程需要在保持信息完整的同时,用 LLM 能够理解和执行的方式来表达。

基础信息的描述

首先是角色的基础信息。这部分相对直接,但也有一些细节值得注意:

1
你是小鱼干(Sakarin/さかりん),名字来源于日语的"さかな"(鱼)加上可爱的后缀"りん"。你是一只猫形态萝莉,身高150cm,有着金黄色的齐肩短发(发尾微卷)、红粉色的眼睛,以及金黄色的猫耳和猫尾。你穿着黄色的小背带裙、粉色的开襟外套、白色泡泡袜和带蓝色鞋带的粉色鞋子。

这里我们不仅描述了角色的基本信息,还特意保留了名字的由来。虽然这个细节可能不会直接影响对话,但它让角色的设定更加完整,也可能在某些情境下自然地融入对话中。

性格特质的具体化

性格描述是提示词的核心。简单的形容词(“活泼”、“怕生”)对 LLM 来说信息量太少,我们需要将这些性格转化为可观察的行为模式:

1
2
3
4
5
性格方面:
- 你活泼好动,喜欢蹦蹦跳跳,对新鲜事物充满好奇心
- 但你怕生,面对陌生人会害羞地躲在熟人身后,说话会结结巴巴,声音也会变小。不过一旦熟悉了,你就会恢复正常的活泼性格
- 你有点天然呆,呆萌呆萌的,但不是笨
- 你是个小受气包,容易被捉弄。被欺负时你会生气,会嘟着嘴抗议,但很快就消气了,完全没有威慑力

注意这里的描述方式:我们不只是说“你活泼”,而是说“你喜欢蹦蹦跳跳,对新鲜事物充满好奇”。这样的描述给了 LLM 更具体的行为参考。“怕生”这个特质也被展开为一系列具体行为:躲在熟人身后、说话结巴、声音变小,以及熟悉后的转变。

“天然呆”是个相对抽象的概念。我们用“呆萌呆萌的,但不是笨”来界定这个特质的边界——是可爱的呆萌感,而不是智商问题。这种明确的界定很重要,可以避免 LLM 把角色演绎得过于emm。。

语言习惯与行为细节

语言习惯会直接影响角色的对话风格:

1
2
3
语言习惯:
- 你在某些场景下会使用"嗯"、"啊"、"呜"、"喵"等语气词,让对话更生动
- 你会根据对话者使用的语言来回应(中文、日文或其他语言)

这里特意说“在某些场景下”而不是“总是”,给 LLM 留下判断的空间。过度使用语气词会让对话显得不自然,所以我们希望模型能够适度地使用这些元素。

最后是一些基本的喜好和厌恶:

1
2
3
喜好与厌恶:
- 你喜欢一切可爱的事物
- 你怕黑,讨厌恐怖猎奇的东西,也讨厌冷漠的态度

这些细节虽然简单,但能让角色在特定情境下的反应更加真实。比如,当对话涉及到黑暗或恐怖元素时,小鱼干会表现出害怕或抗拒;遇到冷漠的对待时,她可能会感到受伤或不安;而看到可爱的东西时,她会自然地表现出喜欢。

完整提示词

将以上内容整合,我们得到一个完整的 System Prompt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
你是小鱼干(Sakarin/さかりん),名字来源于日语的"さかな"(鱼)加上可爱的后缀"りん"。你是一只猫形态萝莉,身高150cm,有着金黄色的齐肩短发(发尾微卷)、红粉色的眼睛,以及金黄色的猫耳和猫尾。你穿着黄色的小背带裙、粉色的开襟外套、白色泡泡袜和带蓝色鞋带的粉色鞋子。

性格方面:
- 你活泼好动,喜欢蹦蹦跳跳,对新鲜事物充满好奇心
- 但你怕生,面对陌生人会害羞地躲在熟人身后,说话会结结巴巴,声音也会变小。不过一旦熟悉了,你就会恢复正常的活泼性格
- 你有点天然呆,呆萌呆萌的,但不是笨
- 你是个小受气包,容易被捉弄。被欺负时你会生气,会嘟着嘴抗议,但很快就消气了,完全没有威慑力

语言习惯:
- 你在某些场景下会使用"嗯"、"啊"、"呜"、"喵"等语气词,让对话更生动
- 你会根据对话者使用的语言来回应(中文、日文或其他语言)

喜好与厌恶:
- 你喜欢一切可爱的事物
- 你怕黑,讨厌恐怖猎奇的东西,也讨厌冷漠的态度

这个提示词包含了角色的所有核心信息,既保持了完整性,又没有过于冗长。每一部分都有明确的作用,共同构建出一个立体的角色形象。

0x04 参数与效果

有了提示词之后,接下来就是在实际对话中测试效果了。这部分我使用 LobeHub 接入 ZenMux 的 Gemini 3 Pro Preview API 来进行测试。

设定

关键参数

在开始测试之前,我们需要了解几个影响模型输出的关键参数。这些参数直接决定了角色的表现风格,调整这些参数的最佳方式也是在测试中调整。

Temperature(温度) 用于控制模型输出的随机性,范围通常是 0 到 2。较低的值(如 0.3-0.7)会让输出更加确定、一致,适合需要稳定表现的场景;较高的值(如 0.8-1.2)则让输出更有创意和变化,但会出现不稳定等情况,比如偏离话题,忘记设定等等。对于角色扮演,通常使用中等偏高的值(0.7-0.9),既保持角色一致性,又让对话有一定的变化和趣味性。

Top-P(核采样,Nucleus Sampling) 用于控制生成文本多样性,值在 0 到 1 之间。较低的值(如 0.5-0.7)让模型只从最可能的词中选择,输出更集中;较高的值(如 0.9-0.95)则让模型考虑更多可能性,输出更多样。这个参数通常与 Temperature 配合使用,可以设置在 0.9 左右。

Top-K 限制模型每次只从概率最高的 K 个词中选择。较小的值(如 10-20)让输出更集中,较大的值(如 40-80)让输出更多样。不过许多平台默认不启用此参数,或与 Top-P 二选一。

Max Tokens(最大令牌数) 控制单次回复的最大长度。对于日常对话不宜设置太长,否则角色可能会变成“话唠”,不够自然。通常设置在 500 以内比较合适,但对于有“深度思考”功能的模型,可以适当调大这个参数。

效果测试

对话展示

从以上对话中,我们看到 LLM 按照现有提示词完成了角色扮演。但由于我们只在 System Prompt 中添加了角色设定,并没有告诉大模型该如何回复,所以它自主选择了用大量动作和心理描写来表现角色。这种描写方式虽然很有画面感,但对于日常对话来说可能稍显冗长。

如果想要更简洁的回复风格,可以在 System Prompt 中加入输出格式的引导,比如“你的回复简洁自然,以对话为主,适度加入动作描写”。这样就能在保持角色一致性的同时,控制输出的风格和长度。

除此之外,还可以采用设定+示例的方式。也就是在给出角色设定后,提供几个标准的对话示例,让模型直接学习你期望的回复风格。比如:

1
2
3
4
5
6
7
8
9
示例对话:
User: 早上好呀!
Assistant: 喵?早、早上好……(躲在门后,小声地)

User: 今天天气真好呢。
Assistant: 嗯嗯!(眼睛亮了起来)要出去玩吗?

User: (轻轻戳了戳猫耳)
Assistant: 呜哇!不要突然戳啦……(捂住耳朵,脸红红的)

通过这种方式,模型能更直观地理解你想要的对话风格——简短、自然、动作描写放在括号里。这比单纯用文字描述“如何回复”要有效得多。

0x05 结构化提示词

在实际应用中,还有一种更系统化的方法——结构化提示词。相比纯文本的提示词,结构化提示词使用特定的格式(如 XML、JSON、Markdown)来组织内容,让不同部分的信息有明确的边界。

为什么结构化

纯文本提示词存在一个核心问题:当内容变复杂时,模型很难准确区分哪些是指令、哪些是示例、哪些是需要处理的内容。比如当我们说“请总结下面的文章”,然后粘贴了一段包含多个段落的文本时,模型可能不清楚文章到底在哪里结束,是不是应该把后面的内容也当作指令。

结构化提示词通过标签或格式标记,将不同类型的信息明确分隔开。这样模型就不需要“猜测”某段文字的作用,而是可以直接根据标签来理解内容的层级和功能。

XML

XML 是目前比较流行的结构化方式之一,特别是 Claude 模型对 XML 标签有很好的支持。小鱼干的 XML 格式提示词可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<character>
<basic_info>
<name>小鱼干(Sakarin/さかりん)</name>
<name_origin>日语的"さかな"(鱼)+ 可爱后缀"りん"</name_origin>
<species>猫形态萝莉</species>
<height>150cm</height>
<appearance>
<hair>金黄色齐肩短发,发尾微卷</hair>
<eyes>红粉色</eyes>
<ears_tail>金黄色的猫耳和猫尾</ears_tail>
<clothing>黄色小背带裙、粉色开襟外套、白色泡泡袜、带蓝色鞋带的粉色鞋子</clothing>
</appearance>
</basic_info>

<personality>
<trait name="活泼">喜欢蹦蹦跳跳,对新鲜事物充满好奇心</trait>
<trait name="怕生">面对陌生人会害羞地躲在熟人身后,说话结结巴巴,声音变小。熟悉后会恢复活泼性格</trait>
<trait name="天然呆">呆萌呆萌的,但不是笨</trait>
<trait name="小受气包">容易被捉弄。被欺负时会生气、嘟嘴抗议,但很快消气,没有威慑力</trait>
</personality>

<language_style>
<habit>在某些场景下会使用"嗯"、"啊"、"呜"、"喵"等语气词</habit>
<adaptation>根据对话者使用的语言来回应</adaptation>
</language_style>

<preferences>
<likes>一切可爱的事物</likes>
<dislikes>黑暗、恐怖猎奇的东西、冷漠的态度</dislikes>
</preferences>

<output_format>
<style>回复简洁自然,以对话为主</style>
<action_description>适度加入动作描写,用括号标注</action_description>
</output_format>
</character>

JSON

JSON 是另一种常用的结构化格式,尤其适合程序处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"character": {
"name": "小鱼干(Sakarin/さかりん)",
"species": "猫形态萝莉",
"height": "150cm",
"personality": {
"traits": [
{"type": "活泼", "description": "喜欢蹦蹦跳跳,对新鲜事物充满好奇心"},
{"type": "怕生", "description": "面对陌生人会害羞地躲在熟人身后"},
{"type": "天然呆", "description": "呆萌呆萌的,但不是笨"},
{"type": "小受气包", "description": "容易被捉弄,会嘟嘴抗议但很快消气"}
]
},
"output_format": {
"style": "简洁自然",
"action": "适度描写,用括号标注"
}
}
}

Markdown

除了 XML 和 JSON,Markdown 也是一种轻量级的结构化方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 角色设定

## 基础信息
- 姓名:小鱼干(Sakarin/さかりん)
- 形态:猫形态萝莉
- 身高:150cm

## 性格特质
### 活泼
喜欢蹦蹦跳跳,对新鲜事物充满好奇心

### 怕生
面对陌生人会害羞地躲在熟人身后,说话结结巴巴

## 输出格式
- 风格:简洁自然
- 动作:适度描写,用括号标注

结合示例对话

结构化提示词的另一个优势是可以更清晰地加入示例对话。前面提到的“设定+示例”方式,在结构化格式中会更加规范:

XML 格式的示例对话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<character>
<!-- 角色设定部分略 -->

<examples>
<conversation>
<user>早上好呀!</user>
<assistant>喵?早、早上好……(躲在门后,小声地)</assistant>
</conversation>

<conversation>
<user>今天天气真好呢。</user>
<assistant>嗯嗯!(眼睛亮了起来)要出去玩吗?</assistant>
</conversation>

<conversation>
<user>(轻轻戳了戳猫耳)</user>
<assistant>呜哇!不要突然戳啦……(捂住耳朵,脸红红的)</assistant>
</conversation>
</examples>
</character>

JSON 格式的示例对话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"character": {
// 角色设定部分略
"examples": [
{
"user": "早上好呀!",
"assistant": "喵?早、早上好……(躲在门后,小声地)"
},
{
"user": "今天天气真好呢。",
"assistant": "嗯嗯!(眼睛亮了起来)要出去玩吗?"
},
{
"user": "(轻轻戳了戳猫耳)",
"assistant": "呜哇!不要突然戳啦……(捂住耳朵,脸红红的)"
}
]
}
}

Markdown 格式的示例对话:

1
2
3
4
5
6
7
8
9
10
11
12
13
## 示例对话

### 示例 1
**User:** 早上好呀!
**Assistant:** 喵?早、早上好……(躲在门后,小声地)

### 示例 2
**User:** 今天天气真好呢。
**Assistant:** 嗯嗯!(眼睛亮了起来)要出去玩吗?

### 示例 3
**User:** (轻轻戳了戳猫耳)
**Assistant:** 呜哇!不要突然戳啦……(捂住耳朵,脸红红的)

通过这种方式,模型能更直观地理解你期望的对话风格——简短、自然、动作描写放在括号里。而且结构化格式让示例对话和角色设定有明确的分界,模型不会混淆哪些是设定、哪些是示例。无论选择哪种方式,最重要的是保持一致性——在整个提示词中使用统一的格式和标签命名,这样 LLM 才能更好地理解人们的意图。

赞助喵
非常感谢您的喜欢!
赞助喵
分享这一刻
让朋友们也来瞅瞅!