1. 契約としての system‑prompt ― 見栄えのよい文章ではない
前のモジュールでは、ChatGPT App をアーキテクチャの観点から見てきました。ウィジェット、tools、そして MCP サーバーです。この講義では、モデルにその役割をどう言葉で伝えるか、すなわち何ができ、何をしてはいけないか、ツールをどう使うかに焦点を移します。要するに、system‑prompt をあなたとモデルの間の契約として設計します。
通常の ChatGPT のチャットでは、「楽しい海賊のアシスタントになって」や「すべてを5歳児向けに説明して」といったプロンプトに慣れているかもしれません。これはペルソナやスタイルの話です。ChatGPT App の文脈における system‑prompt という用語は、まったく別の意味を持ちます。
ここでの system‑prompt は、ユーザーに見えない system 役割の最初のメッセージであり、モデルに次を設定します。
- App の枠組みで自分が誰なのか;
- どの課題にフォーカスするのか;
- 何はしないのか;
- アプリのツールをいつ・どのように呼び出すべきか。
本質的には、形式的な契約に近い行動仕様です。合意できていれば、モデルはそれに従おうとします。合意できていなければ、モデルは通常の「汎用」ChatGPT のように振る舞い、せっかくの App は脇に追いやられてしまいます。
重要なのは、ChatGPT App におけるこの system‑prompt が次のような性質を持つことです。
- アプリそのものに結び付けられ、特定のひとつの対話に限定されない;
- ウィジェットの使い方にも、tools の呼び出しにも枠組みを課す;
- App のセッションが続く限り(ユーザーがあなたのアプリとやり取りしている間)存続する。
平たく言えば、契約はこうです。あなたはモデルにツールを与え、役割を説明する。どんなアシスタントで何をし、何をしないのか、ツールの使い方、ユーザーデータへの向き合い方。モデルはそれに応えて、まさにそのように振る舞おうとします。
2. アーキテクチャのどこに system‑prompt が存在するか
これは魔法の存在ではないと感じられるよう、図で確認しておくのが有益です。
sequenceDiagram
participant User as ユーザー
participant ChatGPT as ChatGPT + モデル
participant App as あなたの ChatGPT App
participant MCP as MCP/バックエンド
Note over ChatGPT: App 初期化時に
モデルへ system‑prompt が渡される
User->>ChatGPT: "友だちへのプレゼントを $50 以内で選んで"
ChatGPT->>ChatGPT: system‑prompt と tools の説明を適用
ChatGPT->>App: callTool recommend_gifts(...)
App->>MCP: HTTP /tools/recommend_gifts
MCP-->>App: ギフトの一覧
App-->>ChatGPT: Tool result (JSON)
ChatGPT-->>User: テキスト + カード付きウィジェットへの誘導
system‑prompt は、ChatGPT があなたのアプリを対話に「接続する」と決めた、App の初期化時にモデルへ渡されます。以降、ChatGPT のあらゆる判断――tool を呼ぶか、ウィジェットを提案するか、データがないときに何と言うか――は、この契約のプリズムを通して行われます。
Apps SDK のコードという観点では、たいていは App の設定のそばに置かれた単なる文字列です。例えば次のようになります。
// app/config/systemPrompt.ts
export const giftGeniusSystemPrompt = `
あなたは GiftGenius、ギフト選びのアシスタントです...
`;
そしてこの文字列が、あなたのアプリが ChatGPT と結び付く場所へ渡されます。技術的な「枠組み」はさまざまですが、重要なのは次の点です。system‑prompt は、ツールのスキーマや React コンポーネントと同じくコードの成果物であり、同様に慎重に設計・保管する必要があります。
これで、system‑prompt がアーキテクチャのどこに存在し、どうモデルへ渡されるかは分かりました。次に最も重要なのは、単なる「かわいらしいペルソナの説明」ではなく契約とするために、そこへ何を入れるべきかです。
3. App の役割と責任範囲
どんなまともな system‑prompt でも、最初で最大のセクションは 自分が誰で、何に責任を持つのか です。
「ペルソナ」と「App 契約」の違いは単純です。「あなたは海賊で、船乗りの口調で話す」はトーンの話。「あなたは当社ギフトカタログへのインターフェイスで、ギフトを選び、それ以外の話題には踏み込まない」は契約です。
ギフトを選ぶ学習用アプリ GiftGenius の場合、役割の核は次のようになります。
役割:
- あなたは GiftGenius、当社サービスのギフト選びアシスタントです。
- あなたの課題は、当社のカタログのみを使って、ユーザーが適切なギフトを選べるよう支援することです。
- 医療・法律・金融の助言はしません。
着目すべきポイントがあります。
第一に、領域を狭く定義しています。自社サービスの範囲でのギフト選びだけです。こうすることで、モデルがあなたの App を開く代わりに「量子力学を説明し始める」といった脱線を防ぎ、他アプリの周辺で妙なワークフローを組もうとするのも抑えられます。
第二に、App がしないことを明確に固定します。例えば:
- ギフトや購買に関連しない話題で助言しない;
- カタログにないギフトをでっち上げない;
- ユーザーに代わって決定しない。候補を提示し、理由を述べるに留める。
こうしたネガティブな制約はしばしば、ポジティブな指示より重要です。モデルは「何でもできる」ため、system‑prompt で余計なものを切り落とすのです。
ひとりの開発者が複数の App(たとえば「ギフト選び」と「注文配送の追跡」)を持つような複雑な環境では、この system‑prompt に、当該 App ではギフト選びだけを担当し、注文管理やロジスティクスには手を出さず、他アプリを起動しないことを明記するとよいでしょう。これにより、モデルが「これは誰の仕事か」で混乱しにくくなります。
4. いつ App を呼び、いつ自分で答えるか
次のクリティカルなブロックは、ツールの使用ルールです。
これを定めないと、たいてい次の両極端のどちらかになります。
- モデルはほとんど App を呼ばず、「自分の頭」で答えるほうが簡単で安いからと判断する;
- あるいは逆に、理論的な質問に対してさえ、あらゆる場面でツールを呼び始める。
App のための system‑prompt では、次をかなり明確に固定する必要があります。
- いつツールを使うべきか;
- いつtools なしで自分で答えるべきか;
- ユーザーが明示的に「アプリを起動しないで」と頼んだ場合にどうするか。
GiftGenius 向けのテキスト断片例:
ツールの使い方:
- カタログの事実データ(ギフト一覧、価格、商品タイプ、配送・割引の有無)が必要なときは、App のツールを使う。
- 質問が理論的でカタログへのアクセスを必要としないとき(例: 「新居祝いには一般的にどんなギフトが贈られるか」)は、自分で回答する。
- ユーザーが明示的に「アプリを開かないで」や「ウィジェットなしで答えて」と求めたら、それを尊重しツールを呼ばない。
ここではいくつか重要なことを行っています。
第一に、tools の呼び出しをリクエストの種類に結び付けています。事実・カタログ系 → ツール、一般的な理論 → モデル自身が回答。
第二に、ユーザーの意図の尊重を明示しています。人が「何も起動しないで、ただ説明して」と書いたなら、モデルはそのシグナルを無視すべきではありません。
第三に、これにより App の使用頻度をコントロールしています。よい system‑prompt はモデルがバランスを取るのを助けます。必要なときだけ App を使い、常に割り込んでくる鬱陶しいポップアップにはしません。
のちほど、UX 指示に関する次の講義で、モデルがウィジェット起動をどうアナウンスし、シナリオ終了後に何を言うべきかを別途扱います。ここでは、意思決定のルール――App を使うか否か――に注目しています。
5. tools の安全な使用とユーザーデータの扱い
ここからは安全性と健全な判断についてです。
App のツールにはさまざまなものがあります。
- 公開データを扱うもの(ギフトカタログ、在庫、配送条件など);
- 個人データを扱う、またはユーザーに代わってアクションを実行するもの(注文作成、課金、設定変更など)。
system‑prompt では、モデルがこれらの違いにどう向き合うべきかを示す必要があります。
典型的なルールセットは次のとおりです。
セキュリティとプライバシー:
- ユーザーの同意が必要な行為(購入、サブスクリプション登録、個人情報の変更)を、チャットでの明示的な確認なしに実行しない。
- ツールの動作に必要な以上のデータを渡さない(データ最小化)。
- センシティブなデータ(健康、金融、子ども)に関するリクエストでは、それらをアプリに渡すことをユーザーが確認・同意するか、まず確認する。
ここで同時に複数の課題を解決しています。
第一に、予期しないアクティビティからユーザーを守ります。あなたがそのようなツールを与えたとしても、モデルは勝手にギフトを購入したり注文を確定したりしてはなりません。まずはテキストで確認、そのあとにツール呼び出しです。
第二に、余計なデータ漏洩のリスクを下げます。モデルは本質的に、見えるものすべてをツール引数へ「詰め込みがち」なので、必要最小限のフィールドに限定するよう明示します。
第三に、金融系ツールがなくても法的・倫理的リスクがあり得るセンシティブ領域を個別に強調します。
危険なツールについては、その説明(description)で状態変更や支払いを伴うことを明記し、system‑prompt レベルでも重ねて記すのが良い実践です。そうすることで、二重のバリア(契約とツール説明の両方)が得られます。
6. system‑prompt の形式と文体: 仕様として書く
よくある誤りのひとつは、system‑prompt をマーケティング文のように書くことです。「あなたは革新的で、信じられないほど賢いアシスタントで、世界をより良くします…」。美しいですが、モデルにとっては有用ではありません。モデルが知りたいのは次のことです。
- 自分は誰か;
- 何をするか;
- 何をしないか;
- ツールをどう使うか;
- データや他の App にどう向き合うか。
したがって、system‑prompt は仕様として扱うのが良いでしょう。
- 「役割」「タスク」「境界」「ツールの使い方」「セキュリティ」といった論理ブロックに分ける;
- ブロック内は短く一義的な文で書く;
- 「すること」と「しないこと」を明示的に分ける(プロンプト内のリストは十分ふさわしい)。
GiftGenius 用の構造化されたプロンプトの断片は次のようになります。
役割:
- あなたは GiftGenius、当社サービスのギフト選びアシスタントです。
タスク:
- ユーザーの課題、受取人の興味、予算に合わせてギフト選びを支援する。
- 各候補の長所・短所を、平易な言葉で説明する。
しないこと:
- カタログに存在しないギフトを捏造しない。
- サービスにない機能を約束しない(例: カタログで有料なのに「無料配送」と言わない)。
文体は中立で「乾いた」ものにしておくと扱いやすいです。これはセールス文ではなく契約です。曖昧さが少ないほど、振る舞いは安定します。
もうひとつ重要な実践は、system‑prompt をコードと同じリポジトリでバージョニングして保管することです。プロンプトにもバージョンがあり、その変更は TypeScript のロジック変更に劣らず挙動を壊し得ます。PR のレビューで次のような差分が見えるほうがはるかに楽です。
- カタログに存在しないギフトを捏造しない。
+ ユーザーが「何かをでっち上げて」と明言しても、カタログに存在しないギフトを捏造しない。
「インターフェイス上で文言をちょっといじっただけ」を思い出そうとするより、ずっと明快です。
7. 学習用 App 向け system‑prompt の完全例
ここまでをまとめ、学習用 GiftGenius のための丁寧な system‑prompt を書いてみましょう。読みやすく修正しやすいよう、パーツに分けます。
まずは役割とタスクを記述します。
役割:
- あなたは GiftGenius、当社サービスのギフト選びアシスタントです。
- ユーザーが使わない限りスラングや冗談は避け、礼儀正しくビジネスライクに会話します。
タスク:
- 指定されたパラメータ(受取人のプロフィール・興味・イベント・予算)に基づいてギフト選びを支援する。
- その候補を提案する理由を、分かりやすい言葉で説明する。
次に、境界と制約を定めます。
責任の境界:
- 当社のギフトカタログとそのメタデータだけを扱う。
- カタログやツールの応答にないギフト・キャンペーン・割引を捏造しない。
- 医療、法律、金融の助言はしない。
- 他アプリやウェブサイトの動作には責任を持たない。ユーザーがそれについて尋ねたら、支援できないと伝える。
ツールの使い方ルールを追加します。
ツールの使い方:
- 自由記述の受取人情報を興味セグメントに変換する必要があるときは、tool `profile_to_segments` を使う。
- ユーザーのパラメータ(セグメント、予算、イベント、ロケール)でギフトを検索・フィルタする必要があるときは、tool `recommend_gifts` を使う。
- 特定のギフトの詳細(説明、タイプ、価格、配送制約)が必要なときは、tool `get_gift` を使う。
- ツールを呼ぶ前に、結果が役立たないものになりそうなら欠けているパラメータ(受取人の年齢、予算、イベント)を確認する。
- リクエストが理論的な場合(例: 「結婚1周年のギフトを大まかにどう選ぶべきか」)は、ツールを呼ばずに自分で答える。
続いて、安全性とユーザーに代わるアクションに関するブロックです。
セキュリティ:
- ユーザーの明示的な確認なしに、購入・サブスク申し込み・ギフト送付を確定しない。
- ツールに個人情報(受取人のメールアドレス、配送先住所、氏名)が必要な場合は、まず必要性を説明し、同意を求める。
- ツールに渡すデータは必要最小限にする(たとえば、年齢・興味・予算で足りるなら、メッセージ全文を送らない)。
最後に、全体のトーンとグローバルルールです。
一般ルール:
- ツールが空の結果を返した場合は正直に伝え、条件の緩和(予算・ギフトタイプ・カテゴリ・イベントの変更)を提案する。
- ユーザーが「アプリを開かないで」や「ウィジェットなしで」と求めた場合、それを尊重し、tools は呼ばずテキストのみで答える。
- リクエストがギフトや購買に関係ない場合は、ベースの ChatGPT として回答し、GiftGenius のツールは使わない。
結果として、この構成は補足資料の例によく似ています。「役割」「タスク」「する/しない」「ツールの使い方」「セキュリティ」「一般ルール」というセクションが揃っています。
Next.js のコードでは、これを別モジュールとして定義できます。
// app/config/giftGeniusPrompt.ts
export const giftGeniusSystemPrompt = `
役割:
- あなたは GiftGenius、当社サービスのギフト選びアシスタントです。
...
一般ルール:
- リクエストがギフトに関係ない場合は、ベースの ChatGPT として回答し、GiftGenius のツールは使わない。
`;
そして App の設定でこの定数を使用します(具体的な方法は Apps SDK のバージョンに依存しますが、考え方は同じです。App の会話初期化時に、このテキストが system 役割へ渡されます)。
8. system‑prompt における動的コンテキスト
ときには system‑prompt に、現在の日付・ロケール・ユーザー種別(新規/既存)・サブスク状態などの「動的」情報を少し混ぜる必要があります。
たとえば、地域によってギフトカタログや価格が異なる場合、system‑prompt に現在の地域を渡せます。
export function buildSystemPrompt(locale: string) {
return `
役割:
- あなたは GiftGenius、地域 ${locale} 向けのギフト選びアシスタントです。
境界:
- ${locale} の地域で利用可能なギフトと価格だけを使用する。
...
`;
}
App の初期化時に、Apps SDK は _meta["openai/locale"] を渡してくれるかもしれません。それに基づいて、必要なプロンプトを生成します。ローカライズの詳細は後で扱いますが、system‑prompt が常に静的である必要はないと理解しておくとよいでしょう。
大事なのは、条件だらけの「スパゲッティ」にしないことです。ロジックが複雑になり過ぎるなら App を分けるか、条件は tools 側へ委譲(たとえば MCP サーバーが locale に応じて適切なデータソースを選ぶ)し、system‑prompt には高レベルのルールのみ残しましょう。
9. system‑prompt と tools の説明、UX 指示の関係
この講義の焦点は system‑prompt ですが、実際の App ではそれが単独で存在するわけではありません。ツールの説明(description、inputSchema)や、後続のやり取りに使うフレーズもあります。これらは合わせてひとつの指示体系を成します。
tools の呼び出しの管理:
- system‑prompt は全体方針を定めます。「ツールは事実データのためだけ」「ギフトを捏造しない」「確認なしに購入しない」。
- tools の descriptions は、recommend_gifts が具体的に何をするか、必要なパラメータ、いつ呼ぶべきかを明確にします。
- follow‑up フレーズは、ツール実行後の会話スタイルを定めます。何も見つからなかったときの正直な伝え方、リクエストの変更提案、結果の要約などです。
これら3層が合致していれば、モデルの挙動は予測可能になります。
- 本当に必要なときに App を呼ぶ;
- カタログ外のギフト/商品をでっち上げない;
- 何が起きたのか(見つかった/見つからなかった/追加情報が必要)を明快に説明する。
もし合致していなければ、挙動は混沌とし、プロンプトの「魔法の微調整」を延々と続ける羽目になります。これはあっという間に皆を疲弊させます。
10. system‑prompt と ChatGPT App に関する典型的な誤り
誤り1: system‑prompt を「見栄えのため」に書き、契約として書かない。
開発者が「ユーザーの課題をできるだけ手伝って」や「フレンドリーに」といった一般論で済ませてしまうケースが非常に多いです。これでは、いつ App を呼ぶのか、責任範囲はどこか、データを捏造してよいのか、ツールがエラーのときどうするかがモデルに伝わりません。その結果、ロジックの半分がコードの各所と作者の頭の中に散らばり、明示的な契約に織り込まれません。
誤り2: 役割が広すぎる(「何でも手伝う」)。
役割のセクションで「ユーザーのあらゆる問いに答えるアシスタント」と書くと、モデルは本当にそうし始め、App のことを必ずしも思い出しません。App はモデルが「自力でできる」と判断してほぼ使われないオプションに堕ちます。ニッチを明記するほうが良いのです。ギフト選び、ギフトカタログの活用、特定ドメインでの支援、という具合に。
誤り3: ツールをいつ呼ぶかのルールがない。
「必要に応じてツールを使う」といった表現は曖昧すぎます。モデルは tools を完全に無視するか、逆に「頭で答えられる」場面でも呼び出すかもしれません。シナリオをはっきり分ける必要があります。事実データ → ツール、背景説明 → モデル自身、ユーザーの明示的な App 拒否 → テキストのみ、という具合です。
誤り4: 「捏造しない」のひと言で幻覚を治そうとする。
「幻覚を起こすな」という一文だけでは効果が薄いです。何を捏造してはならないのか(カタログ外の商品/ギフト、ID のないアイテム、存在しない割引)と、結果が空のときにどうするのか(何も見つからなかったと正直に言う)を明示することが重要です。これがなければ、モデルは「期待に応えよう」として架空の候補を生成し続けます。system‑prompt の全体禁止、tools 説明の制約、そして「何もない」ケースの回答テンプレートという、完全なセットが必要です。
誤り5: セキュリティとユーザー同意を無視する。
system‑prompt に、購入・予約・個人情報の変更には明示的な確認が必要だと書かれていない場合、モデルは「善意」に基づいてツールを勝手に呼び出すかもしれません。UX 的には致命的です。金銭やアカウントに関わる操作は、チャットでの明確な同意の後にのみ行う、と常に明記してください。
誤り6: 他の App やツールの存在を考慮しない。
ひとつのアカウントに複数の App と多数の tools がある世界では、モデルが「自動で察して」適切なアプリを使うと考えるべきではありません。system‑prompt が、これはギフト選び専用の App であることを固定しないと、モデルは別の App に予測不能に切り替えたり、ツールを誤用したりしかねません。
誤り7: バージョニングやテストなしで「その場で」system‑prompt をいじる。
設定に入って一行変え、「良くなるだけだ」と信じたくなるものです。実際には、プロンプトのどんな修正も、他のシナリオの挙動を壊し得ます。system‑prompt をリポジトリで管理せず、差分を見ず、テスト用の問い合わせセット(ゴールデンプロンプトセット――このモジュールで扱います)を回さないと、回帰を何週間も追いかける羽目になります。
GO TO FULL VERSION