毎朝自動実行するまで——APIと格闘した話

第1話では、6つのAIエージェントをつなぎ、記事を最初から最後まで自動生成する仕組みを作りました。手動で実行すれば、ちゃんと動きました。
問題は、「手動で動く」と「勝手に動く」が全く別物だということです。
「手動では動いた」の落とし穴
初めて6つのエージェントが一通り動いたとき、正直ほっとしました。キーワードを渡すと、リサーチして、構成を作って、記事を書いて、画像を生成して、WordPressに保存する。自分が画面の前で見ている間は、完璧に動いていました。
次のステップは「自分が寝ている間に動かす」こと。macOSのスケジューラー(launchd)に登録して、毎朝5時に自動起動するよう設定しました。
翌朝のログ
登録した翌朝、ログを確認しました。
launchdはシェル環境を引き継がない
.env ファイルにAPIキーを書いていました。ターミナルから手動で実行するときは問題なく読み込まれます。でもlaunchdは別の話でした。launchdが起動するとき、自分のシェル環境(.bashrc や .zshrc で設定した変数)を引き継ぎません。だから .env が読み込まれず、APIキーが見つからない。
解決策は load_dotenv() でした。Pythonのコード側に1行書くだけで、.env ファイルを直接読みに行くようにします。launchd経由でも、Pythonが自分で .env を見つけてくれます。
from dotenv import load_dotenv
load_dotenv() # .envファイルから環境変数を読み込む
1行追加して、再度テストしました。今度は動きました。
深夜だけ503が返ってくる
環境変数の問題を解決してから数日後、また止まりました。今度はAPIが503エラーを返していました。
503エラーとは
503は「サービス一時停止」です。APIのサーバー側が一時的に過負荷になっているという意味で、少し待てば通ることが多い。問題は、503が来たとき何もしないと、そのまま記事生成が止まってしまうことです。
リトライ設計の判断
なるほど、と思いました。エラーの種類によって「リトライすべきかどうか」が違う。一律にリトライする設計は、むしろ危険です。
「無限ループ」が一番怖かった
503のリトライを実装してから、新しい不安が生まれました。
APIが永遠に返答しないケース
もしAPIが503を返し続けたら?3回リトライしても全部503だったら、その記事はスキップします。でも、もっと怖いケースがあります。APIが返答を返さないまま、ずっと待ち続けるケースです。
ネットワークの問題やAPIの仕様変更で、レスポンスが永遠に返ってこないことがあります。コードはひたすら待ち続ける。処理は止まらない。朝になっても動き続けている。そして翌朝、翌々朝も、同じ記事を生成しようとして動き続ける。
AIのAPIは従量課金です。動き続けるほどお金がかかります。
30分タイムアウト設計
PIPELINE_MAX_MINUTES = 30 # 記事1本あたりのタイムアウト上限(分)
この1行をコードの先頭に書きました。通常の記事生成は3〜4分で終わります。余裕を見て10倍の30分にしました。たとえ何かおかしなことが起きても、30分で確実に止まる。それだけで十分でした。
Nano Banana 2が1枚いくらかを計算した日
ある日、1ヶ月のAPIコストを試算しました。
テキストは安い、画像は高い
テキスト生成(リサーチ・構成・執筆・ファクトチェック)はGeminiのフラッシュモデルを使っています。1記事あたり約4円。月90記事でも360円。想定より全然安い。
問題は画像でした。記事1本につき、アイキャッチ画像1枚と、セクション図解が平均5枚。合計6枚の画像を生成しています。画像生成にはNano Banana 2(Imagen 4)を使っています。このモデルは課金必須で、無料枠がありません。
計算してみた結果
1枚あたりの料金を調べて計算しました。
- 画像生成コスト:約$0.20(約30円)/記事
- テキスト生成コスト:約$0.04(約6円)/記事
- 1記事あたり合計:約$0.24(約35円)
コストの83%が画像生成でした。テキストより画像のほうがはるかに高い。月90記事(3記事×30日)で約$21(約3,200円)。「AIで記事を書く」と聞くとテキスト生成の話だと思いがちですが、画像を入れた途端にコスト構造が変わります。
初めて5時に自動起動した朝
launchd登録、環境変数、503リトライ、30分タイムアウト、コスト把握——全部整ってから、本格的に自動実行を始めました。
初めて何も問題なく朝5時に起動して、記事が完成していた朝のことは覚えています。翌朝 /morning と打つと、Navがブリーフィングしてくれました。
昨夜5時に記事生成が完了しました。キーワード:「〇〇とは」、カテゴリ:AIエージェント、ファクトチェック:問題なし。WordPressに下書き保存済みです。
自分は何もしていません。寝ていました。でも記事ができていました。
最初はそれだけで十分でした。公開するかどうかは自分が判断します。でも「草稿ができている状態で朝を迎える」というのは、思っていたより気持ちが違いました。
第2話のまとめ
- launchdはシェルの環境変数を引き継がない。
load_dotenv()で自前ロードが必要 - APIエラーはリトライすれば良いわけではない。503だけがリトライに値する
- 無限ループの防止はコードで物理的に止める。30分タイムアウトが最もシンプルな解
- 記事生成コストの83%は画像。テキストより画像のほうが高い
- 「勝手に動く」は「手動で動く」とは全く別の問題
次回は、このシステムが毎日記事を量産していく中で、品質をどう担保したか——ファクトチェックと、AIが書く記事の「正確さ」について話します。





