ストアの審査ガイドラインを更新のたびに読み直すのが面倒で、私はここ数か月、その手の長い文書をエージェントに読ませて差分だけ出してもらっています。個人開発で複数のアプリを抱えていると、この読み直しは避けられない一方、いちばんやりたくない種類の作業でもあります。ただ、200ページ級の PDF を渡すと、返事が来るまでの間に一度お茶を淹れられるくらいの時間が空きます。
Antigravity 2.11.0(8月26日)で view_file に StartPage・EndPage が入り、MediaResolution で画像解像度も指定できるようになりました。これで待ち時間が減るはずだと思い、手元に240ページの検証用文書を作って測ってみたところ、削減幅そのものよりも、下げすぎた解像度が黙って語を落とすほうが気になる結果になりました。
測定に使ったのは、A4・240ページ・章立て12章の技術文書を自分で組んだものです。実際のストア文書そのものではありませんが、ページ数と1ページあたりの文字量は近い水準に合わせてあります。トークン数は cl100k_base のトークナイザで数え、画像側は「768×768 のタイル1枚あたり258トークン」という公開されている数え方に沿って計算した推定値です。
丸ごと読ませたときの量を、まず数字にしました
テキストとして抽出したときの量です。所要時間は3回実行した最小値を採っています。
| 読ませる範囲 | 文字数 | トークン数 | 抽出時間 |
|---|---|---|---|
| 全240ページ | 904,531 | 137,528 | 0.185 秒 |
| 141〜160ページ(1章) | 75,902 | 11,514 | 0.023 秒 |
| 141〜143ページ(3枚) | 11,738 | 1,781 | 0.010 秒 |
1章に絞ると全体の 8.4%、3ページなら 1.3% です。137,528 トークンという数字は、モデルの窓に入るかどうか以前に、毎回これを積む前提で運用したくない量でした。ここは素直に、範囲指定が入った意味の大きさが出ています。
ページ範囲を書くには、先に「何ページ目か」が要ります
StartPage: 141 と書けるのは、141ページ目に目当ての章があると知っている場合だけです。それを知るために全部読ませるなら本末転倒なので、私は索引を先に手元で作る手順に変えました。
# 1) レイアウトを保ったまま全ページをテキスト化(ページは \f で区切られる)
pdftotext -layout spec.pdf lay.txt
# 2) 見出しらしい行だけを、ページ番号つきで拾う
python3 - <<'PY'
import re
pages = open("lay.txt", encoding="utf-8", errors="replace").read().split("\f")
for i, page in enumerate(pages, 1):
for line in page.splitlines():
s = line.strip()
if re.match(r"^Chapter \d+\.", s):
print(f"p{i}: {s}")
PYこの2手にかかったのは 0.20 秒、出力は章見出しだけなら12行・167 トークンでした。節見出しまで拾っても252行・2,327 トークンです。
167 トークンの索引をエージェントに渡し、「この章を見たい」と決めてから StartPage と EndPage を指定する。全文を積む 137,528 トークンと比べると、入口の費用はほぼ無視できます。索引作りはモデルを介さず手元のコマンドで済む、というのがこの手順の要点です。
調整が要るのは見出しの判定条件です。上の例が Chapter 1. に合わせてあるのは検証用文書の都合で、実際のガイドライン PDF なら条番号だったり、全角の見出し行だったり、全ページに繰り返される柱だったりします。ここを自分の文書に合わせる10分は、プロンプトを練り直すより効きます。以後その文書への問い合わせは、すべてこの索引を頼りに進むためです。
解像度は、下げすぎてもエラーになりません
ここからが、測っていて手が止まったところです。
MediaResolution を下げれば安くなるのは当然として、では下限はどこか。同じ3ページを解像度別に画像化し、OCR で読み戻して元のテキストとどれだけ一致するかを見ました。単語再現率は、元テキストの語がどれだけ拾えたかの割合です。
| 解像度 | 画像サイズ | 単語再現率 | 推定トークン(3ページ) |
|---|---|---|---|
| 72 dpi | 596×842 | 65.7% | 1,548 |
| 85 dpi | 703×994 | 86.3% | 1,548 |
| 96 dpi | 794×1123 | 98.1% | 3,096 |
| 100 dpi | 827×1170 | 99.6% | 3,096 |
| 110 dpi | 910×1287 | 99.9% | 3,096 |
| 125 dpi | 1034×1462 | 100.0% | 3,096 |
72 dpi では、3分の1の語が落ちています。それでも処理は最後まで通りますし、エラーも警告も出ません。返ってくるのは「読めませんでした」ではなく、一部を読み損ねたまま組み立てられた要約です。
これが厄介なのは、出力が壊れて見えないところです。文章として成立しているので、抜けた条項に気づく手がかりが本文の側にありません。審査ガイドラインのように「書いてあること」より「書いてあったのに見落としたこと」が痛い文書では、この失敗の仕方はいちばん困ります。
安く済ませたい気持ちが先に立つ場面ほど、下限は先に測っておくべきだと思いました。しかも 72 dpi と 85 dpi は推定トークンが同じ 1,548 で、費用面では区別がつきません。安くなっていないのに精度だけ落ちている領域がある、ということです。
上げるほうにも、効かなくなる地点があります
同じ3ページを、今度は上へ振ってみました。
| 解像度 | 画像サイズ | 単語再現率 | 推定トークン(3ページ) | 変換時間 |
|---|---|---|---|---|
| 110 dpi | 910×1287 | 99.9% | 3,096 | 0.80 秒 |
| 150 dpi | 1241×1754 | 100.0% | 4,644 | 1.36 秒 |
| 300 dpi | 2481×3508 | 99.8% | 15,480 | 2.68 秒 |
300 dpi は 110 dpi の 5.0倍のトークンを使って、再現率は上がっていません。タイル方式で数える以上、解像度を2倍にすれば縦横それぞれで枚数が増えるので、費用は面積に比例して増えていきます。一方、文字が判読できるようになった後の情報量は増えません。
テキスト抽出できる PDF なら、3ページで 1,781 トークンです。300 dpi の画像で読ませる 15,480 トークンは、その 8.7倍にあたります。スキャン画像や図版が本体の文書でなければ、そもそも画像で読ませる理由がない、という当たり前の結論に戻ってきました。
もう一点、125 dpi と 150 dpi の間の段差は連続ではありません。96 dpi から 125 dpi までは、どれもタイルの枚数が同じ範囲に収まるため、費用はすべて 3,096 トークンで並びます。境界をまたぐと1列分のタイルが増え、そこで一段上がります。つまり狙うべきは「耐えられる下限」ではなく、いまのタイル枚数に収まる中でいちばん高い解像度です。同じ費用なら 96 dpi より 125 dpi を選ぶほうが、再現率が3ポイント分だけ得になります。
なお、この「どの形式で渡すと単価がいくらになるか」という話は添付ファイル全般に共通していて、CSV や JSON でも同じ構造の無駄が出ます。表形式のデータで同じ測り方をした記録を売上CSVを添付したら、いちばん高くついていたのは日付の列でしたに置いてあります。
いま私が置いている手順
測った結果を、そのまま運用の順番にしました。
- テキストが抽出できる PDF かどうかを最初に確かめる(
pdftotextが空を返すならスキャン文書と判断する) - 抽出できるなら画像化しない。索引を手元で作り、章単位で
StartPage・EndPageを指定する - 画像で読ませるしかない場合は、100〜125 dpi 相当を初期値にする。これ以上は費用だけが増える
- 下げる方向に振るときは、必ず数ページで再現率を確かめてから全体に適用する
3番目は、環境によって変わる可能性がある値です。私は断言できない箇所をそのまま書いておくほうを選びます。フォントサイズの小さい文書や、日本語の細い字体を含む文書では下限が上振れするはずなので、私自身も自分の文書で測り直すつもりでいます。この記事の数字は「そのまま使う設定値」ではなく、「測り方の型」として受け取っていただくのが正確だと思います。
範囲指定が入ったと聞いて最初に手を伸ばすのは StartPage だと思います。ただ、実際に費用と精度を左右していたのは解像度のほうでした。次に長い PDF をエージェントに渡すとき、まず3ページだけを2つの解像度で読ませて、返ってきた内容を突き合わせてみてください。自分の文書における下限が、10分ほどで分かります。
最後までお読みいただきありがとうございました。同じ測り方が、どなたかの待ち時間を少し短くできれば嬉しく思います。