
LLMアプリのAPIコストが想定より膨らんでいる場合、原因の多くは出力トークンではなく入力トークン側にあります。ツール呼び出しの結果、アプリケーションログ、RAGで取得したチャンク——これらをそのままプロンプトに突っ込んでいると、LLMが実際に必要とする情報量の何倍ものトークンを消費することになります。
入力トークンは「設計で制御できる」という点が重要です。出力はモデルが生成する量に依存しますが、入力は渡す前に削れます。headroomはその「渡す前」に割り込む圧縮レイヤーで、ツール出力・ログ・RAGチャンク・会話履歴を60〜95%削減しつつ、元データを保持する設計になっています。既存のOpenAI SDK呼び出しに何行追加すれば動くか、という「差し込みやすさ」を軸に、実務での判断材料を整理します。
ツール出力・ログ・RAGチャンクはなぜこんなに膨らむのか
「人間が読みやすい形式のまま、LLMに渡している」——膨張の原因はほぼこれに尽きます。開発中はデバッグしやすさを優先してpretty-printやフルログを出力し、その実装がそのまま本番に流れ込む。悪意でも怠慢でもなく、よくある経緯の結果です。
膨らみやすい入力の種類を整理すると、次のようになります。
- ツール出力JSON: インデント・改行つきのpretty-print形式で渡すと、compact形式の2〜3倍のトークンになる
- アプリケーションログ: タイムスタンプ・ホスト名・ログレベルが毎行繰り返され、同一エラーが数十行続く重複が多い
- RAGチャンク: 検索でヒットした文書をそのまま連結するため、関連度の低い周辺文が大量に混入する
- 会話履歴: ターン数が増えるほど線形に膨張し、古い文脈がほぼ無価値になっても削られない
たとえばAPIのツール呼び出し結果として返ってくるJSONを考えてみてください。フィールド名・インデント・改行だけで、実際の値の2倍以上のトークンを消費することは珍しくありません。{"name":"John Doe","age":30} と書けば15トークン程度ですが、pretty-printすると改行とスペースだけで倍近くに膨れます。
ログはさらに極端です。同じFATALエラーが50行繰り返されているログファイルを丸ごと渡しても、LLMが必要としているのはエラーの種類・発生箇所・スタックトレースの骨格だけです。重複行は情報ゼロのトークン消費です。コストとレイテンシの両方に影響するため、放置するほど損失が積み上がります。
トークン単価と削減率を数字で見る
GPT-4oの入力トークン単価は$2.50/1Mトークン(2025年6月時点)です。この数字を使って、削減率が月額にどう効くかを具体的に試算してみます。
シナリオとして「1日1,000回呼び出し・平均8,000入力トークン」を置きます。月30日で計算すると、月間の入力トークン総量は2億4,000万トークンになります。これをそのまま流すと、入力コストだけで月額$600になります。
削減率60%なら月間トークンは9,600万に圧縮され、コストは$240。差分は$360です。削減率80%なら4,800万トークン・$120で、差分は$480。削減率95%まで到達すれば1,200万トークン・$30で、差分は$570になります。月$570の削減は年間$6,840です。GPT-4oより高単価のモデルを使っていれば、この差はさらに広がります。
出力トークンより入力トークンの削減が狙いやすい理由は、制御の主体が設計側にあるからです。出力はモデルが生成する量に依存し、指示で多少絞れても根本的な制御は難しい。一方、入力は「何を渡すか」を完全にコントロールできます。RAGチャンクを圧縮する、ログの重複を除去する、JSONをcompact化する——これらはすべてLLM呼び出し前の処理で完結します。
この試算はあくまで概算です。実際のワークロードでは呼び出し頻度・平均トークン数・モデルが異なります。ただ「削減率が10ポイント変わると月額がどう変わるか」の感覚を持っておくと、headroomを導入するかどうかの判断基準が具体的になります。
headroomの仕組みと差し込み方——ログ圧縮から始める最小例
headroomは入力の種類を自動判別し、6種類のアルゴリズムを使い分けます。MLルーターが入力を見てどのアルゴリズムを選ぶかを決めるため、呼び出し側は基本的に compress() を1回呼ぶだけです。
6アルゴリズムの対象と用途は次のとおりです。
- SmartCrusher: JSON向け。構造を保ちながらキーの省略・値の要約を行う
- CodeCompressor: ソースコード向け。ASTレベルで解析し、コメント・空白・冗長な宣言を除去する
- ログ重複除去: 同一パターンの繰り返し行を集約し、発生回数だけ残す
- 検索結果ランキング: RAGチャンクの関連度スコアに基づき、低スコアのチャンクを削除または要約する
- ModernBERT: 自然言語テキスト向け。意味的に重要な文を保持し、冗長な文を除去する
- gitdiffハンク保持: diffファイル向け。変更のあるハンクを優先し、コンテキスト行を削減する
重要な設計思想として「可逆圧縮(CCR: Compressed Context Representation)」があります。headroomは元データを捨てずに保持し、モデルが詳細を必要とする場合に展開できる構造になっています。これは「圧縮したら情報が消える」という懸念への答えで、精度維持の根拠になっています。公式のデモでは10,144トークンのログを1,260トークンに圧縮しつつ、同じFATALエラーを検出できることを示しています。
Pythonライブラリとしての最小コード例を見てみます。インストールは pip install headroom で完了します。
import headroom
# 圧縮前: 大量のアプリケーションログをそのまま渡す
raw_log = """
2025-06-01 10:23:01 ERROR server-01 [app.py:142] Database connection failed: timeout
2025-06-01 10:23:02 ERROR server-01 [app.py:142] Database connection failed: timeout
2025-06-01 10:23:03 ERROR server-01 [app.py:142] Database connection failed: timeout
2025-06-01 10:23:04 ERROR server-01 [app.py:142] Database connection failed: timeout
2025-06-01 10:23:05 ERROR server-01 [app.py:142] Database connection failed: timeout
2025-06-01 10:23:06 INFO server-01 [app.py:200] Retrying connection (attempt 1/3)
2025-06-01 10:23:09 INFO server-01 [app.py:200] Retrying connection (attempt 2/3)
2025-06-01 10:23:12 FATAL server-01 [app.py:210] Max retries exceeded. Shutting down.
"""
# 圧縮後: 1行で差し込める
compressed = headroom.compress(raw_log)
# LLM呼び出しには圧縮済みのテキストを渡す
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "以下のログを分析し、根本原因を特定してください。"},
{"role": "user", "content": compressed}, # raw_log の代わりに compressed を渡す
]
)
変更点は headroom.compress(raw_log) の1行と、messages に渡す変数を raw_log から compressed に切り替えるだけです。既存のOpenAI SDK呼び出しへの差し込みとしては、これ以上シンプルにはなりません。
RAGパイプラインとAIエージェントへの組み込み
headroomの「差し込みやすさ」が最も活きるのは、RAGパイプラインとAIエージェントの2つのパターンです。どちらも「LLMに渡す直前」という1箇所に圧縮を挟むだけで機能します。
RAGパイプラインへの差し込み
RAGパイプラインでは、ベクトル検索でチャンクを取得した後・LLM呼び出しの前が差し込み箇所です。取得したチャンクを compress() に通してから context として組み立てます。
import headroom
from openai import OpenAI
client = OpenAI()
def answer_with_rag(query: str, retrieved_chunks: list[str]) -> str:
# 取得したチャンクを結合
raw_context = "\n\n".join(retrieved_chunks)
# ここで圧縮を挟む(チャンク取得後・LLM呼び出し前)
compressed_context = headroom.compress(raw_context)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "以下のコンテキストをもとに質問に答えてください。"},
{"role": "user", "content": f"コンテキスト:\n{compressed_context}\n\n質問: {query}"},
]
)
return response.choices[0].message.content
既存のRAGパイプラインに compressed_context = headroom.compress(raw_context) の1行を追加し、渡す変数を切り替えるだけです。ベクトルDBの選択・チャンク分割の戦略・埋め込みモデルの変更は不要です。
AIエージェントのツール出力圧縮
AIエージェントでは、ツール実行結果を受け取った直後に圧縮を挟みます。ツール呼び出しの結果はJSONやテキストで返ってくることが多く、headroomのSmartCrusherやModernBERTが自動的に適切なアルゴリズムを選びます。
import headroom
import json
def run_tool_and_compress(tool_name: str, tool_args: dict) -> str:
# ツール実行(例: 外部APIコール・DBクエリ・ファイル読み込みなど)
raw_result = execute_tool(tool_name, tool_args)
# ツール出力をJSON文字列に変換して圧縮
if isinstance(raw_result, dict):
raw_text = json.dumps(raw_result) # compact形式で渡す
else:
raw_text = str(raw_result)
# 圧縮してエージェントのコンテキストに追加
compressed_result = headroom.compress(raw_text)
return compressed_result
# エージェントループ内での使用例
tool_output = run_tool_and_compress("search_database", {"query": "recent errors"})
messages.append({"role": "tool", "content": tool_output})
プロキシモードとMCPサーバーモード
Pythonコードを直接変更できない場合や、複数のエージェント・ツールにまとめて適用したい場合は、プロキシモードかMCPサーバーモードが選択肢になります。
プロキシモードはheadroomをOpenAI互換のHTTPプロキシとして起動し、既存のコードのエンドポイントURLを書き換えるだけで動作します。アプリケーションコードへの変更はゼロで、すべてのリクエストを透過的に圧縮できます。チームの複数プロジェクトにまとめて適用したい場合や、コードを触れないレガシーシステムに差し込む場合に有効です。
MCPサーバーモードはClaude DesktopやCursorなどのMCP対応クライアントと組み合わせて使います。headroomをMCPサーバーとして起動しておくと、エージェントが読み込むコンテキストを自動的に圧縮します。既存のエージェント設定ファイル(CLAUDE.mdやAGENTS.md)を変更せずに圧縮を適用できるため、Claude Codeなどのコーディングエージェントを使っているチームには特に差し込みやすいモードです。
どのモードを選ぶかは、既存コードへの変更コストと適用範囲のトレードオフです。新規開発ならPythonライブラリとして直接組み込むのが最も透明性が高く、デバッグもしやすい。既存システムへの後付けならプロキシモードが現実的です。
圧縮が裏目に出るケースと導入前の確認
headroomは「可逆」を謳っていますが、圧縮後の表現がモデルの推論に影響するリスクはゼロではありません。圧縮アルゴリズムが「重要でない」と判断した情報が、実はそのタスクでは重要だったというケースは起こりえます。これは正直に認識しておく必要があります。
特に注意が必要なのは、入力の「密度」がもともと高いケースです。すでに最小化されたJSONや、1行1行が独立した意味を持つ設定ファイルを圧縮しようとすると、削減率は低いうえに情報の欠落リスクが上がります。圧縮の恩恵が大きいのは「冗長性が高い入力」であり、すでに整理された入力には向きません。
圧縮が裏目に出やすいケースをまとめると、次のようになります。
- 数値精度が命のデータ: 金融計算・センサーデータ・座標値など、数値の微細な差異が意味を持つ入力。要約・省略が致命的になる
- 構造が意味を持つ短いJSON: フィールド数が少なく、すべてのキーと値が必要なスキーマ定義や設定オブジェクト
- すでに最適化済みの入力: compact JSON・minified CSS・圧縮済みテキストなど、冗長性がほぼない入力
- 順序・位置が意味を持つデータ: CSVの列順・プロトコルのバイト列・差分の行番号など、並び順が情報の一部になっているケース
本番導入前にやるべき確認は、タスク別の出力比較です。圧縮前後で同じプロンプトを流し、LLMの回答が変わらないかを確認します。特に「正解が一意に定まるタスク」(分類・抽出・コード生成)で比較すると、精度への影響を定量的に評価しやすくなります。
逆に言えば、ログ分析・エラー要約・RAGベースのQ&Aのように「大量の冗長テキストから要点を抽出する」タスクは、headroomの恩恵が最も出やすい領域です。入力の冗長性が高く、LLMが必要とする情報が入力全体のごく一部であるほど、圧縮の効果は大きくなります。
導入の判断基準をシンプルに言えば、「入力の大半が繰り返し・重複・フォーマット上の冗長性で構成されているか」です。そうであれば試す価値があります。そうでなければ、まず入力設計の見直しの方が先です。
株式会社ホコサキは山口県宇部市を拠点に、Web制作・業務システム開発・AI活用支援・DX推進に取り組んでいます。LLMアプリのコスト最適化や既存システムへのAI組み込みについて相談したい場合は、お問い合わせページからご連絡ください。

