Claude Codeの初回入力をHARで分解するPython
結論から言うと、AIコーディングエージェントのコストを下げたいなら、モデルを替える前に初回リクエストを一度分解したほうが早いです。大きいのはたいてい会話そのものではなく、ツール定義、リポジトリ指示、起動時に足されるコンテキストです。
7月12日に公開されたSystimaの比較では、同じ短い指示でも Claude Code はおよそ33,000トークン、OpenCode はおよそ6,900トークンを本題前に送っていました。これは特定バージョン・特定構成での測定値です。それでも、入力を観測せずに「このエージェントは高い」と決めるのは雑すぎる、という示唆にはなると思います。比較の条件と生ログの説明も読みましたが、実務では自分のMCPと指示ファイルを載せた状態で見るのが先です。
自分は設定を足した直後、応答が遅くなった理由をモデル側だと思っていました。HARを開くと、増えていたのはユーザーの依頼ではなくツールスキーマでした。ここを見ないと、プロンプトを数十文字削って満足してしまうんだよね。
HARでは「バイト」と「課金トークン」を分けて見る
HARに残る送信JSONからは、system、tools、messages のサイズを正確に数えられます。ただし、JSONのバイト数をトークン数に換算してはいけません。日本語やスキーマの記法で比率が変わるからです。
トークン数は応答側の usage を使います。Anthropic系なら input_tokens、OpenAI互換なら prompt_tokens が入ることがあります。下のスクリプトは、送信側を部品別のバイト数、応答側を実際に返ったusageとして同じ行に並べます。本文や認証情報は出力しません。
ここで見たいのは比較対象の名前ではなく、最初の1リクエストが何で埋まっているかです。
PythonでHARの入力を分解する
ローカルのプロキシやゲートウェイから、リクエスト・レスポンス本文を含むHARを保存した前提です。共有用HARにはAPIキー、Cookie、ユーザー入力を残さないでください。このスクリプトはPython 3.9で、外部パッケージなしで動きます。
import argparse
import json
from pathlib import Path
def byte_len(value):
text = json.dumps(value, ensure_ascii=False, separators=(",", ":"))
return len(text.encode("utf-8"))
def usage_of(entry):
text = entry.get("response", {}).get("content", {}).get("text", "")
try:
usage = json.loads(text).get("usage", {})
except (TypeError, ValueError):
return {}
return {
"input": usage.get("input_tokens", usage.get("prompt_tokens", "-")),
"cache_write": usage.get("cache_creation_input_tokens", 0),
"cache_read": usage.get("cache_read_input_tokens", 0),
}
def rows(har, needle):
entries = har.get("log", {}).get("entries", [])
for n, entry in enumerate(entries, 1):
request = entry.get("request", {})
if needle and needle not in request.get("url", ""):
continue
text = request.get("postData", {}).get("text", "")
try:
payload = json.loads(text)
except (TypeError, ValueError):
continue
if not isinstance(payload, dict):
continue
yield {
"n": n,
"system": byte_len(payload.get("system", "")),
"tools": byte_len(payload.get("tools", [])),
"messages": byte_len(payload.get("messages", payload.get("input", []))),
"total": byte_len(payload),
**usage_of(entry),
}
def main():
parser = argparse.ArgumentParser()
parser.add_argument("har")
parser.add_argument("--match", default="")
args = parser.parse_args()
with Path(args.har).open(encoding="utf-8") as f:
har = json.load(f)
found = list(rows(har, args.match))
if not found:
raise SystemExit("JSONのpostDataを持つHARエントリがありません")
print("# system tools messages total input cache_write cache_read")
for row in found:
print("{n:<2} {system:>7} {tools:>6} {messages:>9} {total:>6} "
"{input:>6} {cache_write:>12} {cache_read:>11}".format(**row))
if __name__ == "__main__":
main()
たとえば agent.har のうちAnthropic APIだけを見たいなら、こう実行します。
$ python3 har_context_audit.py agent.har --match api.anthropic.com
掲載コードは、system、ツール1個、ユーザーメッセージ1個、input_tokens=42 を含む最小HARで動作確認しました。出力は次のとおりです。system などはバイト数、input 以降はAPIのusageです。
# system tools messages total input cache_write cache_read
1 25 83 52 192 42 20 0
数字が増えた場所で打ち手が変わる
tools が突出していれば、使っていないMCPを外すか、似た操作をまとめた小さいツールへ寄せる候補です。system が大きければ、常時必要なルールだけを上位に置き、詳細な手順は必要な作業で読み込む形を検討します。messages が回数と一緒に伸びるなら、長い調査を同じ会話に抱え込みすぎているかもしれません。
特に見落としやすいのが cache_write です。初回だけ大きいならキャッシュの恩恵を受けられます。一方、短い作業なのに何度も書き込みが出るなら、プレフィックスが毎回変わっている可能性があります。ツールの並び、時刻入りの指示、毎ターン追加する長いリマインダーが候補になります。
HARのバイト数だけで料金を出す用途には向きません。プロバイダのトークナイザとキャッシュ課金を無視するからです。それでも、課金トークンが急に増えたときに、どの塊を掘ればよいかはこの表でかなり絞れます。最初から完璧な観測基盤を作る必要はないです。
おわりに
エージェントの初回入力は、ツール数、指示ファイル、プラグインで簡単に膨らみます。モデルの単価だけを比べても、セッション全体の入力コストは読めません。
まず同じ小さな依頼を1回流し、tools と system のサイズ、usageの入力・キャッシュ書き込みを残す。この3点があれば、MCPを外すべきか、指示を分けるべきか、会話の切り方を変えるべきかを根拠付きで選べます。自分は次にMCPを増やすとき、このHARを差分で見るつもりです。追加した便利さが、毎ターン払う前払いに見合うかを確認したいので。