なぜこれが標準リファレンスなのか
本番にある多くのプラグインは小さいものです。送信ドライバーは 1 クラスと接続 Blade。決済ゲートウェイはサービスとリダイレクトコントローラ。シンプルなサイドバー追加は Hook::add 1 回呼び出しのみ。いずれもプラグイン SDK 全体を行使するものではありません — 小さなプラグインは、プラグインをどこまで責任を持って成長させられるかを理解しようとする作者にとって、適切な読書素材ではないのです。
acelle/ai はスペクトルの反対端に位置します。エージェントチャットボックス、汎用テキスト書き換えコンポーネント、KB に根ざしたコーチペルソナ、管理者向け可観測性ダッシュボードからなる、self-contained な AI サブシステムです。AcelleMail インストール上で本プラグインを有効化すると、コアコードに触れることなく AI 表面全体が追加され、無効化するとクリーンに消えます。本プラグインは本ドキュメントで扱うすべての概念を本番で活用しています。これを読むのが、機能が非自明な場合にパターンがどう組み合わさるかを見る最短経路です。
ファイルツリーの俯瞰
プラグイン自身の README より:
| フォルダ | 責務 |
src/AIHandler/ | AI ランタイムエンジン — エンジン、エージェントループ、ツール、設定リゾルバ、可観測性 writer/reader、KB 検索、URL サニタイザ。 |
src/Models/ | 監査基盤用の 8 つの Eloquent モデル (AIConversation、AIMessage、AIRequest、AIToolCall、AIFeedback、AIRawBlob、AIDailyRollup、AIToolUndoRecord)。 |
src/Controllers/ | 管理コントローラ (/rui/admin/ai-*) + 公開 API コントローラ (/api/v1/ai/*) + プラグインランディング PluginDashboardController。 |
src/Services/ | PluginStatusReport、AISettingsService、AutomationService、AIObservabilityPolicy など。 |
src/ServiceProvider.php | 唯一のエントリポイント — PSR-4、ルート、ビュー、lang ファイル、フック、Middleware エイリアス、ライフサイクルリスナーを登録。 |
database/migrations/ | 14 個の監査基盤マイグレーション。有効化時に実行され、削除時にロールバックされます。 |
resources/views/ | 60 個超の管理 Blade テンプレート + 3 つの汎用匿名コンポーネント (<x-mc-ai-chatbox>、<x-mc-ai-rewrite>、<x-mc-ai-subject-ab-generator>) + チャットボックス / sparkle JS パーシャル。 |
resources/assets/ | CSS (~14 ファイル) + JS (~21 ファイル)、プラグインインストール時に vendor:publish --tag=plugin --force 経由で public/plugins/acelle/ai/ に公開されます。 |
resources/lang/ | 18 ロケール × 9 言語ファイル = AI モジュールの全表面を翻訳。 |
tests/ | 100 個超の Pest テスト (Feature + Unit) + Acelle\Ai\Tests\PluginTestCase ベースクラス。 |
routes.php | プラグインのルート (管理 + 公開 API + /plugins/acelle/ai/dashboard のプラグインランディングダッシュボード)。 |
composer.json | プラグインメタデータ。extra.setting-route が PluginDashboardController@index を指すため、管理 Plugins ページの「Settings」ボタンがプラグイン独自のダッシュボードへディープリンクします。 |
これらのフォルダはどれもオーダーメイドではありません。すべて本ドキュメントの章にそのままマッピングできます。プラグインを読むことは、同じパターンが大規模に出荷されていることを認識する作業です。
監査基盤の 8 つの Eloquent モデル
AI モジュールのデータ層は 監査可能性 を中心に設計されています。すべての会話、すべてのモデル呼び出し、すべてのツール呼び出し、すべてのユーザーフィードバック、そしてプロバイダの生レスポンスのすべてが、リプレイと可観測性のために捕捉されます。8 つのモデルがこの基盤を担います。
| モデル | テーブル | 表す内容 |
AIConversation | ai_conversations | マルチターンのエージェント / サポートセッションごとに 1 行。顧客 + ユーザーの FK、タスクキー、画面ルート、トークン / コスト合計のロールアップを保持。 |
AIMessage | ai_messages | ユーザー / エージェントのターンごとに 1 行。role、content JSON、ツール呼び出しへの FK、レイテンシ、使用モデル。 |
AIRequest | ai_requests | 上流 API 呼び出しごとに 1 行。エンジン、プロンプトハッシュ、レイテンシ、コスト、エラー状態。AIMessage と実際の HTTP トラフィックを橋渡しします。 |
AIToolCall | ai_tool_calls | エージェントターンから生成される Function-call 呼び出し。ツール名、入出力 JSON、ソースフラグ。 |
AIFeedback | ai_feedback | メッセージごと・会話ごとの Good/Bad と自由テキストフィードバック。 |
AIRawBlob | ai_raw_blobs | プロバイダの生レスポンスを保持し、リプレイ / 監査に利用。ロールアップテーブルを小さく保つため別テーブルに分離。 |
AIDailyRollup | ai_daily_rollup | 管理者可観測性ダッシュボード用の日次集計 — トークン合計、コスト、エラー率。事前集計済みなのでダッシュボードの読み出しが軽量です。 |
AIToolUndoRecord | ai_tool_undo_records | 「直前操作の undo」機能のため、可逆ツールアクションを追跡します。 |
このリストから 3 つのパターンが他のプラグインに直接転用できます。「プロバイダ生レスポンス」を「ロールアップ要約」とは別テーブルに分離することで、ロールアップテーブルをスキャン可能な小さなサイズに保てます。customers と users への nullable FK により、同じ行を認証済み・匿名トラフィックの両方で使えます。1 日 1 行のロールアップにより、管理ダッシュボードはアクティビティテーブルとの重い JOIN なしで安価に読めます。
14 個のマイグレーションを 1 行ずつ
storage/app/plugins/acelle/ai/database/migrations/ のマイグレーションファイル名がそれ自体で物語ります — 時系列で加算的、即座に可逆、破壊的なスキーマ変更は一切なし。
| ファイル名 | 内容 |
2026_04_28_000001_create_ai_conversations_table.php | マルチターンチャットセッション — uid、customer_id FK、status enum、トークン / コストのロールアップ |
2026_04_28_000002_create_ai_messages_table.php | ユーザー / エージェントの 1 ターン — role、content JSON、ツール呼び出し FK、レイテンシ、使用モデル |
2026_04_28_000003_create_ai_requests_table.php | 上流 API 呼び出しごとに 1 行 — エンジン、プロンプトハッシュ、レイテンシ、コスト、エラー |
2026_04_28_000004_create_ai_tool_calls_table.php | エージェントターンから生成される Function-call 呼び出し — 入出力 JSON |
2026_04_28_000005_create_ai_feedback_table.php | メッセージごと・会話ごとの Good/Bad と自由テキストフィードバック |
2026_04_28_000006_create_ai_raw_blobs_table.php | プロバイダの生レスポンスを保持し、リプレイ / 監査に利用 |
2026_04_28_000007_create_ai_daily_rollup_table.php | 管理ダッシュボード用の日次集計 — トークン合計、コスト、エラー率 |
2026_04_29_000001_add_client_message_id_to_ai_messages.php | クロスタブ重複排除カラム — 加算的、デフォルトなし |
2026_04_30_000002_add_source_to_ai_tool_calls.php | ツール呼び出しがエージェント経路かサポート経路かを追跡 |
2026_05_02_180000_widen_ai_conversations_client_session_uid.php | ULID / UUID 幅修正 — カラム変更マイグレーション、完全に可逆 |
2026_05_02_200000_create_ai_tool_undo_records_table.php | 「直前操作の undo」機能のため、可逆ツールアクションを追跡 |
2026_05_03_000001_add_url_sanitization_to_ai_requests.php | サニタイズ済み URL テレメトリ用に JSON カラムを追加 |
2026_05_04_000001_create_ai_settings_table.php | プラグインレベルの管理設定 — 各行をインデックス可能にするため plugins.data から分離 |
マイグレーションを上から順に読めば、AI モジュール全体のスキーマ進化が把握できます。加算的なマイグレーションはすべて機能リリースです — 加算的な形状こそが、機能蓄積から 1 年経って管理者がアンインストールしてもプラグインの delete_plugin_* ロールバックが必ず動作する理由です。
プラグインが使うすべてのフック
本プラグインの ServiceProvider は FILTER を除くすべてのフックパターンを行使しています。storage/app/plugins/acelle/ai/src/ServiceProvider.php に対する Hook::* の grep:
REGISTRY (Hook::add) — 6 件のコントリビューション
- 9 個の
add_translation_file エントリを register() の 175 〜 197 行目に登録 — 翻訳表面ごとに 1 つ (rewrite、chatbox、prompts、wait、subject AB、settings、admin usage、audit、permissions)。各表面は管理者向け Languages UI から独立して編集可能なファイルです。
layout.head.assets を boot() の 688 行目で登録 — マスター app / admin レイアウトを継承するすべてのページにチャットボックス CSS + JS を寄与します。
layout.body.before_close を boot() の 699 行目で登録 — すべてのページの </body> 直前にチャットボックスのバブル HTML と sparkle ポップオーバーを寄与します。
admin.sidebar.groups を boot() の 718 行目で登録 — 3 〜 4 個の子リンクを持つ AI グループを管理サイドバーに追加します。
6 件いずれも aiPluginAvailable() でゲートしています — 最終的に Plugin::getByName('acelle/ai')->isActive() に解決されるヘルパーです。プラグインがゲートオフのとき null を返すのが、サービスプロバイダーをアンロードせずに綺麗に消える定石です。
EVENT (Hook::on) — 2 つのライフサイクルリスナー
activate_plugin_acelle/ai を 472 行目で登録 — プラグインの migrations フォルダに対して artisan migrate を実行します。
delete_plugin_acelle/ai を 546 行目で登録 — $keepData フラグを受け取り、未設定時はマイグレーションをロールバックし、設定時は監査テーブルを保持します。
BEHAVIOR (Hook::set) — 1 件のアイコン URL 上書き
icon_url_acelle/ai を 522 行目で登録 — プラグイン毎の BEHAVIOR フックを上書きすることで、管理 Plugins ページがホストのデフォルト plugin.svg ではなく AI モジュール独自のアイコンを描画するようにします。
これらが本プラグインのフック表面のほぼすべてです。ServiceProvider.php を上から下まで読むことが、これらのパターンが本番でどう組み合わさるかを見る最短経路です。
チャットボックス UI 表面
本プラグインは resources/views/components/ に 3 つの汎用 Blade コンポーネントを寄与します — いずれもホストアプリケーションがその存在を知っている必要はありません。
<x-mc-ai-chatbox> — マルチターンエージェント会話を開くフローティングチャットボックスバブル。layout.body.before_close REGISTRY フック経由でマウントされるため、すべての app + admin ページに表示されます。
<x-mc-ai-rewrite> — ホスト内の任意の textarea の隣に配置できる汎用「テキストを書き換える」機能。プラグイン名前空間付き、中央登録不要。
<x-mc-ai-subject-ab-generator> — プロンプトから A/B 件名バリアントを生成します。キャンペーンエディタで使用されます。
これら 3 つのコンポーネントは「各ページを fork せずに、プラグインが複数のホストページに UI を寄与する」パターンを示します。プラグインのビュー名前空間下に匿名 Blade コンポーネントを提供し、レイアウト REGISTRY フックでグローバルマウントするか、ホストページに直接 include させてオプトインさせます。両パターンとも有効で、AI プラグインは両方を使い分けています。
9 ファイル × 18 ロケール
プラグインの register() は 9 つの翻訳ファイルを個別に登録します。Language::dump() の仕組みがそれぞれを 17 個の非英語ロケールのランタイムフォルダ (storage/app/data/plugins/acelle/ai/lang/ 配下) にマテリアライズします。ディスク上の結果: 153 個のランタイム翻訳ファイル (9 ファイル × 17 非英語ロケール + 9 英語オリジナル = 162 から 9 英語マスターを除いて 153 個のダンプ複製)。それぞれをホストの Languages 管理 UI から独立して編集できます。
9 つの表面ファイル (ServiceProvider::register() 内の $aiLangFiles ループで登録):
ai_rewrite — 汎用テキスト書き換えコンポーネント
ai_chatbox — チャットボックス UI
ai_chatbox_prompts — チャットボックスに表示される事前定義プロンプト
ai_chatbox_wait — smart-wait UI (「検索中… ツール実行中… 返信を作成中」)
ai_subject_ab — 件名 A/B ジェネレーター
ai_settings — 管理設定ページのラベル
admin_ai_usage — 管理者向け使用量 / コストダッシュボード
admin_ai_audit — 管理者向け監査 / リプレイ UI
admin_ai_permissions — 管理者向け機能別パーミッショントグル
この分割は「翻訳者が 1 度に編集できる程度に翻訳ファイルを小さく保つにはどうすればよいか」への実践的な答えです。論理的な表面ごとに 1 つのマスターを用意し、個別に登録し、ロケールごとにマテリアライズし、管理 UI から独立して編集できるようにします。
プラグイン所有の config ファイル
プラグインは config/ 配下に 2 つの config ファイルを所有しています — ServiceProvider::boot() で $this->mergeConfigFrom() によって登録され、標準の config('ai.*') および config('ai-navigation-hints.*') ヘルパーで参照可能です。プラグイン所有の config は、インストール毎に変わらない静的メタデータ (エンジンカタログ、プロンプトテンプレート、ナビゲーションのデフォルト) を置く適切な場所です。管理者が編集可能な設定は、マイグレーションでシードされた ai_settings テーブルに格納します。
この分割 — プラグイン同梱の不変デフォルトには config を、管理者編集可能な可変設定には DB 行を — は他のプラグインにもクリーンに移植可能なパターンです。両方を plugins.data JSON カラムに置きたくなりますが、管理 UI のパフォーマンスを損ねます。インデックス付きの専用テーブルを用意するのが正解です。
テストインフラ
プラグインのテストディレクトリはホストの tests/ の形状に従います — Unit + Feature フォルダに加え、ルートに PluginTestCase ベースクラスを配置します。
storage/app/plugins/acelle/ai/tests/
├── PluginTestCase.php ← seeds the plugin row as active before every test
├── Feature/
│ ├── AIHandler/ ← engines, agent loop, tools, observability writer
│ └── PluginLifecycle/ ← lifecycle integration tests
├── Unit/ ← isolated unit tests (no Laravel boot for some)
├── Fixtures/ ← test fixtures + factories
├── Snapshots/ ← Pest snapshot artefacts
└── Support/ ← test-only helpers
ホストの phpunit.xml はプラグインの testsuite を <testsuite name="Plugin: acelle/ai"> として登録します。./vendor/bin/pest --testsuite="Plugin: acelle/ai" でホスト自身の Unit + Feature suite と並行してフルスイートを実行できます。
ルートの PluginTestCase は、Middleware を出荷するすべてのプラグインが採用すべき「リクエスト毎の gate-cache リセットトラップ」を示しています — 詳細は テスト § gate-cache トラップ を参照。これがないと、スイート 2 つ目以降のテストが 1 つ目のブート時の古いキャッシュ状態を観測します。
学び方
10 万トークン規模のプラグインを 1 行ずつ読むのは適切な学習ではありません。次の 4 ステップのレシピが有用です。
-
プラグインをホストインストールへクローンまたは symlink。 有効化し、管理サイドバーを開く — AI グループが表示されるはずです。管理 Plugins ページのエントリで「Settings」をクリック — プラグインランディングダッシュボードがロードされるはずです。これでプラグインの UI 表面がローカルホストに結線されていることが確認できます。
-
src/ServiceProvider.php を上から下まで読む。 40 分。プラグインアーキテクチャ + Hook システム + UI 注入 + 翻訳 でカバーしたすべての概念が、本番スケールでこの 1 ファイルに登場します。並行してディープダイブ各ページを参照してください。
-
1 つの機能をエンドツーエンドでトレースする。 チャットボックスバブルを選びます。エントリポイント (
ServiceProvider 内の layout.body.before_close フック) を探し、返されるパーシャル (ai::partials.body_assets) を追い、バブルを描画する匿名コンポーネントを探し、それをマウントする JS を探し、JS が呼ぶ API ルートを探し、コントローラを探し、AIHandler ランタイムエンジンまで下り、AIConversation + AIMessage + AIRequest 行が挿入されるのを観察します。エンドツーエンドで 2 〜 3 時間。これが済めば AI プラグインがどのパターンを使い、どれを使わないかが分かります。
-
1 つのパターンを自分のプラグインに適応させる。 マッピング可能な最小のもの — 通常は翻訳表面ごとの登録ループ、管理サイドバーグループ、日次ロールアップテーブルアプローチのいずれか — を選びます。AI 固有のコードを取り除き、構造的パターンだけを残します。これがプラグイン作者の初日の生産性向上分です。
次のステップ
本ページは 11 個の開発者ディープダイブの最後です。ここからは、インデックスハブが自然な復習場所です。ドキュメントインデックス ではクラスタ内の全ページが Foundation / Building / Quality / Reference に整理されています。開発者ランディング は検索から新規訪問者が到達するマーケティング寄りのエントリポイントです。
実際にプラグインを出荷する準備ができたときの実践的な次の 2 ステップ。テストディープダイブの activate → test → delete サイクル は、プラグインの delete_plugin_* フックリスナーが正しくクリーンアップすることを証明します。プラグインアーキテクチャ § 壊れた状態からの復旧 は本番ランタイム問題のためにブックマークすべきページです — 3 つの失敗モードと、それぞれの正確な修正経路を示しています。
AcelleMail 固有のパターンを超えて、より広い Laravel エコシステムが適用できます。プラグインコードはバニラの Laravel であり、ホストランタイムローダーだけが非標準です。Eloquent、Blade、Pest、キュー、スケジューラ、Middleware について知っていることは、プラグインフォルダ内でもそのまま機能します。