開発の経緯
ある日YouTubeを眺めていたら、面白い動画がおすすめに出てきました。
マギシステムをつくりVtuberの彼氏公表をマギに問う月ノ美兎【にじさんじ/切り抜き】
Vtuberの月ノ美兎さんが、ChatGPTに「人格を3つ」作らせて、エヴァのMAGIみたいに多数決させていたんです。
これを見て「複数視点で意思決定するAIの使い方」としてかなり良いアイデアだと思い、node.jsの練習も兼ねて再現してみました。
利用した技術・サービス
- JavaScript(フロント:ネイティブ)
- Node.js / Express(バックエンド)
- OpenAI API(今回は手間だったので省いたが、人格ごとにgemini,openAI,deepseekのようにサービスを変えたらより中立的な判断が下せるかも?)
- Vercel(デプロイ)
Webアプリ開発の経験がほとんど無かったので、今回はReactなどのフレームワークは使わず、フロントはネイティブJSで組みました。
デモ
MAGI System
待機画面

機能の紹介
- 質問を入力すると、3つのAI(MELCHIOR / BALTHASAR / CASPER)に同時送信
- 各AIが「可決 or 否決 + 理由(JSON)」で返答
- 多数決で最終決定(2/3以上で承認)
仕組み(ざっくり)
- フロント:入力→
/ask-magiにPOST - バックエンド:3つのOpenAI呼び出しを
Promise.allで並列実行 - 最後に多数決して、結果をJSONで返す
環境構築
Dockerで開発環境作成
Dockerfile / docker-compose.yml / package.json を用意して、コンテナ上でNodeを動かしました。
docker-compose up -d --build #コンテナ作成
docker-compose exec magi-system npm install #パッケージインストール
#node_modules は空に見えるが、仮想環境にパッケージがインストされている。
備忘録
フロントとバックエンドの分離
-
require, express, openAIの設定はサーバー用ファイルに格納する。 - ユーザーが通信を見れてしまうのでhtml側ではフロント側スクリプトのみ参照
<script src="script.js"></script>
公開範囲の設定
以下をサーバー側に書いたら、publicフォルダにユーザーに見られて問題ないファイルを入れる(=html,css,フロント側ロジック)
app.use(express.static('public'));
開発中のサーバー起動
Live Serverではなく、ExpressがlistenしているURLで動作確認する。
vercelでのデプロイ
//環境変によってローカルかどうか判断させる
if (process.env.NODE_ENV !== 'production') {
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`MAGI System Online: http://localhost:${PORT}`);
});
}
module.exports = app;
.env はGitHubに上げないように .gitignore に追加し、Vercel側の環境変数に設定する。
node.js学習メモ
//モジュール読み込みと公開設定
const express = require('express'); //ライブラリを読み込む
const app = express(); //サーバー本体(app)を作成
const dotenv = require('dotenv'); //.envから環境変数読み込み
dotenv.config(); //.env を読んで process.env.XXXX に入れる
app.use(express.json()); //POSTの本文がJSONの場合に req.body として読めるようにする
app.use(express.static('public')); //public フォルダをWeb公開する
//async awaitで非同期処理(結果が返るまで次の処理を待機)
async function askMagiCore(config, userMessage, API_KEY) {
try {
const response = await API_KEY.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: `...` },
{ role: "user", content: userMessage }
],
response_format: { type: "json_object" }//JSONでのレスポンスを強制
});
//文字列レスポンスをオブジェクト変換
const answer = JSON.parse(response.choices[0].message.content);
return { ... };
}
}
//エンドポイント
//ブラウザがPOST /ask-magi に { "message": "..." } を送る
app.post('/ask-magi', async (req, res) => {
//サーバーは req.body.message を取り出す
const { message } = req.body;
//3つのAI問い合わせを 同時に走らせる
const results = await Promise.all([
askMagiCore(...MELCHIOR...),
askMagiCore(...BALTHASAR...),
askMagiCore(...CASPER...),
]);
//全部返ってきたら多数決して、JSONでブラウザへ返す
res.json({
finalDecision: calculateDecision(results),
results: results,
});
}),
振り返り
開発の流れを「環境構築 → バックエンド → 簡易フロント → UI/デザイン」の順で踏めたのは良かったです。
一方で、UIの細部調整に時間を使いすぎた感があり、MAGIっぽさを十分に出しきれませんでした。
次はReactやTailwindも触ってみると、UI構築の効率が上がるかもしれません。
また、OpenAI APIを3回呼ぶのでコストや失敗時の扱い(タイムアウト、リトライ、1つエラーでも多数決するか等)も改善ポイントとして残りました。
