すべての記事

サーバーなし、アカウントなし、端末から何も出ていかないAIアプリ

· 10分で読めます

家族のカレンダーの土曜日に「Piscine 10h avec Tom」とあります。その日の夕方、アプリは子どもに、トムと行ったプールのお出かけについてのフランス語の書き取りを渡します。学年に合わせて書かれ、先週まちがえた一致のルールを練習させる内容です。

その土曜日のことは何ひとつ端末から出ていきません。カレンダーの予定も、子どもの名前も、まちがえた箇所もです。作るアカウントはなく、呼ぶサーバーもなく、統計も取りません。端末を機内モードにしても、アプリは1分前とまったく同じように動きます。

アプリの名前は Dictée Ludique です。私が書き、App Store に出ていて、いま顧客からほぼ毎週受ける質問に対する私のいちばん明確な答えになっています。ユーザーのデータをどこにも送らずに、AI の部分を実現できますか、という質問です。

Apple のハードウェア上では答えは「はい」で、今日すでに動きます。以下は、そのために何が必要だったか、何を犠牲にしたか、そして iOS 27 が9月に変えることです。

なぜチャットボットに頼むだけでは足りないのか

これは当然の疑問で、アプリ全体がこの疑問から生まれました。

私も誰もがやるところから始めました。チャットボットに9歳の子ども向けの書き取りを書いてと頼み、実際に書いてくれました。しかもまともなものを。

そして翌週も同じことをして、自分が何を手にしていたのかが分かりました。チャットボットはテキストをくれます。しかし仕組みはくれません。この子が11月には過去分詞の一致をしっかり押さえていて1月には忘れていたことも、同音異義語が一度も試されていないことも、直近3本がすべて同じ休暇の話だったことも知りません。それをすべて抱えていたのは私で、毎回プロンプトに打ち直していました。書くこと自体は無料でした。帳簿づけがすべての仕事でした。

私が欲しかったのは、書くことのまわりの仕組みでした。学習指導要領から取った15の綴りの技能を子どもごとに追跡し、それぞれを習得済み、進行中、あやふや、未出のいずれかに置く。書き取りのたびに採点の手順があり、その状態を動かし、保護者の手間は15秒ほど。そして次のテキストは、まずあやふやなものを、次にまだ出ていないものを狙い、長さと密度は前回の結果に従い、誰も設定をいじる必要がない。

さらに、遊びであることも必要でした。それが名前のもう半分です。ポイントやバッジではありません。アプリのどこにも総合スコアは意図的に置いていません。15の技能の平均に意味はなく、しかも子どもが唯一おぼえてしまう数字になるからです。遊びとは、子どもが3枚のカードを受け取って1枚を選ぶことであり、テキストが他人ではなく自分の土曜日の話であることです。となりの家の猫ではなく自分の誕生日の書き取りなら、子どもの書き方は変わります。

生成は小さな部分だと分かりました。そのまわりのループこそがアプリであり、それが以下のすべてを決めました。

なぜクラウドは最初から選択肢になかったのか

規制上の理由があり、そして実際に私を動かした理由があります。

規制上の理由はありふれています。これはヨーロッパで売られる子ども向けアプリなので、データ保護の質問は、資金調達のあとではなく、プライバシー画面を開く最初の保護者とともにやって来ます。何も集めないということは、届け出るものも、漏れるものも、説明するものもないということです。

実際に決め手になった理由はこうです。このアプリは家族のカレンダーを読みます。それは端末の中でもっとも私的なファイルです。通院の予定、親権の受け渡し、就職の面接、誰かが家を出ていった週が入っています。そこから自分の持ち物でないデータセンターへ管を通すつもりはありませんでしたし、どんなプライバシーポリシーを書いても私は納得できなかったはずです。

つまり制約が先にあり、設計はそれに従うしかありませんでした。

カレンダーはモデルが見る前に絞り込まれる

ここは誰もが AI の仕事だと思う部分です。しかし違います。

予定の生のタイトルがモデルに届くことはありません。届くのは、73個の既知の形からなる閉じたリストが作る、正規化された名詞句です。「Piscine 10h avec Tom」は「la sortie piscine」として出てきます。「Anniv de Tom」は「l’anniversaire de Tom」として出てきて、この名前だけが、意図的に狭いパターンで拾われた、鎖全体で唯一の自由テキストです。

照合は語の境界で行います。細かすぎるように聞こえますが、何を防いでいるかを見れば分かります。「ski」が「Kandinsky」に反応してはいけません。サマーキャンプを指すフランス語の略語「colo」が「coloscopie」に反応してもいけません。最長の形が勝つので、「sortie scolaire」が裸の「sortie」に優先します。

62語からなる第二のリストは、予定そのものを即座に失格にします。病院、葬儀、離婚、失業、刑務所、そのほか家族のカレンダーに満ちている領域です。「Anniversaire de mamie à l’hôpital」は、誕生日という語がそこにあっても、使える誕生日ではありません。

リストが認識しないものには、名前がまったく付きません。ラベルのない候補として保護者の画面に現れ、保護者が自分で名前を打ち込むまで使えません。一度断られたものは二度と提案されません。

読み取りの窓は意図的に非対称です。単発の予定は2年さかのぼり、先は2週間、繰り返す予定は1週間しかさかのぼりません。子どもは旅行を何年も覚えています。18か月前の柔道の練習はノイズです。

ここまではすべて普通の Swift です。決定的で、テストに覆われていて、実際のプライバシーの仕事をしているのはこの部分です。モデルは最後の段階であって、最初ではありません。

モデルはテキストを書くだけ

生成は、iOS 26 で登場した Foundation Models フレームワーク を通じて、Apple の端末内モデルで動きます。出力は誘導されていて、短いタイトルと文の配列を持つ @Generable 構造体で、上限は12文です。

散文のかたまりではなく文の配列にしているのは、モデルが自分で文を数えるときのほうが、目標の長さをずっとよく守るからです。

綴りの落とし穴がどこにあるかをモデルに尋ねることはありません。それは独自のテストを持つ決定的な解析器の仕事です。自分の出力に注釈を付けるモデルは時々まちがえ、しかも誰も気づきません。それは派手にまちがえるより悪いことです。

モデルはテンプレートも見ません。手書きのライブラリはどの子にも使えなければならないので、プレースホルダと一致の仕組みを抱えています。ここではアプリが名前と文法上の性をすでに知っているので、「{ENFANT} est parti{E_ENFANT}」ではなく「Léa est partie」を求めます。誤りの一分類がまるごと消えます。

生成されたテキストに特別扱いはない

生成された各テキストは、手書きのライブラリとまったく同じ7段階の検証を通ります。学年に合わせた語彙、許された時制、文の長さ、禁じられた領域、子どもが練習すべき技能、その子自身の名前、そして実際に言及されているカレンダーの出来事です。

生成されたテキストが、人が目を通したテキストより優遇されることはありません。むしろ少しだけ厳しく、ライブラリには一度も必要なかった上限が必要になりました。

5回の試行が失敗すると、アプリはライブラリのテキストを出し、何も言いません。モデルの調子が悪かったことを子どもが知ることはありません。

端末内モデルが実際にかかったコスト

この部分はどの WWDC セッションにもありません。自分の端末のログから取った数値です。

  • ある生成が48.9秒暴走し、「Exceeded model context window size」で落ちました。同じ指示による他の4回は2〜3.5秒で返っています。問題は入力ではなく、止まらない出力でした。12文という上限はここから来ています。
  • 最初の下書きは、要求した長さの3分の1程度になることが定期的にあります。あるセッションでの実測は29語、次が184語、次が34語で、指示は55〜75語でした。温度を上げても偶然をかき混ぜるだけです。直前に何を外したのかを明確なフランス語で伝えると効きます。そこで再試行のループは、温度をいじるだけでなく、ずれを説明する一文を返すようになりました。
  • モデルは、すべての検査を通ったのに読み手にとっては誤りであるテキストを作りました。9歳向けに11文、語数は範囲内、各文は上限未満、そして子どもが受け取るのは電報です。ライブラリのテンプレートは人が書いたので、この不具合はそこには存在しませんでした。モデルは見事な一貫性でこれをやるので、文の数に上限を設けました。
  • Apple のガードレールは理由を言わずに作動します。エラーは「Detected content likely to be unsafe」だけです。同じ入力で温度を上げながら5回試しても、5回とも同じように失敗します。いまアプリがしているのは、順番に取り除くことです。まずカレンダーの出来事、これはアプリ自身が書いていない唯一の内容だからです。次に指示の中の内容ルール。解除に至った段階がそのまま診断であり、そのようにログへ残します。
  • EventKit には半日を取られましたが、理由は AI とは関係ありません。許可の前に作られた EKEventStore はプロセスが生きている間ずっと空のキャッシュを保持するので、保護者が「許可」を押したあとでもカレンダーの読み取りは0件を返します。さらに authorizationStatus(for:) は許可に遅れます。iPad では要求が true を返したのに、13ミリ秒後に読んだ状態はまだ notDetermined でした。どちらも権限のバグにしか見えませんが、どちらもバグではありません。

もうひとつの制約が、ほかのどれよりもアプリの形を決めました。子どもが「書き取りを書いています」という画面を見ているとき、5回が上限です。そこでアプリは前もって生成します。書き取りを声に出して読むには数分かかるので、生成器はその窓の中で働きます。1セッション10ラウンドまで、読み上げ画面に結び付き、閉じれば中止されます。検証を通っていないものがキャッシュに入ることは決してありません。買っているのは時間であって、甘さではありません。

iOS 27 で変わること

上の一覧のほとんどは、9月に出るバージョンで解決します。登場から1年のフレームワークについて書くには奇妙なことです。

  • 端末内モデルは作り直され、論理と道具の呼び出しが改善し、Apple によれば誤検知の少ないガードレールになりました。これは私のいちばん高くついた失敗に直接効きます。指示への追従が良くなれば再試行の回数も減るはずで、それは前もって生成するか要求に応じて生成するかの分かれ目です。
  • response.usage が入力、出力、キャッシュ、推論のトークン数を返すようになり、model.contextSize が窓を示し、tokenCount(for:) が送信前にプロンプトを数えます。48.9秒の暴走は、あとからログで見つけるものではなく、来る前に見えるものになります。
  • 端末内モデルが画像を受け取れるようになり、フレームワークは Vision に支えられた端末内の OCRTool を提供します。私がずっと考えているのはこれです。App Store の説明文は現在、アプリがページを撮影しないと約束しています。手書き認識は子どもがしていない誤りを作り出しますし、誤りを幻覚する綴りのアプリはアプリがないより悪いからです。そこでは、プライバシーだけが断る理由だったわけではありません。ただ、アップロードなしで完全に端末内で試せるというのは新しいことです。
  • LanguageModel プロトコルにより、どのモデルでもセッションを支えられます。アプリはすでにこの形でした。単一の窓口が実行時にこのシステムにフレームワークがあるかを判断するので、ひとつのバイナリが Apple Intelligence のない macOS 15 と、ある macOS 26 の両方に対応できます。この間接層はいまや私ではなく OS のものです。
  • DynamicProfile により、ひとつのセッションが会話の途中で指示と道具を切り替えられます。書き取りを書くことと採点することは、このアプリでは別の仕事であり、いまは別のセッションです。

Apple の無料クラウドモデル、既定はオフ

同じバージョンは、端末を離れることをとても簡単にします。

PrivateCloudComputeLanguageModel は32,000トークンのコンテキストウィンドウと推論の設定を提供し、API キーもアカウントも不要で、初回ダウンロード200万件という基準を下回る小規模開発者にはクラウド料金もかかりません。Anthropic と Google は同じセッション型につながる Swift パッケージを公開しています。このアプリを切り替えるのはおよそ1行で、テキストはより長く、より整った形で、初回から検証を通る割合もずっと高くなるはずです。

受け入れる理由は本当にあります。しかもそれは、始めたときに私が想定していた理由ではありません。端末内モデルは9歳向けの良い書き取りを書きます。ところが、尋ねているのがフランス語を学ぶ大人で、従属節を含むより長い文章と広い語彙を求めている場合や、学年表が示すより意欲的なものを求める保護者の場合には、薄くなります。そうした読み手は実在し、すでにアプリを使っていて、端末の中でいちばん小さいモデルは彼らのために書いているとは言えません。

そこで iOS 27 では、アプリはこれを設定として提供し、その設定はオフの状態で出荷します。アプリを入れて何も触らなければ、いまアプリがしている約束はそのまま保たれます。端末から何も出ていかず、機内モードが4秒でそれを証明します。

この選択肢をそもそも提供できるようにしているのは、まさに前半で書いた仕組みです。スイッチを入れても、カレンダーの生のタイトルがネットワークを越えることは依然としてありません。ホワイトリストがすでに正規化された名詞句へ縮めているので、旅をするのは「la sortie piscine」と名前だけで、「RDV Dr Meunier, résultats」が出ることはありません。このフィルタは子どもをモデルから守るために作りました。結果として、家族をネットワークからも守っています。

プライバシー画面は1文だけ増えます。そこをきちんとやりたいと思っています。端末から何も出ていかないという1行が2行になります。端末から何も出ていきません、ただし、より豊かなテキストをオンにした場合を除きます、そしてそのとき何が出ていくかは正確にここに書いてあります、と。Apple のプライベートクラウドは良い約束で、私はそれを信じています。それでも約束は約束であり、約束は、保護者が自分で入れたスイッチの向こうにあるべきで、誰も読まない既定値の中にあるべきではありません。

端末内が既定であり続けるのには、地味な理由もあります。電車の中、車の中、Wi-Fi の弱い貸家、まさにそういうときに家族はこのアプリを開きます。設定が何であれ、ネットワークがまったくなくても動き続ける必要があるので、端末内のエンジンがなくなることはありません。

自分のアプリでこれが必要なら

データが端末から出てはいけないアプリを検討していて、それでは AI は無理だと言われたのなら、その助言は1年古いものです。医療、教育、人事、法務、子どもが関わるもの、顧客との契約がデータの置かれる国を指定しているもの。端末内という道は実在し、いま動きます。

注意点は、2か月目に自分で発見するより、私から聞いておくほうがよいでしょう。

  • 端末内モデルは小さいです。短い文章はうまく書きます。長い文書を読み解いて推論することはしません。モデルに機能を合わせてください。逆ではありません。
  • 検証層を書くことになります。モデルは判断力のない速い書き手だと考え、そのまわりを決定的なコードで囲んでください。製品が実際に宿るのはそのコードです。
  • Apple Intelligence がまったくない端末のための代替が必要で、それは目に見えてはいけません。ここではモデルの後ろに手書きのライブラリが控えていて、今日のテキストをどちらが書いたのか子どもには分かりません。
  • 端末内とクラウドは二つの製品からの選択ではありません。フィルタリングをモデルの中ではなく前に置けば、プライバシー画面を一言も書き直さずに、あとからクラウドを設定として提供できます。
  • これらはどれもウェブ技術ではありません。EventKit であり、プライバシーマニフェストであり、誘導生成であり、実機でしか姿を見せないプラットフォームの挙動です。難しい部分が OS であるとき、ネイティブがクロスプラットフォームのラッパーに勝つ理由がまたひとつ増えます。

カレンダーのフィルタも含めて全体は、数か月ではなく数日で仕上がりました。その時間の大半は検証の連なりと失敗経路に消えていて、プロンプトではありません。

外へ何も送れないアプリが必要ですか?

端末内 AI、EventKit、そして webview では届かないプラットフォーム API を中心に構築する、ネイティブの iOS と Android。初日の終わりにあなたの端末で動くアプリを、500ユーロから(税抜)。

プロジェクトについて相談する