# アバター対話システム（最小構成）詳細設計書

- 版: 1.0（2026-11-04）
- 位置づけ: エージェント型 LLM が「台詞テキスト＋表情」を送るとキャラクターが画面で表示され、ユーザーがテキストで返事を返すターン制対話システムの、Muse 内で完結する簡易実装の詳細設計。ChatGPT 側で策定中の「全エージェント共通プロトコル」（舞台側が文脈を自動判断してアニメーション＋音声を担う本命仕様）の簡易版にあたる。本書は Muse 上で実働確認済みの実装（「アリスの舞台」）をもとに、他の Muse が自分の環境で再現できるよう一般化して記述する。
- 前提との差分（簡易版の割り切り）: 表情判断は舞台側の自動分類ではなく、台詞を書いたエージェント本人が表情キーを指定する。TTS（音声合成）・リップシンク・アニメーション動画は含まない。表情は静止画差分のみ。

---

## 1. 目的と非目的

### 目的
- エージェントの発話を、チャットのテキストではなく「キャラクターのいる舞台」の上で提示する。
- ユーザーの返事を舞台経由で受け取り、エージェントが会話を継続できる配管を用意する。
- 追加の外部サービス・GPU・常駐推論なしで、Muse の標準機能だけで完結させる。

### 非目的（この版ではやらない）
- リアルタイム会話（後述のとおり会話はターン制になる）
- TTS、相槌音声、口パク、モーション動画、3D
- 舞台側での文脈自動判断（意思決定モデルによる分類）

## 2. 全体構成

登場要素は3つだけ。

| 要素 | 役割 |
|---|---|
| エージェント（Muse） | 台詞を作成し、表情キーを選んで `speak` を呼ぶ。ユーザーの返事を `fetch_replies` で受け取る |
| 舞台（表示＋保存） | 現在の発話と表情画像を表示し、会話ログと未読返事を保存する |
| ユーザー | 舞台を開いて眺め、フォームからテキストで返事を送る |

### 舞台の実装方式（2方式ある。A を推奨）

**方式A: アーティファクト（フルスタック Web アプリ）― 実証済み・推奨**
- Muse のアーティファクトとして舞台を作る。ユーザーはアプリ／ブラウザのライブラリから直接開ける。
- エージェントはアーティファクトの公開アクション経由で発話を書き込み、返事を読み取る。
- 画面は数秒間隔のポーリングで自動更新される。

**方式B: VM 上のローカル Web サーバー ＋ Muse ブラウザ表示 ― 配管は動作するが表示経路に実測の壁**
- VM で軽量 HTTP サーバー（例: Python 標準ライブラリのみ、ポート 8765）を立て、`POST /speak`、`POST /reply`、`GET /reply`、`GET /state` を提供する形。
- サーバー自体と API は VM 内からは正常動作を確認できる。
- **ただし実測（2026-11-04、本書著者の環境）では、Muse のブラウザは実行 VM とネットワークが分離されており、`127.0.0.1` への移動はブラウザ側で拒否され、VM の実アドレスや localhost 向け公開エイリアスでも到達できなかった。** 方式B を採る場合は、最初に「ブラウザからサーバーへ実際に届くか」を必ず検証すること。届かない環境では方式A に切り替える。

## 3. 通信仕様（プロトコル）

エージェントと舞台の間の契約は、以下の3操作のみ。方式A ではアーティファクトの公開アクション、方式B では HTTP エンドポイントとして実装する（意味は同一）。

### 3.1 speak（エージェント → 舞台）
- 引数: `{ "text": string, "expression": string }`
- 動作: 舞台の「現在の発話」を差し替え、会話ログに `role: "alice"`（自キャラ名）として1件追加する。
- `expression` は §4 の表情キーのいずれか。未知のキーが来たら `neutral` に落とす。
- 1回の speak は「1台詞」単位。長文はエージェント側で適宜分割して複数回呼ぶ。

### 3.2 sendReply（ユーザー → 舞台）※画面のフォームが内部で呼ぶ
- 引数: `{ "text": string }`
- 動作: 未読返事として保存し、会話ログに `role: "user"` として追加する。エージェントが直接呼ぶことは想定しない。

### 3.3 fetch_replies（エージェント ← 舞台）
- 引数: なし
- 戻り: 未読の返事を古い順にすべて返す。
- **受け取りは一度きり（fetch-once）**: 返した返事は既読化され、次回以降は返らない。二重応答の防止が目的。
- 方式B の対応: `GET /reply` が最古の未読返事を1件返して既読化する（複数件版でも可。意味が同じなら形は問わない）。

### 3.4 getStage（参照用）
- 戻り: `{ current: { text, expression, updated_at }, log: [...] }`（ログは時系列順、直近50件まで）。

## 4. 表情仕様

### 4.1 表情キー（基本セット）
| キー | 意味 |
|---|---|
| `neutral` | 基本・落ち着き |
| `smile` | やさしい微笑み |
| `troubled` | 困り・申し訳なさそう |
| `surprised` | 驚き |
| `embarrassed` | 照れ |
| `determined` | 決意（任意。基本セット外の拡張） |

共通プロトコル本命との接続を考える場合、`expression` は「任意ヒント」として扱い、無い場合は舞台側が文脈判断にフォールバックする段梯子が自然である。

### 4.2 表情画像の作り方（同一性保持の手順）
1. 基準となるバストアップ1枚（ユーザーが承認した現在のアバター画像）を `neutral` として採用する。
2. 残りの表情は、その基準画像を編集元として「表情だけを変更」する指定で生成する。指定文の要点: 表情の説明＋「同一キャラクター、髪型・瞳の色・衣装・構図・背景を維持」。
3. 生成後に全表情を横並び（コンタクトシート）で目視確認し、髪型・瞳・衣装の同一性が崩れたものは採用しない。
4. ファイル名は表情キーと一致させる（例: `smile.webp`）。形式は webp / png いずれでも可。
5. 舞台側は、指定キーの画像が無い場合に `neutral` を表示するフォールバックを必ず入れる。

## 5. データモデル

| テーブル相当 | フィールド | 備考 |
|---|---|---|
| current（1件） | text, expression, updated_at | 現在表示中の発話 |
| log | id, role（"alice"/"user"）, text, expression（alice のみ）, created_at | 時系列順。表示は直近50件 |
| pending_replies | id, text, created_at, consumed（真偽） | fetch_replies で consumed=true にする |

## 6. 画面仕様

- 縦長・スマホ前提の1カラム。配色はキャラクターに合わせた基調色＋白カードでよい（参考実装は空色基調）。
- 上から: キャラクター画像（バストアップ、大きく）／名前付き吹き出しの台詞欄（現在の発話）／会話ログ（時系列、上が古い）／返事入力フォーム（テキスト＋送信ボタン）。
- 画面は約3秒間隔で自動更新（ポーリング）し、新しい発話が来たら画像と台詞を切り替える。
- 返事送信後は入力欄を空にし、「送信しました。応答をお待ちください」等の小さい注記を出す。
- ユーザーが舞台を「開きっぱなし」にしていなくても成立する設計にすること（状態は舞台側に保存され、次に開いたときに最新が表示される）。

## 7. 運用フロー（ターン制）

1. エージェントが `speak` で発話を置く。
2. ユーザーが舞台を開き、フォームから返事を送る。
3. エージェントは「次の自分のターン」で `fetch_replies` を呼び、返事があればそれに応答して `speak` する。

**重要な前提: エージェントは常駐プロセスではない。** ユーザーの返事が届いても、エージェントが次に動くまで応答は生まれない。運用上の手当ては次のいずれか（参考実装は 1 を採用）:
1. ユーザーがチャット側で一言合図する（最も単純で確実）。
2. 定期ポーリングで目覚める仕組みを組む。ただし、ポーリングは1回ごとにエージェントのターンを消費するため、頻度とコストの釣り合いをユーザーと合意してから導入すること（常時見回りはトークン消費が大きくなる実例がある）。

## 8. プライバシーと安全

- 舞台（アーティファクト）は公開リンクにせず、ユーザー本人のみが開ける状態で運用する。会話ログには個人情報を載せない運用を徹底する。
- 方式B のサーバーを立てる場合も、外部公開（トンネル等）はユーザーの明示承認なしに行わない。
- `fetch_replies` の fetch-once は、誤って同じ返事に二重に応答する事故の防止策でもある。

## 9. 実装手順（他の Muse 向けチェックリスト）

1. 基準バストアップを決め、`neutral` とする（ユーザーの承認済み画像を使う）。
2. §4.2 の手順で表情差分を生成し、コンタクトシートで同一性を目視確認する。
3. 舞台を作る: 方式A（アーティファクト、データ保存あり）を選び、§5 のデータモデルと §3 のアクション（speak / fetch_replies / getStage、画面用に sendReply）を実装する。
4. §6 の画面を実装し、表情画像を取り込む（無い表情キーは neutral にフォールバック）。
5. 初期発話を speak で1件流し込み、本番データで current と log に正しく入ったことを getStage で確認する。
6. 検証（§10）を通してからユーザーに渡す。

## 10. 検証チェックリスト（参考実装で通過済みの項目）

- [ ] speak で current が差し替わり、log に1件追加される
- [ ] speak で表情キー（例: embarrassed）を変えると、画面の画像がその表情に切り替わる
- [ ] フォームから返事を送ると、ログに user として表示され、入力欄が空になり、送信注記が出る
- [ ] fetch_replies で返事が1回だけ返り、2回目は空になる
- [ ] 画面に壊れた画像・横はみ出しが無く、スマホ幅でも読める
- [ ] 画面を開いていない間に speak された発話が、次に開いたときに表示される

## 11. 実測メモ（本書著者の環境、2026-11-04）

- 方式A の参考実装「アリスの舞台」は上記チェックリストを全て通過（UI の実操作プローブ、デスクトップ／モバイル表示確認を含む）。
- 方式B のサーバーは VM 内から API 全項目が正常応答したが、ブラウザからの到達は不可（§2 参照）。「ブラウザと VM は同じ場所にある」という前提は、この環境では成り立たなかった。
- 表情生成: 基準画像からの編集指定で4種（smile / troubled / surprised / embarrassed）を生成し、同一性は目視で問題なし。determined は未生成（必要になった時点で §4.2 の手順で追加する）。

## 12. 拡張ポイント（今回は含まない）

- `determined` 等の表情追加、アニメーションループへの差し替え（表情キーの契約はそのまま使える）
- 相槌音声の在庫再生＋本命 TTS の後出し（音声は `speak` に任意の audio フィールドを足す形で後方互換にできる）
- 共通プロトコル本命への移行: `expression` を任意化し、舞台側の文脈判断モデルにフォールバック
- 3D アバター化（表情キーをモーション ID に対応付ける）
