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

第1話では、6つのAIエージェントをつなぎ、記事を最初から最後まで自動生成する仕組みを作りました。手動で実行すれば、ちゃんと動きました。

問題は、「手動で動く」と「勝手に動く」が全く別物だということです。

「手動では動いた」の落とし穴

初めて6つのエージェントが一通り動いたとき、正直ほっとしました。キーワードを渡すと、リサーチして、構成を作って、記事を書いて、画像を生成して、WordPressに保存する。自分が画面の前で見ている間は、完璧に動いていました。

次のステップは「自分が寝ている間に動かす」こと。macOSのスケジューラー(launchd)に登録して、毎朝5時に自動起動するよう設定しました。

翌朝のログ

登録した翌朝、ログを確認しました。

Nav パイプラインが起動しましたが、Gemini APIの認証に失敗しました。APIキーが見つかりません。

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が来たとき何もしないと、そのまま記事生成が止まってしまうことです。

リトライ設計の判断

Nav 503のみリトライします。ただし最大3回までです。それ以外のエラー(401・429など)はリトライしません。
なんで503だけ?
Nav 401はAPIキーの認証エラーなので何度リトライしても通りません。429はレート制限超過で、リトライすると状況を悪化させます。503だけが「一時的な混雑」なので、待って再試行する意味があります。

なるほど、と思いました。エラーの種類によって「リトライすべきかどうか」が違う。一律にリトライする設計は、むしろ危険です。

「無限ループ」が一番怖かった

503のリトライを実装してから、新しい不安が生まれました。

APIが永遠に返答しないケース

もしAPIが503を返し続けたら?3回リトライしても全部503だったら、その記事はスキップします。でも、もっと怖いケースがあります。APIが返答を返さないまま、ずっと待ち続けるケースです。

ネットワークの問題やAPIの仕様変更で、レスポンスが永遠に返ってこないことがあります。コードはひたすら待ち続ける。処理は止まらない。朝になっても動き続けている。そして翌朝、翌々朝も、同じ記事を生成しようとして動き続ける。

AIのAPIは従量課金です。動き続けるほどお金がかかります。

30分タイムアウト設計

何があっても30分で止まるようにして
Nav 記事1本あたりのタイムアウトを30分に設定します。記事の途中でも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が書く記事の「正確さ」について話します。