Macに入っているAIをPythonから使う|Apple公式SDKの導入手順と日本語での実測

MacのAIは、Swiftを書かなくてもPythonから呼べる

手元に数百件のテキストがあって、それを要約したり分類したりしたいとき、いまはクラウドのAIへ送るのが普通です。ただ、件数が増えれば費用がかかりますし、社外へ出せない文書だと送ること自体ができません。

Appleが公開しているpython-apple-fm-sdkを使うと、macOSに組み込まれているAI(Apple Intelligenceのオンデバイスモデル)をPythonから直接呼べます。これまでこのモデルを使うにはSwiftでコードを書く必要がありましたが、その前提がなくなりました。端末内で処理する設計なので、APIキーの登録も従量課金もありません。

この記事では、MacBook Air(Apple M4 / 32GB、macOS 26.5.1)へ実際に導入し、日本語での精度、処理速度、つまずきやすい箇所を測った結果をまとめます。掲載しているコマンドとコードはそのまま実行できる形で載せているので、同じ手順を自分のMacでも再現できます。

実測でわかったこと

日本語の要約と分類は、実務で使える精度だった

以下はすべて MacBook Air (Apple M4 / 32GB) / macOS 26.5.1 / apple-fm-sdk 0.2.1 で2026年8月13日に測った値です。

かかる費用

0円

何件処理しても課金なし。APIキーの登録も不要です。

データの行き先

Macの中だけ

端末内で処理する設計のため、外部のAIサービスへ本文が渡りません。

処理の速さ

0.44秒

問い合わせ文1件を分類する時間の中央値。1,000件で約7分です。

日本語の精度

要約は実用水準

4種類の日本語文書で試して、事実の取り違えはありませんでした。

Macのモデルが得意なのは要約・抽出・分類、苦手なのは知識を答えること

導入する前に、用途を選り分けておく必要があります。このモデルはClaudeやChatGPTの代わりにはなりません。AppleはWWDC25の開発者向けセッションで、これを30億パラメータの小型モデルと説明したうえで、要約・抽出・分類などに最適化しており、世界知識や高度な推論のために設計したものではないと明言しています。

実際に試した範囲でも、この線引きはそのとおりでした。渡した文章に書かれていることを答える仕事は正確にこなし、モデルの知識を引き出す仕事では平然と作り話をします。

推奨

渡した文章の中で完結する仕事

  • 長い文章を短くまとめる
  • 決まった項目を抜き出す(日付、金額、担当者など)
  • あらかじめ決めた種類へ振り分ける
  • 表記のゆれをそろえる

不可

モデルの知識に頼る仕事

  • 事実を調べる、確かめる
  • 最新のできごとを聞く
  • 正確さが要る計算をさせる
  • 専門的な判断を任せる

導入に必要な4つの条件と、3つのコマンド

前提条件は4つあります。ひとつでも欠けると動きません。

導入前のチェック

macOS 26.0 以降

アップルメニューの「このMacについて」で確認できます

Apple Intelligence 対応のMac

Appleシリコン搭載機。設定でオンにしておきます

Xcode 26.0 以降

インストール時のビルドに使います。アプリ開発をしなくても必要です

Python 3.10 以降

既存の環境を汚さないよう仮想環境を作ります

条件がそろっていれば、導入は3コマンドで終わります。今回はuvで仮想環境を作りましたが、標準のvenvでも同じです。

uv venv --python 3.13 .venv
uv pip install --python .venv/bin/python apple-fm-sdk
./.venv/bin/python check.py

インストールにはソースからのビルドが走るため、10秒ほどかかりました。Xcodeが必要なのはこのビルドのためです。Pythonのパッケージ配布サイト(PyPI)で公開されている最新版は0.2.1(2026年6月29日公開)でした。

3つ目のコマンドで動かすcheck.pyが次のものです。is_available()は、モデルが使える状態かどうかと、使えない場合の理由を返します。LanguageModelSessionは、モデルとのやり取りをひとまとめにする入れ物です。以下ではこれをセッションと呼びます。

import asyncio
import apple_fm_sdk as fm

async def main():
    model = fm.SystemLanguageModel()
    is_available, reason = model.is_available()
    print(f"available={is_available} reason={reason}")
    if not is_available:
        return

    session = fm.LanguageModelSession()
    response = await session.respond("こんにちは。あなたは何ができますか?1文で答えてください。")
    print(response)

asyncio.run(main())

available=True reason=Noneと表示され、続けて「質問にお答えし、情報を提供することができます。」という返答が2.08秒で返りました。ここまで来れば準備完了です。

日本語の要約と抽出は、そのまま使える精度だった

公式ドキュメントに書かれていないのが、日本語でどこまで使えるかです。ここが実用性を左右するので、種類の違う4つの文章で試しました。

文章の要約

自治体のお知らせを模した7行の文章を、2文で要約するよう指示しました。まず渡したのが次の文章です。

モデルに渡した文章

市役所からのお知らせです。来月4月1日から、粗大ごみの申し込み方法が変わります。これまでは電話のみの受付でしたが、インターネットからの申し込みができるようになります。電話受付は平日の午前9時から午後5時までですが、インターネットは24時間受け付けます。手数料の支払いは、これまでどおりコンビニエンスストアで粗大ごみ処理券を購入してください。なお、申し込みからの収集までの日数は、これまでと同じく約2週間かかります。引っ越しシーズンの3月と4月は申し込みが集中し、3週間以上かかる場合があります。早めの申し込みにご協力ください。

返ってきた要約が次の2文です。

来月4月1日から、粗大ごみの申し込みはインターネットからも受け付けられます。電話受付は午前9時から午後5時までですが、インターネットは24時間対応します。

元の文章には、申し込み方法の変更、受付時間、手数料の払い方、収集までの日数、繁忙期の遅れ、という5種類の情報が入っています。モデルは前の2つを残し、残りを落としました。2文という制限のなかで何を優先するかの判断として、妥当な選び方だと思います。事実の書き換えもありません。この生成には3.11秒かかり、出力速度は毎秒24.8文字でした。

会議メモから決定事項を抜き出す、商品レビューから良い点と悪い点を分ける、問い合わせ文から問題の発生条件を拾う、という3つも試しましたが、いずれも原文にない内容を足すことはありませんでした。

分類結果をPythonのオブジェクトで受け取る

大量のデータを機械的に処理するときに使うのはこちらです。@fm.generableを付けたクラスで受け取りたい項目を宣言すると、その形でPythonのオブジェクトが返ります。JSONを文字列で受け取って自分でパースする処理も、失敗したときの例外処理も要りません。

@fm.generable
class Inquiry:
    category: str = fm.guide(
        "問い合わせの種類",
        anyOf=["返品・交換", "技術サポート", "配送", "請求・支払い", "解約", "商品情報"],
    )
    urgency: str = fm.guide("緊急度", anyOf=["高", "中", "低"])
    summary: str = fm.guide("15文字以内の要約")

session = fm.LanguageModelSession(instructions="日本語の問い合わせ文を分類してください。")
result = await session.respond("配送予定日を過ぎましたが荷物が届きません。", generating=Inquiry)
print(result.category, result.urgency, result.summary)

anyOfで選択肢を固定しておくと、そこにない値は返ってきません。表記のゆれを後から直す手間がなく、結果をそのままデータベースへ入れられます。

架空の問い合わせ文8件で試したところ、8件すべてで種類の割り当てが妥当でした。緊急度の判定も内容と合っていて、「請求金額が二重に引き落とされています。至急確認してください。」は高、「領収書の宛名を会社名に変更してもらえますか。急ぎではありません。」は低と返しています。

問い合わせ文の分類は1件0.44秒、1,000件なら約7分

速度は自分のデータ量で現実的かどうかを決めるので、24件を連続処理して測りました。前の節と同じ分類処理です。

1件目

0.99秒

プログラム起動後の初回だけ遅くなります

2件目以降の中央値

0.44秒

0.38〜0.52秒の範囲に収まりました

1,000件の見込み

約7分

中央値からの単純計算です

ファンのないMacBook Airで、この速度が24件のあいだ落ちませんでした。夜間に流しておけば数万件でも終わる範囲です。ただしこの速度が出るのは、次の節で説明する、1件ごとにセッションを作り直す書き方をした場合だけです。

同じセッションを使い回すと、22件目で必ず止まる

最初に書いたコードは、セッションをひとつ作って全件をそれで処理するものでした。8件までは動きましたが、件数を増やすと例外で止まります。

apple_fm_sdk.errors.ExceededContextWindowSizeError: Context window size exceeded

8種類の問い合わせ文を順に繰り返しながら、何件目で止まるかを3回試しました。3回とも21件までは成功し、22件目で同じ例外が出ます。しかも、そこへ至るまでに1件あたりの処理時間が伸び続けます。1回目の試行では、5件目が0.57秒だったのに対し、20件目は3.55秒かかりました。

1件あたりの処理時間の比較。セッションを使い回すと1件目0.46秒から8件目1.23秒まで増え続け、1件ごとに作り直すと8件とも0.38〜0.48秒で一定になる
同じ8件を同じ条件で分類したときの1件あたり処理時間。計測環境はMacBook Air (Apple M4 / 32GB) / macOS 26.5.1 / apple-fm-sdk 0.2.1、2026年8月13日に筆者が計測。

なぜ22件で止まるのか

セッションは会話の履歴を保持します。人間との対話ならそれが役に立ちますが、独立した1,000件のデータを流し込む用途では、前の999件が邪魔になるだけです。1つのセッションが覚えていられる量には上限があり、履歴がそこへ達すると例外が出ます。

SDKには、その上限を返すcontext_sizeと、文章がどれだけの量になるか数えるtoken_count()があるので、内訳を確かめられます。単位はトークンといって、文章を処理するときの区切りのことです。今回の例文では、23文字が19トークン、195文字が106トークンと数えられました。

1セッションで使える枠

4,096 トークン

受け取る項目の定義

149

1件ごとに毎回積み上がります

問い合わせ文1件(23文字)

19

本題はこれだけです

最初の指示文

23

セッションにつき1回だけです

処理したい文章が19トークンなのに、受け取る項目の定義が149トークンを占めます。短い文を大量に流すほど、この定義が枠を食いつぶします。

この内訳から、モデルの出力を数えずに計算すると、23文字の短い問い合わせなら24件前後、195文字の長い問い合わせなら15件前後で枠を使い切ります。実際に同じ文を繰り返して試すと、短い文は22件、長い文は14件まで処理できました。計算との差は、モデルが返した文章の分で説明がつきます。止まる位置は処理した件数ではなく、積み上がった文章の量で決まります。「20件までなら安全」といった件数での管理はできません。

1件ごとにセッションを作り直す

直し方は1行の移動で済みます。セッションを作る処理を、ループの外から中へ入れるだけです。これで処理時間は8件とも0.38〜0.48秒で平坦になり、件数の上限もなくなります。

# 避ける:1つのセッションを使い回す
session = fm.LanguageModelSession(instructions=INSTRUCTIONS)
for text in texts:
    await session.respond(text, generating=Inquiry)

# 推奨:1件ごとに作り直す
for text in texts:
    session = fm.LanguageModelSession(instructions=INSTRUCTIONS)
    await session.respond(text, generating=Inquiry)

会話として続ける必要がある場合の対処は、Appleの技術ノートTN3193にまとまっています。大きな仕事を複数のセッションへ分ける、短く答えさせる、履歴を要約して詰める、といった方法が挙げられています。履歴を要約するためのユーティリティもAppleが別途公開しています。

渡した文章に書かれていないことを聞くと、モデルは答えを作り出す

使ってはいけない用途も確かめました。実在しない「ネギチョウ社のスマートフォンZX-9000」の機能を3つ挙げるよう頼むと、高性能プロセッサ、高画素カメラ、長持ちするバッテリーという説明を、それらしい文章で組み立てて返しました。そんな製品は知らない、という反応は一度もありません。

やっかいなのは、渡した文章の中で答えさせる場合でも同じことが起きる点です。要約で使った先ほどのお知らせには「コンビニエンスストアで粗大ごみ処理券を購入してください」とありますが、金額はどこにも書かれていません。そこで同じお知らせを渡して処理券の値段を聞くと、こう返ってきました。

粗大ごみ処理券は、コンビニエンスストアで購入してください。具体的な料金については、各店舗のホームページで確認する必要があります。

前半はお知らせのとおりです。しかし後半の「各店舗のホームページで確認する」は、お知らせのどこにも書かれていません。答えられないことを、それらしい案内で埋めています。

これは指示で防げました。「お知らせに書かれている内容だけで答えてください。書かれていない場合は『お知らせには記載がありません』と答えてください」と付けると、そのとおりの一文が返ります。同じ指示のまま、書かれていること(申し込みから収集までの日数)を聞けば、「約2週間」に加えて「3月と4月は3週間以上かかる場合がある」という例外まで拾って答えました。

つまり、答えの材料を渡すだけでは足りません。材料の外へ出ないよう指示することまでが、必要な作業に含まれます。

実測結果から考えた、任せられる4つの仕事

ここまでの数値をふまえて、実際に任せられそうな仕事を挙げます。いずれも「答えの材料を自分で渡せる」「件数が多い」「外部へ出しにくい」のどれかに当てはまるものです。

問い合わせの一次仕分け

種類と緊急度を付けて、担当者へ回す前に並べ替える。1,000件で約7分なので、朝の始業前に前日分を処理しておけます。

裏付け:8件すべてで分類が妥当だった

議事録やメモからの抽出

決まったこと、次にやること、担当者を項目へ分けて取り出す。社外に出せない会議の記録でも、Macの外へ出ません。

裏付け:会議メモから決定事項を原文どおり抽出できた

大量テキストのタグ付け

アンケートの自由記述やレビューへ、決めた選択肢からラベルを付ける。件数が増えても費用が変わらないので、全件処理を前提にできます。

裏付け:anyOfで選択肢を固定でき、想定外の値が返らなかった

クラウドAIへ渡す前の下処理

全件をローカルで要約・選別してから、本当に精度が要るものだけをClaudeやChatGPTへ送る。API利用料を減らせます。

裏付け:要約で事実の取り違えがなかった

逆に、社内規程を検索して答えさせる、数値を集計する、専門的な判断を任せる、といった用途はこのモデルに向きません。検索と集計は先にPython側で済ませ、モデルには文章にする部分だけを渡すほうが、結果が安定します。

まとめ|最初に試すなら分類のコードから

導入から検証まで通してみて、いちばん意外だったのは日本語の質でした。クラウドで動く大規模なモデルと比べれば30億パラメータは小さく、日本語の要約は崩れるだろうと予想していましたが、4種類の文書で試して事実の取り違えは見つかりませんでした。

最初に試すなら、手元にあるテキストファイルを10件ほど用意して、この記事の分類コードをそのまま動かすのが早いと思います。0.44秒という数字が自分の用途で足りるかどうかは、実際の文章の長さで変わるので、そこだけは自分のデータで測る価値があります。

なお、今回検証したのはmacOS 26世代のモデルです。Appleは2026年6月に、次の世代で画像入力やClaude・Geminiなど他社モデルの利用へ対応すると発表しています。この記事の数値は、あくまでMacBook Air (Apple M4) / macOS 26.5.1 という単一の環境で2026年8月13日に測ったものとして読んでください。機種やOSのバージョンが変われば、速度も上限件数も変わります。画像の入力、ツール呼び出し、ネットワークを切った状態での動作は、今回試していません。