LLMに渡すデータ形式でトークンは3倍変わる|JSON・YAML・CSV・XMLを実測
LLMに構造化データを渡すとき、多くのコードは何も考えずJSONを使います。json.dumps(data, indent=2) と書くのが習慣になっているからです。
しかしAPIの課金はトークン数で決まります。同じ内容でも、表現形式が違えば消費トークンは変わります。 どれくらい変わるのかを実測しました。
結論:XMLはCSVの3.05倍
商品マスタ10件(商品名・価格・在庫・カテゴリ)を7つの形式で表現し、トークン数を数えました。
日本語データ(10件)
| 形式 | 文字数 | トークン数 | CSV比 |
|---|---|---|---|
| TSV | 248 | 173 | 0.99 |
| CSV | 260 | 174 | 1.00 |
| Markdown表 | 376 | 221 | 1.27 |
| JSON(最小化) | 525 | 267 | 1.53 |
| YAML | 504 | 358 | 2.06 |
| JSON(整形・indent=2) | 826 | 426 | 2.45 |
| XML | 1,020 | 530 | 3.05 |
英語データ(10件)
| 形式 | 文字数 | トークン数 | CSV比 |
|---|---|---|---|
| TSV | 359 | 120 | 0.98 |
| CSV | 371 | 123 | 1.00 |
| Markdown表 | 487 | 163 | 1.33 |
| JSON(最小化) | 735 | 199 | 1.62 |
| YAML | 714 | 255 | 2.07 |
| JSON(整形・indent=2) | 1,036 | 358 | 2.91 |
| XML | 1,340 | 441 | 3.59 |
英語のほうが差が大きくなります。日本語は本文自体のトークン消費が重いため、形式による差が相対的に薄まるためです。
なぜここまで差が出るのか
原因はキー名の繰り返しです。
CSVはヘッダー行にキー名を1回書くだけで、以降はデータだけが並びます。一方JSONやXMLは、レコードごとに全キー名を書き直します。10件なら10回、1,000件なら1,000回です。
# CSV — キー名は1回だけ
商品名,価格,在庫,カテゴリ
ワイヤレスマウス,2980,45,周辺機器
# JSON — レコードごとに全キーを繰り返す
{
"商品名": "ワイヤレスマウス",
"価格": 2980,
"在庫": 45,
"カテゴリ": "周辺機器"
}
# XML — 開始タグと終了タグで2回ずつ書く
<item>
<商品名>ワイヤレスマウス</商品名>
<価格>2980</価格>
</item>
XMLが最も重いのは、同じキー名を開始タグと終了タグで2回書くからです。
さらに整形(indent)による空白も課金対象です。JSON整形(426トークン)とJSON最小化(267トークン)の差159トークンは、そのほとんどが改行とインデントの空白です。人間が読むために入れた空白に、そのまま課金されています。
実際にいくら変わるか
商品マスタ1,000件を毎日1回、Claude Sonnet 5(入力$2.00 / 100万トークン)に投げる場合を試算します。上の測定値から1件あたりのトークン数を求めて1,000倍しました。
| 形式 | 1,000件のトークン | 月額(30日・$1=150円) |
|---|---|---|
| CSV | 約17,400 | 約157円 |
| JSON(最小化) | 約26,700 | 約240円 |
| JSON(整形) | 約42,600 | 約383円 |
| XML | 約53,000 | 約477円 |
金額そのものは小さく見えます。しかしこれは1リクエストあたりの話です。同じデータを毎回コンテキストに載せるRAGやエージェントでは、リクエスト回数だけ倍率がかかります。1日1万リクエストなら、CSVとXMLの差は月4万円を超えます。
では常にCSVにすべきか
いいえ。CSVには表現できないデータがあります。
| 形式 | 向いているデータ | 注意点 |
|---|---|---|
| CSV / TSV | フラットな表形式 | ネスト構造・配列を表現できない。値に区切り文字が含まれるとエスケープが必要 |
| Markdown表 | フラットな表形式 | CSVより27%重いが、モデルが表として認識しやすい |
| JSON(最小化) | ネスト構造・可変スキーマ | 人間が読みにくい。デバッグしづらい |
| JSON(整形) | 人間が確認する必要がある場面 | 最小化に比べ約1.6倍。LLMに渡す用途では整形する理由がない |
| XML | 既存システムがXMLで固定されている場合 | 最も重い。他に選択肢があるなら避ける |
現実的な指針はこうなります。
- フラットな表データなら、CSVかTSVを使う — 最も軽い
- ネストが必要ならJSONを使う。ただし
indentを外す — これだけで約4割減る - XMLは、外部要件で強制される場合を除いて選ばない
とくに2番は、既存コードの json.dumps(data, indent=2) から indent を消すだけで済みます。1行の変更でトークンが4割減ります。
重要な未検証事項
本記事が測ったのはトークン数だけです。形式によってモデルの回答精度が変わるかどうかは検証していません。
トークンが減っても、モデルがデータを正しく解釈できなければ意味がありません。とくに次の点は本記事では確かめていないため、実運用では自分のタスクで検証してください。
- CSVとJSONで、モデルが列の対応を取り違える頻度に差があるか
- 値が欠損している場合の扱いに、形式による差があるか
- ネストのない表データで、Markdown表がCSVより理解されやすいという通説が実際に成り立つか
トークン数は機械的に測れますが、精度は測り方の設計が必要です。この点は別の記事で扱います。
測定条件と再現方法
| 項目 | 内容 |
|---|---|
| 測定日時 | 2026年8月25日 12:06 JST |
| ライブラリ | tiktoken 0.14.0 |
| エンコーディング | o200k_base(GPT-4o以降) |
| データ | 商品マスタ10件(商品名・価格・在庫・カテゴリの4列) |
測定に使ったコードの中核部分です。
import csv, io, json, yaml, tiktoken
ROWS = [
{"商品名": "ワイヤレスマウス", "価格": 2980, "在庫": 45, "カテゴリ": "周辺機器"},
# ... 全10件
]
def as_csv(rows):
buf = io.StringIO()
w = csv.DictWriter(buf, fieldnames=list(rows[0].keys()))
w.writeheader(); w.writerows(rows)
return buf.getvalue()
enc = tiktoken.get_encoding("o200k_base")
for name, text in [
("JSON (整形)", json.dumps(ROWS, ensure_ascii=False, indent=2)),
("JSON (最小化)", json.dumps(ROWS, ensure_ascii=False, separators=(",", ":"))),
("YAML", yaml.dump(ROWS, allow_unicode=True, sort_keys=False)),
("CSV", as_csv(ROWS)),
]:
print(f"{name}: {len(enc.encode(text))} トークン")
注記
- 測定はOpenAIのトークナイザー(o200k_base)によるものです。Anthropic・Googleのトークナイザーでは比率が変わる可能性があります(未検証)
- 金額試算は10件の実測値から線形に外挿したものです。実データの内容によって前後します
- 料金は2026年8月25日時点の公式価格に基づきます