diff --git "a/source/_posts/2026/20260624a_macOS\343\201\247\343\203\227\343\203\252\343\202\244\343\203\263\343\202\271\343\203\210\343\203\274\343\203\253\343\201\225\343\202\214\343\202\213LLM\343\202\265\343\203\274\343\203\223\343\202\271\343\202\222\343\202\263\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\343\202\250\343\203\274\343\202\270\343\202\247\343\203\263\343\203\210\343\201\213\343\202\211\344\275\277\343\201\206\345\256\237\351\250\223.md" "b/source/_posts/2026/20260624a_macOS\343\201\247\343\203\227\343\203\252\343\202\244\343\203\263\343\202\271\343\203\210\343\203\274\343\203\253\343\201\225\343\202\214\343\202\213LLM\343\202\265\343\203\274\343\203\223\343\202\271\343\202\222\343\202\263\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\343\202\250\343\203\274\343\202\270\343\202\247\343\203\263\343\203\210\343\201\213\343\202\211\344\275\277\343\201\206\345\256\237\351\250\223.md" new file mode 100644 index 00000000000..b5f8a8e8f3b --- /dev/null +++ "b/source/_posts/2026/20260624a_macOS\343\201\247\343\203\227\343\203\252\343\202\244\343\203\263\343\202\271\343\203\210\343\203\274\343\203\253\343\201\225\343\202\214\343\202\213LLM\343\202\265\343\203\274\343\203\223\343\202\271\343\202\222\343\202\263\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\343\202\250\343\203\274\343\202\270\343\202\247\343\203\263\343\203\210\343\201\213\343\202\211\344\275\277\343\201\206\345\256\237\351\250\223.md" @@ -0,0 +1,139 @@ +--- +title: "macOSでプリインストールされるLLMサービスをコーディングエージェントから使う実験" +date: 2026/06/24 00:00:00 +postid: a +tag: + - Mac + - LLM + - Foundationmodel +category: + - Infrastructure +thumbnail: /images/2026/20260624a/thumbnail.png +author: 澁川喜規 +lede: "みなさん、WWDC26の情報はみられましたか?昨年発表されたDocker互換のコンテナランタイムがパワーアップし、WSLのようなLinuxコンソール体験を提供するcontainer machineとか楽しそうですよね。" +--- +みなさん、WWDC26の情報はみられましたか?WWDCを新製品発表会のように思われている方には肩透かしだったようですが、技術的に今回はかなり期待できる発表がいくつもありました。昨年発表されたDocker互換のコンテナランタイムがパワーアップし、WSLのようなLinuxコンソール体験を提供するcontainer machineとか楽しそうですよね。 + +それとも1つ。生成AIではとうとう、より気軽に使えるように拡張されました。WWDC25でも、Foundation Models Frameworkが提供されApple IntelligenceをSwiftを通じて利用できるようになりましたが、今回追加された`fm`コマンドが入り、OllamaとかLMStudioとかインストールしなくても組み込み機能で同等のことができるようになります。リーズニング、ツール呼び出し、構造化出力に対応しているため、高度なシステムに組み込むことができそうです。 + +たとえば、`$ fm chat`で起動すると以下のような画面が出てきます。 + + + +`$ fm serve`ではOpenAI互換のAPIサーバー + + + +が起動します。なお、モデルは2種類あります。 + +* `system`: デバイス上で動くモデル +* `ppc`: プライベートクラウドで動くモデル。利用上限がある。 + +[Apple製の第三世代モデル](https://machinelearning.apple.com/research/introducing-third-generation-of-apple-foundation-models)が使われています。`system`は、AFM 3 Core Advanceですね。20Bの性能を持つが、メモリ上は1B-4B程度の容量しか食わないとされています。`ppc`はAFM 3 Cloudのモデル群ですね。サーバー側でNVIDIAのボードで動いているとなっています。プライバシーとかセキュリティはばっちり、とのこと。 + +どう設定するかはわかりませんが、これら以外にClaudeやGeminiのモデルを追加したり、Hugging Faceに上がっているようなモデルを使ったりもできるようです。 + +とはいえ、少ないメモリで動くローカルモデルというのが一番気になります。ネット使わずに生成AIで開発は果たしてできるのか?実験してみました。 + +# 準備(macOS 27 Golden Gate, OpenCode) + +現時点ではmacOS 27 Golden Gateはリリースされていません。そのため、まずは`fm`コマンドを使えるようにするために、macOSのベータ機能を有効にします。そうするとmacOS 27 Golden Gateの開発者プレビューがインストールできるようになります。会社のパソコンでやる場合は情シスと相談してくださいね。個人マシンでやりました。 + +スクリーンショット_2026-06-13_21.11.16.png + +次にコーディングエージェントをインストールします。今回は[OpenCode](https://opencode.ai)を入れました。インストールの方法はいろいろあるので、公式サイトを参照してください。brewで入れるとXCodeも27 Betaを入れる必要があります。 + +ローカルを参照させるために、`~/.config/opencode/opencode.json`を作成し、モデル設定を行います。これで`/models`にAppleのモデルが出てくるようになります。 + +```json ~/.config/opencode/opencode.json +{ + "$schema": "https://opencode.ai/config.json", + "provider": { + "apple": { + "npm": "@ai-sdk/openai-compatible", + "name": "Apple Intelligence", + "options": { + "baseURL": "http://localhost:1976/v1", + "apiKey": "dummy" + }, + "models": { + "system": { + "name": "Apple Local" + }, + "pcc": { + "name": "Apple PCC" + } + } + } + } +} +``` + +これで実行すれば完璧!?と思ったらエラーが・・・ + +# リクエストを正規化する + +Foundation Modelはモデル呼び出しなどに対応すると書かれていたのですが、実行してみるとエラーレスポンスがログに出ました。小さいリクエストを作ってちょっとしたツール呼び出しを +送信するとエラーが返ってきました。 + +```json +{"error":{"type":"invalid_request_error","code":"400","message":"Invalid tool definition: The data couldn’t be read because it is missing."}} +``` + +どうも、リクエスト時のJSONの形式のチェックが厳格に行われるようで、試行錯誤の結果`required`属性が欠けているとエラーになるようです。そのまま実行するのはできなそうなので、JSONリクエストを変換するプロキシサーバーを途中に挟むことにします。まあ、まだベータなので、きっとリリース版までには不要になるでしょう。 + +スクリーンショット_2026-06-13_21.44.40.png + +OpenCodeが返す複雑なリクエストではエラーになりました。これには11個のツール定義が含まれます。1個ずつばらし、OpenAIの[OpenAPI定義](https://raw.githubusercontent.com/openai/openai-openapi/refs/heads/master/openapi.yaml)(紛らわしい)と見比べてリクエストが通るまで修正して仕様化して、という感じのプランをCodexに渡して検証させたところ、以下のような修正が必要ということがわかりました。 + +* オブジェクトスキーマに properties があるのに required がない場合、すべてのプロパティ名を required に自動追加する。 +* オブジェクトスキーマに required があるものの、一部のプロパティが含まれていない場合、すべてのプロパティ名を追加して required を補完する。 +* fm serve が受け付けないため、"type": ["string", "null"] のような nullable union は自動生成しない。 +* fm serve でクラッシュや拒否が確認されている数値バリデーション用キーワードを削除する: + * minimum + * maximum + * exclusiveMinimum + * exclusiveMaximum +* オブジェクトスキーマに properties があるのに additionalProperties が指定されていない場合、additionalProperties: false を自動追加する。 +* クライアントが明示的に指定した additionalProperties の値はそのまま保持する。 +* properties や items の下にあるネストされたオブジェクトスキーマについても同様の正規化処理を行う。 +* 配列スキーマで items.type == "object" の場合、上流のアイテムスキーマを type: "string" に書き換え、モデルに「JSONオブジェクトを文字列として出力すること」を説明する description を付与する。 +* ストリーミングではないレスポンスの場合、レスポンスをクライアントへ返す前に、 + * 自動生成された null のツール呼び出し引数を削除し、 + * JSON文字列に変換されていた「オブジェクト配列型」の引数を元のオブジェクト配列に復元する。 + +で、これを実装したのが[fmproxy](https://github.com/shibukawa/fmproxy)です。 + +こういうの、Appleに報告したいけどどこから報告すればいいんですかね? + +# 動作確認 + +OpenCodeの設定をそのままに実行するために、fmを別ポートで動かし、プロキシを本来のポートで動かします。 + +```sh +# ターミナル1 +$ fm serve --port 1977 + +# ターミナル2 +$ ./fmproxy --upstream http://localhost:1977 + +# ターミナル3 +$ opencode +``` + +で、実行した結果が以下のものです。バッチリ動きました。まあ動かすので満足したので複雑な実装をさせてみたり・・・はしていませんが。 + +スクリーンショット_2026-06-13_21.50.32.png + +ただ、いくつか落とし穴がありました。 + +* `fm serve`はUnixドメインソケットにも対応するので、当初はfmproxyからfmコマンドを起動し、親子ではUnixドメインソケットで通信させるところまで実装していました。ですが、そうするとなぜかpccモデルが使えなかったので、別プロセス起動とした +* systemモデル(完全ローカル)だとコンテキストウインドウが小さすぎるのかエラーとなってしまった。 + +# まとめ + +というころで、動くには動きましたが、完全ローカル(な方のモデル)で実装までいけるか?というのはできませんでした。まあ、コーディングはAIのやるタスクの中でも大量のパラメータを持つ複雑なフラグシップモデルで実施するようなものなので、動いたとしても性能はそこそこでしょう。 + +とはいえ、ツール呼び出し、構造化出力対応なので、他のシステムから気軽に呼び出せるのは強いです。特別なサブスクも不要でセキュリティは保証されています。ローカルの方なら外部通信も発生しません。OpenAI互換サーバーも(やや気難しいですが)あるので実装もしやすいです。ちょっとした分類タスクとかヒューリスティックな判定処理とか、ちょっとした処理の自動化には十分でしょう。なにより、これがOSインストールからいきなり使える、というのは良いですね。 + +AppleはAIでは周回遅れ、みたいに言われがちですが、個人的には結構ツボなアップデートでした。将来の発展も楽しみです。 diff --git a/source/images/2026/20260624a/thumbnail.png b/source/images/2026/20260624a/thumbnail.png new file mode 100644 index 00000000000..cd45f880473 Binary files /dev/null and b/source/images/2026/20260624a/thumbnail.png differ diff --git "a/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_20.51.48.png" "b/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_20.51.48.png" new file mode 100644 index 00000000000..91fa87bd965 Binary files /dev/null and "b/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_20.51.48.png" differ diff --git "a/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_20.54.21.png" "b/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_20.54.21.png" new file mode 100644 index 00000000000..96908670cd5 Binary files /dev/null and "b/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_20.54.21.png" differ diff --git "a/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_21.11.16.png" "b/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_21.11.16.png" new file mode 100644 index 00000000000..358c0fff6b4 Binary files /dev/null and "b/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_21.11.16.png" differ diff --git "a/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_21.44.40.png" "b/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_21.44.40.png" new file mode 100644 index 00000000000..d6d77aaff0d Binary files /dev/null and "b/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_21.44.40.png" differ diff --git "a/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_21.50.32.png" "b/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_21.50.32.png" new file mode 100644 index 00000000000..4a2584303cf Binary files /dev/null and "b/source/images/2026/20260624a/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210_2026-06-13_21.50.32.png" differ