6月15日の朝、いつものようにパイプラインのログを開いて、最初に見たのはクレジットの残量でした。
前日に課金変更が発効し、headless の claude -p 実行・Agent SDK・GitHub Actions が、サブスクリプションの上限とは別枠の月次クレジットへ移りました。繰り越しはありません。前夜まで、私は自分の見積もりを信じていました。工程ごとに「だいたいこのくらい」と当たりをつけ、月の枠に収まると踏んでいたのです。
ところが2日目の朝に集計してみると、ある1工程だけが、見積もりのほぼ2倍を消費していました。
数字そのものより、なぜそこを読み違えたのかが気になりました。今日はその計測の仕組みと、結果として1工程だけサブスク枠へ戻した判断を、淡々と書き残しておきます。同じように自動運用のコストを見直している個人開発者の方の、手がかりになれば幸いです。
まず「工程」を分けないと、何も測れません
私は Dolice Labs の4サイトを自動運用しています。記事生成は1本のスクリプトに見えますが、内側はいくつかの独立した工程に分かれています。
- 題材選定(参照データを読み、既存記事との重複を避けてテーマを決める)
- 本文生成(日本語と英語の2本)
- 品質ゲート(テンプレ検出・整合性チェック)
- push と後処理(commit、ログ記録)
課金変更の前は、これらをまとめて「1本いくら」と考えていました。サブスク枠の中で動いている限り、内訳を知る必要がなかったからです。
別枠の月次クレジットに変わると、話が変わります。どの工程が重いかを知らないまま枠を使うのは、電気代の総額だけ見て、どの家電が食っているかを把握していないのと同じです。まず工程を分けて、それぞれにクレジットを当てる必要がありました。
工程別にコストを当てる、小さなラッパー
大掛かりな計測基盤は作りませんでした。claude -p を呼ぶ箇所を、薄いラッパーで包んだだけです。各呼び出しの直前に工程名を渡し、終了時に所要時間と概算コストを1行で追記します。
#!/usr/bin/env bash
# run_stage.sh — claude -p を工程名つきで実行し、消費を1行ログに残す
# 使い方: ./run_stage.sh "topic-select" "プロンプト文字列"
set -euo pipefail
STAGE="$1"
PROMPT="$2"
LOG="${HOME}/pipeline_cost/$(TZ=Asia/Tokyo date +%Y-%m-%d).tsv"
mkdir -p "$(dirname "$LOG")"
start=$(date +%s)
# --output-format json で usage を受け取り、人間向けの結果と分離する
result=$(claude -p "$PROMPT" --output-format json)
end=$(date +%s)
elapsed=$((end - start))
# usage からトークン数を取り出す(フィールド名は環境で確認しておく)
in_tok=$(echo "$result" | jq -r '.usage.input_tokens // 0')
out_tok=$(echo "$result" | jq -r '.usage.output_tokens // 0')
cache_read=$(echo "$result" | jq -r '.usage.cache_read_input_tokens // 0')
printf '%s\t%s\t%ss\t%s\t%s\t%s\n' \
"$(TZ=Asia/Tokyo date +%H:%M:%S)" "$STAGE" "$elapsed" \
"$in_tok" "$out_tok" "$cache_read" >> "$LOG"
# 本来の結果(result フィールド)だけを後段へ渡す
echo "$result" | jq -r '.result'ポイントは2つあります。
1つは --output-format json を使い、生成結果と usage を分けて取り出すことです。これがないと、トークン数を後から推定するしかなくなります。
もう1つは cache_read_input_tokens を別カラムで残すことです。プロンプトキャッシュからの読み出しは課金単価が大きく違うため、入力トークンと混ぜて記録すると、後で原因を切り分けられなくなります。私が見積もりを外したのは、まさにこのキャッシュ読み出しの扱いでした。
計測そのものにも、小さな落とし穴がありました。--output-format json の usage は、工程によっては期待したフィールドが空で返ることがあります。私は最初、空の値を0として黙って足していたため、初日の集計が実態より軽く出ていました。jq -r '.usage // empty' で usage の有無を先に確かめ、欠けている行はログに「usage欠落」と明示して、平均値で補わずに別扱いにしています。総額を信じる前に、まず計測の穴を疑う——回り道に見えて、これが一番の近道でした。
2日分のログを工程別に畳む
1日分のログがたまったら、工程ごとに合計します。集計は短い Python で十分でした。
# aggregate_cost.py — 工程別にトークンを合計し、概算コストを出す
import csv, sys
from collections import defaultdict
# 単価は自分のプランの料金表で必ず確認すること(ここは概算用の例)
PRICE_IN = 3.00 / 1_000_000 # 入力 100万トークンあたり
PRICE_OUT = 15.00 / 1_000_000 # 出力 100万トークンあたり
PRICE_CACHE_READ = 0.30 / 1_000_000 # キャッシュ読み出しは桁が違う
agg = defaultdict(lambda: {"in": 0, "out": 0, "cache": 0, "runs": 0})
for path in sys.argv[1:]:
with open(path) as f:
for row in csv.reader(f, delimiter="\t"):
_, stage, _elapsed, in_tok, out_tok, cache = row
a = agg[stage]
a["in"] += int(in_tok)
a["out"] += int(out_tok)
a["cache"] += int(cache)
a["runs"] += 1
print(f"{'stage':24} {'runs':>5} {'in':>10} {'out':>10} {'cache':>12} {'$est':>8}")
for stage, a in sorted(agg.items(), key=lambda kv: -(kv[1]['in'])):
cost = a["in"]*PRICE_IN + a["out"]*PRICE_OUT + a["cache"]*PRICE_CACHE_READ
print(f"{stage:24} {a['runs']:5d} {a['in']:10d} {a['out']:10d} {a['cache']:12d} {cost:8.2f}")2日分のTSVを渡すと、工程ごとの実消費が一覧になります。これを、課金変更の前夜に立てておいた見積もりと並べて眺めました。
ズレが一番大きかったのは「題材選定」でした
意外だったのは、最も重かったのが本文生成ではなく、題材選定だったことです。
本文生成は出力トークンが多いぶん高くつく、と予想していました。実際それは見積もりどおりでした。読み違えていたのは入力側です。
題材選定の工程では、参照データ・既存記事のタイトル一覧・読者ペルソナを毎回読み込みます。私はこれを「キャッシュが効くから安い」と決めつけていました。ところがログを見ると、キャッシュが効いていたのは連続実行のときだけでした。4サイトを時間帯で分散させている都合上、同じ参照データを読む呼び出しの間隔が空き、キャッシュの有効期間を越えて毎回フルの入力課金が発生していたのです。
つまり、コストを押し上げていたのは出力の量ではなく、分散スケジュールとキャッシュ寿命のすれ違いでした。これは総額だけ見ていたら一生気づけなかった種類のズレです。AdMob の収益を国別・時間帯別に分解して初めてボトルネックが見えるのと、感覚としてはよく似ています。
1工程だけ、サブスク枠へ戻しました
対処は単純です。題材選定だけを headless から外し、通常のサブスクリプション枠(対話セッション内)へ戻しました。
判断の基準は、月次クレジットの解説を読んで自分なりに整理した、次の問いです。
- 完全無人で回す必要があるか → はい、なら headless(月次クレジット)に残す
- 入力が重く、キャッシュが効きにくいか → はい、ならサブスク枠へ戻す候補
- 出力が主役で、無人実行の価値が高いか → はい、なら headless に残す
- 自分が朝にまとめて確認しながら回せるか → はい、ならサブスク枠で十分
題材選定は「入力が重く、キャッシュが効きにくい」「朝にまとめて確認できる」の2つに当てはまりました。逆に本文生成と品質ゲートは、深夜帯に無人で完結させる価値が大きいため、headless に残しています。
ここで大事なのは、全部を一気に動かさないことだと考えています。1工程だけ移して、翌日のログでまた確かめる。個人開発の運用変更は、この「1つだけ変えて測る」を守るほど、後で原因を追いやすくなります。
これから数日で確かめること
2日分の実測は、あくまで初期値です。月初は枠が潤沢に見えるので、バーンレートが正しいかどうかは、もう数日ぶんのログがたまらないと判断できません。
これから確かめるのは3つです。題材選定をサブスク枠へ戻したあと、headless 側のクレジット消費が見積もりの線に乗るか。キャッシュ寿命を意識してスケジュールを少し寄せたとき、入力課金がどれだけ下がるか。そして、Anthropic 側のダッシュボードに出る消費と、自分のローカル集計がどこまで一致するか。
数字が出そろったら、また工程表を引き直すつもりです。総額ではなく工程で見る——課金体系が変わった今、この一手間が運用の効きどころになる、というのが2日目の実感です。
最後までお読みいただき、ありがとうございました。同じく月次クレジットの設計に頭を悩ませている方の、小さな足場になればうれしく思います。