「とりあえずリポジトリ全体をAIに貼り付けよう!」と思ったことはありませんか?🤔
実はこれ、かなり危険な操作なんですよね。コンテキストウィンドウの上限を超えると、AIは先頭のファイルを黙って切り捨てて回答します。エラーが出るならまだしも、「一部しか見ていない状態で答えが返ってくる」というのが一番怖いパターンです。
今回は「貼り付ける前にトークン数を計測して、必要なら削ぎ落とす」という実践的なアプローチをご紹介します 💡
そもそもコンテキストウィンドウって何?

イメージとしては「AIが一度に読める原稿用紙の枚数」です。GPT-4oなら128,000トークン、Claude 3.5 Sonnetなら200,000トークンといった上限があります。1トークンはざっくり英語で4文字、日本語だと1〜2文字が目安です。
ソースコードはコメントや変数名など英数字が多いので、日本語テキストよりはトークン消費が少なめです。ただ大規模なリポジトリになると、すぐに数十万トークンを超えることも珍しくありません。
| モデル名 | コンテキストウィンドウ上限 |
|---|---|
| GPT-4o | 128,000トークン |
| Claude 3.5 Sonnet | 200,000トークン |
| Gemini 1.5 Pro | 1,000,000トークン |
上限を超えても「エラー」にならないモデルが多く、気づかないまま不完全な回答を受け取ってしまうことがあります。だからこそ、送る前に自分でトークン数を把握しておくことが大切です。
Step 1: APIを使わずにトークン数を推定する
ネットワーク通信なしでもかなり正確に推定できます。Pythonの tiktoken ライブラリを使えば、OpenAI系モデルのトークン数をローカルで計算可能です 🛠️
# pip install tiktoken
import tiktoken
import os
def estimate_tokens(directory: str, model: str = "gpt-4o") -> dict:
"""
指定ディレクトリ以下のソースファイルのトークン数を計測する
戻り値: {ファイルパス: トークン数} の辞書
"""
enc = tiktoken.encoding_for_model(model)
results = {}
# 対象とする拡張子(必要に応じて追加)
TARGET_EXTS = {".py", ".js", ".ts", ".jsx", ".tsx",
".java", ".cpp", ".c", ".h", ".md", ".txt"}
for root, dirs, files in os.walk(directory):
# .git や node_modules は除外
dirs[:] = [d for d in dirs if d not in {".git", "node_modules", "__pycache__", ".venv"}]
for fname in files:
ext = os.path.splitext(fname)[1]
if ext not in TARGET_EXTS:
continue
fpath = os.path.join(root, fname)
try:
with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
text = f.read()
tokens = enc.encode(text)
results[fpath] = len(tokens)
except Exception as e:
print(f"[SKIP] {fpath}: {e}")
return results
if __name__ == "__main__":
import sys
target_dir = sys.argv[1] if len(sys.argv) > 1 else "."
result = estimate_tokens(target_dir)
total = sum(result.values())
print(f"\n{'ファイル':<50} {'トークン数':>10}")
print("-" * 62)
for path, count in sorted(result.items(), key=lambda x: -x[1]):
print(f"{path:<50} {count:>10,}")
print("-" * 62)
print(f"{'合計':<50} {total:>10,}")
実行例(カレントディレクトリを計測する場合):
python estimate_tokens.py ./my_project
出力はトークン数の多い順にソートされるので、「どのファイルが一番重いか」が一目でわかります。
Step 2: 上限に対して何%使っているか確認する
合計トークン数がわかったら、使いたいモデルのコンテキストウィンドウに対して何%を消費しているか計算してみましょう。
MODEL_LIMITS = {
"gpt-4o": 128_000,
"gpt-4o-mini": 128_000,
"claude-3-5-sonnet": 200_000,
"gemini-1-5-pro": 1_000_000,
}
def check_usage(total_tokens: int, model: str = "gpt-4o") -> None:
limit = MODEL_LIMITS.get(model, 128_000)
usage_pct = total_tokens / limit * 100
print(f"\n=== トークン使用状況 ===")
print(f"モデル : {model}")
print(f"合計トークン数 : {total_tokens:,}")
print(f"上限 : {limit:,}")
print(f"使用率 : {usage_pct:.1f}%")
if usage_pct >= 100:
print("⚠️ 上限超過! トリミングが必要です")
elif usage_pct >= 80:
print("🔶 80%以上。プロンプト文を加えるとギリギリになる可能性があります")
else:
print("✅ 余裕あり")
# 使い方
result = estimate_tokens("./my_project")
total = sum(result.values())
check_usage(total, model="gpt-4o")
⚠️ 注意:コンテキストウィンドウにはプロンプト文(質問文)やシステムメッセージも含まれます。実際にはコードベース以外で数百〜数千トークン消費するので、余裕を持って70〜80%以内に収めるのがベストです。
Step 3: 優先度でファイルをトリミングする
上限を超えそうなら、重要度の低いファイルを除外してから貼り付けましょう。以下のスクリプトは「指定したトークン上限に収まるように、ファイルを優先度順に選択する」ものです。
def trim_to_limit(
token_map: dict,
max_tokens: int,
priority_keywords: list = None
) -> list:
"""
max_tokens に収まるよう、優先度の高いファイルを選んで返す
priority_keywords: ファイルパスにこの文字列が含まれると優先される
例: ["main", "core", "service", "model"]
"""
if priority_keywords is None:
priority_keywords = ["main", "core", "service", "model", "util"]
def priority_score(path: str) -> int:
"""パスに優先キーワードが含まれるほどスコアが高い"""
path_lower = path.lower()
return sum(1 for kw in priority_keywords if kw in path_lower)
# (優先スコア降順, トークン数昇順) でソート
sorted_files = sorted(
token_map.items(),
key=lambda x: (-priority_score(x[0]), x[1])
)
selected = []
used = 0
skipped = []
for path, count in sorted_files:
if used + count <= max_tokens:
selected.append(path)
used += count
else:
skipped.append((path, count))
print(f"\n✅ 選択: {len(selected)}ファイル / {used:,}トークン")
print(f"❌ 除外: {len(skipped)}ファイル")
if skipped:
print(" 除外されたファイル(上位5件):")
for p, c in skipped[:5]:
print(f" {p} ({c:,} tokens)")
return selected
# 使い方例
result = estimate_tokens("./my_project")
selected_files = trim_to_limit(
result,
max_tokens=90_000, # 上限の約70%を目安に
priority_keywords=["main", "service", "model", "api"]
)
Step 4: 選択したファイルを1つのテキストにまとめる
選択されたファイル一覧ができたら、AIに渡す用のテキストをまとめて生成しましょう。
def bundle_files(file_paths: list, base_dir: str = ".") -> str:
"""
ファイル一覧を読み込んで、AIに渡しやすい形式の文字列にまとめる
"""
chunks = []
for path in file_paths:
rel_path = os.path.relpath(path, base_dir)
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
chunks.append(f"### {rel_path}\n```\n{content}\n```")
except Exception as e:
chunks.append(f"### {rel_path}\n[読み込みエラー: {e}]")
return "\n\n".join(chunks)
# 使い方例
bundled = bundle_files(selected_files, base_dir="./my_project")
# クリップボードにコピーしたい場合(macOS)
# import subprocess
# subprocess.run("pbcopy", input=bundled.encode("utf-8"))
print(bundled[:500]) # 先頭500文字だけプレビュー
出力されたテキストをそのままAIのチャット画面に貼り付ければOKです。ファイル名がヘッダーとして付くので、AIも「どのファイルのコードか」を把握しやすくなります。
全部まとめた完全版スクリプト
ここまでの処理を1ファイルにまとめたものがこちらです。コマンドライン引数でディレクトリ・モデル・上限を指定できます。
# token_trimmer.py
# 使い方: python token_trimmer.py ./my_project --model gpt-4o --limit 90000
import os
import sys
import argparse
import tiktoken
MODEL_LIMITS = {
"gpt-4o": 128_000,
"gpt-4o-mini": 128_000,
"claude-3-5-sonnet": 200_000,
}
TARGET_EXTS = {".py", ".js", ".ts", ".jsx", ".tsx",
".java", ".cpp", ".c", ".h", ".md", ".txt"}
EXCLUDE_DIRS = {".git", "node_modules", "__pycache__", ".venv", "dist", "build"}
def estimate_tokens(directory: str, model: str = "gpt-4o") -> dict:
enc = tiktoken.encoding_for_model(model)
results = {}
for root, dirs, files in os.walk(directory):
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
for fname in files:
if os.path.splitext(fname)[1] not in TARGET_EXTS:
continue
fpath = os.path.join(root, fname)
try:
with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
text = f.read()
results[fpath] = len(enc.encode(text))
except Exception:
pass
return results
def trim_to_limit(token_map: dict, max_tokens: int,
priority_keywords: list = None) -> list:
if priority_keywords is None:
priority_keywords = ["main", "core", "service", "model", "api", "util"]
def score(path):
p = path.lower()
return sum(1 for kw in priority_keywords if kw in p)
sorted_files = sorted(token_map.items(), key=lambda x: (-score(x[0]), x[1]))
selected, used = [], 0
for path, count in sorted_files:
if used + count <= max_tokens:
selected.append(path)
used += count
return selected, used
def bundle_files(file_paths: list, base_dir: str = ".") -> str:
chunks = []
for path in file_paths:
rel = os.path.relpath(path, base_dir)
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
chunks.append(f"### {rel}\n```\n{content}\n```")
except Exception as e:
chunks.append(f"### {rel}\n[Error: {e}]")
return "\n\n".join(chunks)
def main():
parser = argparse.ArgumentParser(description="コードベースのトークン数を計測してトリミングする")
parser.add_argument("directory", help="対象ディレクトリ")
parser.add_argument("--model", default="gpt-4o", choices=list(MODEL_LIMITS.keys()))
parser.add_argument("--limit", type=int, default=None, help="使用トークン上限(省略時はモデル上限の70%)")
parser.add_argument("--output", default="bundled_code.txt", help="出力ファイル名")
args = parser.parse_args()
model_limit = MODEL_LIMITS[args.model]
max_tokens = args.limit or int(model_limit * 0.7)
print(f"📁 対象: {args.directory}")
print(f"🤖 モデル: {args.model} (上限: {model_limit:,} tokens)")
print(f"🎯 使用上限: {max_tokens:,} tokens")
print()
print("⏳ トークン数を計測中...")
token_map = estimate_tokens(args.directory, args.model)
total = sum(token_map.values())
print(f" 合計: {total:,} tokens / {len(token_map)} ファイル")
selected, used = trim_to_limit(token_map, max_tokens)
print(f"\n✅ 選択: {len(selected)} ファイル / {used:,} tokens")
print(f"❌ 除外: {len(token_map) - len(selected)} ファイル")
bundled = bundle_files(selected, base_dir=args.directory)
with open(args.output, "w", encoding="utf-8") as f:
f.write(bundled)
print(f"\n💾 出力完了: {args.output}")
if __name__ == "__main__":
main()
実行するとこんな感じの出力になります:
📁 対象: ./my_project
🤖 モデル: gpt-4o (上限: 128,000 tokens)
🎯 使用上限: 89,600 tokens
⏳ トークン数を計測中...
合計: 142,380 tokens / 87 ファイル
✅ 選択: 61 ファイル / 88,942 tokens
❌ 除外: 26 ファイル
💾 出力完了: bundled_code.txt
Claude・Geminiに使うには?(tiktoken以外の選択肢)
tiktoken はOpenAI公式のライブラリなので、他のモデルでは厳密には異なるトークナイザーが使われています。とはいえ、英数字主体のソースコードであれば誤差は5〜10%程度なので、上限に対して70〜80%を目安にしておけば実用上は問題ありません。
より正確に計測したい場合は以下の方法もあります:
- Anthropic(Claude):APIの
usageフィールドで実際のトークン数を確認できます - Google(Gemini):
generativelanguageAPIのcountTokensエンドポイントを利用できます - 汎用的な推定:文字数 ÷ 3(日本語)または 文字数 ÷ 4(英語)でざっくり計算するのも手軽です
まとめ:「見えない切り捨て」を防ぐために
今回のポイントをまとめます。
- コンテキストウィンドウを超えると、AIはエラーを出さずに静かに先頭を切り捨てる
tiktokenを使えばローカルでトークン数を事前計測できる- 上限の70〜80%を目安にして、プロンプト文の分も余裕を残す
- 優先度キーワードを使って、重要なファイルを自動選択できる
- まとめたテキストをAIに渡すことで、確実に全体を見てもらえる
「なんかAIの回答がズレてるな…」と感じたら、まずトークン数を疑ってみてください。貼り付け前の一手間が、AIコーディングの質を大きく変えてくれますよ 🚀





