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
)の呼び出し
app/Services/ChatService.php
)
APIリクエスト処理(このサービスクラスは、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