额外输出格式#
我们会经常遇到不仅需要 AI 理解提示词, 还需要 AI 按要求输出内容的情况, 例如思维链、时间框、摘要总结、文字状态栏、变量更新格式等. 但输出内容中, 有的部分是应该原封不动输出出来的, 有的部分则是需要 AI 根据要求填充的.
为了让 AI 更好区分这些, 我研究了一套针对额外输出格式的提示词写法. 这种写法下:
要输出的内容与其他只是发给 AI 看的提示词有明显区分, AI 能更好理解怎样输出格式
可以引用其他提示词, 简单表达原本不好描述的输出格式
允许将输出格式模块化. AI 只该在特定情况下输出某一部分内容? 没问题!
假设我们要为玩家输出行动选项, 为了让选项更多样, 我们可能制作很多选项类型, 但我们显然不想让 AI 固定地输出:
1. 普通选项: 给出一种普通发展
2. 普通选项: 给出一种普通发展
3. 邪恶选项: 给出一种邪恶发展
我们希望的是, 动态发送十多种选项类型中的几种给 AI, 让 AI 根据类型要求制作 5~6 个选项——可点击的选择框就凭本文的方法实现了这样的选择框:

而日记络络凭本文实现了流式立绘、流式日记和 Galgame 界面:
此外, 妹妹请求你保护她露出 </青空莉/作品集/index>
凭本文实现了仅显示在场角色、每个角色有专属内容的状态栏:

特殊语法#
为了区分哪些是 AI 应该原封不动输出的格式, 哪些是 AI 需要根据要求填充的格式, 我采用了 AI 能听懂的几种特殊语法:
${描述}
: AI 需要根据 "描述" 将它替换为对应的内容. 例如衣着: ${具体描述角色当前衣着}
可能输出衣着: 粉金色宽松T恤睡裙
;$(要求)
: AI 仅会听从 "要求" 而不对它进行输出. 例如$(以下内容应该按英文输出)
会让 AI 更倾向于用英文输出之后的内容;...
: AI 需要仿照之前给定的规则和内容补充输出. 例如其他角色: ...
会让 AI 根据前面给定的络络
输出格式, 补充其他角色的输出;其他内容原封不动地进行输出.
以角色状态栏为例, 我们可以这样写:
■ ${角色名称,仅显示当前剧情中占比最多的一名角色}:
心情: ${用↑↓→来表示角色对<user>的好感度变化}(${变化原因})
衣着: ${具体描述角色当前衣着,最多20字}
身体: ${具体描述,注重人体每个部位的细节描写,包括声、形、色、味、感的整体表现,赋予真实体验感。用词淫靡,不低于40字,不超过60字}
行动: ...
计划: ...
■ 周围角色:
- ${周围某个没有和<user>互动的角色}: ${简要描述这个角色正在做什么}
则 AI 可能输出:
■ 心语:
心情: →(观望姐姐)
衣着: 粉金色宽松T恤睡裙
身材: 修长美腿随意交叠,浑圆的臀部若隐若现,琥珀色眼眸中闪烁着狡黠的光芒,嘴角挂着意味深长的笑容
行动: 思考姐姐的反常
计划: 暗中观察姐姐的动向
■ 周围角色:
- 千枝子: 准备就寝
整体格式#
而为了让 AI 知道某部分提示词是要用于输出的, 我采用 format: |-
来标记:
---
状态栏:
rule: 你必须在回复末尾插入<status_block>块来显示状态栏
format: |-
<status_block>
■ ${角色名称,仅显示当前剧情中占比最多的一名角色}:
心情: ${用↑↓→来表示角色对<user>的好感度变化}(${变化原因})
衣着: ${具体描述角色当前衣着,最多20字}
身体: ${具体描述,注重人体每个部位的细节描写,包括声、形、色、味、感的整体表现,赋予真实体验感。用词淫靡,不低于40字,不超过60字}
行动: ...
计划: ...
■ 周围角色:
- ${周围某个没有和<user>互动的角色}: ${简要描述这个角色正在做什么}
</status_block>
这里, 我将状态栏用 <status_block>
块包裹, 便于我们可能希望用正则处理状态栏. 例如用 最小深度: 8
的 仅格式提示词
正则将状态栏替换为空, 从而不发送过早的状态栏, 节省 token 和 AI 注意力.
考虑插入深度#
在此我给的状态栏例子比较简单, 但你可能需要输出露出系统、Galgame 界面等更为复杂的格式.
我们显然不希望将这些东西一股脑插入到 D1/D0, 这很容易让 AI 失去对末尾剧情的连贯理解.
为此, 通常的做法是, 将完整的输出提示词放在 D4 等位置, 而在 D1/D0 位置仅利用输出格式的 <tag>
来提醒 AI 输出:
---
状态栏强调:
rule: 你必须在回复末尾插入<status_block>块来显示状态栏
format: |-
<status_block>
...
</status_block>
嵌套输出内容#
${描述}
不仅可以是文字描述, 还可以是其他提示词.
假设我们要实现多角色状态栏, 每个角色不仅有共同的 "好感度"、"衣着" 等内容, 还可能在状态栏中有自己独有的信息. 通过我的提示词写法, 你会很容易写出这样的状态栏:
---
状态栏:
rule: 你必须在每次回复的最底部,输出<status_block>包裹的状态栏
format:
basic: |-
<status_block>
<character>
名称: ${角色名称}
心情: ${用↑↓→来表示角色对<user>的好感度变化}(${变化原因})
衣着: ${具体描述角色当前衣着,最多20字}
${special status}
</character>
...
</status_block>
special status:
心爱: |-
路人视角: ${考虑穿着、外在遮掩和角度,来判断非<user>角色能够看到什么样子的心爱}
露出程度: ${当前露出程度数值}
心语: |-
约会模拟次数: ${约会模拟次数}
上面的提示词中, 我们在 basic
部分定义所有角色共同的状态栏内容, 而在 special status
部分定义每个角色独有的状态栏内容. basic
内我们用 ${special status}
来引用 special status
部分内容, 从而指示 AI 输出.
我在妹妹请求你保护她露出中就是这么做的.
另#
另外在组合和命名提示词中, 还提示了可以用 YAML 锚点来引用内容, 它可以解决这里给出方式比较难表述的情况.
示例#
任务系统提示框#
实例: 妹妹请求你保护她露出
---
略:
condition: 略
format:
basic: |-
<ExposurePrompt>
[系统提示|${title}|${content}$(禁止在content后输出 |)]
</ExposurePrompt>
specific prompt:
- title: ${难度}任务进度
content: ${任务内容}(${进度})$(任务奖励不应在任务完全完成前减少)
- title: 已完成${难度}任务-${任务内容}
content: 获得${奖励}点数
special: 此外,输出一个`更换${难度}任务`提示
- title: 更换${难度}任务
content: ${特定内容,必须包含明确目标、目的地、交通方式、到达动作等}(${进度}) - ${奖励}点数
- title: 已兑换商品-${商品名称}
content: 扣除${价格}点数
special: 此外,输出一个`更换商品${编号}-${物品名称}`提示
- title: 更换商品${编号}-${物品名称}
content: ${商品效果,必须包含使用次数/持续时间/使用条件或其他限制}(${一果对商品的评论,比如'哥哥看不见这个商品哦~',不超过10个字}) - ${价格}点数
- title: 遭遇特殊${事件/任务/商品}
content: ...
- ...
立绘列表#
实例: 日记络络
---
立绘列表:
文件信息:
格式: PNG
后缀: .png
不在场表示: 无人
服装:
- 开衫
- 格纹衫
- 水手服
- 睡衣
表情:
- 微笑
- 浅笑
- 哭泣
- 擦眼泪
# 特殊表情及其含义(并非严肃而是用于有趣的幽默剧情,例如并不是只有生气的时候才能使用生气和猫爪生气,开玩笑的时候也可以用,其中猫爪生气是举起一只手比作猫爪状,在络络和<user>开玩笑的时候可以用)
- 看透一切的坏笑: 发现秘密时的调侃表情
- 邪恶的坏笑: 更强烈的坏笑版本
- 星星眼: 强烈崇拜或兴奋状态
- 晕晕眼: 大脑当机状态
- 猫爪生气: 举猫爪的可爱威胁
调用格式: ${服装}/${表情}.png
允许自定义选项类型的选择框#
实例: 可点击的选择框
---
略:
rule: 略
format:
basic: |-
<roleplay_options>
```
${按照content而非type拟定标题标题}:${content,必须以第三人称输出行为的主语}
...$(generate {{random::4::5::6}} options based on `option type` below)
```
</roleplay_options>
option type: [
{
type: '普通发展',
content: '${根据当前的情况,合理构思当前可能出现的不同发展}'
},
{
type: '跳过场景',
content: '${当前场景描写十分足够时,用概括性的语言来跳过当前动作的描写并展开下一场景进行新描写,剧情上保持连贯}',
rule: '必须给出一个跳过场景选项'
},
]
...
用 Typescript 语法定义 JSON 格式输出#
实例: 日记络络
---
略:
rule: 略
type: |-
interface Chat {
speaker: string; // 当前发言者的名字或`旁白`二字
message: string; // 当前发言者的台词
background: string; // `背景列表`中支持的当前所处地点位置、时间等的背景文件名
characters: {
name: string; // 角色名称
expression: string; // 表情,从`立绘列表`中选择存在的立绘文件名
costume: string; // 服装,仅有格纹衫、开衫、水手服、睡衣和全裸五个选项
}[];
}
format: |-
${以JSON格式输出Chat[],包含不少于10个Chat,不多于15个Chat}
由于这往往涉及前端界面代码, 你可能会用 zod 检验数据, 则也可以尝试直接用 zod 定义数据结构然后在 format
内引用.
---
略:
rule: 略
type: |-
const Chat = z.object({
speaker: z.union([z.literal('旁白'), z.string()]),
message: z.string().describe('当前发言者的台词'),
background: z.enum(['白天', '黄昏']),
characters: z.array(
z.object({
name: z.literal('络络'), // 目前只有络络这一个角色
expression: z.enum(['微笑', '浅笑', '生气', '惊讶', '害羞']).catch('微笑'),
costume: z.enum(['水手服', '格纹衫', '开衫', '睡衣', '全裸']).catch('水手服'),
}),
),
});
type Chat = z.infer<typeof Chat>;
format: |-
${以JSON格式输出Chat[],包含不少于10个Chat,不多于15个Chat}