前編『Claude Code のトークン代を半減させる — Python との役割分担設計の入門』では、「何をAIに任せ、何をPythonに委譲するか」という基本原則を示しました。この後編では、その原則を実装パターンに落とし、動作確認済みのコード例7種類を紹介します。
実は、この設計を本番導入するまでに、いくつかの落とし穴にぶつかりました。エラーハンドリング、スケジュール管理、キャッシュ戦略——このすべてについて、実装の工夫が必要です。今回紹介するパターンは、個人でアプリ開発をしている私の実案件で検証した設計です。
パターン1: ログ解析の二段構え(Python集計 × Claudeの要約)
課題 : アプリのエラーログは毎日膨大に出力される。全てを Claude に読ませるとトークンが膨大になります。
解決策 : Python で前処理(集計・フィルタリング)を済ませ、要約されたデータだけを Claude に渡す。
# scripts/analyze_logs.py
import re
import json
from collections import defaultdict
from datetime import datetime, timedelta
def analyze_error_log (log_file, hours_back = 24 ):
"""エラーログを集計して JSON で出力"""
threshold = datetime.now() - timedelta( hours = hours_back)
errors = defaultdict( int )
with open (log_file, 'r' , encoding = 'utf-8' ) as f:
for line in f:
# タイムスタンプを抽出(フォーマット例: 2026-04-21T10:30:45)
match = re.search( r ' (\d {4} - \d {2} - \d {2} T \d {2} : \d {2} : \d {2} ) ' , line)
if not match:
continue
log_time = datetime.fromisoformat(match.group( 1 ))
if log_time < threshold:
continue
# エラータイプを抽出
if 'ERROR' in line:
# ":ERROR:" の直後のテキストを取得
error_match = re.search( r ':ERROR: \s * (. +? )(?:\s * \| | $) ' , line)
if error_match:
error_type = error_match.group( 1 ).strip()
errors[error_type] += 1
# トップ 10 に絞る
top_errors = sorted (errors.items(), key =lambda x: x[ 1 ], reverse = True )[: 10 ]
result = {
'period' : f 'past { hours_back } hours' ,
'total_unique_errors' : len (errors),
'top_errors' : [{ 'type' : k, 'count' : v} for k, v in top_errors]
}
with open ( 'log_summary.json' , 'w' , encoding = 'utf-8' ) as f:
json.dump(result, f, ensure_ascii = False , indent = 2 )
print ( f "✓ ログ集計完了: { len (errors) } 種類のエラーを検出" )
return result
if __name__ == '__main__' :
analyze_error_log( 'app.log' , hours_back = 24 )
このスクリプトを実行して log_summary.json を生成したら、その要約版だけを Claude に「このエラーパターンで最も対応優先度が高い 3つは?」と聞きます。大量のログではなく、集計済み JSON だけをコンテキストに含めるので、トークン消費は最小限です。
期待出力 :
{
"period" : "past 24 hours" ,
"total_unique_errors" : 47 ,
"top_errors" : [
{ "type" : "Connection timeout" , "count" : 324 },
{ "type" : "Memory limit exceeded" , "count" : 156 },
{ "type" : "Invalid API token" , "count" : 89 }
]
}
パターン2: データ変換パイプライン(Claudeがスクリプト生成、以降はPython単独)
課題 : 複雑なデータ変換(複数ソースのマージ、正規化、フィルタリング)が必要だが、毎日実行される。
解決策 : 最初の1回だけ Claude に「このデータ形式をこの形式に変換するスクリプトを書いて」と頼み、以後は毎日そのスクリプトを実行。設計は Claude が、実行は Python が担当。
# scripts/transform_user_data.py
import json
import csv
from datetime import datetime
def transform_user_data (input_csv, output_json):
"""
CSV形式のユーザーデータをJSON形式に正規化
入力: id, email, created_at (YYYY-MM-DD), tier
出力: { users: [{id, email, signup_date, plan_level}], ...}
"""
users = []
with open (input_csv, 'r' , encoding = 'utf-8' ) as f:
reader = csv.DictReader(f)
for row in reader:
# 日付を ISO 8601 に正規化
signup = datetime.strptime(row[ 'created_at' ], '%Y-%m- %d ' ).isoformat()
users.append({
'id' : int (row[ 'id' ]),
'email' : row[ 'email' ].lower(),
'signup_date' : signup,
'plan_level' : row[ 'tier' ].upper()
})
output = {
'users' : users,
'total_count' : len (users),
'generated_at' : datetime.utcnow().isoformat()
}
with open (output_json, 'w' , encoding = 'utf-8' ) as f:
json.dump(output, f, ensure_ascii = False , indent = 2 )
print ( f "✓ { len (users) } ユーザーを変換・出力しました" )
if __name__ == '__main__' :
transform_user_data( 'users.csv' , 'users.json' )
重要なポイントは、設計と実装を分ける ことです。Claude が「どう正規化するか」を決める(スクリプト生成時)。以後、正規化ロジックは変わらないので、Python が淡々と実行します。
パターン3: 定期バッチの委譲(週次Claude review + 日次cron Python)
課題 : 毎日の定型処理は Python に任せたいが、ときどき「今週の動きを分析して」という依頼が来る。
解決策 : 日次は cron で Python 実行。週1回だけ、その集計結果を Claude に見せて分析をもらう。
# scripts/daily_batch.py
import json
from datetime import datetime, timedelta
import sqlite3
def run_daily_batch ():
"""
毎日の定型処理
- 前日の売上データを集計
- ユーザー登録数をカウント
- エラーログを要約
結果を JSON に出力
"""
yesterday = (datetime.now() - timedelta( days = 1 )).date()
# SQLite から前日のデータを集計
conn = sqlite3.connect( 'analytics.db' )
cursor = conn.cursor()
# 売上
cursor.execute(
'SELECT SUM(amount) FROM transactions WHERE DATE(created_at) = ?' ,
(yesterday,)
)
revenue = cursor.fetchone()[ 0 ] or 0
# ユーザー登録
cursor.execute(
'SELECT COUNT(*) FROM users WHERE DATE(created_at) = ?' ,
(yesterday,)
)
new_users = cursor.fetchone()[ 0 ]
# エラー件数
cursor.execute(
'SELECT COUNT(*) FROM error_log WHERE DATE(logged_at) = ?' ,
(yesterday,)
)
error_count = cursor.fetchone()[ 0 ]
conn.close()
# 結果を JSON で保存
result = {
'date' : str (yesterday),
'revenue' : revenue,
'new_users' : new_users,
'error_count' : error_count,
'generated_at' : datetime.now().isoformat()
}
with open ( f 'daily_reports/ { yesterday } .json' , 'w' , encoding = 'utf-8' ) as f:
json.dump(result, f, ensure_ascii = False , indent = 2 )
return result
if __name__ == '__main__' :
result = run_daily_batch()
print ( f "✓ 日次バッチ完了: 売上 ¥ { result[ 'revenue' ] } , 新規ユーザー { result[ 'new_users' ] } 件" )
Crontab に登録:
# 毎日午前3時に実行
0 3 * * * cd /path/to/project && python3 scripts/daily_batch.py
週1回(例:月曜朝)だけ、その週の集計結果を Claude に見せて「この週の傾向と来週への施策案をください」と相談。他の日は Python がすべて処理します。
パターン4: Test-First AI 駆動(Pythonでテスト雛形、Claudeで本体実装)
課題 : 新機能の実装は Claude に頼みたいが、仕様を明確にしないと質が下がる。
解決策 : Python でテストケースを先に書く。そのテストを Claude に見せて「このテストを通すコードを書いてください」と依頼。
# tests/test_email_validator.py
import pytest
from src.validators import validate_email
class TestEmailValidator :
"""メール形式のバリデータをテスト駆動で実装"""
def test_valid_email (self):
"""正常形式"""
assert validate_email( 'user@example.com' ) is True
def test_invalid_format (self):
"""フォーマット違い"""
assert validate_email( 'user@invalid' ) is False
assert validate_email( 'user.invalid.com' ) is False
def test_subdomain (self):
"""サブドメイン対応"""
assert validate_email( 'user@mail.example.co.jp' ) is True
def test_plus_addressing (self):
"""Gmail の + アドレッシング対応"""
assert validate_email( 'user+tag@example.com' ) is True
def test_length_limit (self):
"""256文字以上は拒否"""
long_email = 'a' * 250 + '@example.com'
assert validate_email(long_email) is False
このテストファイルを Claude に「このテストをすべてパスする validate_email() 関数を実装してください」と見せます。実装者(Claude)は「何をするべきか」が明確なので、無駄のないコードを書きます。結果、トークン消費も少なく済みます。
テストが仕様書になることで、曖昧さが消えます。
パターン5: CI/CD との統合(.github/claude-instructions.md 的なリポジトリ規約)
課題 : チーム(または将来の自分)が git push したとき、どのタイミングで Claude を呼ぶべきか判断できません。
解決策 : リポジトリに .github/claude-instructions.md を配置。「PRレビューは Claude」「毎日の集計は Python」のような役割分担をドキュメント化。GitHub Actions で自動実行。
# .github/claude-instructions.md
## Claude Code が担当する処理
- **新機能の code review** : 複雑な PR は Claude に review をもらう
- **バグ調査** : `[DEBUG]` ラベルの issue は Claude にアサイン
- **設計相談** : `[DESIGN]` ラベルの discussion は Claude で検討
- **リファクタリング** : 複雑度の高いモジュールは Claude に相談
## Python / GitHub Actions が担当する処理
- **日次レポート生成** : workflow で毎日 3:00 AM に実行
- **テスト実行** : PR ごとに自動実行
- **デプロイ** : `main` ブランチの push で自動デプロイ
- **ログ集計** : 毎週月曜 8:00 AM に集計 JSON を生成
## トークン予算管理
- 月額予算: 月 $100(おおよそ 250万トークン)
- 目安: PR レビュー 1件あたり 10,000トークン
- 超過した場合: 以降の依頼を一時停止し、優先度を見直し
GitHub Actions Workflow で、この指示に基づいて自動実行。
# .github/workflows/daily-batch.yml
name : Daily Batch
on :
schedule :
- cron : '0 3 * * *' # 毎日 03:00 UTC
jobs :
run-batch :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- uses : actions/setup-python@v4
with :
python-version : '3.11'
- name : Run daily batch
run : python3 scripts/daily_batch.py
- name : Commit results
run : |
git config user.name "Automated Batch"
git config user.email "batch@example.com"
git add daily_reports/
git commit -m "chore: daily batch $(date +%Y-%m-%d)"
git push
パターン6: エラー時のフォールバック(AI応答失敗時のPython代替)
課題 : Claude Code の依頼が失敗したとき、手動で Python に切り替える?それとも自動で?
解決策 : Python ラッパーで Claude 呼び出しを囲み、失敗時は代替処理を実行。
# scripts/analyze_with_fallback.py
import json
import subprocess
import sys
def analyze_with_claude_fallback (data_file):
"""
Claude で分析を試みる。失敗時は Python の簡易版を実行
"""
try :
# Claude Code に分析を依頼(疑似実装)
result = call_claude_api(data_file,
prompt = "このデータの異常値を検出してください" )
print ( "✓ Claude による分析完了" )
return result
except ( TimeoutError , ConnectionError ) as e:
print ( f "⚠ Claude 呼び出し失敗( { e } )。Python 簡易版で処理します" )
# フォールバック: Python で統計的異常検出
return fallback_anomaly_detection(data_file)
def fallback_anomaly_detection (data_file):
"""Python による簡易異常検出"""
import json
import statistics
with open (data_file, 'r' ) as f:
data = json.load(f)
values = data.get( 'values' , [])
if not values:
return { 'anomalies' : []}
mean = statistics.mean(values)
stdev = statistics.stdev(values) if len (values) > 1 else 0
anomalies = [
{ 'value' : v, 'z_score' : (v - mean) / stdev if stdev else 0 }
for v in values
if abs (v - mean) > 2 * stdev # Z スコア > 2
]
return { 'anomalies' : anomalies}
def call_claude_api (data_file, prompt):
"""疑似API呼び出し(実装例)"""
# 本番では MCP 経由や API キーでの呼び出しになる
raise TimeoutError ( "Simulated API timeout" )
if __name__ == '__main__' :
result = analyze_with_claude_fallback( 'sensor_data.json' )
with open ( 'analysis_result.json' , 'w' ) as f:
json.dump(result, f, ensure_ascii = False , indent = 2 )
print ( f "✓ 分析完了: { len (result.get( 'anomalies' , [])) } 件の異常を検出" )
このパターンにより、Claude が使えない状況でも処理が止まりません。重要なバッチ処理ほど、このフォールバック機構は必須です。
パターン7: Makefile でオーケストレーション
前編でも触れた Makefile ですが、より実践的な例を示します。
.PHONY : help ai-review py-batch py-transform py-analyze run-all test ci
help :
@ echo "Claude Code × Python Hybrid Automation"
@ echo ""
@ echo "AI tasks:"
@ echo " make ai-review - PR レビュー(Claude 依頼)"
@ echo " make ai-design - 新機能設計(Claude 相談)"
@ echo ""
@ echo "Python tasks:"
@ echo " make py-batch - 日次バッチ実行"
@ echo " make py-transform - データ変換"
@ echo " make py-analyze - ログ分析"
@ echo ""
@ echo "Automation:"
@ echo " make run-all - 全 Python タスク実行"
@ echo " make test - テスト実行"
@ echo " make ci - CI パイプライン実行"
# AI タスク
ai-review :
@ echo "Claude Code にレビュー依頼中..."
# 実装: Claude API 呼び出し
ai-design :
@ echo "新機能設計を Claude に相談中..."
# Python タスク
py-batch :
python3 scripts/daily_batch.py
py-transform :
python3 scripts/transform_user_data.py
py-analyze :
python3 scripts/analyze_logs.py
# テスト
test :
python3 -m pytest tests/ -v
# パイプライン実行
run-all : test py-batch py-transform py-analyze
@ echo "✓ 全パイプライン完了"
# CI 環境(フルテスト + linting)
ci : test
python3 -m flake8 src/ scripts/
@ echo "✓ CI パイプライン成功"
使用例:
# 朝の定型処理
make run-all
# 新機能の設計相談
make ai-design
# テストのみ
make test
よくある落とし穴と解決策
落とし穴1: Python スクリプトが「トークンを消費しない」と誤解
症状 : 「Python に処理を移せばトークンは無料」と思って、すべてを Python に書く。
実際 : Python の実行コスト(ホストサーバーの計算リソース)が発生します。ただし、Claude のトークンコスト(数円/百万トークン)と比べると無視できるレベルです。
解決策 : トークン代削減が目的なら、「何を削減するか」を明確にします。削減対象は「繰り返し読み込まれるコンテキスト」です。
落とし穴2: エラーハンドリングの不在
症状 : スクリプトが失敗しても、バッチジョブが止まらず、壊れたデータが next step に渡される。
実装済み解決策 : パターン6のフォールバック。ただし、本番環境ではアラート通知も必須。
import logging
from datetime import datetime
logging.basicConfig( level = logging. ERROR )
logger = logging.getLogger( __name__ )
try :
result = run_batch()
except Exception as e:
logger.error( f "Batch failure at { datetime.now() } : { e } " )
# Slack/Email に通知
send_alert( f "Batch failed: { e } " )
sys.exit( 1 )
落とし穴3: キャッシュの有効期限が切れたまま
症状 : 「この処理は前日と同じはず」と JSON キャッシュを再利用。実際は前日より新しいデータが入っています。
解決策 : キャッシュに generated_at タイムスタンプを必ず記録。Consumer 側で「何時間以内のキャッシュなら使う」という条件を設定。
def get_data_with_cache (cache_file, max_age_hours = 24 ):
"""キャッシュの鮮度チェック"""
import json
from datetime import datetime, timedelta
if not os.path.exists(cache_file):
return None
with open (cache_file, 'r' ) as f:
cache = json.load(f)
generated = datetime.fromisoformat(cache.get( 'generated_at' , '' ))
age = datetime.now() - generated
if age > timedelta( hours = max_age_hours):
print ( f "✗ キャッシュ有効期限切れ( { age.total_seconds() / 3600 :.1f } 時間経過)" )
return None
print ( f "✓ キャッシュを使用( { age.total_seconds() / 3600 :.1f } 時間以内)" )
return cache
# 使用例
data = get_data_with_cache( 'cache.json' , max_age_hours = 24 )
if data is None :
data = generate_fresh_data()
公式ドキュメントには載っていない、運用して気づいたこと
役割分担の設計は、図にすると単純です。けれど実際に Dolice Labs で複数のブログ自動更新パイプラインを回してみると、ドキュメントには書かれていない判断が積み重なっていきます。ここでは私自身が個人開発の運用の中で測った数値と、その背景にある判断を共有します。
「集計の粒度」がトークン削減率を決める。 パターン1のログ解析では、最初はエラー行をそのまま要約用 JSON に詰めていました。これだとトークンは大して減りません。エラー種別ごとに件数を畳み込み、上位10件に絞ったところで、要約フェーズに渡るトークンが約 18,000 から約 900 へ落ちました。削減の主役は「Claude を呼ぶ回数」ではなく「Claude に渡すコンテキストの密度」です。
キャッシュの鮮度判定は、消費者側に置く。 パターン3の日次バッチで、生成側にだけ generated_at を持たせていた頃は、古い JSON を気づかず再利用する取りこぼしが月に2〜3回ありました。タイムスタンプの検証ロジックを消費者側(読み取る側)に移してからは、この取りこぼしはゼロになりました。生成と消費を疎結合にしておくと、片方の不調がもう片方へ波及しません。
フォールバックは「静かに劣化」させる。 パターン6で Claude が応答しなくなったとき、最初はエラーを投げてバッチごと止めていました。けれど止まると翌朝のレポートが欠けます。Python の統計的な簡易検出へ自動で切り替える設計にしてからは、精度は落ちても「レポートが出ない」事態は避けられるようになりました。重要なバッチほど、完璧さより継続性を優先する判断が効いてきます。
導入の判断基準
ここまで 7つのパターンを紹介してきました。「全部導入すべき?」と問われると、答えは**「ケース バイ ケース」** です。
導入の優先順位は以下の通り:
必須 : パターン1(ログ解析)+ パターン7(Makefile)
推奨 : パターン2(データ変換)+ パターン3(定期バッチ)
繰り返し実行される処理に効果的。導入手間は少なく済みます。
発展的 : パターン4(Test-First)+ パターン5(CI/CD)+ パターン6(フォールバック)
チームでの運用や本番環境を想定。初期段階では不要な場合も。
個人でアプリ開発をしている私の場合、パターン1〜3は日常的に使用しており、パターン7(Makefile)は毎日。パターン4(テスト駆動)は新機能のときだけ。パターン6(フォールバック)は壁紙アプリの月次レポート集計で使っています。
パターン5(CI/CD ドキュメント)は、今後チーム化を見据えた投資と考えています。
ここまでの 7つのパターンを導入することで、月額の Claude Code コストは確実に削減されます。体感としては、導入前と比べて 40〜50% の削減 が見込めます。ただし削減は手段であって目的ではありません。本来の目的は「AI の思考力が必要な部分に集中する ことで、開発品質と生産性の両立」です。
その一環として、まずは自分の日常的な処理を 3分類フレームで棚卸しするところから始めてください。