毎朝、ニュースを読もうとして、結局読まない。そういう日が続いていた。気づいたら夕方で、今日も世界で何かが起きていたが、自分はそれを知らない。Anthropicの話も、イランの話も、ワンテンポ遅れる。だから自動化することにした。読む習慣がないなら、届く仕組みを作ればいい。
この記事にはAPIやモデル名など、技術的な単語がいくつか出てくる。自分もこれらを最初から知っていたわけではない。ほぼ全部、AIに聞きながら覚えたものだ。わからない単語が出てきても、「こういうのがあるんだな」くらいで読み飛ばしてもらえれば、話の流れは追えるはず。
何を作ったか
毎朝7時(JST)に自動でWebを検索し、AI・ビジネス・政治経済のニュースを要約してDiscordに投稿するBotだ。
技術スタックはシンプルに絞った。
- Tavily API — ニュース検索。LLM向けに最適化されていて、URLと本文をまとめて返してくれる
- Google Gemini 2.5 Flash — 要約。無料枠で十分だった
- GitHub Actions — cron実行。サーバー不要、毎朝7時に動く
- Discord Webhook — 配信先。既存のDiscordサーバーに流すだけ
- Python 3.11 — つなぎ役
設計思想は「検索はTavily、考えるのはGemini、実行はGitHub Actions」という明確な役割分担だ。ひとつのAPIに何でもやらせない。
クエリの設計が一番面白かった
4つのカテゴリを用意した。
- AIエージェント・LLM動向
- AI活用ビジネス
- 海外スモールビジネス
- 政治・経済
政治・経済だけ少し変えた。西洋メディアとロシア、中国など反対側のメディアの両方を検索して、視点の差分を並べる設計にした。同じ出来事でも、どちらのメディアが何を強調するかは全然違う。その差分自体が情報になる、という発想だ。
プロンプトはprompt.mdとして外部ファイルに切り出した。コードの中に埋め込むと、修正のたびにコードを触ることになる。プロンプトはロジックではなく「指示書」なので、別管理が正しい。
エラーとの格闘記録
最初の動作確認で、まず404が返ってきた。
モデル名をgemini-1.5-flashと書いていた。正しくはgemini-2.5-flashだった。マイナーバージョンが違うだけで404になる。Google AI Studioのモデル一覧を確認して直した。
次は429(クォータ超過)。おそらくこれもバージョン違いによって生じたエラーだった。時間をおいて再実行したら直った。
クォータ問題の最中に「OpenRouterの無料モデルで代替できないか」と試みた時期がある。いくつかのモデルを試したが、要約の質がばらつきすぎた。ニュースの要約に一貫性がないと、読む気が失せる。結局Geminiに戻った。無料枠の範囲で十分だった。
そして一番厄介だったのが、Geminiがニュースを捏造したことだ。
Tavilyが取得した本文をGeminiに渡して要約させるとき、プロンプトの指示が甘いと、Geminiは「それっぽいURL」を自分で作って貼ってくる。見た目は正常なニュース要約なのに、URLを開いたら404だった。しばらく気づかなかった。
対策として、プロンプトに厳守事項を追加した。
- URLは必ずTavilyの検索結果から引用すること
- 検索結果に存在しないURLを生成・推測してはならない
- 情報が不足している場合は「情報なし」と明記すること
これで捏造は止まった。LLMに「やってはいけないこと」を明示的に書かないと、やる。教訓というより、仕様だと思ったほうがいい。
記事の品質が上がった理由
最初のバージョンは、古い記事やまとめブログが大量に混入してきた。Tavilyで「最近のニュース」を検索しているつもりが、2年前の記事が上位に来ることがある。
Tavilyにはdaysパラメータがあって、「直近N日以内の記事だけ取得」と指定できる。が、これはtopic="news"と組み合わせないと機能しない。単体では無視される。ドキュメントに書いてあるが、読み飛ばした。
もうひとつ、include_domainsでメディアを絞り込んだ。セクションごとに信頼できるメディアのドメインをリストアップして、それ以外は取得しない設計にした。これが品質改善に一番効いた。オープンな検索は量は多いが、ノイズも多い。絞り込んだほうが、結果として読める要約になる。
現在の仕上がりは、こんな感じだ。
- 冒頭に固定文「おはようございます!執事N’s Daily Newsのお時間です。」
- 4セクションの要約(信頼メディアから厳選)
- 政治・経済は西洋/中・露の視点を並記
- 締めに、その日のニュースにちなんだ大喜利
最後の大喜利は完全に遊びだ。でも、これがないと毎朝読む気がしない。
やってみてわかったこと
APIエラーの切り分けは「モデル名 → キーの有効性 → 課金・クォータ」の順でやると早い。最初から課金の問題を疑う人が多いが、たいていモデル名のミスで終わる。
Tavilyのdaysはtopic="news"とセットで使う。これは忘れるたびに同じハマり方をするやつなので、コードにコメントを入れた。
LLMに要約させるとき、「やってはいけないこと」をプロンプトに書かないと、やる。URL捏造はその典型だ。「引用だけ使え」と書いて初めて、引用だけ使うようになる。
ドメイン絞り込みは、最初から設計に組み込んでおいたほうがいい。後から追加すると、クエリごとに設定が必要になって地味に面倒だ。
毎朝7時にDiscordへ通知が来るようになってから、ニュースを読む頻度が上がった。「読もう」という意志より、「届いているから開く」という設計のほうが、習慣として機能する。当たり前のことだけど、実際に作ってみて実感した。
ここまで読んで、「APIとかモデルとか、自分には無理だ」と思った人がいるかもしれない。正直に言うと、自分もこのBotを作る前はTavilyもGitHub Actionsも知らなかった。全部、Claudeに「こういうことがしたい」と伝えて、「じゃあこれを使いましょう」と教えてもらいながら進めた結果がこれだ。技術の知識がなくても、やりたいことが明確なら、AIが道具を選んでくれる時代になっている。