额外输出格式#

帖子: 类脑/旅程

我们会经常遇到不仅需要 AI 理解提示词, 还需要 AI 按要求输出内容的情况, 例如思维链、时间框、摘要总结、文字状态栏、变量更新格式等. 但输出内容中, 有的部分是应该原封不动输出出来的, 有的部分则是需要 AI 根据要求填充的.

为了让 AI 更好区分这些, 我研究了一套针对额外输出格式的提示词写法. 这种写法下:

  • 要输出的内容与其他只是发给 AI 看的提示词有明显区分, AI 能更好理解怎样输出格式

  • 可以引用其他提示词, 简单表达原本不好描述的输出格式

  • 允许将输出格式模块化. AI 只该在特定情况下输出某一部分内容? 没问题!

假设我们要为玩家输出行动选项, 为了让选项更多样, 我们可能制作很多选项类型, 但我们显然不想让 AI 固定地输出:

1. 普通选项: 给出一种普通发展
2. 普通选项: 给出一种普通发展
3. 邪恶选项: 给出一种邪恶发展

我们希望的是, 动态发送十多种选项类型中的几种给 AI, 让 AI 根据类型要求制作 5~6 个选项——可点击的选择框就凭本文的方法实现了这样的选择框:

../../../../_images/%E9%80%89%E6%8B%A9%E6%A1%86%E9%80%89%E9%A1%B9.png

日记络络凭本文实现了流式立绘、流式日记和 Galgame 界面:

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

../../../../_images/%E7%8A%B6%E6%80%81%E6%A0%8F.png

特殊语法#

为了区分哪些是 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}