WWDC 2026 の発表を追いかけていた 6 月の半ば、State of the Union のなかで手が止まった一節がありました。初回ダウンロード 200 万件未満の開発者は、Private Cloud Compute 上の Foundation Models を無償で利用できる、という線引きです。
AI 機能の原価——つまり推論 API の従量課金は、個人開発のアプリにとって価格設計をいちばん強く縛る要素でした。DAU が増えるほど費用も比例して増えるのに、競合は同じ機能を無料で出してくる。利益を守ろうとすると体験を削るしかない。そういう窮屈な構造の一部が「無償」に変わるなら、設計の前提から考え直す価値があります。
オンデバイス版の Foundation Models は、以前 Rork で Apple FoundationModels を使う — iOS 26 のオンデバイス LLM をプロダクトに組み込む実装ガイド で扱い、「狭く速いタスクは端末内、重いタスクはクラウド」という二分法を書きました。今回の発表は、その二分法の真ん中に「無償のクラウド層」をもう一段差し込むものです。ここでは、その前提の変化を費用の試算から実装の経路まで、一つずつ確かめていきます。
「初回 200 万ダウンロード未満は無償」をどう読むか
発表時点の公開情報を整理すると、骨子は次の 4 点です。
無償の対象は Private Cloud Compute(PCC)上の Foundation Models 。端末内の約 30 億パラメータ版ではなく、Apple のサーバー側で動くより大きなモデルを、初回ダウンロード 200 万件未満の開発者は追加費用なしで呼べるようになります
Foundation Models フレームワークに画像入力が加わる 。スクリーンショットや写真を渡す理解系のタスクが、同じ Swift API の延長で書けるようになります
同一の Swift API からサーバーサイドモデルの統合が可能になる 。Claude や Gemini といったサードパーティのモデルも、フレームワークの呼び出し口を変えずに接続できる設計が示されました
フレームワークは今夏オープンソース化の予定 。内部挙動を確認しながら設計できるようになります
注意したいのは 3 点目の読み方です。「同じ Swift API から Claude を呼べる」というのは接続の利便性の話であって、サードパーティモデルの推論費用まで無償になるわけではありません。各社の API キーと課金体系はそのまま生きています。発表の高揚感のなかでここを混同すると、原価計算が根本から狂います。私はまずこの境界線を紙に書き出すところから始めました。
もう一つ、200 万件という線引きの実務的な意味です。個人開発のアプリの多くは、単一アプリの初回ダウンロードがこの数字に届く前の段階にいます。つまりこの無償枠は、実質的に「個人開発者と小規模チームのための施策」と読めます。ただし条件のカウント方法など細部は今後の正式ドキュメントで確定していくはずなので、実装前に最新の開発者向け規約を確認する前提で読み進めてください。
三層の役割分担 — どのタスクをどこに置くか
無償の PCC 層が加わったことで、AI 機能の置き場所は実質的に三層になりました。それぞれの特性を並べます。
第 1 層(オンデバイス) : 約 30 億パラメータ・コンテキスト約 4K トークン。費用ゼロ・オフライン動作・遅延最小。分類、タグ付け、短文の整形、定型応答の生成に向きます
第 2 層(PCC 上の Foundation Models) : サーバーサイドの大きめのモデル。無償枠の主役です。要約、構造化抽出、画像入力を伴う理解系など、オンデバイスでは収まらない中量級のタスクを受け持ちます。ネットワーク接続が必須です
第 3 層(サードパーティ API) : Claude や Gemini。従量課金。長文の推論、品質が売上に直結する生成、多言語の難所など、上位モデルでなければ成立しないタスクだけに絞ります
仕分けの判断基準は 4 つあれば足ります。①オフライン要件があるか、②コンテキスト長が約 4K トークンに収まるか、③出力品質がそのまま売上や評価に響くか、④呼び出し頻度が高いか。頻度の高いタスクほど下の層(第 1 層側)へ寄せるのが原則です。従量課金から高頻度タスクを外すことが、費用構造にいちばん効くからです。
私が個人開発で運用している壁紙系アプリに当てはめると、画像のタグ候補生成は第 1 層、ユーザーレビューへの返信下書きは第 2 層、ストア説明文を多言語に展開する初稿づくりは第 3 層、という置き方になります。ひとつのアプリの中でも、タスクごとに層が違うのが自然な姿です。
試算してから動く — 三層化の効果をスクリプトで見積もる
層の置き直しには実装コストがかかるので、先に「いくら浮くのか」を数字にしておきます。次のスクリプトは、タスクごとの月間呼び出し回数とトークン量から、全部サードパーティ構成と三層構成の月額を比較するものです。
// ai-cost-simulation.ts — 三層化の効果を月額で見積もる
// 実行: npx tsx ai-cost-simulation.ts
type Layer = "on-device" | "pcc" | "third-party" ;
interface TaskProfile {
name : string ;
monthlyCalls : number ; // 月間呼び出し回数
avgInputTokens : number ; // 1回あたりの入力トークン
avgOutputTokens : number ; // 1回あたりの出力トークン
layer : Layer ; // 三層設計での配置
}
// 単価は仮置きです。実際に使うモデルの現行単価に差し替えてください
const THIRD_PARTY_PRICE = {
inputPerMTok: 3.0 , // USD / 100万入力トークン
outputPerMTok: 15.0 , // USD / 100万出力トークン
};
const tasks : TaskProfile [] = [
{ name: "画像タグ候補の生成" , monthlyCalls: 120_000 , avgInputTokens: 200 , avgOutputTokens: 20 , layer: "on-device" },
{ name: "レビュー返信の下書き" , monthlyCalls: 90_000 , avgInputTokens: 1_200 , avgOutputTokens: 200 , layer: "pcc" },
{ name: "長文ドキュメント解析" , monthlyCalls: 6_000 , avgInputTokens: 6_000 , avgOutputTokens: 800 , layer: "third-party" },
];
function thirdPartyCost ( t : TaskProfile ) : number {
const input = (t.monthlyCalls * t.avgInputTokens) / 1_000_000 * THIRD_PARTY_PRICE .inputPerMTok;
const output = (t.monthlyCalls * t.avgOutputTokens) / 1_000_000 * THIRD_PARTY_PRICE .outputPerMTok;
return input + output;
}
// 全タスクをサードパーティ API に置いた場合
const allThirdParty = tasks. reduce (( sum , t ) => sum + thirdPartyCost (t), 0 );
// 三層設計: on-device と pcc は原価ゼロ(無償枠の範囲内とする)
const threeLayer = tasks
. filter (( t ) => t.layer === "third-party" )
. reduce (( sum , t ) => sum + thirdPartyCost (t), 0 );
console. log ( `全部サードパーティ: $${ allThirdParty . toFixed ( 2 ) } / 月` );
console. log ( `三層設計: $${ threeLayer . toFixed ( 2 ) } / 月` );
console. log ( `削減率: ${ (( 1 - threeLayer / allThirdParty ) * 100 ). toFixed ( 1 ) }%` );
// 期待される出力:
// 全部サードパーティ: $882.00 / 月
// 三層設計: $180.00 / 月
// 削減率: 79.6%
このサンプル値では月 882 ドルが 180 ドルになり、約 8 割の削減です。効いているのは金額の大きさそのものではなく、内訳です。高頻度・低トークンの「画像タグ候補」と、中頻度・中トークンの「レビュー返信下書き」が従量課金から外れたことで、残る費用は低頻度・高単価のタスクだけになりました。この形になっていれば、ユーザーが増えても費用の伸びはゆるやかです。
数字を入れ替えて試すときは、THIRD_PARTY_PRICE を自分が使うモデルの現行単価に直すことと、monthlyCalls を楽観値ではなく直近の実測から取ることを推奨します。試算が甘いと、三層化の優先順位を読み違えます。
オンデバイス層と PCC 層 — 呼び出し口を 1 箇所に揃える実装
三層設計でいちばん壊れやすいのは、層の選択ロジックがアプリのあちこちに散らばった状態です。無償枠の条件が変わったり、PCC の確定 API が正式リリースで多少変わったりするたびに、修正箇所を探し回ることになります。私は層の選択を 1 つのルーターに集約し、各層の実装を関数単位で差し替えられる形にしておくのが現実的だと考えています。
// AIRouter.swift — 層の選択を 1 箇所に集約する
import FoundationModels
enum AILayer {
case onDevice // 第1層: 端末内・無料・オフライン可
case privateCloud // 第2層: PCC 上の Foundation Models(無償枠)
case thirdParty // 第3層: Claude / Gemini など従量課金 API
}
struct AITask {
let prompt: String
let needsOffline: Bool
let estimatedTokens: Int
let qualityCritical: Bool
}
enum AIRouter {
static func layer ( for task: AITask) -> AILayer {
// オフライン要件があるものはオンデバイス一択
if task.needsOffline { return .onDevice }
// 品質が売上に直結するタスクだけ従量課金の第3層へ
if task.qualityCritical { return .thirdParty }
// 約4Kトークンを超えるならオンデバイスには収まらない
if task.estimatedTokens > 3_500 { return .privateCloud }
return .onDevice
}
static func respond ( to task: AITask) async throws -> String {
switch layer ( for : task) {
case .onDevice :
// 対応端末判定を必ず通す(非対応端末は PCC に退避)
guard case .available = SystemLanguageModel.default.availability else {
return try await respondViaPrivateCloud (task)
}
let session = LanguageModelSession (
instructions : "落ち着いた日本語で簡潔に答えてください。"
)
let response = try await session. respond ( to : task.prompt)
return response.content
case .privateCloud :
return try await respondViaPrivateCloud (task)
case .thirdParty :
return try await respondViaBackend (task) // 自前バックエンド経由
}
}
private static func respondViaPrivateCloud ( _ task: AITask) async throws -> String {
// PCC 指定の確定 API は正式リリースのドキュメントで確認のうえ、
// この関数の中身だけを差し替える(呼び出し側は変更不要)
let session = LanguageModelSession (
instructions : "箇条書きを使わず、短い段落で答えてください。"
)
let response = try await session. respond ( to : task.prompt)
return response.content
}
private static func respondViaBackend ( _ task: AITask) async throws -> String {
var request = URLRequest ( url : URL ( string : "https://api.example.com/ai/heavy" ) ! )
request.httpMethod = "POST"
request. setValue ( "application/json" , forHTTPHeaderField : "Content-Type" )
request.httpBody = try JSONEncoder (). encode ([ "prompt" : task.prompt])
let (data, _ ) = try await URLSession.shared. data ( for : request)
struct Reply : Decodable { let text: String }
return try JSONDecoder (). decode (Reply. self , from : data). text
}
}
ポイントは 2 つあります。1 つ目は、非対応端末でオンデバイス層が使えないときに、エラーにせず PCC 層へ静かに退避させていることです。Apple Intelligence 対応端末(iPhone 15 Pro 以降など)以外でも機能自体は提供できます。2 つ目は、respondViaPrivateCloud の中身を意図的に「差し替え前提」で書いていることです。PCC を明示指定する確定 API の名称・形は正式リリース時に確認が必要ですが、プロトコル境界をここに引いておけば、確定後の修正はこの関数 1 つで済みます。発表直後の今は、確定情報と未確定情報を分けてコードの構造に反映しておくことが、いちばん堅実な準備だと感じています。
Expo 基盤の Rork アプリから橋を架ける
ここまでのコードは Swift の話なので、Rork Max(月 200 ドル・Swift を直接生成)で作ったアプリならそのまま載ります。一方、通常の Rork(月 25 ドル〜)が生成するのは Expo(React Native)アプリです。Foundation Models は Swift のフレームワークなので、JavaScript からそのままは呼べません。橋が要ります。
選択肢は 3 つです。
Rork Max に寄せる : ネイティブ機能が主戦場のアプリなら最短です。ただし月額差が大きいので、この 1 点だけで乗り換えを決める必要はありません
Expo Modules API でネイティブモジュールを書く : 通常の Rork で作ったアプリをエクスポートし、開発ビルドに切り替えて自前のモジュールを足す経路です。AI が主機能で、第 1〜2 層の原価ゼロ化が収益に直結するなら、この工数を払う価値があります
これまでどおりバックエンド経由でサードパーティ API を使う : AI が補助機能にとどまるなら、無理に橋を架けず第 3 層だけで完結させる判断も十分に合理的です
2 つ目の経路の骨子を載せておきます。モジュールの設定ファイルと Swift 実装、JavaScript 側の呼び出し口の 3 点セットです。
// expo-module.config.json — モジュールの宣言
{
"platforms" : [ "apple" ],
"apple" : {
"modules" : [ "FoundationModelsBridgeModule" ]
}
}
// ios/FoundationModelsBridgeModule.swift — Swift 側の橋
import ExpoModulesCore
import FoundationModels
public class FoundationModelsBridgeModule : Module {
public func definition () -> ModuleDefinition {
Name ( "FoundationModelsBridge" )
AsyncFunction ( "isAvailable" ) { () -> Bool in
if case .available = SystemLanguageModel.default.availability {
return true
}
return false
}
AsyncFunction ( "respond" ) { ( prompt : String ) -> String in
let session = LanguageModelSession (
instructions : "短く丁寧に答えてください。"
)
let response = try await session. respond ( to : prompt)
return response.content
}
}
}
// lib/foundation-models.ts — JS 側の呼び出し口
import { requireNativeModule } from "expo-modules-core" ;
const FoundationModelsBridge = requireNativeModule ( "FoundationModelsBridge" );
export async function localSummarize ( text : string ) : Promise < string | null > {
const available : boolean = await FoundationModelsBridge. isAvailable ();
if ( ! available) return null ; // 呼び出し側で第2層・第3層に切り替える
return FoundationModelsBridge. respond ( `次のテキストを3行で要約してください: ${ text }` );
}
正直に書くと、この経路は Rork のマネージドな開発体験の外に一歩出ます。Expo の開発ビルド(development build)が必要になるため、Rork のブラウザ内だけでは完結しません。それでも、月間数十万回呼ばれる高頻度タスクを従量課金から外せるなら、エクスポートして橋を架ける工数は数字で正当化できるはずです。先ほどの試算スクリプトで自分のアプリの数字を出してから判断するのが順序として正しいと思います。
第 3 層には予算の天井を — ルーターに上限を持たせる
三層化しても、第 3 層の従量課金は残ります。ここに天井がないと、バズやボットの異常呼び出しで月末の請求が跳ねる構造も残ったままです。私は第 3 層への入口を必ず自前バックエンドに置き、月次予算の上限をコードで強制する設計を選びます。考え方の全体像は AI 機能のコスト上限を実行時に強制する設計 — Rork アプリの予算ガード・アーキテクチャ に書いたので、ここでは三層構成に合わせた最小実装だけ示します。
// server/ai-router.ts — 第3層への入口に月次予算の天井を付ける
import { Hono } from "hono" ;
type Bindings = {
ANTHROPIC_API_KEY : string ; // 環境変数で注入する(コードに直書きしない)
AI_SPEND : KVNamespace ; // 月間使用額の積算を保存する KV
};
const MONTHLY_BUDGET_USD = 50 ;
const app = new Hono <{ Bindings : Bindings }>();
app. post ( "/ai/heavy" , async ( c ) => {
const { prompt , estimatedCostUsd } = await c.req. json <{
prompt : string ;
estimatedCostUsd : number ;
}>();
const monthKey = `spend:${ new Date (). toISOString (). slice ( 0 , 7 ) }` ;
const spent = Number (( await c.env. AI_SPEND . get (monthKey)) ?? "0" );
if (spent + estimatedCostUsd > MONTHLY_BUDGET_USD ) {
// 予算超過時は 429 を返し、クライアント側で第2層・第1層へ退避させる
return c. json ({ fallback: true , reason: "budget_exceeded" }, 429 );
}
const res = await fetch ( "https://api.anthropic.com/v1/messages" , {
method: "POST" ,
headers: {
"content-type" : "application/json" ,
"x-api-key" : c.env. ANTHROPIC_API_KEY ,
"anthropic-version" : "2023-06-01" ,
},
body: JSON . stringify ({
model: "claude-haiku-4-5-20251001" ,
max_tokens: 1024 ,
messages: [{ role: "user" , content: prompt }],
}),
});
const data = ( await res. json ()) as { content : { text : string }[] };
await c.env. AI_SPEND . put (monthKey, String (spent + estimatedCostUsd));
return c. json ({ text: data.content[ 0 ].text });
});
export default app;
簡略化のため、ここではクライアントから渡された見積もり額をそのまま積算していますが、本番運用ではレスポンスの usage フィールドから実トークン数を取り、サーバー側で実費を計算して積算するべきです。クライアント申告を信用する設計は、予算ガードとしては穴になります。
三層構成での予算超過時のふるまいも決めておきます。429 を受けたクライアントは、同じタスクを第 2 層(PCC)に流すか、品質を一段落として第 1 層で済ませるか、処理をキューに積んで翌月に回すか。タスクの性質ごとにこの退避先をあらかじめ決めておくと、予算の天井が「機能停止」ではなく「品質の段階的な調整」として働きます。
価格設計への波及 — 無償枠は無料プランの拡張余地になる
原価構造が変わると、価格ページの設計も変わります。第 1 層と第 2 層は限界費用がほぼゼロなので、無料プランに置いても赤字になりません。逆に、第 3 層を使う機能は 1 回ごとに実費が出るので、有料プランか回数制限つきの枠に置くのが自然です。
無料プラン : 第 1 層・第 2 層のタスクで構成する。タグ付け、要約、返信下書きなどは無料で開放しても原価を圧迫しません
有料プラン : 第 3 層の品質を割り当てる。長文の処理、高品質な生成、業務利用に耐える出力を差別化点にします
回数券的な中間 : 第 3 層のタスクに月 N 回の無料枠を設け、超過分をサブスクに誘導する形も組みやすくなりました
無料プラン側を AdMob のような広告収益で支えている構成とも相性がよく、原価ゼロの層が広がるほど、広告単価の変動に対する耐性も上がります。このあたりの価格の決め方は Rorkアプリのサブスク価格設計 — 継続率を落とさずLTVを最大化する5つの原則 と地続きです。
ひとつだけ、浮いた原価を全部「無料化」に回す前に考えておきたいことがあります。200 万ダウンロードの線を超えた瞬間、PCC 層の原価前提は変わります。成功するほど崖に近づく構造なので、私は「第 2 層の全タスクが第 3 層の単価に振り替わっても、有料プランの粗利で吸収できるか」を試算の段階で確認しておく設計を選びます。無償枠は土台ではなく、追い風として扱うくらいの距離感が長持ちすると思います。
先回りしておきたい落とし穴
検証と設計の過程で、踏みそうになった箇所をまとめておきます。
対応端末判定の省略 : 第 1 層は Apple Intelligence 対応端末でしか動きません。SystemLanguageModel.default.availability の確認を飛ばすと、古い端末で起動直後に落ちます。判定と PCC への退避をルーターに組み込んでおきます
PCC をオフライン前提の機能に割り当てる : 第 2 層はネットワーク必須です。機内モードでも使われる機能(メモ、下書き、オフライン閲覧の補助など)は第 1 層に置くか、ローカルのフォールバックを用意して回避します
「同じ Swift API で Claude が呼べる」を「Claude も無償」と読み違える : サードパーティモデルの費用は各社の課金のままです。接続の統一と費用の無償化は別の話として扱います
OSS 化や正式ドキュメントを待って設計ごと止める : 未確定なのは PCC 指定の API の細部であって、三層に分けるという構造ではありません。ルーターの抽象化さえ先に済ませておけば、確定後の差し替えは小さく済みます
無償条件の細部を思い込みで実装する : 初回ダウンロードのカウント方法など、条件の詳細は正式な開発者向け規約で確認してから依存する設計にします。ここを楽観で固定すると、後からの修正がいちばん高くつきます
最初の一歩は、AI 呼び出しの棚卸しから
手元のアプリで AI を呼んでいる箇所をすべて書き出し、タスクごとに「月間回数・平均トークン量・オフライン要否・品質要求」の 4 列を埋めてみてください。その表を本文の試算スクリプトに流し込めば、どのタスクから三層に移すべきか、優先順位が数字で見えてきます。表を作る作業自体は 30 分もかかりませんが、価格設計まで波及する判断の土台になります。
私自身、無償枠の発表を機に棚卸しをやり直して、第 3 層に残すタスクが思っていたより少ないことに気づきました。同じ作業をされる方の参考になれば幸いです。お読みいただきありがとうございました。