日記の要約や朝の短い文章の生成を、OpenAI から Apple の Private Cloud Compute(PCC)に移しました。41日間の本番のログを数えると、614回のリクエストのうち65回(10.6%)が「不適切な内容の可能性」として断られ、59回(9.6%)が「短時間にリクエストしすぎ」で止められていました。断られたのは、普通の日記や会話の要約です。どんな失敗をして、どう作り直したかを書きます。
やりたかったこと
iPhone の中で会話を文字にする日記アプリ(Liner Notes)では、会話の要約、毎朝の短い文章(朝の一筆)、記録の題名、会話の話し手の役割を、言語モデルで作っています。最初は OpenAI の API を使っていました。
2026年9月末に、これらを PCC に移しました。PCC は、Apple が自社のサーバーで動かす言語モデルを、iOS の Foundation Models フレームワークから呼べる仕組みです。アプリ側の費用はかからず、リクエストの内容は Apple のサーバーに残りません。会話の内容を扱うアプリにとって、両方とも大きな利点です。
本番のログを数えてみた
要約が付かないメモがあることに気づき、本番のログ(自分の iPhone、41日分)を数えました。
| リクエスト | 失敗 | 「不適切」として拒否 | 短時間のリクエストしすぎ(レート制限) | 1日の上限 | 取り消し、その他 |
|---|---|---|---|---|---|
| 614回 | 130回(21%) | 65回(10.6%) | 59回(9.6%) | 0回 | 6回 |
本番のリクエスト614回の内訳(41日間)
表で見る
| 項目 | 値 | 割合 |
|---|---|---|
| 成功 | 484回 | 78.8% |
| 「不適切」として拒否 | 65回 | 10.6% |
| レート制限 | 59回 | 9.6% |
| 取り消し、その他 | 6回 | 1.0% |
拒否のメッセージは3種類ありました。
| メッセージ | 回数 |
|---|---|
| Streamed response may contain sensitive or unsafe content | 41 |
| Safety guardrail was triggered. | 18 |
| Response may contain sensitive or unsafe content | 6 |
断られたのは、日記の下書きや会話の要約で、性的な内容や暴力的な内容ではありません。技術の打ち合わせの録音の要約も断られます。Apple の端末内モデルについては、普通のリクエストを断りすぎるという報告が開発者フォーラムに何度も出ています。サーバー側の PCC でも同じことが起きていました。
断られるときは時間もかかります。成功は1〜3秒で返りますが、拒否は7.6〜12.7秒かかりました。返事を流し始めてから、途中でフィルタが止めるためです。
返事が返るまでの時間
秒(範囲の中央で描いている)
表で見る
| 項目 | 値 |
|---|---|
| 成功 | 1〜3秒 |
| 拒否 | 7.6〜12.7秒 |
起きた問題
拒否とレート制限によって、アプリで4つの問題が起きていました。
1つ目は、要約が黙って欠けたことです。PCC が失敗すると要約が作られず、「文字起こしはあるのに要約が無いメモ」が残りました。利用者には、アプリの不具合に見えます。
2つ目は、安全装置が本番の PCC を止めたことです。生成の途中でアプリが落ちたら、その経路を止める安全装置を入れていました。ところが、バックグラウンドで期限切れになっただけの回を「落ちた」と数え、10月16日に本番で PCC がまるごと止まりました。
3つ目は、1日の上限に当たっている最中に記録を消したことです。開発用の iPhone で人物像を一括で作り直した日、リクエストは1,158回になり、そのうち689回が上限のエラーで失敗しました。上限に達したあとのリクエストは、すべて失敗します。「古い記録を消してから作り直す」処理を上限中に走らせたので、消したあと作り直せず、記録が7件に減りました。
4つ目は、毎朝の一括生成がレート制限に当たっていたことです。レート制限は、直前60秒のリクエストが8〜10回になると当たります。朝の一筆を7時台にまとめて作っていたので、本番の7時台は14回中3回しか成功していませんでした。
時刻ごとの成功率(本番、リクエストが10回以上あった時刻)
%
表で見る
| 項目 | 値 |
|---|---|
| 0時 | 83%(10/12) |
| 7時 | 21%(3/14) |
| 8時 | 79%(38/48) |
| 9時 | 70%(14/20) |
| 10時 | 53%(60/114) |
| 12時 | 82%(31/38) |
| 13時 | 86%(38/44) |
| 14時 | 96%(22/23) |
| 16時 | 97%(31/32) |
| 18時 | 76%(25/33) |
| 19時 | 82%(28/34) |
| 20時 | 93%(14/15) |
| 21時 | 97%(31/32) |
| 22時 | 90%(54/60) |
| 23時 | 90%(55/61) |
朝の7時台と、リクエストが最も多かった10時台で成功率が下がっています。どちらも、まとめて生成していた時間帯です。
拒否を減らせないか試した
拒否されるリクエストとされないリクエストで、入力の長さに違いはありませんでした(873字でも2,174字でも拒否されます)。どの言葉が拒否の引き金になっているかは分からず、指示の書き方を変えて拒否を減らす方法は見つけられていません。拒否は減らせないものとして、設計を変えることにしました。
作り直したこと
| 問題 | 直したこと |
|---|---|
| 要約が黙って欠ける | 失敗したら端末内のモデルで作る。会話の内容は OpenAI に送らない方針なので、代わりは端末内だけ |
| 要約が無いと不具合に見える | 文字起こしを先に保存し、要約は後から付け直す。拒否されても文字起こしは読める |
| 安全装置が誤作動する | バックグラウンドの期限切れは「落ちた」と数えない |
| 上限中に記録を消す | 一括処理は1日2〜3回まで。上限中は、消してから作り直す処理を走らせない |
| 朝の一括生成がレート制限に当たる | 生成の時刻をばらす |
| どの機能が断られているか分からない | リクエストごとに、機能の名前、かかった時間、失敗の種類(拒否、レート制限、上限)をログに残す |
1日の上限は、エラーに次に使える時刻(resetDate)が入っていて、残りの回数も quotaUsage で分かります。私はこれを読んでおらず、「翌朝になっても戻らない」と悩んでいました。上限はアプリごとではなく、利用者の iCloud アカウント単位とされています。
まとめ
- PCC を本番に入れる前に、自分のアプリの実際のリクエストで拒否率を測る。私のアプリでは10.6%だった
- 拒否されたときの代わりを先に決めておく。要約なら、文字起こしを先に保存して後から付け直す
- レート制限は直前60秒に8〜10回で当たる。一括生成は時刻をばらす
- 1日の上限に当たったら、エラーの
resetDateを読む。上限中に、消してから作り直す処理を走らせない - リクエストごとに機能と失敗の種類を記録し、どの機能が断られているかを見えるようにする
ほかの開発ノート
- iOS のバックグラウンドで音声を処理するなら Neural Engine を使う
iOS のバックグラウンド(BGContinuedProcessingTask)では CPU の処理だけが12〜21倍遅くなり、Neural Engine は2倍以内だった。黙って消える原因3つと直し方を、実機の数字で。
- ベンチマークだけで音声認識を選ぶと危ない。自分の用途の音声で測ると結果が変わる
読み上げのベンチマークでは Apple の音声認識が最良に見えたが、会話の音声では誤りが Parakeet の約1.8倍。日本語の音声認識を選ぶときの測り方と落とし穴を、数字で。
- 文字起こしの精度が同じでも、要約の誤りは3倍違うことがある
文字誤り率が同じ2つの音声認識で、要約の誤りは3倍違った。要約の指示を厳しくすると誤りが増えた理由と、要約で比べる方法。
- 先行研究の方法で自分のコピー AI を作ったが、何を学ばせても一致率は62〜67%で止まった
先行研究の方法で自分のコピー AI を作ったが、日記2万字でも自己紹介1行でも一致率は62〜67%。その人らしさは内容より話し方に出ることを、自分の録音と文献で。