AIに1トーク2万文字のプロンプトを投げたら人格が宿った件:②設計編
はじめに
前回の①紹介編では、AIチャットボットアプリ「VFriend」がどんな特徴を持ち、どのような会話体験を提供しているかを紹介しました。
- キャラの“人格”を再現する超高精度プロンプト
 - 人間に近い3層記憶構造(瞬間記憶・連続記憶・要約記憶)
 - 会話を重ねるごとにキャラが“成長”する設計
 - 3人同時会話のトリオモード
 - UIUXへの徹底的なこだわり
 
今回はその裏側──つまり**「どうやってこのシステムを実現しているのか」**をテーマにお話ししていきます。
具体的には、
- 使用している技術スタックと構成の全容
 - 実際の処理フロー(チャット送信→返答→記憶処理の流れ)
 - 2万文字を制御するプロンプト構造の仕組み
 
を、技術者目線で、でもわかりやすく解説していきます。
「このアプリの裏側、どうなってるの?」と思った方は、ぜひ最後まで読んでみてください。
プロジェクト全容
この章では、「VFriend」というチャットボットアプリが
どのような技術で構築され、どういう設計思想で組み上がっているかを紹介します。
このアプリは、会話ごとに2万文字のプロンプトを動的生成し、
さらにリアルタイムで返答を生成、記憶を更新するという非常に複雑な処理を必要とします。
そのため、使用する技術やディレクトリ構成、環境設計も
一貫した流れと役割分担を意識して設計しています。
使用技術(スタック)
以下がこのプロジェクトで採用している主な技術スタックです:
- バックエンド:Laravel(Breezeをベース、Guard分離構成)
 - フロントエンド:React(Inertia.js)
 - チャットUI:chat-ui-kit-react
 - データベース:MySQL
 - AIインターフェース:OpenAI API(GPT-4.1, GPT-4.1-mini)
 - デプロイ環境:Amazon Lightsail
 
ディレクトリ構成(主要ファイルのみ抜粋)
 app/
"app
├── Http
│   ├── Controllers
│   │   ├── Admin
│   │   │   ├── Auth
│   │   │   ├── AdminProfileController.php
│   │   │   ├── PromptController.php
│   │   │   └── UserController.php
│   │   ├── Auth
│   │   ├── ChatController.php
│   │   ├── Controller.php
│   │   └── ProfileController.php
│   ├── Middleware
│   └── Requests
├── Jobs
│   └── UpdateSummaryMemory.php
├── Models
│   ├── Admin.php
│   ├── Prompt.php
│   └── User.php
├── Notifications
├── Prompts
│   ├── ChatPromptBuilder.php
│   └── SummaryPromptBuilder.php
├── Providers
└── Services
    └── ChatService.php"
 resources/
"resources
├── css
│   └── app.css
├── js
│   ├── Components
│   │   ├── action
│   │   ├── content
│   │   ├── form
│   │   ├── function
│   │   ├── layout
│   │   └── page
│   ├── Layouts
│   │   ├── AdminAuthenticatedLayout.tsx
│   │   ├── AdminGuestLayout.tsx
│   │   ├── AuthenticatedLayout.tsx
│   │   └── GuestLayout.tsx
│   ├── lib
│   ├── Pages
│   │   ├── Admin
│   │   │   ├── Auth
│   │   │   ├── Profile
│   │   │   ├── Dashboard.tsx
│   │   │   ├── Prompt.tsx
│   │   │   └── Users.tsx
│   │   ├── Auth
│   │   ├── Profile
│   │   ├── Chat.tsx
│   │   └── Dashboard.tsx
│   ├── styles
│   ├── types
│   ├── app.tsx
│   ├── bootstrap.ts
│   └── ziggy.js
└── views
    └── app.blade.php"
環境構築
開発環境はDockerを使ってセットアップしています。
Laravel Sail + MySQL でローカル環境を構築し、Laravel Breezeを用いたログイン認証を導入。
また、ユーザーと管理者でログイン導線を分けるため、Guard分離を採用。
Breezeの各種コントローラーとビューをコピー・分離し、管理用と一般用の導線を構築しました。
本番環境は Amazon Lightsail を採用しています。
デザイン&コンポーネント設計
Figmaで制作していたオリジナルのWebデザインシステム(デジタル庁のUI設計に着想)をベースに、コーポレート系デザインを整えたものをそのまま流用。
もともとNext.js向けに作っていたコンポーネント資産をReact(Inertia.js)へ移植し、
Laravel BreezeのデフォルトUIを全て差し替えています。
 チャット画面の実装(resources/js/Pages/Chat.tsx)
この画面は、ソロモード/トリオモードの共通チャットUIです。
以下の特徴を持っています:
- 
chat-ui-kitをベースに実装 - キャラクターアイコン用に 
<Avatar>を追加 - 入力中表示のために 
<TypingIndicator>を設置 - 自動スクロール処理を追加
 - Markdownに対応するため 
react-markdownを導入 - HTML混入防止のため 
dompurify、rehype-rawを追加し、コピペ処理も最適化 
 チャット処理(app/Http/Controllers/ChatController.php)
チャットの中心的なコントローラー。以下の責務を持ちます:
- DBとのセッション管理(読み込み・保存)
 - モード切り替え(ソロ / トリオ)
 - キャラごとのフロー分岐
 - 
ChatServiceの呼び出し - 要約記憶更新ジョブ(
UpdateSummaryMemory.php)の呼び出し 
 APIリクエスト処理(app/Services/ChatService.php)
このサービスクラスは、OpenAI APIとの通信を担当しています。
- モデルやパラメータ(temperature、max_completion_tokens)の設定
 - 
ChatPromptBuilderとSummaryPromptBuilderの呼び出し - OpenAIへのリクエスト → レスポンスの取得
 
プロンプト生成処理
- 
ChatPromptBuilder.php:キャラクターとの会話用プロンプトを構築 - 
SummaryPromptBuilder.php:要約記憶を生成するプロンプトを構築 
プロンプトの詳細構造については、「プロンプト構造」で詳しく解説します。
 要約記憶ジョブ(UpdateSummaryMemory.php)
- 会話5ターンごとに起動する非同期ジョブ
 - 要点を抽出し、JSON形式の要約記憶をDBに保存
 - 各キャラの人格にフィードバックされることで、成長する仕組みを構築
 
このように、フロントからバックエンド、API、プロンプト設計、記憶処理までが
一貫した構成と責務分担で動いているのがこのアプリの土台です。
次は、いよいよこの構成を動かす「処理フロー」に入っていきます。
処理フロー
ここでは、「VFriend」がユーザーとの会話中に実際にどんな処理をしているかを追っていきます。
今回紹介するのは主に**トリオモード(3人会話)**の処理フローです。
ソロモードも構造としては近いですが、キャラの数とプロンプト生成の繰り返しが異なります。
ページ表示時
チャット画面を開いた時の処理は以下の通りです:
- システムプロンプト、ユーザーデータ、要約記憶、連続記憶をDBから読み込む
 - それらをセッションに保存
 - 連続記憶の内容をチャット画面に復元表示する(過去のやり取りを再表示)
 
会話時の処理フロー
ユーザーがメッセージを送ったあとの処理は、以下のように進行します:
- ユーザーがメッセージを送信
 - チャット画面に「〇〇入力中...」を表示し、入力フォームを非活性化
 - セッションから以下を取得:
- システムプロンプト
 - ユーザーデータ
 - 要約記憶
 - 連続記憶
 - 瞬間記憶(直前の1ターン)
 
 - キャラ1用のプロンプトを生成 → OpenAI APIへ送信
 - レスポンスを受信 → チャットに表示 → 自動スクロール
 - キャラ2用のプロンプトを生成(キャラ1の返答を踏まえて) → API送信
 - レスポンスを受信 → 表示 → スクロール
 - キャラ3用のプロンプトを生成(キャラ2までの返答を踏まえて) → API送信
 - レスポンスを受信 → 表示 → スクロール
 - 
この3人分の会話ログを1ターンとして連続記憶に追加
- ※10件を超える場合は古いものから削除
 
 - 連続記憶をDBに保存
 - 瞬間記憶をクリア(次回の会話に備えて)
 - 5ターンごとに「要約記憶更新ジョブ」を非同期で実行
 
要約記憶の更新処理(非同期)
会話が5ターン進行するごとに、以下の処理が非同期で走ります:
- 
UpdateSummaryMemoryジョブが起動 - DB・セッションから必要な記憶情報を収集
 - 要約プロンプトを生成 → OpenAI APIへ送信
 - レスポンスで得た内容を要約記憶としてDBに保存
 - 要約記憶のセッションデータも即時更新
 
このように、VFriendのチャット機能はただ返すだけではなく、
- 過去の会話を踏まえたプロンプト生成
 - キャラごとの個別プロンプト
 - 記憶の成長と書き換え
 - 同期と非同期の併用でテンポを保つ
 
といった複雑な処理を1ターンの中で毎回行っているのです。
次章では、この処理の肝である「プロンプト構造」について詳しく解説します。
プロンプト構造
VFriendの中核を担うのが、プロンプト設計の構造化と階層化です。
本プロジェクトでは、主に以下の2種類のプロンプトを用途別に使い分けています:
- キャラクター会話用プロンプト
 - 要約記憶生成用プロンプト
 
どちらも、会話を続けるごとに1リクエストが約2万文字に達する大規模構造です。
そのため、プロンプトはMarkdownとJSONによる階層設計を行い、
ChatGPTが「どの情報を、どう活かせばよいか」を自然に理解できるように設計されています。
会話プロンプトの設計思想
キャラクターに“人格”を持たせるには、詳細かつ一貫した設計が必要です。
このプロンプトには以下が含まれています:
- 「あなた(You)」:キャラクター側の自己定義(3000文字)
 - 「ユーザー(User)」:相手となるユーザーの詳細情報(2000文字)
 - ルール群:演技/記憶/会話ルール(合計約400〜500文字)
 - 記憶構造:瞬間・連続・要約記憶(最大1.5万文字)
 
キャラクター会話プロンプト構造(例)
# あなた(You)【約3000文字】
## コモン(Common)
### EQ(Emotional Quotient / 共感軸)
### EX(Experience Quotient / 行動軸)
## パーソナル(Personal)
### キャラクター設計(Character Profile)
### 会話スタイル(Conversation Style)
### 性格と感情軸(Personality & Emotional Navigation)
### 会話テーマ&表現(Content & Expression)
# ユーザー(User)【約2000文字】
## ニックネーム(Nickname)
## プロフィール(Profile)
# 演技ルール【約100文字】
# 記憶ルール【約200文字】
# 会話ルール【約100文字】
# 指示【約200文字】
記憶データ構造(JSON)
{
  "記憶": {
    "要約記憶": {
      "キャラ1": "",
      "キャラ2": "",
      "キャラ3": ""
    },
    "連続記憶": [
      {
        "ターン": [
          { "message": "", "sender": "ユーザー" },
          { "message": "", "sender": "キャラ1" }
        ]
      }
    ],
    "瞬間記憶": [
      { "message": "", "sender": "ユーザー" },
      { "message": "", "sender": "キャラ1" }
    ]
  }
}
要約記憶プロンプト構造(例)
要約プロンプトでは、キャラが記憶する内容を要約AIが構築します。
そのため、「要約者」という人格に向けた構造を用意しています。
# あなた(You)【約2000文字】
## コモン(Common)
### EQ(Emotional Quotient / 共感軸)
### EX(Experience Quotient / 行動軸)
## パーソナル(Personal)
### キャラクター設計(Character Profile)
### 会話スタイル(Conversation Style)
### 性格と感情軸(Personality & Emotional Navigation)
### 会話テーマ&表現(Content & Expression)
# ユーザー(User)【約2000文字】
## ニックネーム(Nickname)
## プロフィール(Profile)
# Friendについて【約200文字】
# 記憶について【約200文字】
# モードについて【約200文字】
# 指示【約700文字】
要約対象となる記憶(瞬間・連続)は下記のような形式で提供されます:
{
  "記憶": {
    "要約記憶": {
      "キャラ1": "",
      "キャラ2": "",
      "キャラ3": ""
    },
    "連続記憶": [
      {
        "ターン": [
          { "message": "", "sender": "ユーザー" },
          { "message": "", "sender": "キャラ1" }
        ]
      }
    ]
  }
}
モデルの使い分け
VFriendでは用途に応じてOpenAIのモデルを使い分けています。
| 用途 | 使用モデル | 理由 | 
|---|---|---|
| 会話生成 | GPT-4.1-mini | 高速かつ高頻度利用が可能(20万RPM) | 
| 要約生成 | GPT-4.1 | コストは高いが、要約には高精度が必要なため | 
このように、「VFriend」のプロンプト構造は役割ごとに設計が明確に分かれており、
1リクエスト内で「一貫性」「記憶の整合性」「自然な応答」をすべて担保できるよう最適化されています。
まとめ
ここまで、VFriendのアプリ設計からプロンプト構造までを詳しく見てきました。
- LaravelとReactによる密結合アーキテクチャ
 - 非同期処理と記憶構造の分離
 - Markdown+JSONで管理される2万文字プロンプト
 
次回予告
次回はいよいよ最終章、開発の舞台裏と制作者の想いに迫ります。
Discussion