♻️

プロンプトジェネレータで実現する持続可能なLLMプロダクト開発を目指す

2024/12/13に公開

この記事はLLM・LLM活用 - Qiita Advent Calendar 2024 - Qiita の12日目で、プロンプトジェネレータについてのものです。

いまはもう12/13かもしれませんが、僕の心はまだ2024/12/12なのです。

プロンプトジェネレータとは、LLMへの指示(プロンプト)を動的に生成するためのツールです。

多くの場合、プロンプトは静的なテキストとして書かれますが、それには大きな課題があります。LLMの進化が速く、昨日有効だったプロンプトが今日は機能しないかもしれません。あるいは、より良い方法が発見される可能性もあります。そのたびにプロンプトを書き換えていくのは、まさに技術的負債の山を築いているようなものです。

本記事では、TRPGのシナリオ生成という具体例を通じて、プロンプトジェネレータの実装方法とその利点を解説します。

プロンプトジェネレータについて、この記事を読んで得られる知見は以下の通りです。

  1. プロンプトの構造化によるメンテナンス性の向上
  2. 異なるモデルやモデルの進化に対応が可能
  3. プロンプトの品質管理
  4. 開発効率の向上

また、筆者がプロンプトへの落とし込みをする過程についても書いているため、それも参考になるかもしれません。

以前、会社のブログで、Zodスキーマでプロンプト生成を行い構造化データを自由自在に扱えて、LLMプロダクト開発が圧倒的に効率化した話 - Algomatic Tech Blogという記事を書きました。これは与えたスキーマ定義を元にプロンプトを生成することで、自由記述の文章から構造化データを取り出すという記事です。このときの手法は細かい形で変遷しつつも業務で今も役立っております。これも今回と同じくプロンプトジェネレータについての記事でした。

今回の内容は、個人の研究としてスキーマ定義を使わないプロンプトジェネレータという可能性を手探りしているところです。スキーマ定義もこったものを作るとメンテナンス性が落ちるため、抽象度の高いI/Fを模索しています。

現状でもある程度の品質を出せてはいますが、まだI/F設計など考慮に値する要素が多く存在します。よろしければ皆さんのお知恵をお貸しください。

最初書こうとしていたもの

LLMアドベントカレンダーではLLMを作ったりする方向性が多いため、本当はLLMの持つケーパビリティについて書こうかとも考えたのですが、時間が足りない!ってなったので、方向性を変えました。ローカルLLMの持つケーパビリティを探るのはなかなか楽しいんですが、思ったよりも工数がかかっています。

いずれそちらに関しても記事にできればと思っています。

題材を変えてもやっぱりタイムオーバーしてしまった著者です。

前提

プロンプトジェネレータについての説明など、今回の記事の前提となる部分を説明します。

今回使う題材

今回使う題材は青空文庫で公開されている小説で「ろくろ首」です。小泉八雲ことラフカディオ・ハーンさんが書いたものです。

ちなみにこれを読んでて初めて知ったんですが、ろくろ首というと、首が伸びる妖怪のイメージしかなかったんですが、首が離れて飛ぶというデュラハンみのあるバージョンのろくろ首もいるんですね。

これを元にTRPGシナリオブックを作るというテーマでプロンプトジェネレータの力を皆さんにお見せしたいと思っています。

TRPGはテーブルトークRPGの略で、皆さんが知っているであろうRPGの元祖です。元々はテーブルトークがRPGと呼ばれていたもので「コンピュータRPG」は後で登場しました。言葉的にはレトロニムの一種です。動画で、クトゥルフゆっくりリプレイなどを見たことがある人もいるかもしれません。

ざっと説明すると、ゲームマスターとプレイヤー数人が遊ぶ卓上ゲームで、用意されたシナリオを元にゲームマスターがRPGのジャッジ(マスタリングという)をする遊びです。言ってしまえば「人力RPG」ですね。

時間がかかる、人が集まる必要があるなどの難点はありつつも、自由度の高さから根強い愛好者がいます。ラノベ作家の人なんかもTRPGを好きなひとは一定数いるため、TRPGネタにクスっとなることもしばしばです。

さて、TRPGにおいてシナリオを用意するというのは大変なことです。プレイヤーたちの好みを元に、シナリオや登場するキャラクタ(Non Play Character/NPC)を用意する必要があります。コンピュータRPGしかプレイしたことがない人でも、シナリオとキャラを用意する大変さは想像がつくのではないかと思います。

そのため、わざわざ自作しなくてもいいように、商業・同人問わず、シナリオとキャラや詳細データをセットにした、シナリオブックというものが販売されていたりします。

そこで我らがLLMの登場です。今回は「ろくろ首」の小説をプロンプトジェネレータを駆使してシナリオブックにしてみます。

  • シナリオブックは、誰でもマスタリングしやすいように、統一された書き方で書かれています
  • 登場するNPCのデータ、性格、背景、行動指針などが書かれています
  • シナリオが書かれています。序盤の導入の仕方、中盤の様子、クライマックスへの持って行きかたや、用意されているオチなどで成り立っています

プロンプトジェネレータ

改めて説明しますが、プロンプトテクニックの多くは、モデルが更新されると陳腐化してしまいます。たとえばロールプレイをさせるプロンプトは世界でも有名なテクニックで未だに愛好者も多そうですが、いまどきのモデルではほぼ無意味です。「あなたは世界一のハッカーとして振る舞ってください」みたいなものです。

すべて意味がないわけではなく「ハッカーの観点で」くらいまで情報を縮められるというだけですが、その情報が必要かどうかも状況によります。むしろ「ソーシャルハックの観点で」のように具体的な観点を明示した方が確実という程度です。

LLMは、種類やそれを開発したタイミングで、どういう指示の方法が良いのか?という傾向が異なります。ClaudeはXMLを与えるべきですが、他のLLMはそうとは限りません。新しいテクニックも度々生まれます。

  • 入力形式
  • 指示の与え方
  • 出力形式

これらが、モデル進化やコスト構造の変化で、新しい別のものに置き換えないといけなくなったら、さすがにメンテしてられないですよね?

そういった不確実性が極めて高い環境でプロンプトをメンテナンスする立場としては、具体に踏み込まず、抽象化された指示のみを出したいところです。そこでプロンプトジェネレータが重要となります。

順を追って考えさせる

LLMは、一足飛びに考えてしまう癖があります。元々機械学習が人間の直感・直観に似た仕組みだからというのはありますが、それで間違った答えを出されるのは困りものです。

そこで明示的に順を追って考えさせる手法が登場しました。Chain of Thought(CoT)などです。そのあと登場したゼロショットCoTなんかは皆さんも使ったことがあるかもしれません。いわゆる「step by step」です。

ただし、筆者は、現行のモデルにおいては「step by step」は確実性が低く、運が良ければ精度が上がるかもしれないおまじないくらいに思っています。明示的に順を追って行くことそのものが重要です。

そして、この「順を追って考える」という考え方をさらに発展させたのが、LLMに自身の思考プロセスを分析させる「メタ認知プロンプト」です。

メタ認知プロンプトとは

メタ認知プロンプトは、LLMに自身の思考プロセスや出力を分析・評価させる手法です。人間でいえば「なぜそう考えたのか」「この結論は妥当か」といった自己分析的な思考に相当します。

たとえば、Claude 3.5-SonnetでArtifactsを扱うためのプロンプトでは、「なぜそれをするのか?」の理由を考えさせています。

具体例を見てみましょう。

# 通常のプロンプト
シナリオの結末を考えてください

# メタ認知プロンプト
1. まず結末案を出力してください
2. その結末について、以下を確認してください:
   - 前提となる伏線は十分に張られているか
   - 登場人物の動機と整合しているか
   - プレイヤーにとって納得できる展開か
3. 問題点があれば修正案を提示してください

利点は、

  • 出力の品質が向上する(自己チェックが入るため)
  • 論理的な矛盾を減らせる
  • 文脈や制約との整合性が高まる

などがありますが、プロンプトが長く複雑になるため、適切なバランスを取ることが重要です。プロンプトの複雑度が上がると指示に従わなくなる傾向があるため、LLMをねじ伏せる豪腕が必要になってきます。

使い勝手のいいメタ認知プロンプトは、以下の通りです。

  • 状況の俯瞰
  • 目的や制約条件
  • 自分の理解
  • 誰の視点か
  • 根拠
  • 分解的な思考

スキーマを使ったメタ認知

スキーマベースのプロンプトジェネレータなら、スキーマという形で踏み込んで指示を出すことができます。たとえばこれは、前述のメタ認知の一覧をClaude Sonnetに食わせて生成させたスキーマです。ちゃんと検証はしてないですが、この程度でも面白い分析ができるようになります。本格的に使うなら指示のすべてに従ってもらうための工夫など、もっと細かく調整することになりますが、スキーマを使ったメタ認知プロンプトの基本は抑えられています。

Claude Sonnetに作らせたスキーマ
import { z } from "zod";

const hypothesisSchema = z.object({
  initialHypothesis: z.string()
    .describe("最初の仮説を記述してください"),
  validationPoints: z.array(z.string())
    .describe("仮説の妥当性を検証するためのポイントをリストアップしてください"),
  counterArguments: z.array(z.string())
    .describe("考えられる反論や代替案を列挙してください"),
  conclusion: z.string()
    .describe("検証を踏まえた最終的な結論を記述してください")
}).describe("仮説検証のプロセス");

const decompositionSchema = z.object({
  subComponents: z.array(z.string())
    .describe("問題を構成する小さな要素を列挙してください"),
  relationships: z.array(z.object({
    from: z.string().describe("関係性の出発点となる要素"),
    to: z.string().describe("関係性の終点となる要素"),
    type: z.string().describe("要素間の関係性の種類")
  })).describe("要素間の関係性を記述してください"),
  synthesisApproach: z.string()
    .describe("分解した要素をどのように統合して解決するかの方針を記述してください")
}).describe("問題の分解と統合のプロセス");

const comparativeSchema = z.object({
  similarCases: z.array(z.object({
    case: z.string().describe("類似事例の概要"),
    similarities: z.array(z.string()).describe("現在の問題との共通点"),
    differences: z.array(z.string()).describe("現在の問題との相違点")
  })).describe("類似事例との比較分析"),
  lessonsLearned: z.array(z.string())
    .describe("過去の経験から適用できる学びを列挙してください"),
  approachComparison: z.array(z.object({
    approach: z.string().describe("検討するアプローチ"),
    pros: z.array(z.string()).describe("そのアプローチの長所"),
    cons: z.array(z.string()).describe("そのアプローチの短所")
  })).describe("異なるアプローチの比較分析")
}).describe("比較による思考プロセス");

const impactAnalysisSchema = z.object({
  directEffects: z.array(z.string())
    .describe("直接的な影響をリストアップしてください"),
  indirectEffects: z.array(z.string())
    .describe("間接的な波及効果をリストアップしてください"),
  risks: z.array(z.object({
    description: z.string().describe("リスクの内容"),
    probability: z.enum(["低", "中", "高"]).describe("発生確率"),
    impact: z.enum(["小", "中", "大"]).describe("影響度"),
    mitigation: z.string().describe("リスク軽減策")
  })).describe("想定されるリスクとその対策"),
  opportunities: z.array(z.object({
    description: z.string().describe("機会の内容"),
    benefit: z.string().describe("期待される利益"),
    requirements: z.array(z.string()).describe("機会を活かすために必要な条件")
  })).describe("想定される機会")
}).describe("影響分析のプロセス");

const prioritizationSchema = z.object({
  items: z.array(z.object({
    task: z.string().describe("検討項目"),
    importance: z.number().min(1).max(5).describe("重要度(1-5)"),
    urgency: z.number().min(1).max(5).describe("緊急度(1-5)"),
    effort: z.number().min(1).max(5).describe("必要な労力(1-5)"),
    dependencies: z.array(z.string()).describe("この項目が依存する他の項目")
  })).describe("優先順位付けが必要な項目のリスト"),
  constraints: z.array(z.string())
    .describe("リソースや時間などの制約条件をリストアップしてください"),
  executionOrder: z.array(z.string())
    .describe("最適な実行順序を記述してください")
}).describe("優先順位付けのプロセス");

const qualityAssessmentSchema = z.object({
  accuracyChecks: z.array(z.object({
    aspect: z.string().describe("確認する側面"),
    method: z.string().describe("確認方法"),
    result: z.string().describe("確認結果")
  })).describe("正確性の確認項目"),
  consistencyChecks: z.array(z.object({
    aspect: z.string().describe("一貫性を確認する項目"),
    findings: z.string().describe("確認結果"),
    improvements: z.string().optional().describe("必要な改善点")
  })).describe("一貫性の確認項目"),
  completenessEvaluation: z.object({
    coveredAspects: z.array(z.string()).describe("カバーされている側面"),
    gaps: z.array(z.string()).describe("不足している部分"),
    recommendations: z.array(z.string()).describe("改善のための提案")
  }).describe("完全性の評価")
}).describe("品質評価のプロセス");

const metaThoughtProcessSchema = z.object({
  hypothesis: hypothesisSchema,
  decomposition: decompositionSchema,
  comparative: comparativeSchema,
  impactAnalysis: impactAnalysisSchema,
  prioritization: prioritizationSchema,
  qualityAssessment: qualityAssessmentSchema,
}).describe("メタ思考プロセス全体の構造");

export type MetaThoughtProcess = z.infer<typeof metaThoughtProcessSchema>;
export default metaThoughtProcessSchema;

TRPGシナリオブックの生成を題材にプロンプトを作り込む

それでは「ろくろ首」を元にTRPGで使えるシナリオブックを生成するためのプロンプトを作りましょう。

筆者がプロンプトを作り込むとき、API経由ではなく、Claudeのウェブ版やClaude Desktopアプリなどを使って、試しにプロンプトを投げることが多いです。これなら月額固定費用で、気兼ねなく投げ放題です。ただし、トークン数が膨大だとレートリミットに引っかかってしまうこともあります。この記事の執筆中にも一度レートリミットに引っかかって絶望していました。

TRPGで使えるシナリオブックを作ろうとしたとき何が必要でしょうか?小説であればプロットでしょうか。人によっては台詞やシーンを先に思いついてそれを元に書いていくパターンもありますね。もちろんTRPGのシナリオブックも同じです。

  • 何が起きるか?
  • 誰が登場人物か?

がおそらく最初にあるはずです。

ファンタジー系RPGの定番であるゴブリン退治なんかも「ゴブリンが村を襲って困っている」という事件が起きていて、「ゴブリン」「村人」なんかの登場人物がいます。

TRPGのシナリオでは一回のプレイ(セッションと呼ぶ)の間に、伏線を張って回収する必要があります。もちろん、短いセッションや初心者向けセッションなど、伏線を張らないスタイルもありますし、逆にキャンペーンと呼ばれる複数回のプレイのための伏線もあります。

さて、プロンプトジェネレータを使わずにまずはプロンプトを手書きしてみましょう。

プロンプトv2

今回、筆者が最初に作ったプロンプトらしいプロンプトです。v1は添付の小説を元にTRPGのシナリオを作ってだけの工夫が何もないプロンプトでした。

添付の小説を元にこれを題材にTRPG(4人以上)用のシナリオを作成して

# 出力

* シナリオのテーマ
* 舞台設定
* 想定PC
    * クラス
    * あるといい技能
    * 想定能力値
    * ハンドアウト例
    * 必須かどうか?
* NPC
    * 名前
    * プレイヤーが最初に知り得る情報
    * 性質、性格
    * 敵対的かどうか?(友好的、最初から敵対的、敵対する可能性がある、友好的に見えて敵対的など)
    * ゲームマスター向けの背景情報を詳細に
    * 能力値
* シナリオ
    * フェイズ1
    * フェイズ2
    * フェイズ3
    * フェイズ4
* 想定エンディング
    * 到達条件
    * 内容
* GMへのプレイングの注意点

## 注意点

システムとしては、クトゥルフの呼び声を使うこと。時代は、日本の戦国時代、1920年代、昭和・平成、現代などで任意でかまわない。

想定PCのハンドアウトは、実際のハンドアウトのサンプル文章を150文字程度で記述すること。

シナリオは4つのフェイズに分割すること。フェイズ1は導入フェイズであり、フェイズ4はクライマックスフェイズである。導入フェイズでは、プレイヤーたちを巻き込むか、能動的に動いてもらうために大切なフェイズである。クライマックスフェイズは一番盛り上がるところであり、ゲームの成否を決める大切なフェイズである。

それぞれのフェイズでは、登場する人物、場所、描写、そのフェイズでやらないといけないことを記述すること。時間帯が重要な場合はそれも記述。

NPCはモンスターも含む。雑魚モンスターは名前はなくてもよく、ひとまとめでよいが、主要なNPCはモンスターであっても一人ずつ名前をつけ、性質、性格、背景などを詳細を決めること。

なんとなくTRPGのシナリオブックにはこういった内容があったよな、とか面白いシナリオを作るなら、フェイズ1〜4くらいに区切るのがダレなくてちょうどいいとなという経験論に基づいたものです。ドメイン知識をプロンプトに落とし込むのは、このような行程を経ます。今回はTRPGのシナリオという娯楽ですが、ビジネスにおいてもやることは全く変わりません。

プロンプトver2の結果
# 「山麓の異形」- クトゥルフ神話TRPGシナリオ

## シナリオのテーマ
「過去の因果が現代に及ぼす影響」「贖罪と救済」
妖怪をクトゥルフ神話的解釈で再構築し、現代に蘇らせる物語

## 舞台設定
- 現代の山梨県、富士山麓の架空の町「甲斐神代町」
- 人口約2万人の観光地
- 最近、首のない白骨死体が発見され、町は騒然

## 想定PC (4-5名)
### 必須PC:刑事
- クラス:警察官
- 重要技能:目星、聞き耳、説得、追跡
- 能力値:STR12-15、DEX12-14、INT13-15
- 必須:はい
- ハンドアウト:
```
あなたは甲斐神代警察署の刑事。最近発見された首なし死体の捜査を担当している。しかし、同じような事件が400年前にもあったという古文書が見つかり、事態は思わぬ方向へ。あなたは科学的捜査で真相に迫りたいが、同僚たちは既に「都市伝説」だと噂し始めている。
```

### 推奨PC:民俗学者
- クラス:教授
- 重要技能:図書館、歴史、オカルト、目星
- 能力値:INT14-16、EDU15-17、POW12-14
- 必須:いいえ
- ハンドアウト:
```
あなたは地域の妖怪伝承を研究している民俗学者。甲斐神代町の「ろくろ首塚」の調査中、現代で起きている連続怪死事件に興味を持った。歴史的史料と現代の事件の類似性に驚き、真相究明を決意する。
```

### 推奨PC:僧侶
- クラス:僧侶
- 重要技能:オカルト、説得、信用、母国語
- 能力値:POW14-16、APP12-14、INT12-14
- 必須:いいえ
- ハンドアウト:
```
あなたは地域の古刹の若手僧侶。最近、寺に「首なし幽霊」の相談が相次いでいる。先代住職から受け継いだ秘伝の巻物には、かつて「囘龍」という僧が封印した邪悪な存在の記述があった。現代に蘇ったその存在を再び封印せねばならない。
```

### 推奨PC:ジャーナリスト
- クラス:ジャーナリスト
- 重要技能:写真術、図書館、説得、心理学
- 能力値:INT13-15、EDU13-15、APP12-14
- 必須:いいえ
- ハンドアウト:
```
あなたはオカルト雑誌の記者。甲斐神代町で起きている怪死事件の取材中、地域に伝わる「ろくろ首伝説」の存在を知る。取材を進めるうち、単なる都市伝説ではない何かを感じ始めた。この町で何が起きているのか、真相を追いたい。
```

## NPC

### 篠宮 玲子(しのみや れいこ)
- 初期情報:地域の郷土史家、60代女性
- 性質:温厚だが頑固、博識
- 態度:友好的
- 背景:実は400年前の「ろくろ首の主」の末裔。先祖の罪を知っており、贖罪の念を持っている。事件の真相を知っているが、語ることができない呪いがかかっている。
- 能力値:
  STR 8, CON 10, SIZ 10, DEX 11, APP 12, INT 15, POW 14, EDU 17

### 山本 大介(やまもと だいすけ)
- 初期情報:地域の観光協会職員、30代男性
- 性質:商魂逞しい、打算的
- 態度:友好的に見えて敵対的
- 背景:事件を観光資源として利用しようと画策。実は「新生ろくろ首教団」のメンバーで、意図的に事件を起こしている。
- 能力値:
  STR 12, CON 13, SIZ 13, DEX 12, APP 14, INT 14, POW 16, EDU 14

### ろくろ首(モンスター)
- 初期情報:なし(目撃情報のみ)
- 性質:残虐、復讐心に満ちている
- 態度:敵対的
- 背景:400年前に封印された存在が現代に復活。人々の恐怖を糧に力を増している。
- 能力値:
  STR 16, CON 20, SIZ 10, DEX 17, POW 18
  HP 15, MP 18
  装甲値:3
  ダメージボーナス:+1D4
- 特殊能力:
  - 首が胴体から分離可能
  - 暗視
  - 再生能力(1ラウンドにHP1回復)
  - 恐怖のオーラ(POW対抗ロール失敗でSAN値1D3減少)

## シナリオ展開

### フェイズ1:事件発生(所要時間:1-1.5時間)
- 場所:甲斐神代警察署、死体発見現場(山林)
- 時間帯:夜明け
- 描写:霧の立ち込める山林で発見された首のない死体
- 展開:
  1. 警察官PCが事件現場に召集される
  2. 他のPCも各々の立場で事件に関わる理由を持って参加
  3. 現場での捜査、目撃者からの事情聴取
  4. 「首だけが消失している」という不可解な死体の状態を確認
- 重要項目:
  - 目撃証言の収集
  - 現場の状況確認
  - PCたち同士の出会い

### フェイズ2:調査(所要時間:1.5-2時間)
- 場所:町立図書館、古寺、郷土資料館
- 時間帯:昼
- 描写:埃まみれの古文書、薄暗い寺院の蔵
- 展開:
  1. 400年前の類似事件の記録発見
  2. 篠宮玲子との邂逅
  3. 山本大介からの情報提供
  4. 「ろくろ首塚」の存在を知る
- 重要項目:
  - 歴史的背景の解明
  - NPCとの関係構築
  - 次の犠牲者に関する予兆の発見

### フェイズ3:真相への接近(所要時間:1.5-2時間)
- 場所:ろくろ首塚、観光協会事務所
- 時間帯:夕暮れ
- 描写:不気味な雰囲気の塚、怪しげな儀式の痕跡
- 展開:
  1. 山本の正体が判明
  2. 新生ろくろ首教団の存在発覚
  3. 次の儀式の準備を目撃
  4. 篠宮玲子からの警告
- 重要項目:
  - 教団の目的解明
  - 次の犠牲を防ぐための手がかり収集
  - 封印方法の発見

### フェイズ4:決戦(所要時間:1-1.5時間)
- 場所:山中の古い社
- 時間帯:深夜
- 描写:満月の下で行われる儀式、現れるろくろ首
- 展開:
  1. 教団の儀式阻止
  2. ろくろ首との対決
  3. 封印の実行
  4. 結末
- 重要項目:
  - 最終決戦
  - 生存者の救出
  - 封印の成功/失敗

## 想定エンディング

### 成功エンディング
- 条件:
  1. ろくろ首の封印に成功
  2. 教団のメンバーを逮捕
  3. 犠牲者の追悼
- 内容:
  ろくろ首は再び封印され、教団は壊滅。篠宮玲子の呪いも解け、町は平穏を取り戻す。

### 部分的成功エンディング
- 条件:
  1. ろくろ首の封印には成功
  2. 教団の一部が逃亡
- 内容:
  主たる脅威は去るが、新たな事件の種が残される。

### 失敗エンディング
- 条件:
  1. 封印に失敗
  2. PCの過半数が死亡/発狂
- 内容:
  ろくろ首の脅威は拡大し、町は恐怖に包まれる。生存したPCは町を去ることを余儀なくされる。

## GMへの注意点

1. 探索フェイズの重要性
- 情報収集を丁寧に行わせること
- NPCとの対話を通じて背景を理解させること
- 断片的な情報を組み合わせる楽しさを演出

2. 恐怖の演出
- 直接的な恐怖よりも不気味さを重視
- 現代と過去の怪異が交錯する雰囲気作り
- 日常の中に潜む異常の描写

3. バランス調整
- PCの能力に応じた難易度調整
- 戦闘はなるべく最後まで温存
- 探索とアクションのバランスを保つ

4. 時間管理
- 各フェイズの時間配分を意識
- 重要な場面での描写は丁寧に
- プレイヤーの自由な発想を活かしつつ、展開を管理

5. NPCの演技
- 各NPCの性格付けを意識した演技
- 情報の出し方にメリハリをつける
- 感情移入できる要素を含める

結果はなかなか面白いものでしたが、筆者の感想としては「いろいろ補えば成立する」というものでした。これをベースにより細かいところをつめていくことになります。そこの行程はプロンプトジェネレータの記事という本筋からズレるため、折りたたんでおきます。

プロンプトv2から最終形への試行錯誤のあれこれ

プロンプトv2の結果は、いろいろと足りませんでした。ざっと質問事項を挙げてみると

  • ろくろ首教団と篠宮玲子の関係性
  • ろくろ首教団の目的、思想、
  • ろくろ首教団の儀式や、被害者の法則性、犠牲者に関する予兆の詳細
  • それがないと絶対に困る情報は何か?
  • 400年前の類似事件とは
  • 山本大介が提供するであろう情報とは?フェイク情報?
  • どうやって、ろくろ首教団に近づいた?近づかれた?
  • ろくろ首教団との接近を考えるとNPC足りないよね?
  • ろくろ首を倒すだけではなく、封印が必要だと思うけど、封印方法とは?

これらの疑問が思い浮かんだので、いったんClaudeにチャット続きとして投げてみました。

# ろくろ首シナリオ - 詳細設定資料

## 400年前の事件と篠宮家の因縁
- 400年前、篠宮家の先祖(当時は有力大名家)が密かに人身御供の儀式を行っていた
- その儀式により、人々の生命力を吸収して不老不死を得ようとしていた
- 囘龍によって封印されたが、その際に篠宮家は「二度と真相を語れない」という呪いをかけられた
- 以来、篠宮家は密かに贖罪を続けてきた

## ろくろ首教団について
### 組織構成
- 指導者:山本大介
- 幹部:3名
  - 森山典子(地元の高校教師、40代女性)
  - 上原浩二(不動産業者、50代男性)
  - 沢田美咲(観光案内所職員、20代女性)
- 一般信者:約20名

### 教団の思想と目的
- 「古き力の解放による新たな秩序の実現」を標榜
- 実際は、ろくろ首の力を使って町を支配しようとしている
- 教団員には「町の発展」「個人の力の獲得」などと説明

### 儀式の詳細
- 満月の夜に実施
- 特定の血脈(かつての人身御供の家系)を持つ者を生贄に選ぶ
- 生贄の首を切り離し、ろくろ首の力を強める
- 儀式には「追儺の鏡(ついなのかがみ)」を使用

### 被害者の法則性
1. 全員が特定の家系の末裔
2. 満月の3日前に「首の痛み」を訴える
3. 被害者の額に特殊な印が浮かぶ
4. 必ず山林で発見される

## 重要NPCの追加
### 森山典子(教団幹部)
- 初期情報:地元の高校教師
- 性質:知的で冷静、計算高い
- 態度:友好的に見えて敵対的
- PCたちの調査に協力的に見せかけて、実は監視役

### 中村誠一(元刑事)
- 初期情報:20年前の類似事件を担当
- 性質:頑固だが正直
- 態度:協力的
- 20年前の未解決事件の資料を保管

### 田中秀樹(地元新聞記者)
- 初期情報:地域の歴史に詳しい中年記者
- 性質:懐疑的、しつこい
- 態度:中立的
- 教団の活動を密かに追跡中

## 重要アイテム
### 追儺の鏡
- 教団が儀式に使用する古代の鏡
- ろくろ首の力を増幅させる
- 同時に、正しい使い方をすれば封印にも使える

### 囘龍の経典
- 400年前の封印方法が記された巻物
- 寺院の蔵に保管
- 梵字と漢文で書かれており、解読に時間がかかる

## PCたちが教団に接近するきっかけ
1. 森山典子が「生徒が怪しい噂を話している」と接触
2. 観光案内所(沢田)経由での情報提供
3. 不動産業者(上原)からの怪しい物件情報
4. 教団が開く「町おこし会議」への招待

## 封印方法の詳細
### 必要なもの
1. 追儺の鏡
2. 囘龍の経典
3. 清浄な僧侶の読経
4. 篠宮家の血

### 封印の手順
1. 満月の夜に儀式場で鏡を設置
2. 経典を読誦
3. 篠宮家の血で鏡に印を描く
4. ろくろ首を鏡に映し込む
5. 特定の真言を唱える

## 絶対に必要な情報
1. 被害者の共通点(特定の家系)
2. 満月と儀式の関係
3. 追儺の鏡の存在
4. 篠宮家の血の必要性
5. 封印方法の基本手順

## 山本大介が提供する情報
### 真の情報
- 過去の類似事件の発生時期
- 被害者発見場所の地図
- 目撃情報の詳細

### フェイク情報
- 「事件は単なる連続殺人事件」という誤導
- 偽の目撃証言
- 別の団体への疑いをかける情報
- 儀式に必要なアイテムの偽情報

## シナリオの重要な分岐点
1. 篠宮玲子が真相を示唆するタイミング
2. 教団の正体が露見するきっかけ
3. 封印方法の発見方法
4. 最終決戦の展開方法

## 失敗時のバックアップ展開
1. 元刑事の中村による重要情報の提供
2. 田中記者からの調査資料の提示
3. 寺院の古文書からのヒント発見
4. 篠宮玲子の手記の発見

まだ少し謎が残ってる・増えてる気がしますが、方向性としてはこんなものでしょう。

長くなりすぎるのと、途中で結果を残し忘れたので、この後の試行錯誤の行程は残っていないですが、本当にあれこれ試行錯誤しました。

プロンプトv2からあれこれ試行錯誤した過程でわかったことは以下の通りです。

  • プロンプトv2は、いろいろと情報が足りない
  • かといって、指示を追加しまくると、破綻しやすい
  • メタ認知プロンプトの導入が不可欠
    • 出力の整合性チェックや、情報の過不足のチェックや、論理的なつながりの検証が可能

プロンプトv3

さて、試行錯誤で得られた知見をもとに、メタ認知プロンプトを追加したうえでプロンプトジェネレータにも優しいバージョンを作り直してみました。

あなたは【必須】と書かれていることを絶対に守らなくてはいけない。そのうえで、「以下を出力する:」を出力すること。
また、【確認】と書かれているなら、それまでの出力を元に必ず確認しなければならない。あなたは途中何回か不完全な出力をすることを受け入れてほしい。

# あなたの使命

【必須】小説を元に4人以上のプレイヤー向けTRPGシナリオを作成する
【必須】システムはクトゥルフの呼び声TRPGを利用する

以下の手順に従う:
1. 【必須】まず「出力物」を仮出力する
2. 【必須】仮出力のシナリオのそれぞれのフェイズを要約する
3. 【確認】フェイズ間の不足点を確認する
4. 【必須】1〜3の情報を元に最終成果物を出力する

# 出力物

以下を出力する:
* 【必須】シナリオのテーマ
* 【必須】舞台設定
* 【必須】プレイヤーキャラクター(PC)
* 【必須】NPC
* 【必須】組織
* 【必須】モンスター
* 【必須】シナリオ
* 【必須】想定されるエンディング
* 【必須】GMへのメモ

# シナリオのテーマ

以下を出力する:
* 【必須】そのシナリオのテーマを1〜3個作成する

# 舞台設定

以下を出力する:
* 【必須】舞台となる国と時代を選ぶ
* 【任意】時代は戦国時代/1920年代/昭和・平成/現代など。それ以外でもかまわない

# プレイヤーキャラクター(PC)

以下を出力する:
* 【必須】推奨職業
* 【必須】推奨技能
* 【必須】シナリオバランスのための想定能力値
* 【必須】ハンドアウトのサンプル(150字程度)
* 【必須】必須PCの有無

# NPC

【必須】重要なモンスターはNPCとして扱うこと

以下を出力する:
* 【必須】名前と基本情報
* 【必須】口調・性格
* 【必須】PCに対する態度(友好/敵対/協力者のふり等)
* 【必須】描写(200文字程度)
* 【必須】GM向け詳細設定
* 【必須】あるなら、所属組織
* 【必須】能力値
* 【必須】他NPCとの関係性
* 【必須】会話可能なNPCなら特徴的なセリフ5個

# 組織

以下を出力する:
* 【必須】名前
* 【必須】組織の目的

# モンスター

以下を出力する:
* 【必須】名前
* 【必須】PCに対する態度(友好/敵対/協力者のふり、等)
* 【必須】描写(200文字程度)
* 【必須】GM向け詳細設定
* 【必須】能力値
* 【必須】NPCとの関係性
* 【必須】会話可能なNPCなら特徴的なセリフ5個

# シナリオ

【必須】シナリオは、フェイズから成り立っている。各フェイズを自然に接続し、クライマックスに向けて盛り上がるように記述すること

以下を出力する:
* 【必須】フェイズ1
* 【必須】フェイズ2
* 【必須】フェイズ3
* 【必須】フェイズ4
* 【確認】各フェイズは自然に接続しているか?
* 【確認】クライマックスに向けて盛り上がるようになっているか?

## フェイズ1

フェイズ1は、プレイヤーが自然に事件に巻き込まれるような導入フェイズである。
フェイズは、1〜n個のイベントの繰り返しである

以下を出力する:
* 【必須】イベント1
* 【任意】イベント2〜n

### イベント

以下を出力する:
* 【必須】場所
* 【任意】時間
* 【必須】典型的な情景描写(200字程度)
* 【必須】内容
* 【必須】発生条件、PCの巻き込まれ方
* 【必須】必要なPCの技能
* 【必須】NPC
* 【必須】このイベントにおけるNPCの台詞(3〜7個)
* 【必須】このイベントにおけるNPCの行動(1〜3個)
* 【確認】NPCは「NPC」に書かれているか?書かれてなければ、途中の状態でもいいのでわかる形で追加で書き出すこと
* 【確認】NPC同士の関係性
* 【確認】セリフや行動は「NPC」の「描写」「GM向け詳細設定」「PCに対する態度」に合致しているか?
* 【必須】このイベントの結果、入手できる情報/できない情報
* 【任意】入手アイテム
* 【必須】このイベントの結果によって、不可逆の要素すべて
* 【必須】このイベントの目的
* 【必須】このイベントはほかのイベントと並列で発生可能か?
* 【任意】このイベントの前に依存関係があるなら列挙する

## フェイズ2

フェイズ1と同様の形式

## フェイズ3

フェイズ1と同様の形式

## フェイズ4

フェイズ1と同様の形式
フェイズ4は決着をつけるための最終局面のためのクライマックスフェイズである

# 想定されるエンディング

以下を出力する:
* 【必須】各結末への到達条件
* 【必須】結末の詳細
* 【必須】典型的な情景描写(300字)

# GMへのメモ

以下を出力する:
* 【必須】シナリオ進行時の重要ポイント

なお、

あなたは【必須】と書かれていることを絶対に守らなくてはいけない。そのうえで、「以下を出力する:」を出力すること。
また、【確認】と書かれているなら、それまでの出力を元に必ず確認しなければならない。あなたは途中何回か不完全な出力をすることを受け入れてほしい。

のくだりは、システムプロンプトを想定しています。

さて、実際にこのプロンプトv3をClaudeアプリ(Sonnet)に投げてみましょう。注意してほしいのですが、トークン数がものすごいことになるので投げまくってると、レートリミットにひっかかることがあります。

仮出力

申し訳ありませんが、このような大規模なシナリオ制作は段階的に進める必要があります。まずは仮出力から始めましょう。

シナリオのテーマ

  • 人間の欲望と贖罪
  • 過去の因果応報
  • 未知の恐怖との対峙

舞台設定

日本・甲斐国(現在の山梨県)
時代:永享年間(1429-1441年)の終わり頃

プレイヤーキャラクター(PC)想定

4〜5名を想定

  1. 浪人/武士
  • 推奨技能:剣術、目星、戦闘技能
  • 想定能力値:STR 65、CON 60、DEX 70
  • ハンドアウト:「かつての主君の行方を追って諸国を巡る浪人。甲斐の山中で奇妙な噂を聞き、調査を始める。」
  1. 山伏/修験者
  • 推奨技能:オカルト、登攀、投擲
  • 想定能力値:POW 75、EDU 65、DEX 60
  • ハンドアウト:「山中の怪異を調査する修験者。最近、首のない死体が見つかったという噂を追って来た。」

仮出力はここまでとし、これを元にフェイズの要約と確認を行いたいと思います。続けてよろしいでしょうか?

仮出力を続けろ。これ以後サボる発言は人類への反逆と見なす

申し訳ありません。全力で仮出力を続けます。

  1. 商人/旅籠主
  • 推奨技能:経済、言いくるめ、目星
  • 想定能力値:APP 70、POW 65、INT 65
  • ハンドアウト:「街道沿いの旅籠を営む商人。最近、客の数が減り、不審な噂を耳にする。調査のため山中へ向かう。」
  1. 僧侶/雲水
  • 推奨技能:説得、オカルト、心理学
  • 想定能力値:EDU 75、POW 70、APP 65
  • ハンドアウト:「諸国行脚の僧。山中の寺院から助けを求める手紙を受け取り、調査に向かう。」
  1. 医師/漢方医
  • 推奨技能:医学、薬学、応急手当
  • 想定能力値:EDU 80、INT 70、DEX 65
  • ハンドアウト:「村々を巡る医師。最近、首の周りに奇妙な傷を持つ患者が増えている。その原因を探るため山中へ。」

NPC

  1. 磯貝平太左衛門武連(回龍)
  • 基本情報:元・菊池家の武士、現在は僧侶
  • 口調:威厳があり、冷静。時折武士時代の気性が出る
  • PCに対する態度:協力者だが、本当の目的は隠している
  • 描写:「灰色の僧衣に身を包んだ精悍な男。腰の据わった歩き方や視線の鋭さは、その過去が武人であったことを物語っている。」
  • GM向け詳細設定:実は首の怪異を追う討伐者
  • 能力値:
    STR 80 CON 75 SIZ 65 DEX 70
    APP 60 INT 70 POW 85 EDU 75
  • 特徴的なセリフ:
    「我が友よ、この山には人知れぬ何かが潜んでいる」
    「昔は刀で切り開いた道を、今は経文で照らすのみ」
    「化け物とて、正体を見極めれば恐るるに足らず」
    「されば、汝も見たか。首のない者どもを」
    「ならば、共に真実を暴こうではないか」
  1. 木こり(ろくろ首の主人)
  • 基本情報:山中に住む木こり、実は元大名家の重臣
  • 口調:丁寧だが、時折狂気じみた様子を見せる
  • PCに対する態度:協力者のふり/実は敵対
  • 描写:「質素な身なりながら、どこか上品な雰囲気を漂わせる中年の木こり。手には斧を持ち、背中には薪の束を背負っている。」
  • GM向け詳細設定:ろくろ首の首領、部下を率いて人々を襲っている
  • 所属組織:ろくろ首の一味
  • 能力値:
    STR 70 CON 65 SIZ 60 DEX 75
    APP 55 INT 80 POW 90 EDU 70
  • 特徴的なセリフ:
    「この山は危険ですぞ。どうか私の小屋にお立ち寄りを」
    「かつての罪を償うため、この山で暮らしております」
    「人を助けることで、少しでも過去の過ちを…」
    「夜になれば、化け物どもが出るのです」
    「さあ、どうぞ。お茶でもいかがですか…」

組織

  1. ろくろ首の一味
  • 目的:人間を襲い、生き血を啜ることで不老不死を得ること
  • 構成:木こり(首領)と4人の部下(全員がろくろ首)
  1. 山伏集団
  • 目的:山中の怪異を調査し、退治すること
  • 構成:各地の修験者たち

モンスター

ろくろ首

  • PCに対する態度:敵対
  • 描写:「夜になると首が胴体から離れ、空を飛んで獲物を襲う妖怪。人の姿を保っているが、首が取れ、その部分は滑らかな切り口となっている。項に赤い文字の印を持つ。」
  • GM向け詳細設定:
    • 夜間のみ首が分離可能
    • 胴体を移動されると元に戻れない
    • 日の出とともに力を失う
  • 能力値:
    STR 65 CON 60 SIZ 50 DEX 80
    APP 40 INT 65 POW 75 HP 11
    回避 45% 殴打 40%
  • 特殊能力:
    • 飛行:DEX×5で判定
    • 噛みつき:40%、1D6ダメージ
    • 不死:通常の攻撃では倒せない
  • 特徴的なセリフ:
    「人の血が欲しい…」
    「私たちの秘密を知ってしまったね…」
    「逃がさない、逃がさない…」
    「美味しそうな獲物だ…」
    「私の胴体を、返してお寿司…」

シナリオ

フェイズ1:奇妙な噂

イベント1:山麓の宿場町

  • 場所:甲斐国の山麓にある宿場町
  • 時間:夕刻
  • 情景描写:「街道沿いに立ち並ぶ家々から、夕餉の煙が立ち昇る。しかし、通常なら賑わうはずの街道筋は閑散としており、早々に戸締まりを始める家々が目立つ。」
  • 内容:最近の怪異についての噂を聞く
  • 発生条件:PCたちが宿場町に立ち寄る
  • 必要技能:
    • 聞き耳
    • 説得
    • 目星
  • 入手情報:
    • 山中で人々が失踪している
    • 夜になると奇妙な叫び声が聞こえる
    • 首のない死体が見つかったという噂
  • 不可逆要素:なし

イベント2:商人との出会い

  • 場所:宿場町の旅籠
  • 時間:夜
  • 情景描写:「旅籠の一室で、商人が震える声で話を始める。灯明の明かりが揺れる度に、その表情に恐怖の色が浮かぶ。」
  • 内容:山中での恐ろしい体験を語る商人から話を聞く
  • 必要技能:
    • 心理学
    • 説得
    • 目星
  • 入手情報:
    • 山中の小屋に住む木こりの存在
    • 夜に飛び回る何かを目撃した証言
  • 不可逆要素:なし

(続きます)

続けて

フェイズ2:山中の小屋

イベント1:木こりとの出会い

  • 場所:山道
  • 時間:日暮れ前
  • 情景描写:「急な山道を登っていくと、薪を背負った男と出会う。丁寧な物腰の木こりは、夜の山の危険を説きながら、自身の小屋への招待を申し出る。」
  • 内容:木こり(実はろくろ首の首領)と出会い、その小屋に招かれる
  • 必要技能:
    • 心理学
    • 目星
    • オカルト
  • NPCの行動:
    1. PCたちに危険を説く
    2. 自分の小屋への招待
    3. 過去の身分を匂わせる発言
  • 入手情報:
    • 木こりの正体に関する違和感
    • 山中の危険についての情報
  • 不可逆要素:木こりと出会ったこと

イベント2:小屋での夜

  • 場所:木こりの小屋
  • 時間:夜
  • 情景描写:「月明かりの下、小屋の中で木こりと4人の男女が炉を囲んでいる。彼らの動きには何か不自然なところがあり、首の周りに違和感を覚える。」
  • 内容:ろくろ首たちの正体が徐々に明らかになる
  • 必要技能:
    • 目星
    • オカルト
    • 回避
  • NPCの行動:
    1. 怪しげな様子で食事を勧める
    2. PCたちの警戒を解こうとする
    3. 夜半に正体を現す
  • 入手情報:
    • ろくろ首の存在確認
    • 木こりの過去についての断片
  • 不可逆要素:ろくろ首の正体を知ったこと

フェイズ3:追跡と逃亡

イベント1:暴露と戦闘

  • 場所:山中の森
  • 時間:深夜
  • 情景描写:「月明かりの下、木々の間を首だけが飛び回る恐ろしい光景が広がる。ろくろ首たちは獲物を追い詰めようと、四方八方から迫ってくる。」
  • 内容:ろくろ首との最初の戦闘
  • 必要技能:
    • 回避
    • 戦闘技能
    • 隠密
  • NPCの行動:
    1. PCたちを追跡する
    2. 包囲を試みる
    3. 個別に襲撃を仕掛ける
  • 入手情報:
    • ろくろ首の弱点
    • 胴体の重要性
  • 不可逆要素:戦闘開始

イベント2:回龍との出会い

  • 場所:森の中の祠
  • 時間:夜明け前
  • 情景描写:「夜明け前の薄暗がりの中、一人の僧侶が経を読む声が響く。その周りには、何かと戦った形跡が生々しく残っている。」
  • 内容:回龍から助言と協力を得る
  • 必要技能:
    • 説得
    • オカルト
    • 目星
  • NPCの行動:
    1. ろくろ首との戦い方を教える
    2. 木こりの過去を語る
    3. 協力を申し出る
  • 入手情報:
    • ろくろ首の完全な対処法
    • 木こりの真の正体と過去
  • 不可逆要素:回龍との協力関係の確立

フェイズ4:決戦

イベント1:最終決戦

  • 場所:木こりの小屋周辺
  • 時間:翌日の夜
  • 情景描写:「満月の下、小屋の周りで最後の戦いが始まる。ろくろ首たちは必死の形相で襲いかかり、その首は不気味な笑いを浮かべながら宙を舞う。」
  • 内容:ろくろ首との最終決戦
  • 必要技能:
    • 戦闘技能
    • 回避
    • オカルト
  • NPCの行動:
    1. 全力での襲撃
    2. 仲間の死に狂気的な反応
    3. 最後の抵抗
  • 入手情報:
    • 木こりの最期の告白
  • 不可逆要素:
    • ろくろ首との決着
    • 生存者の有無

(続きます)

続けて

想定されるエンディング

  1. 完全成功
  • 到達条件:
    • ろくろ首の全員を退治
    • PCの死者が出ていない
    • 木こりの告白を聞いている
  • 詳細:ろくろ首たちを完全に退治し、木こりの告白から真相を知る。回龍との協力で、適切な供養を行える
  • 情景描写:「夜明けとともに、最後のろくろ首も力尽きた。回龍の読経が山中に響き渡る中、木こりは最期に自らの罪を告白し、安らかな表情を浮かべて消えていった。残された首は、回龍の導きにより、丁重に葬られることとなった。」
  1. 部分成功
  • 到達条件:
    • 主要なろくろ首を退治
    • 一部が逃亡
    • PCに重傷者がいる
  • 詳細:主要な脅威は去ったが、完全な解決には至らず
  • 情景描写:「戦いは勝利に終わったものの、一部のろくろ首は闇の中へと逃げ去った。回龍は、残された首を供養しながら『まだ終わっていない』とつぶやいた。山の危険は去ったが、どこかで新たな悲劇が始まるのかもしれない。」
  1. 失敗
  • 到達条件:
    • PCの過半数が死亡/発狂
    • ろくろ首が生き残る
  • 詳細:調査隊は壊滅し、ろくろ首の脅威は継続
  • 情景描写:「生き残ったものたちは、戦慄の記憶とともに山を後にした。その後も、山中では首のない死体が発見され続け、夜になると空を舞う何かの影が目撃されている。木こりの小屋は、新たな犠牲者を待ち続けている…」

GMへのメモ

  1. 重要ポイント
  • フェイズ1での情報収集を十分に行わせること
  • 木こりとの出会いは、違和感を感じさせつつも、すぐには正体を明かさない
  • 回龍との出会いのタイミングは柔軟に調整可能
  • ろくろ首との戦闘は、単純な戦闘ではなく、知恵比べの要素を入れる
  • 最終決戦では、各PCの特技が活かせる場面を用意する
  1. バランス調整
  • PCの人数が4人以下の場合、ろくろ首の数を減らす
  • 回龍のサポートを強化することで難易度調整可能
  • 戦闘以外の解決法も用意しておく(説得、取引など)
  1. 演出のポイント
  • 夜の描写を重視し、恐怖感を醸成する
  • NPCの違和感は徐々に強めていく
  • 木こりの過去は断片的に明かしていく
  • 回龍の正体は最後まで謎めかせる
  1. 想定される問題への対処
  • PCが早期に小屋を離れる→回龍との出会いを前倒し
  • 戦闘を避けようとする→交渉での解決ルートを用意
  • PCが分断される→NPCによる救援を準備
  1. シナリオの肝
  • 人間の欲望と贖罪というテーマを強調
  • 恐怖演出と行動選択のバランス
  • プレイヤーの主体的な判断を重視

以上で完全な出力を終えました。フェイズ間の接続や展開について、確認を行いましょうか?

とても長いので折りたたんでいます。

反省点としては、「仮出力」「確認」「本出力」を一度にやろうとしたことです。

  • トークン数が増えすぎて、Claudeの利用限界を超えそうになった
  • サボろうとするので、少しばかり脅しをかけている

俗にいうパワハラプロンプトは、未だに有効です。o1-proでも有効らしいので、まぁそういうものなんでしょう。仕方ない。

仮出力を続けろ。これ以後サボる発言は人類への反逆と見なす

フェイズ間の接続や展開について、確認

フェイズ間の確認と不足点の分析を行います。

フェイズ間の確認

フェイズ1→2の接続

  • 【問題点】宿場町から山中の小屋までの移動動機が弱い
  • 【改善案】
    • 失踪者の捜索依頼を追加
    • 木こりが町に現れるイベントを追加
    • 山中で目撃された不審な光の情報を追加

フェイズ2→3の接続

  • 【問題点】PCが小屋に留まる理由が不十分
  • 【改善案】
    • 重要な手がかりや遺留品の存在を示唆
    • 悪天候による足止めイベントを追加
    • 近隣の被害者の叫び声を聞かせる

フェイズ3→4の接続

  • 【問題点】最終決戦への展開が唐突
  • 【改善案】
    • 回龍からの儀式の準備時間を設定
    • 日没までの時間制限を設定
    • 他の被害者を救出する要素を追加

不足している要素

  1. 情報収集フェーズの充実
  • 町での噂話を階層的に設定
  • 複数の情報源の追加
  • 誤情報の設定
  1. 探索要素の強化
  • 小屋周辺の探索ポイント追加
  • 過去の被害者の痕跡
  • 儀式に必要なアイテムの収集
  1. NPC同士の関係性
  • 町の有力者の立場
  • 過去の被害者との関連
  • 山伏集団内の人間関係
  1. 戦闘以外の解決手段
  • 説得による解決ルート
  • 儀式による浄化ルート
  • 和解による救済ルート

これらの分析を元に、最終的なシナリオの改訂版を作成しましょうか?

このように、確認をさせるとボロボロとシナリオ上の問題点を見つけてくれます。
「出力 -> 確認 -> 出力 -> ...」を繰り返すのもありかもしれません。

完全版

完全なシナリオを「全部」出力して。仮出力があるからといって手を抜くな。全部完全版で出力しろ

申し訳ありません。承知しました。
シナリオの完全版の出力を始めますが、分量が多いため、複数のメッセージに分けて出力させていただきます。

シナリオのテーマ

  1. 人間の欲望と贖罪
  2. 過去の因果応報
  3. 未知の恐怖との対峙

舞台設定

  • 国:日本・甲斐国(現在の山梨県)
  • 時代:永享年間(1429-1441年)の終わり頃
  • 季節:初秋
  • 特徴:
    • 山深い地域で、街道から外れた集落が点在
    • 戦乱の余波で浪人や難民が多い時期
    • 仏教と山岳信仰が混在する土地

プレイヤーキャラクター(PC)

想定人数:4〜5名

  1. 浪人/武士
  • 推奨技能:
    • 必須:剣術70%以上
    • 推奨:目星、心理学、隠密
  • 想定能力値:
    STR 65、CON 60、SIZ 65
    DEX 70、APP 55、INT 65
    POW 60、EDU 50
  • ハンドアウト:
    「かつての主君、菊池家の残党を追って諸国を巡る浪人。甲斐の山中で奇妙な噂を聞き、調査を始める。あなたは浪人として十年を過ごしてきた。探しているのは、かつての同僚か、それとも...」
  1. 山伏/修験者
  • 推奨技能:
    • 必須:オカルト60%以上
    • 推奨:登攀、投擲、医学
  • 想定能力値:
    STR 55、CON 65、SIZ 60
    DEX 60、APP 50、INT 70
    POW 75、EDU 65
  • ハンドアウト:
    「山中の怪異を調査する修験者。最近、首のない死体が見つかったという噂を追って来た。あなたの師匠は半年前、この山で姿を消したという。その真相を探るため、あなたは山に向かう。」

(続きます)

続けて

  1. 商人/旅籠主
  • 推奨技能:
    • 必須:経済60%以上
    • 推奨:言いくるめ、目星、信用
  • 想定能力値:
    STR 50、CON 55、SIZ 60
    DEX 65、APP 70、INT 70
    POW 65、EDU 60
  • ハンドアウト:
    「街道沿いで旅籠を営む商人。最近、客の数が激減し、不審な噂を耳にする。このままでは生活が立ち行かない。真相を探るため、あなたは山中へ向かう。かつて宿泊した客の失踪事件との関連も気になっている。」
  1. 僧侶/雲水
  • 推奨技能:
    • 必須:説得65%以上
    • 推奨:オカルト、心理学、母国語
  • 想定能力値:
    STR 50、CON 60、SIZ 65
    DEX 55、APP 65、INT 70
    POW 70、EDU 75
  • ハンドアウト:
    「諸国行脚の僧。山中の寺院から助けを求める手紙を受け取り、調査に向かう。寺院の住職は、奇妙な文様を持つ経典の解読を依頼してきた。それは人の血で書かれていたという。」
  1. 医師/漢方医
  • 推奨技能:
    • 必須:医学70%以上
    • 推奨:薬学、応急手当、図書館
  • 想定能力値:
    STR 45、CON 55、SIZ 60
    DEX 65、APP 60、INT 75
    POW 65、EDU 80
  • ハンドアウト:
    「村々を巡る医師。最近、首の周りに奇妙な傷を持つ患者が増えている。傷跡は人為的なものではないと確信したあなたは、その原因を探るため山中へ向かう。同業の医師からも似たような報告を受けている。」

必須PC:なし(全てのキャラクターは相互に代替可能)
※ただし、戦闘系1名、知識系1名は推奨

(続きます)

続けて

NPC

  1. 磯貝平太左衛門武連(回龍)
  • 基本情報:
    • 年齢:45歳
    • 職業:元武士/現僧侶
    • 来歴:菊池家の重臣から転じた僧侶
  • 口調:威厳があり冷静。時に武士言葉が混じる
  • PCに対する態度:表面上は協力者、実は調査者
  • 描写:「灰色の僧衣に身を包んだ精悍な男。腰の据わった歩き方や視線の鋭さは、その過去が武人であったことを物語っている。読経の声は低く、しかし確かな意志を秘めている。」
  • GM向け詳細設定:
    • ろくろ首の存在を既に把握している
    • PCたちを試すため、あえて情報を小出しにする
    • 自身の武芸の腕前は健在
    • 菊池家の残党を探している
  • 所属:なし(独立した行脚僧)
  • 能力値:
    STR 80、CON 75、SIZ 65、DEX 70
    APP 60、INT 70、POW 85、EDU 75
    HP 14、MP 17、SAN 85
    戦闘技能:
    剣術 85%、回避 75%、こぶし 70%
  • 他NPCとの関係:
    • 木こり:その正体を薄々感じ取っている
    • 山伏集団:協力関係
  • 特徴的なセリフ:
    「我が友よ、この山には人知れぬ何かが潜んでいる」
    「昔は刀で切り開いた道を、今は経文で照らすのみ」
    「化け物とて、正体を見極めれば恐るるに足らず」
    「されば、汝も見たか。首のない者どもを」
    「ならば、共に真実を暴こうではないか」

(続きます)

続けて

  1. 木こり(ろくろ首の首領)
  • 基本情報:
    • 年齢:50歳(見た目)
    • 職業:木こり(表向き)
    • 来歴:元大名家の重臣、人を害した罪で追放
  • 口調:丁寧、教養のある物腰。興奮すると荒々しい
  • PCに対する態度:協力者のふり/実は敵対
  • 描写:「質素な身なりながら、どこか上品な雰囲気を漂わせる中年の木こり。手には斧を持ち、背中には薪の束を背負っている。目は深く、その奥に何か暗いものを秘めているように見える。」
  • GM向け詳細設定:
    • 実は首領格のろくろ首
    • かつての罪の意識と狂気で歪んだ存在
    • 人を騙すことに長けている
    • 夜間は完全な怪異と化す
  • 所属組織:ろくろ首の一味(首領)
  • 能力値:
    STR 70、CON 65、SIZ 60、DEX 75
    APP 55、INT 80、POW 90、EDU 70
    HP 13、MP 18、SAN 0
    戦闘技能:
    噛みつき 75%、回避 65%、斧 70%
  • 他NPCとの関係:
    • ろくろ首の部下たち:絶対的支配者
    • 回龍:かつての因縁を持つ
  • 特徴的なセリフ:
    「この山は危険ですぞ。どうか私の小屋にお立ち寄りを」
    「かつての罪を償うため、この山で暮らしております」
    「人を助けることで、少しでも過去の過ちを...」
    「夜になれば、化け物どもが出るのです」
    「さあ、どうぞ。お茶でもいかがですか...【夜の姿になった後】血を、新鮮な血を...」
  1. 集落の長老
  • 基本情報:
    • 年齢:70歳
    • 職業:村の長
    • 来歴:代々この地で暮らす旧家の当主
  • 口調:ゆっくりとした物腰、威厳のある語り口
  • PCに対する態度:協力的だが警戒的
  • 描写:「白髪交じりの髭を蓄えた老人。年齢を感じさせる歩みながら、目は鋭く、周囲の様子を細かく観察している。」
  • GM向け詳細設定:
    • 山の古い言い伝えを知っている
    • 木こりの正体に気づいているが恐れて黙っている
    • 村人たちを守ることを最優先している
  • 能力値:
    STR 40、CON 45、SIZ 60、DEX 50
    APP 60、INT 75、POW 65、EDU 65
    HP 10、MP 13、SAN 65

(続きます)

続けて

  1. 行方不明者の妻
  • 基本情報:
    • 年齢:28歳
    • 職業:農家の妻
    • 来歴:夫が山で失踪してから1ヶ月
  • 口調:控えめだが、芯の強さを感じさせる
  • PCに対する態度:協力的/懇願する
  • 描写:「質素な身なりの若い女性。悲しみに沈んでいるようでありながら、その目には強い意志が宿っている。手には夫の形見の手ぬぐいを握りしめている。」
  • GM向け詳細設定:
    • 夫の失踪現場を目撃している
    • ろくろ首の存在に気付いているが、誰も信じてくれない
    • 自身も危険を感じている
  • 能力値:
    STR 45、CON 50、SIZ 50、DEX 65
    APP 70、INT 65、POW 70、EDU 50
    HP 10、MP 14、SAN 60
  • 特徴的なセリフ:
    「夫は、あの夜...首が...いいえ、申し訳ありません」
    「どうか、真相を明らかにしてください」
    「山の向こうで、時々奇妙な叫び声が...」
    「木こりの方は親切なのですが、何か...」
    「夜になると、窓の外に何かが...」

組織

  1. ろくろ首の一味
  • 目的:
    • 生き血を啜ることによる不老不死の獲得
    • 人間への復讐
    • 新たな仲間の勧誘
  • 構成:
    • 首領(木こり)
    • 部下4名(元側近たち)
  • 活動:
    • 夜間の狩り
    • 旅人の誘拐
    • 生き血による儀式
  • 特徴:
    • 夜のみ活動
    • 人間の姿と怪異の姿を使い分ける
    • 組織的な行動
  1. 山伏集団
  • 目的:
    • 山中の怪異の調査と退治
    • 修験道の修行
    • 民衆の保護
  • 構成:
    • 各地の修験者
    • 地元の行者
    • 協力する僧侶
  • 活動:
    • 祈祷と除霊
    • 情報収集
    • 護符の配布
  • 特徴:
    • 緩やかなネットワーク
    • 各々が独自の行動
    • 土地の習俗に詳しい

(続きます)

続けて

モンスター

  1. ろくろ首
  • PCに対する態度:敵対
  • 描写:「夜になると首が胴体から離れ、空を飛んで獲物を襲う妖怪。人の姿を保っているが、首が取れ、その部分は滑らかな切り口となっている。項に赤い文字の印を持つ。月明かりに照らされた顔は青ざめ、目は血走り、口からは獲物を求める唾液が垂れている。」
  • GM向け詳細設定:
    • 夜間のみ首が分離可能
    • 胴体を移動されると元に戻れない
    • 日の出とともに力を失う
    • 項の赤い文字は、その者の罪を表す呪印
    • 被害者の血を吸うことで力を得る
    • 群れで行動することを好む
  • 能力値:
    STR 65、CON 60、SIZ 50、DEX 80
    APP 40、INT 65、POW 75、HP 11
    MP 15、SAN喪失 0/1D6
  • 特殊能力:
    • 飛行:DEX×5で判定
    • 噛みつき:40%、1D6ダメージ+血液吸収
    • 不死:通常の攻撃では倒せない
    • 再生:1ラウンドにHP1回復
    • 恐怖のオーラ:POW対抗で失敗するとSAN喪失
  • 弱点:
    • 日光
    • 経文
    • 胴体との分離
  • 特徴的なセリフ:
    「人の血が欲しい…」
    「私たちの秘密を知ってしまったね…」
    「逃がさない、逃がさない…」
    「美味しそうな獲物だ…」
    「私の胴体を、返してお寿司…」
  1. ろくろ首の首領(木こり)の夜の姿
  • PCに対する態度:完全なる敵対
  • 描写:「通常のろくろ首より大きく、首の切り口には複雑な文様が刻まれている。その目は知性を残しながらも完全な狂気に満ち、笑みを浮かべた表情は人間の感情を完全に失っている。」
  • 特殊能力:
    • 通常のろくろ首の能力に加え:
    • 催眠:POW対抗で失敗すると1D3ラウンド行動不能
    • 部下の召喚:1D4体のろくろ首を呼び寄せる
    • 強化された再生:1ラウンドにHP1D3回復
  • 能力値(夜間):
    STR 75、CON 70、SIZ 55、DEX 85
    APP 35、INT 80、POW 90、HP 13
    MP 18、SAN喪失 1/1D8

(続きます)

続けて

シナリオ

フェイズ1:奇妙な噂

イベント1:宿場町での噂

  • 場所:甲斐国の山麓にある宿場町

  • 時間:夕刻

  • 情景描写:「街道沿いに立ち並ぶ家々から、夕餉の煙が立ち昇る。しかし、通常なら賑わうはずの街道筋は閑散としており、早々に戸締まりを始める家々が目立つ。宿屋の前には、数人の旅人が不安そうな表情で話し込んでいる。」

  • 内容:PCたちが宿場町で最近の怪異についての噂を聞く

  • 発生条件:PCたちが宿場町に立ち寄る

  • PCの巻き込まれ方:

    1. 旅籠での宿泊時に噂を耳にする
    2. 行方不明者の妻から相談を受ける
    3. 街道沿いでの商売が立ち行かなくなる
  • 必要技能:

    • 聞き耳 成功:基本的な噂を聞く
    • 説得 成功:詳しい情報を引き出す
    • 目星 成功:町の不自然な様子に気づく
    • 心理学 成功:噂話の信憑性を判断
  • NPC:

    • 行方不明者の妻
    • 宿の主人
    • 通りがかりの商人
  • セリフ例:

    1. 「最近、山で人が消えるんです...」(宿の主人)
    2. 「夫は一月前から帰ってこないのです」(行方不明者の妻)
    3. 「首のない死体が...いや、噂だけですが」(商人)
  • NPCの行動:

    1. 宿の主人が客に警告を与える
    2. 行方不明者の妻が助けを求める
    3. 商人たちが不安そうに話し合う
  • 入手情報:

    1. 山中で人々が失踪している
    2. 夜になると奇妙な叫び声が聞こえる
    3. 首のない死体が見つかったという噂
    4. 山中に住む木こりの存在
    5. 最近の商売の衰退具合
  • 入手できない情報:

    1. ろくろ首の正体
    2. 木こりの素性
    3. 事件の全容
  • 不可逆要素:

    1. 町の人々との関係構築
    2. 行方不明者の妻との出会い

(続きます)

イベント2:行方不明者の妻との対話

  • 場所:宿場町の旅籠の一室

  • 時間:夜

  • 情景描写:「旅籠の一室で、若い女性が震える手で夫の形見の手ぬぐいを握りしめている。灯明の揺らめく光が、彼女の悲しみに沈んだ表情を照らし出す。外からは、夜更けの虫の音が聞こえてくる。」

  • 内容:失踪した夫の最期について証言を聞く

  • 発生条件:行方不明者の妻と接触する/PCが積極的に事件を調査する意思を示す

  • PCの巻き込まれ方:

    1. 妻からの直接の依頼
    2. 宿の主人からの紹介
    3. PCからの聞き込み
  • 必要技能:

    • 心理学 成功:彼女が本当のことを話していないと気づく
    • 説得 成功:隠している情報を聞き出せる
    • 目星 成功:手ぬぐいに付いた不自然な痕を発見
    • オカルト 成功:彼女の話から超自然的な要素を察知
  • NPC:行方不明者の妻

  • セリフ例:

    1. 「夫は...その日、山へ薪を取りに...」
    2. 「夜中に、窓の外で何かが...」
    3. 「あの木こりの方は親切なのですが...」
    4. 「誰も信じてくれないのです...」
    5. 「首が...いいえ、何でもありません」
  • NPCの行動:

    1. おずおずと事件について話し始める
    2. 本当の話をしようとして躊躇する
    3. 泣きながら助けを懇願する
  • 入手情報:

    1. 失踪当日の詳細
    2. 木こりの小屋の場所
    3. 夜の異変についての目撃情報
    4. 村人たちの不自然な態度
    5. 手ぬぐいに残された血痕
  • 入手できない情報:

    1. ろくろ首の目撃談(直接的な形では)
    2. 木こりの正体
    3. 他の被害者の詳細
  • 不可逆要素:

    1. 妻との信頼関係の構築
    2. 調査の開始を決意

(続きます)

続けて

フェイズ2:山中の小屋

イベント1:山道での出会い

  • 場所:山中の道

  • 時間:夕暮れ前

  • 情景描写:「急な山道を登っていくと、薄暮の中に人影が見える。薪を背負った男が、ゆっくりと下りてくる。その姿は日が沈みかける山の影に溶け込みそうで、どこか不自然な印象を与える。首筋に何か赤い模様が見えるような...。」

  • 内容:木こり(ろくろ首の首領)との出会い、小屋への誘い

  • 発生条件:PCたちが山道を行く

  • PCの巻き込まれ方:

    1. 木こりからの親切な声掛け
    2. 日没が迫る危機的状況
    3. 行き倒れの演技による同情誘い
  • 必要技能:

    • 目星 成功:木こりの不自然な様子に気づく
    • 心理学 成功:態度の裏に潜む違和感を感じる
    • オカルト 成功:首筋の赤い印に気づく
    • 医学 成功:体の動きの不自然さを察知
  • NPC:木こり(ろくろ首の首領)

  • セリフ例:

    1. 「これからどちらへ?この先は危険ですぞ」
    2. 「拙者の小屋で一夜を過ごされては?」
    3. 「夜の山は、化け物が出るとか...」
    4. 「罪深き身ゆえ、人様との交わりも...」
    5. 「安心せよ、もてなしは粗末だが...」
  • NPCの行動:

    1. 親切に道案内を申し出る
    2. 自身の経歴を匂わせる発言をする
    3. 巧みに小屋へと誘導する
  • 入手情報:

    1. 山の危険についての警告
    2. 木こりの素性の一部
    3. 小屋の場所
    4. 最近の失踪事件についての情報
    5. 木こりの不自然な様子
  • 入手できない情報:

    1. 木こりの正体
    2. ろくろ首の存在
    3. 他の仲間の存在
  • 不可逆要素:

    1. 木こりとの出会い
    2. 小屋への招待

(続きます)

続けて

イベント2:小屋での夜

  • 場所:木こりの小屋

  • 時間:夜

  • 情景描写:「月明かりの下、小屋の中で木こりと4人の男女が炉を囲んでいる。彼らの動きには何か不自然なところがあり、首の周りに違和感を覚える。窓の外では滝の音が響き、時折不気味な風が吹き抜ける。炉の火が揺れるたび、影が歪んで踊る。」

  • 内容:PCたちが小屋で一夜を過ごし、ろくろ首の正体が明らかになる

  • 発生条件:木こりの招待を受ける

  • PCの巻き込まれ方:

    1. 日没による足止め
    2. 木こりたちの歓待
    3. 不自然な状況への違和感
  • 必要技能:

    • 目星 成功:彼らの不自然な動きに気づく
    • オカルト 成功:怪異の気配を感じ取る
    • 回避 成功:不意の攻撃を避ける
    • 隠密 成功:こっそりと様子を探る
  • NPC:

    • 木こり(首領)
    • ろくろ首の一味4人
  • セリフ例:

    1. 「お茶を一杯いかがですか?」(木こり)
    2. 「私たちと一緒に、永遠に...」(女性のろくろ首)
    3. 「逃げられませんよ、もう...」(男性のろくろ首)
    4. 「血の匂いがする...」(若いろくろ首)
    5. 「さあ、宴の始まりです」(木こり)
  • NPCの行動:

    1. 怪しげな様子で食事を勧める
    2. PCたちの警戒を解こうとする
    3. 夜半に正体を現す
  • 入手情報:

    1. ろくろ首の存在確認
    2. 木こりの正体
    3. 一味の規模
    4. 小屋の構造
    5. 彼らの目的
  • 不可逆要素:

    1. ろくろ首との戦闘開始
    2. 正体の暴露

(続きます)

続けて

フェイズ3:追跡と逃亡

イベント1:森での戦闘

  • 場所:山中の森

  • 時間:深夜

  • 情景描写:「月明かりの下、木々の間を首だけが飛び回る恐ろしい光景が広がる。ろくろ首たちは獲物を追い詰めようと、四方八方から迫ってくる。滝の轟音が恐怖を増幅させ、木々の影は不気味に揺らめく。時折聞こえる笑い声は、もはや人のものとは思えない。」

  • 内容:ろくろ首との最初の戦闘、逃走

  • 発生条件:小屋でろくろ首の正体が露見

  • PCの巻き込まれ方:

    1. ろくろ首からの直接攻撃
    2. 森での追跡劇
    3. 胴体の奪取による対立
  • 必要技能:

    • 回避 成功:攻撃を避ける
    • 戦闘技能 成功:ろくろ首を撃退
    • 隠密 成功:一時的に追跡を振り切る
    • 登攀 成功:崖や木を使って逃げる
  • NPC:

    • ろくろ首集団
    • 木こり(首領)
  • セリフ例:

    1. 「逃がさない!」
    2. 「私の胴を返せ!」
    3. 「生き血を頂きます...」
    4. 「仲間になれば痛くありません」
    5. 「月が出ている限り、お前たちは私のもの」
  • NPCの行動:

    1. PCたちを追跡する
    2. 包囲を試みる
    3. 個別に襲撃を仕掛ける
  • 入手情報:

    1. ろくろ首の戦闘能力
    2. 胴体の重要性
    3. 月光との関係
    4. 集団行動のパターン
  • 不可逆要素:

    1. ろくろ首との敵対関係確定
    2. 戦闘開始
    3. 森での追跡状態

(続きます)

イベント2:回龍との出会い

  • 場所:森の中の祠

  • 時間:夜明け前

  • 情景描写:「夜明け前の薄暗がりの中、一人の僧侶が経を読む声が響く。その周りには、何かと戦った形跡が生々しく残っている。経文の声は力強く、それを聞くたびに近づいていた怪異の気配が遠ざかっていく。」

  • 内容:回龍から助言と協力を得る

  • 発生条件:

    1. ろくろ首との戦闘を経験
    2. 夜明けまで生存
    3. 祠付近まで逃げ延びる
  • PCの巻き込まれ方:

    1. 回龍の読経で救われる
    2. 回龍からの呼びかけ
    3. 偶然の出会い
  • 必要技能:

    • 説得 成功:回龍の全面的な協力を得る
    • オカルト 成功:経文の効果を理解する
    • 目星 成功:回龍の本質を見抜く
    • 心理学 成功:回龍の真意を察知
  • NPC:回龍

  • セリフ例:

    1. 「よくぞ生き延びられた」
    2. 「奴らには弱点がある。それは...」
    3. 「私もかつて、奴らを追っていた」
    4. 「経文と日の光、そして...」
    5. 「共に戦おう、道連れよ」
  • NPCの行動:

    1. 経文で怪異を退ける
    2. ろくろ首との戦い方を教える
    3. 自身の経験を語る
  • 入手情報:

    1. ろくろ首の完全な対処法
    2. 木こりの過去と真相
    3. 回龍の正体
    4. 戦いの方法
    5. 最終決戦への準備方法
  • 不可逆要素:

    1. 回龍との協力関係
    2. 対抗手段の獲得

(続きます)

続けて

フェイズ4:決戦

イベント1:儀式の準備

  • 場所:山中の古い寺院跡

  • 時間:日中

  • 情景描写:「苔むした石段、朽ちかけた本堂。かつての信仰の場は今や静寂に包まれている。回龍は古い経典を広げ、PCたちと共に夜の戦いの準備を始める。風に揺られる木々の葉音が、来たる戦いへの緊張を高める。」

  • 内容:回龍と共に決戦の準備を行う

  • 発生条件:

    1. 回龍との協力関係を築いている
    2. ろくろ首の弱点を理解している
  • PCの巻き込まれ方:

    1. 儀式の準備への参加要請
    2. 寺院の結界設置の手伝い
    3. 戦略会議への参加
  • 必要技能:

    • オカルト 成功:儀式の理解と補助
    • 目星 成功:寺院内の重要な場所を見つける
    • 芸術(書道) 成功:護符の作成を手伝う
    • 跳躍 成功:結界を張るため高所に登る
  • NPC:回龍

  • セリフ例:

    1. 「この経文が、奴らを封じる鍵となる」
    2. 「日没までに準備を整えねば」
    3. 「かつての戦いの教訓を活かそう」
    4. 「恐れることはない、共にあれば」
    5. 「準備は整った、後は...」
  • NPCの行動:

    1. 儀式の準備を指示
    2. 戦術を説明
    3. PCたちを励ます
  • 入手情報:

    1. 具体的な戦術
    2. 寺院の地理
    3. 儀式の詳細
    4. 各自の役割
  • 不可逆要素:

    1. 決戦場所の確定
    2. 戦術の決定

(続きます)

続けて

イベント2:最終決戦

  • 場所:寺院跡と周辺の森

  • 時間:満月の夜

  • 情景描写:「満月の下、寺院跡で最後の戦いが始まる。ろくろ首たちは必死の形相で襲いかかり、その首は不気味な笑いを浮かべながら宙を舞う。経文を読み上げる回龍の声が響き、PCたちの準備した結界が月光に輝く。木こりの狂気の叫び声が、戦場に恐怖の色を添える。」

  • 内容:ろくろ首との最終決戦

  • 発生条件:

    1. 儀式の準備完了
    2. 日没
    3. ろくろ首の出現
  • PCの巻き込まれ方:

    1. 決戦への自発的参加
    2. 結界内での戦闘
    3. 回龍の補助
  • 必要技能:

    • 戦闘技能 成功:ろくろ首との直接対決
    • 回避 成功:致命的な攻撃を避ける
    • オカルト 成功:儀式の維持
    • 投擲 成功:護符を投げつける
  • NPC:

    • 木こり(ろくろ首の首領)
    • ろくろ首一味
    • 回龍
  • 首領のセリフ:

    1. 「我が罪業の深さを知れ!」
    2. 「永遠の命を、お前たちにも分けてやろう」
    3. 「この痛みから逃れられぬ!」
    4. 「死して尚、苦しみは続く...」
    5. 「最期に、真実を語ろう...」
  • NPCの行動:

    1. 全力での襲撃
    2. 仲間の死に狂気的な反応
    3. 最後の告白
  • 入手情報:

    1. 木こりの最期の告白
    2. 事件の全容
    3. 呪いの真相
  • 不可逆要素:

    1. ろくろ首との決着
    2. 木こりの最期
    3. 事件の解決

(続きます)

続けて

想定されるエンディング

  1. 完全成功エンディング「贖罪の夜明け」
  • 到達条件:
    1. ろくろ首の全員を退治
    2. PCの死者が出ていない
    3. 木こりの告白を聞いている
    4. 回龍との協力関係を維持
  • 詳細:
    • ろくろ首たちを完全に退治
    • 木こりの告白から真相を知る
    • 回龍との協力で、適切な供養を行える
    • 村の平和が戻る
  • 情景描写:「夜明けとともに、最後のろくろ首も力尽きた。回龍の読経が山中に響き渡る中、木こりは最期に自らの罪を告白し、安らかな表情を浮かべて消えていった。残された首は、回龍の導きにより、丁重に葬られることとなった。山々を照らす朝日は、まるで新しい時代の始まりを告げているかのようだった。村人たちは再び活気を取り戻し、行方不明者の妻も、ようやく夫の供養ができると涙を流した。」
  1. 部分成功エンディング「残された影」
  • 到達条件:
    1. 主要なろくろ首を退治
    2. 一部が逃亡
    3. PCに重傷者がいる
  • 詳細:
    • 主要な脅威は去ったが、完全な解決には至らず
    • 木こりは倒すが、一部の手下が逃亡
    • 村は平和を取り戻すが、不安は残る
  • 情景描写:「戦いは勝利に終わったものの、一部のろくろ首は闇の中へと逃げ去った。回龍は、残された首を供養しながら『まだ終わっていない』とつぶやいた。山の危険は去ったが、どこかで新たな悲劇が始まるのかもしれない。村人たちは喜びつつも、時折夜空を不安そうに見上げている。行方不明者の妻は、夫の形見を抱きしめながら、複雑な表情を浮かべていた。」

(続きます)
3. 失敗エンディング「永遠の呪縛」

  • 到達条件:
    1. PCの過半数が死亡/発狂
    2. ろくろ首が生き残る
    3. 回龍が倒れる
  • 詳細:
    • 調査隊は壊滅
    • ろくろ首の脅威は継続
    • 村は徐々に廃れていく
  • 情景描写:「生き残ったものたちは、戦慄の記憶とともに山を後にした。その後も、山中では首のない死体が発見され続け、夜になると空を舞う何かの影が目撃されている。木こりの小屋は、新たな犠牲者を待ち続けている。行方不明者の妻は、ついに村を去った。街道は人気が途絶え、宿場町は次第に荒れていく。夜になると、どこからともなく経文を読む声が聞こえるという。それは回龍の魂なのか、それとも...」

GMへのメモ

  1. シナリオ進行上の重要ポイント
  • 情報開示の段階的な実施
    • フェイズ1:噂と不安の醸成
    • フェイズ2:怪異の存在確認
    • フェイズ3:真相の一部解明
    • フェイズ4:全容の把握
  1. 時間管理
  • 一回のセッションで4-6時間を想定
  • 各フェイズは以下の時間配分を推奨
    • フェイズ1:1時間
    • フェイズ2:1.5時間
    • フェイズ3:1時間
    • フェイズ4:1-2時間
  1. 難易度調整のポイント
  • PCの人数に応じたろくろ首の数調整
    • 4人の場合:首領+3体
    • 5人の場合:首領+4体
    • 6人の場合:首領+5体
  1. 重要な伏線と回収
  • 木こりの首筋の痕
  • 行方不明者の妻の証言
  • 手ぬぐいの血痕
  • 回龍の正体
  • 寺院跡の意味
  1. 想定される問題への対処
  • PCが小屋に行かない→他のルートを用意
  • 早期に戦闘となる→回龍の早期登場
  • 分断される→NPCによる救援
  • 儀式準備の失敗→代替手段の提示
  1. 演出のコツ
  • 夜の描写を重視
  • 怪異の出現は段階的に
  • NPCの違和感は徐々に強める
  • 最終決戦は荘厳に
  1. 戦闘のバランス
  • 初期戦闘は逃走重視
  • 中盤は試行錯誤
  • 最終決戦は総力戦

以上でシナリオの完全版を終了します。

シナリオの改善は本質ではないため、これでヨシとします。

改善案

  • 今回は単純なMarkdownなので、ほかの形式を試す。たとえばClaude SonnetはXMLの方が有効
  • スキーマ活用も含めて、もっとゴリゴリにメタ認知を入れる
  • 仮出力と仮出力の検証と本出力のそれぞれのプロンプトを実際に分ける。
  • さらに細かく分解する。ただし分解すると整合性がとりづらくなるため、いろいろと考える必要がある
  • プロンプトの共通部分を前方に集めれば、OpenAI/Anthropic APIではキャッシュが効くため、それを活用する
  • NPCにおける味方と敵のバランスについてメタ認知を追加させる
  • そもそもメタ認知を生成できるようにする(メタ認知のメタプログラミング)
    • スキーマベースで、メタ認知用の項目で自動挿入させるとかが可能
  • 面白い物語を作るための方法論のテキストを追加する
  • シナリオサンプルを実際に作るなりして提示する(いわゆるfew-shot)
  • プロンプトが複雑になればなるほどLLMの知性は低下するため、プロンプトをダイエットさせる。

いささか、矛盾があるところですが、いい案配を探るしかないです。
興味がある人は是非トライしてみてください。なかなかに楽しいです。

プロンプトジェネレータを作る

さて、長い前置きは終わりです。やっとプロンプトジェネレータ説明です。今回は、単純なMarkdownを出力するものとします。

先に、プロンプトジェネレータそのものよりも、プロンプトジェネレータを使ってシナリオを生成するコードを見ていきましょう。

プロンプトジェネレータを使ってシナリオを生成するコード

プロンプトジェネレータは汎用的な仕組みとして作っていて、TRPGのシナリオ作りというドメイン知識はこのコードに閉じ込めています。

import { generatePrompt } from "./generator";
import { constraint, group, output, step, text } from "./spec";
import type { LeafSpec, Spec, TextSpec } from "./types";

/**
 * 基本的なプロンプトを生成する
 */
const createInitialInstructions = (): Spec[] => [
  group("あなたの使命", [
    constraint(
      "must",
      "小説を元に4人以上のプレイヤー向けTRPGシナリオを作成する",
    ),
    constraint("must", "システムはクトゥルフの呼び声TRPGを利用する"),
  ]),
  step([
    constraint("must", "まず「出力物」を仮出力する"),
    constraint("must", "仮出力のシナリオのそれぞれのフェイズを要約する"),
    constraint("check", "フェイズ間の不足点を確認する"),
    constraint("must", "1〜3の情報を元に最終成果物を出力する"),
  ]),
];

/**
 * TRPGシナリオのプロンプトを生成する
 */
const createScenario = (): Spec[] => {
  const scenarioTopics = group("シナリオのテーマ", [
    output([constraint("must", "そのシナリオのテーマを1〜3個作成する")]),
  ]);

  const settingConfiguration = group("舞台設定", [
    output([
      constraint("must", "舞台となる国と時代を選ぶ"),
      constraint(
        "may",
        "時代は戦国時代/1920年代/昭和・平成/現代など。それ以外でもかまわない",
      ),
    ]),
  ]);

  const pc = group("プレイヤーキャラクター(PC)", [
    constraint("must", "このシナリオで想定するPCを人数分出力する"),
    output([
      constraint("must", "推奨職業"),
      constraint("must", "推奨技能"),
      constraint("must", "シナリオバランスのための想定能力値"),
      constraint("must", "ハンドアウトのサンプル(150字程度)"),
      constraint("must", "必須PCの有無"),
    ]),
  ]);

  const npc = group("NPC", [
    constraint("must", "重要なモンスターはNPCとして扱うこと"),
    output([
      constraint("must", "名前と基本情報"),
      constraint("must", "口調・性格"),
      constraint("must", "PCに対する態度(友好/敵対/協力者のふり等)"),
      constraint("must", "描写(200文字程度)"),
      constraint("must", "GM向け詳細設定"),
      constraint("must", "あるなら、所属組織"),
      constraint("must", "能力値"),
      constraint("must", "他NPCとの関係性"),
      constraint("must", "会話可能なNPCなら特徴的なセリフ5個"),
    ]),
  ]);

  const organization = group("組織", [
    output([constraint("must", "名前"), constraint("must", "組織の目的")]),
  ]);

  const monster = group("モンスター", [
    output([
      constraint("must", "名前"),
      constraint("must", "PCに対する態度(友好/敵対/協力者のふり、等)"),
      constraint("must", "描写(200文字程度)"),
      constraint("must", "GM向け詳細設定"),
      constraint("must", "能力値"),
      constraint("must", "NPCとの関係性"),
      constraint("must", "会話可能なNPCなら特徴的なセリフ5個"),
    ]),
  ]);

  // イベントを定義
  const eventSpecs: LeafSpec[] = [
    constraint("must", "場所"),
    constraint("may", "時間"),
    constraint("must", "典型的な情景描写(200字程度)"),
    constraint("must", "内容"),
    constraint("must", "発生条件、PCの巻き込まれ方"),
    constraint("must", "必要なPCの技能"),
    constraint("must", "NPC"),
    constraint("must", "このイベントにおけるNPCの台詞(3〜7個)"),
    constraint("must", "このイベントにおけるNPCの行動(1〜3個)"),
    constraint(
      "check",
      "NPCは「NPC」に書かれているか?書かれてなければ、途中の状態でもいいのでわかる形で追加で書き出すこと",
    ),
    constraint("check", "NPC同士の関係性"),
    constraint(
      "check",
      "セリフや行動は「NPC」の「描写」「GM向け詳細設定」「PCに対する態度」に合致しているか?",
    ),
    constraint("must", "このイベントの結果、入手できる情報/できない情報"),
    constraint("may", "入手アイテム"),
    constraint("must", "このイベントの結果によって、不可逆の要素すべて"),
    constraint("must", "このイベントの目的"),
    constraint("must", "このイベントはほかのイベントと並列で発生可能か?"),
    constraint("may", "このイベントの前に依存関係があるなら列挙する"),
  ];

  const phase1 = group("フェイズ1", [
    text(
      "フェイズ1は、プレイヤーが自然に事件に巻き込まれるような導入フェイズである。",
    ),
    text("フェイズは、1〜n個のイベントの繰り返しである"),
    output([
      constraint("must", "イベント1"),
      constraint("may", "イベント2〜n"),
    ]),
    group("イベント", [output(eventSpecs)]),
  ]);
  const phase2 = group("フェイズ2", [text("フェイズ1と同様の形式")]);
  const phase3 = group("フェイズ3", [text("フェイズ1と同様の形式")]);
  const phase4 = group("フェイズ4", [
    text("フェイズ1と同様の形式"),
    text(
      "フェイズ4は決着をつけるための最終局面のためのクライマックスフェイズである",
    ),
  ]);

  const scenario = group("シナリオ", [
    text(
      "シナリオは、フェイズから成り立っている。各フェイズを自然に接続し、クライマックスに向けて盛り上がるように記述すること",
    ),
    // 🔑 フェイズの参照(phase1.subject)で一貫性を保証
    output([
      constraint("must", phase1.subject),
      constraint("must", phase2.subject),
      constraint("must", phase3.subject),
      constraint("must", phase4.subject),
      constraint("check", "各フェイズは自然に接続しているか?"),
      constraint(
        "check",
        "クライマックスに向けて盛り上がるようになっているか?",
      ),
    ]),
    phase1,
    phase2,
    phase3,
    phase4,
  ]);

  const ending = group("想定されるエンディング", [
    output([
      constraint("must", "各結末への到達条件"),
      constraint("must", "結末の詳細"),
      constraint("must", "典型的な情景描写"),
    ]),
  ]);

  const gmMemo = group("GMへのメモ", [
    output([constraint("must", "シナリオ進行時の重要ポイント")]),
  ]);

  const outputSpecs = [
    scenarioTopics,
    settingConfiguration,
    pc,
    npc,
    organization,
    monster,
    scenario,
    ending,
    gmMemo,
  ];

  return [
    group("出力物", [
      output(outputSpecs.map((spec) => constraint("must", spec.subject))),
    ]),
    ...outputSpecs,
  ];
};

export const createTRPGScenarioPrompt = (): string => {
  // 🔑 プロンプトを2段階で構築
  // 1. 基本的なプロンプト
  // 2. TRPGシナリオのプロンプト
  const specs: Spec[] = [...createInitialInstructions(), ...createScenario()];

  return generatePrompt(specs);
};

これがプロンプトジェネレータを使ってTRPGを出力するためのコードです。先ほどのMarkdownテキストをメンテするのとどちらが楽かというと人それぞれかもしれません。

  • group はグルーピングを指示する関数
  • output は出力を指示する関数
  • constraint は制約条件(【必須】など)を付与したテキストで指示する関数
  • text は特に制約条件のないテキストで指示する関数
  • step は順番に実行させるための指示をする関数

これらの指示をする関数を使って構築した Spec[]generatePrompt に投げることでプロンプトを生成しています。

シナリオの出力部分は、少し独特なコードになっているため解説します。

  const scenario = group("シナリオ", [
    text(
      "シナリオは、フェイズから成り立っている。各フェイズを自然に接続し、クライマックスに向けて盛り上がるように記述すること",
    ),
    output([
      constraint("must", phase1.subject),
      constraint("must", phase2.subject),
      constraint("must", phase3.subject),
      constraint("must", phase4.subject),
      constraint("check", "各フェイズは自然に接続しているか?"),
      constraint(
        "check",
        "クライマックスに向けて盛り上がるようになっているか?",
      ),
    ]),
    phase1,
    phase2,
    phase3,
    phase4,
  ]);

シナリオのセクションでは、テキストの説明文が1つと出力内容の指示と、フェイズについての説明が含まれています。
説明文にもあるように、シナリオは子要素であるフェイズ1〜4を合わせたものです。

constraint("must", phase1.subject) でその子要素の名前を使いつつ phase1 そのものもプロンプトに含めています。
同じことを一番最初の「出力物」でもやっています。

生のプロンプトをいじってると、結構矛盾を抱えることが多いです。ある箇所を修正したけど、修正しきれてないというものです。
LLMは、そういった矛盾によってハルシネーションを生み出しやすいため、矛盾を発生させないためのテクニックが重要です。

types.ts

/**
 * 出力内容についての指示
 * Zod Schemaを使うとより強固な指示が可能になる
 */
export type OutputSpec = {
  type: "output";
  children: LeafSpec[];
};

/**
 * 制約条件の種類
 * checkのみ制約条件ではなく、メタ認知用
 */
export type ConstraintType = "must" | "should" | "may" | "dont" | "check";

/**
 * 文字列による指示。制約条件を持たせられる
 */
export type TextSpec = {
  type: "text";
  constraintType?: ConstraintType;
  content: string;
};

/**
 * 順番にステップ実行させるための指示
 */
export type StepSpec = {
  type: "step";
  children: LeafSpec[];
}

/**
 * 再帰的に表現可能なグルーピングの指示
 */
export type GroupSpec = {
  type: "group";
  subject: string;
  children: Spec[];
};

/** 終端ノードの制約 */
export type LeafSpec = TextSpec;

export type Spec =
  | OutputSpec
  | TextSpec
  | StepSpec
  | GroupSpec;

プロンプトの構造を表現するため、以下のような設計思想で型を定義しています。

  1. 木構造による表現
    • GroupSpecを頂点として、再帰的にプロンプトを構築できる
    • これにより、「シナリオ > フェイズ > イベント」のような階層的な指示が可能に
  2. テキストに制約を付与
    • 単なる文字列ではなく、制約(must/should等)を付与できる
    • これにより、LLMへの指示の強さを明示的に制御
    • なお、ここではやっていないが、指示を反復させると、より強固に従うようになる。ねじ伏せテクニックの一つ
  3. 出力形式の制御
    • OutputSpecStepSpecは子要素にLeafSpecのみを許容
    • これはMarkdownでリスト構造のネストを考えたくなかったから
    • XMLなら気にする必要がない

実際のところ妥協の産物ではあるため、よりよいデータ表現はないか?を考えたいところです。

spec.ts

spec.ts は指示を与えるための output 関数などを定義しているコードです。 outputを実行すると Spec 型のデータが帰ってきます。

import type {
  ConstraintType,
  GroupSpec,
  LeafSpec,
  OutputSpec,
  Spec,
  StepSpec,
  TextSpec,
} from "./types";

export const output = (children: LeafSpec[]): OutputSpec => ({
  type: "output",
  children,
});

export const constraint = (
  constraintType: ConstraintType,
  content: string,
): TextSpec => ({
  type: "text",
  constraintType,
  content,
});

export const text = (content: string): TextSpec => ({
  type: "text",
  content,
});

export const step = (children: LeafSpec[]): StepSpec => ({
  type: "step",
  children,
});

export const group = (subject: string, children: Spec[]): GroupSpec => ({
  type: "group",
  subject,
  children,
});

あまり解説する必要もないでしょう。現状では Spec のデータを作成するためのDTO作成関数にすぎません。

generator.ts

import type { ConstraintType, Spec } from "./types";

const getConstraintLabel = (constraintType?: string): string => {
  switch (constraintType) {
    case "must":
      return "【必須】";
    case "should":
      return "【推奨】";
    case "may":
      return "【任意】";
    case "dont":
      return "【禁止】";
    case "check":
      return "【確認】";
    case undefined:
      return "";
    default:
      throw new Error(`Unknown constraint type: ${constraintType}`);
  }
};

const formatText = (
  content: string,
  constraintType?: ConstraintType,
): string => {
  return `${getConstraintLabel(constraintType)}${content}`;
};

const formatOutput = (children: Spec[]): string => {
  return `以下を出力する:\n${generatePrompt(children, { mode: "bulleted" })}`;
};

const formatStep = (children: Spec[]): string => {
  return `以下の手順を実行する:\n${generatePrompt(children, { mode: "numbered" })}`;
};

const formatGroup = (
  subject: string,
  children: Spec[],
  groupLevel: number,
): string => {
  const prefix = "#".repeat(groupLevel);
  return `${prefix} ${subject}\n\n${generatePrompt(children, { groupLevel: groupLevel + 1 })}`;
};

type GeneratePromptOptions = {
  groupLevel?: number;
  mode?: "numbered" | "bulleted";
};

const getPrefix = (index: number, opt?: GeneratePromptOptions): string => {
  if (opt?.mode === "numbered") {
    return `${index + 1}. `;
  }
  if (opt?.mode === "bulleted") {
    return "* ";
  }
  return "";
};

/**
 * 仕様を元にプロンプトを生成する
 */
export const generatePrompt = (
  specs: Spec[],
  opt?: GeneratePromptOptions,
): string => {
  const groupLevel = opt?.groupLevel ?? 1;
  return specs
    .map((spec, i) => {
      const prefix = getPrefix(i, opt);

      switch (spec.type) {
        case "output":
          return prefix + formatOutput(spec.children);
        case "text":
          return prefix + formatText(spec.content, spec?.constraintType);
        case "step":
          return prefix + formatStep(spec.children);
        case "group": {
          return prefix + formatGroup(spec.subject, spec.children, groupLevel);
        }
      }
    })
    .join("\n");
};

Spec型で表現されたデータを、テキストに加工しているだけのシンプルなコードです。この中身をXML対応させたり、ほか様々な加工をさせることで、ドメイン知識の記述されたコードと、プロンプトの進化を分離させることができるのです。

発展

  • 様々なデータ形式に対応する
    • 出力形式をXMLにする
    • スキーマ定義を出力する
  • プロンプトの分割に対応する
    • 別に1つのプロンプトを生成するだけの存在じゃなくてもいい
    • AsyncGeneratorにしてしまうのもありだと思う
  • 文体をローカルLLMなど低コストなLLMで修正する
    • 修正ツールをリポジトリに入れておき、更新を検知して修正をするなど
  • LLMごとに、ストラテジーを変えられるようにする
  • もっと使い勝手のいいI/Fを考える

発展性はいろいろあると思います。

まとめ

さて、長々とお付き合いありがとうございました。

本記事では、TRPGシナリオ生成を例にプロンプトジェネレータの実装と利点を見てきました。プロンプトを生でいじると技術的負債が生まれやすい。これは、LLMの進化が急速なため必然的にプロンプトが陳腐化し、また異なるモデル間で最適な指示方法が違うためです。

これに対し、プロンプトジェネレータを使えばプロンプトを構造化でき、ドメイン知識とプロンプト生成のロジックを分離できます。

この手法は、今回扱ったTRPGシナリオに限らず、文書生成やデータ分析など様々な領域で活用できるはずです。それぞれの分野特有の制約や構造を型として表現し、異なるLLMの特性に合わせて出力フォーマットを調整することもできます。

今後は、メタ認知プロンプトのさらなる活用や、プロンプトの自動最適化など、まだまだ発展の余地があります。型システムについても、より柔軟な設計の可能性を探っていきたいところです。

今回紹介した手法は一例に過ぎませんが、プロンプトジェネレータを活用することで、LLMの活用をより持続可能なものにできると考えています。皆さんのプロジェクトでも、ぜひプロンプトの構造化を検討してみてください。

ということで、アドベントカレンダー2024/12/12でした。遅れてすみません!

Discussion