今日は、OpenAI・Anthropic・xAI(Grok)の3つのAIに同じ質問を投げて回答を並べる個人ツール「LLM Council」を手入れしました。画面に出るモデル名が実際と違っていたのをきっかけに、Web版のモデル更新、画面の配信方法の変更、PWAアイコン、そして Claude Code から使うための CLI の拡張まで進めました。
今日やったこと
- Web版のバックエンド(Cloudflare Workers)のモデルを、CLI と同じ gpt-6-luna / Claude Sonnet 5.5(effort low)/ grok-4.7 に揃えた
- 画面のモデル名を、HTML に直書きした文字列から「API のレスポンスが返す model」の表示に変えた
- 画面を GitHub Pages から Workers の静的アセット配信に移し、自分のドメインのサブドメインで公開した
- PWA のマニフェストとアイコンを追加した
- CLI に --no-context・標準入力・--models・--model を足し、Claude Code から別会社のモデルにセカンドオピニオンを聞くスキルを作った
- README を Web版・Worker・CLI の3つが分かる形に書き直した
- テストは Worker が38件、CLI のオフライン確認が11件、すべて通過
詰まったところと、どう解いたか
一つ目は画面の表示のずれです。Claude のパネルに「claude-haiku」と出ていましたが、コードを見ると実際に呼んでいたのは Opus 4.8 でした。表示名を HTML に直書きしていたため、モデルを変えても画面だけが古いまま残っていたのが原因です。さらに、直近のモデル切り替えのコミットは CLI 側にしか入っておらず、Web版は gpt-4o や grok-4.3 のまま取り残されていました。
そこで、各社の API のレスポンスに含まれる model をそのまま画面に返すようにしました。Anthropic はサーバー側のフォールバックで別のモデルに切り替わることがあるので、要求したモデル名ではなく「実際に答えたモデル名」を出すほうが正確です。
Sonnet 5.5 への切り替えでは、もう一つ注意点がありました。思考(thinking)が常に有効なので、レスポンスの content の先頭が thinking ブロックになることがあります。content[0].text を決め打ちで読むと失敗するため、text ブロックだけを集めて結合する形に直しました。

Web版のモデルの変更前後。OpenAI は gpt-4o から gpt-6-luna、Anthropic は claude-opus-4-8(画面の表示は claude-haiku)から claude-sonnet-5-5、Grok は grok-4.3 から grok-4.7 になり、表示名は API が返した model に変えました。
二つ目が今日いちばんの「ほう」でした。画面を Workers から配信するために、wrangler.toml で静的アセット([assets])を有効にしたところ、ローカルの wrangler dev が起動しなくなりました。
Incorrect type for map entry 'ANTHROPIC_MAX_TOKENS':
the provided value is not of type 'function or ExportedHandler'テストから使うために、エントリのモジュールで定数(数値や文字列)を export していたのが原因でした。静的アセットを有効にすると、エントリの named export がすべて「入口(ハンドラ)」として扱われ、関数でもハンドラでもない値は弾かれます。有効にする前は同じコードで問題なく動いていたので、気づきにくいところです。
処理と定数を別のモジュール(council.js)に移し、エントリ(index.js)は default export だけを持つ形にしました。テストは本体のモジュールから import します。同じ問題が戻らないよう、「エントリは default 以外を export しない」ことを確かめるテストも足しました。

静的アセットを有効にしたときの変更前後。エントリで定数を export していると起動に失敗するため、関数と定数を council.js に移し、エントリの index.js は default export だけにしました。
三つ目は CLI の小さな罠です。Python の argparse で共通のオプションを parents(親パーサー)にまとめたとき、位置引数まで親に入れると、子の位置引数より前に並んでしまいます。ask openai "質問" の順番が逆に解釈されるので、位置引数は各サブコマンドの最後に足し、親にはオプションだけを置きました。
気づき
表示と実体を別々に持つと、片方だけが更新されてずれます。今回は「モデルの切り替えが CLI にしか入っていない」「画面の表示名が直書き」という二重のずれが重なっていました。モデル名は1か所で定義し、表示は API の返り値から取るようにしたので、次にモデルを変えるときは1行で済みます。
もう一つは、github.io のような共有ドメインの扱いです。同じユーザーの GitHub Pages は1つのオリジンを共有するので、ブラウザに保存するデータは他のアプリとも同じ場所に置かれます。アプリごとにサブドメインを分けると、この心配がなくなります。
Claude Code からほかの AI に相談する仕組みは、最初は MCP サーバーにすることも考えました。ただ、Claude Code は Bash から CLI をそのまま呼べるので、まずはスキルに使い方を書くだけにしました。相談役の OpenAI は、軽量な gpt-6-luna ではなく上位の gpt-6.1-sol に差し替えて使います。本番の API で試すと、gpt-6.1-sol 単体で約5秒、gpt-6.1-sol と grok-4.7 の2社並列で約10秒で返ってきました。
次にやること
- 実際の設計判断や原因調査で、Claude Code からのセカンドオピニオンを使ってみる
- よく使うようなら MCP サーバーにするか判断する
- Web版で3社の回答がそろって返るか、普段の使い方で確かめる
